File Index
Files and directories
Sign in
FILE

ccu-sun6i-rtc.c

File preview with syntax highlighting.

linux-6.19.14/drivers/clk/sunxi-ng/ccu-sun6i-rtc.c C
  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
 47struct 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
 55static bool have_iosc_calibration;
 56
 57static 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
 64static 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
 71static 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
 78static 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
 97static unsigned long ccu_iosc_recalc_accuracy(struct clk_hw *hw,
 98					      unsigned long parent_accuracy)
 99{
100	return IOSC_ACCURACY;
101}
102
103static 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
111static 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
117static 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
132static 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
145static 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
164static 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
181static 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
188static 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
194static const struct clk_hw *ext_osc32k[] = { NULL }; /* updated during probe */
195
196static SUNXI_CCU_GATE_HWS(ext_osc32k_gate_clk, "ext-osc32k-gate",
197			  ext_osc32k, 0x0, BIT(4), 0);
198
199static const struct clk_hw *osc32k_parents[] = {
200	&iosc_32k_clk.hw,
201	&ext_osc32k_gate_clk.common.hw
202};
203
204static 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
211static 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. */
221static const struct clk_parent_data osc24M[] = {
222	{ .fw_name = "hosc", .name = "osc24M" }
223};
224
225static 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
236static const struct clk_hw *rtc_32k_parents[] = {
237	&osc32k_clk.common.hw,
238	&osc24M_32k_clk.common.hw
239};
240
241static 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
249static 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
258static 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
264static 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
273static 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
283static 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
296static 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
303static 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
309static 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
315static 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
322static 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
328static const struct sun6i_rtc_match_data sun55i_a523_rtc_ccu_data = {
329	.have_ext_osc32k	= true,
330	.have_iosc_calibration	= true,
331	.osc32k_fanout_parents	= sun50i_r329_osc32k_fanout_parents,
332	.osc32k_fanout_nparents	= ARRAY_SIZE(sun50i_r329_osc32k_fanout_parents),
333};
334
335static const struct of_device_id sun6i_rtc_ccu_match[] = {
336	{
337		.compatible	= "allwinner,sun50i-h616-rtc",
338		.data		= &sun50i_h616_rtc_ccu_data,
339	},
340	{
341		.compatible	= "allwinner,sun50i-r329-rtc",
342		.data		= &sun50i_r329_rtc_ccu_data,
343	},
344	{
345		.compatible	= "allwinner,sun55i-a523-rtc",
346		.data		= &sun55i_a523_rtc_ccu_data,
347	},
348	{},
349};
350MODULE_DEVICE_TABLE(of, sun6i_rtc_ccu_match);
351
352int sun6i_rtc_ccu_probe(struct device *dev, void __iomem *reg)
353{
354	const struct sun6i_rtc_match_data *data;
355	struct clk *ext_osc32k_clk = NULL;
356	const struct of_device_id *match;
357
358	/* This driver is only used for newer variants of the hardware. */
359	match = of_match_device(sun6i_rtc_ccu_match, dev);
360	if (!match)
361		return 0;
362
363	data = match->data;
364	have_iosc_calibration = data->have_iosc_calibration;
365
366	if (data->have_ext_osc32k) {
367		const char *fw_name;
368
369		/* ext-osc32k was the only input clock in the old binding. */
370		fw_name = of_property_present(dev->of_node, "clock-names")
371			? "ext-osc32k" : NULL;
372		ext_osc32k_clk = devm_clk_get_optional(dev, fw_name);
373		if (IS_ERR(ext_osc32k_clk))
374			return PTR_ERR(ext_osc32k_clk);
375	}
376
377	if (ext_osc32k_clk) {
378		/* Link ext-osc32k-gate to its parent. */
379		*ext_osc32k = __clk_get_hw(ext_osc32k_clk);
380	} else {
381		/* ext-osc32k-gate is an orphan, so do not register it. */
382		sun6i_rtc_ccu_hw_clks.hws[CLK_EXT_OSC32K_GATE] = NULL;
383		osc32k_init_data.num_parents = 1;
384	}
385
386	if (data->rtc_32k_single_parent)
387		rtc_32k_init_data.num_parents = 1;
388
389	osc32k_fanout_init_data.parent_data = data->osc32k_fanout_parents;
390	osc32k_fanout_init_data.num_parents = data->osc32k_fanout_nparents;
391
392	return devm_sunxi_ccu_probe(dev, reg, &sun6i_rtc_ccu_desc);
393}
394
395MODULE_IMPORT_NS("SUNXI_CCU");
396MODULE_DESCRIPTION("Support for the Allwinner H616/R329 RTC CCU");
397MODULE_LICENSE("GPL");
~/ Read-only file preview.