1 // SPDX-License-Identifier: GPL-2.0-or-later
3 * Microchip Sparx5 SoC Clock driver.
5 * Copyright (c) 2019 Microchip Inc.
7 * Author: Lars Povlsen <lars.povlsen@microchip.com>
11 #include <linux/module.h>
12 #include <linux/clk-provider.h>
13 #include <linux/bitfield.h>
15 #include <linux/slab.h>
16 #include <linux/platform_device.h>
17 #include <dt-bindings/clock/microchip,sparx5.h>
19 #define PLL_DIV GENMASK(7, 0)
20 #define PLL_PRE_DIV GENMASK(10, 8)
21 #define PLL_ROT_DIR BIT(11)
22 #define PLL_ROT_SEL GENMASK(13, 12)
23 #define PLL_ROT_ENA BIT(14)
24 #define PLL_CLK_ENA BIT(15)
27 #define MAX_PRE BIT(3)
29 static const u8 sel_rates
[MAX_SEL
] = { 0, 2*8, 2*4, 2*2 };
31 static const char *clk_names
[N_CLOCKS
] = {
32 "core", "ddr", "cpu2", "arm2",
33 "aux1", "aux2", "aux3", "aux4",
44 struct s5_hw_clk s5_hw
[N_CLOCKS
];
56 #define to_s5_pll(hw) container_of(hw, struct s5_hw_clk, hw)
58 static unsigned long s5_calc_freq(unsigned long parent_rate
,
59 const struct s5_pll_conf
*conf
)
61 unsigned long rate
= parent_rate
/ conf
->div
;
64 int sign
= conf
->rot_dir
? -1 : 1;
65 int divt
= sel_rates
[conf
->rot_sel
] * (1 + conf
->pre_div
);
66 int divb
= divt
+ sign
;
68 rate
= mult_frac(rate
, divt
, divb
);
69 rate
= roundup(rate
, 1000);
75 static void s5_search_fractional(unsigned long rate
,
76 unsigned long parent_rate
,
78 struct s5_pll_conf
*conf
)
80 struct s5_pll_conf best
;
81 ulong cur_offset
, best_offset
= rate
;
84 memset(conf
, 0, sizeof(*conf
));
86 conf
->rot_ena
= 1; /* Fractional rate */
88 for (d
= 0; best_offset
> 0 && d
<= 1 ; d
++) {
90 for (i
= 0; best_offset
> 0 && i
< MAX_PRE
; i
++) {
92 for (j
= 1; best_offset
> 0 && j
< MAX_SEL
; j
++) {
94 conf
->freq
= s5_calc_freq(parent_rate
, conf
);
95 cur_offset
= abs(rate
- conf
->freq
);
96 if (cur_offset
< best_offset
) {
97 best_offset
= cur_offset
;
108 static unsigned long s5_calc_params(unsigned long rate
,
109 unsigned long parent_rate
,
110 struct s5_pll_conf
*conf
)
112 if (parent_rate
% rate
) {
113 struct s5_pll_conf alt1
, alt2
;
116 div
= DIV_ROUND_CLOSEST_ULL(parent_rate
, rate
);
117 s5_search_fractional(rate
, parent_rate
, div
, &alt1
);
119 /* Straight match? */
120 if (alt1
.freq
== rate
) {
123 /* Try without rounding divider */
124 div
= parent_rate
/ rate
;
125 if (div
!= alt1
.div
) {
126 s5_search_fractional(rate
, parent_rate
, div
,
128 /* Select the better match */
129 if (abs(rate
- alt1
.freq
) <
130 abs(rate
- alt2
.freq
))
138 memset(conf
, 0, sizeof(*conf
));
139 conf
->div
= parent_rate
/ rate
;
145 static int s5_pll_enable(struct clk_hw
*hw
)
147 struct s5_hw_clk
*pll
= to_s5_pll(hw
);
148 u32 val
= readl(pll
->reg
);
151 writel(val
, pll
->reg
);
156 static void s5_pll_disable(struct clk_hw
*hw
)
158 struct s5_hw_clk
*pll
= to_s5_pll(hw
);
159 u32 val
= readl(pll
->reg
);
162 writel(val
, pll
->reg
);
165 static int s5_pll_set_rate(struct clk_hw
*hw
,
167 unsigned long parent_rate
)
169 struct s5_hw_clk
*pll
= to_s5_pll(hw
);
170 struct s5_pll_conf conf
;
171 unsigned long eff_rate
;
174 eff_rate
= s5_calc_params(rate
, parent_rate
, &conf
);
175 if (eff_rate
!= rate
)
178 val
= readl(pll
->reg
) & PLL_CLK_ENA
;
179 val
|= FIELD_PREP(PLL_DIV
, conf
.div
);
182 val
|= FIELD_PREP(PLL_ROT_SEL
, conf
.rot_sel
);
183 val
|= FIELD_PREP(PLL_PRE_DIV
, conf
.pre_div
);
187 writel(val
, pll
->reg
);
192 static unsigned long s5_pll_recalc_rate(struct clk_hw
*hw
,
193 unsigned long parent_rate
)
195 struct s5_hw_clk
*pll
= to_s5_pll(hw
);
196 struct s5_pll_conf conf
;
199 val
= readl(pll
->reg
);
201 if (val
& PLL_CLK_ENA
) {
202 conf
.div
= FIELD_GET(PLL_DIV
, val
);
203 conf
.pre_div
= FIELD_GET(PLL_PRE_DIV
, val
);
204 conf
.rot_ena
= FIELD_GET(PLL_ROT_ENA
, val
);
205 conf
.rot_dir
= FIELD_GET(PLL_ROT_DIR
, val
);
206 conf
.rot_sel
= FIELD_GET(PLL_ROT_SEL
, val
);
208 conf
.freq
= s5_calc_freq(parent_rate
, &conf
);
216 static long s5_pll_round_rate(struct clk_hw
*hw
, unsigned long rate
,
217 unsigned long *parent_rate
)
219 struct s5_pll_conf conf
;
221 return s5_calc_params(rate
, *parent_rate
, &conf
);
224 static const struct clk_ops s5_pll_ops
= {
225 .enable
= s5_pll_enable
,
226 .disable
= s5_pll_disable
,
227 .set_rate
= s5_pll_set_rate
,
228 .round_rate
= s5_pll_round_rate
,
229 .recalc_rate
= s5_pll_recalc_rate
,
232 static struct clk_hw
*s5_clk_hw_get(struct of_phandle_args
*clkspec
, void *data
)
234 struct s5_clk_data
*s5_clk
= data
;
235 unsigned int idx
= clkspec
->args
[0];
237 if (idx
>= N_CLOCKS
) {
238 pr_err("%s: invalid index %u\n", __func__
, idx
);
239 return ERR_PTR(-EINVAL
);
242 return &s5_clk
->s5_hw
[idx
].hw
;
245 static int s5_clk_probe(struct platform_device
*pdev
)
247 struct device
*dev
= &pdev
->dev
;
249 struct s5_clk_data
*s5_clk
;
250 struct clk_parent_data pdata
= { .index
= 0 };
251 struct clk_init_data init
= {
254 .parent_data
= &pdata
,
257 s5_clk
= devm_kzalloc(dev
, sizeof(*s5_clk
), GFP_KERNEL
);
261 s5_clk
->base
= devm_platform_ioremap_resource(pdev
, 0);
262 if (IS_ERR(s5_clk
->base
))
263 return PTR_ERR(s5_clk
->base
);
265 for (i
= 0; i
< N_CLOCKS
; i
++) {
266 struct s5_hw_clk
*s5_hw
= &s5_clk
->s5_hw
[i
];
268 init
.name
= clk_names
[i
];
269 s5_hw
->reg
= s5_clk
->base
+ (i
* 4);
270 s5_hw
->hw
.init
= &init
;
271 ret
= devm_clk_hw_register(dev
, &s5_hw
->hw
);
273 dev_err(dev
, "failed to register %s clock\n",
279 return devm_of_clk_add_hw_provider(dev
, s5_clk_hw_get
, s5_clk
);
282 static const struct of_device_id s5_clk_dt_ids
[] = {
283 { .compatible
= "microchip,sparx5-dpll", },
286 MODULE_DEVICE_TABLE(of
, s5_clk_dt_ids
);
288 static struct platform_driver s5_clk_driver
= {
289 .probe
= s5_clk_probe
,
291 .name
= "sparx5-clk",
292 .of_match_table
= s5_clk_dt_ids
,
295 builtin_platform_driver(s5_clk_driver
);