[PATCH linux dev-5.8 1/4] clk: ast2600: Add functionality to the APLL clock
Eddie James
eajames at linux.ibm.com
Thu Sep 3 01:28:16 AEST 2020
On 9/1/20 1:36 AM, Joel Stanley wrote:
> On Mon, 24 Aug 2020 at 22:11, Eddie James <eajames at 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 at 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
>>
More information about the openbmc
mailing list