* [PATCH] clk: Add Sunplus SP7021 clock driver
@ 2022-08-17 7:01 Qin Jian
2022-08-17 20:23 ` kernel test robot
2022-08-22 8:40 ` Dan Carpenter
0 siblings, 2 replies; 4+ messages in thread
From: Qin Jian @ 2022-08-17 7:01 UTC (permalink / raw)
To: sboyd; +Cc: mturquette, linux-kernel, linux-clk, linux-arm-kernel, Qin Jian
Add clock driver for Sunplus SP7021 SoC.
Signed-off-by: Qin Jian <qinjian@cqplus1.com>
---
MAINTAINERS | 1 +
drivers/clk/Kconfig | 10 +
drivers/clk/Makefile | 1 +
drivers/clk/clk-sp7021.c | 713 +++++++++++++++++++++++++++++++++++
include/linux/clk-provider.h | 18 +
5 files changed, 743 insertions(+)
create mode 100644 drivers/clk/clk-sp7021.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 8a5012ba6..d7c875ac6 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2883,6 +2883,7 @@ F: Documentation/devicetree/bindings/reset/sunplus,reset.yaml
F: arch/arm/boot/dts/sunplus-sp7021*.dts*
F: arch/arm/configs/sp7021_*defconfig
F: arch/arm/mach-sunplus/
+F: drivers/clk/clk-sp7021.c
F: drivers/irqchip/irq-sp7021-intc.c
F: drivers/reset/reset-sunplus.c
F: include/dt-bindings/clock/sunplus,sp7021-clkc.h
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index 48f8f4221..134919446 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -428,6 +428,16 @@ config COMMON_CLK_K210
help
Support for the Canaan Kendryte K210 RISC-V SoC clocks.
+config COMMON_CLK_SP7021
+ tristate "Clock driver for Sunplus SP7021 SoC"
+ depends on SOC_SP7021 || COMPILE_TEST
+ default SOC_SP7021
+ help
+ This driver supports the Sunplus SP7021 SoC clocks.
+ It implements SP7021 PLLs/gate.
+ Not all features of the PLL are currently supported
+ by the driver.
+
source "drivers/clk/actions/Kconfig"
source "drivers/clk/analogbits/Kconfig"
source "drivers/clk/baikal-t1/Kconfig"
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index d5db170d3..2979675a5 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -65,6 +65,7 @@ obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o
obj-$(CONFIG_COMMON_CLK_SI514) += clk-si514.o
obj-$(CONFIG_COMMON_CLK_SI544) += clk-si544.o
obj-$(CONFIG_COMMON_CLK_SI570) += clk-si570.o
+obj-$(CONFIG_COMMON_CLK_SP7021) += clk-sp7021.o
obj-$(CONFIG_COMMON_CLK_STM32F) += clk-stm32f4.o
obj-$(CONFIG_COMMON_CLK_STM32H7) += clk-stm32h7.o
obj-$(CONFIG_COMMON_CLK_STM32MP157) += clk-stm32mp1.o
diff --git a/drivers/clk/clk-sp7021.c b/drivers/clk/clk-sp7021.c
new file mode 100644
index 000000000..c11f32a97
--- /dev/null
+++ b/drivers/clk/clk-sp7021.c
@@ -0,0 +1,713 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+/*
+ * Copyright (C) Sunplus Technology Co., Ltd.
+ * All rights reserved.
+ */
+#include <linux/module.h>
+#include <linux/clk-provider.h>
+#include <linux/of.h>
+#include <linux/bitfield.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <dt-bindings/clock/sunplus,sp7021-clkc.h>
+
+/* speical div_width values for PLLTV/PLLA */
+#define DIV_TV 33
+#define DIV_A 34
+
+/* PLLTV parameters */
+enum {
+ SEL_FRA,
+ SDM_MOD,
+ PH_SEL,
+ NFRA,
+ DIVR,
+ DIVN,
+ DIVM,
+ P_MAX
+};
+
+#define MASK_SEL_FRA GENMASK(1, 1)
+#define MASK_SDM_MOD GENMASK(2, 2)
+#define MASK_PH_SEL GENMASK(4, 4)
+#define MASK_NFRA GENMASK(12, 6)
+#define MASK_DIVR GENMASK(8, 7)
+#define MASK_DIVN GENMASK(7, 0)
+#define MASK_DIVM GENMASK(14, 8)
+
+/* HIWORD_MASK FIELD_PREP */
+#define HWM_FIELD_PREP(mask, value) \
+({ \
+ u32 _m = mask; \
+ (_m << 16) | FIELD_PREP(_m, value); \
+})
+
+struct sp_pll {
+ struct clk_hw hw;
+ void __iomem *reg;
+ spinlock_t lock; /* lock for reg */
+ int div_shift;
+ int div_width;
+ int pd_bit; /* power down bit idx */
+ int bp_bit; /* bypass bit idx */
+ unsigned long brate; /* base rate, TODO: replace brate with muldiv */
+ u32 p[P_MAX]; /* for hold PLLTV/PLLA parameters */
+};
+
+#define to_sp_pll(_hw) container_of(_hw, struct sp_pll, hw)
+
+struct sp_clk_gate_info {
+ u16 reg; /* reg_index_shift */
+ u16 ext_parent; /* parent is extclk */
+};
+
+static const struct sp_clk_gate_info sp_clk_gates[] = {
+ { 0x02 },
+ { 0x05 },
+ { 0x06 },
+ { 0x07 },
+ { 0x09 },
+ { 0x0b, 1 },
+ { 0x0f, 1 },
+ { 0x14 },
+ { 0x15 },
+ { 0x16 },
+ { 0x17 },
+ { 0x18, 1 },
+ { 0x19, 1 },
+ { 0x1a, 1 },
+ { 0x1b, 1 },
+ { 0x1c, 1 },
+ { 0x1d, 1 },
+ { 0x1e },
+ { 0x1f, 1 },
+ { 0x20 },
+ { 0x21 },
+ { 0x22 },
+ { 0x23 },
+ { 0x24 },
+ { 0x25 },
+ { 0x26 },
+ { 0x2a },
+ { 0x2b },
+ { 0x2d },
+ { 0x2e },
+ { 0x30 },
+ { 0x31 },
+ { 0x32 },
+ { 0x33 },
+ { 0x3d },
+ { 0x3e },
+ { 0x3f },
+ { 0x42 },
+ { 0x44 },
+ { 0x4b },
+ { 0x4c },
+ { 0x4d },
+ { 0x4e },
+ { 0x4f },
+ { 0x50 },
+ { 0x55 },
+ { 0x60 },
+ { 0x61 },
+ { 0x6a },
+ { 0x73 },
+ { 0x86 },
+ { 0x8a },
+ { 0x8b },
+ { 0x8d },
+ { 0x8e },
+ { 0x8f },
+ { 0x90 },
+ { 0x92 },
+ { 0x93 },
+ { 0x95 },
+ { 0x96 },
+ { 0x97 },
+ { 0x98 },
+ { 0x99 },
+};
+
+#define _M 1000000UL
+#define F_27M (27 * _M)
+
+/*********************************** PLL_TV **********************************/
+
+/* TODO: set proper FVCO range */
+#define FVCO_MIN (100 * _M)
+#define FVCO_MAX (200 * _M)
+
+#define F_MIN (FVCO_MIN / 8)
+#define F_MAX (FVCO_MAX)
+
+static long plltv_integer_div(struct sp_pll *clk, unsigned long freq)
+{
+ /* valid m values: 27M must be divisible by m */
+ static const u32 m_table[] = {
+ 1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 15, 16, 18, 20, 24, 25, 27, 30, 32
+ };
+ u32 m, n, r;
+ unsigned long fvco, nf;
+ long ret;
+
+ freq = clamp(freq, F_MIN, F_MAX);
+
+ /* DIVR 0~3 */
+ for (r = 0; r <= 3; r++) {
+ fvco = freq << r;
+ if (fvco <= FVCO_MAX)
+ break;
+ }
+
+ /* DIVM */
+ for (m = 0; m < ARRAY_SIZE(m_table); m++) {
+ nf = fvco * m_table[m];
+ n = nf / F_27M;
+ if ((n * F_27M) == nf)
+ break;
+ }
+ m = m_table[m];
+
+ if (!m) {
+ ret = -EINVAL;
+ goto err_not_found;
+ }
+
+ /* save parameters */
+ clk->p[SEL_FRA] = 0;
+ clk->p[DIVR] = r;
+ clk->p[DIVN] = n;
+ clk->p[DIVM] = m;
+
+ return freq;
+
+err_not_found:
+ pr_err("%s: %s freq:%lu not found a valid setting\n",
+ __func__, clk_hw_get_name(&clk->hw), freq);
+
+ return ret;
+}
+
+/* parameters for PLLTV fractional divider */
+static const u32 pt[][5] = {
+ /* conventional fractional */
+ {
+ 1, /* factor */
+ 5, /* 5 * p0 (nint) */
+ 1, /* 1 * p0 */
+ F_27M, /* F_27M / p0 */
+ 1, /* p0 / p2 */
+ },
+ /* phase rotation */
+ {
+ 10, /* factor */
+ 54, /* 5.4 * p0 (nint) */
+ 2, /* 0.2 * p0 */
+ F_27M / 10, /* F_27M / p0 */
+ 5, /* p0 / p2 */
+ },
+};
+
+static const u32 sdm_mod_vals[] = { 91, 55 };
+
+static long plltv_fractional_div(struct sp_pll *clk, unsigned long freq)
+{
+ u32 m, r;
+ u32 nint, nfra;
+ u32 df_quotient_min = 210000000;
+ u32 df_remainder_min = 0;
+ unsigned long fvco, nf, f, fout = 0;
+ int sdm, ph;
+
+ freq = clamp(freq, F_MIN, F_MAX);
+
+ /* DIVR 0~3 */
+ for (r = 0; r <= 3; r++) {
+ fvco = freq << r;
+ if (fvco <= FVCO_MAX)
+ break;
+ }
+ f = F_27M >> r;
+
+ /* PH_SEL */
+ for (ph = ARRAY_SIZE(pt) - 1; ph >= 0; ph--) {
+ const u32 *pp = pt[ph];
+
+ /* SDM_MOD */
+ for (sdm = 0; sdm < ARRAY_SIZE(sdm_mod_vals); sdm++) {
+ u32 mod = sdm_mod_vals[sdm];
+
+ /* DIVM 1~32 */
+ for (m = 1; m <= 32; m++) {
+ u32 df; /* diff freq */
+ u32 df_quotient, df_remainder;
+
+ nf = fvco * m;
+ nint = nf / pp[3];
+
+ if (nint < pp[1])
+ continue;
+ if (nint > pp[1])
+ break;
+
+ nfra = (((nf % pp[3]) * mod * pp[4]) + (F_27M / 2)) / F_27M;
+ if (nfra) {
+ u32 df0 = f * (nint + pp[2]) / pp[0];
+ u32 df1 = f * (mod - nfra) / mod / pp[4];
+
+ df = df0 - df1;
+ } else {
+ df = f * (nint) / pp[0];
+ }
+
+ df_quotient = df / m;
+ df_remainder = ((df % m) * 1000) / m;
+
+ if (freq > df_quotient) {
+ df_quotient = freq - df_quotient - 1;
+ df_remainder = 1000 - df_remainder;
+ } else {
+ df_quotient = df_quotient - freq;
+ }
+
+ if (df_quotient_min > df_quotient ||
+ (df_quotient_min == df_quotient &&
+ df_remainder_min > df_remainder)) {
+ /* found a closer freq, save parameters */
+ clk->p[SEL_FRA] = 1;
+ clk->p[SDM_MOD] = sdm;
+ clk->p[PH_SEL] = ph;
+ clk->p[NFRA] = nfra;
+ clk->p[DIVR] = r;
+ clk->p[DIVM] = m;
+
+ fout = df / m;
+ df_quotient_min = df_quotient;
+ df_remainder_min = df_remainder;
+ }
+ }
+ }
+ }
+
+ if (!fout) {
+ pr_err("%s: %s freq:%lu not found a valid setting\n",
+ __func__, clk_hw_get_name(&clk->hw), freq);
+ return -EINVAL;
+ }
+
+ return fout;
+}
+
+static long plltv_div(struct sp_pll *clk, unsigned long freq)
+{
+ if (freq % 100)
+ return plltv_fractional_div(clk, freq);
+
+ return plltv_integer_div(clk, freq);
+}
+
+static int plltv_set_rate(struct sp_pll *clk)
+{
+ unsigned long flags;
+ u32 r0, r1, r2;
+
+ r0 = BIT(clk->bp_bit + 16);
+ r0 |= HWM_FIELD_PREP(MASK_SEL_FRA, clk->p[SEL_FRA]);
+ r0 |= HWM_FIELD_PREP(MASK_SDM_MOD, clk->p[SDM_MOD]);
+ r0 |= HWM_FIELD_PREP(MASK_PH_SEL, clk->p[PH_SEL]);
+ r0 |= HWM_FIELD_PREP(MASK_NFRA, clk->p[NFRA]);
+
+ r1 = HWM_FIELD_PREP(MASK_DIVR, clk->p[DIVR]);
+
+ r2 = HWM_FIELD_PREP(MASK_DIVN, clk->p[DIVN] - 1);
+ r2 |= HWM_FIELD_PREP(MASK_DIVM, clk->p[DIVM] - 1);
+
+ spin_lock_irqsave(&clk->lock, flags);
+ writel(r0, clk->reg);
+ writel(r1, clk->reg + 4);
+ writel(r2, clk->reg + 8);
+ spin_unlock_irqrestore(&clk->lock, flags);
+
+ return 0;
+}
+
+/*********************************** PLL_A ***********************************/
+
+/* from Q628_PLLs_REG_setting.xlsx */
+static const struct {
+ u32 rate;
+ u32 regs[5];
+} pa[] = {
+ {
+ .rate = 135475200,
+ .regs = {
+ 0x4801,
+ 0x02df,
+ 0x248f,
+ 0x0211,
+ 0x33e9
+ }
+ },
+ {
+ .rate = 147456000,
+ .regs = {
+ 0x4801,
+ 0x1adf,
+ 0x2490,
+ 0x0349,
+ 0x33e9
+ }
+ },
+ {
+ .rate = 196608000,
+ .regs = {
+ 0x4801,
+ 0x42ef,
+ 0x2495,
+ 0x01c6,
+ 0x33e9
+ }
+ },
+};
+
+static int plla_set_rate(struct sp_pll *clk)
+{
+ const u32 *pp = pa[clk->p[0]].regs;
+ unsigned long flags;
+ int i;
+
+ spin_lock_irqsave(&clk->lock, flags);
+ for (i = 0; i < ARRAY_SIZE(pa->regs); i++)
+ writel(0xffff0000 | pp[i], clk->reg + (i * 4));
+ spin_unlock_irqrestore(&clk->lock, flags);
+
+ return 0;
+}
+
+static long plla_round_rate(struct sp_pll *clk, unsigned long rate)
+{
+ int i = ARRAY_SIZE(pa);
+
+ while (--i) {
+ if (rate >= pa[i].rate)
+ break;
+ }
+ clk->p[0] = i;
+
+ return pa[i].rate;
+}
+
+/********************************** SP_PLL ***********************************/
+
+static long sp_pll_calc_div(struct sp_pll *clk, unsigned long rate)
+{
+ u32 fbdiv;
+ u32 max = 1 << clk->div_width;
+
+ fbdiv = DIV_ROUND_CLOSEST(rate, clk->brate);
+ if (fbdiv > max)
+ fbdiv = max;
+
+ return fbdiv;
+}
+
+static long sp_pll_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *prate)
+{
+ struct sp_pll *clk = to_sp_pll(hw);
+ long ret;
+
+ if (rate == *prate) {
+ ret = *prate; /* bypass */
+ } else if (clk->div_width == DIV_A) {
+ ret = plla_round_rate(clk, rate);
+ } else if (clk->div_width == DIV_TV) {
+ ret = plltv_div(clk, rate);
+ if (ret < 0)
+ ret = *prate;
+ } else {
+ ret = sp_pll_calc_div(clk, rate) * clk->brate;
+ }
+
+ return ret;
+}
+
+static unsigned long sp_pll_recalc_rate(struct clk_hw *hw,
+ unsigned long prate)
+{
+ struct sp_pll *clk = to_sp_pll(hw);
+ u32 reg = readl(clk->reg);
+ unsigned long ret;
+
+ if (reg & BIT(clk->bp_bit)) {
+ ret = prate; /* bypass */
+ } else if (clk->div_width == DIV_A) {
+ ret = pa[clk->p[0]].rate;
+ } else if (clk->div_width == DIV_TV) {
+ u32 m, r, reg2;
+
+ r = FIELD_GET(MASK_DIVR, readl(clk->reg + 4));
+ reg2 = readl(clk->reg + 8);
+ m = FIELD_GET(MASK_DIVM, reg2) + 1;
+
+ if (reg & MASK_SEL_FRA) {
+ /* fractional divider */
+ u32 sdm = FIELD_GET(MASK_SDM_MOD, reg);
+ u32 ph = FIELD_GET(MASK_PH_SEL, reg);
+ u32 nfra = FIELD_GET(MASK_NFRA, reg);
+ const u32 *pp = pt[ph];
+ unsigned long r0, r1;
+
+ ret = prate >> r;
+ r0 = ret * (pp[1] + pp[2]) / pp[0];
+ r1 = ret * (sdm_mod_vals[sdm] - nfra) / sdm_mod_vals[sdm] / pp[4];
+ ret = (r0 - r1) / m;
+ } else {
+ /* integer divider */
+ u32 n = FIELD_GET(MASK_DIVN, reg2) + 1;
+
+ ret = (prate / m * n) >> r;
+ }
+ } else {
+ u32 fbdiv = ((reg >> clk->div_shift) & ((1 << clk->div_width) - 1)) + 1;
+
+ ret = clk->brate * fbdiv;
+ }
+
+ return ret;
+}
+
+static int sp_pll_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long prate)
+{
+ struct sp_pll *clk = to_sp_pll(hw);
+ unsigned long flags;
+ u32 reg;
+
+ reg = BIT(clk->bp_bit + 16); /* HIWORD_MASK */
+
+ if (rate == prate) {
+ reg |= BIT(clk->bp_bit); /* bypass */
+ } else if (clk->div_width == DIV_A) {
+ return plla_set_rate(clk);
+ } else if (clk->div_width == DIV_TV) {
+ return plltv_set_rate(clk);
+ } else if (clk->div_width) {
+ u32 fbdiv = sp_pll_calc_div(clk, rate);
+ u32 mask = GENMASK(clk->div_shift + clk->div_width - 1, clk->div_shift);
+
+ reg |= mask << 16;
+ reg |= ((fbdiv - 1) << clk->div_shift) & mask;
+ }
+
+ spin_lock_irqsave(&clk->lock, flags);
+ writel(reg, clk->reg);
+ spin_unlock_irqrestore(&clk->lock, flags);
+
+ return 0;
+}
+
+static int sp_pll_enable(struct clk_hw *hw)
+{
+ struct sp_pll *clk = to_sp_pll(hw);
+
+ writel(BIT(clk->pd_bit + 16) | BIT(clk->pd_bit), clk->reg);
+
+ return 0;
+}
+
+static void sp_pll_disable(struct clk_hw *hw)
+{
+ struct sp_pll *clk = to_sp_pll(hw);
+
+ writel(BIT(clk->pd_bit + 16), clk->reg);
+}
+
+static int sp_pll_is_enabled(struct clk_hw *hw)
+{
+ struct sp_pll *clk = to_sp_pll(hw);
+
+ return readl(clk->reg) & BIT(clk->pd_bit);
+}
+
+static const struct clk_ops sp_pll_ops = {
+ .enable = sp_pll_enable,
+ .disable = sp_pll_disable,
+ .is_enabled = sp_pll_is_enabled,
+ .round_rate = sp_pll_round_rate,
+ .recalc_rate = sp_pll_recalc_rate,
+ .set_rate = sp_pll_set_rate
+};
+
+static const struct clk_ops sp_pll_sub_ops = {
+ .enable = sp_pll_enable,
+ .disable = sp_pll_disable,
+ .is_enabled = sp_pll_is_enabled,
+ .recalc_rate = sp_pll_recalc_rate,
+};
+
+static struct clk_hw *sp_pll_register(struct device *dev, const char *name,
+ const struct clk_parent_data *parent_data,
+ void __iomem *reg, int pd_bit, int bp_bit,
+ unsigned long brate, int shift, int width,
+ unsigned long flags)
+{
+ struct sp_pll *pll;
+ struct clk_hw *hw;
+ struct clk_init_data initd = {
+ .name = name,
+ .parent_data = parent_data,
+ .ops = (bp_bit >= 0) ? &sp_pll_ops : &sp_pll_sub_ops,
+ .num_parents = 1,
+ .flags = flags,
+ };
+ int ret;
+
+ pll = devm_kzalloc(dev, sizeof(*pll), GFP_KERNEL);
+ if (!pll)
+ return ERR_PTR(-ENOMEM);
+
+ pll->hw.init = &initd;
+ pll->reg = reg;
+ pll->pd_bit = pd_bit;
+ pll->bp_bit = bp_bit;
+ pll->brate = brate;
+ pll->div_shift = shift;
+ pll->div_width = width;
+ spin_lock_init(&pll->lock);
+
+ hw = &pll->hw;
+ ret = devm_clk_hw_register(dev, hw);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return hw;
+}
+
+#define PLLA_CTL (pll_base + 0x1c)
+#define PLLE_CTL (pll_base + 0x30)
+#define PLLF_CTL (pll_base + 0x34)
+#define PLLTV_CTL (pll_base + 0x38)
+
+static int sp7021_clk_probe(struct platform_device *pdev)
+{
+ static const u32 sp_clken[] = {
+ 0x67ef, 0x03ff, 0xff03, 0xfff0, 0x0004, /* G0.1~5 */
+ 0x0000, 0x8000, 0xffff, 0x0040, 0x0000, /* G0.6~10 */
+ };
+ static struct clk_parent_data pd_ext, pd_sys, pd_e;
+ struct device *dev = &pdev->dev;
+ void __iomem *clk_base, *pll_base, *sys_base;
+ struct clk_hw_onecell_data *clk_data;
+ struct clk_hw **hws;
+ int i;
+
+ clk_base = devm_platform_ioremap_resource(pdev, 0);
+ if (!clk_base)
+ return -ENXIO;
+ pll_base = devm_platform_ioremap_resource(pdev, 1);
+ if (!pll_base)
+ return -ENXIO;
+ sys_base = devm_platform_ioremap_resource(pdev, 2);
+ if (!sys_base)
+ return -ENXIO;
+
+ /* enable default clks */
+ for (i = 0; i < ARRAY_SIZE(sp_clken); i++)
+ writel((sp_clken[i] << 16) | sp_clken[i], clk_base + i * 4);
+
+ clk_data = devm_kzalloc(dev, struct_size(clk_data, hws, CLK_MAX),
+ GFP_KERNEL);
+ if (!clk_data)
+ return -ENOMEM;
+
+ hws = clk_data->hws;
+ pd_ext.index = 0;
+
+ /* PLLs */
+ hws[PLL_A] = sp_pll_register(dev, "plla", &pd_ext, PLLA_CTL,
+ 11, 12, 27000000, 0, DIV_A, 0);
+ if (IS_ERR(hws[PLL_A]))
+ return PTR_ERR(hws[PLL_A]);
+
+ hws[PLL_E] = sp_pll_register(dev, "plle", &pd_ext, PLLE_CTL,
+ 6, 2, 50000000, 0, 0, 0);
+ if (IS_ERR(hws[PLL_E]))
+ return PTR_ERR(hws[PLL_E]);
+ pd_e.hw = hws[PLL_E];
+ hws[PLL_E_2P5] = sp_pll_register(dev, "plle_2p5", &pd_e, PLLE_CTL,
+ 13, -1, 2500000, 0, 0, 0);
+ if (IS_ERR(hws[PLL_E_2P5]))
+ return PTR_ERR(hws[PLL_E_2P5]);
+ hws[PLL_E_25] = sp_pll_register(dev, "plle_25", &pd_e, PLLE_CTL,
+ 12, -1, 25000000, 0, 0, 0);
+ if (IS_ERR(hws[PLL_E_25]))
+ return PTR_ERR(hws[PLL_E_25]);
+ hws[PLL_E_112P5] = sp_pll_register(dev, "plle_112p5", &pd_e, PLLE_CTL,
+ 11, -1, 112500000, 0, 0, 0);
+ if (IS_ERR(hws[PLL_E_112P5]))
+ return PTR_ERR(hws[PLL_E_112P5]);
+
+ hws[PLL_F] = sp_pll_register(dev, "pllf", &pd_ext, PLLF_CTL,
+ 0, 10, 13500000, 1, 4, 0);
+ if (IS_ERR(hws[PLL_F]))
+ return PTR_ERR(hws[PLL_F]);
+
+ hws[PLL_TV] = sp_pll_register(dev, "plltv", &pd_ext, PLLTV_CTL,
+ 0, 15, 27000000, 0, DIV_TV, 0);
+ if (IS_ERR(hws[PLL_TV]))
+ return PTR_ERR(hws[PLL_TV]);
+ hws[PLL_TV_A] = devm_clk_hw_register_divider(dev, "plltv_a", "plltv", 0,
+ PLLTV_CTL + 4, 5, 1,
+ CLK_DIVIDER_POWER_OF_TWO,
+ &to_sp_pll(hws[PLL_TV])->lock);
+ if (IS_ERR(hws[PLL_TV_A]))
+ return PTR_ERR(hws[PLL_TV_A]);
+
+ /* system clock, should not be disabled */
+ hws[PLL_SYS] = sp_pll_register(dev, "pllsys", &pd_ext, sys_base,
+ 10, 9, 13500000, 0, 4, CLK_IS_CRITICAL);
+ if (IS_ERR(hws[PLL_SYS]))
+ return PTR_ERR(hws[PLL_SYS]);
+ pd_sys.hw = hws[PLL_SYS];
+
+ /* gates */
+ for (i = 0; i < ARRAY_SIZE(sp_clk_gates); i++) {
+ char name[10];
+ u32 j = sp_clk_gates[i].reg;
+ struct clk_parent_data *pd = sp_clk_gates[i].ext_parent ? &pd_ext : &pd_sys;
+
+ sprintf(name, "%02d_0x%02x", i, j);
+ hws[i] = devm_clk_hw_register_gate_parent_data(dev, name, pd, 0,
+ clk_base + (j >> 4) * 4,
+ j & 0x0f,
+ CLK_GATE_HIWORD_MASK,
+ NULL);
+ if (IS_ERR(hws[i]))
+ return PTR_ERR(hws[i]);
+ }
+
+ clk_data->num = CLK_MAX;
+ return devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, clk_data);
+}
+
+static const struct of_device_id sp7021_clk_dt_ids[] = {
+ { .compatible = "sunplus,sp7021-clkc" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, sp7021_clk_dt_ids);
+
+static struct platform_driver sp7021_clk_driver = {
+ .probe = sp7021_clk_probe,
+ .driver = {
+ .name = "sp7021-clk",
+ .of_match_table = sp7021_clk_dt_ids,
+ },
+};
+module_platform_driver(sp7021_clk_driver);
+
+MODULE_AUTHOR("Sunplus Technology");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Clock driver for Sunplus SP7021 SoC");
diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h
index 1615010aa..672866c3e 100644
--- a/include/linux/clk-provider.h
+++ b/include/linux/clk-provider.h
@@ -567,6 +567,24 @@ struct clk *clk_register_gate(struct device *dev, const char *name,
__devm_clk_hw_register_gate((dev), NULL, (name), (parent_name), NULL, \
NULL, (flags), (reg), (bit_idx), \
(clk_gate_flags), (lock))
+/**
+ * devm_clk_hw_register_gate_parent_data - register a gate clock with the
+ * clock framework
+ * @dev: device that is registering this clock
+ * @name: name of this clock
+ * @parent_data: parent clk data
+ * @flags: framework-specific flags for this clock
+ * @reg: register address to control gating of this clock
+ * @bit_idx: which bit in the register controls gating of this clock
+ * @clk_gate_flags: gate-specific flags for this clock
+ * @lock: shared register lock for this clock
+ */
+#define devm_clk_hw_register_gate_parent_data(dev, name, parent_data, flags, \
+ reg, bit_idx, clk_gate_flags, \
+ lock) \
+ __devm_clk_hw_register_gate((dev), NULL, (name), NULL, NULL, \
+ (parent_data), (flags), (reg), (bit_idx), \
+ (clk_gate_flags), (lock))
void clk_unregister_gate(struct clk *clk);
void clk_hw_unregister_gate(struct clk_hw *hw);
int clk_gate_is_enabled(struct clk_hw *hw);
base-commit: 568035b01cfb107af8d2e4bd2fb9aea22cf5b868
--
2.33.1
^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH] clk: Add Sunplus SP7021 clock driver
2022-08-17 7:01 [PATCH] clk: Add Sunplus SP7021 clock driver Qin Jian
@ 2022-08-17 20:23 ` kernel test robot
2022-08-22 8:40 ` Dan Carpenter
1 sibling, 0 replies; 4+ messages in thread
From: kernel test robot @ 2022-08-17 20:23 UTC (permalink / raw)
To: Qin Jian, sboyd
Cc: llvm, kbuild-all, mturquette, linux-kernel, linux-clk,
linux-arm-kernel, Qin Jian
Hi Qin,
I love your patch! Perhaps something to improve:
[auto build test WARNING on 568035b01cfb107af8d2e4bd2fb9aea22cf5b868]
url: https://github.com/intel-lab-lkp/linux/commits/Qin-Jian/clk-Add-Sunplus-SP7021-clock-driver/20220817-151010
base: 568035b01cfb107af8d2e4bd2fb9aea22cf5b868
config: arm-randconfig-r013-20220818 (https://download.01.org/0day-ci/archive/20220818/202208180400.EvwdX195-lkp@intel.com/config)
compiler: clang version 16.0.0 (https://github.com/llvm/llvm-project aed5e3bea138ce581d682158eb61c27b3cfdd6ec)
reproduce (this is a W=1 build):
wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
chmod +x ~/bin/make.cross
# install arm cross compiling tool for clang build
# apt-get install binutils-arm-linux-gnueabi
# https://github.com/intel-lab-lkp/linux/commit/5673230d6a56d6c37b8db18d202c0ef526f52b2e
git remote add linux-review https://github.com/intel-lab-lkp/linux
git fetch --no-tags linux-review Qin-Jian/clk-Add-Sunplus-SP7021-clock-driver/20220817-151010
git checkout 5673230d6a56d6c37b8db18d202c0ef526f52b2e
# save the config file
mkdir build_dir && cp config build_dir/.config
COMPILER_INSTALL_PATH=$HOME/0day COMPILER=clang make.cross W=1 O=build_dir ARCH=arm SHELL=/bin/bash drivers/clk/
If you fix the issue, kindly add following tag where applicable
Reported-by: kernel test robot <lkp@intel.com>
All warnings (new ones prefixed by >>):
>> drivers/clk/clk-sp7021.c:317:8: warning: result of comparison of constant 18446744073709551615 with expression of type 'typeof (_Generic((_m), char: (unsigned char)0, unsigned char: (unsigned char)0, signed char: (unsigned char)0, unsigned short: (unsigned short)0, short: (unsigned short)0, unsigned int: (unsigned int)0, int: (unsigned int)0, unsigned long: (unsigned long)0, long: (unsigned long)0, unsigned long long: (unsigned long long)0, long long: (unsigned long long)0, default: (_m)))' (aka 'unsigned int') is always false [-Wtautological-constant-out-of-range-compare]
r0 |= HWM_FIELD_PREP(MASK_SEL_FRA, clk->p[SEL_FRA]);
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
drivers/clk/clk-sp7021.c:44:15: note: expanded from macro 'HWM_FIELD_PREP'
(_m << 16) | FIELD_PREP(_m, value); \
^~~~~~~~~~~~~~~~~~~~~
include/linux/bitfield.h:114:3: note: expanded from macro 'FIELD_PREP'
__BF_FIELD_CHECK(_mask, 0ULL, _val, "FIELD_PREP: "); \
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/linux/bitfield.h:71:53: note: expanded from macro '__BF_FIELD_CHECK'
BUILD_BUG_ON_MSG(__bf_cast_unsigned(_mask, _mask) > \
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~
note: (skipping 1 expansions in backtrace; use -fmacro-backtrace-limit=0 to see all)
include/linux/compiler_types.h:354:22: note: expanded from macro 'compiletime_assert'
_compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__)
~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/linux/compiler_types.h:342:23: note: expanded from macro '_compiletime_assert'
__compiletime_assert(condition, msg, prefix, suffix)
~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/linux/compiler_types.h:334:9: note: expanded from macro '__compiletime_assert'
if (!(condition)) \
^~~~~~~~~
drivers/clk/clk-sp7021.c:318:8: warning: result of comparison of constant 18446744073709551615 with expression of type 'typeof (_Generic((_m), char: (unsigned char)0, unsigned char: (unsigned char)0, signed char: (unsigned char)0, unsigned short: (unsigned short)0, short: (unsigned short)0, unsigned int: (unsigned int)0, int: (unsigned int)0, unsigned long: (unsigned long)0, long: (unsigned long)0, unsigned long long: (unsigned long long)0, long long: (unsigned long long)0, default: (_m)))' (aka 'unsigned int') is always false [-Wtautological-constant-out-of-range-compare]
r0 |= HWM_FIELD_PREP(MASK_SDM_MOD, clk->p[SDM_MOD]);
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
drivers/clk/clk-sp7021.c:44:15: note: expanded from macro 'HWM_FIELD_PREP'
(_m << 16) | FIELD_PREP(_m, value); \
^~~~~~~~~~~~~~~~~~~~~
include/linux/bitfield.h:114:3: note: expanded from macro 'FIELD_PREP'
__BF_FIELD_CHECK(_mask, 0ULL, _val, "FIELD_PREP: "); \
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/linux/bitfield.h:71:53: note: expanded from macro '__BF_FIELD_CHECK'
BUILD_BUG_ON_MSG(__bf_cast_unsigned(_mask, _mask) > \
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~
note: (skipping 1 expansions in backtrace; use -fmacro-backtrace-limit=0 to see all)
include/linux/compiler_types.h:354:22: note: expanded from macro 'compiletime_assert'
_compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__)
~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/linux/compiler_types.h:342:23: note: expanded from macro '_compiletime_assert'
__compiletime_assert(condition, msg, prefix, suffix)
~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/linux/compiler_types.h:334:9: note: expanded from macro '__compiletime_assert'
if (!(condition)) \
^~~~~~~~~
drivers/clk/clk-sp7021.c:319:8: warning: result of comparison of constant 18446744073709551615 with expression of type 'typeof (_Generic((_m), char: (unsigned char)0, unsigned char: (unsigned char)0, signed char: (unsigned char)0, unsigned short: (unsigned short)0, short: (unsigned short)0, unsigned int: (unsigned int)0, int: (unsigned int)0, unsigned long: (unsigned long)0, long: (unsigned long)0, unsigned long long: (unsigned long long)0, long long: (unsigned long long)0, default: (_m)))' (aka 'unsigned int') is always false [-Wtautological-constant-out-of-range-compare]
r0 |= HWM_FIELD_PREP(MASK_PH_SEL, clk->p[PH_SEL]);
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
drivers/clk/clk-sp7021.c:44:15: note: expanded from macro 'HWM_FIELD_PREP'
(_m << 16) | FIELD_PREP(_m, value); \
^~~~~~~~~~~~~~~~~~~~~
include/linux/bitfield.h:114:3: note: expanded from macro 'FIELD_PREP'
__BF_FIELD_CHECK(_mask, 0ULL, _val, "FIELD_PREP: "); \
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/linux/bitfield.h:71:53: note: expanded from macro '__BF_FIELD_CHECK'
BUILD_BUG_ON_MSG(__bf_cast_unsigned(_mask, _mask) > \
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~
note: (skipping 1 expansions in backtrace; use -fmacro-backtrace-limit=0 to see all)
include/linux/compiler_types.h:354:22: note: expanded from macro 'compiletime_assert'
_compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__)
~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/linux/compiler_types.h:342:23: note: expanded from macro '_compiletime_assert'
__compiletime_assert(condition, msg, prefix, suffix)
~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/linux/compiler_types.h:334:9: note: expanded from macro '__compiletime_assert'
if (!(condition)) \
^~~~~~~~~
drivers/clk/clk-sp7021.c:320:8: warning: result of comparison of constant 18446744073709551615 with expression of type 'typeof (_Generic((_m), char: (unsigned char)0, unsigned char: (unsigned char)0, signed char: (unsigned char)0, unsigned short: (unsigned short)0, short: (unsigned short)0, unsigned int: (unsigned int)0, int: (unsigned int)0, unsigned long: (unsigned long)0, long: (unsigned long)0, unsigned long long: (unsigned long long)0, long long: (unsigned long long)0, default: (_m)))' (aka 'unsigned int') is always false [-Wtautological-constant-out-of-range-compare]
r0 |= HWM_FIELD_PREP(MASK_NFRA, clk->p[NFRA]);
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
drivers/clk/clk-sp7021.c:44:15: note: expanded from macro 'HWM_FIELD_PREP'
(_m << 16) | FIELD_PREP(_m, value); \
^~~~~~~~~~~~~~~~~~~~~
include/linux/bitfield.h:114:3: note: expanded from macro 'FIELD_PREP'
__BF_FIELD_CHECK(_mask, 0ULL, _val, "FIELD_PREP: "); \
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/linux/bitfield.h:71:53: note: expanded from macro '__BF_FIELD_CHECK'
BUILD_BUG_ON_MSG(__bf_cast_unsigned(_mask, _mask) > \
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~
note: (skipping 1 expansions in backtrace; use -fmacro-backtrace-limit=0 to see all)
include/linux/compiler_types.h:354:22: note: expanded from macro 'compiletime_assert'
_compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__)
~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/linux/compiler_types.h:342:23: note: expanded from macro '_compiletime_assert'
__compiletime_assert(condition, msg, prefix, suffix)
~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/linux/compiler_types.h:334:9: note: expanded from macro '__compiletime_assert'
if (!(condition)) \
^~~~~~~~~
drivers/clk/clk-sp7021.c:322:8: warning: result of comparison of constant 18446744073709551615 with expression of type 'typeof (_Generic((_m), char: (unsigned char)0, unsigned char: (unsigned char)0, signed char: (unsigned char)0, unsigned short: (unsigned short)0, short: (unsigned short)0, unsigned int: (unsigned int)0, int: (unsigned int)0, unsigned long: (unsigned long)0, long: (unsigned long)0, unsigned long long: (unsigned long long)0, long long: (unsigned long long)0, default: (_m)))' (aka 'unsigned int') is always false [-Wtautological-constant-out-of-range-compare]
r1 = HWM_FIELD_PREP(MASK_DIVR, clk->p[DIVR]);
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
drivers/clk/clk-sp7021.c:44:15: note: expanded from macro 'HWM_FIELD_PREP'
(_m << 16) | FIELD_PREP(_m, value); \
^~~~~~~~~~~~~~~~~~~~~
include/linux/bitfield.h:114:3: note: expanded from macro 'FIELD_PREP'
__BF_FIELD_CHECK(_mask, 0ULL, _val, "FIELD_PREP: "); \
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/linux/bitfield.h:71:53: note: expanded from macro '__BF_FIELD_CHECK'
BUILD_BUG_ON_MSG(__bf_cast_unsigned(_mask, _mask) > \
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~
note: (skipping 1 expansions in backtrace; use -fmacro-backtrace-limit=0 to see all)
vim +317 drivers/clk/clk-sp7021.c
310
311 static int plltv_set_rate(struct sp_pll *clk)
312 {
313 unsigned long flags;
314 u32 r0, r1, r2;
315
316 r0 = BIT(clk->bp_bit + 16);
> 317 r0 |= HWM_FIELD_PREP(MASK_SEL_FRA, clk->p[SEL_FRA]);
318 r0 |= HWM_FIELD_PREP(MASK_SDM_MOD, clk->p[SDM_MOD]);
319 r0 |= HWM_FIELD_PREP(MASK_PH_SEL, clk->p[PH_SEL]);
320 r0 |= HWM_FIELD_PREP(MASK_NFRA, clk->p[NFRA]);
321
322 r1 = HWM_FIELD_PREP(MASK_DIVR, clk->p[DIVR]);
323
324 r2 = HWM_FIELD_PREP(MASK_DIVN, clk->p[DIVN] - 1);
325 r2 |= HWM_FIELD_PREP(MASK_DIVM, clk->p[DIVM] - 1);
326
327 spin_lock_irqsave(&clk->lock, flags);
328 writel(r0, clk->reg);
329 writel(r1, clk->reg + 4);
330 writel(r2, clk->reg + 8);
331 spin_unlock_irqrestore(&clk->lock, flags);
332
333 return 0;
334 }
335
--
0-DAY CI Kernel Test Service
https://01.org/lkp
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH] clk: Add Sunplus SP7021 clock driver
2022-08-17 7:01 [PATCH] clk: Add Sunplus SP7021 clock driver Qin Jian
2022-08-17 20:23 ` kernel test robot
@ 2022-08-22 8:40 ` Dan Carpenter
1 sibling, 0 replies; 4+ messages in thread
From: Dan Carpenter @ 2022-08-22 8:40 UTC (permalink / raw)
To: kbuild, Qin Jian, sboyd
Cc: lkp, kbuild-all, mturquette, linux-kernel, linux-clk,
linux-arm-kernel, Qin Jian
Hi Qin,
url: https://github.com/intel-lab-lkp/linux/commits/Qin-Jian/clk-Add-Sunplus-SP7021-clock-driver/20220817-151010
base: 568035b01cfb107af8d2e4bd2fb9aea22cf5b868
config: arm64-randconfig-m031-20220821 (https://download.01.org/0day-ci/archive/20220821/202208212144.aUofUlUt-lkp@intel.com/config)
compiler: aarch64-linux-gcc (GCC) 12.1.0
If you fix the issue, kindly add following tag where applicable
Reported-by: kernel test robot <lkp@intel.com>
Reported-by: Dan Carpenter <dan.carpenter@oracle.com>
smatch warnings:
drivers/clk/clk-sp7021.c:171 plltv_integer_div() error: buffer overflow 'm_table' 19 <= 19 (assuming for loop doesn't break)
vim +171 drivers/clk/clk-sp7021.c
5673230d6a56d6 Qin Jian 2022-08-17 145 static long plltv_integer_div(struct sp_pll *clk, unsigned long freq)
5673230d6a56d6 Qin Jian 2022-08-17 146 {
5673230d6a56d6 Qin Jian 2022-08-17 147 /* valid m values: 27M must be divisible by m */
5673230d6a56d6 Qin Jian 2022-08-17 148 static const u32 m_table[] = {
5673230d6a56d6 Qin Jian 2022-08-17 149 1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 15, 16, 18, 20, 24, 25, 27, 30, 32
5673230d6a56d6 Qin Jian 2022-08-17 150 };
5673230d6a56d6 Qin Jian 2022-08-17 151 u32 m, n, r;
5673230d6a56d6 Qin Jian 2022-08-17 152 unsigned long fvco, nf;
5673230d6a56d6 Qin Jian 2022-08-17 153 long ret;
5673230d6a56d6 Qin Jian 2022-08-17 154
5673230d6a56d6 Qin Jian 2022-08-17 155 freq = clamp(freq, F_MIN, F_MAX);
5673230d6a56d6 Qin Jian 2022-08-17 156
5673230d6a56d6 Qin Jian 2022-08-17 157 /* DIVR 0~3 */
5673230d6a56d6 Qin Jian 2022-08-17 158 for (r = 0; r <= 3; r++) {
5673230d6a56d6 Qin Jian 2022-08-17 159 fvco = freq << r;
5673230d6a56d6 Qin Jian 2022-08-17 160 if (fvco <= FVCO_MAX)
5673230d6a56d6 Qin Jian 2022-08-17 161 break;
5673230d6a56d6 Qin Jian 2022-08-17 162 }
5673230d6a56d6 Qin Jian 2022-08-17 163
5673230d6a56d6 Qin Jian 2022-08-17 164 /* DIVM */
5673230d6a56d6 Qin Jian 2022-08-17 165 for (m = 0; m < ARRAY_SIZE(m_table); m++) {
5673230d6a56d6 Qin Jian 2022-08-17 166 nf = fvco * m_table[m];
5673230d6a56d6 Qin Jian 2022-08-17 167 n = nf / F_27M;
5673230d6a56d6 Qin Jian 2022-08-17 168 if ((n * F_27M) == nf)
5673230d6a56d6 Qin Jian 2022-08-17 169 break;
5673230d6a56d6 Qin Jian 2022-08-17 170 }
5673230d6a56d6 Qin Jian 2022-08-17 @171 m = m_table[m];
^^^^^^^^^^^^^^^
If we know the for loop is going to break then why bother with a limit?
5673230d6a56d6 Qin Jian 2022-08-17 172
5673230d6a56d6 Qin Jian 2022-08-17 173 if (!m) {
5673230d6a56d6 Qin Jian 2022-08-17 174 ret = -EINVAL;
5673230d6a56d6 Qin Jian 2022-08-17 175 goto err_not_found;
5673230d6a56d6 Qin Jian 2022-08-17 176 }
5673230d6a56d6 Qin Jian 2022-08-17 177
5673230d6a56d6 Qin Jian 2022-08-17 178 /* save parameters */
5673230d6a56d6 Qin Jian 2022-08-17 179 clk->p[SEL_FRA] = 0;
5673230d6a56d6 Qin Jian 2022-08-17 180 clk->p[DIVR] = r;
5673230d6a56d6 Qin Jian 2022-08-17 181 clk->p[DIVN] = n;
5673230d6a56d6 Qin Jian 2022-08-17 182 clk->p[DIVM] = m;
5673230d6a56d6 Qin Jian 2022-08-17 183
5673230d6a56d6 Qin Jian 2022-08-17 184 return freq;
5673230d6a56d6 Qin Jian 2022-08-17 185
5673230d6a56d6 Qin Jian 2022-08-17 186 err_not_found:
5673230d6a56d6 Qin Jian 2022-08-17 187 pr_err("%s: %s freq:%lu not found a valid setting\n",
5673230d6a56d6 Qin Jian 2022-08-17 188 __func__, clk_hw_get_name(&clk->hw), freq);
5673230d6a56d6 Qin Jian 2022-08-17 189
5673230d6a56d6 Qin Jian 2022-08-17 190 return ret;
5673230d6a56d6 Qin Jian 2022-08-17 191 }
--
0-DAY CI Kernel Test Service
https://01.org/lkp
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH] clk: Add Sunplus SP7021 clock driver
@ 2021-10-22 9:07 qinjian
0 siblings, 0 replies; 4+ messages in thread
From: qinjian @ 2021-10-22 9:07 UTC (permalink / raw)
To: mturquette
Cc: sboyd, linux-arm-kernel, linux-clk, linux-kernel, wells.lu, qinjian
Add clock driver for Sunplus SP7021 SoC.
Signed-off-by: qinjian <qinjian@cqplus1.com>
---
MAINTAINERS | 1 +
drivers/clk/Kconfig | 8 +
drivers/clk/Makefile | 1 +
drivers/clk/clk-sp7021.c | 762 +++++++++++++++++++++++++++++++++++++++
4 files changed, 772 insertions(+)
create mode 100644 drivers/clk/clk-sp7021.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 65cd295e9..4279615e3 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2663,6 +2663,7 @@ F: Documentation/devicetree/bindings/arm/sunplus,sp7021.yaml
F: Documentation/devicetree/bindings/clock/sunplus,sp7021-clkc.yaml
F: Documentation/devicetree/bindings/interrupt-controller/sunplus,sp7021-intc.yaml
F: Documentation/devicetree/bindings/reset/sunplus,reset.yaml
+F: drivers/clk/clk-sp7021.c
F: drivers/irqchip/irq-sp7021-intc.c
F: include/dt-bindings/clock/sp-sp7021.h
F: include/dt-bindings/interrupt-controller/sp7021-intc.h
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index c5b3dc973..e2494c2aa 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -334,6 +334,14 @@ config COMMON_CLK_VC5
This driver supports the IDT VersaClock 5 and VersaClock 6
programmable clock generators.
+config COMMON_CLK_SP7021
+ def_bool OF && COMMON_CLK && SOC_SP7021
+ help
+ This driver supports the Sunplus SP7021 SoC clocks.
+ It implemented SP7021 PLLs/gate.
+ Not all features of the PLL are currently supported
+ by the driver.
+
config COMMON_CLK_STM32MP157
def_bool COMMON_CLK && MACH_STM32MP157
help
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index e42312121..f15bb5070 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -60,6 +60,7 @@ obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o
obj-$(CONFIG_COMMON_CLK_SI514) += clk-si514.o
obj-$(CONFIG_COMMON_CLK_SI544) += clk-si544.o
obj-$(CONFIG_COMMON_CLK_SI570) += clk-si570.o
+obj-$(CONFIG_COMMON_CLK_SP7021) += clk-sp7021.o
obj-$(CONFIG_COMMON_CLK_STM32F) += clk-stm32f4.o
obj-$(CONFIG_COMMON_CLK_STM32H7) += clk-stm32h7.o
obj-$(CONFIG_COMMON_CLK_STM32MP157) += clk-stm32mp1.o
diff --git a/drivers/clk/clk-sp7021.c b/drivers/clk/clk-sp7021.c
new file mode 100644
index 000000000..d0e27bfe6
--- /dev/null
+++ b/drivers/clk/clk-sp7021.c
@@ -0,0 +1,762 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) Sunplus Technology Co., Ltd.
+ * All rights reserved.
+ */
+//#define DEBUG
+#include <linux/module.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/clkdev.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <mach/io_map.h>
+#include <dt-bindings/clock/sp-sp7021.h>
+
+//#define TRACE pr_info("### %s:%d (%d)\n", __func__, __LINE__, (clk->reg - REG(4, 0)) / 4)
+#define TRACE
+
+#ifndef clk_readl
+#define clk_readl readl
+#define clk_writel writel
+#endif
+
+#define MASK_SET(shift, width, value) \
+({ \
+ u32 m = ((1 << (width)) - 1) << (shift); \
+ (m << 16) | (((value) << (shift)) & m); \
+})
+#define MASK_GET(shift, width, value) (((value) >> (shift)) & ((1 << (width)) - 1))
+
+#define REG(g, i) ((void __iomem *)VA_IOB_ADDR(((g) * 32 + (i)) * 4))
+
+#define PLLA_CTL REG(4, 7)
+#define PLLE_CTL REG(4, 12)
+#define PLLF_CTL REG(4, 13)
+#define PLLTV_CTL REG(4, 14)
+#define PLLSYS_CTL REG(4, 26)
+
+#define EXTCLK "extclk"
+
+/* speical div_width values for PLLTV/PLLA */
+#define DIV_TV 33
+#define DIV_A 34
+
+/* PLLTV parameters */
+enum {
+ SEL_FRA,
+ SDM_MOD,
+ PH_SEL,
+ NFRA,
+ DIVR,
+ DIVN,
+ DIVM,
+ P_MAX
+};
+
+struct sp_pll {
+ struct clk_hw hw;
+ void __iomem *reg;
+ spinlock_t *lock;
+ int pd_bit; /* power down bit idx */
+ int bp_bit; /* bypass bit idx */
+ unsigned long brate; /* base rate, FIXME: replace brate with muldiv */
+ int div_shift;
+ int div_width;
+ u32 p[P_MAX]; /* for hold PLLTV/PLLA parameters */
+};
+#define to_sp_pll(_hw) container_of(_hw, struct sp_pll, hw)
+
+#define P_EXTCLK (1 << 16)
+static const char * const parents[] = {
+ "pllsys",
+ "extclk",
+};
+
+/* FIXME: parent clk incorrect cause clk_get_rate got error value */
+static const u32 gates[] = {
+ SYSTEM,
+ RTC,
+ IOCTL,
+ IOP,
+ OTPRX,
+ NOC,
+ BR,
+ RBUS_L00,
+ SPIFL,
+ SDCTRL0,
+ PERI0 | P_EXTCLK,
+ A926,
+ UMCTL2,
+ PERI1 | P_EXTCLK,
+
+ DDR_PHY0,
+ ACHIP,
+ STC0,
+ STC_AV0,
+ STC_AV1,
+ STC_AV2,
+ UA0 | P_EXTCLK,
+ UA1 | P_EXTCLK,
+ UA2 | P_EXTCLK,
+ UA3 | P_EXTCLK,
+ UA4 | P_EXTCLK,
+ HWUA | P_EXTCLK,
+ DDC0,
+ UADMA | P_EXTCLK,
+
+ CBDMA0,
+ CBDMA1,
+ SPI_COMBO_0,
+ SPI_COMBO_1,
+ SPI_COMBO_2,
+ SPI_COMBO_3,
+ AUD,
+ USBC0,
+ USBC1,
+ UPHY0,
+ UPHY1,
+
+ I2CM0,
+ I2CM1,
+ I2CM2,
+ I2CM3,
+ PMC,
+ CARD_CTL0,
+ CARD_CTL1,
+
+ CARD_CTL4,
+ BCH,
+ DDFCH,
+ CSIIW0,
+ CSIIW1,
+ MIPICSI0,
+ MIPICSI1,
+
+ HDMI_TX,
+ VPOST,
+
+ TGEN,
+ DMIX,
+ TCON,
+ INTERRUPT,
+
+ RGST,
+ GPIO,
+ RBUS_TOP,
+
+ MAILBOX,
+ SPIND,
+ I2C2CBUS,
+ SEC,
+ GPOST0,
+ DVE,
+
+ OSD0,
+ DISP_PWM,
+ UADBG,
+ DUMMY_MASTER,
+ FIO_CTL,
+ FPGA,
+ L2SW,
+ ICM,
+ AXI_GLOBAL,
+};
+static struct clk *clks[CLK_MAX];
+static struct clk_onecell_data clk_data;
+
+static DEFINE_SPINLOCK(plla_lock);
+static DEFINE_SPINLOCK(plle_lock);
+static DEFINE_SPINLOCK(pllf_lock);
+static DEFINE_SPINLOCK(pllsys_lock);
+static DEFINE_SPINLOCK(plltv_lock);
+
+#define _M 1000000UL
+#define F_27M (27 * _M)
+
+/******************************************** PLL_TV *******************************************/
+
+//#define PLLTV_STEP_DIR (?) /* Unit: HZ */
+
+/* TODO: set proper FVCO range */
+#define FVCO_MIN (100 * _M)
+#define FVCO_MAX (200 * _M)
+
+#define F_MIN (FVCO_MIN / 8)
+#define F_MAX (FVCO_MAX)
+
+static long plltv_integer_div(struct sp_pll *clk, unsigned long freq)
+{
+ /* valid m values: 27M must be divisible by m, 0 means end */
+ static const u32 m_table[] = {
+ 1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 15, 16, 18, 20, 24, 25, 27, 30, 32, 0
+ };
+ u32 m, n, r;
+#ifdef PLLTV_STEP_DIR
+ u32 step = (PLLTV_STEP_DIR > 0) ? PLLTV_STEP_DIR : -PLLTV_STEP_DIR;
+ int calc_times = 1000000 / step;
+#endif
+ unsigned long fvco, nf;
+
+ TRACE;
+
+ /* check freq */
+ if (freq < F_MIN) {
+ pr_warn("[%s:%d] freq:%lu < F_MIN:%lu, round up\n",
+ __func__, __LINE__, freq, F_MIN);
+ freq = F_MIN;
+ } else if (freq > F_MAX) {
+ pr_warn("[%s:%d] freq:%lu > F_MAX:%lu, round down\n",
+ __func__, __LINE__, freq, F_MAX);
+ freq = F_MAX;
+ }
+
+#ifdef PLLTV_STEP_DIR
+ if ((freq % step) != 0)
+ freq += step - (freq % step) + ((PLLTV_STEP_DIR > 0) ? 0 : PLLTV_STEP_DIR);
+#endif
+
+#ifdef PLLTV_STEP_DIR
+CALC:
+ if (!calc_times) {
+ pr_err("[%s:%d] freq:%lu out of recalc times\n", __func__, __LINE__, freq);
+ return -ETIMEOUT;
+ }
+#endif
+
+ /* DIVR 0~3 */
+ for (r = 0; r <= 3; r++) {
+ fvco = freq << r;
+ if (fvco <= FVCO_MAX)
+ break;
+ }
+
+ /* DIVM */
+ for (m = 0; m_table[m]; m++) {
+ nf = fvco * m_table[m];
+ n = nf / F_27M;
+ if ((n * F_27M) == nf)
+ break;
+ }
+ m = m_table[m];
+
+ if (!m) {
+#ifdef PLLTV_STEP_DIR
+ freq += PLLTV_STEP_DIR;
+ calc_times--;
+ goto CALC;
+#else
+ pr_err("[%s:%d] freq:%lu not found a valid setting\n", __func__, __LINE__, freq);
+ return -EINVAL;
+#endif
+ }
+
+ /* save parameters */
+ clk->p[SEL_FRA] = 0;
+ clk->p[DIVR] = r;
+ clk->p[DIVN] = n;
+ clk->p[DIVM] = m;
+
+ pr_info("[%s:%d] M:%u N:%u R:%u CKREF:%lu FVCO:%lu FCKOUT:%lu\n",
+ __func__, __LINE__, m, n, r, (fvco / m), fvco, freq);
+
+ return freq;
+}
+
+/* parameters for PLLTV fractional divider */
+/* FIXME: better parameter naming */
+static const u32 pt[][5] = {
+ /* conventional fractional */
+ {
+ 1, // factor
+ 5, // 5 * p0 (nint)
+ 1, // 1 * p0
+ F_27M, // F_27M / p0
+ 1, // p0 / p2
+ },
+ /* phase rotation */
+ {
+ 10, // factor
+ 54, // 5.4 * p0 (nint)
+ 2, // 0.2 * p0
+ F_27M / 10, // F_27M / p0
+ 5, // p0 / p2
+ },
+};
+static const u32 mods[] = { 91, 55 }; /* SDM_MOD mod values */
+
+static long plltv_fractional_div(struct sp_pll *clk, unsigned long freq)
+{
+ u32 m, r;
+ u32 nint, nfra;
+ u32 diff_min_quotient = 210000000, diff_min_remainder = 0;
+ u32 diff_min_sign = 0;
+ unsigned long fvco, nf, f, fout = 0;
+ int sdm, ph;
+
+ TRACE;
+ /* check freq */
+ if (freq < F_MIN) {
+ pr_warn("[%s:%d] freq:%lu < F_MIN:%lu, round up\n",
+ __func__, __LINE__, freq, F_MIN);
+ freq = F_MIN;
+ } else if (freq > F_MAX) {
+ pr_warn("[%s:%d] freq:%lu > F_MAX:%lu, round down\n",
+ __func__, __LINE__, freq, F_MAX);
+ freq = F_MAX;
+ }
+
+ /* DIVR 0~3 */
+ for (r = 0; r <= 3; r++) {
+ fvco = freq << r;
+ if (fvco <= FVCO_MAX)
+ break;
+ }
+ pr_info("freq:%lu fvco:%lu R:%u\n", freq, fvco, r);
+ f = F_27M >> r;
+
+ /* PH_SEL 1/0 */
+ for (ph = 1; ph >= 0; ph--) {
+ const u32 *pp = pt[ph];
+ u32 ms = 1;
+
+ /* SDM_MOD 0/1 */
+ for (sdm = 0; sdm <= 1; sdm++) {
+ u32 mod = mods[sdm];
+
+ /* DIVM 1~32 */
+ for (m = ms; m <= 32; m++) {
+ u32 diff_freq;
+ u32 diff_freq_quotient = 0, diff_freq_remainder = 0;
+ u32 diff_freq_sign = 0; /* 0:Positive number, 1:Negative number */
+
+ nf = fvco * m;
+ nint = nf / pp[3];
+
+ if (nint < pp[1])
+ continue;
+ if (nint > pp[1])
+ break;
+
+ nfra = (((nf % pp[3]) * mod * pp[4]) + (F_27M / 2)) / F_27M;
+ if (nfra)
+ diff_freq = (f * (nint + pp[2]) / pp[0]) -
+ (f * (mod - nfra) / mod / pp[4]);
+ else
+ diff_freq = (f * (nint) / pp[0]);
+
+ diff_freq_quotient = diff_freq / m;
+ diff_freq_remainder = ((diff_freq % m) * 1000) / m;
+
+ pr_info("m = %d N.f = %2d.%03d%03d, nfra = %d/%d fout = %u\n",
+ m, nint, (nfra * 1000) / mod,
+ (((nfra * 1000) % mod) * 1000) / mod,
+ nfra, mod, diff_freq_quotient);
+
+ if (freq > diff_freq_quotient) {
+ diff_freq_quotient = freq - diff_freq_quotient - 1;
+ diff_freq_remainder = 1000 - diff_freq_remainder;
+ diff_freq_sign = 1;
+ } else {
+ diff_freq_quotient = diff_freq_quotient - freq;
+ diff_freq_sign = 0;
+ }
+
+ if ((diff_min_quotient > diff_freq_quotient) ||
+ ((diff_min_quotient == diff_freq_quotient) &&
+ (diff_min_remainder > diff_freq_remainder))) {
+
+ /* found a closer freq, save parameters */
+ TRACE;
+ clk->p[SEL_FRA] = 1;
+ clk->p[SDM_MOD] = sdm;
+ clk->p[PH_SEL] = ph;
+ clk->p[NFRA] = nfra;
+ clk->p[DIVR] = r;
+ clk->p[DIVM] = m;
+
+ fout = diff_freq / m;
+ diff_min_quotient = diff_freq_quotient;
+ diff_min_remainder = diff_freq_remainder;
+ diff_min_sign = diff_freq_sign;
+ }
+ }
+ }
+ }
+
+ if (!fout) {
+ pr_err("[%s:%d] freq:%lu not found a valid setting\n", __func__, __LINE__, freq);
+ return -EINVAL;
+ }
+
+ //pr_info("MOD:%u PH_SEL:%u NFRA:%u M:%u R:%u\n",
+ // mods[clk->p[SDM_MOD]], clk->p[PH_SEL], clk->p[NFRA], clk->p[DIVM], clk->p[DIVR]);
+
+ pr_info("[%s:%d] real out:%lu/%lu Hz(%u, %u, sign %u)\n",
+ __func__, __LINE__, fout, freq,
+ diff_min_quotient, diff_min_remainder, diff_min_sign);
+
+ return fout;
+}
+
+static long plltv_div(struct sp_pll *clk, unsigned long freq)
+{
+ TRACE;
+ if (freq % 100)
+ return plltv_fractional_div(clk, freq);
+ else
+ return plltv_integer_div(clk, freq);
+}
+
+static void plltv_set_rate(struct sp_pll *clk)
+{
+ u32 reg;
+
+ //pr_info("MOD:%u PH_SEL:%u NFRA:%u M:%u R:%u\n",
+ // mods[clk->p[SDM_MOD]], clk->p[PH_SEL], clk->p[NFRA], clk->p[DIVM], clk->p[DIVR]);
+ reg = MASK_SET(1, 1, clk->p[SEL_FRA]);
+ reg |= MASK_SET(2, 1, clk->p[SDM_MOD]);
+ reg |= MASK_SET(4, 1, clk->p[PH_SEL]);
+ reg |= MASK_SET(6, 7, clk->p[NFRA]);
+ clk_writel(reg, clk->reg);
+
+ reg = MASK_SET(7, 2, clk->p[DIVR]);
+ clk_writel(reg, clk->reg + 4);
+
+ reg = MASK_SET(0, 8, clk->p[DIVN] - 1);
+ reg |= MASK_SET(8, 7, clk->p[DIVM] - 1);
+ clk_writel(reg, clk->reg + 8);
+}
+
+/******************************************** PLL_A ********************************************/
+
+/* from Q628_PLLs_REG_setting.xlsx */
+struct {
+ u32 rate;
+ u32 regs[5];
+} pa[] = {
+ {
+ .rate = 135475200,
+ .regs = {
+ 0x4801,
+ 0x02df,
+ 0x248f,
+ 0x0211,
+ 0x33e9
+ }
+ },
+ {
+ .rate = 147456000,
+ .regs = {
+ 0x4801,
+ 0x1adf,
+ 0x2490,
+ 0x0349,
+ 0x33e9
+ }
+ },
+ {
+ .rate = 196608000,
+ .regs = {
+ 0x4801,
+ 0x42ef,
+ 0x2495,
+ 0x01c6,
+ 0x33e9
+ }
+ },
+};
+
+static void plla_set_rate(struct sp_pll *clk)
+{
+ const u32 *pp = pa[clk->p[0]].regs;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(pa->regs); i++) {
+ clk_writel(0xffff0000 | pp[i], clk->reg + (i * 4));
+ pr_info("%04x\n", pp[i]);
+ }
+}
+
+static long plla_round_rate(struct sp_pll *clk, unsigned long rate)
+{
+ int i = ARRAY_SIZE(pa);
+
+ while (--i) {
+ if (rate >= pa[i].rate)
+ break;
+ }
+ clk->p[0] = i;
+ return pa[i].rate;
+}
+
+/******************************************* SP_PLL ********************************************/
+
+static long sp_pll_calc_div(struct sp_pll *clk, unsigned long rate)
+{
+ u32 fbdiv;
+ u32 max = 1 << clk->div_width;
+
+ fbdiv = DIV_ROUND_CLOSEST(rate, clk->brate);
+ if (fbdiv > max)
+ fbdiv = max;
+ return fbdiv;
+}
+
+static long sp_pll_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *prate)
+{
+ struct sp_pll *clk = to_sp_pll(hw);
+ long ret;
+
+ TRACE;
+ //pr_info("round_rate: %lu %lu\n", rate, *prate);
+
+ if (rate == *prate)
+ ret = *prate; /* bypass */
+ else if (clk->div_width == DIV_A) {
+ ret = plla_round_rate(clk, rate);
+ } else if (clk->div_width == DIV_TV) {
+ ret = plltv_div(clk, rate);
+ if (ret < 0)
+ ret = *prate;
+ } else
+ ret = sp_pll_calc_div(clk, rate) * clk->brate;
+
+ return ret;
+}
+
+static unsigned long sp_pll_recalc_rate(struct clk_hw *hw,
+ unsigned long prate)
+{
+ struct sp_pll *clk = to_sp_pll(hw);
+ u32 reg = clk_readl(clk->reg);
+ unsigned long ret;
+
+ //TRACE;
+ if (reg & BIT(clk->bp_bit))
+ ret = prate; /* bypass */
+ else if (clk->div_width == DIV_A) {
+ ret = pa[clk->p[0]].rate;
+ //reg = clk_readl(clk->reg + 12); // G4.10 K_SDM_A
+ } else if (clk->div_width == DIV_TV) {
+ u32 m, r, reg2;
+
+ r = MASK_GET(7, 2, clk_readl(clk->reg + 4));
+ reg2 = clk_readl(clk->reg + 8);
+ m = MASK_GET(8, 7, reg2) + 1;
+
+ if (reg & BIT(1)) { /* SEL_FRA */
+ /* fractional divider */
+ u32 sdm = MASK_GET(2, 1, reg);
+ u32 ph = MASK_GET(4, 1, reg);
+ u32 nfra = MASK_GET(6, 7, reg);
+ const u32 *pp = pt[ph];
+
+ ret = prate >> r;
+ ret = (ret * (pp[1] + pp[2]) / pp[0]) -
+ (ret * (mods[sdm] - nfra) / mods[sdm] / pp[4]);
+ ret /= m;
+ } else {
+ /* integer divider */
+ u32 n = MASK_GET(0, 8, reg2) + 1;
+
+ ret = (prate / m * n) >> r;
+ }
+ } else {
+ u32 fbdiv = MASK_GET(clk->div_shift, clk->div_width, reg) + 1;
+
+ ret = clk->brate * fbdiv;
+ }
+ //pr_info("recalc_rate: %lu -> %lu\n", prate, ret);
+
+ return ret;
+}
+
+static int sp_pll_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long prate)
+{
+ struct sp_pll *clk = to_sp_pll(hw);
+ unsigned long flags;
+ u32 reg;
+
+ //TRACE;
+ pr_info("set_rate: %lu -> %lu\n", prate, rate);
+
+ spin_lock_irqsave(clk->lock, flags);
+
+ reg = BIT(clk->bp_bit + 16); /* HIWORD_MASK */
+
+ if (rate == prate)
+ reg |= BIT(clk->bp_bit); /* bypass */
+ else if (clk->div_width == DIV_A)
+ plla_set_rate(clk);
+ else if (clk->div_width == DIV_TV)
+ plltv_set_rate(clk);
+ else if (clk->div_width) {
+ u32 fbdiv = sp_pll_calc_div(clk, rate);
+
+ reg |= MASK_SET(clk->div_shift, clk->div_width, fbdiv - 1);
+ }
+
+ clk_writel(reg, clk->reg);
+
+ spin_unlock_irqrestore(clk->lock, flags);
+
+ return 0;
+}
+
+static int sp_pll_enable(struct clk_hw *hw)
+{
+ struct sp_pll *clk = to_sp_pll(hw);
+ unsigned long flags;
+
+ TRACE;
+ spin_lock_irqsave(clk->lock, flags);
+ clk_writel(BIT(clk->pd_bit + 16) | BIT(clk->pd_bit), clk->reg); /* power up */
+ spin_unlock_irqrestore(clk->lock, flags);
+
+ return 0;
+}
+
+static void sp_pll_disable(struct clk_hw *hw)
+{
+ struct sp_pll *clk = to_sp_pll(hw);
+ unsigned long flags;
+
+ TRACE;
+ spin_lock_irqsave(clk->lock, flags);
+ clk_writel(BIT(clk->pd_bit + 16), clk->reg); /* power down */
+ spin_unlock_irqrestore(clk->lock, flags);
+}
+
+static int sp_pll_is_enabled(struct clk_hw *hw)
+{
+ struct sp_pll *clk = to_sp_pll(hw);
+
+ return clk_readl(clk->reg) & BIT(clk->pd_bit);
+}
+
+static const struct clk_ops sp_pll_ops = {
+ .enable = sp_pll_enable,
+ .disable = sp_pll_disable,
+ .is_enabled = sp_pll_is_enabled,
+ .round_rate = sp_pll_round_rate,
+ .recalc_rate = sp_pll_recalc_rate,
+ .set_rate = sp_pll_set_rate
+};
+
+static const struct clk_ops sp_pll_sub_ops = {
+ .enable = sp_pll_enable,
+ .disable = sp_pll_disable,
+ .is_enabled = sp_pll_is_enabled,
+ .recalc_rate = sp_pll_recalc_rate,
+};
+
+struct clk *clk_register_sp_pll(const char *name, const char *parent,
+ void __iomem *reg, int pd_bit, int bp_bit,
+ unsigned long brate, int shift, int width,
+ spinlock_t *lock)
+{
+ struct sp_pll *pll;
+ struct clk *clk;
+ //unsigned long flags = 0;
+ struct clk_init_data initd = {
+ .name = name,
+ .parent_names = &parent,
+ .ops = (bp_bit >= 0) ? &sp_pll_ops : &sp_pll_sub_ops,
+ .num_parents = 1,
+ .flags = CLK_IGNORE_UNUSED
+ };
+
+ pll = kzalloc(sizeof(*pll), GFP_KERNEL);
+ if (!pll)
+ return ERR_PTR(-ENOMEM);
+
+ if (reg == PLLSYS_CTL)
+ initd.flags |= CLK_IS_CRITICAL;
+
+ pll->hw.init = &initd;
+ pll->reg = reg;
+ pll->pd_bit = pd_bit;
+ pll->bp_bit = bp_bit;
+ pll->brate = brate;
+ pll->div_shift = shift;
+ pll->div_width = width;
+ pll->lock = lock;
+
+ clk = clk_register(NULL, &pll->hw);
+ if (WARN_ON(IS_ERR(clk))) {
+ kfree(pll);
+ } else {
+ pr_info("%-20s%lu\n", name, clk_get_rate(clk));
+ clk_register_clkdev(clk, NULL, name);
+ }
+
+ return clk;
+}
+
+static void __init sp_clk_setup(struct device_node *np)
+{
+ int i, j;
+
+ pr_info("@@@ Sunplus clock init\n");
+
+ /* TODO: PLLs initial */
+
+ /* PLL_A */
+ clks[PLL_A] = clk_register_sp_pll("plla", EXTCLK,
+ PLLA_CTL, 11, 12, 27000000, 0, DIV_A, &plla_lock);
+
+ /* PLL_E */
+ clks[PLL_E] = clk_register_sp_pll("plle", EXTCLK,
+ PLLE_CTL, 6, 2, 50000000, 0, 0, &plle_lock);
+ clks[PLL_E_2P5] = clk_register_sp_pll("plle_2p5", "plle",
+ PLLE_CTL, 13, -1, 2500000, 0, 0, &plle_lock);
+ clks[PLL_E_25] = clk_register_sp_pll("plle_25", "plle",
+ PLLE_CTL, 12, -1, 25000000, 0, 0, &plle_lock);
+ clks[PLL_E_112P5] = clk_register_sp_pll("plle_112p5", "plle",
+ PLLE_CTL, 11, -1, 112500000, 0, 0, &plle_lock);
+
+ /* PLL_F */
+ clks[PLL_F] = clk_register_sp_pll("pllf", EXTCLK,
+ PLLF_CTL, 0, 10, 13500000, 1, 4, &pllf_lock);
+
+ /* PLL_TV */
+ clks[PLL_TV] = clk_register_sp_pll("plltv", EXTCLK,
+ PLLTV_CTL, 0, 15, 27000000, 0, DIV_TV, &plltv_lock);
+ clks[PLL_TV_A] = clk_register_divider(NULL, "plltv_a", "plltv", 0,
+ PLLTV_CTL + 4, 5, 1,
+ CLK_DIVIDER_POWER_OF_TWO, &plltv_lock);
+ clk_register_clkdev(clks[PLL_TV_A], NULL, "plltv_a");
+
+ /* PLL_SYS */
+ clks[PLL_SYS] = clk_register_sp_pll("pllsys", EXTCLK,
+ PLLSYS_CTL, 10, 9, 13500000, 0, 4, &pllsys_lock);
+
+ /* gates */
+ for (i = 0; i < ARRAY_SIZE(gates); i++) {
+ char s[10];
+
+ j = gates[i] & 0xffff;
+ sprintf(s, "clken%02x", j);
+ clks[j] = clk_register_gate(NULL, s, parents[gates[i] >> 16], CLK_IGNORE_UNUSED,
+ REG(0, j >> 4), j & 0x0f,
+ CLK_GATE_HIWORD_MASK, NULL);
+ //pr_info("%02x %px %px.%d\n", j, clks[j], REG(0, j >> 4), j & 0x0f);
+ }
+
+ clk_data.clks = clks;
+ clk_data.clk_num = ARRAY_SIZE(clks);
+ of_clk_add_provider(np, of_clk_src_onecell_get, &clk_data);
+}
+
+CLK_OF_DECLARE(sp_clkc, "sunplus,sp7021-clkc", sp_clk_setup);
+
+MODULE_AUTHOR("Qin Jian <qinjian@cqplus1.com>");
+MODULE_DESCRIPTION("Sunplus SP7021 Clock Driver");
+MODULE_LICENSE("GPL v2");
--
2.33.1
^ permalink raw reply related [flat|nested] 4+ messages in thread
end of thread, other threads:[~2022-08-22 8:41 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-08-17 7:01 [PATCH] clk: Add Sunplus SP7021 clock driver Qin Jian
2022-08-17 20:23 ` kernel test robot
2022-08-22 8:40 ` Dan Carpenter
-- strict thread matches above, loose matches on Subject: below --
2021-10-22 9:07 qinjian
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).