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_H32MX)		+= clk-h32mx.o | ||||||
| obj-$(CONFIG_HAVE_AT91_GENERATED_CLK)	+= clk-generated.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_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_AT91SAM9) += at91sam9260.o at91sam9rl.o at91sam9x5.o | ||||||
| obj-$(CONFIG_SOC_SAMA5D4) += sama5d4.o | obj-$(CONFIG_SOC_SAMA5D4) += sama5d4.o | ||||||
| obj-$(CONFIG_SOC_SAMA5D2) += sama5d2.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; | 	struct clk_range *output; | ||||||
| 	u16 *icpll; | 	u16 *icpll; | ||||||
| 	u8 *out; | 	u8 *out; | ||||||
|  | 	u8 upll : 1; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| struct clk_programmable_layout { | struct clk_programmable_layout { | ||||||
| @ -169,6 +170,11 @@ struct clk_hw * __init | |||||||
| at91_clk_register_plldiv(struct regmap *regmap, const char *name, | at91_clk_register_plldiv(struct regmap *regmap, const char *name, | ||||||
| 			 const char *parent_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 | struct clk_hw * __init | ||||||
| at91_clk_register_programmable(struct regmap *regmap, const char *name, | at91_clk_register_programmable(struct regmap *regmap, const char *name, | ||||||
| 			       const char **parent_names, u8 num_parents, u8 id, | 			       const char **parent_names, u8 num_parents, u8 id, | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user