Commit | Line | Data |
---|---|---|
d3da3eae TP |
1 | /* |
2 | * Marvell Armada CP110 System Controller | |
3 | * | |
4 | * Copyright (C) 2016 Marvell | |
5 | * | |
6 | * Thomas Petazzoni <thomas.petazzoni@free-electrons.com> | |
7 | * | |
8 | * This file is licensed under the terms of the GNU General Public | |
9 | * License version 2. This program is licensed "as is" without any | |
10 | * warranty of any kind, whether express or implied. | |
11 | */ | |
12 | ||
13 | /* | |
14 | * CP110 has 5 core clocks: | |
15 | * | |
16 | * - APLL (1 Ghz) | |
17 | * - PPv2 core (1/3 APLL) | |
18 | * - EIP (1/2 APLL) | |
19 | * - Core (1/2 EIP) | |
20 | * | |
21 | * - NAND clock, which is either: | |
22 | * - Equal to the core clock | |
23 | * - 2/5 APLL | |
24 | * | |
25 | * CP110 has 32 gatable clocks, for the various peripherals in the | |
26 | * IP. They have fairly complicated parent/child relationships. | |
27 | */ | |
28 | ||
29 | #define pr_fmt(fmt) "cp110-system-controller: " fmt | |
30 | ||
31 | #include <linux/clk-provider.h> | |
32 | #include <linux/mfd/syscon.h> | |
33 | #include <linux/module.h> | |
34 | #include <linux/of.h> | |
35 | #include <linux/of_address.h> | |
36 | #include <linux/platform_device.h> | |
37 | #include <linux/regmap.h> | |
38 | #include <linux/slab.h> | |
39 | ||
40 | #define CP110_PM_CLOCK_GATING_REG 0x220 | |
41 | #define CP110_NAND_FLASH_CLK_CTRL_REG 0x700 | |
42 | #define NF_CLOCK_SEL_400_MASK BIT(0) | |
43 | ||
44 | enum { | |
45 | CP110_CLK_TYPE_CORE, | |
46 | CP110_CLK_TYPE_GATABLE, | |
47 | }; | |
48 | ||
49 | #define CP110_MAX_CORE_CLOCKS 5 | |
50 | #define CP110_MAX_GATABLE_CLOCKS 32 | |
51 | ||
52 | #define CP110_CLK_NUM \ | |
53 | (CP110_MAX_CORE_CLOCKS + CP110_MAX_GATABLE_CLOCKS) | |
54 | ||
55 | #define CP110_CORE_APLL 0 | |
56 | #define CP110_CORE_PPV2 1 | |
57 | #define CP110_CORE_EIP 2 | |
58 | #define CP110_CORE_CORE 3 | |
59 | #define CP110_CORE_NAND 4 | |
60 | ||
61 | /* A number of gatable clocks need special handling */ | |
62 | #define CP110_GATE_AUDIO 0 | |
63 | #define CP110_GATE_COMM_UNIT 1 | |
64 | #define CP110_GATE_NAND 2 | |
65 | #define CP110_GATE_PPV2 3 | |
66 | #define CP110_GATE_SDIO 4 | |
67 | #define CP110_GATE_XOR1 7 | |
68 | #define CP110_GATE_XOR0 8 | |
69 | #define CP110_GATE_PCIE_X1_0 11 | |
70 | #define CP110_GATE_PCIE_X1_1 12 | |
71 | #define CP110_GATE_PCIE_X4 13 | |
72 | #define CP110_GATE_PCIE_XOR 14 | |
73 | #define CP110_GATE_SATA 15 | |
74 | #define CP110_GATE_SATA_USB 16 | |
75 | #define CP110_GATE_MAIN 17 | |
76 | #define CP110_GATE_SDMMC 18 | |
77 | #define CP110_GATE_SLOW_IO 21 | |
78 | #define CP110_GATE_USB3H0 22 | |
79 | #define CP110_GATE_USB3H1 23 | |
80 | #define CP110_GATE_USB3DEV 24 | |
81 | #define CP110_GATE_EIP150 25 | |
82 | #define CP110_GATE_EIP197 26 | |
83 | ||
84 | static struct clk *cp110_clks[CP110_CLK_NUM]; | |
85 | ||
86 | static struct clk_onecell_data cp110_clk_data = { | |
87 | .clks = cp110_clks, | |
88 | .clk_num = CP110_CLK_NUM, | |
89 | }; | |
90 | ||
91 | struct cp110_gate_clk { | |
92 | struct clk_hw hw; | |
93 | struct regmap *regmap; | |
94 | u8 bit_idx; | |
95 | }; | |
96 | ||
97 | #define to_cp110_gate_clk(clk) container_of(clk, struct cp110_gate_clk, hw) | |
98 | ||
99 | static int cp110_gate_enable(struct clk_hw *hw) | |
100 | { | |
101 | struct cp110_gate_clk *gate = to_cp110_gate_clk(hw); | |
102 | ||
103 | regmap_update_bits(gate->regmap, CP110_PM_CLOCK_GATING_REG, | |
104 | BIT(gate->bit_idx), BIT(gate->bit_idx)); | |
105 | ||
106 | return 0; | |
107 | } | |
108 | ||
109 | static void cp110_gate_disable(struct clk_hw *hw) | |
110 | { | |
111 | struct cp110_gate_clk *gate = to_cp110_gate_clk(hw); | |
112 | ||
113 | regmap_update_bits(gate->regmap, CP110_PM_CLOCK_GATING_REG, | |
114 | BIT(gate->bit_idx), 0); | |
115 | } | |
116 | ||
117 | static int cp110_gate_is_enabled(struct clk_hw *hw) | |
118 | { | |
119 | struct cp110_gate_clk *gate = to_cp110_gate_clk(hw); | |
120 | u32 val; | |
121 | ||
122 | regmap_read(gate->regmap, CP110_PM_CLOCK_GATING_REG, &val); | |
123 | ||
124 | return val & BIT(gate->bit_idx); | |
125 | } | |
126 | ||
127 | static const struct clk_ops cp110_gate_ops = { | |
128 | .enable = cp110_gate_enable, | |
129 | .disable = cp110_gate_disable, | |
130 | .is_enabled = cp110_gate_is_enabled, | |
131 | }; | |
132 | ||
133 | static struct clk *cp110_register_gate(const char *name, | |
134 | const char *parent_name, | |
135 | struct regmap *regmap, u8 bit_idx) | |
136 | { | |
137 | struct cp110_gate_clk *gate; | |
138 | struct clk *clk; | |
139 | struct clk_init_data init; | |
140 | ||
141 | gate = kzalloc(sizeof(*gate), GFP_KERNEL); | |
142 | if (!gate) | |
143 | return ERR_PTR(-ENOMEM); | |
144 | ||
145 | init.name = name; | |
146 | init.ops = &cp110_gate_ops; | |
147 | init.parent_names = &parent_name; | |
148 | init.num_parents = 1; | |
149 | ||
150 | gate->regmap = regmap; | |
151 | gate->bit_idx = bit_idx; | |
152 | gate->hw.init = &init; | |
153 | ||
154 | clk = clk_register(NULL, &gate->hw); | |
155 | if (IS_ERR(clk)) | |
156 | kfree(gate); | |
157 | ||
158 | return clk; | |
159 | } | |
160 | ||
161 | static void cp110_unregister_gate(struct clk *clk) | |
162 | { | |
163 | struct clk_hw *hw; | |
164 | ||
165 | hw = __clk_get_hw(clk); | |
166 | if (!hw) | |
167 | return; | |
168 | ||
169 | clk_unregister(clk); | |
170 | kfree(to_cp110_gate_clk(hw)); | |
171 | } | |
172 | ||
173 | static struct clk *cp110_of_clk_get(struct of_phandle_args *clkspec, void *data) | |
174 | { | |
175 | struct clk_onecell_data *clk_data = data; | |
176 | unsigned int type = clkspec->args[0]; | |
177 | unsigned int idx = clkspec->args[1]; | |
178 | ||
179 | if (type == CP110_CLK_TYPE_CORE) { | |
180 | if (idx > CP110_MAX_CORE_CLOCKS) | |
181 | return ERR_PTR(-EINVAL); | |
182 | return clk_data->clks[idx]; | |
183 | } else if (type == CP110_CLK_TYPE_GATABLE) { | |
184 | if (idx > CP110_MAX_GATABLE_CLOCKS) | |
185 | return ERR_PTR(-EINVAL); | |
186 | return clk_data->clks[CP110_MAX_CORE_CLOCKS + idx]; | |
187 | } | |
188 | ||
189 | return ERR_PTR(-EINVAL); | |
190 | } | |
191 | ||
192 | static int cp110_syscon_clk_probe(struct platform_device *pdev) | |
193 | { | |
194 | struct regmap *regmap; | |
195 | struct device_node *np = pdev->dev.of_node; | |
196 | const char *ppv2_name, *apll_name, *core_name, *eip_name, *nand_name; | |
197 | struct clk *clk; | |
198 | u32 nand_clk_ctrl; | |
199 | int i, ret; | |
200 | ||
201 | regmap = syscon_node_to_regmap(np); | |
202 | if (IS_ERR(regmap)) | |
203 | return PTR_ERR(regmap); | |
204 | ||
205 | ret = regmap_read(regmap, CP110_NAND_FLASH_CLK_CTRL_REG, | |
206 | &nand_clk_ctrl); | |
207 | if (ret) | |
208 | return ret; | |
209 | ||
210 | /* Register the APLL which is the root of the clk tree */ | |
211 | of_property_read_string_index(np, "core-clock-output-names", | |
212 | CP110_CORE_APLL, &apll_name); | |
213 | clk = clk_register_fixed_rate(NULL, apll_name, NULL, 0, | |
214 | 1000 * 1000 * 1000); | |
215 | if (IS_ERR(clk)) { | |
216 | ret = PTR_ERR(clk); | |
217 | goto fail0; | |
218 | } | |
219 | ||
220 | cp110_clks[CP110_CORE_APLL] = clk; | |
221 | ||
222 | /* PPv2 is APLL/3 */ | |
223 | of_property_read_string_index(np, "core-clock-output-names", | |
224 | CP110_CORE_PPV2, &ppv2_name); | |
225 | clk = clk_register_fixed_factor(NULL, ppv2_name, apll_name, 0, 1, 3); | |
226 | if (IS_ERR(clk)) { | |
227 | ret = PTR_ERR(clk); | |
228 | goto fail1; | |
229 | } | |
230 | ||
231 | cp110_clks[CP110_CORE_PPV2] = clk; | |
232 | ||
233 | /* EIP clock is APLL/2 */ | |
234 | of_property_read_string_index(np, "core-clock-output-names", | |
235 | CP110_CORE_EIP, &eip_name); | |
236 | clk = clk_register_fixed_factor(NULL, eip_name, apll_name, 0, 1, 2); | |
237 | if (IS_ERR(clk)) { | |
238 | ret = PTR_ERR(clk); | |
239 | goto fail2; | |
240 | } | |
241 | ||
242 | cp110_clks[CP110_CORE_EIP] = clk; | |
243 | ||
244 | /* Core clock is EIP/2 */ | |
245 | of_property_read_string_index(np, "core-clock-output-names", | |
246 | CP110_CORE_CORE, &core_name); | |
247 | clk = clk_register_fixed_factor(NULL, core_name, eip_name, 0, 1, 2); | |
248 | if (IS_ERR(clk)) { | |
249 | ret = PTR_ERR(clk); | |
250 | goto fail3; | |
251 | } | |
252 | ||
253 | cp110_clks[CP110_CORE_CORE] = clk; | |
254 | ||
255 | /* NAND can be either APLL/2.5 or core clock */ | |
256 | of_property_read_string_index(np, "core-clock-output-names", | |
257 | CP110_CORE_NAND, &nand_name); | |
258 | if (nand_clk_ctrl & NF_CLOCK_SEL_400_MASK) | |
259 | clk = clk_register_fixed_factor(NULL, nand_name, | |
260 | apll_name, 0, 2, 5); | |
261 | else | |
262 | clk = clk_register_fixed_factor(NULL, nand_name, | |
263 | core_name, 0, 1, 1); | |
264 | if (IS_ERR(clk)) { | |
265 | ret = PTR_ERR(clk); | |
266 | goto fail4; | |
267 | } | |
268 | ||
269 | cp110_clks[CP110_CORE_NAND] = clk; | |
270 | ||
271 | for (i = 0; i < CP110_MAX_GATABLE_CLOCKS; i++) { | |
272 | const char *parent, *name; | |
273 | int ret; | |
274 | ||
275 | ret = of_property_read_string_index(np, | |
276 | "gate-clock-output-names", | |
277 | i, &name); | |
278 | /* Reached the end of the list? */ | |
279 | if (ret < 0) | |
280 | break; | |
281 | ||
282 | if (!strcmp(name, "none")) | |
283 | continue; | |
284 | ||
285 | switch (i) { | |
286 | case CP110_GATE_AUDIO: | |
287 | case CP110_GATE_COMM_UNIT: | |
288 | case CP110_GATE_EIP150: | |
289 | case CP110_GATE_EIP197: | |
290 | case CP110_GATE_SLOW_IO: | |
291 | of_property_read_string_index(np, | |
292 | "gate-clock-output-names", | |
293 | CP110_GATE_MAIN, &parent); | |
294 | break; | |
295 | case CP110_GATE_NAND: | |
296 | parent = nand_name; | |
297 | break; | |
298 | case CP110_GATE_PPV2: | |
299 | parent = ppv2_name; | |
300 | break; | |
301 | case CP110_GATE_SDIO: | |
302 | of_property_read_string_index(np, | |
303 | "gate-clock-output-names", | |
304 | CP110_GATE_SDMMC, &parent); | |
305 | break; | |
306 | case CP110_GATE_XOR1: | |
307 | case CP110_GATE_XOR0: | |
308 | case CP110_GATE_PCIE_X1_0: | |
309 | case CP110_GATE_PCIE_X1_1: | |
310 | case CP110_GATE_PCIE_X4: | |
311 | of_property_read_string_index(np, | |
312 | "gate-clock-output-names", | |
313 | CP110_GATE_PCIE_XOR, &parent); | |
314 | break; | |
315 | case CP110_GATE_SATA: | |
316 | case CP110_GATE_USB3H0: | |
317 | case CP110_GATE_USB3H1: | |
318 | case CP110_GATE_USB3DEV: | |
319 | of_property_read_string_index(np, | |
320 | "gate-clock-output-names", | |
321 | CP110_GATE_SATA_USB, &parent); | |
322 | break; | |
323 | default: | |
324 | parent = core_name; | |
325 | break; | |
326 | } | |
327 | ||
328 | clk = cp110_register_gate(name, parent, regmap, i); | |
329 | if (IS_ERR(clk)) { | |
330 | ret = PTR_ERR(clk); | |
331 | goto fail_gate; | |
332 | } | |
333 | ||
334 | cp110_clks[CP110_MAX_CORE_CLOCKS + i] = clk; | |
335 | } | |
336 | ||
337 | ret = of_clk_add_provider(np, cp110_of_clk_get, &cp110_clk_data); | |
338 | if (ret) | |
339 | goto fail_clk_add; | |
340 | ||
341 | return 0; | |
342 | ||
343 | fail_clk_add: | |
344 | fail_gate: | |
345 | for (i = 0; i < CP110_MAX_GATABLE_CLOCKS; i++) { | |
346 | clk = cp110_clks[CP110_MAX_CORE_CLOCKS + i]; | |
347 | ||
348 | if (clk) | |
349 | cp110_unregister_gate(clk); | |
350 | } | |
351 | ||
352 | clk_unregister_fixed_factor(cp110_clks[CP110_CORE_NAND]); | |
353 | fail4: | |
354 | clk_unregister_fixed_factor(cp110_clks[CP110_CORE_CORE]); | |
355 | fail3: | |
356 | clk_unregister_fixed_factor(cp110_clks[CP110_CORE_EIP]); | |
357 | fail2: | |
358 | clk_unregister_fixed_factor(cp110_clks[CP110_CORE_PPV2]); | |
359 | fail1: | |
360 | clk_unregister_fixed_rate(cp110_clks[CP110_CORE_APLL]); | |
361 | fail0: | |
362 | return ret; | |
363 | } | |
364 | ||
365 | static int cp110_syscon_clk_remove(struct platform_device *pdev) | |
366 | { | |
367 | int i; | |
368 | ||
369 | of_clk_del_provider(pdev->dev.of_node); | |
370 | ||
371 | for (i = 0; i < CP110_MAX_GATABLE_CLOCKS; i++) { | |
372 | struct clk *clk = cp110_clks[CP110_MAX_CORE_CLOCKS + i]; | |
373 | ||
374 | if (clk) | |
375 | cp110_unregister_gate(clk); | |
376 | } | |
377 | ||
378 | clk_unregister_fixed_factor(cp110_clks[CP110_CORE_NAND]); | |
379 | clk_unregister_fixed_factor(cp110_clks[CP110_CORE_CORE]); | |
380 | clk_unregister_fixed_factor(cp110_clks[CP110_CORE_EIP]); | |
381 | clk_unregister_fixed_factor(cp110_clks[CP110_CORE_PPV2]); | |
382 | clk_unregister_fixed_rate(cp110_clks[CP110_CORE_APLL]); | |
383 | ||
384 | return 0; | |
385 | } | |
386 | ||
387 | static const struct of_device_id cp110_syscon_of_match[] = { | |
388 | { .compatible = "marvell,cp110-system-controller0", }, | |
389 | { } | |
390 | }; | |
391 | MODULE_DEVICE_TABLE(of, armada8k_pcie_of_match); | |
392 | ||
393 | static struct platform_driver cp110_syscon_driver = { | |
394 | .probe = cp110_syscon_clk_probe, | |
395 | .remove = cp110_syscon_clk_remove, | |
396 | .driver = { | |
397 | .name = "marvell-cp110-system-controller0", | |
398 | .of_match_table = cp110_syscon_of_match, | |
399 | }, | |
400 | }; | |
401 | ||
402 | module_platform_driver(cp110_syscon_driver); | |
403 | ||
404 | MODULE_DESCRIPTION("Marvell CP110 System Controller 0 driver"); | |
405 | MODULE_AUTHOR("Thomas Petazzoni <thomas.petazzoni@free-electrons.com>"); | |
406 | MODULE_LICENSE("GPL"); |