mirror of
				git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
				synced 2025-09-04 20:19:47 +08:00 
			
		
		
		
	clk: at91: add sam9x60 PLL driver
The PLLs on the sam9x60 (PLLA and USB PLL) use a different register set and programming model than the previous SoCs. Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com> Signed-off-by: Stephen Boyd <sboyd@kernel.org>
This commit is contained in:
		
							parent
							
								
									e5be537064
								
							
						
					
					
						commit
						a436c2a447
					
				| @ -14,6 +14,7 @@ obj-$(CONFIG_HAVE_AT91_SMD)		+= clk-smd.o | ||||
| obj-$(CONFIG_HAVE_AT91_H32MX)		+= clk-h32mx.o | ||||
| obj-$(CONFIG_HAVE_AT91_GENERATED_CLK)	+= clk-generated.o | ||||
| obj-$(CONFIG_HAVE_AT91_I2S_MUX_CLK)	+= clk-i2s-mux.o | ||||
| obj-$(CONFIG_HAVE_AT91_SAM9X60_PLL)	+= clk-sam9x60-pll.o | ||||
| obj-$(CONFIG_SOC_AT91SAM9) += at91sam9260.o at91sam9rl.o at91sam9x5.o | ||||
| obj-$(CONFIG_SOC_SAMA5D4) += sama5d4.o | ||||
| obj-$(CONFIG_SOC_SAMA5D2) += sama5d2.o | ||||
|  | ||||
							
								
								
									
										330
									
								
								drivers/clk/at91/clk-sam9x60-pll.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										330
									
								
								drivers/clk/at91/clk-sam9x60-pll.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,330 @@ | ||||
| // SPDX-License-Identifier: GPL-2.0+
 | ||||
| /*
 | ||||
|  *  Copyright (C) 2019 Microchip Technology Inc. | ||||
|  * | ||||
|  */ | ||||
| 
 | ||||
| #include <linux/bitfield.h> | ||||
| #include <linux/clk-provider.h> | ||||
| #include <linux/clkdev.h> | ||||
| #include <linux/clk/at91_pmc.h> | ||||
| #include <linux/of.h> | ||||
| #include <linux/mfd/syscon.h> | ||||
| #include <linux/regmap.h> | ||||
| 
 | ||||
| #include "pmc.h" | ||||
| 
 | ||||
| #define PMC_PLL_CTRL0	0xc | ||||
| #define		PMC_PLL_CTRL0_DIV_MSK		GENMASK(7, 0) | ||||
| #define		PMC_PLL_CTRL0_ENPLL		BIT(28) | ||||
| #define		PMC_PLL_CTRL0_ENPLLCK		BIT(29) | ||||
| #define		PMC_PLL_CTRL0_ENLOCK		BIT(31) | ||||
| 
 | ||||
| #define PMC_PLL_CTRL1	0x10 | ||||
| #define		PMC_PLL_CTRL1_FRACR_MSK		GENMASK(21, 0) | ||||
| #define		PMC_PLL_CTRL1_MUL_MSK		GENMASK(30, 24) | ||||
| 
 | ||||
| #define PMC_PLL_ACR	0x18 | ||||
| #define		PMC_PLL_ACR_DEFAULT		0x1b040010UL | ||||
| #define		PMC_PLL_ACR_UTMIVR		BIT(12) | ||||
| #define		PMC_PLL_ACR_UTMIBG		BIT(13) | ||||
| #define		PMC_PLL_ACR_LOOP_FILTER_MSK	GENMASK(31, 24) | ||||
| 
 | ||||
| #define PMC_PLL_UPDT	0x1c | ||||
| #define		PMC_PLL_UPDT_UPDATE		BIT(8) | ||||
| 
 | ||||
| #define PMC_PLL_ISR0	0xec | ||||
| 
 | ||||
| #define PLL_DIV_MAX		(FIELD_GET(PMC_PLL_CTRL0_DIV_MSK, UINT_MAX) + 1) | ||||
| #define UPLL_DIV		2 | ||||
| #define PLL_MUL_MAX		(FIELD_GET(PMC_PLL_CTRL1_MUL_MSK, UINT_MAX) + 1) | ||||
| 
 | ||||
