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