xref: /linux/drivers/clk/sunxi-ng/ccu-sun6i-rtc.c (revision a3a02a52bcfcbcc4a637d4b68bf1bc391c9fad02)
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(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(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(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(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(cm->base + IOSC_CLK_CALI_REG);
126 	writel(val | IOSC_CLK_CALI_EN | IOSC_CLK_CALI_SRC_SEL,
127 	       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(cm->base + IOSC_CLK_CALI_REG);
141 	writel(val & ~(IOSC_CLK_CALI_EN | IOSC_CLK_CALI_SRC_SEL),
142 	       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(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(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(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 MODULE_DEVICE_TABLE(of, sun6i_rtc_ccu_match);
340 
341 int sun6i_rtc_ccu_probe(struct device *dev, void __iomem *reg)
342 {
343 	const struct sun6i_rtc_match_data *data;
344 	struct clk *ext_osc32k_clk = NULL;
345 	const struct of_device_id *match;
346 
347 	/* This driver is only used for newer variants of the hardware. */
348 	match = of_match_device(sun6i_rtc_ccu_match, dev);
349 	if (!match)
350 		return 0;
351 
352 	data = match->data;
353 	have_iosc_calibration = data->have_iosc_calibration;
354 
355 	if (data->have_ext_osc32k) {
356 		const char *fw_name;
357 
358 		/* ext-osc32k was the only input clock in the old binding. */
359 		fw_name = of_property_read_bool(dev->of_node, "clock-names")
360 			? "ext-osc32k" : NULL;
361 		ext_osc32k_clk = devm_clk_get_optional(dev, fw_name);
362 		if (IS_ERR(ext_osc32k_clk))
363 			return PTR_ERR(ext_osc32k_clk);
364 	}
365 
366 	if (ext_osc32k_clk) {
367 		/* Link ext-osc32k-gate to its parent. */
368 		*ext_osc32k = __clk_get_hw(ext_osc32k_clk);
369 	} else {
370 		/* ext-osc32k-gate is an orphan, so do not register it. */
371 		sun6i_rtc_ccu_hw_clks.hws[CLK_EXT_OSC32K_GATE] = NULL;
372 		osc32k_init_data.num_parents = 1;
373 	}
374 
375 	if (data->rtc_32k_single_parent)
376 		rtc_32k_init_data.num_parents = 1;
377 
378 	osc32k_fanout_init_data.parent_data = data->osc32k_fanout_parents;
379 	osc32k_fanout_init_data.num_parents = data->osc32k_fanout_nparents;
380 
381 	return devm_sunxi_ccu_probe(dev, reg, &sun6i_rtc_ccu_desc);
382 }
383 
384 MODULE_IMPORT_NS(SUNXI_CCU);
385 MODULE_DESCRIPTION("Support for the Allwinner H616/R329 RTC CCU");
386 MODULE_LICENSE("GPL");
387