1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | // |
3 | // Copyright (c) 2021 Samuel Holland <samuel@sholland.org> |
4 | // |
5 | |
6 | #include <linux/clk.h> |
7 | #include <linux/clk-provider.h> |
8 | #include <linux/device.h> |
9 | #include <linux/io.h> |
10 | #include <linux/module.h> |
11 | #include <linux/of.h> |
12 | #include <linux/of_device.h> |
13 | |
14 | #include <linux/clk/sunxi-ng.h> |
15 | |
16 | #include "ccu_common.h" |
17 | |
18 | #include "ccu_div.h" |
19 | #include "ccu_gate.h" |
20 | #include "ccu_mux.h" |
21 | |
22 | #include "ccu-sun6i-rtc.h" |
23 | |
24 | #define IOSC_ACCURACY 300000000 /* 30% */ |
25 | #define IOSC_RATE 16000000 |
26 | |
27 | #define LOSC_RATE 32768 |
28 | #define LOSC_RATE_SHIFT 15 |
29 | |
30 | #define LOSC_CTRL_REG 0x0 |
31 | #define LOSC_CTRL_KEY 0x16aa0000 |
32 | |
33 | #define IOSC_32K_CLK_DIV_REG 0x8 |
34 | #define IOSC_32K_CLK_DIV GENMASK(4, 0) |
35 | #define IOSC_32K_PRE_DIV 32 |
36 | |
37 | #define IOSC_CLK_CALI_REG 0xc |
38 | #define IOSC_CLK_CALI_DIV_ONES 22 |
39 | #define IOSC_CLK_CALI_EN BIT(1) |
40 | #define IOSC_CLK_CALI_SRC_SEL BIT(0) |
41 | |
42 | #define LOSC_OUT_GATING_REG 0x60 |
43 | |
44 | #define DCXO_CTRL_REG 0x160 |
45 | #define DCXO_CTRL_CLK16M_RC_EN BIT(0) |
46 | |
47 | struct sun6i_rtc_match_data { |
48 | bool have_ext_osc32k : 1; |
49 | bool have_iosc_calibration : 1; |
50 | bool rtc_32k_single_parent : 1; |
51 | const struct clk_parent_data *osc32k_fanout_parents; |
52 | u8 osc32k_fanout_nparents; |
53 | }; |
54 | |
55 | static bool have_iosc_calibration; |
56 | |
57 | static int ccu_iosc_enable(struct clk_hw *hw) |
58 | { |
59 | struct ccu_common *cm = hw_to_ccu_common(hw); |
60 | |
61 | return ccu_gate_helper_enable(common: cm, DCXO_CTRL_CLK16M_RC_EN); |
62 | } |
63 | |
64 | static void ccu_iosc_disable(struct clk_hw *hw) |
65 | { |
66 | struct ccu_common *cm = hw_to_ccu_common(hw); |
67 | |
68 | return ccu_gate_helper_disable(common: cm, DCXO_CTRL_CLK16M_RC_EN); |
69 | } |
70 | |
71 | static int ccu_iosc_is_enabled(struct clk_hw *hw) |
72 | { |
73 | struct ccu_common *cm = hw_to_ccu_common(hw); |
74 | |
75 | return ccu_gate_helper_is_enabled(common: cm, DCXO_CTRL_CLK16M_RC_EN); |
76 | } |
77 | |
78 | static unsigned long ccu_iosc_recalc_rate(struct clk_hw *hw, |
79 | unsigned long parent_rate) |
80 | { |
81 | struct ccu_common *cm = hw_to_ccu_common(hw); |
82 | |
83 | if (have_iosc_calibration) { |
84 | u32 reg = readl(addr: cm->base + IOSC_CLK_CALI_REG); |
85 | |
86 | /* |
87 | * Recover the IOSC frequency by shifting the ones place of |
88 | * (fixed-point divider * 32768) into bit zero. |
89 | */ |
90 | if (reg & IOSC_CLK_CALI_EN) |
91 | return reg >> (IOSC_CLK_CALI_DIV_ONES - LOSC_RATE_SHIFT); |
92 | } |
93 | |
94 | return IOSC_RATE; |
95 | } |
96 | |
97 | static unsigned long ccu_iosc_recalc_accuracy(struct clk_hw *hw, |
98 | unsigned long parent_accuracy) |
99 | { |
100 | return IOSC_ACCURACY; |
101 | } |
102 | |
103 | static const struct clk_ops ccu_iosc_ops = { |
104 | .enable = ccu_iosc_enable, |
105 | .disable = ccu_iosc_disable, |
106 | .is_enabled = ccu_iosc_is_enabled, |
107 | .recalc_rate = ccu_iosc_recalc_rate, |
108 | .recalc_accuracy = ccu_iosc_recalc_accuracy, |
109 | }; |
110 | |
111 | static struct ccu_common iosc_clk = { |
112 | .reg = DCXO_CTRL_REG, |
113 | .hw.init = CLK_HW_INIT_NO_PARENT("iosc" , &ccu_iosc_ops, |
114 | CLK_GET_RATE_NOCACHE), |
115 | }; |
116 | |
117 | static int ccu_iosc_32k_prepare(struct clk_hw *hw) |
118 | { |
119 | struct ccu_common *cm = hw_to_ccu_common(hw); |
120 | u32 val; |
121 | |
122 | if (!have_iosc_calibration) |
123 | return 0; |
124 | |
125 | val = readl(addr: cm->base + IOSC_CLK_CALI_REG); |
126 | writel(val: val | IOSC_CLK_CALI_EN | IOSC_CLK_CALI_SRC_SEL, |
127 | addr: cm->base + IOSC_CLK_CALI_REG); |
128 | |
129 | return 0; |
130 | } |
131 | |
132 | static void ccu_iosc_32k_unprepare(struct clk_hw *hw) |
133 | { |
134 | struct ccu_common *cm = hw_to_ccu_common(hw); |
135 | u32 val; |
136 | |
137 | if (!have_iosc_calibration) |
138 | return; |
139 | |
140 | val = readl(addr: cm->base + IOSC_CLK_CALI_REG); |
141 | writel(val: val & ~(IOSC_CLK_CALI_EN | IOSC_CLK_CALI_SRC_SEL), |
142 | addr: cm->base + IOSC_CLK_CALI_REG); |
143 | } |
144 | |
145 | static unsigned long ccu_iosc_32k_recalc_rate(struct clk_hw *hw, |
146 | unsigned long parent_rate) |
147 | { |
148 | struct ccu_common *cm = hw_to_ccu_common(hw); |
149 | u32 val; |
150 | |
151 | if (have_iosc_calibration) { |
152 | val = readl(addr: cm->base + IOSC_CLK_CALI_REG); |
153 | |
154 | /* Assume the calibrated 32k clock is accurate. */ |
155 | if (val & IOSC_CLK_CALI_SRC_SEL) |
156 | return LOSC_RATE; |
157 | } |
158 | |
159 | val = readl(addr: cm->base + IOSC_32K_CLK_DIV_REG) & IOSC_32K_CLK_DIV; |
160 | |
161 | return parent_rate / IOSC_32K_PRE_DIV / (val + 1); |
162 | } |
163 | |
164 | static unsigned long ccu_iosc_32k_recalc_accuracy(struct clk_hw *hw, |
165 | unsigned long parent_accuracy) |
166 | { |
167 | struct ccu_common *cm = hw_to_ccu_common(hw); |
168 | u32 val; |
169 | |
170 | if (have_iosc_calibration) { |
171 | val = readl(addr: cm->base + IOSC_CLK_CALI_REG); |
172 | |
173 | /* Assume the calibrated 32k clock is accurate. */ |
174 | if (val & IOSC_CLK_CALI_SRC_SEL) |
175 | return 0; |
176 | } |
177 | |
178 | return parent_accuracy; |
179 | } |
180 | |
181 | static const struct clk_ops ccu_iosc_32k_ops = { |
182 | .prepare = ccu_iosc_32k_prepare, |
183 | .unprepare = ccu_iosc_32k_unprepare, |
184 | .recalc_rate = ccu_iosc_32k_recalc_rate, |
185 | .recalc_accuracy = ccu_iosc_32k_recalc_accuracy, |
186 | }; |
187 | |
188 | static struct ccu_common iosc_32k_clk = { |
189 | .hw.init = CLK_HW_INIT_HW("iosc-32k" , &iosc_clk.hw, |
190 | &ccu_iosc_32k_ops, |
191 | CLK_GET_RATE_NOCACHE), |
192 | }; |
193 | |
194 | static const struct clk_hw *ext_osc32k[] = { NULL }; /* updated during probe */ |
195 | |
196 | static SUNXI_CCU_GATE_HWS(ext_osc32k_gate_clk, "ext-osc32k-gate" , |
197 | ext_osc32k, 0x0, BIT(4), 0); |
198 | |
199 | static const struct clk_hw *osc32k_parents[] = { |
200 | &iosc_32k_clk.hw, |
201 | &ext_osc32k_gate_clk.common.hw |
202 | }; |
203 | |
204 | static struct clk_init_data osc32k_init_data = { |
205 | .name = "osc32k" , |
206 | .ops = &ccu_mux_ops, |
207 | .parent_hws = osc32k_parents, |
208 | .num_parents = ARRAY_SIZE(osc32k_parents), /* updated during probe */ |
209 | }; |
210 | |
211 | static struct ccu_mux osc32k_clk = { |
212 | .mux = _SUNXI_CCU_MUX(0, 1), |
213 | .common = { |
214 | .reg = LOSC_CTRL_REG, |
215 | .features = CCU_FEATURE_KEY_FIELD, |
216 | .hw.init = &osc32k_init_data, |
217 | }, |
218 | }; |
219 | |
220 | /* This falls back to the global name for fwnodes without a named reference. */ |
221 | static const struct clk_parent_data osc24M[] = { |
222 | { .fw_name = "hosc" , .name = "osc24M" } |
223 | }; |
224 | |
225 | static struct ccu_gate osc24M_32k_clk = { |
226 | .enable = BIT(16), |
227 | .common = { |
228 | .reg = LOSC_OUT_GATING_REG, |
229 | .prediv = 750, |
230 | .features = CCU_FEATURE_ALL_PREDIV, |
231 | .hw.init = CLK_HW_INIT_PARENTS_DATA("osc24M-32k" , osc24M, |
232 | &ccu_gate_ops, 0), |
233 | }, |
234 | }; |
235 | |
236 | static const struct clk_hw *rtc_32k_parents[] = { |
237 | &osc32k_clk.common.hw, |
238 | &osc24M_32k_clk.common.hw |
239 | }; |
240 | |
241 | static struct clk_init_data rtc_32k_init_data = { |
242 | .name = "rtc-32k" , |
243 | .ops = &ccu_mux_ops, |
244 | .parent_hws = rtc_32k_parents, |
245 | .num_parents = ARRAY_SIZE(rtc_32k_parents), /* updated during probe */ |
246 | .flags = CLK_IS_CRITICAL, |
247 | }; |
248 | |
249 | static struct ccu_mux rtc_32k_clk = { |
250 | .mux = _SUNXI_CCU_MUX(1, 1), |
251 | .common = { |
252 | .reg = LOSC_CTRL_REG, |
253 | .features = CCU_FEATURE_KEY_FIELD, |
254 | .hw.init = &rtc_32k_init_data, |
255 | }, |
256 | }; |
257 | |
258 | static struct clk_init_data osc32k_fanout_init_data = { |
259 | .name = "osc32k-fanout" , |
260 | .ops = &ccu_mux_ops, |
261 | /* parents are set during probe */ |
262 | }; |
263 | |
264 | static struct ccu_mux osc32k_fanout_clk = { |
265 | .enable = BIT(0), |
266 | .mux = _SUNXI_CCU_MUX(1, 2), |
267 | .common = { |
268 | .reg = LOSC_OUT_GATING_REG, |
269 | .hw.init = &osc32k_fanout_init_data, |
270 | }, |
271 | }; |
272 | |
273 | static struct ccu_common *sun6i_rtc_ccu_clks[] = { |
274 | &iosc_clk, |
275 | &iosc_32k_clk, |
276 | &ext_osc32k_gate_clk.common, |
277 | &osc32k_clk.common, |
278 | &osc24M_32k_clk.common, |
279 | &rtc_32k_clk.common, |
280 | &osc32k_fanout_clk.common, |
281 | }; |
282 | |
283 | static struct clk_hw_onecell_data sun6i_rtc_ccu_hw_clks = { |
284 | .num = CLK_NUMBER, |
285 | .hws = { |
286 | [CLK_OSC32K] = &osc32k_clk.common.hw, |
287 | [CLK_OSC32K_FANOUT] = &osc32k_fanout_clk.common.hw, |
288 | [CLK_IOSC] = &iosc_clk.hw, |
289 | [CLK_IOSC_32K] = &iosc_32k_clk.hw, |
290 | [CLK_EXT_OSC32K_GATE] = &ext_osc32k_gate_clk.common.hw, |
291 | [CLK_OSC24M_32K] = &osc24M_32k_clk.common.hw, |
292 | [CLK_RTC_32K] = &rtc_32k_clk.common.hw, |
293 | }, |
294 | }; |
295 | |
296 | static const struct sunxi_ccu_desc sun6i_rtc_ccu_desc = { |
297 | .ccu_clks = sun6i_rtc_ccu_clks, |
298 | .num_ccu_clks = ARRAY_SIZE(sun6i_rtc_ccu_clks), |
299 | |
300 | .hw_clks = &sun6i_rtc_ccu_hw_clks, |
301 | }; |
302 | |
303 | static const struct clk_parent_data sun50i_h616_osc32k_fanout_parents[] = { |
304 | { .hw = &osc32k_clk.common.hw }, |
305 | { .fw_name = "pll-32k" }, |
306 | { .hw = &osc24M_32k_clk.common.hw } |
307 | }; |
308 | |
309 | static const struct clk_parent_data sun50i_r329_osc32k_fanout_parents[] = { |
310 | { .hw = &osc32k_clk.common.hw }, |
311 | { .hw = &ext_osc32k_gate_clk.common.hw }, |
312 | { .hw = &osc24M_32k_clk.common.hw } |
313 | }; |
314 | |
315 | static const struct sun6i_rtc_match_data sun50i_h616_rtc_ccu_data = { |
316 | .have_iosc_calibration = true, |
317 | .rtc_32k_single_parent = true, |
318 | .osc32k_fanout_parents = sun50i_h616_osc32k_fanout_parents, |
319 | .osc32k_fanout_nparents = ARRAY_SIZE(sun50i_h616_osc32k_fanout_parents), |
320 | }; |
321 | |
322 | static const struct sun6i_rtc_match_data sun50i_r329_rtc_ccu_data = { |
323 | .have_ext_osc32k = true, |
324 | .osc32k_fanout_parents = sun50i_r329_osc32k_fanout_parents, |
325 | .osc32k_fanout_nparents = ARRAY_SIZE(sun50i_r329_osc32k_fanout_parents), |
326 | }; |
327 | |
328 | static const struct of_device_id sun6i_rtc_ccu_match[] = { |
329 | { |
330 | .compatible = "allwinner,sun50i-h616-rtc" , |
331 | .data = &sun50i_h616_rtc_ccu_data, |
332 | }, |
333 | { |
334 | .compatible = "allwinner,sun50i-r329-rtc" , |
335 | .data = &sun50i_r329_rtc_ccu_data, |
336 | }, |
337 | {}, |
338 | }; |
339 | |
340 | int sun6i_rtc_ccu_probe(struct device *dev, void __iomem *reg) |
341 | { |
342 | const struct sun6i_rtc_match_data *data; |
343 | struct clk *ext_osc32k_clk = NULL; |
344 | const struct of_device_id *match; |
345 | |
346 | /* This driver is only used for newer variants of the hardware. */ |
347 | match = of_match_device(matches: sun6i_rtc_ccu_match, dev); |
348 | if (!match) |
349 | return 0; |
350 | |
351 | data = match->data; |
352 | have_iosc_calibration = data->have_iosc_calibration; |
353 | |
354 | if (data->have_ext_osc32k) { |
355 | const char *fw_name; |
356 | |
357 | /* ext-osc32k was the only input clock in the old binding. */ |
358 | fw_name = of_property_read_bool(np: dev->of_node, propname: "clock-names" ) |
359 | ? "ext-osc32k" : NULL; |
360 | ext_osc32k_clk = devm_clk_get_optional(dev, id: fw_name); |
361 | if (IS_ERR(ptr: ext_osc32k_clk)) |
362 | return PTR_ERR(ptr: ext_osc32k_clk); |
363 | } |
364 | |
365 | if (ext_osc32k_clk) { |
366 | /* Link ext-osc32k-gate to its parent. */ |
367 | *ext_osc32k = __clk_get_hw(clk: ext_osc32k_clk); |
368 | } else { |
369 | /* ext-osc32k-gate is an orphan, so do not register it. */ |
370 | sun6i_rtc_ccu_hw_clks.hws[CLK_EXT_OSC32K_GATE] = NULL; |
371 | osc32k_init_data.num_parents = 1; |
372 | } |
373 | |
374 | if (data->rtc_32k_single_parent) |
375 | rtc_32k_init_data.num_parents = 1; |
376 | |
377 | osc32k_fanout_init_data.parent_data = data->osc32k_fanout_parents; |
378 | osc32k_fanout_init_data.num_parents = data->osc32k_fanout_nparents; |
379 | |
380 | return devm_sunxi_ccu_probe(dev, reg, desc: &sun6i_rtc_ccu_desc); |
381 | } |
382 | |
383 | MODULE_IMPORT_NS(SUNXI_CCU); |
384 | MODULE_LICENSE("GPL" ); |
385 | |