1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Author: Conor Dooley <conor.dooley@microchip.com> |
4 | * |
5 | * Copyright (C) 2022 Microchip Technology Inc. and its subsidiaries |
6 | */ |
7 | #include <linux/clk-provider.h> |
8 | #include <linux/errno.h> |
9 | #include <linux/io.h> |
10 | #include <linux/module.h> |
11 | #include <linux/platform_device.h> |
12 | #include <dt-bindings/clock/microchip,mpfs-clock.h> |
13 | |
14 | /* address offset of control registers */ |
15 | #define MPFS_CCC_PLL_CR 0x04u |
16 | #define MPFS_CCC_REF_CR 0x08u |
17 | #define MPFS_CCC_SSCG_2_CR 0x2Cu |
18 | #define MPFS_CCC_POSTDIV01_CR 0x10u |
19 | #define MPFS_CCC_POSTDIV23_CR 0x14u |
20 | |
21 | #define MPFS_CCC_FBDIV_SHIFT 0x00u |
22 | #define MPFS_CCC_FBDIV_WIDTH 0x0Cu |
23 | #define MPFS_CCC_POSTDIV0_SHIFT 0x08u |
24 | #define MPFS_CCC_POSTDIV1_SHIFT 0x18u |
25 | #define MPFS_CCC_POSTDIV2_SHIFT MPFS_CCC_POSTDIV0_SHIFT |
26 | #define MPFS_CCC_POSTDIV3_SHIFT MPFS_CCC_POSTDIV1_SHIFT |
27 | #define MPFS_CCC_POSTDIV_WIDTH 0x06u |
28 | #define MPFS_CCC_REFCLK_SEL BIT(6) |
29 | #define MPFS_CCC_REFDIV_SHIFT 0x08u |
30 | #define MPFS_CCC_REFDIV_WIDTH 0x06u |
31 | |
32 | #define MPFS_CCC_FIXED_DIV 4 |
33 | #define MPFS_CCC_OUTPUTS_PER_PLL 4 |
34 | #define MPFS_CCC_REFS_PER_PLL 2 |
35 | |
36 | struct mpfs_ccc_data { |
37 | void __iomem **pll_base; |
38 | struct device *dev; |
39 | struct clk_hw_onecell_data hw_data; |
40 | }; |
41 | |
42 | struct mpfs_ccc_pll_hw_clock { |
43 | void __iomem *base; |
44 | const char *name; |
45 | const struct clk_parent_data *parents; |
46 | unsigned int id; |
47 | u32 reg_offset; |
48 | u32 shift; |
49 | u32 width; |
50 | u32 flags; |
51 | struct clk_hw hw; |
52 | struct clk_init_data init; |
53 | }; |
54 | |
55 | #define to_mpfs_ccc_clk(_hw) container_of(_hw, struct mpfs_ccc_pll_hw_clock, hw) |
56 | |
57 | /* |
58 | * mpfs_ccc_lock prevents anything else from writing to a fabric ccc |
59 | * while a software locked register is being written. |
60 | */ |
61 | static DEFINE_SPINLOCK(mpfs_ccc_lock); |
62 | |
63 | static const struct clk_parent_data mpfs_ccc_pll0_refs[] = { |
64 | { .fw_name = "pll0_ref0" }, |
65 | { .fw_name = "pll0_ref1" }, |
66 | }; |
67 | |
68 | static const struct clk_parent_data mpfs_ccc_pll1_refs[] = { |
69 | { .fw_name = "pll1_ref0" }, |
70 | { .fw_name = "pll1_ref1" }, |
71 | }; |
72 | |
73 | static unsigned long mpfs_ccc_pll_recalc_rate(struct clk_hw *hw, unsigned long prate) |
74 | { |
75 | struct mpfs_ccc_pll_hw_clock *ccc_hw = to_mpfs_ccc_clk(hw); |
76 | void __iomem *mult_addr = ccc_hw->base + ccc_hw->reg_offset; |
77 | void __iomem *ref_div_addr = ccc_hw->base + MPFS_CCC_REF_CR; |
78 | u32 mult, ref_div; |
79 | |
80 | mult = readl_relaxed(mult_addr) >> MPFS_CCC_FBDIV_SHIFT; |
81 | mult &= clk_div_mask(MPFS_CCC_FBDIV_WIDTH); |
82 | ref_div = readl_relaxed(ref_div_addr) >> MPFS_CCC_REFDIV_SHIFT; |
83 | ref_div &= clk_div_mask(MPFS_CCC_REFDIV_WIDTH); |
84 | |
85 | return prate * mult / (ref_div * MPFS_CCC_FIXED_DIV); |
86 | } |
87 | |
88 | static u8 mpfs_ccc_pll_get_parent(struct clk_hw *hw) |
89 | { |
90 | struct mpfs_ccc_pll_hw_clock *ccc_hw = to_mpfs_ccc_clk(hw); |
91 | void __iomem *pll_cr_addr = ccc_hw->base + MPFS_CCC_PLL_CR; |
92 | |
93 | return !!(readl_relaxed(pll_cr_addr) & MPFS_CCC_REFCLK_SEL); |
94 | } |
95 | |
96 | static const struct clk_ops mpfs_ccc_pll_ops = { |
97 | .recalc_rate = mpfs_ccc_pll_recalc_rate, |
98 | .get_parent = mpfs_ccc_pll_get_parent, |
99 | }; |
100 | |
101 | #define CLK_CCC_PLL(_id, _parents, _shift, _width, _flags, _offset) { \ |
102 | .id = _id, \ |
103 | .shift = _shift, \ |
104 | .width = _width, \ |
105 | .reg_offset = _offset, \ |
106 | .flags = _flags, \ |
107 | .parents = _parents, \ |
108 | } |
109 | |
110 | static struct mpfs_ccc_pll_hw_clock mpfs_ccc_pll_clks[] = { |
111 | CLK_CCC_PLL(CLK_CCC_PLL0, mpfs_ccc_pll0_refs, MPFS_CCC_FBDIV_SHIFT, |
112 | MPFS_CCC_FBDIV_WIDTH, 0, MPFS_CCC_SSCG_2_CR), |
113 | CLK_CCC_PLL(CLK_CCC_PLL1, mpfs_ccc_pll1_refs, MPFS_CCC_FBDIV_SHIFT, |
114 | MPFS_CCC_FBDIV_WIDTH, 0, MPFS_CCC_SSCG_2_CR), |
115 | }; |
116 | |
117 | struct mpfs_ccc_out_hw_clock { |
118 | struct clk_divider divider; |
119 | struct clk_init_data init; |
120 | unsigned int id; |
121 | u32 reg_offset; |
122 | }; |
123 | |
124 | #define CLK_CCC_OUT(_id, _shift, _width, _flags, _offset) { \ |
125 | .id = _id, \ |
126 | .divider.shift = _shift, \ |
127 | .divider.width = _width, \ |
128 | .reg_offset = _offset, \ |
129 | .divider.flags = _flags, \ |
130 | .divider.lock = &mpfs_ccc_lock, \ |
131 | } |
132 | |
133 | static struct mpfs_ccc_out_hw_clock mpfs_ccc_pll0out_clks[] = { |
134 | CLK_CCC_OUT(CLK_CCC_PLL0_OUT0, MPFS_CCC_POSTDIV0_SHIFT, MPFS_CCC_POSTDIV_WIDTH, |
135 | CLK_DIVIDER_ONE_BASED, MPFS_CCC_POSTDIV01_CR), |
136 | CLK_CCC_OUT(CLK_CCC_PLL0_OUT1, MPFS_CCC_POSTDIV1_SHIFT, MPFS_CCC_POSTDIV_WIDTH, |
137 | CLK_DIVIDER_ONE_BASED, MPFS_CCC_POSTDIV01_CR), |
138 | CLK_CCC_OUT(CLK_CCC_PLL0_OUT2, MPFS_CCC_POSTDIV2_SHIFT, MPFS_CCC_POSTDIV_WIDTH, |
139 | CLK_DIVIDER_ONE_BASED, MPFS_CCC_POSTDIV23_CR), |
140 | CLK_CCC_OUT(CLK_CCC_PLL0_OUT3, MPFS_CCC_POSTDIV3_SHIFT, MPFS_CCC_POSTDIV_WIDTH, |
141 | CLK_DIVIDER_ONE_BASED, MPFS_CCC_POSTDIV23_CR), |
142 | }; |
143 | |
144 | static struct mpfs_ccc_out_hw_clock mpfs_ccc_pll1out_clks[] = { |
145 | CLK_CCC_OUT(CLK_CCC_PLL1_OUT0, MPFS_CCC_POSTDIV0_SHIFT, MPFS_CCC_POSTDIV_WIDTH, |
146 | CLK_DIVIDER_ONE_BASED, MPFS_CCC_POSTDIV01_CR), |
147 | CLK_CCC_OUT(CLK_CCC_PLL1_OUT1, MPFS_CCC_POSTDIV1_SHIFT, MPFS_CCC_POSTDIV_WIDTH, |
148 | CLK_DIVIDER_ONE_BASED, MPFS_CCC_POSTDIV01_CR), |
149 | CLK_CCC_OUT(CLK_CCC_PLL1_OUT2, MPFS_CCC_POSTDIV2_SHIFT, MPFS_CCC_POSTDIV_WIDTH, |
150 | CLK_DIVIDER_ONE_BASED, MPFS_CCC_POSTDIV23_CR), |
151 | CLK_CCC_OUT(CLK_CCC_PLL1_OUT3, MPFS_CCC_POSTDIV3_SHIFT, MPFS_CCC_POSTDIV_WIDTH, |
152 | CLK_DIVIDER_ONE_BASED, MPFS_CCC_POSTDIV23_CR), |
153 | }; |
154 | |
155 | static struct mpfs_ccc_out_hw_clock *mpfs_ccc_pllout_clks[] = { |
156 | mpfs_ccc_pll0out_clks, mpfs_ccc_pll1out_clks |
157 | }; |
158 | |
159 | static int mpfs_ccc_register_outputs(struct device *dev, struct mpfs_ccc_out_hw_clock *out_hws, |
160 | unsigned int num_clks, struct mpfs_ccc_data *data, |
161 | struct mpfs_ccc_pll_hw_clock *parent) |
162 | { |
163 | int ret; |
164 | |
165 | for (unsigned int i = 0; i < num_clks; i++) { |
166 | struct mpfs_ccc_out_hw_clock *out_hw = &out_hws[i]; |
167 | char *name = devm_kasprintf(dev, GFP_KERNEL, fmt: "%s_out%u" , parent->name, i); |
168 | |
169 | if (!name) |
170 | return -ENOMEM; |
171 | |
172 | out_hw->divider.hw.init = CLK_HW_INIT_HW(name, &parent->hw, &clk_divider_ops, 0); |
173 | out_hw->divider.reg = data->pll_base[i / MPFS_CCC_OUTPUTS_PER_PLL] + |
174 | out_hw->reg_offset; |
175 | |
176 | ret = devm_clk_hw_register(dev, hw: &out_hw->divider.hw); |
177 | if (ret) |
178 | return dev_err_probe(dev, err: ret, fmt: "failed to register clock id: %d\n" , |
179 | out_hw->id); |
180 | |
181 | data->hw_data.hws[out_hw->id] = &out_hw->divider.hw; |
182 | } |
183 | |
184 | return 0; |
185 | } |
186 | |
187 | #define CLK_HW_INIT_PARENTS_DATA_FIXED_SIZE(_name, _parents, _ops, _flags) \ |
188 | (&(struct clk_init_data) { \ |
189 | .flags = _flags, \ |
190 | .name = _name, \ |
191 | .parent_data = _parents, \ |
192 | .num_parents = MPFS_CCC_REFS_PER_PLL, \ |
193 | .ops = _ops, \ |
194 | }) |
195 | |
196 | static int mpfs_ccc_register_plls(struct device *dev, struct mpfs_ccc_pll_hw_clock *pll_hws, |
197 | unsigned int num_clks, struct mpfs_ccc_data *data) |
198 | { |
199 | int ret; |
200 | |
201 | for (unsigned int i = 0; i < num_clks; i++) { |
202 | struct mpfs_ccc_pll_hw_clock *pll_hw = &pll_hws[i]; |
203 | |
204 | pll_hw->name = devm_kasprintf(dev, GFP_KERNEL, fmt: "ccc%s_pll%u" , |
205 | strchrnul(dev->of_node->full_name, '@'), i); |
206 | if (!pll_hw->name) |
207 | return -ENOMEM; |
208 | |
209 | pll_hw->base = data->pll_base[i]; |
210 | pll_hw->hw.init = CLK_HW_INIT_PARENTS_DATA_FIXED_SIZE(pll_hw->name, |
211 | pll_hw->parents, |
212 | &mpfs_ccc_pll_ops, 0); |
213 | |
214 | ret = devm_clk_hw_register(dev, hw: &pll_hw->hw); |
215 | if (ret) |
216 | return dev_err_probe(dev, err: ret, fmt: "failed to register ccc id: %d\n" , |
217 | pll_hw->id); |
218 | |
219 | data->hw_data.hws[pll_hw->id] = &pll_hw->hw; |
220 | |
221 | ret = mpfs_ccc_register_outputs(dev, out_hws: mpfs_ccc_pllout_clks[i], |
222 | MPFS_CCC_OUTPUTS_PER_PLL, data, parent: pll_hw); |
223 | if (ret) |
224 | return ret; |
225 | } |
226 | |
227 | return 0; |
228 | } |
229 | |
230 | static int mpfs_ccc_probe(struct platform_device *pdev) |
231 | { |
232 | struct mpfs_ccc_data *clk_data; |
233 | void __iomem *pll_base[ARRAY_SIZE(mpfs_ccc_pll_clks)]; |
234 | unsigned int num_clks; |
235 | int ret; |
236 | |
237 | num_clks = ARRAY_SIZE(mpfs_ccc_pll_clks) + ARRAY_SIZE(mpfs_ccc_pll0out_clks) + |
238 | ARRAY_SIZE(mpfs_ccc_pll1out_clks); |
239 | |
240 | clk_data = devm_kzalloc(dev: &pdev->dev, struct_size(clk_data, hw_data.hws, num_clks), |
241 | GFP_KERNEL); |
242 | if (!clk_data) |
243 | return -ENOMEM; |
244 | |
245 | pll_base[0] = devm_platform_ioremap_resource(pdev, index: 0); |
246 | if (IS_ERR(ptr: pll_base[0])) |
247 | return PTR_ERR(ptr: pll_base[0]); |
248 | |
249 | pll_base[1] = devm_platform_ioremap_resource(pdev, index: 1); |
250 | if (IS_ERR(ptr: pll_base[1])) |
251 | return PTR_ERR(ptr: pll_base[1]); |
252 | |
253 | clk_data->pll_base = pll_base; |
254 | clk_data->hw_data.num = num_clks; |
255 | clk_data->dev = &pdev->dev; |
256 | |
257 | ret = mpfs_ccc_register_plls(dev: clk_data->dev, pll_hws: mpfs_ccc_pll_clks, |
258 | ARRAY_SIZE(mpfs_ccc_pll_clks), data: clk_data); |
259 | if (ret) |
260 | return ret; |
261 | |
262 | return devm_of_clk_add_hw_provider(dev: clk_data->dev, get: of_clk_hw_onecell_get, |
263 | data: &clk_data->hw_data); |
264 | } |
265 | |
266 | static const struct of_device_id mpfs_ccc_of_match_table[] = { |
267 | { .compatible = "microchip,mpfs-ccc" , }, |
268 | {} |
269 | }; |
270 | MODULE_DEVICE_TABLE(of, mpfs_ccc_of_match_table); |
271 | |
272 | static struct platform_driver mpfs_ccc_driver = { |
273 | .probe = mpfs_ccc_probe, |
274 | .driver = { |
275 | .name = "microchip-mpfs-ccc" , |
276 | .of_match_table = mpfs_ccc_of_match_table, |
277 | }, |
278 | }; |
279 | |
280 | static int __init clk_ccc_init(void) |
281 | { |
282 | return platform_driver_register(&mpfs_ccc_driver); |
283 | } |
284 | core_initcall(clk_ccc_init); |
285 | |
286 | static void __exit clk_ccc_exit(void) |
287 | { |
288 | platform_driver_unregister(&mpfs_ccc_driver); |
289 | } |
290 | module_exit(clk_ccc_exit); |
291 | |
292 | MODULE_DESCRIPTION("Microchip PolarFire SoC Clock Conditioning Circuitry Driver" ); |
293 | MODULE_AUTHOR("Conor Dooley <conor.dooley@microchip.com>" ); |
294 | |