cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

ccu-sun6i-rtc.c (9708B)


      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
     45struct 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
     53static bool have_iosc_calibration;
     54
     55static 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
     62static 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
     69static 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
     76static 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
     95static unsigned long ccu_iosc_recalc_accuracy(struct clk_hw *hw,
     96					      unsigned long parent_accuracy)
     97{
     98	return IOSC_ACCURACY;
     99}
    100
    101static 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
    109static 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
    115static 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
    130static 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
    143static 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
    162static 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
    179static 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
    186static 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
    192static const struct clk_hw *ext_osc32k[] = { NULL }; /* updated during probe */
    193
    194static SUNXI_CCU_GATE_HWS(ext_osc32k_gate_clk, "ext-osc32k-gate",
    195			  ext_osc32k, 0x0, BIT(4), 0);
    196
    197static const struct clk_hw *osc32k_parents[] = {
    198	&iosc_32k_clk.hw,
    199	&ext_osc32k_gate_clk.common.hw
    200};
    201
    202static 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
    209static 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. */
    219static const struct clk_parent_data osc24M[] = {
    220	{ .fw_name = "hosc", .name = "osc24M" }
    221};
    222
    223static 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
    234static const struct clk_hw *rtc_32k_parents[] = {
    235	&osc32k_clk.common.hw,
    236	&osc24M_32k_clk.common.hw
    237};
    238
    239static 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
    247static 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
    256static 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
    262static 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
    271static 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
    281static 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
    294static 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
    301static 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
    307static 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
    313static 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
    320static 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
    326static 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
    338int 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
    381MODULE_IMPORT_NS(SUNXI_CCU);
    382MODULE_LICENSE("GPL");