1 // SPDX-License-Identifier: GPL-2.0-only
3 // Copyright (c) 2021 Samuel Holland <samuel@sholland.org>
7 #include <linux/clk-provider.h>
8 #include <linux/device.h>
10 #include <linux/module.h>
12 #include <linux/of_device.h>
14 #include <linux/clk/sunxi-ng.h>
16 #include "ccu_common.h"
22 #include "ccu-sun6i-rtc.h"
24 #define IOSC_ACCURACY 300000000 /* 30% */
25 #define IOSC_RATE 16000000
27 #define LOSC_RATE 32768
28 #define LOSC_RATE_SHIFT 15
30 #define LOSC_CTRL_REG 0x0
31 #define LOSC_CTRL_KEY 0x16aa0000
33 #define IOSC_32K_CLK_DIV_REG 0x8
34 #define IOSC_32K_CLK_DIV GENMASK(4, 0)
35 #define IOSC_32K_PRE_DIV 32
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)
42 #define LOSC_OUT_GATING_REG 0x60
44 #define DCXO_CTRL_REG 0x160
45 #define DCXO_CTRL_CLK16M_RC_EN BIT(0)
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
;
55 static bool have_iosc_calibration
;
57 static int ccu_iosc_enable(struct clk_hw
*hw
)
59 struct ccu_common
*cm
= hw_to_ccu_common(hw
);
61 return ccu_gate_helper_enable(cm
, DCXO_CTRL_CLK16M_RC_EN
);
64 static void ccu_iosc_disable(struct clk_hw
*hw
)
66 struct ccu_common
*cm
= hw_to_ccu_common(hw
);
68 return ccu_gate_helper_disable(cm
, DCXO_CTRL_CLK16M_RC_EN
);
71 static int ccu_iosc_is_enabled(struct clk_hw
*hw
)
73 struct ccu_common
*cm
= hw_to_ccu_common(hw
);
75 return ccu_gate_helper_is_enabled(cm
, DCXO_CTRL_CLK16M_RC_EN
);
78 static unsigned long ccu_iosc_recalc_rate(struct clk_hw
*hw
,
79 unsigned long parent_rate
)
81 struct ccu_common
*cm
= hw_to_ccu_common(hw
);
83 if (have_iosc_calibration
) {
84 u32 reg
= readl(cm
->base
+ IOSC_CLK_CALI_REG
);
87 * Recover the IOSC frequency by shifting the ones place of
88 * (fixed-point divider * 32768) into bit zero.
90 if (reg
& IOSC_CLK_CALI_EN
)
91 return reg
>> (IOSC_CLK_CALI_DIV_ONES
- LOSC_RATE_SHIFT
);
97 static unsigned long ccu_iosc_recalc_accuracy(struct clk_hw
*hw
,
98 unsigned long parent_accuracy
)
100 return IOSC_ACCURACY
;
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
,
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
),
117 static int ccu_iosc_32k_prepare(struct clk_hw
*hw
)
119 struct ccu_common
*cm
= hw_to_ccu_common(hw
);
122 if (!have_iosc_calibration
)
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
);
132 static void ccu_iosc_32k_unprepare(struct clk_hw
*hw
)
134 struct ccu_common
*cm
= hw_to_ccu_common(hw
);
137 if (!have_iosc_calibration
)
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
);
145 static unsigned long ccu_iosc_32k_recalc_rate(struct clk_hw
*hw
,
146 unsigned long parent_rate
)
148 struct ccu_common
*cm
= hw_to_ccu_common(hw
);
151 if (have_iosc_calibration
) {
152 val
= readl(cm
->base
+ IOSC_CLK_CALI_REG
);
154 /* Assume the calibrated 32k clock is accurate. */
155 if (val
& IOSC_CLK_CALI_SRC_SEL
)
159 val
= readl(cm
->base
+ IOSC_32K_CLK_DIV_REG
) & IOSC_32K_CLK_DIV
;
161 return parent_rate
/ IOSC_32K_PRE_DIV
/ (val
+ 1);
164 static unsigned long ccu_iosc_32k_recalc_accuracy(struct clk_hw
*hw
,
165 unsigned long parent_accuracy
)
167 struct ccu_common
*cm
= hw_to_ccu_common(hw
);
170 if (have_iosc_calibration
) {
171 val
= readl(cm
->base
+ IOSC_CLK_CALI_REG
);
173 /* Assume the calibrated 32k clock is accurate. */
174 if (val
& IOSC_CLK_CALI_SRC_SEL
)
178 return parent_accuracy
;
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
,
188 static struct ccu_common iosc_32k_clk
= {
189 .hw
.init
= CLK_HW_INIT_HW("iosc-32k", &iosc_clk
.hw
,
191 CLK_GET_RATE_NOCACHE
),
194 static const struct clk_hw
*ext_osc32k
[] = { NULL
}; /* updated during probe */
196 static SUNXI_CCU_GATE_HWS(ext_osc32k_gate_clk
, "ext-osc32k-gate",
197 ext_osc32k
, 0x0, BIT(4), 0);
199 static const struct clk_hw
*osc32k_parents
[] = {
201 &ext_osc32k_gate_clk
.common
.hw
204 static struct clk_init_data osc32k_init_data
= {
207 .parent_hws
= osc32k_parents
,
208 .num_parents
= ARRAY_SIZE(osc32k_parents
), /* updated during probe */
211 static struct ccu_mux osc32k_clk
= {
212 .mux
= _SUNXI_CCU_MUX(0, 1),
214 .reg
= LOSC_CTRL_REG
,
215 .features
= CCU_FEATURE_KEY_FIELD
,
216 .hw
.init
= &osc32k_init_data
,
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" }
225 static struct ccu_gate osc24M_32k_clk
= {
228 .reg
= LOSC_OUT_GATING_REG
,
230 .features
= CCU_FEATURE_ALL_PREDIV
,
231 .hw
.init
= CLK_HW_INIT_PARENTS_DATA("osc24M-32k", osc24M
,
236 static const struct clk_hw
*rtc_32k_parents
[] = {
237 &osc32k_clk
.common
.hw
,
238 &osc24M_32k_clk
.common
.hw
241 static struct clk_init_data rtc_32k_init_data
= {
244 .parent_hws
= rtc_32k_parents
,
245 .num_parents
= ARRAY_SIZE(rtc_32k_parents
), /* updated during probe */
246 .flags
= CLK_IS_CRITICAL
,
249 static struct ccu_mux rtc_32k_clk
= {
250 .mux
= _SUNXI_CCU_MUX(1, 1),
252 .reg
= LOSC_CTRL_REG
,
253 .features
= CCU_FEATURE_KEY_FIELD
,
254 .hw
.init
= &rtc_32k_init_data
,
258 static struct clk_init_data osc32k_fanout_init_data
= {
259 .name
= "osc32k-fanout",
261 /* parents are set during probe */
264 static struct ccu_mux osc32k_fanout_clk
= {
266 .mux
= _SUNXI_CCU_MUX(1, 2),
268 .reg
= LOSC_OUT_GATING_REG
,
269 .hw
.init
= &osc32k_fanout_init_data
,
273 static struct ccu_common
*sun6i_rtc_ccu_clks
[] = {
276 &ext_osc32k_gate_clk
.common
,
278 &osc24M_32k_clk
.common
,
280 &osc32k_fanout_clk
.common
,
283 static struct clk_hw_onecell_data sun6i_rtc_ccu_hw_clks
= {
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
,
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
),
300 .hw_clks
= &sun6i_rtc_ccu_hw_clks
,
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
}
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
}
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
),
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
),
328 static const struct of_device_id sun6i_rtc_ccu_match
[] = {
330 .compatible
= "allwinner,sun50i-h616-rtc",
331 .data
= &sun50i_h616_rtc_ccu_data
,
334 .compatible
= "allwinner,sun50i-r329-rtc",
335 .data
= &sun50i_r329_rtc_ccu_data
,
339 MODULE_DEVICE_TABLE(of
, sun6i_rtc_ccu_match
);
341 int sun6i_rtc_ccu_probe(struct device
*dev
, void __iomem
*reg
)
343 const struct sun6i_rtc_match_data
*data
;
344 struct clk
*ext_osc32k_clk
= NULL
;
345 const struct of_device_id
*match
;
347 /* This driver is only used for newer variants of the hardware. */
348 match
= of_match_device(sun6i_rtc_ccu_match
, dev
);
353 have_iosc_calibration
= data
->have_iosc_calibration
;
355 if (data
->have_ext_osc32k
) {
358 /* ext-osc32k was the only input clock in the old binding. */
359 fw_name
= of_property_present(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
);
366 if (ext_osc32k_clk
) {
367 /* Link ext-osc32k-gate to its parent. */
368 *ext_osc32k
= __clk_get_hw(ext_osc32k_clk
);
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;
375 if (data
->rtc_32k_single_parent
)
376 rtc_32k_init_data
.num_parents
= 1;
378 osc32k_fanout_init_data
.parent_data
= data
->osc32k_fanout_parents
;
379 osc32k_fanout_init_data
.num_parents
= data
->osc32k_fanout_nparents
;
381 return devm_sunxi_ccu_probe(dev
, reg
, &sun6i_rtc_ccu_desc
);
384 MODULE_IMPORT_NS("SUNXI_CCU");
385 MODULE_DESCRIPTION("Support for the Allwinner H616/R329 RTC CCU");
386 MODULE_LICENSE("GPL");