From 153bc1c66a8814e621ca0483098ac722be860aaf Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Fri, 3 Jul 2020 09:32:35 +0200 Subject: clk: at91: fix possible dead lock in new drivers syscon_node_to_regmap() will make the created regmap get and enable the first clock it can parse from the device tree. This clock is not needed to access the registers and should not be enabled at that time. Use device_node_to_regmap to resolve this as it looks up the regmap in the same list but doesn't care about the clocks. This issue is detected by lockdep when booting the sama5d3 with a device tree containing the new clk bindings. This fix already happened in 6956eb33abb5 ("clk: at91: fix possible deadlock") for the drivers that had been migrated to the new clk binding back then. This does the same for the new drivers as well. Fixes: 01e2113de9a5 ("clk: at91: add sam9x60 pmc driver") Signed-off-by: Ahmad Fatoum Link: https://lore.kernel.org/r/20200703073236.23923-1-a.fatoum@pengutronix.de Acked-by: Alexandre Belloni Signed-off-by: Stephen Boyd --- drivers/clk/at91/at91sam9g45.c | 2 +- drivers/clk/at91/at91sam9n12.c | 2 +- drivers/clk/at91/sam9x60.c | 2 +- drivers/clk/at91/sama5d3.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers/clk') diff --git a/drivers/clk/at91/at91sam9g45.c b/drivers/clk/at91/at91sam9g45.c index 9873b583c260..fe9d391adeba 100644 --- a/drivers/clk/at91/at91sam9g45.c +++ b/drivers/clk/at91/at91sam9g45.c @@ -111,7 +111,7 @@ static void __init at91sam9g45_pmc_setup(struct device_node *np) return; mainxtal_name = of_clk_get_parent_name(np, i); - regmap = syscon_node_to_regmap(np); + regmap = device_node_to_regmap(np); if (IS_ERR(regmap)) return; diff --git a/drivers/clk/at91/at91sam9n12.c b/drivers/clk/at91/at91sam9n12.c index 630dc5d87171..4aa97e672bd6 100644 --- a/drivers/clk/at91/at91sam9n12.c +++ b/drivers/clk/at91/at91sam9n12.c @@ -124,7 +124,7 @@ static void __init at91sam9n12_pmc_setup(struct device_node *np) return; mainxtal_name = of_clk_get_parent_name(np, i); - regmap = syscon_node_to_regmap(np); + regmap = device_node_to_regmap(np); if (IS_ERR(regmap)) return; diff --git a/drivers/clk/at91/sam9x60.c b/drivers/clk/at91/sam9x60.c index 3e20aa68259f..2b4c67485eee 100644 --- a/drivers/clk/at91/sam9x60.c +++ b/drivers/clk/at91/sam9x60.c @@ -178,7 +178,7 @@ static void __init sam9x60_pmc_setup(struct device_node *np) return; mainxtal_name = of_clk_get_parent_name(np, i); - regmap = syscon_node_to_regmap(np); + regmap = device_node_to_regmap(np); if (IS_ERR(regmap)) return; diff --git a/drivers/clk/at91/sama5d3.c b/drivers/clk/at91/sama5d3.c index 5e4e44dd4c37..5609b04e6565 100644 --- a/drivers/clk/at91/sama5d3.c +++ b/drivers/clk/at91/sama5d3.c @@ -121,7 +121,7 @@ static void __init sama5d3_pmc_setup(struct device_node *np) return; mainxtal_name = of_clk_get_parent_name(np, i); - regmap = syscon_node_to_regmap(np); + regmap = device_node_to_regmap(np); if (IS_ERR(regmap)) return; -- cgit v1.2.3 From 3a5c42b18a6ddc0de77dde017baf45262fbf1ced Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Wed, 22 Jul 2020 10:38:09 +0300 Subject: clk: at91: clk-generated: continue if __clk_determine_rate() returns error __clk_determine_rate() may return error. Skip the current step in case of error. Fixes: 1a1a36d72e3d3 ("clk: at91: clk-generated: make gclk determine audio_pll rate") Signed-off-by: Claudiu Beznea Reviewed-by: Alexandre Belloni Link: https://lore.kernel.org/r/1595403506-8209-2-git-send-email-claudiu.beznea@microchip.com Signed-off-by: Stephen Boyd --- drivers/clk/at91/clk-generated.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers/clk') diff --git a/drivers/clk/at91/clk-generated.c b/drivers/clk/at91/clk-generated.c index 44a46dcc0518..995a13133cfb 100644 --- a/drivers/clk/at91/clk-generated.c +++ b/drivers/clk/at91/clk-generated.c @@ -170,7 +170,8 @@ static int clk_generated_determine_rate(struct clk_hw *hw, for (div = 1; div < GENERATED_MAX_DIV + 2; div++) { req_parent.rate = req->rate * div; - __clk_determine_rate(parent, &req_parent); + if (__clk_determine_rate(parent, &req_parent)) + continue; clk_generated_best_diff(req, parent, req_parent.rate, div, &best_diff, &best_rate); -- cgit v1.2.3 From 83331bfcc021a24da79f38a2452e86182c7ae94b Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Wed, 22 Jul 2020 10:38:10 +0300 Subject: clk: at91: clk-generated: check best_rate against ranges Check best_rate against available clock ranges. Fixes: df70aeef6083 ("clk: at91: add generated clock driver") Signed-off-by: Claudiu Beznea Reviewed-by: Alexandre Belloni Link: https://lore.kernel.org/r/1595403506-8209-3-git-send-email-claudiu.beznea@microchip.com Signed-off-by: Stephen Boyd --- drivers/clk/at91/clk-generated.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/clk') diff --git a/drivers/clk/at91/clk-generated.c b/drivers/clk/at91/clk-generated.c index 995a13133cfb..f8e557e0e1b8 100644 --- a/drivers/clk/at91/clk-generated.c +++ b/drivers/clk/at91/clk-generated.c @@ -185,8 +185,8 @@ end: __clk_get_name((req->best_parent_hw)->clk), req->best_parent_rate); - if (best_rate < 0) - return best_rate; + if (best_rate < 0 || (gck->range.max && best_rate > gck->range.max)) + return -EINVAL; req->rate = best_rate; return 0; -- cgit v1.2.3 From 3bf639a611a8deca7e4daa57a4df9ba4c6025249 Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Wed, 22 Jul 2020 10:38:11 +0300 Subject: clk: at91: clk-sam9x60-pll: fix mul mask According to datasheet mul mask is on bits 31..24. Fixes: a436c2a447e59 ("clk: at91: add sam9x60 PLL driver") Signed-off-by: Claudiu Beznea Reviewed-by: Alexandre Belloni Link: https://lore.kernel.org/r/1595403506-8209-4-git-send-email-claudiu.beznea@microchip.com Signed-off-by: Stephen Boyd --- drivers/clk/at91/clk-sam9x60-pll.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/clk') diff --git a/drivers/clk/at91/clk-sam9x60-pll.c b/drivers/clk/at91/clk-sam9x60-pll.c index e699803986e5..3522eae2edd6 100644 --- a/drivers/clk/at91/clk-sam9x60-pll.c +++ b/drivers/clk/at91/clk-sam9x60-pll.c @@ -15,7 +15,7 @@ #include "pmc.h" #define PMC_PLL_CTRL0_DIV_MSK GENMASK(7, 0) -#define PMC_PLL_CTRL1_MUL_MSK GENMASK(30, 24) +#define PMC_PLL_CTRL1_MUL_MSK GENMASK(31, 24) #define PLL_DIV_MAX (FIELD_GET(PMC_PLL_CTRL0_DIV_MSK, UINT_MAX) + 1) #define UPLL_DIV 2 -- cgit v1.2.3 From 1bef0986b125942d65d39c10cbfa75b20c3b94b3 Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Wed, 22 Jul 2020 10:38:12 +0300 Subject: clk: at91: sam9x60-pll: use logical or for range check Use logical or for range check. In case bestrate is not in characteristics->output[0].min..characteristics->output[0].max range we should return -ERANGE. Fixes: a436c2a447e59 ("clk: at91: add sam9x60 PLL driver") Signed-off-by: Claudiu Beznea Reviewed-by: Alexandre Belloni Link: https://lore.kernel.org/r/1595403506-8209-5-git-send-email-claudiu.beznea@microchip.com Signed-off-by: Stephen Boyd --- drivers/clk/at91/clk-sam9x60-pll.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/clk') diff --git a/drivers/clk/at91/clk-sam9x60-pll.c b/drivers/clk/at91/clk-sam9x60-pll.c index 3522eae2edd6..4b7b6c435d4e 100644 --- a/drivers/clk/at91/clk-sam9x60-pll.c +++ b/drivers/clk/at91/clk-sam9x60-pll.c @@ -231,7 +231,7 @@ static long sam9x60_pll_get_best_div_mul(struct sam9x60_pll *pll, } /* Check if bestrate is a valid output rate */ - if (bestrate < characteristics->output[0].min && + if (bestrate < characteristics->output[0].min || bestrate > characteristics->output[0].max) return -ERANGE; -- cgit v1.2.3 From 390227dca870cd0b8b0961da9e293551015c0007 Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Wed, 22 Jul 2020 10:38:13 +0300 Subject: clk: at91: sam9x60-pll: check fcore against ranges According to datasheet the range of 600-1200MHz is for the frequency generated by the fractional part of the PLL (namely Fcorepllck according to datasheet). With this in mind the output range of the PLL itself (fractional + div), taking into account that the divider is 8 bits wide, is 600/256-1200Hz=2.3-1200MHz. Fixes: a436c2a447e59 ("clk: at91: add sam9x60 PLL driver") Signed-off-by: Claudiu Beznea Link: https://lore.kernel.org/r/1595403506-8209-6-git-send-email-claudiu.beznea@microchip.com Signed-off-by: Stephen Boyd --- drivers/clk/at91/clk-sam9x60-pll.c | 12 +++++++++++- drivers/clk/at91/sam9x60.c | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) (limited to 'drivers/clk') diff --git a/drivers/clk/at91/clk-sam9x60-pll.c b/drivers/clk/at91/clk-sam9x60-pll.c index 4b7b6c435d4e..a933abebfbaf 100644 --- a/drivers/clk/at91/clk-sam9x60-pll.c +++ b/drivers/clk/at91/clk-sam9x60-pll.c @@ -21,6 +21,9 @@ #define UPLL_DIV 2 #define PLL_MUL_MAX (FIELD_GET(PMC_PLL_CTRL1_MUL_MSK, UINT_MAX) + 1) +#define FCORE_MIN (600000000) +#define FCORE_MAX (1200000000) + #define PLL_MAX_ID 1 struct sam9x60_pll { @@ -168,6 +171,7 @@ static long sam9x60_pll_get_best_div_mul(struct sam9x60_pll *pll, unsigned long bestdiv = 0; unsigned long bestmul = 0; unsigned long bestfrac = 0; + u64 fcore = 0; if (rate < characteristics->output[0].min || rate > characteristics->output[0].max) @@ -212,6 +216,11 @@ static long sam9x60_pll_get_best_div_mul(struct sam9x60_pll *pll, remainder = rate - tmprate; } + fcore = parent_rate * (tmpmul + 1) + + ((u64)parent_rate * tmpfrac >> 22); + if (fcore < FCORE_MIN || fcore > FCORE_MAX) + continue; + /* * Compare the remainder with the best remainder found until * now and elect a new best multiplier/divider pair if the @@ -231,7 +240,8 @@ static long sam9x60_pll_get_best_div_mul(struct sam9x60_pll *pll, } /* Check if bestrate is a valid output rate */ - if (bestrate < characteristics->output[0].min || + if (fcore < FCORE_MIN || fcore > FCORE_MAX || + bestrate < characteristics->output[0].min || bestrate > characteristics->output[0].max) return -ERANGE; diff --git a/drivers/clk/at91/sam9x60.c b/drivers/clk/at91/sam9x60.c index 2b4c67485eee..19945138123b 100644 --- a/drivers/clk/at91/sam9x60.c +++ b/drivers/clk/at91/sam9x60.c @@ -22,7 +22,7 @@ static const struct clk_master_layout sam9x60_master_layout = { }; static const struct clk_range plla_outputs[] = { - { .min = 300000000, .max = 600000000 }, + { .min = 2343750, .max = 1200000000 }, }; static const struct clk_pll_characteristics plla_characteristics = { -- cgit v1.2.3 From 35d06f74785cc8d108dcb1e3324b2404fb39df0b Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Wed, 22 Jul 2020 10:38:14 +0300 Subject: clk: at91: sam9x60-pll: use frac when setting frequency In commit a436c2a447e59 ("clk: at91: add sam9x60 PLL driver") the fractional part of PLL wasn't set on registers but it was calculated and taken into account for determining div and mul (see sam9x60_pll_get_best_div_mul()). Fixes: a436c2a447e59 ("clk: at91: add sam9x60 PLL driver") Signed-off-by: Claudiu Beznea Link: https://lore.kernel.org/r/1595403506-8209-7-git-send-email-claudiu.beznea@microchip.com Signed-off-by: Stephen Boyd --- drivers/clk/at91/clk-sam9x60-pll.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'drivers/clk') diff --git a/drivers/clk/at91/clk-sam9x60-pll.c b/drivers/clk/at91/clk-sam9x60-pll.c index a933abebfbaf..1f0bcc6b4fea 100644 --- a/drivers/clk/at91/clk-sam9x60-pll.c +++ b/drivers/clk/at91/clk-sam9x60-pll.c @@ -16,6 +16,7 @@ #define PMC_PLL_CTRL0_DIV_MSK GENMASK(7, 0) #define PMC_PLL_CTRL1_MUL_MSK GENMASK(31, 24) +#define PMC_PLL_CTRL1_FRACR_MSK GENMASK(21, 0) #define PLL_DIV_MAX (FIELD_GET(PMC_PLL_CTRL0_DIV_MSK, UINT_MAX) + 1) #define UPLL_DIV 2 @@ -55,7 +56,7 @@ static int sam9x60_pll_prepare(struct clk_hw *hw) unsigned long flags; u8 div; u16 mul; - u32 val; + u32 val, frac; spin_lock_irqsave(pll->lock, flags); regmap_write(regmap, AT91_PMC_PLL_UPDT, pll->id); @@ -65,9 +66,10 @@ static int sam9x60_pll_prepare(struct clk_hw *hw) regmap_read(regmap, AT91_PMC_PLL_CTRL1, &val); mul = FIELD_GET(PMC_PLL_CTRL1_MUL_MSK, val); + frac = FIELD_GET(PMC_PLL_CTRL1_FRACR_MSK, val); if (sam9x60_pll_ready(regmap, pll->id) && - (div == pll->div && mul == pll->mul)) { + (div == pll->div && mul == pll->mul && frac == pll->frac)) { spin_unlock_irqrestore(pll->lock, flags); return 0; } @@ -80,7 +82,8 @@ static int sam9x60_pll_prepare(struct clk_hw *hw) regmap_write(regmap, AT91_PMC_PLL_ACR, val); regmap_write(regmap, AT91_PMC_PLL_CTRL1, - FIELD_PREP(PMC_PLL_CTRL1_MUL_MSK, pll->mul)); + FIELD_PREP(PMC_PLL_CTRL1_MUL_MSK, pll->mul) | + FIELD_PREP(PMC_PLL_CTRL1_FRACR_MSK, pll->frac)); if (pll->characteristics->upll) { /* Enable the UTMI internal bandgap */ @@ -155,7 +158,8 @@ static unsigned long sam9x60_pll_recalc_rate(struct clk_hw *hw, { struct sam9x60_pll *pll = to_sam9x60_pll(hw); - return (parent_rate * (pll->mul + 1)) / (pll->div + 1); + return DIV_ROUND_CLOSEST_ULL((parent_rate * (pll->mul + 1) + + ((u64)parent_rate * pll->frac >> 22)), (pll->div + 1)); } static long sam9x60_pll_get_best_div_mul(struct sam9x60_pll *pll, -- cgit v1.2.3 From 172e7ddeeafd95caa809b7b1a140bfd7f20597d9 Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Wed, 22 Jul 2020 10:38:15 +0300 Subject: clk: at91: sam9x60: fix main rc oscillator frequency Main RC oscillator frequency is 12MHz according to datasheet (chapter 27.2). Fixes: 01e2113de9a52 ("clk: at91: add sam9x60 pmc driver") Signed-off-by: Claudiu Beznea Reviewed-by: Alexandre Belloni Link: https://lore.kernel.org/r/1595403506-8209-8-git-send-email-claudiu.beznea@microchip.com Signed-off-by: Stephen Boyd --- drivers/clk/at91/sam9x60.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/clk') diff --git a/drivers/clk/at91/sam9x60.c b/drivers/clk/at91/sam9x60.c index 19945138123b..d9f6d9ba1eca 100644 --- a/drivers/clk/at91/sam9x60.c +++ b/drivers/clk/at91/sam9x60.c @@ -189,7 +189,7 @@ static void __init sam9x60_pmc_setup(struct device_node *np) if (!sam9x60_pmc) return; - hw = at91_clk_register_main_rc_osc(regmap, "main_rc_osc", 24000000, + hw = at91_clk_register_main_rc_osc(regmap, "main_rc_osc", 12000000, 50000000); if (IS_ERR(hw)) goto err_free; -- cgit v1.2.3 From e1e3e7008a90f9ffe0161191bd64a4c06f0d417a Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Wed, 22 Jul 2020 10:38:16 +0300 Subject: clk: at91: sckc: register slow_rc with accuracy option Chapter 57.7.5 of SAM9X60 datasheet specifies that the maximum drift of this oscillator is +- 3KHz. Use that value and the formula at [1] or the calculator at [2] to compute the PPB value. [1] https://www.everythingrf.com/rf-calculators/ppm-to-hz-calculator [2] https://www.changpuak.ch/electronics/ppm_to_Hz_converter.php Fixes: 04bcc4275e601 ("clk: at91: sckc: add support for SAM9X60") Signed-off-by: Claudiu Beznea Link: https://lore.kernel.org/r/1595403506-8209-9-git-send-email-claudiu.beznea@microchip.com Signed-off-by: Stephen Boyd --- drivers/clk/at91/sckc.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'drivers/clk') diff --git a/drivers/clk/at91/sckc.c b/drivers/clk/at91/sckc.c index 15dc4cd86d76..2d65770d8665 100644 --- a/drivers/clk/at91/sckc.c +++ b/drivers/clk/at91/sckc.c @@ -471,8 +471,9 @@ static void __init of_sam9x60_sckc_setup(struct device_node *np) if (!regbase) return; - slow_rc = clk_hw_register_fixed_rate(NULL, parent_names[0], NULL, 0, - 32768); + slow_rc = clk_hw_register_fixed_rate_with_accuracy(NULL, parent_names[0], + NULL, 0, 32768, + 93750000); if (IS_ERR(slow_rc)) return; -- cgit v1.2.3 From 42324d953b38e74cf5cb05a02c81d4922a2ddcd5 Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Wed, 22 Jul 2020 10:38:17 +0300 Subject: clk: at91: replace conditional operator with double logical not Replace conditional operator with double logical not as code may be simpler to read. Signed-off-by: Claudiu Beznea Link: https://lore.kernel.org/r/1595403506-8209-10-git-send-email-claudiu.beznea@microchip.com Signed-off-by: Stephen Boyd --- drivers/clk/at91/clk-generated.c | 2 +- drivers/clk/at91/clk-main.c | 6 +++--- drivers/clk/at91/clk-master.c | 2 +- drivers/clk/at91/clk-peripheral.c | 2 +- drivers/clk/at91/clk-system.c | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) (limited to 'drivers/clk') diff --git a/drivers/clk/at91/clk-generated.c b/drivers/clk/at91/clk-generated.c index f8e557e0e1b8..2448bdc63425 100644 --- a/drivers/clk/at91/clk-generated.c +++ b/drivers/clk/at91/clk-generated.c @@ -83,7 +83,7 @@ static int clk_generated_is_enabled(struct clk_hw *hw) regmap_read(gck->regmap, gck->layout->offset, &status); spin_unlock_irqrestore(gck->lock, flags); - return status & AT91_PMC_PCR_GCKEN ? 1 : 0; + return !!(status & AT91_PMC_PCR_GCKEN); } static unsigned long diff --git a/drivers/clk/at91/clk-main.c b/drivers/clk/at91/clk-main.c index 37c22667e831..5c83e899084f 100644 --- a/drivers/clk/at91/clk-main.c +++ b/drivers/clk/at91/clk-main.c @@ -175,7 +175,7 @@ static bool clk_main_rc_osc_ready(struct regmap *regmap) regmap_read(regmap, AT91_PMC_SR, &status); - return status & AT91_PMC_MOSCRCS; + return !!(status & AT91_PMC_MOSCRCS); } static int clk_main_rc_osc_prepare(struct clk_hw *hw) @@ -336,7 +336,7 @@ static int clk_rm9200_main_is_prepared(struct clk_hw *hw) regmap_read(clkmain->regmap, AT91_CKGR_MCFR, &status); - return status & AT91_PMC_MAINRDY ? 1 : 0; + return !!(status & AT91_PMC_MAINRDY); } static unsigned long clk_rm9200_main_recalc_rate(struct clk_hw *hw, @@ -398,7 +398,7 @@ static inline bool clk_sam9x5_main_ready(struct regmap *regmap) regmap_read(regmap, AT91_PMC_SR, &status); - return status & AT91_PMC_MOSCSELS ? 1 : 0; + return !!(status & AT91_PMC_MOSCSELS); } static int clk_sam9x5_main_prepare(struct clk_hw *hw) diff --git a/drivers/clk/at91/clk-master.c b/drivers/clk/at91/clk-master.c index e7e0ba652de1..88d545b1698c 100644 --- a/drivers/clk/at91/clk-master.c +++ b/drivers/clk/at91/clk-master.c @@ -33,7 +33,7 @@ static inline bool clk_master_ready(struct regmap *regmap) regmap_read(regmap, AT91_PMC_SR, &status); - return status & AT91_PMC_MCKRDY ? 1 : 0; + return !!(status & AT91_PMC_MCKRDY); } static int clk_master_prepare(struct clk_hw *hw) diff --git a/drivers/clk/at91/clk-peripheral.c b/drivers/clk/at91/clk-peripheral.c index c2ab4860a2bf..4c9a4147dfe5 100644 --- a/drivers/clk/at91/clk-peripheral.c +++ b/drivers/clk/at91/clk-peripheral.c @@ -208,7 +208,7 @@ static int clk_sam9x5_peripheral_is_enabled(struct clk_hw *hw) regmap_read(periph->regmap, periph->layout->offset, &status); spin_unlock_irqrestore(periph->lock, flags); - return status & AT91_PMC_PCR_EN ? 1 : 0; + return !!(status & AT91_PMC_PCR_EN); } static unsigned long diff --git a/drivers/clk/at91/clk-system.c b/drivers/clk/at91/clk-system.c index c4b3877aa445..f83ec0de86c3 100644 --- a/drivers/clk/at91/clk-system.c +++ b/drivers/clk/at91/clk-system.c @@ -34,7 +34,7 @@ static inline bool clk_system_ready(struct regmap *regmap, int id) regmap_read(regmap, AT91_PMC_SR, &status); - return status & (1 << id) ? 1 : 0; + return !!(status & (1 << id)); } static int clk_system_prepare(struct clk_hw *hw) @@ -74,7 +74,7 @@ static int clk_system_is_prepared(struct clk_hw *hw) regmap_read(sys->regmap, AT91_PMC_SR, &status); - return status & (1 << sys->id) ? 1 : 0; + return !!(status & (1 << sys->id)); } static const struct clk_ops system_ops = { -- cgit v1.2.3 From 64c9247b9e87e96e41cea545eb64727cee10c55c Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Wed, 22 Jul 2020 10:38:18 +0300 Subject: clk: at91: clk-generated: pass the id of changeable parent at registration Pass the ID of changeable parent at registration. This will allow the scalability of this clock driver with regards to the changeable parent ID for versions of this IP where changeable parent is not the last one in the parents list (e.g. SAMA7G5). With this the clock flags are set to zero in case we have no changeable parent. Also in clk_generated_best_diff() the *best_diff variable is check against tmp_diff variable using ">=" operator instead of ">" so that in case the requested frequency could be obtained using fix parents + gck dividers but the clock also supports changeable parent to be able to force the usage of the changeable parent. Signed-off-by: Claudiu Beznea Link: https://lore.kernel.org/r/1595403506-8209-11-git-send-email-claudiu.beznea@microchip.com Signed-off-by: Stephen Boyd --- drivers/clk/at91/clk-generated.c | 26 ++++++++++++++------------ drivers/clk/at91/dt-compat.c | 8 +++++--- drivers/clk/at91/pmc.h | 4 ++-- drivers/clk/at91/sam9x60.c | 3 +-- drivers/clk/at91/sama5d2.c | 31 +++++++++++++++---------------- 5 files changed, 37 insertions(+), 35 deletions(-) (limited to 'drivers/clk') diff --git a/drivers/clk/at91/clk-generated.c b/drivers/clk/at91/clk-generated.c index 2448bdc63425..f9ca04c97128 100644 --- a/drivers/clk/at91/clk-generated.c +++ b/drivers/clk/at91/clk-generated.c @@ -18,8 +18,6 @@ #define GENERATED_MAX_DIV 255 -#define GCK_INDEX_DT_AUDIO_PLL 5 - struct clk_generated { struct clk_hw hw; struct regmap *regmap; @@ -29,7 +27,7 @@ struct clk_generated { u32 gckdiv; const struct clk_pcr_layout *layout; u8 parent_id; - bool audio_pll_allowed; + int chg_pid; }; #define to_clk_generated(hw) \ @@ -109,7 +107,7 @@ static void clk_generated_best_diff(struct clk_rate_request *req, tmp_rate = parent_rate / div; tmp_diff = abs(req->rate - tmp_rate); - if (*best_diff < 0 || *best_diff > tmp_diff) { + if (*best_diff < 0 || *best_diff >= tmp_diff) { *best_rate = tmp_rate; *best_diff = tmp_diff; req->best_parent_rate = parent_rate; @@ -129,7 +127,10 @@ static int clk_generated_determine_rate(struct clk_hw *hw, int i; u32 div; - for (i = 0; i < clk_hw_get_num_parents(hw) - 1; i++) { + for (i = 0; i < clk_hw_get_num_parents(hw); i++) { + if (gck->chg_pid == i) + continue; + parent = clk_hw_get_parent_by_index(hw, i); if (!parent) continue; @@ -161,10 +162,10 @@ static int clk_generated_determine_rate(struct clk_hw *hw, * that the only clks able to modify gck rate are those of audio IPs. */ - if (!gck->audio_pll_allowed) + if (gck->chg_pid < 0) goto end; - parent = clk_hw_get_parent_by_index(hw, GCK_INDEX_DT_AUDIO_PLL); + parent = clk_hw_get_parent_by_index(hw, gck->chg_pid); if (!parent) goto end; @@ -272,8 +273,8 @@ struct clk_hw * __init at91_clk_register_generated(struct regmap *regmap, spinlock_t *lock, const struct clk_pcr_layout *layout, const char *name, const char **parent_names, - u8 num_parents, u8 id, bool pll_audio, - const struct clk_range *range) + u8 num_parents, u8 id, + const struct clk_range *range, int chg_pid) { struct clk_generated *gck; struct clk_init_data init; @@ -288,15 +289,16 @@ at91_clk_register_generated(struct regmap *regmap, spinlock_t *lock, init.ops = &generated_ops; init.parent_names = parent_names; init.num_parents = num_parents; - init.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE | - CLK_SET_RATE_PARENT; + init.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE; + if (chg_pid >= 0) + init.flags |= CLK_SET_RATE_PARENT; gck->id = id; gck->hw.init = &init; gck->regmap = regmap; gck->lock = lock; gck->range = *range; - gck->audio_pll_allowed = pll_audio; + gck->chg_pid = chg_pid; gck->layout = layout; clk_generated_startup(gck); diff --git a/drivers/clk/at91/dt-compat.c b/drivers/clk/at91/dt-compat.c index aa1754eac59f..8a652c44c25a 100644 --- a/drivers/clk/at91/dt-compat.c +++ b/drivers/clk/at91/dt-compat.c @@ -22,6 +22,8 @@ #define SYSTEM_MAX_ID 31 +#define GCK_INDEX_DT_AUDIO_PLL 5 + #ifdef CONFIG_HAVE_AT91_AUDIO_PLL static void __init of_sama5d2_clk_audio_pll_frac_setup(struct device_node *np) { @@ -135,7 +137,7 @@ static void __init of_sama5d2_clk_generated_setup(struct device_node *np) return; for_each_child_of_node(np, gcknp) { - bool pll_audio = false; + int chg_pid = INT_MIN; if (of_property_read_u32(gcknp, "reg", &id)) continue; @@ -152,12 +154,12 @@ static void __init of_sama5d2_clk_generated_setup(struct device_node *np) if (of_device_is_compatible(np, "atmel,sama5d2-clk-generated") && (id == GCK_ID_I2S0 || id == GCK_ID_I2S1 || id == GCK_ID_CLASSD)) - pll_audio = true; + chg_pid = GCK_INDEX_DT_AUDIO_PLL; hw = at91_clk_register_generated(regmap, &pmc_pcr_lock, &dt_pcr_layout, name, parent_names, num_parents, - id, pll_audio, &range); + id, &range, chg_pid); if (IS_ERR(hw)) continue; diff --git a/drivers/clk/at91/pmc.h b/drivers/clk/at91/pmc.h index df616f2937e7..949b1a7b91e5 100644 --- a/drivers/clk/at91/pmc.h +++ b/drivers/clk/at91/pmc.h @@ -122,8 +122,8 @@ struct clk_hw * __init at91_clk_register_generated(struct regmap *regmap, spinlock_t *lock, const struct clk_pcr_layout *layout, const char *name, const char **parent_names, - u8 num_parents, u8 id, bool pll_audio, - const struct clk_range *range); + u8 num_parents, u8 id, + const struct clk_range *range, int chg_pid); struct clk_hw * __init at91_clk_register_h32mx(struct regmap *regmap, const char *name, diff --git a/drivers/clk/at91/sam9x60.c b/drivers/clk/at91/sam9x60.c index d9f6d9ba1eca..58661b6f6eaa 100644 --- a/drivers/clk/at91/sam9x60.c +++ b/drivers/clk/at91/sam9x60.c @@ -290,8 +290,7 @@ static void __init sam9x60_pmc_setup(struct device_node *np) sam9x60_gck[i].n, parent_names, 6, sam9x60_gck[i].id, - false, - &sam9x60_gck[i].r); + &sam9x60_gck[i].r, INT_MIN); if (IS_ERR(hw)) goto err_free; diff --git a/drivers/clk/at91/sama5d2.c b/drivers/clk/at91/sama5d2.c index d69421d71daf..42637d9531d3 100644 --- a/drivers/clk/at91/sama5d2.c +++ b/drivers/clk/at91/sama5d2.c @@ -116,21 +116,20 @@ static const struct { char *n; u8 id; struct clk_range r; - bool pll; + int chg_pid; } sama5d2_gck[] = { - { .n = "sdmmc0_gclk", .id = 31, }, - { .n = "sdmmc1_gclk", .id = 32, }, - { .n = "tcb0_gclk", .id = 35, .r = { .min = 0, .max = 83000000 }, }, - { .n = "tcb1_gclk", .id = 36, .r = { .min = 0, .max = 83000000 }, }, - { .n = "pwm_gclk", .id = 38, .r = { .min = 0, .max = 83000000 }, }, - { .n = "isc_gclk", .id = 46, }, - { .n = "pdmic_gclk", .id = 48, }, - { .n = "i2s0_gclk", .id = 54, .pll = true }, - { .n = "i2s1_gclk", .id = 55, .pll = true }, - { .n = "can0_gclk", .id = 56, .r = { .min = 0, .max = 80000000 }, }, - { .n = "can1_gclk", .id = 57, .r = { .min = 0, .max = 80000000 }, }, - { .n = "classd_gclk", .id = 59, .r = { .min = 0, .max = 100000000 }, - .pll = true }, + { .n = "sdmmc0_gclk", .id = 31, .chg_pid = INT_MIN, }, + { .n = "sdmmc1_gclk", .id = 32, .chg_pid = INT_MIN, }, + { .n = "tcb0_gclk", .id = 35, .chg_pid = INT_MIN, .r = { .min = 0, .max = 83000000 }, }, + { .n = "tcb1_gclk", .id = 36, .chg_pid = INT_MIN, .r = { .min = 0, .max = 83000000 }, }, + { .n = "pwm_gclk", .id = 38, .chg_pid = INT_MIN, .r = { .min = 0, .max = 83000000 }, }, + { .n = "isc_gclk", .id = 46, .chg_pid = INT_MIN, }, + { .n = "pdmic_gclk", .id = 48, .chg_pid = INT_MIN, }, + { .n = "i2s0_gclk", .id = 54, .chg_pid = 5, }, + { .n = "i2s1_gclk", .id = 55, .chg_pid = 5, }, + { .n = "can0_gclk", .id = 56, .chg_pid = INT_MIN, .r = { .min = 0, .max = 80000000 }, }, + { .n = "can1_gclk", .id = 57, .chg_pid = INT_MIN, .r = { .min = 0, .max = 80000000 }, }, + { .n = "classd_gclk", .id = 59, .chg_pid = 5, .r = { .min = 0, .max = 100000000 }, }, }; static const struct clk_programmable_layout sama5d2_programmable_layout = { @@ -324,8 +323,8 @@ static void __init sama5d2_pmc_setup(struct device_node *np) sama5d2_gck[i].n, parent_names, 6, sama5d2_gck[i].id, - sama5d2_gck[i].pll, - &sama5d2_gck[i].r); + &sama5d2_gck[i].r, + sama5d2_gck[i].chg_pid); if (IS_ERR(hw)) goto err_free; -- cgit v1.2.3 From 22a1dfe93bf496d03cb1d76b1fbd23a7ff4a062c Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Wed, 22 Jul 2020 10:38:19 +0300 Subject: clk: at91: clk-generated: add mux_table option Add mux table option. This is necessary for IP versions that has gaps in the range of available clock sources (e.g. SAMA7G5). Signed-off-by: Claudiu Beznea Link: https://lore.kernel.org/r/1595403506-8209-12-git-send-email-claudiu.beznea@microchip.com Signed-off-by: Stephen Boyd --- drivers/clk/at91/clk-generated.c | 13 ++++++++++--- drivers/clk/at91/dt-compat.c | 5 +++-- drivers/clk/at91/pmc.h | 2 +- drivers/clk/at91/sam9x60.c | 2 +- drivers/clk/at91/sama5d2.c | 2 +- 5 files changed, 16 insertions(+), 8 deletions(-) (limited to 'drivers/clk') diff --git a/drivers/clk/at91/clk-generated.c b/drivers/clk/at91/clk-generated.c index f9ca04c97128..b4fc8d71daf2 100644 --- a/drivers/clk/at91/clk-generated.c +++ b/drivers/clk/at91/clk-generated.c @@ -23,6 +23,7 @@ struct clk_generated { struct regmap *regmap; struct clk_range range; spinlock_t *lock; + u32 *mux_table; u32 id; u32 gckdiv; const struct clk_pcr_layout *layout; @@ -201,7 +202,11 @@ static int clk_generated_set_parent(struct clk_hw *hw, u8 index) if (index >= clk_hw_get_num_parents(hw)) return -EINVAL; - gck->parent_id = index; + if (gck->mux_table) + gck->parent_id = clk_mux_index_to_val(gck->mux_table, 0, index); + else + gck->parent_id = index; + return 0; } @@ -273,8 +278,9 @@ struct clk_hw * __init at91_clk_register_generated(struct regmap *regmap, spinlock_t *lock, const struct clk_pcr_layout *layout, const char *name, const char **parent_names, - u8 num_parents, u8 id, - const struct clk_range *range, int chg_pid) + u32 *mux_table, u8 num_parents, u8 id, + const struct clk_range *range, + int chg_pid) { struct clk_generated *gck; struct clk_init_data init; @@ -300,6 +306,7 @@ at91_clk_register_generated(struct regmap *regmap, spinlock_t *lock, gck->range = *range; gck->chg_pid = chg_pid; gck->layout = layout; + gck->mux_table = mux_table; clk_generated_startup(gck); hw = &gck->hw; diff --git a/drivers/clk/at91/dt-compat.c b/drivers/clk/at91/dt-compat.c index 8a652c44c25a..cc95d42f4d53 100644 --- a/drivers/clk/at91/dt-compat.c +++ b/drivers/clk/at91/dt-compat.c @@ -158,8 +158,9 @@ static void __init of_sama5d2_clk_generated_setup(struct device_node *np) hw = at91_clk_register_generated(regmap, &pmc_pcr_lock, &dt_pcr_layout, name, - parent_names, num_parents, - id, &range, chg_pid); + parent_names, NULL, + num_parents, id, &range, + chg_pid); if (IS_ERR(hw)) continue; diff --git a/drivers/clk/at91/pmc.h b/drivers/clk/at91/pmc.h index 949b1a7b91e5..2bfe1405f9f8 100644 --- a/drivers/clk/at91/pmc.h +++ b/drivers/clk/at91/pmc.h @@ -122,7 +122,7 @@ struct clk_hw * __init at91_clk_register_generated(struct regmap *regmap, spinlock_t *lock, const struct clk_pcr_layout *layout, const char *name, const char **parent_names, - u8 num_parents, u8 id, + u32 *mux_table, u8 num_parents, u8 id, const struct clk_range *range, int chg_pid); struct clk_hw * __init diff --git a/drivers/clk/at91/sam9x60.c b/drivers/clk/at91/sam9x60.c index 58661b6f6eaa..197ffc70305a 100644 --- a/drivers/clk/at91/sam9x60.c +++ b/drivers/clk/at91/sam9x60.c @@ -288,7 +288,7 @@ static void __init sam9x60_pmc_setup(struct device_node *np) hw = at91_clk_register_generated(regmap, &pmc_pcr_lock, &sam9x60_pcr_layout, sam9x60_gck[i].n, - parent_names, 6, + parent_names, NULL, 6, sam9x60_gck[i].id, &sam9x60_gck[i].r, INT_MIN); if (IS_ERR(hw)) diff --git a/drivers/clk/at91/sama5d2.c b/drivers/clk/at91/sama5d2.c index 42637d9531d3..6a685d00f16d 100644 --- a/drivers/clk/at91/sama5d2.c +++ b/drivers/clk/at91/sama5d2.c @@ -321,7 +321,7 @@ static void __init sama5d2_pmc_setup(struct device_node *np) hw = at91_clk_register_generated(regmap, &pmc_pcr_lock, &sama5d2_pcr_layout, sama5d2_gck[i].n, - parent_names, 6, + parent_names, NULL, 6, sama5d2_gck[i].id, &sama5d2_gck[i].r, sama5d2_gck[i].chg_pid); -- cgit v1.2.3 From 75c88143f3b879664cc5bf68b91854c1a98f5e5b Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Wed, 22 Jul 2020 10:38:20 +0300 Subject: clk: at91: clk-master: add master clock support for SAMA7G5 Add master clock support (MCK1..4) for SAMA7G5. SAMA7G5's PMC has multiple master clocks feeding different subsystems. One of them feeds image subsystem and is changeable based on image subsystem needs. Signed-off-by: Claudiu Beznea Link: https://lore.kernel.org/r/1595403506-8209-13-git-send-email-claudiu.beznea@microchip.com Signed-off-by: Stephen Boyd --- drivers/clk/at91/clk-master.c | 310 +++++++++++++++++++++++++++++++++++++++++- drivers/clk/at91/pmc.h | 7 + include/linux/clk/at91_pmc.h | 1 + 3 files changed, 313 insertions(+), 5 deletions(-) (limited to 'drivers/clk') diff --git a/drivers/clk/at91/clk-master.c b/drivers/clk/at91/clk-master.c index 88d545b1698c..bd0d8a69a2cf 100644 --- a/drivers/clk/at91/clk-master.c +++ b/drivers/clk/at91/clk-master.c @@ -17,30 +17,49 @@ #define MASTER_DIV_SHIFT 8 #define MASTER_DIV_MASK 0x3 +#define PMC_MCR 0x30 +#define PMC_MCR_ID_MSK GENMASK(3, 0) +#define PMC_MCR_CMD BIT(7) +#define PMC_MCR_DIV GENMASK(10, 8) +#define PMC_MCR_CSS GENMASK(20, 16) +#define PMC_MCR_CSS_SHIFT (16) +#define PMC_MCR_EN BIT(28) + +#define PMC_MCR_ID(x) ((x) & PMC_MCR_ID_MSK) + +#define MASTER_MAX_ID 4 + #define to_clk_master(hw) container_of(hw, struct clk_master, hw) struct clk_master { struct clk_hw hw; struct regmap *regmap; + spinlock_t *lock; const struct clk_master_layout *layout; const struct clk_master_characteristics *characteristics; + u32 *mux_table; u32 mckr; + int chg_pid; + u8 id; + u8 parent; + u8 div; }; -static inline bool clk_master_ready(struct regmap *regmap) +static inline bool clk_master_ready(struct clk_master *master) { + unsigned int bit = master->id ? AT91_PMC_MCKXRDY : AT91_PMC_MCKRDY; unsigned int status; - regmap_read(regmap, AT91_PMC_SR, &status); + regmap_read(master->regmap, AT91_PMC_SR, &status); - return !!(status & AT91_PMC_MCKRDY); + return !!(status & bit); } static int clk_master_prepare(struct clk_hw *hw) { struct clk_master *master = to_clk_master(hw); - while (!clk_master_ready(master->regmap)) + while (!clk_master_ready(master)) cpu_relax(); return 0; @@ -50,7 +69,7 @@ static int clk_master_is_prepared(struct clk_hw *hw) { struct clk_master *master = to_clk_master(hw); - return clk_master_ready(master->regmap); + return clk_master_ready(master); } static unsigned long clk_master_recalc_rate(struct clk_hw *hw, @@ -143,6 +162,287 @@ at91_clk_register_master(struct regmap *regmap, return hw; } +static unsigned long +clk_sama7g5_master_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct clk_master *master = to_clk_master(hw); + + return DIV_ROUND_CLOSEST_ULL(parent_rate, (1 << master->div)); +} + +static void clk_sama7g5_master_best_diff(struct clk_rate_request *req, + struct clk_hw *parent, + unsigned long parent_rate, + long *best_rate, + long *best_diff, + u32 div) +{ + unsigned long tmp_rate, tmp_diff; + + if (div == MASTER_PRES_MAX) + tmp_rate = parent_rate / 3; + else + tmp_rate = parent_rate >> div; + + tmp_diff = abs(req->rate - tmp_rate); + + if (*best_diff < 0 || *best_diff >= tmp_diff) { + *best_rate = tmp_rate; + *best_diff = tmp_diff; + req->best_parent_rate = parent_rate; + req->best_parent_hw = parent; + } +} + +static int clk_sama7g5_master_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct clk_master *master = to_clk_master(hw); + struct clk_rate_request req_parent = *req; + struct clk_hw *parent; + long best_rate = LONG_MIN, best_diff = LONG_MIN; + unsigned long parent_rate; + unsigned int div, i; + + /* First: check the dividers of MCR. */ + for (i = 0; i < clk_hw_get_num_parents(hw); i++) { + parent = clk_hw_get_parent_by_index(hw, i); + if (!parent) + continue; + + parent_rate = clk_hw_get_rate(parent); + if (!parent_rate) + continue; + + for (div = 0; div < MASTER_PRES_MAX + 1; div++) { + clk_sama7g5_master_best_diff(req, parent, parent_rate, + &best_rate, &best_diff, + div); + if (!best_diff) + break; + } + + if (!best_diff) + break; + } + + /* Second: try to request rate form changeable parent. */ + if (master->chg_pid < 0) + goto end; + + parent = clk_hw_get_parent_by_index(hw, master->chg_pid); + if (!parent) + goto end; + + for (div = 0; div < MASTER_PRES_MAX + 1; div++) { + if (div == MASTER_PRES_MAX) + req_parent.rate = req->rate * 3; + else + req_parent.rate = req->rate << div; + + if (__clk_determine_rate(parent, &req_parent)) + continue; + + clk_sama7g5_master_best_diff(req, parent, req_parent.rate, + &best_rate, &best_diff, div); + + if (!best_diff) + break; + } + +end: + pr_debug("MCK: %s, best_rate = %ld, parent clk: %s @ %ld\n", + __func__, best_rate, + __clk_get_name((req->best_parent_hw)->clk), + req->best_parent_rate); + + if (best_rate < 0) + return -EINVAL; + + req->rate = best_rate; + + return 0; +} + +static u8 clk_sama7g5_master_get_parent(struct clk_hw *hw) +{ + struct clk_master *master = to_clk_master(hw); + unsigned long flags; + u8 index; + + spin_lock_irqsave(master->lock, flags); + index = clk_mux_val_to_index(&master->hw, master->mux_table, 0, + master->parent); + spin_unlock_irqrestore(master->lock, flags); + + return index; +} + +static int clk_sama7g5_master_set_parent(struct clk_hw *hw, u8 index) +{ + struct clk_master *master = to_clk_master(hw); + unsigned long flags; + + if (index >= clk_hw_get_num_parents(hw)) + return -EINVAL; + + spin_lock_irqsave(master->lock, flags); + master->parent = clk_mux_index_to_val(master->mux_table, 0, index); + spin_unlock_irqrestore(master->lock, flags); + + return 0; +} + +static int clk_sama7g5_master_enable(struct clk_hw *hw) +{ + struct clk_master *master = to_clk_master(hw); + unsigned long flags; + unsigned int val, cparent; + + spin_lock_irqsave(master->lock, flags); + + regmap_write(master->regmap, PMC_MCR, PMC_MCR_ID(master->id)); + regmap_read(master->regmap, PMC_MCR, &val); + regmap_update_bits(master->regmap, PMC_MCR, + PMC_MCR_EN | PMC_MCR_CSS | PMC_MCR_DIV | + PMC_MCR_CMD | PMC_MCR_ID_MSK, + PMC_MCR_EN | (master->parent << PMC_MCR_CSS_SHIFT) | + (master->div << MASTER_DIV_SHIFT) | + PMC_MCR_CMD | PMC_MCR_ID(master->id)); + + cparent = (val & PMC_MCR_CSS) >> PMC_MCR_CSS_SHIFT; + + /* Wait here only if parent is being changed. */ + while ((cparent != master->parent) && !clk_master_ready(master)) + cpu_relax(); + + spin_unlock_irqrestore(master->lock, flags); + + return 0; +} + +static void clk_sama7g5_master_disable(struct clk_hw *hw) +{ + struct clk_master *master = to_clk_master(hw); + unsigned long flags; + + spin_lock_irqsave(master->lock, flags); + + regmap_write(master->regmap, PMC_MCR, master->id); + regmap_update_bits(master->regmap, PMC_MCR, + PMC_MCR_EN | PMC_MCR_CMD | PMC_MCR_ID_MSK, + PMC_MCR_CMD | PMC_MCR_ID(master->id)); + + spin_unlock_irqrestore(master->lock, flags); +} + +static int clk_sama7g5_master_is_enabled(struct clk_hw *hw) +{ + struct clk_master *master = to_clk_master(hw); + unsigned long flags; + unsigned int val; + + spin_lock_irqsave(master->lock, flags); + + regmap_write(master->regmap, PMC_MCR, master->id); + regmap_read(master->regmap, PMC_MCR, &val); + + spin_unlock_irqrestore(master->lock, flags); + + return !!(val & PMC_MCR_EN); +} + +static int clk_sama7g5_master_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct clk_master *master = to_clk_master(hw); + unsigned long div, flags; + + div = DIV_ROUND_CLOSEST(parent_rate, rate); + if ((div > (1 << (MASTER_PRES_MAX - 1))) || (div & (div - 1))) + return -EINVAL; + + if (div == 3) + div = MASTER_PRES_MAX; + else + div = ffs(div) - 1; + + spin_lock_irqsave(master->lock, flags); + master->div = div; + spin_unlock_irqrestore(master->lock, flags); + + return 0; +} + +static const struct clk_ops sama7g5_master_ops = { + .enable = clk_sama7g5_master_enable, + .disable = clk_sama7g5_master_disable, + .is_enabled = clk_sama7g5_master_is_enabled, + .recalc_rate = clk_sama7g5_master_recalc_rate, + .determine_rate = clk_sama7g5_master_determine_rate, + .set_rate = clk_sama7g5_master_set_rate, + .get_parent = clk_sama7g5_master_get_parent, + .set_parent = clk_sama7g5_master_set_parent, +}; + +struct clk_hw * __init +at91_clk_sama7g5_register_master(struct regmap *regmap, + const char *name, int num_parents, + const char **parent_names, + u32 *mux_table, + spinlock_t *lock, u8 id, + bool critical, int chg_pid) +{ + struct clk_master *master; + struct clk_hw *hw; + struct clk_init_data init; + unsigned long flags; + unsigned int val; + int ret; + + if (!name || !num_parents || !parent_names || !mux_table || + !lock || id > MASTER_MAX_ID) + return ERR_PTR(-EINVAL); + + master = kzalloc(sizeof(*master), GFP_KERNEL); + if (!master) + return ERR_PTR(-ENOMEM); + + init.name = name; + init.ops = &sama7g5_master_ops; + init.parent_names = parent_names; + init.num_parents = num_parents; + init.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE; + if (chg_pid >= 0) + init.flags |= CLK_SET_RATE_PARENT; + if (critical) + init.flags |= CLK_IS_CRITICAL; + + master->hw.init = &init; + master->regmap = regmap; + master->id = id; + master->chg_pid = chg_pid; + master->lock = lock; + master->mux_table = mux_table; + + spin_lock_irqsave(master->lock, flags); + regmap_write(master->regmap, PMC_MCR, master->id); + regmap_read(master->regmap, PMC_MCR, &val); + master->parent = (val & PMC_MCR_CSS) >> PMC_MCR_CSS_SHIFT; + master->div = (val & PMC_MCR_DIV) >> MASTER_DIV_SHIFT; + spin_unlock_irqrestore(master->lock, flags); + + hw = &master->hw; + ret = clk_hw_register(NULL, &master->hw); + if (ret) { + kfree(master); + hw = ERR_PTR(ret); + } + + return hw; +} + const struct clk_master_layout at91rm9200_master_layout = { .mask = 0x31F, .pres_shift = 2, diff --git a/drivers/clk/at91/pmc.h b/drivers/clk/at91/pmc.h index 2bfe1405f9f8..29d150feaa46 100644 --- a/drivers/clk/at91/pmc.h +++ b/drivers/clk/at91/pmc.h @@ -154,6 +154,13 @@ at91_clk_register_master(struct regmap *regmap, const char *name, const struct clk_master_layout *layout, const struct clk_master_characteristics *characteristics); +struct clk_hw * __init +at91_clk_sama7g5_register_master(struct regmap *regmap, + const char *name, int num_parents, + const char **parent_names, u32 *mux_table, + spinlock_t *lock, u8 id, bool critical, + int chg_pid); + struct clk_hw * __init at91_clk_register_peripheral(struct regmap *regmap, const char *name, const char *parent_name, u32 id); diff --git a/include/linux/clk/at91_pmc.h b/include/linux/clk/at91_pmc.h index 49a53a137610..77d6dabc4c3c 100644 --- a/include/linux/clk/at91_pmc.h +++ b/include/linux/clk/at91_pmc.h @@ -174,6 +174,7 @@ #define AT91_PMC_MOSCRCS (1 << 17) /* Main On-Chip RC [some SAM9] */ #define AT91_PMC_CFDEV (1 << 18) /* Clock Failure Detector Event [some SAM9] */ #define AT91_PMC_GCKRDY (1 << 24) /* Generated Clocks */ +#define AT91_PMC_MCKXRDY (1 << 26) /* Master Clock x [x=1..4] Ready Status */ #define AT91_PMC_IMR 0x6c /* Interrupt Mask Register */ #define AT91_PMC_FSMR 0x70 /* Fast Startup Mode Register */ -- cgit v1.2.3 From b4c115c76184f2c56a295579161652fd5eb2dcc1 Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Wed, 22 Jul 2020 10:38:21 +0300 Subject: clk: at91: clk-peripheral: add support for changeable parent rate Some peripheral clocks on SAMA7G5 supports requesting parent to change its rate (image related clocks: csi, csi2dc, isc). Add support so that if registered with this option the clock rate to be requested from parent. Signed-off-by: Claudiu Beznea Link: https://lore.kernel.org/r/1595403506-8209-14-git-send-email-claudiu.beznea@microchip.com Signed-off-by: Stephen Boyd --- drivers/clk/at91/at91sam9n12.c | 2 +- drivers/clk/at91/at91sam9x5.c | 4 +- drivers/clk/at91/clk-peripheral.c | 109 ++++++++++++++++++++++++++++++++++++-- drivers/clk/at91/dt-compat.c | 3 +- drivers/clk/at91/pmc.h | 3 +- drivers/clk/at91/sam9x60.c | 2 +- drivers/clk/at91/sama5d2.c | 5 +- drivers/clk/at91/sama5d3.c | 3 +- drivers/clk/at91/sama5d4.c | 4 +- 9 files changed, 119 insertions(+), 16 deletions(-) (limited to 'drivers/clk') diff --git a/drivers/clk/at91/at91sam9n12.c b/drivers/clk/at91/at91sam9n12.c index 4aa97e672bd6..aaf4da30584e 100644 --- a/drivers/clk/at91/at91sam9n12.c +++ b/drivers/clk/at91/at91sam9n12.c @@ -222,7 +222,7 @@ static void __init at91sam9n12_pmc_setup(struct device_node *np) at91sam9n12_periphck[i].n, "masterck", at91sam9n12_periphck[i].id, - &range); + &range, INT_MIN); if (IS_ERR(hw)) goto err_free; diff --git a/drivers/clk/at91/at91sam9x5.c b/drivers/clk/at91/at91sam9x5.c index 0ce3da080287..52a9d2f7ec83 100644 --- a/drivers/clk/at91/at91sam9x5.c +++ b/drivers/clk/at91/at91sam9x5.c @@ -257,7 +257,7 @@ static void __init at91sam9x5_pmc_setup(struct device_node *np, at91sam9x5_periphck[i].n, "masterck", at91sam9x5_periphck[i].id, - &range); + &range, INT_MIN); if (IS_ERR(hw)) goto err_free; @@ -270,7 +270,7 @@ static void __init at91sam9x5_pmc_setup(struct device_node *np, extra_pcks[i].n, "masterck", extra_pcks[i].id, - &range); + &range, INT_MIN); if (IS_ERR(hw)) goto err_free; diff --git a/drivers/clk/at91/clk-peripheral.c b/drivers/clk/at91/clk-peripheral.c index 4c9a4147dfe5..7867eaf0447f 100644 --- a/drivers/clk/at91/clk-peripheral.c +++ b/drivers/clk/at91/clk-peripheral.c @@ -38,6 +38,7 @@ struct clk_sam9x5_peripheral { u32 div; const struct clk_pcr_layout *layout; bool auto_div; + int chg_pid; }; #define to_clk_sam9x5_peripheral(hw) \ @@ -238,6 +239,87 @@ clk_sam9x5_peripheral_recalc_rate(struct clk_hw *hw, return parent_rate >> periph->div; } +static void clk_sam9x5_peripheral_best_diff(struct clk_rate_request *req, + struct clk_hw *parent, + unsigned long parent_rate, + u32 shift, long *best_diff, + long *best_rate) +{ + unsigned long tmp_rate = parent_rate >> shift; + unsigned long tmp_diff = abs(req->rate - tmp_rate); + + if (*best_diff < 0 || *best_diff >= tmp_diff) { + *best_rate = tmp_rate; + *best_diff = tmp_diff; + req->best_parent_rate = parent_rate; + req->best_parent_hw = parent; + } +} + +static int clk_sam9x5_peripheral_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw); + struct clk_hw *parent = clk_hw_get_parent(hw); + struct clk_rate_request req_parent = *req; + unsigned long parent_rate = clk_hw_get_rate(parent); + unsigned long tmp_rate; + long best_rate = LONG_MIN; + long best_diff = LONG_MIN; + u32 shift; + + if (periph->id < PERIPHERAL_ID_MIN || !periph->range.max) + return parent_rate; + + /* Fist step: check the available dividers. */ + for (shift = 0; shift <= PERIPHERAL_MAX_SHIFT; shift++) { + tmp_rate = parent_rate >> shift; + + if (periph->range.max && tmp_rate > periph->range.max) + continue; + + clk_sam9x5_peripheral_best_diff(req, parent, parent_rate, + shift, &best_diff, &best_rate); + + if (!best_diff || best_rate <= req->rate) + break; + } + + if (periph->chg_pid < 0) + goto end; + + /* Step two: try to request rate from parent. */ + parent = clk_hw_get_parent_by_index(hw, periph->chg_pid); + if (!parent) + goto end; + + for (shift = 0; shift <= PERIPHERAL_MAX_SHIFT; shift++) { + req_parent.rate = req->rate << shift; + + if (__clk_determine_rate(parent, &req_parent)) + continue; + + clk_sam9x5_peripheral_best_diff(req, parent, req_parent.rate, + shift, &best_diff, &best_rate); + + if (!best_diff) + break; + } +end: + if (best_rate < 0 || + (periph->range.max && best_rate > periph->range.max)) + return -EINVAL; + + pr_debug("PCK: %s, best_rate = %ld, parent clk: %s @ %ld\n", + __func__, best_rate, + __clk_get_name((req->best_parent_hw)->clk), + req->best_parent_rate); + + req->rate = best_rate; + + return 0; +} + static long clk_sam9x5_peripheral_round_rate(struct clk_hw *hw, unsigned long rate, unsigned long *parent_rate) @@ -320,11 +402,21 @@ static const struct clk_ops sam9x5_peripheral_ops = { .set_rate = clk_sam9x5_peripheral_set_rate, }; +static const struct clk_ops sam9x5_peripheral_chg_ops = { + .enable = clk_sam9x5_peripheral_enable, + .disable = clk_sam9x5_peripheral_disable, + .is_enabled = clk_sam9x5_peripheral_is_enabled, + .recalc_rate = clk_sam9x5_peripheral_recalc_rate, + .determine_rate = clk_sam9x5_peripheral_determine_rate, + .set_rate = clk_sam9x5_peripheral_set_rate, +}; + struct clk_hw * __init at91_clk_register_sam9x5_peripheral(struct regmap *regmap, spinlock_t *lock, const struct clk_pcr_layout *layout, const char *name, const char *parent_name, - u32 id, const struct clk_range *range) + u32 id, const struct clk_range *range, + int chg_pid) { struct clk_sam9x5_peripheral *periph; struct clk_init_data init; @@ -339,10 +431,16 @@ at91_clk_register_sam9x5_peripheral(struct regmap *regmap, spinlock_t *lock, return ERR_PTR(-ENOMEM); init.name = name; - init.ops = &sam9x5_peripheral_ops; - init.parent_names = (parent_name ? &parent_name : NULL); - init.num_parents = (parent_name ? 1 : 0); - init.flags = 0; + init.parent_names = &parent_name; + init.num_parents = 1; + if (chg_pid < 0) { + init.flags = 0; + init.ops = &sam9x5_peripheral_ops; + } else { + init.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE | + CLK_SET_RATE_PARENT; + init.ops = &sam9x5_peripheral_chg_ops; + } periph->id = id; periph->hw.init = &init; @@ -353,6 +451,7 @@ at91_clk_register_sam9x5_peripheral(struct regmap *regmap, spinlock_t *lock, periph->auto_div = true; periph->layout = layout; periph->range = *range; + periph->chg_pid = chg_pid; hw = &periph->hw; ret = clk_hw_register(NULL, &periph->hw); diff --git a/drivers/clk/at91/dt-compat.c b/drivers/clk/at91/dt-compat.c index cc95d42f4d53..1b90c4f7b1d1 100644 --- a/drivers/clk/at91/dt-compat.c +++ b/drivers/clk/at91/dt-compat.c @@ -463,7 +463,8 @@ of_at91_clk_periph_setup(struct device_node *np, u8 type) &dt_pcr_layout, name, parent_name, - id, &range); + id, &range, + INT_MIN); } if (IS_ERR(hw)) diff --git a/drivers/clk/at91/pmc.h b/drivers/clk/at91/pmc.h index 29d150feaa46..34c9506e6275 100644 --- a/drivers/clk/at91/pmc.h +++ b/drivers/clk/at91/pmc.h @@ -168,7 +168,8 @@ struct clk_hw * __init at91_clk_register_sam9x5_peripheral(struct regmap *regmap, spinlock_t *lock, const struct clk_pcr_layout *layout, const char *name, const char *parent_name, - u32 id, const struct clk_range *range); + u32 id, const struct clk_range *range, + int chg_pid); struct clk_hw * __init at91_clk_register_pll(struct regmap *regmap, const char *name, diff --git a/drivers/clk/at91/sam9x60.c b/drivers/clk/at91/sam9x60.c index 197ffc70305a..f090518d24a7 100644 --- a/drivers/clk/at91/sam9x60.c +++ b/drivers/clk/at91/sam9x60.c @@ -277,7 +277,7 @@ static void __init sam9x60_pmc_setup(struct device_node *np) sam9x60_periphck[i].n, "masterck", sam9x60_periphck[i].id, - &range); + &range, INT_MIN); if (IS_ERR(hw)) goto err_free; diff --git a/drivers/clk/at91/sama5d2.c b/drivers/clk/at91/sama5d2.c index 6a685d00f16d..c7765b664940 100644 --- a/drivers/clk/at91/sama5d2.c +++ b/drivers/clk/at91/sama5d2.c @@ -291,7 +291,7 @@ static void __init sama5d2_pmc_setup(struct device_node *np) sama5d2_periphck[i].n, "masterck", sama5d2_periphck[i].id, - &range); + &range, INT_MIN); if (IS_ERR(hw)) goto err_free; @@ -304,7 +304,8 @@ static void __init sama5d2_pmc_setup(struct device_node *np) sama5d2_periph32ck[i].n, "h32mxck", sama5d2_periph32ck[i].id, - &sama5d2_periph32ck[i].r); + &sama5d2_periph32ck[i].r, + INT_MIN); if (IS_ERR(hw)) goto err_free; diff --git a/drivers/clk/at91/sama5d3.c b/drivers/clk/at91/sama5d3.c index 5609b04e6565..49ec5964bcb3 100644 --- a/drivers/clk/at91/sama5d3.c +++ b/drivers/clk/at91/sama5d3.c @@ -223,7 +223,8 @@ static void __init sama5d3_pmc_setup(struct device_node *np) sama5d3_periphck[i].n, "masterck", sama5d3_periphck[i].id, - &sama5d3_periphck[i].r); + &sama5d3_periphck[i].r, + INT_MIN); if (IS_ERR(hw)) goto err_free; diff --git a/drivers/clk/at91/sama5d4.c b/drivers/clk/at91/sama5d4.c index 662ff5fa6e98..fa121897d95d 100644 --- a/drivers/clk/at91/sama5d4.c +++ b/drivers/clk/at91/sama5d4.c @@ -246,7 +246,7 @@ static void __init sama5d4_pmc_setup(struct device_node *np) sama5d4_periphck[i].n, "masterck", sama5d4_periphck[i].id, - &range); + &range, INT_MIN); if (IS_ERR(hw)) goto err_free; @@ -259,7 +259,7 @@ static void __init sama5d4_pmc_setup(struct device_node *np) sama5d4_periph32ck[i].n, "h32mxck", sama5d4_periph32ck[i].id, - &range); + &range, INT_MIN); if (IS_ERR(hw)) goto err_free; -- cgit v1.2.3 From c57aaaa28cf1a123c0029a36361a809eae2b1960 Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Wed, 22 Jul 2020 10:38:22 +0300 Subject: clk: at91: clk-programmable: add mux_table option Add mux table option. This is necessary for IP versions that has gaps in the range of available clock sources (e.g. SAMA7G5). Signed-off-by: Claudiu Beznea Link: https://lore.kernel.org/r/1595403506-8209-15-git-send-email-claudiu.beznea@microchip.com Signed-off-by: Stephen Boyd --- drivers/clk/at91/at91rm9200.c | 3 ++- drivers/clk/at91/at91sam9260.c | 3 ++- drivers/clk/at91/at91sam9g45.c | 3 ++- drivers/clk/at91/at91sam9n12.c | 3 ++- drivers/clk/at91/at91sam9rl.c | 3 ++- drivers/clk/at91/at91sam9x5.c | 3 ++- drivers/clk/at91/clk-programmable.c | 11 ++++++++++- drivers/clk/at91/dt-compat.c | 11 ++++++----- drivers/clk/at91/pmc.h | 3 ++- drivers/clk/at91/sam9x60.c | 3 ++- drivers/clk/at91/sama5d2.c | 3 ++- drivers/clk/at91/sama5d3.c | 3 ++- drivers/clk/at91/sama5d4.c | 3 ++- 13 files changed, 38 insertions(+), 17 deletions(-) (limited to 'drivers/clk') diff --git a/drivers/clk/at91/at91rm9200.c b/drivers/clk/at91/at91rm9200.c index 38bdb4981315..2c3d8e6ca63c 100644 --- a/drivers/clk/at91/at91rm9200.c +++ b/drivers/clk/at91/at91rm9200.c @@ -160,7 +160,8 @@ static void __init at91rm9200_pmc_setup(struct device_node *np) hw = at91_clk_register_programmable(regmap, name, parent_names, 4, i, - &at91rm9200_programmable_layout); + &at91rm9200_programmable_layout, + NULL); if (IS_ERR(hw)) goto err_free; diff --git a/drivers/clk/at91/at91sam9260.c b/drivers/clk/at91/at91sam9260.c index 6d0723aa8b13..bb81ff731ad8 100644 --- a/drivers/clk/at91/at91sam9260.c +++ b/drivers/clk/at91/at91sam9260.c @@ -436,7 +436,8 @@ static void __init at91sam926x_pmc_setup(struct device_node *np, hw = at91_clk_register_programmable(regmap, name, parent_names, 4, i, - &at91rm9200_programmable_layout); + &at91rm9200_programmable_layout, + NULL); if (IS_ERR(hw)) goto err_free; diff --git a/drivers/clk/at91/at91sam9g45.c b/drivers/clk/at91/at91sam9g45.c index fe9d391adeba..c88ee20bee31 100644 --- a/drivers/clk/at91/at91sam9g45.c +++ b/drivers/clk/at91/at91sam9g45.c @@ -181,7 +181,8 @@ static void __init at91sam9g45_pmc_setup(struct device_node *np) hw = at91_clk_register_programmable(regmap, name, parent_names, 5, i, - &at91sam9g45_programmable_layout); + &at91sam9g45_programmable_layout, + NULL); if (IS_ERR(hw)) goto err_free; diff --git a/drivers/clk/at91/at91sam9n12.c b/drivers/clk/at91/at91sam9n12.c index aaf4da30584e..93f7eb216122 100644 --- a/drivers/clk/at91/at91sam9n12.c +++ b/drivers/clk/at91/at91sam9n12.c @@ -199,7 +199,8 @@ static void __init at91sam9n12_pmc_setup(struct device_node *np) hw = at91_clk_register_programmable(regmap, name, parent_names, 5, i, - &at91sam9x5_programmable_layout); + &at91sam9x5_programmable_layout, + NULL); if (IS_ERR(hw)) goto err_free; diff --git a/drivers/clk/at91/at91sam9rl.c b/drivers/clk/at91/at91sam9rl.c index 0d1cc44b056f..a343eb69bb35 100644 --- a/drivers/clk/at91/at91sam9rl.c +++ b/drivers/clk/at91/at91sam9rl.c @@ -137,7 +137,8 @@ static void __init at91sam9rl_pmc_setup(struct device_node *np) hw = at91_clk_register_programmable(regmap, name, parent_names, 5, i, - &at91rm9200_programmable_layout); + &at91rm9200_programmable_layout, + NULL); if (IS_ERR(hw)) goto err_free; diff --git a/drivers/clk/at91/at91sam9x5.c b/drivers/clk/at91/at91sam9x5.c index 52a9d2f7ec83..22b9aad9efb8 100644 --- a/drivers/clk/at91/at91sam9x5.c +++ b/drivers/clk/at91/at91sam9x5.c @@ -226,7 +226,8 @@ static void __init at91sam9x5_pmc_setup(struct device_node *np, hw = at91_clk_register_programmable(regmap, name, parent_names, 5, i, - &at91sam9x5_programmable_layout); + &at91sam9x5_programmable_layout, + NULL); if (IS_ERR(hw)) goto err_free; diff --git a/drivers/clk/at91/clk-programmable.c b/drivers/clk/at91/clk-programmable.c index 8ee66fbee3d9..fcf8f6a1c2c6 100644 --- a/drivers/clk/at91/clk-programmable.c +++ b/drivers/clk/at91/clk-programmable.c @@ -21,6 +21,7 @@ struct clk_programmable { struct clk_hw hw; struct regmap *regmap; + u32 *mux_table; u8 id; const struct clk_programmable_layout *layout; }; @@ -108,6 +109,9 @@ static int clk_programmable_set_parent(struct clk_hw *hw, u8 index) if (layout->have_slck_mck) mask |= AT91_PMC_CSSMCK_MCK; + if (prog->mux_table) + pckr = clk_mux_index_to_val(prog->mux_table, 0, index); + if (index > layout->css_mask) { if (index > PROG_MAX_RM9200_CSS && !layout->have_slck_mck) return -EINVAL; @@ -134,6 +138,9 @@ static u8 clk_programmable_get_parent(struct clk_hw *hw) if (layout->have_slck_mck && (pckr & AT91_PMC_CSSMCK_MCK) && !ret) ret = PROG_MAX_RM9200_CSS + 1; + if (prog->mux_table) + ret = clk_mux_val_to_index(&prog->hw, prog->mux_table, 0, ret); + return ret; } @@ -182,7 +189,8 @@ struct clk_hw * __init at91_clk_register_programmable(struct regmap *regmap, const char *name, const char **parent_names, u8 num_parents, u8 id, - const struct clk_programmable_layout *layout) + const struct clk_programmable_layout *layout, + u32 *mux_table) { struct clk_programmable *prog; struct clk_hw *hw; @@ -206,6 +214,7 @@ at91_clk_register_programmable(struct regmap *regmap, prog->layout = layout; prog->hw.init = &init; prog->regmap = regmap; + prog->mux_table = mux_table; hw = &prog->hw; ret = clk_hw_register(NULL, &prog->hw); diff --git a/drivers/clk/at91/dt-compat.c b/drivers/clk/at91/dt-compat.c index 1b90c4f7b1d1..a50084de97d4 100644 --- a/drivers/clk/at91/dt-compat.c +++ b/drivers/clk/at91/dt-compat.c @@ -677,7 +677,8 @@ CLK_OF_DECLARE(at91sam9x5_clk_plldiv, "atmel,at91sam9x5-clk-plldiv", static void __init of_at91_clk_prog_setup(struct device_node *np, - const struct clk_programmable_layout *layout) + const struct clk_programmable_layout *layout, + u32 *mux_table) { int num; u32 id; @@ -711,7 +712,7 @@ of_at91_clk_prog_setup(struct device_node *np, hw = at91_clk_register_programmable(regmap, name, parent_names, num_parents, - id, layout); + id, layout, mux_table); if (IS_ERR(hw)) continue; @@ -721,21 +722,21 @@ of_at91_clk_prog_setup(struct device_node *np, static void __init of_at91rm9200_clk_prog_setup(struct device_node *np) { - of_at91_clk_prog_setup(np, &at91rm9200_programmable_layout); + of_at91_clk_prog_setup(np, &at91rm9200_programmable_layout, NULL); } CLK_OF_DECLARE(at91rm9200_clk_prog, "atmel,at91rm9200-clk-programmable", of_at91rm9200_clk_prog_setup); static void __init of_at91sam9g45_clk_prog_setup(struct device_node *np) { - of_at91_clk_prog_setup(np, &at91sam9g45_programmable_layout); + of_at91_clk_prog_setup(np, &at91sam9g45_programmable_layout, NULL); } CLK_OF_DECLARE(at91sam9g45_clk_prog, "atmel,at91sam9g45-clk-programmable", of_at91sam9g45_clk_prog_setup); static void __init of_at91sam9x5_clk_prog_setup(struct device_node *np) { - of_at91_clk_prog_setup(np, &at91sam9x5_programmable_layout); + of_at91_clk_prog_setup(np, &at91sam9x5_programmable_layout, NULL); } CLK_OF_DECLARE(at91sam9x5_clk_prog, "atmel,at91sam9x5-clk-programmable", of_at91sam9x5_clk_prog_setup); diff --git a/drivers/clk/at91/pmc.h b/drivers/clk/at91/pmc.h index 34c9506e6275..8d3e22f566cf 100644 --- a/drivers/clk/at91/pmc.h +++ b/drivers/clk/at91/pmc.h @@ -188,7 +188,8 @@ sam9x60_clk_register_pll(struct regmap *regmap, spinlock_t *lock, struct clk_hw * __init at91_clk_register_programmable(struct regmap *regmap, const char *name, const char **parent_names, u8 num_parents, u8 id, - const struct clk_programmable_layout *layout); + const struct clk_programmable_layout *layout, + u32 *mux_table); struct clk_hw * __init at91_clk_register_sam9260_slow(struct regmap *regmap, diff --git a/drivers/clk/at91/sam9x60.c b/drivers/clk/at91/sam9x60.c index f090518d24a7..d8d4b28ec4cb 100644 --- a/drivers/clk/at91/sam9x60.c +++ b/drivers/clk/at91/sam9x60.c @@ -254,7 +254,8 @@ static void __init sam9x60_pmc_setup(struct device_node *np) hw = at91_clk_register_programmable(regmap, name, parent_names, 6, i, - &sam9x60_programmable_layout); + &sam9x60_programmable_layout, + NULL); if (IS_ERR(hw)) goto err_free; diff --git a/drivers/clk/at91/sama5d2.c b/drivers/clk/at91/sama5d2.c index c7765b664940..8b220762941a 100644 --- a/drivers/clk/at91/sama5d2.c +++ b/drivers/clk/at91/sama5d2.c @@ -268,7 +268,8 @@ static void __init sama5d2_pmc_setup(struct device_node *np) hw = at91_clk_register_programmable(regmap, name, parent_names, 6, i, - &sama5d2_programmable_layout); + &sama5d2_programmable_layout, + NULL); if (IS_ERR(hw)) goto err_free; diff --git a/drivers/clk/at91/sama5d3.c b/drivers/clk/at91/sama5d3.c index 49ec5964bcb3..7c6e0a5b9dc8 100644 --- a/drivers/clk/at91/sama5d3.c +++ b/drivers/clk/at91/sama5d3.c @@ -200,7 +200,8 @@ static void __init sama5d3_pmc_setup(struct device_node *np) hw = at91_clk_register_programmable(regmap, name, parent_names, 5, i, - &at91sam9x5_programmable_layout); + &at91sam9x5_programmable_layout, + NULL); if (IS_ERR(hw)) goto err_free; diff --git a/drivers/clk/at91/sama5d4.c b/drivers/clk/at91/sama5d4.c index fa121897d95d..92d8d4141b43 100644 --- a/drivers/clk/at91/sama5d4.c +++ b/drivers/clk/at91/sama5d4.c @@ -223,7 +223,8 @@ static void __init sama5d4_pmc_setup(struct device_node *np) hw = at91_clk_register_programmable(regmap, name, parent_names, 5, i, - &at91sam9x5_programmable_layout); + &at91sam9x5_programmable_layout, + NULL); if (IS_ERR(hw)) goto err_free; -- cgit v1.2.3 From 43b1bb4a9b3e183af12225f56c27164c10d06223 Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Wed, 22 Jul 2020 10:38:24 +0300 Subject: clk: at91: clk-sam9x60-pll: re-factor to support plls with multiple outputs Some of the SAMA7G5 PLLs support multiple outputs (e.g. AUDIO PLL). For these, split the PLL clock in two: fractional clock and divider clock. In case PLLs supports multiple outputs (since these outputs are dividers (with different settings) sharing the same fractional part), it will register one fractional clock and multiple divider clocks (dividers sharing the fractional clock). Signed-off-by: Claudiu Beznea Link: https://lore.kernel.org/r/1595403506-8209-17-git-send-email-claudiu.beznea@microchip.com Signed-off-by: Stephen Boyd --- drivers/clk/at91/clk-sam9x60-pll.c | 547 +++++++++++++++++++++++++------------ drivers/clk/at91/pmc.h | 22 +- drivers/clk/at91/sam9x60.c | 50 +++- 3 files changed, 433 insertions(+), 186 deletions(-) (limited to 'drivers/clk') diff --git a/drivers/clk/at91/clk-sam9x60-pll.c b/drivers/clk/at91/clk-sam9x60-pll.c index 1f0bcc6b4fea..b473298ef7e6 100644 --- a/drivers/clk/at91/clk-sam9x60-pll.c +++ b/drivers/clk/at91/clk-sam9x60-pll.c @@ -25,20 +25,31 @@ #define FCORE_MIN (600000000) #define FCORE_MAX (1200000000) -#define PLL_MAX_ID 1 +#define PLL_MAX_ID 7 -struct sam9x60_pll { - struct clk_hw hw; +struct sam9x60_pll_core { struct regmap *regmap; spinlock_t *lock; const struct clk_pll_characteristics *characteristics; - u32 frac; + const struct clk_pll_layout *layout; + struct clk_hw hw; u8 id; - u8 div; +}; + +struct sam9x60_frac { + struct sam9x60_pll_core core; + u32 frac; u16 mul; }; -#define to_sam9x60_pll(hw) container_of(hw, struct sam9x60_pll, hw) +struct sam9x60_div { + struct sam9x60_pll_core core; + u8 div; +}; + +#define to_sam9x60_pll_core(hw) container_of(hw, struct sam9x60_pll_core, hw) +#define to_sam9x60_frac(core) container_of(core, struct sam9x60_frac, core) +#define to_sam9x60_div(core) container_of(core, struct sam9x60_div, core) static inline bool sam9x60_pll_ready(struct regmap *regmap, int id) { @@ -49,43 +60,53 @@ static inline bool sam9x60_pll_ready(struct regmap *regmap, int id) return !!(status & BIT(id)); } -static int sam9x60_pll_prepare(struct clk_hw *hw) +static bool sam9x60_frac_pll_ready(struct regmap *regmap, u8 id) { - struct sam9x60_pll *pll = to_sam9x60_pll(hw); - struct regmap *regmap = pll->regmap; - unsigned long flags; - u8 div; - u16 mul; - u32 val, frac; + return sam9x60_pll_ready(regmap, id); +} - spin_lock_irqsave(pll->lock, flags); - regmap_write(regmap, AT91_PMC_PLL_UPDT, pll->id); +static unsigned long sam9x60_frac_pll_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw); + struct sam9x60_frac *frac = to_sam9x60_frac(core); - regmap_read(regmap, AT91_PMC_PLL_CTRL0, &val); - div = FIELD_GET(PMC_PLL_CTRL0_DIV_MSK, val); + return (parent_rate * (frac->mul + 1) + + ((u64)parent_rate * frac->frac >> 22)); +} +static int sam9x60_frac_pll_prepare(struct clk_hw *hw) +{ + struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw); + struct sam9x60_frac *frac = to_sam9x60_frac(core); + struct regmap *regmap = core->regmap; + unsigned int val, cfrac, cmul; + unsigned long flags; + + spin_lock_irqsave(core->lock, flags); + + regmap_update_bits(regmap, AT91_PMC_PLL_UPDT, + AT91_PMC_PLL_UPDT_ID_MSK, core->id); regmap_read(regmap, AT91_PMC_PLL_CTRL1, &val); - mul = FIELD_GET(PMC_PLL_CTRL1_MUL_MSK, val); - frac = FIELD_GET(PMC_PLL_CTRL1_FRACR_MSK, val); + cmul = (val & core->layout->mul_mask) >> core->layout->mul_shift; + cfrac = (val & core->layout->frac_mask) >> core->layout->frac_shift; - if (sam9x60_pll_ready(regmap, pll->id) && - (div == pll->div && mul == pll->mul && frac == pll->frac)) { - spin_unlock_irqrestore(pll->lock, flags); - return 0; - } + if (sam9x60_frac_pll_ready(regmap, core->id) && + (cmul == frac->mul && cfrac == frac->frac)) + goto unlock; - /* Recommended value for AT91_PMC_PLL_ACR */ - if (pll->characteristics->upll) + /* Recommended value for PMC_PLL_ACR */ + if (core->characteristics->upll) val = AT91_PMC_PLL_ACR_DEFAULT_UPLL; else val = AT91_PMC_PLL_ACR_DEFAULT_PLLA; regmap_write(regmap, AT91_PMC_PLL_ACR, val); regmap_write(regmap, AT91_PMC_PLL_CTRL1, - FIELD_PREP(PMC_PLL_CTRL1_MUL_MSK, pll->mul) | - FIELD_PREP(PMC_PLL_CTRL1_FRACR_MSK, pll->frac)); + (frac->mul << core->layout->mul_shift) | + (frac->frac << core->layout->frac_shift)); - if (pll->characteristics->upll) { + if (core->characteristics->upll) { /* Enable the UTMI internal bandgap */ val |= AT91_PMC_PLL_ACR_UTMIBG; regmap_write(regmap, AT91_PMC_PLL_ACR, val); @@ -100,229 +121,409 @@ static int sam9x60_pll_prepare(struct clk_hw *hw) } regmap_update_bits(regmap, AT91_PMC_PLL_UPDT, - AT91_PMC_PLL_UPDT_UPDATE, AT91_PMC_PLL_UPDT_UPDATE); + AT91_PMC_PLL_UPDT_UPDATE | AT91_PMC_PLL_UPDT_ID_MSK, + AT91_PMC_PLL_UPDT_UPDATE | core->id); - regmap_write(regmap, AT91_PMC_PLL_CTRL0, - AT91_PMC_PLL_CTRL0_ENLOCK | AT91_PMC_PLL_CTRL0_ENPLL | - AT91_PMC_PLL_CTRL0_ENPLLCK | pll->div); + regmap_update_bits(regmap, AT91_PMC_PLL_CTRL0, + AT91_PMC_PLL_CTRL0_ENLOCK | AT91_PMC_PLL_CTRL0_ENPLL, + AT91_PMC_PLL_CTRL0_ENLOCK | AT91_PMC_PLL_CTRL0_ENPLL); regmap_update_bits(regmap, AT91_PMC_PLL_UPDT, - AT91_PMC_PLL_UPDT_UPDATE, AT91_PMC_PLL_UPDT_UPDATE); + AT91_PMC_PLL_UPDT_UPDATE | AT91_PMC_PLL_UPDT_ID_MSK, + AT91_PMC_PLL_UPDT_UPDATE | core->id); - while (!sam9x60_pll_ready(regmap, pll->id)) + while (!sam9x60_pll_ready(regmap, core->id)) cpu_relax(); - spin_unlock_irqrestore(pll->lock, flags); +unlock: + spin_unlock_irqrestore(core->lock, flags); return 0; } -static int sam9x60_pll_is_prepared(struct clk_hw *hw) +static void sam9x60_frac_pll_unprepare(struct clk_hw *hw) { - struct sam9x60_pll *pll = to_sam9x60_pll(hw); + struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw); + struct regmap *regmap = core->regmap; + unsigned long flags; - return sam9x60_pll_ready(pll->regmap, pll->id); + spin_lock_irqsave(core->lock, flags); + + regmap_update_bits(regmap, AT91_PMC_PLL_UPDT, + AT91_PMC_PLL_UPDT_ID_MSK, core->id); + + regmap_update_bits(regmap, AT91_PMC_PLL_CTRL0, AT91_PMC_PLL_CTRL0_ENPLL, 0); + + if (core->characteristics->upll) + regmap_update_bits(regmap, AT91_PMC_PLL_ACR, + AT91_PMC_PLL_ACR_UTMIBG | AT91_PMC_PLL_ACR_UTMIVR, 0); + + regmap_update_bits(regmap, AT91_PMC_PLL_UPDT, + AT91_PMC_PLL_UPDT_UPDATE | AT91_PMC_PLL_UPDT_ID_MSK, + AT91_PMC_PLL_UPDT_UPDATE | core->id); + + spin_unlock_irqrestore(core->lock, flags); } -static void sam9x60_pll_unprepare(struct clk_hw *hw) +static int sam9x60_frac_pll_is_prepared(struct clk_hw *hw) { - struct sam9x60_pll *pll = to_sam9x60_pll(hw); - unsigned long flags; + struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw); + + return sam9x60_pll_ready(core->regmap, core->id); +} - spin_lock_irqsave(pll->lock, flags); +static long sam9x60_frac_pll_compute_mul_frac(struct sam9x60_pll_core *core, + unsigned long rate, + unsigned long parent_rate, + bool update) +{ + struct sam9x60_frac *frac = to_sam9x60_frac(core); + unsigned long tmprate, remainder; + unsigned long nmul = 0; + unsigned long nfrac = 0; - regmap_write(pll->regmap, AT91_PMC_PLL_UPDT, pll->id); + if (rate < FCORE_MIN || rate > FCORE_MAX) + return -ERANGE; - regmap_update_bits(pll->regmap, AT91_PMC_PLL_CTRL0, - AT91_PMC_PLL_CTRL0_ENPLLCK, 0); + /* + * Calculate the multiplier associated with the current + * divider that provide the closest rate to the requested one. + */ + nmul = mult_frac(rate, 1, parent_rate); + tmprate = mult_frac(parent_rate, nmul, 1); + remainder = rate - tmprate; - regmap_update_bits(pll->regmap, AT91_PMC_PLL_UPDT, - AT91_PMC_PLL_UPDT_UPDATE, AT91_PMC_PLL_UPDT_UPDATE); + if (remainder) { + nfrac = DIV_ROUND_CLOSEST_ULL((u64)remainder * (1 << 22), + parent_rate); - regmap_update_bits(pll->regmap, AT91_PMC_PLL_CTRL0, - AT91_PMC_PLL_CTRL0_ENPLL, 0); + tmprate += DIV_ROUND_CLOSEST_ULL((u64)nfrac * parent_rate, + (1 << 22)); + } - if (pll->characteristics->upll) - regmap_update_bits(pll->regmap, AT91_PMC_PLL_ACR, - AT91_PMC_PLL_ACR_UTMIBG | - AT91_PMC_PLL_ACR_UTMIVR, 0); + /* Check if resulted rate is a valid. */ + if (tmprate < FCORE_MIN || tmprate > FCORE_MAX) + return -ERANGE; - regmap_update_bits(pll->regmap, AT91_PMC_PLL_UPDT, - AT91_PMC_PLL_UPDT_UPDATE, AT91_PMC_PLL_UPDT_UPDATE); + if (update) { + frac->mul = nmul - 1; + frac->frac = nfrac; + } - spin_unlock_irqrestore(pll->lock, flags); + return tmprate; } -static unsigned long sam9x60_pll_recalc_rate(struct clk_hw *hw, - unsigned long parent_rate) +static long sam9x60_frac_pll_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) { - struct sam9x60_pll *pll = to_sam9x60_pll(hw); + struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw); - return DIV_ROUND_CLOSEST_ULL((parent_rate * (pll->mul + 1) + - ((u64)parent_rate * pll->frac >> 22)), (pll->div + 1)); + return sam9x60_frac_pll_compute_mul_frac(core, rate, *parent_rate, false); } -static long sam9x60_pll_get_best_div_mul(struct sam9x60_pll *pll, - unsigned long rate, - unsigned long parent_rate, - bool update) +static int sam9x60_frac_pll_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) { - 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; - u64 fcore = 0; + struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw); - if (rate < characteristics->output[0].min || - rate > characteristics->output[0].max) - return -ERANGE; + return sam9x60_frac_pll_compute_mul_frac(core, rate, parent_rate, true); +} - if (!pll->characteristics->upll) { - mindiv = parent_rate / rate; - if (mindiv < 2) - mindiv = 2; +static const struct clk_ops sam9x60_frac_pll_ops = { + .prepare = sam9x60_frac_pll_prepare, + .unprepare = sam9x60_frac_pll_unprepare, + .is_prepared = sam9x60_frac_pll_is_prepared, + .recalc_rate = sam9x60_frac_pll_recalc_rate, + .round_rate = sam9x60_frac_pll_round_rate, + .set_rate = sam9x60_frac_pll_set_rate, +}; - maxdiv = DIV_ROUND_UP(parent_rate * PLL_MUL_MAX, rate); - if (maxdiv > PLL_DIV_MAX) - maxdiv = PLL_DIV_MAX; - } else { - mindiv = maxdiv = UPLL_DIV; - } +static int sam9x60_div_pll_prepare(struct clk_hw *hw) +{ + struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw); + struct sam9x60_div *div = to_sam9x60_div(core); + struct regmap *regmap = core->regmap; + unsigned long flags; + unsigned int val, cdiv; - for (tmpdiv = mindiv; tmpdiv <= maxdiv; tmpdiv++) { - unsigned long remainder; - unsigned long tmprate; - unsigned long tmpmul; - unsigned long tmpfrac = 0; + spin_lock_irqsave(core->lock, flags); + regmap_update_bits(regmap, AT91_PMC_PLL_UPDT, + AT91_PMC_PLL_UPDT_ID_MSK, core->id); + regmap_read(regmap, AT91_PMC_PLL_CTRL0, &val); + cdiv = (val & core->layout->div_mask) >> core->layout->div_shift; - /* - * 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; + /* Stop if enabled an nothing changed. */ + if (!!(val & core->layout->endiv_mask) && cdiv == div->div) + goto unlock; - if (remainder) { - tmpfrac = DIV_ROUND_CLOSEST_ULL((u64)remainder * tmpdiv * (1 << 22), - parent_rate); + regmap_update_bits(regmap, AT91_PMC_PLL_CTRL0, + core->layout->div_mask | core->layout->endiv_mask, + (div->div << core->layout->div_shift) | + (1 << core->layout->endiv_shift)); - tmprate += DIV_ROUND_CLOSEST_ULL((u64)tmpfrac * parent_rate, - tmpdiv * (1 << 22)); + regmap_update_bits(regmap, AT91_PMC_PLL_UPDT, + AT91_PMC_PLL_UPDT_UPDATE | AT91_PMC_PLL_UPDT_ID_MSK, + AT91_PMC_PLL_UPDT_UPDATE | core->id); - if (tmprate > rate) - remainder = tmprate - rate; - else - remainder = rate - tmprate; - } + while (!sam9x60_pll_ready(regmap, core->id)) + cpu_relax(); + +unlock: + spin_unlock_irqrestore(core->lock, flags); + + return 0; +} + +static void sam9x60_div_pll_unprepare(struct clk_hw *hw) +{ + struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw); + struct regmap *regmap = core->regmap; + unsigned long flags; + + spin_lock_irqsave(core->lock, flags); + + regmap_update_bits(regmap, AT91_PMC_PLL_UPDT, + AT91_PMC_PLL_UPDT_ID_MSK, core->id); + + regmap_update_bits(regmap, AT91_PMC_PLL_CTRL0, + core->layout->endiv_mask, 0); + + regmap_update_bits(regmap, AT91_PMC_PLL_UPDT, + AT91_PMC_PLL_UPDT_UPDATE | AT91_PMC_PLL_UPDT_ID_MSK, + AT91_PMC_PLL_UPDT_UPDATE | core->id); + + spin_unlock_irqrestore(core->lock, flags); +} + +static int sam9x60_div_pll_is_prepared(struct clk_hw *hw) +{ + struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw); + struct regmap *regmap = core->regmap; + unsigned long flags; + unsigned int val; + + spin_lock_irqsave(core->lock, flags); + + regmap_update_bits(regmap, AT91_PMC_PLL_UPDT, + AT91_PMC_PLL_UPDT_ID_MSK, core->id); + regmap_read(regmap, AT91_PMC_PLL_CTRL0, &val); + + spin_unlock_irqrestore(core->lock, flags); - fcore = parent_rate * (tmpmul + 1) + - ((u64)parent_rate * tmpfrac >> 22); - if (fcore < FCORE_MIN || fcore > FCORE_MAX) + return !!(val & core->layout->endiv_mask); +} + +static unsigned long sam9x60_div_pll_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw); + struct sam9x60_div *div = to_sam9x60_div(core); + + return DIV_ROUND_CLOSEST_ULL(parent_rate, (div->div + 1)); +} + +static long sam9x60_div_pll_compute_div(struct sam9x60_pll_core *core, + unsigned long *parent_rate, + unsigned long rate) +{ + const struct clk_pll_characteristics *characteristics = + core->characteristics; + struct clk_hw *parent = clk_hw_get_parent(&core->hw); + unsigned long tmp_rate, tmp_parent_rate, tmp_diff; + long best_diff = -1, best_rate = -EINVAL; + u32 divid, best_div; + + if (!rate) + return 0; + + if (rate < characteristics->output[0].min || + rate > characteristics->output[0].max) + return -ERANGE; + + for (divid = 1; divid < core->layout->div_mask; divid++) { + tmp_parent_rate = clk_hw_round_rate(parent, rate * divid); + if (!tmp_parent_rate) continue; - /* - * 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; + tmp_rate = DIV_ROUND_CLOSEST_ULL(tmp_parent_rate, divid); + tmp_diff = abs(rate - tmp_rate); + + if (best_diff < 0 || best_diff > tmp_diff) { + *parent_rate = tmp_parent_rate; + best_rate = tmp_rate; + best_diff = tmp_diff; + best_div = divid; } - /* We've found a perfect match! */ - if (!remainder) + if (!best_diff) break; } - /* Check if bestrate is a valid output rate */ - if (fcore < FCORE_MIN || fcore > FCORE_MAX || - bestrate < characteristics->output[0].min || - bestrate > characteristics->output[0].max) + if (best_rate < characteristics->output[0].min || + best_rate > characteristics->output[0].max) return -ERANGE; - if (update) { - pll->div = bestdiv - 1; - pll->mul = bestmul - 1; - pll->frac = bestfrac; - } - - return bestrate; + return best_rate; } -static long sam9x60_pll_round_rate(struct clk_hw *hw, unsigned long rate, - unsigned long *parent_rate) +static long sam9x60_div_pll_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) { - struct sam9x60_pll *pll = to_sam9x60_pll(hw); + struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw); - return sam9x60_pll_get_best_div_mul(pll, rate, *parent_rate, false); + return sam9x60_div_pll_compute_div(core, parent_rate, rate); } -static int sam9x60_pll_set_rate(struct clk_hw *hw, unsigned long rate, - unsigned long parent_rate) +static int sam9x60_div_pll_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) { - struct sam9x60_pll *pll = to_sam9x60_pll(hw); + struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw); + struct sam9x60_div *div = to_sam9x60_div(core); + + div->div = DIV_ROUND_CLOSEST(parent_rate, rate) - 1; - return sam9x60_pll_get_best_div_mul(pll, rate, parent_rate, true); + return 0; } -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, +static const struct clk_ops sam9x60_div_pll_ops = { + .prepare = sam9x60_div_pll_prepare, + .unprepare = sam9x60_div_pll_unprepare, + .is_prepared = sam9x60_div_pll_is_prepared, + .recalc_rate = sam9x60_div_pll_recalc_rate, + .round_rate = sam9x60_div_pll_round_rate, + .set_rate = sam9x60_div_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) +sam9x60_clk_register_frac_pll(struct regmap *regmap, spinlock_t *lock, + const char *name, const char *parent_name, + struct clk_hw *parent_hw, u8 id, + const struct clk_pll_characteristics *characteristics, + const struct clk_pll_layout *layout, bool critical) { - struct sam9x60_pll *pll; + struct sam9x60_frac *frac; struct clk_hw *hw; struct clk_init_data init; - unsigned int pllr; + unsigned long parent_rate, flags; + unsigned int val; int ret; - if (id > PLL_MAX_ID) + if (id > PLL_MAX_ID || !lock || !parent_hw) return ERR_PTR(-EINVAL); - pll = kzalloc(sizeof(*pll), GFP_KERNEL); - if (!pll) + frac = kzalloc(sizeof(*frac), GFP_KERNEL); + if (!frac) return ERR_PTR(-ENOMEM); init.name = name; - init.ops = &pll_ops; init.parent_names = &parent_name; init.num_parents = 1; + init.ops = &sam9x60_frac_pll_ops; init.flags = CLK_SET_RATE_GATE; + if (critical) + init.flags |= CLK_IS_CRITICAL; + + frac->core.id = id; + frac->core.hw.init = &init; + frac->core.characteristics = characteristics; + frac->core.layout = layout; + frac->core.regmap = regmap; + frac->core.lock = lock; + + spin_lock_irqsave(frac->core.lock, flags); + if (sam9x60_pll_ready(regmap, id)) { + regmap_update_bits(regmap, AT91_PMC_PLL_UPDT, + AT91_PMC_PLL_UPDT_ID_MSK, id); + regmap_read(regmap, AT91_PMC_PLL_CTRL1, &val); + frac->mul = FIELD_GET(PMC_PLL_CTRL1_MUL_MSK, val); + frac->frac = FIELD_GET(PMC_PLL_CTRL1_FRACR_MSK, val); + } else { + /* + * This means the PLL is not setup by bootloaders. In this + * case we need to set the minimum rate for it. Otherwise + * a clock child of this PLL may be enabled before setting + * its rate leading to enabling this PLL with unsupported + * rate. This will lead to PLL not being locked at all. + */ + parent_rate = clk_hw_get_rate(parent_hw); + if (!parent_rate) { + hw = ERR_PTR(-EINVAL); + goto free; + } + + ret = sam9x60_frac_pll_compute_mul_frac(&frac->core, FCORE_MIN, + parent_rate, true); + if (ret <= 0) { + hw = ERR_PTR(ret); + goto free; + } + } + spin_unlock_irqrestore(frac->core.lock, flags); + + hw = &frac->core.hw; + ret = clk_hw_register(NULL, hw); + if (ret) { + kfree(frac); + hw = ERR_PTR(ret); + } - pll->id = id; - pll->hw.init = &init; - pll->characteristics = characteristics; - pll->regmap = regmap; - pll->lock = lock; + return hw; + +free: + spin_unlock_irqrestore(frac->core.lock, flags); + kfree(frac); + return hw; +} + +struct clk_hw * __init +sam9x60_clk_register_div_pll(struct regmap *regmap, spinlock_t *lock, + const char *name, const char *parent_name, u8 id, + const struct clk_pll_characteristics *characteristics, + const struct clk_pll_layout *layout, bool critical) +{ + struct sam9x60_div *div; + struct clk_hw *hw; + struct clk_init_data init; + unsigned long flags; + unsigned int val; + int ret; + + if (id > PLL_MAX_ID || !lock) + return ERR_PTR(-EINVAL); + + div = kzalloc(sizeof(*div), GFP_KERNEL); + if (!div) + return ERR_PTR(-ENOMEM); + + init.name = name; + init.parent_names = &parent_name; + init.num_parents = 1; + init.ops = &sam9x60_div_pll_ops; + init.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE | + CLK_SET_RATE_PARENT; + if (critical) + init.flags |= CLK_IS_CRITICAL; + + div->core.id = id; + div->core.hw.init = &init; + div->core.characteristics = characteristics; + div->core.layout = layout; + div->core.regmap = regmap; + div->core.lock = lock; + + spin_lock_irqsave(div->core.lock, flags); + + regmap_update_bits(regmap, AT91_PMC_PLL_UPDT, + AT91_PMC_PLL_UPDT_ID_MSK, id); + regmap_read(regmap, AT91_PMC_PLL_CTRL0, &val); + div->div = FIELD_GET(PMC_PLL_CTRL0_DIV_MSK, val); - regmap_write(regmap, AT91_PMC_PLL_UPDT, id); - regmap_read(regmap, AT91_PMC_PLL_CTRL0, &pllr); - pll->div = FIELD_GET(PMC_PLL_CTRL0_DIV_MSK, pllr); - regmap_read(regmap, AT91_PMC_PLL_CTRL1, &pllr); - pll->mul = FIELD_GET(PMC_PLL_CTRL1_MUL_MSK, pllr); + spin_unlock_irqrestore(div->core.lock, flags); - hw = &pll->hw; + hw = &div->core.hw; ret = clk_hw_register(NULL, hw); if (ret) { - kfree(pll); + kfree(div); hw = ERR_PTR(ret); } diff --git a/drivers/clk/at91/pmc.h b/drivers/clk/at91/pmc.h index 8d3e22f566cf..6340b9be8205 100644 --- a/drivers/clk/at91/pmc.h +++ b/drivers/clk/at91/pmc.h @@ -54,8 +54,14 @@ struct clk_master_characteristics { struct clk_pll_layout { u32 pllr_mask; - u16 mul_mask; + u32 mul_mask; + u32 frac_mask; + u32 div_mask; + u32 endiv_mask; u8 mul_shift; + u8 frac_shift; + u8 div_shift; + u8 endiv_shift; }; extern const struct clk_pll_layout at91rm9200_pll_layout; @@ -181,9 +187,17 @@ 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); +sam9x60_clk_register_div_pll(struct regmap *regmap, spinlock_t *lock, + const char *name, const char *parent_name, u8 id, + const struct clk_pll_characteristics *characteristics, + const struct clk_pll_layout *layout, bool critical); + +struct clk_hw * __init +sam9x60_clk_register_frac_pll(struct regmap *regmap, spinlock_t *lock, + const char *name, const char *parent_name, + struct clk_hw *parent_hw, u8 id, + const struct clk_pll_characteristics *characteristics, + const struct clk_pll_layout *layout, bool critical); struct clk_hw * __init at91_clk_register_programmable(struct regmap *regmap, const char *name, diff --git a/drivers/clk/at91/sam9x60.c b/drivers/clk/at91/sam9x60.c index d8d4b28ec4cb..ab6318c0589e 100644 --- a/drivers/clk/at91/sam9x60.c +++ b/drivers/clk/at91/sam9x60.c @@ -42,6 +42,20 @@ static const struct clk_pll_characteristics upll_characteristics = { .upll = true, }; +static const struct clk_pll_layout pll_frac_layout = { + .mul_mask = GENMASK(31, 24), + .frac_mask = GENMASK(21, 0), + .mul_shift = 24, + .frac_shift = 0, +}; + +static const struct clk_pll_layout pll_div_layout = { + .div_mask = GENMASK(7, 0), + .endiv_mask = BIT(29), + .div_shift = 0, + .endiv_shift = 29, +}; + static const struct clk_programmable_layout sam9x60_programmable_layout = { .pres_mask = 0xff, .pres_shift = 8, @@ -156,6 +170,7 @@ static void __init sam9x60_pmc_setup(struct device_node *np) const char *td_slck_name, *md_slck_name, *mainxtal_name; struct pmc_data *sam9x60_pmc; const char *parent_names[6]; + struct clk_hw *main_osc_hw; struct regmap *regmap; struct clk_hw *hw; int i; @@ -200,6 +215,7 @@ static void __init sam9x60_pmc_setup(struct device_node *np) bypass); if (IS_ERR(hw)) goto err_free; + main_osc_hw = hw; parent_names[0] = "main_rc_osc"; parent_names[1] = "main_osc"; @@ -209,15 +225,31 @@ static void __init sam9x60_pmc_setup(struct device_node *np) sam9x60_pmc->chws[PMC_MAIN] = hw; - hw = sam9x60_clk_register_pll(regmap, &pmc_pll_lock, "pllack", - "mainck", 0, &plla_characteristics); + hw = sam9x60_clk_register_frac_pll(regmap, &pmc_pll_lock, "pllack_fracck", + "mainck", sam9x60_pmc->chws[PMC_MAIN], + 0, &plla_characteristics, + &pll_frac_layout, true); + if (IS_ERR(hw)) + goto err_free; + + hw = sam9x60_clk_register_div_pll(regmap, &pmc_pll_lock, "pllack_divck", + "pllack_fracck", 0, &plla_characteristics, + &pll_div_layout, true); if (IS_ERR(hw)) goto err_free; sam9x60_pmc->chws[PMC_PLLACK] = hw; - hw = sam9x60_clk_register_pll(regmap, &pmc_pll_lock, "upllck", - "main_osc", 1, &upll_characteristics); + hw = sam9x60_clk_register_frac_pll(regmap, &pmc_pll_lock, "upllck_fracck", + "main_osc", main_osc_hw, 1, + &upll_characteristics, + &pll_frac_layout, false); + if (IS_ERR(hw)) + goto err_free; + + hw = sam9x60_clk_register_div_pll(regmap, &pmc_pll_lock, "upllck_divck", + "upllck_fracck", 1, &upll_characteristics, + &pll_div_layout, false); if (IS_ERR(hw)) goto err_free; @@ -225,7 +257,7 @@ static void __init sam9x60_pmc_setup(struct device_node *np) parent_names[0] = md_slck_name; parent_names[1] = "mainck"; - parent_names[2] = "pllack"; + parent_names[2] = "pllack_divck"; hw = at91_clk_register_master(regmap, "masterck", 3, parent_names, &sam9x60_master_layout, &mck_characteristics); @@ -234,8 +266,8 @@ static void __init sam9x60_pmc_setup(struct device_node *np) sam9x60_pmc->chws[PMC_MCK] = hw; - parent_names[0] = "pllack"; - parent_names[1] = "upllck"; + parent_names[0] = "pllack_divck"; + parent_names[1] = "upllck_divck"; parent_names[2] = "main_osc"; hw = sam9x60_clk_register_usb(regmap, "usbck", parent_names, 3); if (IS_ERR(hw)) @@ -245,8 +277,8 @@ static void __init sam9x60_pmc_setup(struct device_node *np) parent_names[1] = td_slck_name; parent_names[2] = "mainck"; parent_names[3] = "masterck"; - parent_names[4] = "pllack"; - parent_names[5] = "upllck"; + parent_names[4] = "pllack_divck"; + parent_names[5] = "upllck_divck"; for (i = 0; i < 8; i++) { char name[6]; -- cgit v1.2.3 From ef396df99251b848596c717b63ff4fe74a941193 Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Wed, 22 Jul 2020 10:38:25 +0300 Subject: clk: at91: clk-utmi: add utmi support for sama7g5 Add UTMI support for SAMA7G5. SAMA7G5's UTMI control is done via XTALF register. Values written at bits 2..0 in this register correspond to the on board crystal oscillator frequency. Signed-off-by: Claudiu Beznea Link: https://lore.kernel.org/r/1595403506-8209-18-git-send-email-claudiu.beznea@microchip.com Signed-off-by: Stephen Boyd --- drivers/clk/at91/clk-utmi.c | 103 ++++++++++++++++++++++++++++++++++++++++--- drivers/clk/at91/pmc.h | 4 ++ include/linux/clk/at91_pmc.h | 2 + 3 files changed, 104 insertions(+), 5 deletions(-) (limited to 'drivers/clk') diff --git a/drivers/clk/at91/clk-utmi.c b/drivers/clk/at91/clk-utmi.c index f1ef4e1f41a9..df9f3fc3b6a6 100644 --- a/drivers/clk/at91/clk-utmi.c +++ b/drivers/clk/at91/clk-utmi.c @@ -120,9 +120,11 @@ static const struct clk_ops utmi_ops = { .recalc_rate = clk_utmi_recalc_rate, }; -struct clk_hw * __init -at91_clk_register_utmi(struct regmap *regmap_pmc, struct regmap *regmap_sfr, - const char *name, const char *parent_name) +static struct clk_hw * __init +at91_clk_register_utmi_internal(struct regmap *regmap_pmc, + struct regmap *regmap_sfr, + const char *name, const char *parent_name, + const struct clk_ops *ops, unsigned long flags) { struct clk_utmi *utmi; struct clk_hw *hw; @@ -134,10 +136,10 @@ at91_clk_register_utmi(struct regmap *regmap_pmc, struct regmap *regmap_sfr, return ERR_PTR(-ENOMEM); init.name = name; - init.ops = &utmi_ops; + init.ops = ops; init.parent_names = parent_name ? &parent_name : NULL; init.num_parents = parent_name ? 1 : 0; - init.flags = CLK_SET_RATE_GATE; + init.flags = flags; utmi->hw.init = &init; utmi->regmap_pmc = regmap_pmc; @@ -152,3 +154,94 @@ at91_clk_register_utmi(struct regmap *regmap_pmc, struct regmap *regmap_sfr, return hw; } + +struct clk_hw * __init +at91_clk_register_utmi(struct regmap *regmap_pmc, struct regmap *regmap_sfr, + const char *name, const char *parent_name) +{ + return at91_clk_register_utmi_internal(regmap_pmc, regmap_sfr, name, + parent_name, &utmi_ops, CLK_SET_RATE_GATE); +} + +static int clk_utmi_sama7g5_prepare(struct clk_hw *hw) +{ + struct clk_utmi *utmi = to_clk_utmi(hw); + struct clk_hw *hw_parent; + unsigned long parent_rate; + unsigned int val; + + hw_parent = clk_hw_get_parent(hw); + parent_rate = clk_hw_get_rate(hw_parent); + + switch (parent_rate) { + case 16000000: + val = 0; + break; + case 20000000: + val = 2; + break; + case 24000000: + val = 3; + break; + case 32000000: + val = 5; + break; + default: + pr_err("UTMICK: unsupported main_xtal rate\n"); + return -EINVAL; + } + + regmap_write(utmi->regmap_pmc, AT91_PMC_XTALF, val); + + return 0; + +} + +static int clk_utmi_sama7g5_is_prepared(struct clk_hw *hw) +{ + struct clk_utmi *utmi = to_clk_utmi(hw); + struct clk_hw *hw_parent; + unsigned long parent_rate; + unsigned int val; + + hw_parent = clk_hw_get_parent(hw); + parent_rate = clk_hw_get_rate(hw_parent); + + regmap_read(utmi->regmap_pmc, AT91_PMC_XTALF, &val); + switch (val & 0x7) { + case 0: + if (parent_rate == 16000000) + return 1; + break; + case 2: + if (parent_rate == 20000000) + return 1; + break; + case 3: + if (parent_rate == 24000000) + return 1; + break; + case 5: + if (parent_rate == 32000000) + return 1; + break; + default: + break; + } + + return 0; +} + +static const struct clk_ops sama7g5_utmi_ops = { + .prepare = clk_utmi_sama7g5_prepare, + .is_prepared = clk_utmi_sama7g5_is_prepared, + .recalc_rate = clk_utmi_recalc_rate, +}; + +struct clk_hw * __init +at91_clk_sama7g5_register_utmi(struct regmap *regmap_pmc, const char *name, + const char *parent_name) +{ + return at91_clk_register_utmi_internal(regmap_pmc, NULL, name, + parent_name, &sama7g5_utmi_ops, 0); +} diff --git a/drivers/clk/at91/pmc.h b/drivers/clk/at91/pmc.h index 6340b9be8205..7b86affc6d7c 100644 --- a/drivers/clk/at91/pmc.h +++ b/drivers/clk/at91/pmc.h @@ -236,6 +236,10 @@ struct clk_hw * __init at91_clk_register_utmi(struct regmap *regmap_pmc, struct regmap *regmap_sfr, const char *name, const char *parent_name); +struct clk_hw * __init +at91_clk_sama7g5_register_utmi(struct regmap *regmap, const char *name, + const char *parent_name); + #ifdef CONFIG_PM void pmc_register_id(u8 id); void pmc_register_pck(u8 pck); diff --git a/include/linux/clk/at91_pmc.h b/include/linux/clk/at91_pmc.h index dc5e85f124e0..a4f82e836a7c 100644 --- a/include/linux/clk/at91_pmc.h +++ b/include/linux/clk/at91_pmc.h @@ -137,6 +137,8 @@ #define AT91_PMC_PLLADIV2_ON (1 << 12) #define AT91_PMC_H32MXDIV BIT(24) +#define AT91_PMC_XTALF 0x34 /* Main XTAL Frequency Register [SAMA7G5 only] */ + #define AT91_PMC_USB 0x38 /* USB Clock Register [some SAM9 only] */ #define AT91_PMC_USBS (0x1 << 0) /* USB OHCI Input clock selection */ #define AT91_PMC_USBS_PLLA (0 << 0) -- cgit v1.2.3 From cb783bbbcf54c36256006895c215e86c5e7266d8 Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Wed, 22 Jul 2020 10:38:26 +0300 Subject: clk: at91: sama7g5: add clock support for sama7g5 Add clock support for SAMA7G5. Signed-off-by: Claudiu Beznea Link: https://lore.kernel.org/r/1595403506-8209-19-git-send-email-claudiu.beznea@microchip.com Signed-off-by: Stephen Boyd --- drivers/clk/at91/Makefile | 1 + drivers/clk/at91/sama7g5.c | 1059 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1060 insertions(+) create mode 100644 drivers/clk/at91/sama7g5.c (limited to 'drivers/clk') diff --git a/drivers/clk/at91/Makefile b/drivers/clk/at91/Makefile index 8b90357f2a93..79301e1c1c36 100644 --- a/drivers/clk/at91/Makefile +++ b/drivers/clk/at91/Makefile @@ -23,3 +23,4 @@ obj-$(CONFIG_SOC_SAM9X60) += sam9x60.o obj-$(CONFIG_SOC_SAMA5D3) += sama5d3.o obj-$(CONFIG_SOC_SAMA5D4) += sama5d4.o obj-$(CONFIG_SOC_SAMA5D2) += sama5d2.o +obj-$(CONFIG_SOC_SAMA7G5) += sama7g5.o diff --git a/drivers/clk/at91/sama7g5.c b/drivers/clk/at91/sama7g5.c new file mode 100644 index 000000000000..0db2ab3eca14 --- /dev/null +++ b/drivers/clk/at91/sama7g5.c @@ -0,0 +1,1059 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * SAMA7G5 PMC code. + * + * Copyright (C) 2020 Microchip Technology Inc. and its subsidiaries + * + * Author: Claudiu Beznea + * + */ +#include +#include +#include +#include + +#include + +#include "pmc.h" + +#define SAMA7G5_INIT_TABLE(_table, _count) \ + do { \ + u8 _i; \ + for (_i = 0; _i < (_count); _i++) \ + (_table)[_i] = _i; \ + } while (0) + +#define SAMA7G5_FILL_TABLE(_to, _from, _count) \ + do { \ + u8 _i; \ + for (_i = 0; _i < (_count); _i++) { \ + (_to)[_i] = (_from)[_i]; \ + } \ + } while (0) + +static DEFINE_SPINLOCK(pmc_pll_lock); +static DEFINE_SPINLOCK(pmc_mckX_lock); + +/** + * PLL clocks identifiers + * @PLL_ID_CPU: CPU PLL identifier + * @PLL_ID_SYS: System PLL identifier + * @PLL_ID_DDR: DDR PLL identifier + * @PLL_ID_IMG: Image subsystem PLL identifier + * @PLL_ID_BAUD: Baud PLL identifier + * @PLL_ID_AUDIO: Audio PLL identifier + * @PLL_ID_ETH: Ethernet PLL identifier + */ +enum pll_ids { + PLL_ID_CPU, + PLL_ID_SYS, + PLL_ID_DDR, + PLL_ID_IMG, + PLL_ID_BAUD, + PLL_ID_AUDIO, + PLL_ID_ETH, + PLL_ID_MAX, +}; + +/** + * PLL type identifiers + * @PLL_TYPE_FRAC: fractional PLL identifier + * @PLL_TYPE_DIV: divider PLL identifier + */ +enum pll_type { + PLL_TYPE_FRAC, + PLL_TYPE_DIV, +}; + +/* Layout for fractional PLLs. */ +static const struct clk_pll_layout pll_layout_frac = { + .mul_mask = GENMASK(31, 24), + .frac_mask = GENMASK(21, 0), + .mul_shift = 24, + .frac_shift = 0, +}; + +/* Layout for DIVPMC dividers. */ +static const struct clk_pll_layout pll_layout_divpmc = { + .div_mask = GENMASK(7, 0), + .endiv_mask = BIT(29), + .div_shift = 0, + .endiv_shift = 29, +}; + +/* Layout for DIVIO dividers. */ +static const struct clk_pll_layout pll_layout_divio = { + .div_mask = GENMASK(19, 12), + .endiv_mask = BIT(30), + .div_shift = 12, + .endiv_shift = 30, +}; + +/** + * PLL clocks description + * @n: clock name + * @p: clock parent + * @l: clock layout + * @t: clock type + * @f: true if clock is critical and cannot be disabled + * @eid: export index in sama7g5->chws[] array + */ +static const struct { + const char *n; + const char *p; + const struct clk_pll_layout *l; + u8 t; + u8 c; + u8 eid; +} sama7g5_plls[][PLL_ID_MAX] = { + [PLL_ID_CPU] = { + { .n = "cpupll_fracck", + .p = "mainck", + .l = &pll_layout_frac, + .t = PLL_TYPE_FRAC, + .c = 1, }, + + { .n = "cpupll_divpmcck", + .p = "cpupll_fracck", + .l = &pll_layout_divpmc, + .t = PLL_TYPE_DIV, + .c = 1, }, + }, + + [PLL_ID_SYS] = { + { .n = "syspll_fracck", + .p = "mainck", + .l = &pll_layout_frac, + .t = PLL_TYPE_FRAC, + .c = 1, }, + + { .n = "syspll_divpmcck", + .p = "syspll_fracck", + .l = &pll_layout_divpmc, + .t = PLL_TYPE_DIV, + .c = 1, }, + }, + + [PLL_ID_DDR] = { + { .n = "ddrpll_fracck", + .p = "mainck", + .l = &pll_layout_frac, + .t = PLL_TYPE_FRAC, + .c = 1, }, + + { .n = "ddrpll_divpmcck", + .p = "ddrpll_fracck", + .l = &pll_layout_divpmc, + .t = PLL_TYPE_DIV, + .c = 1, }, + }, + + [PLL_ID_IMG] = { + { .n = "imgpll_fracck", + .p = "mainck", + .l = &pll_layout_frac, + .t = PLL_TYPE_FRAC, }, + + { .n = "imgpll_divpmcck", + .p = "imgpll_fracck", + .l = &pll_layout_divpmc, + .t = PLL_TYPE_DIV, }, + }, + + [PLL_ID_BAUD] = { + { .n = "baudpll_fracck", + .p = "mainck", + .l = &pll_layout_frac, + .t = PLL_TYPE_FRAC, }, + + { .n = "baudpll_divpmcck", + .p = "baudpll_fracck", + .l = &pll_layout_divpmc, + .t = PLL_TYPE_DIV, }, + }, + + [PLL_ID_AUDIO] = { + { .n = "audiopll_fracck", + .p = "main_xtal", + .l = &pll_layout_frac, + .t = PLL_TYPE_FRAC, }, + + { .n = "audiopll_divpmcck", + .p = "audiopll_fracck", + .l = &pll_layout_divpmc, + .t = PLL_TYPE_DIV, + .eid = PMC_I2S0_MUX, }, + + { .n = "audiopll_diviock", + .p = "audiopll_fracck", + .l = &pll_layout_divio, + .t = PLL_TYPE_DIV, + .eid = PMC_I2S1_MUX, }, + }, + + [PLL_ID_ETH] = { + { .n = "ethpll_fracck", + .p = "main_xtal", + .l = &pll_layout_frac, + .t = PLL_TYPE_FRAC, }, + + { .n = "ethpll_divpmcck", + .p = "ethpll_fracck", + .l = &pll_layout_divpmc, + .t = PLL_TYPE_DIV, }, + }, +}; + +/** + * Master clock (MCK[1..4]) description + * @n: clock name + * @ep: extra parents names array + * @ep_chg_chg_id: index in parents array that specifies the changeable + * parent + * @ep_count: extra parents count + * @ep_mux_table: mux table for extra parents + * @id: clock id + * @c: true if clock is critical and cannot be disabled + */ +static const struct { + const char *n; + const char *ep[4]; + int ep_chg_id; + u8 ep_count; + u8 ep_mux_table[4]; + u8 id; + u8 c; +} sama7g5_mckx[] = { + { .n = "mck1", + .id = 1, + .ep = { "syspll_divpmcck", }, + .ep_mux_table = { 5, }, + .ep_count = 1, + .ep_chg_id = INT_MIN, + .c = 1, }, + + { .n = "mck2", + .id = 2, + .ep = { "ddrpll_divpmcck", }, + .ep_mux_table = { 6, }, + .ep_count = 1, + .ep_chg_id = INT_MIN, + .c = 1, }, + + { .n = "mck3", + .id = 3, + .ep = { "syspll_divpmcck", "ddrpll_divpmcck", "imgpll_divpmcck", }, + .ep_mux_table = { 5, 6, 7, }, + .ep_count = 3, + .ep_chg_id = 6, }, + + { .n = "mck4", + .id = 4, + .ep = { "syspll_divpmcck", }, + .ep_mux_table = { 5, }, + .ep_count = 1, + .ep_chg_id = INT_MIN, + .c = 1, }, +}; + +/** + * System clock description + * @n: clock name + * @p: clock parent name + * @id: clock id + */ +static const struct { + const char *n; + const char *p; + u8 id; +} sama7g5_systemck[] = { + { .n = "pck0", .p = "prog0", .id = 8, }, + { .n = "pck1", .p = "prog1", .id = 9, }, + { .n = "pck2", .p = "prog2", .id = 10, }, + { .n = "pck3", .p = "prog3", .id = 11, }, + { .n = "pck4", .p = "prog4", .id = 12, }, + { .n = "pck5", .p = "prog5", .id = 13, }, + { .n = "pck6", .p = "prog6", .id = 14, }, + { .n = "pck7", .p = "prog7", .id = 15, }, +}; + +/* Mux table for programmable clocks. */ +static u32 sama7g5_prog_mux_table[] = { 0, 1, 2, 3, 5, 6, 7, 8, 9, 10, }; + +/** + * Peripheral clock description + * @n: clock name + * @p: clock parent name + * @r: clock range values + * @id: clock id + * @chgp: index in parent array of the changeable parent + */ +static const struct { + const char *n; + const char *p; + struct clk_range r; + u8 chgp; + u8 id; +} sama7g5_periphck[] = { + { .n = "pioA_clk", .p = "mck0", .id = 11, }, + { .n = "sfr_clk", .p = "mck1", .id = 19, }, + { .n = "hsmc_clk", .p = "mck1", .id = 21, }, + { .n = "xdmac0_clk", .p = "mck1", .id = 22, }, + { .n = "xdmac1_clk", .p = "mck1", .id = 23, }, + { .n = "xdmac2_clk", .p = "mck1", .id = 24, }, + { .n = "acc_clk", .p = "mck1", .id = 25, }, + { .n = "aes_clk", .p = "mck1", .id = 27, }, + { .n = "tzaesbasc_clk", .p = "mck1", .id = 28, }, + { .n = "asrc_clk", .p = "mck1", .id = 30, .r = { .max = 200000000, }, }, + { .n = "cpkcc_clk", .p = "mck0", .id = 32, }, + { .n = "csi_clk", .p = "mck3", .id = 33, .r = { .max = 266000000, }, .chgp = 1, }, + { .n = "csi2dc_clk", .p = "mck3", .id = 34, .r = { .max = 266000000, }, .chgp = 1, }, + { .n = "eic_clk", .p = "mck1", .id = 37, }, + { .n = "flex0_clk", .p = "mck1", .id = 38, }, + { .n = "flex1_clk", .p = "mck1", .id = 39, }, + { .n = "flex2_clk", .p = "mck1", .id = 40, }, + { .n = "flex3_clk", .p = "mck1", .id = 41, }, + { .n = "flex4_clk", .p = "mck1", .id = 42, }, + { .n = "flex5_clk", .p = "mck1", .id = 43, }, + { .n = "flex6_clk", .p = "mck1", .id = 44, }, + { .n = "flex7_clk", .p = "mck1", .id = 45, }, + { .n = "flex8_clk", .p = "mck1", .id = 46, }, + { .n = "flex9_clk", .p = "mck1", .id = 47, }, + { .n = "flex10_clk", .p = "mck1", .id = 48, }, + { .n = "flex11_clk", .p = "mck1", .id = 49, }, + { .n = "gmac0_clk", .p = "mck1", .id = 51, }, + { .n = "gmac1_clk", .p = "mck1", .id = 52, }, + { .n = "icm_clk", .p = "mck1", .id = 55, }, + { .n = "isc_clk", .p = "mck3", .id = 56, .r = { .max = 266000000, }, .chgp = 1, }, + { .n = "i2smcc0_clk", .p = "mck1", .id = 57, .r = { .max = 200000000, }, }, + { .n = "i2smcc1_clk", .p = "mck1", .id = 58, .r = { .max = 200000000, }, }, + { .n = "matrix_clk", .p = "mck1", .id = 60, }, + { .n = "mcan0_clk", .p = "mck1", .id = 61, .r = { .max = 200000000, }, }, + { .n = "mcan1_clk", .p = "mck1", .id = 62, .r = { .max = 200000000, }, }, + { .n = "mcan2_clk", .p = "mck1", .id = 63, .r = { .max = 200000000, }, }, + { .n = "mcan3_clk", .p = "mck1", .id = 64, .r = { .max = 200000000, }, }, + { .n = "mcan4_clk", .p = "mck1", .id = 65, .r = { .max = 200000000, }, }, + { .n = "mcan5_clk", .p = "mck1", .id = 66, .r = { .max = 200000000, }, }, + { .n = "pdmc0_clk", .p = "mck1", .id = 68, .r = { .max = 200000000, }, }, + { .n = "pdmc1_clk", .p = "mck1", .id = 69, .r = { .max = 200000000, }, }, + { .n = "pit64b0_clk", .p = "mck1", .id = 70, }, + { .n = "pit64b1_clk", .p = "mck1", .id = 71, }, + { .n = "pit64b2_clk", .p = "mck1", .id = 72, }, + { .n = "pit64b3_clk", .p = "mck1", .id = 73, }, + { .n = "pit64b4_clk", .p = "mck1", .id = 74, }, + { .n = "pit64b5_clk", .p = "mck1", .id = 75, }, + { .n = "pwm_clk", .p = "mck1", .id = 77, }, + { .n = "qspi0_clk", .p = "mck1", .id = 78, }, + { .n = "qspi1_clk", .p = "mck1", .id = 79, }, + { .n = "sdmmc0_clk", .p = "mck1", .id = 80, }, + { .n = "sdmmc1_clk", .p = "mck1", .id = 81, }, + { .n = "sdmmc2_clk", .p = "mck1", .id = 82, }, + { .n = "sha_clk", .p = "mck1", .id = 83, }, + { .n = "spdifrx_clk", .p = "mck1", .id = 84, .r = { .max = 200000000, }, }, + { .n = "spdiftx_clk", .p = "mck1", .id = 85, .r = { .max = 200000000, }, }, + { .n = "ssc0_clk", .p = "mck1", .id = 86, .r = { .max = 200000000, }, }, + { .n = "ssc1_clk", .p = "mck1", .id = 87, .r = { .max = 200000000, }, }, + { .n = "tcb0_ch0_clk", .p = "mck1", .id = 88, .r = { .max = 200000000, }, }, + { .n = "tcb0_ch1_clk", .p = "mck1", .id = 89, .r = { .max = 200000000, }, }, + { .n = "tcb0_ch2_clk", .p = "mck1", .id = 90, .r = { .max = 200000000, }, }, + { .n = "tcb1_ch0_clk", .p = "mck1", .id = 91, .r = { .max = 200000000, }, }, + { .n = "tcb1_ch1_clk", .p = "mck1", .id = 92, .r = { .max = 200000000, }, }, + { .n = "tcb1_ch2_clk", .p = "mck1", .id = 93, .r = { .max = 200000000, }, }, + { .n = "tcpca_clk", .p = "mck1", .id = 94, }, + { .n = "tcpcb_clk", .p = "mck1", .id = 95, }, + { .n = "tdes_clk", .p = "mck1", .id = 96, }, + { .n = "trng_clk", .p = "mck1", .id = 97, }, + { .n = "udphsa_clk", .p = "mck1", .id = 104, }, + { .n = "udphsb_clk", .p = "mck1", .id = 105, }, + { .n = "uhphs_clk", .p = "mck1", .id = 106, }, +}; + +/** + * Generic clock description + * @n: clock name + * @pp: PLL parents + * @pp_mux_table: PLL parents mux table + * @r: clock output range + * @pp_chg_id: id in parrent array of changeable PLL parent + * @pp_count: PLL parents count + * @id: clock id + */ +static const struct { + const char *n; + const char *pp[8]; + const char pp_mux_table[8]; + struct clk_range r; + int pp_chg_id; + u8 pp_count; + u8 id; +} sama7g5_gck[] = { + { .n = "adc_gclk", + .id = 26, + .r = { .max = 100000000, }, + .pp = { "syspll_divpmcck", "imgpll_divpmcck", "audiopll_divpmcck", }, + .pp_mux_table = { 5, 7, 9, }, + .pp_count = 3, + .pp_chg_id = INT_MIN, }, + + { .n = "asrc_gclk", + .id = 30, + .r = { .max = 200000000 }, + .pp = { "audiopll_divpmcck", }, + .pp_mux_table = { 9, }, + .pp_count = 1, + .pp_chg_id = 4, }, + + { .n = "csi_gclk", + .id = 33, + .r = { .max = 27000000 }, + .pp = { "ddrpll_divpmcck", "imgpll_divpmcck", }, + .pp_mux_table = { 6, 7, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "flex0_gclk", + .id = 38, + .r = { .max = 200000000 }, + .pp = { "syspll_divpmcck", "baudpll_divpmcck", }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "flex1_gclk", + .id = 39, + .r = { .max = 200000000 }, + .pp = { "syspll_divpmcck", "baudpll_divpmcck", }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "flex2_gclk", + .id = 40, + .r = { .max = 200000000 }, + .pp = { "syspll_divpmcck", "baudpll_divpmcck", }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "flex3_gclk", + .id = 41, + .r = { .max = 200000000 }, + .pp = { "syspll_divpmcck", "baudpll_divpmcck", }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "flex4_gclk", + .id = 42, + .r = { .max = 200000000 }, + .pp = { "syspll_divpmcck", "baudpll_divpmcck", }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "flex5_gclk", + .id = 43, + .r = { .max = 200000000 }, + .pp = { "syspll_divpmcck", "baudpll_divpmcck", }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "flex6_gclk", + .id = 44, + .r = { .max = 200000000 }, + .pp = { "syspll_divpmcck", "baudpll_divpmcck", }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "flex7_gclk", + .id = 45, + .r = { .max = 200000000 }, + .pp = { "syspll_divpmcck", "baudpll_divpmcck", }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "flex8_gclk", + .id = 46, + .r = { .max = 200000000 }, + .pp = { "syspll_divpmcck", "baudpll_divpmcck", }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "flex9_gclk", + .id = 47, + .r = { .max = 200000000 }, + .pp = { "syspll_divpmcck", "baudpll_divpmcck", }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "flex10_gclk", + .id = 48, + .r = { .max = 200000000 }, + .pp = { "syspll_divpmcck", "baudpll_divpmcck", }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "flex11_gclk", + .id = 49, + .r = { .max = 200000000 }, + .pp = { "syspll_divpmcck", "baudpll_divpmcck", }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "gmac0_gclk", + .id = 51, + .r = { .max = 125000000 }, + .pp = { "ethpll_divpmcck", }, + .pp_mux_table = { 10, }, + .pp_count = 1, + .pp_chg_id = 4, }, + + { .n = "gmac1_gclk", + .id = 52, + .r = { .max = 50000000 }, + .pp = { "ethpll_divpmcck", }, + .pp_mux_table = { 10, }, + .pp_count = 1, + .pp_chg_id = INT_MIN, }, + + { .n = "gmac0_tsu_gclk", + .id = 53, + .r = { .max = 300000000 }, + .pp = { "audiopll_divpmcck", "ethpll_divpmcck", }, + .pp_mux_table = { 9, 10, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "gmac1_tsu_gclk", + .id = 54, + .r = { .max = 300000000 }, + .pp = { "audiopll_divpmcck", "ethpll_divpmcck", }, + .pp_mux_table = { 9, 10, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "i2smcc0_gclk", + .id = 57, + .r = { .max = 100000000 }, + .pp = { "syspll_divpmcck", "audiopll_divpmcck", }, + .pp_mux_table = { 5, 9, }, + .pp_count = 2, + .pp_chg_id = 5, }, + + { .n = "i2smcc1_gclk", + .id = 58, + .r = { .max = 100000000 }, + .pp = { "syspll_divpmcck", "audiopll_divpmcck", }, + .pp_mux_table = { 5, 9, }, + .pp_count = 2, + .pp_chg_id = 5, }, + + { .n = "mcan0_gclk", + .id = 61, + .r = { .max = 200000000 }, + .pp = { "syspll_divpmcck", "baudpll_divpmcck", }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "mcan1_gclk", + .id = 62, + .r = { .max = 200000000 }, + .pp = { "syspll_divpmcck", "baudpll_divpmcck", }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "mcan2_gclk", + .id = 63, + .r = { .max = 200000000 }, + .pp = { "syspll_divpmcck", "baudpll_divpmcck", }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "mcan3_gclk", + .id = 64, + .r = { .max = 200000000 }, + .pp = { "syspll_divpmcck", "baudpll_divpmcck", }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "mcan4_gclk", + .id = 65, + .r = { .max = 200000000 }, + .pp = { "syspll_divpmcck", "baudpll_divpmcck", }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "mcan5_gclk", + .id = 66, + .r = { .max = 200000000 }, + .pp = { "syspll_divpmcck", "baudpll_divpmcck", }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "pdmc0_gclk", + .id = 68, + .r = { .max = 50000000 }, + .pp = { "syspll_divpmcck", "baudpll_divpmcck", }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "pdmc1_gclk", + .id = 69, + .r = { .max = 50000000, }, + .pp = { "syspll_divpmcck", "baudpll_divpmcck", }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "pit64b0_gclk", + .id = 70, + .r = { .max = 200000000 }, + .pp = { "syspll_divpmcck", "imgpll_divpmcck", "baudpll_divpmcck", + "audiopll_divpmcck", "ethpll_divpmcck", }, + .pp_mux_table = { 5, 7, 8, 9, 10, }, + .pp_count = 5, + .pp_chg_id = INT_MIN, }, + + { .n = "pit64b1_gclk", + .id = 71, + .r = { .max = 200000000 }, + .pp = { "syspll_divpmcck", "imgpll_divpmcck", "baudpll_divpmcck", + "audiopll_divpmcck", "ethpll_divpmcck", }, + .pp_mux_table = { 5, 7, 8, 9, 10, }, + .pp_count = 5, + .pp_chg_id = INT_MIN, }, + + { .n = "pit64b2_gclk", + .id = 72, + .r = { .max = 200000000 }, + .pp = { "syspll_divpmcck", "imgpll_divpmcck", "baudpll_divpmcck", + "audiopll_divpmcck", "ethpll_divpmcck", }, + .pp_mux_table = { 5, 7, 8, 9, 10, }, + .pp_count = 5, + .pp_chg_id = INT_MIN, }, + + { .n = "pit64b3_gclk", + .id = 73, + .r = { .max = 200000000 }, + .pp = { "syspll_divpmcck", "imgpll_divpmcck", "baudpll_divpmcck", + "audiopll_divpmcck", "ethpll_divpmcck", }, + .pp_mux_table = { 5, 7, 8, 9, 10, }, + .pp_count = 5, + .pp_chg_id = INT_MIN, }, + + { .n = "pit64b4_gclk", + .id = 74, + .r = { .max = 200000000 }, + .pp = { "syspll_divpmcck", "imgpll_divpmcck", "baudpll_divpmcck", + "audiopll_divpmcck", "ethpll_divpmcck", }, + .pp_mux_table = { 5, 7, 8, 9, 10, }, + .pp_count = 5, + .pp_chg_id = INT_MIN, }, + + { .n = "pit64b5_gclk", + .id = 75, + .r = { .max = 200000000 }, + .pp = { "syspll_divpmcck", "imgpll_divpmcck", "baudpll_divpmcck", + "audiopll_divpmcck", "ethpll_divpmcck", }, + .pp_mux_table = { 5, 7, 8, 9, 10, }, + .pp_count = 5, + .pp_chg_id = INT_MIN, }, + + { .n = "qspi0_gclk", + .id = 78, + .r = { .max = 200000000 }, + .pp = { "syspll_divpmcck", "baudpll_divpmcck", }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "qspi1_gclk", + .id = 79, + .r = { .max = 200000000 }, + .pp = { "syspll_divpmcck", "baudpll_divpmcck", }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "sdmmc0_gclk", + .id = 80, + .r = { .max = 208000000 }, + .pp = { "syspll_divpmcck", "baudpll_divpmcck", }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = 5, }, + + { .n = "sdmmc1_gclk", + .id = 81, + .r = { .max = 208000000 }, + .pp = { "syspll_divpmcck", "baudpll_divpmcck", }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = 5, }, + + { .n = "sdmmc2_gclk", + .id = 82, + .r = { .max = 208000000 }, + .pp = { "syspll_divpmcck", "baudpll_divpmcck", }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = 5, }, + + { .n = "spdifrx_gclk", + .id = 84, + .r = { .max = 150000000 }, + .pp = { "syspll_divpmcck", "audiopll_divpmcck", }, + .pp_mux_table = { 5, 9, }, + .pp_count = 2, + .pp_chg_id = 5, }, + + { .n = "spdiftx_gclk", + .id = 85, + .r = { .max = 25000000 }, + .pp = { "syspll_divpmcck", "audiopll_divpmcck", }, + .pp_mux_table = { 5, 9, }, + .pp_count = 2, + .pp_chg_id = 5, }, + + { .n = "tcb0_ch0_gclk", + .id = 88, + .r = { .max = 200000000 }, + .pp = { "syspll_divpmcck", "imgpll_divpmcck", "baudpll_divpmcck", + "audiopll_divpmcck", "ethpll_divpmcck", }, + .pp_mux_table = { 5, 7, 8, 9, 10, }, + .pp_count = 5, + .pp_chg_id = INT_MIN, }, + + { .n = "tcb1_ch0_gclk", + .id = 91, + .r = { .max = 200000000 }, + .pp = { "syspll_divpmcck", "imgpll_divpmcck", "baudpll_divpmcck", + "audiopll_divpmcck", "ethpll_divpmcck", }, + .pp_mux_table = { 5, 7, 8, 9, 10, }, + .pp_count = 5, + .pp_chg_id = INT_MIN, }, + + { .n = "tcpca_gclk", + .id = 94, + .r = { .max = 32768, }, + .pp_chg_id = INT_MIN, }, + + { .n = "tcpcb_gclk", + .id = 95, + .r = { .max = 32768, }, + .pp_chg_id = INT_MIN, }, +}; + +/* PLL output range. */ +static const struct clk_range pll_outputs[] = { + { .min = 2343750, .max = 1200000000 }, +}; + +/* PLL characteristics. */ +static const struct clk_pll_characteristics pll_characteristics = { + .input = { .min = 12000000, .max = 50000000 }, + .num_output = ARRAY_SIZE(pll_outputs), + .output = pll_outputs, +}; + +/* MCK0 characteristics. */ +static const struct clk_master_characteristics mck0_characteristics = { + .output = { .min = 140000000, .max = 200000000 }, + .divisors = { 1, 2, 4, 3 }, + .have_div3_pres = 1, +}; + +/* MCK0 layout. */ +static const struct clk_master_layout mck0_layout = { + .mask = 0x373, + .pres_shift = 4, + .offset = 0x28, +}; + +/* Programmable clock layout. */ +static const struct clk_programmable_layout programmable_layout = { + .pres_mask = 0xff, + .pres_shift = 8, + .css_mask = 0x1f, + .have_slck_mck = 0, + .is_pres_direct = 1, +}; + +/* Peripheral clock layout. */ +static const struct clk_pcr_layout sama7g5_pcr_layout = { + .offset = 0x88, + .cmd = BIT(31), + .gckcss_mask = GENMASK(12, 8), + .pid_mask = GENMASK(6, 0), +}; + +static void __init sama7g5_pmc_setup(struct device_node *np) +{ + const char *td_slck_name, *md_slck_name, *mainxtal_name; + struct pmc_data *sama7g5_pmc; + const char *parent_names[10]; + void **alloc_mem = NULL; + int alloc_mem_size = 0; + struct regmap *regmap; + struct clk_hw *hw; + bool bypass; + int i, j; + + i = of_property_match_string(np, "clock-names", "td_slck"); + if (i < 0) + return; + + td_slck_name = of_clk_get_parent_name(np, i); + + i = of_property_match_string(np, "clock-names", "md_slck"); + if (i < 0) + return; + + md_slck_name = of_clk_get_parent_name(np, i); + + i = of_property_match_string(np, "clock-names", "main_xtal"); + if (i < 0) + return; + + mainxtal_name = of_clk_get_parent_name(np, i); + + regmap = device_node_to_regmap(np); + if (IS_ERR(regmap)) + return; + + sama7g5_pmc = pmc_data_allocate(PMC_I2S1_MUX + 1, + nck(sama7g5_systemck), + nck(sama7g5_periphck), + nck(sama7g5_gck)); + if (!sama7g5_pmc) + return; + + alloc_mem = kmalloc(sizeof(void *) * + (ARRAY_SIZE(sama7g5_mckx) + ARRAY_SIZE(sama7g5_gck)), + GFP_KERNEL); + if (!alloc_mem) + goto err_free; + + hw = at91_clk_register_main_rc_osc(regmap, "main_rc_osc", 12000000, + 50000000); + if (IS_ERR(hw)) + goto err_free; + + bypass = of_property_read_bool(np, "atmel,osc-bypass"); + + hw = at91_clk_register_main_osc(regmap, "main_osc", mainxtal_name, + bypass); + if (IS_ERR(hw)) + goto err_free; + + parent_names[0] = "main_rc_osc"; + parent_names[1] = "main_osc"; + hw = at91_clk_register_sam9x5_main(regmap, "mainck", parent_names, 2); + if (IS_ERR(hw)) + goto err_free; + + sama7g5_pmc->chws[PMC_MAIN] = hw; + + for (i = 0; i < PLL_ID_MAX; i++) { + for (j = 0; j < 3; j++) { + struct clk_hw *parent_hw; + + if (!sama7g5_plls[i][j].n) + continue; + + switch (sama7g5_plls[i][j].t) { + case PLL_TYPE_FRAC: + if (!strcmp(sama7g5_plls[i][j].p, "mainck")) + parent_hw = sama7g5_pmc->chws[PMC_MAIN]; + else + parent_hw = __clk_get_hw(of_clk_get_by_name(np, + sama7g5_plls[i][j].p)); + + hw = sam9x60_clk_register_frac_pll(regmap, + &pmc_pll_lock, sama7g5_plls[i][j].n, + sama7g5_plls[i][j].p, parent_hw, i, + &pll_characteristics, + sama7g5_plls[i][j].l, + sama7g5_plls[i][j].c); + break; + + case PLL_TYPE_DIV: + hw = sam9x60_clk_register_div_pll(regmap, + &pmc_pll_lock, sama7g5_plls[i][j].n, + sama7g5_plls[i][j].p, i, + &pll_characteristics, + sama7g5_plls[i][j].l, + sama7g5_plls[i][j].c); + break; + + default: + continue; + } + + if (IS_ERR(hw)) + goto err_free; + + if (sama7g5_plls[i][j].eid) + sama7g5_pmc->chws[sama7g5_plls[i][j].eid] = hw; + } + } + + parent_names[0] = md_slck_name; + parent_names[1] = "mainck"; + parent_names[2] = "cpupll_divpmcck"; + parent_names[3] = "syspll_divpmcck"; + hw = at91_clk_register_master(regmap, "mck0", 4, parent_names, + &mck0_layout, &mck0_characteristics); + if (IS_ERR(hw)) + goto err_free; + + sama7g5_pmc->chws[PMC_MCK] = hw; + + parent_names[0] = md_slck_name; + parent_names[1] = td_slck_name; + parent_names[2] = "mainck"; + parent_names[3] = "mck0"; + for (i = 0; i < ARRAY_SIZE(sama7g5_mckx); i++) { + u8 num_parents = 4 + sama7g5_mckx[i].ep_count; + u32 *mux_table; + + mux_table = kmalloc_array(num_parents, sizeof(*mux_table), + GFP_KERNEL); + if (!mux_table) + goto err_free; + + SAMA7G5_INIT_TABLE(mux_table, 4); + SAMA7G5_FILL_TABLE(&mux_table[4], sama7g5_mckx[i].ep_mux_table, + sama7g5_mckx[i].ep_count); + SAMA7G5_FILL_TABLE(&parent_names[4], sama7g5_mckx[i].ep, + sama7g5_mckx[i].ep_count); + + hw = at91_clk_sama7g5_register_master(regmap, sama7g5_mckx[i].n, + num_parents, parent_names, mux_table, + &pmc_mckX_lock, sama7g5_mckx[i].id, + sama7g5_mckx[i].c, + sama7g5_mckx[i].ep_chg_id); + if (IS_ERR(hw)) + goto err_free; + + alloc_mem[alloc_mem_size++] = mux_table; + } + + hw = at91_clk_sama7g5_register_utmi(regmap, "utmick", "main_xtal"); + if (IS_ERR(hw)) + goto err_free; + + sama7g5_pmc->chws[PMC_UTMI] = hw; + + parent_names[0] = md_slck_name; + parent_names[1] = td_slck_name; + parent_names[2] = "mainck"; + parent_names[3] = "mck0"; + parent_names[4] = "syspll_divpmcck"; + parent_names[5] = "ddrpll_divpmcck"; + parent_names[6] = "imgpll_divpmcck"; + parent_names[7] = "baudpll_divpmcck"; + parent_names[8] = "audiopll_divpmcck"; + parent_names[9] = "ethpll_divpmcck"; + for (i = 0; i < 8; i++) { + char name[6]; + + snprintf(name, sizeof(name), "prog%d", i); + + hw = at91_clk_register_programmable(regmap, name, parent_names, + 10, i, + &programmable_layout, + sama7g5_prog_mux_table); + if (IS_ERR(hw)) + goto err_free; + } + + for (i = 0; i < ARRAY_SIZE(sama7g5_systemck); i++) { + hw = at91_clk_register_system(regmap, sama7g5_systemck[i].n, + sama7g5_systemck[i].p, + sama7g5_systemck[i].id); + if (IS_ERR(hw)) + goto err_free; + + sama7g5_pmc->shws[sama7g5_systemck[i].id] = hw; + } + + for (i = 0; i < ARRAY_SIZE(sama7g5_periphck); i++) { + hw = at91_clk_register_sam9x5_peripheral(regmap, &pmc_pcr_lock, + &sama7g5_pcr_layout, + sama7g5_periphck[i].n, + sama7g5_periphck[i].p, + sama7g5_periphck[i].id, + &sama7g5_periphck[i].r, + sama7g5_periphck[i].chgp ? 0 : + INT_MIN); + if (IS_ERR(hw)) + goto err_free; + + sama7g5_pmc->phws[sama7g5_periphck[i].id] = hw; + } + + parent_names[0] = md_slck_name; + parent_names[1] = td_slck_name; + parent_names[2] = "mainck"; + parent_names[3] = "mck0"; + for (i = 0; i < ARRAY_SIZE(sama7g5_gck); i++) { + u8 num_parents = 4 + sama7g5_gck[i].pp_count; + u32 *mux_table; + + mux_table = kmalloc_array(num_parents, sizeof(*mux_table), + GFP_KERNEL); + if (!mux_table) + goto err_free; + + SAMA7G5_INIT_TABLE(mux_table, 4); + SAMA7G5_FILL_TABLE(&mux_table[4], sama7g5_gck[i].pp_mux_table, + sama7g5_gck[i].pp_count); + SAMA7G5_FILL_TABLE(&parent_names[4], sama7g5_gck[i].pp, + sama7g5_gck[i].pp_count); + + hw = at91_clk_register_generated(regmap, &pmc_pcr_lock, + &sama7g5_pcr_layout, + sama7g5_gck[i].n, + parent_names, mux_table, + num_parents, + sama7g5_gck[i].id, + &sama7g5_gck[i].r, + sama7g5_gck[i].pp_chg_id); + if (IS_ERR(hw)) + goto err_free; + + sama7g5_pmc->ghws[sama7g5_gck[i].id] = hw; + alloc_mem[alloc_mem_size++] = mux_table; + } + + of_clk_add_hw_provider(np, of_clk_hw_pmc_get, sama7g5_pmc); + + return; + +err_free: + if (alloc_mem) { + for (i = 0; i < alloc_mem_size; i++) + kfree(alloc_mem[i]); + kfree(alloc_mem); + } + + pmc_data_free(sama7g5_pmc); +} + +/* Some clks are used for a clocksource */ +CLK_OF_DECLARE(sama7g5_pmc, "microchip,sama7g5-pmc", sama7g5_pmc_setup); -- cgit v1.2.3 From 53727eb6b3c210e826bb4c9d0aa89f65a5ae9342 Mon Sep 17 00:00:00 2001 From: Lars Povlsen Date: Mon, 27 Jul 2020 10:42:09 +0200 Subject: clk: sparx5: Add Sparx5 SoC DPLL clock driver This adds a device driver for the Sparx5 SoC DPLL clock Signed-off-by: Lars Povlsen Link: https://lore.kernel.org/r/20200727084211.6632-9-lars.povlsen@microchip.com Signed-off-by: Stephen Boyd --- drivers/clk/Makefile | 1 + drivers/clk/clk-sparx5.c | 295 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 296 insertions(+) create mode 100644 drivers/clk/clk-sparx5.c (limited to 'drivers/clk') diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index ca9af11d3391..da8fcf147eb1 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -28,6 +28,7 @@ obj-$(CONFIG_COMMON_CLK_CDCE925) += clk-cdce925.o obj-$(CONFIG_ARCH_CLPS711X) += clk-clps711x.o obj-$(CONFIG_COMMON_CLK_CS2000_CP) += clk-cs2000-cp.o obj-$(CONFIG_ARCH_EFM32) += clk-efm32gg.o +obj-$(CONFIG_ARCH_SPARX5) += clk-sparx5.o obj-$(CONFIG_COMMON_CLK_FIXED_MMIO) += clk-fixed-mmio.o obj-$(CONFIG_COMMON_CLK_FSL_SAI) += clk-fsl-sai.o obj-$(CONFIG_COMMON_CLK_GEMINI) += clk-gemini.o diff --git a/drivers/clk/clk-sparx5.c b/drivers/clk/clk-sparx5.c new file mode 100644 index 000000000000..0fad0c1a0186 --- /dev/null +++ b/drivers/clk/clk-sparx5.c @@ -0,0 +1,295 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Microchip Sparx5 SoC Clock driver. + * + * Copyright (c) 2019 Microchip Inc. + * + * Author: Lars Povlsen + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define PLL_DIV GENMASK(7, 0) +#define PLL_PRE_DIV GENMASK(10, 8) +#define PLL_ROT_DIR BIT(11) +#define PLL_ROT_SEL GENMASK(13, 12) +#define PLL_ROT_ENA BIT(14) +#define PLL_CLK_ENA BIT(15) + +#define MAX_SEL 4 +#define MAX_PRE BIT(3) + +static const u8 sel_rates[MAX_SEL] = { 0, 2*8, 2*4, 2*2 }; + +static const char *clk_names[N_CLOCKS] = { + "core", "ddr", "cpu2", "arm2", + "aux1", "aux2", "aux3", "aux4", + "synce", +}; + +struct s5_hw_clk { + struct clk_hw hw; + void __iomem *reg; +}; + +struct s5_clk_data { + void __iomem *base; + struct s5_hw_clk s5_hw[N_CLOCKS]; +}; + +struct s5_pll_conf { + unsigned long freq; + u8 div; + bool rot_ena; + u8 rot_sel; + u8 rot_dir; + u8 pre_div; +}; + +#define to_s5_pll(hw) container_of(hw, struct s5_hw_clk, hw) + +static unsigned long s5_calc_freq(unsigned long parent_rate, + const struct s5_pll_conf *conf) +{ + unsigned long rate = parent_rate / conf->div; + + if (conf->rot_ena) { + int sign = conf->rot_dir ? -1 : 1; + int divt = sel_rates[conf->rot_sel] * (1 + conf->pre_div); + int divb = divt + sign; + + rate = mult_frac(rate, divt, divb); + rate = roundup(rate, 1000); + } + + return rate; +} + +static void s5_search_fractional(unsigned long rate, + unsigned long parent_rate, + int div, + struct s5_pll_conf *conf) +{ + struct s5_pll_conf best; + ulong cur_offset, best_offset = rate; + int d, i, j; + + memset(conf, 0, sizeof(*conf)); + conf->div = div; + conf->rot_ena = 1; /* Fractional rate */ + + for (d = 0; best_offset > 0 && d <= 1 ; d++) { + conf->rot_dir = !!d; + for (i = 0; best_offset > 0 && i < MAX_PRE; i++) { + conf->pre_div = i; + for (j = 1; best_offset > 0 && j < MAX_SEL; j++) { + conf->rot_sel = j; + conf->freq = s5_calc_freq(parent_rate, conf); + cur_offset = abs(rate - conf->freq); + if (cur_offset < best_offset) { + best_offset = cur_offset; + best = *conf; + } + } + } + } + + /* Best match */ + *conf = best; +} + +static unsigned long s5_calc_params(unsigned long rate, + unsigned long parent_rate, + struct s5_pll_conf *conf) +{ + if (parent_rate % rate) { + struct s5_pll_conf alt1, alt2; + int div; + + div = DIV_ROUND_CLOSEST_ULL(parent_rate, rate); + s5_search_fractional(rate, parent_rate, div, &alt1); + + /* Straight match? */ + if (alt1.freq == rate) { + *conf = alt1; + } else { + /* Try without rounding divider */ + div = parent_rate / rate; + if (div != alt1.div) { + s5_search_fractional(rate, parent_rate, div, + &alt2); + /* Select the better match */ + if (abs(rate - alt1.freq) < + abs(rate - alt2.freq)) + *conf = alt1; + else + *conf = alt2; + } + } + } else { + /* Straight fit */ + memset(conf, 0, sizeof(*conf)); + conf->div = parent_rate / rate; + } + + return conf->freq; +} + +static int s5_pll_enable(struct clk_hw *hw) +{ + struct s5_hw_clk *pll = to_s5_pll(hw); + u32 val = readl(pll->reg); + + val |= PLL_CLK_ENA; + writel(val, pll->reg); + + return 0; +} + +static void s5_pll_disable(struct clk_hw *hw) +{ + struct s5_hw_clk *pll = to_s5_pll(hw); + u32 val = readl(pll->reg); + + val &= ~PLL_CLK_ENA; + writel(val, pll->reg); +} + +static int s5_pll_set_rate(struct clk_hw *hw, + unsigned long rate, + unsigned long parent_rate) +{ + struct s5_hw_clk *pll = to_s5_pll(hw); + struct s5_pll_conf conf; + unsigned long eff_rate; + u32 val; + + eff_rate = s5_calc_params(rate, parent_rate, &conf); + if (eff_rate != rate) + return -EOPNOTSUPP; + + val = readl(pll->reg) & PLL_CLK_ENA; + val |= FIELD_PREP(PLL_DIV, conf.div); + if (conf.rot_ena) { + val |= PLL_ROT_ENA; + val |= FIELD_PREP(PLL_ROT_SEL, conf.rot_sel); + val |= FIELD_PREP(PLL_PRE_DIV, conf.pre_div); + if (conf.rot_dir) + val |= PLL_ROT_DIR; + } + writel(val, pll->reg); + + return 0; +} + +static unsigned long s5_pll_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct s5_hw_clk *pll = to_s5_pll(hw); + struct s5_pll_conf conf; + u32 val; + + val = readl(pll->reg); + + if (val & PLL_CLK_ENA) { + conf.div = FIELD_GET(PLL_DIV, val); + conf.pre_div = FIELD_GET(PLL_PRE_DIV, val); + conf.rot_ena = FIELD_GET(PLL_ROT_ENA, val); + conf.rot_dir = FIELD_GET(PLL_ROT_DIR, val); + conf.rot_sel = FIELD_GET(PLL_ROT_SEL, val); + + conf.freq = s5_calc_freq(parent_rate, &conf); + } else { + conf.freq = 0; + } + + return conf.freq; +} + +static long s5_pll_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + struct s5_pll_conf conf; + + return s5_calc_params(rate, *parent_rate, &conf); +} + +static const struct clk_ops s5_pll_ops = { + .enable = s5_pll_enable, + .disable = s5_pll_disable, + .set_rate = s5_pll_set_rate, + .round_rate = s5_pll_round_rate, + .recalc_rate = s5_pll_recalc_rate, +}; + +static struct clk_hw *s5_clk_hw_get(struct of_phandle_args *clkspec, void *data) +{ + struct s5_clk_data *s5_clk = data; + unsigned int idx = clkspec->args[0]; + + if (idx >= N_CLOCKS) { + pr_err("%s: invalid index %u\n", __func__, idx); + return ERR_PTR(-EINVAL); + } + + return &s5_clk->s5_hw[idx].hw; +} + +static int s5_clk_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + int i, ret; + struct s5_clk_data *s5_clk; + struct clk_parent_data pdata = { .index = 0 }; + struct clk_init_data init = { + .ops = &s5_pll_ops, + .num_parents = 1, + .parent_data = &pdata, + }; + + s5_clk = devm_kzalloc(dev, sizeof(*s5_clk), GFP_KERNEL); + if (!s5_clk) + return -ENOMEM; + + s5_clk->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(s5_clk->base)) + return PTR_ERR(s5_clk->base); + + for (i = 0; i < N_CLOCKS; i++) { + struct s5_hw_clk *s5_hw = &s5_clk->s5_hw[i]; + + init.name = clk_names[i]; + s5_hw->reg = s5_clk->base + (i * 4); + s5_hw->hw.init = &init; + ret = devm_clk_hw_register(dev, &s5_hw->hw); + if (ret) { + dev_err(dev, "failed to register %s clock\n", + init.name); + return ret; + } + } + + return devm_of_clk_add_hw_provider(dev, s5_clk_hw_get, s5_clk); +} + +static const struct of_device_id s5_clk_dt_ids[] = { + { .compatible = "microchip,sparx5-dpll", }, + { } +}; +MODULE_DEVICE_TABLE(of, s5_clk_dt_ids); + +static struct platform_driver s5_clk_driver = { + .probe = s5_clk_probe, + .driver = { + .name = "sparx5-clk", + .of_match_table = s5_clk_dt_ids, + }, +}; +builtin_platform_driver(s5_clk_driver); -- cgit v1.2.3 From 4aeccdf0671125adee903b651fb134a4759ce161 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Wed, 29 Jul 2020 13:34:39 +0200 Subject: clk: mmp: avoid missing prototype warning The kernel test robot points out two harmless warnings in the mmp clk drivers: drivers/clk/mmp/clk-pxa168.c:68:13: warning: no previous prototype for 'pxa168_clk_init' [-Wmissing-prototypes] drivers/clk/mmp/clk-pxa910.c:66:13: warning: no previous prototype for 'pxa910_clk_init' [-Wmissing-prototypes] Fix these by including corresponding header file. Reported-by: kernel test robot Signed-off-by: Arnd Bergmann Link: https://lore.kernel.org/r/20200729113456.4072290-1-arnd@arndb.de Signed-off-by: Stephen Boyd --- drivers/clk/mmp/clk-pxa168.c | 1 + drivers/clk/mmp/clk-pxa910.c | 1 + 2 files changed, 2 insertions(+) (limited to 'drivers/clk') diff --git a/drivers/clk/mmp/clk-pxa168.c b/drivers/clk/mmp/clk-pxa168.c index 8e2551ab8462..b351039cac09 100644 --- a/drivers/clk/mmp/clk-pxa168.c +++ b/drivers/clk/mmp/clk-pxa168.c @@ -10,6 +10,7 @@ */ #include +#include #include #include #include diff --git a/drivers/clk/mmp/clk-pxa910.c b/drivers/clk/mmp/clk-pxa910.c index 7a7965141918..f254ceff3ea7 100644 --- a/drivers/clk/mmp/clk-pxa910.c +++ b/drivers/clk/mmp/clk-pxa910.c @@ -10,6 +10,7 @@ */ #include +#include #include #include #include -- cgit v1.2.3 From 6cfde88418fe95240e32d2955b51988360ee0942 Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Sat, 1 Aug 2020 10:53:42 +0200 Subject: clk: drop unused function __clk_get_flags The function __clk_get_flags has not been used since the April 2019 commit a348f05361c9 ("ARM: omap2+: hwmod: drop CLK_IS_BASIC flag usage"). Other uses were removed in June 2015, eg by commit 98d8a60eccee ("clk: Convert __clk_get_flags() to clk_hw_get_flags()"), which shows how clk_hw_get_flags can easily be used instead. Signed-off-by: Julia Lawall Link: https://lore.kernel.org/r/1596272022-14173-1-git-send-email-Julia.Lawall@inria.fr Signed-off-by: Stephen Boyd --- drivers/clk/clk.c | 6 ------ include/linux/clk-provider.h | 1 - 2 files changed, 7 deletions(-) (limited to 'drivers/clk') diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c index 3f588ed06ce3..d71456aad11d 100644 --- a/drivers/clk/clk.c +++ b/drivers/clk/clk.c @@ -500,12 +500,6 @@ static unsigned long clk_core_get_accuracy_no_lock(struct clk_core *core) return core->accuracy; } -unsigned long __clk_get_flags(struct clk *clk) -{ - return !clk ? 0 : clk->core->flags; -} -EXPORT_SYMBOL_GPL(__clk_get_flags); - unsigned long clk_hw_get_flags(const struct clk_hw *hw) { return hw->core->flags; diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h index bd1ee9039558..93a78a5256d1 100644 --- a/include/linux/clk-provider.h +++ b/include/linux/clk-provider.h @@ -1096,7 +1096,6 @@ int clk_hw_get_parent_index(struct clk_hw *hw); int clk_hw_set_parent(struct clk_hw *hw, struct clk_hw *new_parent); unsigned int __clk_get_enable_count(struct clk *clk); unsigned long clk_hw_get_rate(const struct clk_hw *hw); -unsigned long __clk_get_flags(struct clk *clk); unsigned long clk_hw_get_flags(const struct clk_hw *hw); #define clk_hw_can_set_rate_parent(hw) \ (clk_hw_get_flags((hw)) & CLK_SET_RATE_PARENT) -- cgit v1.2.3