Message ID | 20200824221051.47972-2-eajames@linux.ibm.com |
---|---|
State | New |
Headers | show |
Series | AST2600 clock and FSI: Add APLL to control FSI clock | expand |
On Mon, 24 Aug 2020 at 22:11, Eddie James <eajames@linux.ibm.com> wrote: > > Register a clock with it's own operations to describe the APLL on > the AST2600. The clock is controlled by an SCU register containing > a multiplier and divider of the 25MHz input clock. > The functionality to change the APLL is necessary to finely control > the FSI clock. I thought the FSI clock could be sourced from either the APLL or another PLL. Should we go to the effort of modelling that in the clock driver? > > Signed-off-by: Eddie James <eajames@linux.ibm.com> > --- > drivers/clk/clk-ast2600.c | 177 ++++++++++++++++++++-- > include/dt-bindings/clock/ast2600-clock.h | 1 + > 2 files changed, 166 insertions(+), 12 deletions(-) > > diff --git a/drivers/clk/clk-ast2600.c b/drivers/clk/clk-ast2600.c > index bbacaccad554..975677491f09 100644 > --- a/drivers/clk/clk-ast2600.c > +++ b/drivers/clk/clk-ast2600.c > @@ -4,6 +4,7 @@ > > #define pr_fmt(fmt) "clk-ast2600: " fmt > > +#include <linux/kernel.h> > #include <linux/mfd/syscon.h> > #include <linux/of_address.h> > #include <linux/of_device.h> > @@ -15,7 +16,7 @@ > > #include "clk-aspeed.h" > > -#define ASPEED_G6_NUM_CLKS 71 > +#define ASPEED_G6_NUM_CLKS 72 > > #define ASPEED_G6_SILICON_REV 0x004 > > @@ -31,6 +32,7 @@ > #define ASPEED_G6_CLK_SELECTION1 0x300 > #define ASPEED_G6_CLK_SELECTION2 0x304 > #define ASPEED_G6_CLK_SELECTION4 0x310 > +#define ASPEED_G6_CLK_SELECTION5 0x314 > > #define ASPEED_HPLL_PARAM 0x200 > #define ASPEED_APLL_PARAM 0x210 > @@ -116,7 +118,7 @@ static const struct aspeed_gate_data aspeed_g6_gates[] = { > [ASPEED_CLK_GATE_UART11CLK] = { 59, -1, "uart11clk-gate", "uartx", 0 }, /* UART11 */ > [ASPEED_CLK_GATE_UART12CLK] = { 60, -1, "uart12clk-gate", "uartx", 0 }, /* UART12 */ > [ASPEED_CLK_GATE_UART13CLK] = { 61, -1, "uart13clk-gate", "uartx", 0 }, /* UART13 */ > - [ASPEED_CLK_GATE_FSICLK] = { 62, 59, "fsiclk-gate", NULL, 0 }, /* FSI */ > + [ASPEED_CLK_GATE_FSICLK] = { 62, 59, "fsiclk-gate", "aplln", CLK_SET_RATE_PARENT }, /* FSI */ > }; > > static const struct clk_div_table ast2600_eclk_div_table[] = { > @@ -187,24 +189,166 @@ static struct clk_hw *ast2600_calc_pll(const char *name, u32 val) > mult, div); > }; > > -static struct clk_hw *ast2600_calc_apll(const char *name, u32 val) > +/* > + * APLL Frequency: F = 25MHz * (2 - od) * [(m + 2) / (n + 1)] > + */ > +static void ast2600_apll_get_params(unsigned int *div, unsigned int *mul) > { > - unsigned int mult, div; > + u32 val = readl(scu_g6_base + ASPEED_APLL_PARAM); > > if (val & BIT(20)) { > /* Pass through mode */ > - mult = div = 1; > + *mul = *div = 1; > } else { > - /* F = 25Mhz * (2-od) * [(m + 2) / (n + 1)] */ > u32 m = (val >> 5) & 0x3f; > u32 od = (val >> 4) & 0x1; > u32 n = val & 0xf; > > - mult = (2 - od) * (m + 2); > - div = n + 1; > + *mul = (2 - od) * (m + 2); > + *div = n + 1; > } > - return clk_hw_register_fixed_factor(NULL, name, "clkin", 0, > - mult, div); > +} > + > +static long ast2600_apll_best(unsigned long ul_rate, unsigned long ul_prate, > + unsigned int *out_div, unsigned int *out_mul, > + unsigned int *output_divider) > +{ > +#define min_mult 2ULL > +#define max_mult 65ULL > +#define min_div 1ULL > +#define max_div 16ULL > + int i; > + unsigned int bod = 0; > + unsigned long long rem = 1ULL; > + unsigned long long brem = ~(0ULL); > + unsigned long long bdiv = 1ULL; > + unsigned long long tdiv; > + unsigned long long bmul = 16ULL; > + unsigned long long tmul; > + long brate = -ERANGE; > + unsigned long long trate; > + unsigned long long rate = ul_rate; > + unsigned long long prate = ul_prate; This is pretty full on. Can you take a look at some other clock drivers and see how they solve it? Can we hardcode a few known configurations, and select the closest? If we have to do a search, take a look at something like rational_best_approximation in include/linux/rational.h Importantly, send the changes to the clk driver upstream for review instead of doing a v2 on the openbmc list. > + > + for (i = 0; i < 2; ++i, prate *= 2ULL) { > + for (tdiv = min_div; tdiv <= max_div; ++tdiv) { > + tmul = DIV_ROUND_CLOSEST_ULL(rate * tdiv, prate); > + if (tmul < min_mult || tmul > max_mult) > + continue; > + > + trate = DIV_ROUND_CLOSEST_ULL(prate * tmul, tdiv); > + if (trate > rate) > + rem = trate - rate; > + else > + rem = rate - trate; > + > + if (rem < brem) { > + bod = !i; > + brem = rem; > + bdiv = tdiv; > + bmul = tmul; > + brate = (long)trate; > + } > + > + if (!rem) > + break; > + } > + > + if (!rem) > + break; > + } > + > + if (out_div) > + *out_div = (unsigned int)bdiv; > + > + if (out_mul) > + *out_mul = (unsigned int)bmul; > + > + if (output_divider) > + *output_divider = bod; > + > + return brate; > +#undef min_mult > +#undef max_mult > +#undef min_div > +#undef max_div > +} > + > +static unsigned long ast2600_apll_recalc_rate(struct clk_hw *hw, > + unsigned long parent_rate) > +{ > + unsigned int div; > + unsigned int mul; > + unsigned long long rate; > + unsigned long long prate = (unsigned long long)parent_rate; > + > + ast2600_apll_get_params(&div, &mul); > + > + rate = DIV_ROUND_CLOSEST_ULL(prate * (unsigned long long)mul, div); > + return (unsigned long)rate; > +} > + > +static long ast2600_apll_round_rate(struct clk_hw *hw, unsigned long rate, > + unsigned long *parent_rate) > +{ > + return ast2600_apll_best(rate, *parent_rate, NULL, NULL, NULL); > +} > + > +static int ast2600_apll_set_rate(struct clk_hw *hw, unsigned long rate, > + unsigned long parent_rate) > +{ > + u32 val; > + unsigned int od; > + unsigned int div; > + unsigned int mul; > + long brate = ast2600_apll_best(rate, parent_rate, &div, &mul, &od); > + > + if (brate < 0) > + return brate; > + > + val = readl(scu_g6_base + ASPEED_APLL_PARAM); > + val &= ~0x7ff; > + val |= (div - 1) & 0xf; > + val |= ((mul - 2) & 0x3f) << 5; > + if (od) > + val |= 0x10; > + writel(val, scu_g6_base + ASPEED_APLL_PARAM); > + > + return 0; > +} > + > +static const struct clk_ops ast2600_apll_ops = { > + .recalc_rate = ast2600_apll_recalc_rate, > + .round_rate = ast2600_apll_round_rate, > + .set_rate = ast2600_apll_set_rate, > +}; > + > +static struct clk_hw *ast2600_create_apll(void) > +{ > + int rc; > + const char *parent = "clkin"; > + struct clk_init_data init = { const? > + .name = "apll", > + .ops = &ast2600_apll_ops, > + .parent_names = &parent, > + .parent_data = NULL, > + .parent_hws = NULL, > + .num_parents = 1, > + .flags = 0, > + }; > + struct clk_hw *clk = kzalloc(sizeof(*clk), GFP_KERNEL); > + > + if (!clk) > + return ERR_PTR(-ENOMEM); > + > + clk->init = &init; > + rc = of_clk_hw_register(NULL, clk); > + if (rc) { > + kfree(clk); > + clk = ERR_PTR(rc); > + } > + > + return clk; > }; > > static u32 get_bit(u8 idx) > @@ -630,6 +774,16 @@ static int aspeed_g6_clk_probe(struct platform_device *pdev) > return PTR_ERR(hw); > aspeed_g6_clk_data->hws[ASPEED_CLK_ECLK] = hw; > > + hw = clk_hw_register_divider_table(dev, "aplln", "apll", > + CLK_SET_RATE_PARENT, > + scu_g6_base + ASPEED_G6_CLK_SELECTION5, > + 28, 3, CLK_DIVIDER_READ_ONLY, > + ast2600_eclk_div_table, > + &aspeed_g6_clk_lock); > + if (IS_ERR(hw)) > + return PTR_ERR(hw); > + aspeed_g6_clk_data->hws[ASPEED_CLK_APLLN] = hw; > + > for (i = 0; i < ARRAY_SIZE(aspeed_g6_gates); i++) { > const struct aspeed_gate_data *gd = &aspeed_g6_gates[i]; > u32 gate_flags; > @@ -710,8 +864,7 @@ static void __init aspeed_g6_cc(struct regmap *map) > regmap_read(map, ASPEED_EPLL_PARAM, &val); > aspeed_g6_clk_data->hws[ASPEED_CLK_EPLL] = ast2600_calc_pll("epll", val); > > - regmap_read(map, ASPEED_APLL_PARAM, &val); > - aspeed_g6_clk_data->hws[ASPEED_CLK_APLL] = ast2600_calc_apll("apll", val); > + aspeed_g6_clk_data->hws[ASPEED_CLK_APLL] = ast2600_create_apll(); > > /* Strap bits 12:11 define the AXI/AHB clock frequency ratio (aka HCLK)*/ > regmap_read(map, ASPEED_G6_STRAP1, &val); > diff --git a/include/dt-bindings/clock/ast2600-clock.h b/include/dt-bindings/clock/ast2600-clock.h > index 62b9520a00fd..a286d63de399 100644 > --- a/include/dt-bindings/clock/ast2600-clock.h > +++ b/include/dt-bindings/clock/ast2600-clock.h > @@ -87,6 +87,7 @@ > #define ASPEED_CLK_MAC2RCLK 68 > #define ASPEED_CLK_MAC3RCLK 69 > #define ASPEED_CLK_MAC4RCLK 70 > +#define ASPEED_CLK_APLLN 71 > > /* Only list resets here that are not part of a gate */ > #define ASPEED_RESET_ADC 55 > -- > 2.26.2 >
On 9/1/20 1:36 AM, Joel Stanley wrote: > On Mon, 24 Aug 2020 at 22:11, Eddie James <eajames@linux.ibm.com> wrote: >> Register a clock with it's own operations to describe the APLL on >> the AST2600. The clock is controlled by an SCU register containing >> a multiplier and divider of the 25MHz input clock. >> The functionality to change the APLL is necessary to finely control >> the FSI clock. > I thought the FSI clock could be sourced from either the APLL or > another PLL. Should we go to the effort of modelling that in the clock > driver? Hm, I don't see that in the specification? Thanks, Eddie > >> Signed-off-by: Eddie James <eajames@linux.ibm.com> >> --- >> drivers/clk/clk-ast2600.c | 177 ++++++++++++++++++++-- >> include/dt-bindings/clock/ast2600-clock.h | 1 + >> 2 files changed, 166 insertions(+), 12 deletions(-) >> >> diff --git a/drivers/clk/clk-ast2600.c b/drivers/clk/clk-ast2600.c >> index bbacaccad554..975677491f09 100644 >> --- a/drivers/clk/clk-ast2600.c >> +++ b/drivers/clk/clk-ast2600.c >> @@ -4,6 +4,7 @@ >> >> #define pr_fmt(fmt) "clk-ast2600: " fmt >> >> +#include <linux/kernel.h> >> #include <linux/mfd/syscon.h> >> #include <linux/of_address.h> >> #include <linux/of_device.h> >> @@ -15,7 +16,7 @@ >> >> #include "clk-aspeed.h" >> >> -#define ASPEED_G6_NUM_CLKS 71 >> +#define ASPEED_G6_NUM_CLKS 72 >> >> #define ASPEED_G6_SILICON_REV 0x004 >> >> @@ -31,6 +32,7 @@ >> #define ASPEED_G6_CLK_SELECTION1 0x300 >> #define ASPEED_G6_CLK_SELECTION2 0x304 >> #define ASPEED_G6_CLK_SELECTION4 0x310 >> +#define ASPEED_G6_CLK_SELECTION5 0x314 >> >> #define ASPEED_HPLL_PARAM 0x200 >> #define ASPEED_APLL_PARAM 0x210 >> @@ -116,7 +118,7 @@ static const struct aspeed_gate_data aspeed_g6_gates[] = { >> [ASPEED_CLK_GATE_UART11CLK] = { 59, -1, "uart11clk-gate", "uartx", 0 }, /* UART11 */ >> [ASPEED_CLK_GATE_UART12CLK] = { 60, -1, "uart12clk-gate", "uartx", 0 }, /* UART12 */ >> [ASPEED_CLK_GATE_UART13CLK] = { 61, -1, "uart13clk-gate", "uartx", 0 }, /* UART13 */ >> - [ASPEED_CLK_GATE_FSICLK] = { 62, 59, "fsiclk-gate", NULL, 0 }, /* FSI */ >> + [ASPEED_CLK_GATE_FSICLK] = { 62, 59, "fsiclk-gate", "aplln", CLK_SET_RATE_PARENT }, /* FSI */ >> }; >> >> static const struct clk_div_table ast2600_eclk_div_table[] = { >> @@ -187,24 +189,166 @@ static struct clk_hw *ast2600_calc_pll(const char *name, u32 val) >> mult, div); >> }; >> >> -static struct clk_hw *ast2600_calc_apll(const char *name, u32 val) >> +/* >> + * APLL Frequency: F = 25MHz * (2 - od) * [(m + 2) / (n + 1)] >> + */ >> +static void ast2600_apll_get_params(unsigned int *div, unsigned int *mul) >> { >> - unsigned int mult, div; >> + u32 val = readl(scu_g6_base + ASPEED_APLL_PARAM); >> >> if (val & BIT(20)) { >> /* Pass through mode */ >> - mult = div = 1; >> + *mul = *div = 1; >> } else { >> - /* F = 25Mhz * (2-od) * [(m + 2) / (n + 1)] */ >> u32 m = (val >> 5) & 0x3f; >> u32 od = (val >> 4) & 0x1; >> u32 n = val & 0xf; >> >> - mult = (2 - od) * (m + 2); >> - div = n + 1; >> + *mul = (2 - od) * (m + 2); >> + *div = n + 1; >> } >> - return clk_hw_register_fixed_factor(NULL, name, "clkin", 0, >> - mult, div); >> +} >> + >> +static long ast2600_apll_best(unsigned long ul_rate, unsigned long ul_prate, >> + unsigned int *out_div, unsigned int *out_mul, >> + unsigned int *output_divider) >> +{ >> +#define min_mult 2ULL >> +#define max_mult 65ULL >> +#define min_div 1ULL >> +#define max_div 16ULL >> + int i; >> + unsigned int bod = 0; >> + unsigned long long rem = 1ULL; >> + unsigned long long brem = ~(0ULL); >> + unsigned long long bdiv = 1ULL; >> + unsigned long long tdiv; >> + unsigned long long bmul = 16ULL; >> + unsigned long long tmul; >> + long brate = -ERANGE; >> + unsigned long long trate; >> + unsigned long long rate = ul_rate; >> + unsigned long long prate = ul_prate; > This is pretty full on. Can you take a look at some other clock > drivers and see how they solve it? > > Can we hardcode a few known configurations, and select the closest? > > If we have to do a search, take a look at something like > rational_best_approximation in include/linux/rational.h > > Importantly, send the changes to the clk driver upstream for review > instead of doing a v2 on the openbmc list. > >> + >> + for (i = 0; i < 2; ++i, prate *= 2ULL) { >> + for (tdiv = min_div; tdiv <= max_div; ++tdiv) { >> + tmul = DIV_ROUND_CLOSEST_ULL(rate * tdiv, prate); >> + if (tmul < min_mult || tmul > max_mult) >> + continue; >> + >> + trate = DIV_ROUND_CLOSEST_ULL(prate * tmul, tdiv); >> + if (trate > rate) >> + rem = trate - rate; >> + else >> + rem = rate - trate; >> + >> + if (rem < brem) { >> + bod = !i; >> + brem = rem; >> + bdiv = tdiv; >> + bmul = tmul; >> + brate = (long)trate; >> + } >> + >> + if (!rem) >> + break; >> + } >> + >> + if (!rem) >> + break; >> + } >> + >> + if (out_div) >> + *out_div = (unsigned int)bdiv; >> + >> + if (out_mul) >> + *out_mul = (unsigned int)bmul; >> + >> + if (output_divider) >> + *output_divider = bod; >> + >> + return brate; >> +#undef min_mult >> +#undef max_mult >> +#undef min_div >> +#undef max_div >> +} >> + >> +static unsigned long ast2600_apll_recalc_rate(struct clk_hw *hw, >> + unsigned long parent_rate) >> +{ >> + unsigned int div; >> + unsigned int mul; >> + unsigned long long rate; >> + unsigned long long prate = (unsigned long long)parent_rate; >> + >> + ast2600_apll_get_params(&div, &mul); >> + >> + rate = DIV_ROUND_CLOSEST_ULL(prate * (unsigned long long)mul, div); >> + return (unsigned long)rate; >> +} >> + >> +static long ast2600_apll_round_rate(struct clk_hw *hw, unsigned long rate, >> + unsigned long *parent_rate) >> +{ >> + return ast2600_apll_best(rate, *parent_rate, NULL, NULL, NULL); >> +} >> + >> +static int ast2600_apll_set_rate(struct clk_hw *hw, unsigned long rate, >> + unsigned long parent_rate) >> +{ >> + u32 val; >> + unsigned int od; >> + unsigned int div; >> + unsigned int mul; >> + long brate = ast2600_apll_best(rate, parent_rate, &div, &mul, &od); >> + >> + if (brate < 0) >> + return brate; >> + >> + val = readl(scu_g6_base + ASPEED_APLL_PARAM); >> + val &= ~0x7ff; >> + val |= (div - 1) & 0xf; >> + val |= ((mul - 2) & 0x3f) << 5; >> + if (od) >> + val |= 0x10; >> + writel(val, scu_g6_base + ASPEED_APLL_PARAM); >> + >> + return 0; >> +} >> + >> +static const struct clk_ops ast2600_apll_ops = { >> + .recalc_rate = ast2600_apll_recalc_rate, >> + .round_rate = ast2600_apll_round_rate, >> + .set_rate = ast2600_apll_set_rate, >> +}; >> + >> +static struct clk_hw *ast2600_create_apll(void) >> +{ >> + int rc; >> + const char *parent = "clkin"; >> + struct clk_init_data init = { > const? > >> + .name = "apll", >> + .ops = &ast2600_apll_ops, >> + .parent_names = &parent, >> + .parent_data = NULL, >> + .parent_hws = NULL, >> + .num_parents = 1, >> + .flags = 0, >> + }; >> + struct clk_hw *clk = kzalloc(sizeof(*clk), GFP_KERNEL); >> + >> + if (!clk) >> + return ERR_PTR(-ENOMEM); >> + >> + clk->init = &init; >> + rc = of_clk_hw_register(NULL, clk); >> + if (rc) { >> + kfree(clk); >> + clk = ERR_PTR(rc); >> + } >> + >> + return clk; >> }; >> >> static u32 get_bit(u8 idx) >> @@ -630,6 +774,16 @@ static int aspeed_g6_clk_probe(struct platform_device *pdev) >> return PTR_ERR(hw); >> aspeed_g6_clk_data->hws[ASPEED_CLK_ECLK] = hw; >> >> + hw = clk_hw_register_divider_table(dev, "aplln", "apll", >> + CLK_SET_RATE_PARENT, >> + scu_g6_base + ASPEED_G6_CLK_SELECTION5, >> + 28, 3, CLK_DIVIDER_READ_ONLY, >> + ast2600_eclk_div_table, >> + &aspeed_g6_clk_lock); >> + if (IS_ERR(hw)) >> + return PTR_ERR(hw); >> + aspeed_g6_clk_data->hws[ASPEED_CLK_APLLN] = hw; >> + >> for (i = 0; i < ARRAY_SIZE(aspeed_g6_gates); i++) { >> const struct aspeed_gate_data *gd = &aspeed_g6_gates[i]; >> u32 gate_flags; >> @@ -710,8 +864,7 @@ static void __init aspeed_g6_cc(struct regmap *map) >> regmap_read(map, ASPEED_EPLL_PARAM, &val); >> aspeed_g6_clk_data->hws[ASPEED_CLK_EPLL] = ast2600_calc_pll("epll", val); >> >> - regmap_read(map, ASPEED_APLL_PARAM, &val); >> - aspeed_g6_clk_data->hws[ASPEED_CLK_APLL] = ast2600_calc_apll("apll", val); >> + aspeed_g6_clk_data->hws[ASPEED_CLK_APLL] = ast2600_create_apll(); >> >> /* Strap bits 12:11 define the AXI/AHB clock frequency ratio (aka HCLK)*/ >> regmap_read(map, ASPEED_G6_STRAP1, &val); >> diff --git a/include/dt-bindings/clock/ast2600-clock.h b/include/dt-bindings/clock/ast2600-clock.h >> index 62b9520a00fd..a286d63de399 100644 >> --- a/include/dt-bindings/clock/ast2600-clock.h >> +++ b/include/dt-bindings/clock/ast2600-clock.h >> @@ -87,6 +87,7 @@ >> #define ASPEED_CLK_MAC2RCLK 68 >> #define ASPEED_CLK_MAC3RCLK 69 >> #define ASPEED_CLK_MAC4RCLK 70 >> +#define ASPEED_CLK_APLLN 71 >> >> /* Only list resets here that are not part of a gate */ >> #define ASPEED_RESET_ADC 55 >> -- >> 2.26.2 >>
diff --git a/drivers/clk/clk-ast2600.c b/drivers/clk/clk-ast2600.c index bbacaccad554..975677491f09 100644 --- a/drivers/clk/clk-ast2600.c +++ b/drivers/clk/clk-ast2600.c @@ -4,6 +4,7 @@ #define pr_fmt(fmt) "clk-ast2600: " fmt +#include <linux/kernel.h> #include <linux/mfd/syscon.h> #include <linux/of_address.h> #include <linux/of_device.h> @@ -15,7 +16,7 @@ #include "clk-aspeed.h" -#define ASPEED_G6_NUM_CLKS 71 +#define ASPEED_G6_NUM_CLKS 72 #define ASPEED_G6_SILICON_REV 0x004 @@ -31,6 +32,7 @@ #define ASPEED_G6_CLK_SELECTION1 0x300 #define ASPEED_G6_CLK_SELECTION2 0x304 #define ASPEED_G6_CLK_SELECTION4 0x310 +#define ASPEED_G6_CLK_SELECTION5 0x314 #define ASPEED_HPLL_PARAM 0x200 #define ASPEED_APLL_PARAM 0x210 @@ -116,7 +118,7 @@ static const struct aspeed_gate_data aspeed_g6_gates[] = { [ASPEED_CLK_GATE_UART11CLK] = { 59, -1, "uart11clk-gate", "uartx", 0 }, /* UART11 */ [ASPEED_CLK_GATE_UART12CLK] = { 60, -1, "uart12clk-gate", "uartx", 0 }, /* UART12 */ [ASPEED_CLK_GATE_UART13CLK] = { 61, -1, "uart13clk-gate", "uartx", 0 }, /* UART13 */ - [ASPEED_CLK_GATE_FSICLK] = { 62, 59, "fsiclk-gate", NULL, 0 }, /* FSI */ + [ASPEED_CLK_GATE_FSICLK] = { 62, 59, "fsiclk-gate", "aplln", CLK_SET_RATE_PARENT }, /* FSI */ }; static const struct clk_div_table ast2600_eclk_div_table[] = { @@ -187,24 +189,166 @@ static struct clk_hw *ast2600_calc_pll(const char *name, u32 val) mult, div); }; -static struct clk_hw *ast2600_calc_apll(const char *name, u32 val) +/* + * APLL Frequency: F = 25MHz * (2 - od) * [(m + 2) / (n + 1)] + */ +static void ast2600_apll_get_params(unsigned int *div, unsigned int *mul) { - unsigned int mult, div; + u32 val = readl(scu_g6_base + ASPEED_APLL_PARAM); if (val & BIT(20)) { /* Pass through mode */ - mult = div = 1; + *mul = *div = 1; } else { - /* F = 25Mhz * (2-od) * [(m + 2) / (n + 1)] */ u32 m = (val >> 5) & 0x3f; u32 od = (val >> 4) & 0x1; u32 n = val & 0xf; - mult = (2 - od) * (m + 2); - div = n + 1; + *mul = (2 - od) * (m + 2); + *div = n + 1; } - return clk_hw_register_fixed_factor(NULL, name, "clkin", 0, - mult, div); +} + +static long ast2600_apll_best(unsigned long ul_rate, unsigned long ul_prate, + unsigned int *out_div, unsigned int *out_mul, + unsigned int *output_divider) +{ +#define min_mult 2ULL +#define max_mult 65ULL +#define min_div 1ULL +#define max_div 16ULL + int i; + unsigned int bod = 0; + unsigned long long rem = 1ULL; + unsigned long long brem = ~(0ULL); + unsigned long long bdiv = 1ULL; + unsigned long long tdiv; + unsigned long long bmul = 16ULL; + unsigned long long tmul; + long brate = -ERANGE; + unsigned long long trate; + unsigned long long rate = ul_rate; + unsigned long long prate = ul_prate; + + for (i = 0; i < 2; ++i, prate *= 2ULL) { + for (tdiv = min_div; tdiv <= max_div; ++tdiv) { + tmul = DIV_ROUND_CLOSEST_ULL(rate * tdiv, prate); + if (tmul < min_mult || tmul > max_mult) + continue; + + trate = DIV_ROUND_CLOSEST_ULL(prate * tmul, tdiv); + if (trate > rate) + rem = trate - rate; + else + rem = rate - trate; + + if (rem < brem) { + bod = !i; + brem = rem; + bdiv = tdiv; + bmul = tmul; + brate = (long)trate; + } + + if (!rem) + break; + } + + if (!rem) + break; + } + + if (out_div) + *out_div = (unsigned int)bdiv; + + if (out_mul) + *out_mul = (unsigned int)bmul; + + if (output_divider) + *output_divider = bod; + + return brate; +#undef min_mult +#undef max_mult +#undef min_div +#undef max_div +} + +static unsigned long ast2600_apll_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + unsigned int div; + unsigned int mul; + unsigned long long rate; + unsigned long long prate = (unsigned long long)parent_rate; + + ast2600_apll_get_params(&div, &mul); + + rate = DIV_ROUND_CLOSEST_ULL(prate * (unsigned long long)mul, div); + return (unsigned long)rate; +} + +static long ast2600_apll_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + return ast2600_apll_best(rate, *parent_rate, NULL, NULL, NULL); +} + +static int ast2600_apll_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + u32 val; + unsigned int od; + unsigned int div; + unsigned int mul; + long brate = ast2600_apll_best(rate, parent_rate, &div, &mul, &od); + + if (brate < 0) + return brate; + + val = readl(scu_g6_base + ASPEED_APLL_PARAM); + val &= ~0x7ff; + val |= (div - 1) & 0xf; + val |= ((mul - 2) & 0x3f) << 5; + if (od) + val |= 0x10; + writel(val, scu_g6_base + ASPEED_APLL_PARAM); + + return 0; +} + +static const struct clk_ops ast2600_apll_ops = { + .recalc_rate = ast2600_apll_recalc_rate, + .round_rate = ast2600_apll_round_rate, + .set_rate = ast2600_apll_set_rate, +}; + +static struct clk_hw *ast2600_create_apll(void) +{ + int rc; + const char *parent = "clkin"; + struct clk_init_data init = { + .name = "apll", + .ops = &ast2600_apll_ops, + .parent_names = &parent, + .parent_data = NULL, + .parent_hws = NULL, + .num_parents = 1, + .flags = 0, + }; + struct clk_hw *clk = kzalloc(sizeof(*clk), GFP_KERNEL); + + if (!clk) + return ERR_PTR(-ENOMEM); + + clk->init = &init; + rc = of_clk_hw_register(NULL, clk); + if (rc) { + kfree(clk); + clk = ERR_PTR(rc); + } + + return clk; }; static u32 get_bit(u8 idx) @@ -630,6 +774,16 @@ static int aspeed_g6_clk_probe(struct platform_device *pdev) return PTR_ERR(hw); aspeed_g6_clk_data->hws[ASPEED_CLK_ECLK] = hw; + hw = clk_hw_register_divider_table(dev, "aplln", "apll", + CLK_SET_RATE_PARENT, + scu_g6_base + ASPEED_G6_CLK_SELECTION5, + 28, 3, CLK_DIVIDER_READ_ONLY, + ast2600_eclk_div_table, + &aspeed_g6_clk_lock); + if (IS_ERR(hw)) + return PTR_ERR(hw); + aspeed_g6_clk_data->hws[ASPEED_CLK_APLLN] = hw; + for (i = 0; i < ARRAY_SIZE(aspeed_g6_gates); i++) { const struct aspeed_gate_data *gd = &aspeed_g6_gates[i]; u32 gate_flags; @@ -710,8 +864,7 @@ static void __init aspeed_g6_cc(struct regmap *map) regmap_read(map, ASPEED_EPLL_PARAM, &val); aspeed_g6_clk_data->hws[ASPEED_CLK_EPLL] = ast2600_calc_pll("epll", val); - regmap_read(map, ASPEED_APLL_PARAM, &val); - aspeed_g6_clk_data->hws[ASPEED_CLK_APLL] = ast2600_calc_apll("apll", val); + aspeed_g6_clk_data->hws[ASPEED_CLK_APLL] = ast2600_create_apll(); /* Strap bits 12:11 define the AXI/AHB clock frequency ratio (aka HCLK)*/ regmap_read(map, ASPEED_G6_STRAP1, &val); diff --git a/include/dt-bindings/clock/ast2600-clock.h b/include/dt-bindings/clock/ast2600-clock.h index 62b9520a00fd..a286d63de399 100644 --- a/include/dt-bindings/clock/ast2600-clock.h +++ b/include/dt-bindings/clock/ast2600-clock.h @@ -87,6 +87,7 @@ #define ASPEED_CLK_MAC2RCLK 68 #define ASPEED_CLK_MAC3RCLK 69 #define ASPEED_CLK_MAC4RCLK 70 +#define ASPEED_CLK_APLLN 71 /* Only list resets here that are not part of a gate */ #define ASPEED_RESET_ADC 55
Register a clock with it's own operations to describe the APLL on the AST2600. The clock is controlled by an SCU register containing a multiplier and divider of the 25MHz input clock. The functionality to change the APLL is necessary to finely control the FSI clock. Signed-off-by: Eddie James <eajames@linux.ibm.com> --- drivers/clk/clk-ast2600.c | 177 ++++++++++++++++++++-- include/dt-bindings/clock/ast2600-clock.h | 1 + 2 files changed, 166 insertions(+), 12 deletions(-)