brintos

brintos / linux-shallow public Read only

0
0
Text · 9.6 KiB · 87e23d1 Raw
387 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2//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			1600000026 27#define LOSC_RATE			3276828#define LOSC_RATE_SHIFT			1529 30#define LOSC_CTRL_REG			0x031#define LOSC_CTRL_KEY			0x16aa000032 33#define IOSC_32K_CLK_DIV_REG		0x834#define IOSC_32K_CLK_DIV		GENMASK(4, 0)35#define IOSC_32K_PRE_DIV		3236 37#define IOSC_CLK_CALI_REG		0xc38#define IOSC_CLK_CALI_DIV_ONES		2239#define IOSC_CLK_CALI_EN		BIT(1)40#define IOSC_CLK_CALI_SRC_SEL		BIT(0)41 42#define LOSC_OUT_GATING_REG		0x6043 44#define DCXO_CTRL_REG			0x16045#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 of88		 * (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.hw202};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.hw239};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 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};339MODULE_DEVICE_TABLE(of, sun6i_rtc_ccu_match);340 341int 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 384MODULE_IMPORT_NS(SUNXI_CCU);385MODULE_DESCRIPTION("Support for the Allwinner H616/R329 RTC CCU");386MODULE_LICENSE("GPL");387