| #define PLL_MAX_ID		1 | ||||
| 
 | ||||
| struct sam9x60_pll { | ||||
| 	struct clk_hw hw; | ||||
| 	struct regmap *regmap; | ||||
| 	spinlock_t *lock; | ||||
| 	const struct clk_pll_characteristics *characteristics; | ||||
| 	u32 frac; | ||||
| 	u8 id; | ||||
| 	u8 div; | ||||
| 	u16 mul; | ||||
| }; | ||||
| 
 | ||||
| #define to_sam9x60_pll(hw) container_of(hw, struct sam9x60_pll, hw) | ||||
| 
 | ||||
| static inline bool sam9x60_pll_ready(struct regmap *regmap, int id) | ||||
| { | ||||
| 	unsigned int status; | ||||
| 
 | ||||
| 	regmap_read(regmap, PMC_PLL_ISR0, &status); | ||||
| 
 | ||||
| 	return !!(status & BIT(id)); | ||||
| } | ||||
| 
 | ||||
| static int sam9x60_pll_prepare(struct clk_hw *hw) | ||||
| { | ||||
| 	struct sam9x60_pll *pll = to_sam9x60_pll(hw); | ||||
| 	struct regmap *regmap = pll->regmap; | ||||
| 	unsigned long flags; | ||||
| 	u8 div; | ||||
| 	u16 mul; | ||||
| 	u32 val; | ||||
| 
 | ||||
| 	spin_lock_irqsave(pll->lock, flags); | ||||
| 	regmap_write(regmap, PMC_PLL_UPDT, pll->id); | ||||
| 
 | ||||
| 	regmap_read(regmap, PMC_PLL_CTRL0, &val); | ||||
| 	div = FIELD_GET(PMC_PLL_CTRL0_DIV_MSK, val); | ||||
| 
 | ||||
| 	regmap_read(regmap, PMC_PLL_CTRL1, &val); | ||||
| 	mul = FIELD_GET(PMC_PLL_CTRL1_MUL_MSK, val); | ||||
| 
 | ||||
| 	if (sam9x60_pll_ready(regmap, pll->id) && | ||||
| 	    (div == pll->div && mul == pll->mul)) { | ||||
| 		spin_unlock_irqrestore(pll->lock, flags); | ||||
| 		return 0; | ||||
| 	} | ||||
| 
 | ||||
| 	/* Recommended value for PMC_PLL_ACR */ | ||||
| 	val = PMC_PLL_ACR_DEFAULT; | ||||
| 	regmap_write(regmap, PMC_PLL_ACR, val); | ||||
| 
 | ||||
| 	regmap_write(regmap, PMC_PLL_CTRL1, | ||||
| 		     FIELD_PREP(PMC_PLL_CTRL1_MUL_MSK, pll->mul)); | ||||
| 
 | ||||
| 	if (pll->characteristics->upll) { | ||||
| 		/* Enable the UTMI internal bandgap */ | ||||
| 		val |= PMC_PLL_ACR_UTMIBG; | ||||
| 		regmap_write(regmap, PMC_PLL_ACR, val); | ||||
| 
 | ||||
| 		udelay(10); | ||||
| 
 | ||||
| 		/* Enable the UTMI internal regulator */ | ||||
| 		val |= PMC_PLL_ACR_UTMIVR; | ||||
| 		regmap_write(regmap, PMC_PLL_ACR, val); | ||||
| 
 | ||||
| 		udelay(10); | ||||
| 	} | ||||
| 
 | ||||
| 	regmap_update_bits(regmap, PMC_PLL_UPDT, | ||||
| 			   PMC_PLL_UPDT_UPDATE, PMC_PLL_UPDT_UPDATE); | ||||
| 
 | ||||
| 	regmap_write(regmap, PMC_PLL_CTRL0, | ||||
| 		     PMC_PLL_CTRL0_ENLOCK | PMC_PLL_CTRL0_ENPLL | | ||||
| 		     PMC_PLL_CTRL0_ENPLLCK | pll->div); | ||||
| 
 | ||||
| 	regmap_update_bits(regmap, PMC_PLL_UPDT, | ||||
| 			   PMC_PLL_UPDT_UPDATE, PMC_PLL_UPDT_UPDATE); | ||||
| 
 | ||||
| 	while (!sam9x60_pll_ready(regmap, pll->id)) | ||||
| 		cpu_relax(); | ||||
| 
 | ||||
| 	spin_unlock_irqrestore(pll->lock, flags); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static int sam9x60_pll_is_prepared(struct clk_hw *hw) | ||||
| { | ||||
| 	struct sam9x60_pll *pll = to_sam9x60_pll(hw); | ||||
| 
 | ||||
| 	return sam9x60_pll_ready(pll->regmap, pll->id); | ||||
| } | ||||
| 
 | ||||
| static void sam9x60_pll_unprepare(struct clk_hw *hw) | ||||
| { | ||||
| 	struct sam9x60_pll *pll = to_sam9x60_pll(hw); | ||||
| 	unsigned long flags; | ||||
| 
 | ||||
| 	spin_lock_irqsave(pll->lock, flags); | ||||
| 
 | ||||
| 	regmap_write(pll->regmap, PMC_PLL_UPDT, pll->id); | ||||
| 
 | ||||
| 	regmap_update_bits(pll->regmap, PMC_PLL_CTRL0, | ||||
| 			   PMC_PLL_CTRL0_ENPLLCK, 0); | ||||
| 
 | ||||
| 	regmap_update_bits(pll->regmap, PMC_PLL_UPDT, | ||||
| 			   PMC_PLL_UPDT_UPDATE, PMC_PLL_UPDT_UPDATE); | ||||
| 
 | ||||
| 	regmap_update_bits(pll->regmap, PMC_PLL_CTRL0, PMC_PLL_CTRL0_ENPLL, 0); | ||||
| 
 | ||||
| 	if (pll->characteristics->upll) | ||||
| 		regmap_update_bits(pll->regmap, PMC_PLL_ACR, | ||||
| 				   PMC_PLL_ACR_UTMIBG | PMC_PLL_ACR_UTMIVR, 0); | ||||
| 
 | ||||
| 	regmap_update_bits(pll->regmap, PMC_PLL_UPDT, | ||||
| 			   PMC_PLL_UPDT_UPDATE, PMC_PLL_UPDT_UPDATE); | ||||
| 
 | ||||
| 	spin_unlock_irqrestore(pll->lock, flags); | ||||
| } | ||||
| 
 | ||||
| static unsigned long sam9x60_pll_recalc_rate(struct clk_hw *hw, | ||||
| 					     unsigned long parent_rate) | ||||
| { | ||||
| 	struct sam9x60_pll *pll = to_sam9x60_pll(hw); | ||||
| 
 | ||||
| 	return (parent_rate * (pll->mul + 1)) / (pll->div + 1); | ||||
| } | ||||
| 
 | ||||
| static long sam9x60_pll_get_best_div_mul(struct sam9x60_pll *pll, | ||||
| 					 unsigned long rate, | ||||
| 					 unsigned long parent_rate, | ||||
| 					 bool update) | ||||
| { | ||||
| 	const struct clk_pll_characteristics *characteristics = | ||||
| 							pll->characteristics; | ||||
| 	unsigned long bestremainder = ULONG_MAX; | ||||
| 	unsigned long maxdiv, mindiv, tmpdiv; | ||||
| 	long bestrate = -ERANGE; | ||||
| 	unsigned long bestdiv = 0; | ||||
| 	unsigned long bestmul = 0; | ||||
| 	unsigned long bestfrac = 0; | ||||
| 
 | ||||
| 	if (rate < characteristics->output[0].min || | ||||
| 	    rate > characteristics->output[0].max) | ||||
| 		return -ERANGE; | ||||
| 
 | ||||
| 	if (!pll->characteristics->upll) { | ||||
| 		mindiv = parent_rate / rate; | ||||
| 		if (mindiv < 2) | ||||
| 			mindiv = 2; | ||||
| 
 | ||||
| 		maxdiv = DIV_ROUND_UP(parent_rate * PLL_MUL_MAX, rate); | ||||
| 		if (maxdiv > PLL_DIV_MAX) | ||||
| 			maxdiv = PLL_DIV_MAX; | ||||
| 	} else { | ||||
| 		mindiv = maxdiv = UPLL_DIV; | ||||
| 	} | ||||
| 
 | ||||
| 	for (tmpdiv = mindiv; tmpdiv <= maxdiv; tmpdiv++) { | ||||
| 		unsigned long remainder; | ||||
| 		unsigned long tmprate; | ||||
| 		unsigned long tmpmul; | ||||
| 		unsigned long tmpfrac = 0; | ||||
| 
 | ||||
| 		/*
 | ||||
| 		 * Calculate the multiplier associated with the current | ||||
| 		 * divider that provide the closest rate to the requested one. | ||||
| 		 */ | ||||
| 		tmpmul = mult_frac(rate, tmpdiv, parent_rate); | ||||
| 		tmprate = mult_frac(parent_rate, tmpmul, tmpdiv); | ||||
| 		remainder = rate - tmprate; | ||||
| 
 | ||||
| 		if (remainder) { | ||||
| 			tmpfrac = DIV_ROUND_CLOSEST_ULL((u64)remainder * tmpdiv * (1 << 22), | ||||
| 							parent_rate); | ||||
| 
 | ||||
| 			tmprate += DIV_ROUND_CLOSEST_ULL((u64)tmpfrac * parent_rate, | ||||
| 							 tmpdiv * (1 << 22)); | ||||
| 
 | ||||
| 			if (tmprate > rate) | ||||
| 				remainder = tmprate - rate; | ||||
| 			else | ||||
| 				remainder = rate - tmprate; | ||||
| 		} | ||||
| 
 | ||||
| 		/*
 | ||||
| 		 * Compare the remainder with the best remainder found until | ||||
| 		 * now and elect a new best multiplier/divider pair if the | ||||
| 		 * current remainder is smaller than the best one. | ||||
| 		 */ | ||||
| 		if (remainder < bestremainder) { | ||||
| 			bestremainder = remainder; | ||||
| 			bestdiv = tmpdiv; | ||||
| 			bestmul = tmpmul; | ||||
| 			bestrate = tmprate; | ||||
| 			bestfrac = tmpfrac; | ||||
| 		} | ||||
| 
 | ||||
| 		/* We've found a perfect match!  */ | ||||
| 		if (!remainder) | ||||
| 			break; | ||||
| 	} | ||||
| 
 | ||||
| 	/* Check if bestrate is a valid output rate  */ | ||||
| 	if (bestrate < characteristics->output[0].min && | ||||
| 	    bestrate > characteristics->output[0].max) | ||||
| 		return -ERANGE; | ||||
| 
 | ||||
| 	if (update) { | ||||
| 		pll->div = bestdiv - 1; | ||||
| 		pll->mul = bestmul - 1; | ||||
| 		pll->frac = bestfrac; | ||||
| 	} | ||||
| 
 | ||||
| 	return bestrate; | ||||
| } | ||||
| 
 | ||||
| static long sam9x60_pll_round_rate(struct clk_hw *hw, unsigned long rate, | ||||
| 				   unsigned long *parent_rate) | ||||
| { | ||||
| 	struct sam9x60_pll *pll = to_sam9x60_pll(hw); | ||||
| 
 | ||||
| 	return sam9x60_pll_get_best_div_mul(pll, rate, *parent_rate, false); | ||||
| } | ||||
| 
 | ||||
| static int sam9x60_pll_set_rate(struct clk_hw *hw, unsigned long rate, | ||||
| 				unsigned long parent_rate) | ||||
| { | ||||
| 	struct sam9x60_pll *pll = to_sam9x60_pll(hw); | ||||
| 
 | ||||
| 	return sam9x60_pll_get_best_div_mul(pll, rate, parent_rate, true); | ||||
| } | ||||
| 
 | ||||
| static const struct clk_ops pll_ops = { | ||||
| 	.prepare = sam9x60_pll_prepare, | ||||
| 	.unprepare = sam9x60_pll_unprepare, | ||||
| 	.is_prepared = sam9x60_pll_is_prepared, | ||||
| 	.recalc_rate = sam9x60_pll_recalc_rate, | ||||
| 	.round_rate = sam9x60_pll_round_rate, | ||||
| 	.set_rate = sam9x60_pll_set_rate, | ||||
| }; | ||||
| 
 | ||||
| struct clk_hw * __init | ||||
| sam9x60_clk_register_pll(struct regmap *regmap, spinlock_t *lock, | ||||
| 			 const char *name, const char *parent_name, u8 id, | ||||
| 			 const struct clk_pll_characteristics *characteristics) | ||||
| { | ||||
| 	struct sam9x60_pll *pll; | ||||
| 	struct clk_hw *hw; | ||||
| 	struct clk_init_data init; | ||||
| 	unsigned int pllr; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	if (id > PLL_MAX_ID) | ||||
| 		return ERR_PTR(-EINVAL); | ||||
| 
 | ||||
| 	pll = kzalloc(sizeof(*pll), GFP_KERNEL); | ||||
| 	if (!pll) | ||||
| 		return ERR_PTR(-ENOMEM); | ||||
| 
 | ||||
| 	init.name = name; | ||||
| 	init.ops = &pll_ops; | ||||
| 	init.parent_names = &parent_name; | ||||
| 	init.num_parents = 1; | ||||
| 	init.flags = CLK_SET_RATE_GATE; | ||||
| 
 | ||||
| 	pll->id = id; | ||||
| 	pll->hw.init = &init; | ||||
| 	pll->characteristics = characteristics; | ||||
| 	pll->regmap = regmap; | ||||
| 	pll->lock = lock; | ||||
| 
 | ||||
| 	regmap_write(regmap, PMC_PLL_UPDT, id); | ||||
| 	regmap_read(regmap, PMC_PLL_CTRL0, &pllr); | ||||
| 	pll->div = FIELD_GET(PMC_PLL_CTRL0_DIV_MSK, pllr); | ||||
| 	regmap_read(regmap, PMC_PLL_CTRL1, &pllr); | ||||
| 	pll->mul = FIELD_GET(PMC_PLL_CTRL1_MUL_MSK, pllr); | ||||
| 
 | ||||
| 	hw = &pll->hw; | ||||
| 	ret = clk_hw_register(NULL, hw); | ||||
| 	if (ret) { | ||||
| 		kfree(pll); | ||||
| 		hw = ERR_PTR(ret); | ||||
| 	} | ||||
| 
 | ||||
| 	return hw; | ||||
| } | ||||
| 
 | ||||
| @ -69,6 +69,7 @@ struct clk_pll_characteristics { | ||||
| 	struct clk_range *output; | ||||
| 	u16 *icpll; | ||||
| 	u8 *out; | ||||
| 	u8 upll : 1; | ||||
| }; | ||||
| 
 | ||||
| struct clk_programmable_layout { | ||||
| @ -169,6 +170,11 @@ struct clk_hw * __init | ||||
| at91_clk_register_plldiv(struct regmap *regmap, const char *name, | ||||
| 			 const char *parent_name); | ||||
| 
 | ||||
| struct clk_hw * __init | ||||
| sam9x60_clk_register_pll(struct regmap *regmap, spinlock_t *lock, | ||||
| 			 const char *name, const char *parent_name, u8 id, | ||||
| 			 const struct clk_pll_characteristics *characteristics); | ||||
| 
 | ||||
| struct clk_hw * __init | ||||
| at91_clk_register_programmable(struct regmap *regmap, const char *name, | ||||
| 			       const char **parent_names, u8 num_parents, u8 id, | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Alexandre Belloni
						Alexandre Belloni