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