summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStephen Boyd <sboyd@kernel.org>2026-01-21 22:23:30 +0300
committerStephen Boyd <sboyd@kernel.org>2026-01-21 22:23:30 +0300
commitd5798ed98baa7691f7fbd90afd1ed03e0d62d79a (patch)
tree0841e1dbacb4499e9eabe91cecca8010bd1dde95
parent8f0b4cce4481fb22653697cced8d0d04027cb1e8 (diff)
parentbaf4fc7c03bd0f68c768cfe27829674bd060c6b4 (diff)
downloadlinux-d5798ed98baa7691f7fbd90afd1ed03e0d62d79a.tar.xz
Merge tag 'thead-clk-for-v6.20' of https://git.kernel.org/pub/scm/linux/kernel/git/fustini/linux into clk-thead
Pull T-HEAD clock driver updates from Drew Fustini: There is just one set of changes for thead this cycle. They add support for CPU scaling on the T-HEAD TH1520 by allowing the PLL rate used for the CPU cluster to be reconfigured. The changes have been tested in linux-next. Signed-off-by: Drew Fustini <fustini@kernel.org> * tag 'thead-clk-for-v6.20' of https://git.kernel.org/pub/scm/linux/kernel/git/fustini/linux: clk: thead: th1520-ap: Support CPU frequency scaling clk: thead: th1520-ap: Add macro to define multiplexers with flags clk: thead: th1520-ap: Support setting PLL rates clk: thead: th1520-ap: Add C910 bus clock clk: thead: th1520-ap: Poll for PLL lock and wait for stability dt-bindings: clock: thead,th1520-clk-ap: Add ID for C910 bus clock
-rw-r--r--drivers/clk/thead/clk-th1520-ap.c350
-rw-r--r--include/dt-bindings/clock/thead,th1520-clk-ap.h1
2 files changed, 344 insertions, 7 deletions
diff --git a/drivers/clk/thead/clk-th1520-ap.c b/drivers/clk/thead/clk-th1520-ap.c
index 71ad03a998e8..3a6847f1c950 100644
--- a/drivers/clk/thead/clk-th1520-ap.c
+++ b/drivers/clk/thead/clk-th1520-ap.c
@@ -7,22 +7,38 @@
#include <dt-bindings/clock/thead,th1520-clk-ap.h>
#include <linux/bitfield.h>
+#include <linux/clk.h>
#include <linux/clk-provider.h>
+#include <linux/delay.h>
#include <linux/device.h>
+#include <linux/minmax.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
+#define TH1520_PLL_STS 0x80
+
#define TH1520_PLL_POSTDIV2 GENMASK(26, 24)
#define TH1520_PLL_POSTDIV1 GENMASK(22, 20)
#define TH1520_PLL_FBDIV GENMASK(19, 8)
#define TH1520_PLL_REFDIV GENMASK(5, 0)
#define TH1520_PLL_BYPASS BIT(30)
#define TH1520_PLL_VCO_RST BIT(29)
+#define TH1520_PLL_DACPD BIT(25)
#define TH1520_PLL_DSMPD BIT(24)
#define TH1520_PLL_FRAC GENMASK(23, 0)
#define TH1520_PLL_FRAC_BITS 24
+/*
+ * All PLLs in TH1520 take 21250ns at maximum to lock, let's take its double
+ * for safety.
+ */
+#define TH1520_PLL_LOCK_TIMEOUT_US 44
+#define TH1520_PLL_STABLE_DELAY_US 30
+
+/* c910_bus_clk must be kept below 750MHz for stability */
+#define TH1520_C910_BUS_MAX_RATE (750 * 1000 * 1000)
+
struct ccu_internal {
u8 shift;
u8 width;
@@ -62,8 +78,19 @@ struct ccu_div {
struct ccu_common common;
};
+struct ccu_pll_cfg {
+ unsigned long freq;
+ u32 fbdiv;
+ u32 frac;
+ u32 postdiv1;
+ u32 postdiv2;
+};
+
struct ccu_pll {
struct ccu_common common;
+ u32 lock_sts_mask;
+ int cfgnum;
+ const struct ccu_pll_cfg *cfgs;
};
#define TH_CCU_ARG(_shift, _width) \
@@ -79,17 +106,22 @@ struct ccu_pll {
.flags = _flags, \
}
-#define TH_CCU_MUX(_name, _parents, _shift, _width) \
+#define TH_CCU_MUX_FLAGS(_name, _parents, _shift, _width, _flags, \
+ _mux_flags) \
{ \
.mask = GENMASK(_width - 1, 0), \
.shift = _shift, \
+ .flags = _mux_flags, \
.hw.init = CLK_HW_INIT_PARENTS_DATA( \
_name, \
_parents, \
&clk_mux_ops, \
- 0), \
+ _flags), \
}
+#define TH_CCU_MUX(_name, _parents, _shift, _width) \
+ TH_CCU_MUX_FLAGS(_name, _parents, _shift, _width, 0, 0)
+
#define CCU_GATE(_clkid, _struct, _name, _parent, _reg, _bit, _flags) \
struct ccu_gate _struct = { \
.clkid = _clkid, \
@@ -299,9 +331,21 @@ static void ccu_pll_disable(struct clk_hw *hw)
static int ccu_pll_enable(struct clk_hw *hw)
{
struct ccu_pll *pll = hw_to_ccu_pll(hw);
+ u32 reg;
+ int ret;
- return regmap_clear_bits(pll->common.map, pll->common.cfg1,
- TH1520_PLL_VCO_RST);
+ regmap_clear_bits(pll->common.map, pll->common.cfg1,
+ TH1520_PLL_VCO_RST);
+
+ ret = regmap_read_poll_timeout_atomic(pll->common.map, TH1520_PLL_STS,
+ reg, reg & pll->lock_sts_mask,
+ 5, TH1520_PLL_LOCK_TIMEOUT_US);
+ if (ret)
+ return ret;
+
+ udelay(TH1520_PLL_STABLE_DELAY_US);
+
+ return 0;
}
static int ccu_pll_is_enabled(struct clk_hw *hw)
@@ -368,17 +412,168 @@ static unsigned long ccu_pll_recalc_rate(struct clk_hw *hw,
return rate;
}
+static const struct ccu_pll_cfg *ccu_pll_lookup_best_cfg(struct ccu_pll *pll,
+ unsigned long rate)
+{
+ unsigned long best_delta = ULONG_MAX;
+ const struct ccu_pll_cfg *best_cfg;
+ int i;
+
+ for (i = 0; i < pll->cfgnum; i++) {
+ const struct ccu_pll_cfg *cfg = &pll->cfgs[i];
+ unsigned long delta;
+
+ delta = abs_diff(cfg->freq, rate);
+ if (delta < best_delta) {
+ best_delta = delta;
+ best_cfg = cfg;
+ }
+ }
+
+ return best_cfg;
+}
+
+static int ccu_pll_determine_rate(struct clk_hw *hw,
+ struct clk_rate_request *req)
+{
+ struct ccu_pll *pll = hw_to_ccu_pll(hw);
+
+ req->rate = ccu_pll_lookup_best_cfg(pll, req->rate)->freq;
+
+ return 0;
+}
+
+static int ccu_pll_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct ccu_pll *pll = hw_to_ccu_pll(hw);
+ const struct ccu_pll_cfg *cfg;
+
+ cfg = ccu_pll_lookup_best_cfg(pll, rate);
+
+ ccu_pll_disable(hw);
+
+ regmap_write(pll->common.map, pll->common.cfg0,
+ FIELD_PREP(TH1520_PLL_REFDIV, 1) |
+ FIELD_PREP(TH1520_PLL_FBDIV, cfg->fbdiv) |
+ FIELD_PREP(TH1520_PLL_POSTDIV1, cfg->postdiv1) |
+ FIELD_PREP(TH1520_PLL_POSTDIV2, cfg->postdiv2));
+
+ regmap_update_bits(pll->common.map, pll->common.cfg1,
+ TH1520_PLL_DACPD | TH1520_PLL_DSMPD |
+ TH1520_PLL_FRAC,
+ cfg->frac ? cfg->frac :
+ TH1520_PLL_DACPD | TH1520_PLL_DSMPD);
+
+ return ccu_pll_enable(hw);
+}
+
static const struct clk_ops clk_pll_ops = {
.disable = ccu_pll_disable,
.enable = ccu_pll_enable,
.is_enabled = ccu_pll_is_enabled,
.recalc_rate = ccu_pll_recalc_rate,
+ .determine_rate = ccu_pll_determine_rate,
+ .set_rate = ccu_pll_set_rate,
+};
+
+/*
+ * c910_clk could be reparented glitchlessly for DVFS. There are two parents,
+ * - c910_i0_clk, derived from cpu_pll0_clk or osc_24m.
+ * - cpu_pll1_clk, which provides the exact same set of rates as cpu_pll0_clk.
+ *
+ * During rate setting, always forward the request to the unused parent, and
+ * then switch c910_clk to it to avoid glitch.
+ */
+static u8 c910_clk_get_parent(struct clk_hw *hw)
+{
+ return clk_mux_ops.get_parent(hw);
+}
+
+static int c910_clk_set_parent(struct clk_hw *hw, u8 index)
+{
+ return clk_mux_ops.set_parent(hw, index);
+}
+
+static unsigned long c910_clk_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ return parent_rate;
+}
+
+static int c910_clk_determine_rate(struct clk_hw *hw,
+ struct clk_rate_request *req)
+{
+ u8 alt_parent_index = !c910_clk_get_parent(hw);
+ struct clk_hw *alt_parent;
+
+ alt_parent = clk_hw_get_parent_by_index(hw, alt_parent_index);
+
+ req->rate = clk_hw_round_rate(alt_parent, req->rate);
+ req->best_parent_hw = alt_parent;
+ req->best_parent_rate = req->rate;
+
+ return 0;
+}
+
+static int c910_clk_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ return -EOPNOTSUPP;
+}
+
+static int c910_clk_set_rate_and_parent(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate, u8 index)
+{
+ struct clk_hw *parent = clk_hw_get_parent_by_index(hw, index);
+
+ clk_set_rate(parent->clk, parent_rate);
+
+ c910_clk_set_parent(hw, index);
+
+ return 0;
+}
+
+static const struct clk_ops c910_clk_ops = {
+ .get_parent = c910_clk_get_parent,
+ .set_parent = c910_clk_set_parent,
+ .recalc_rate = c910_clk_recalc_rate,
+ .determine_rate = c910_clk_determine_rate,
+ .set_rate = c910_clk_set_rate,
+ .set_rate_and_parent = c910_clk_set_rate_and_parent,
};
static const struct clk_parent_data osc_24m_clk[] = {
{ .index = 0 }
};
+static const struct ccu_pll_cfg cpu_pll_cfgs[] = {
+ { 125000000, 125, 0, 6, 4 },
+ { 200000000, 125, 0, 5, 3 },
+ { 300000000, 125, 0, 5, 2 },
+ { 400000000, 100, 0, 3, 2 },
+ { 500000000, 125, 0, 6, 1 },
+ { 600000000, 125, 0, 5, 1 },
+ { 702000000, 117, 0, 4, 1 },
+ { 800000000, 100, 0, 3, 1 },
+ { 900000000, 75, 0, 2, 1 },
+ { 1000000000, 125, 0, 3, 1 },
+ { 1104000000, 92, 0, 2, 1 },
+ { 1200000000, 100, 0, 2, 1 },
+ { 1296000000, 108, 0, 2, 1 },
+ { 1404000000, 117, 0, 2, 1 },
+ { 1500000000, 125, 0, 2, 1 },
+ { 1608000000, 67, 0, 1, 1 },
+ { 1704000000, 71, 0, 1, 1 },
+ { 1800000000, 75, 0, 1, 1 },
+ { 1896000000, 79, 0, 1, 1 },
+ { 1992000000, 83, 0, 1, 1 },
+ { 2112000000, 88, 0, 1, 1 },
+ { 2208000000, 92, 0, 1, 1 },
+ { 2304000000, 96, 0, 1, 1 },
+ { 2400000000, 100, 0, 1, 1 },
+};
+
static struct ccu_pll cpu_pll0_clk = {
.common = {
.clkid = CLK_CPU_PLL0,
@@ -389,6 +584,9 @@ static struct ccu_pll cpu_pll0_clk = {
&clk_pll_ops,
CLK_IS_CRITICAL),
},
+ .lock_sts_mask = BIT(1),
+ .cfgnum = ARRAY_SIZE(cpu_pll_cfgs),
+ .cfgs = cpu_pll_cfgs,
};
static struct ccu_pll cpu_pll1_clk = {
@@ -401,6 +599,17 @@ static struct ccu_pll cpu_pll1_clk = {
&clk_pll_ops,
CLK_IS_CRITICAL),
},
+ .lock_sts_mask = BIT(4),
+ .cfgnum = ARRAY_SIZE(cpu_pll_cfgs),
+ .cfgs = cpu_pll_cfgs,
+};
+
+static const struct ccu_pll_cfg gmac_pll_cfg = {
+ .freq = 1000000000,
+ .fbdiv = 125,
+ .frac = 0,
+ .postdiv1 = 3,
+ .postdiv2 = 1,
};
static struct ccu_pll gmac_pll_clk = {
@@ -413,6 +622,9 @@ static struct ccu_pll gmac_pll_clk = {
&clk_pll_ops,
CLK_IS_CRITICAL),
},
+ .lock_sts_mask = BIT(3),
+ .cfgnum = 1,
+ .cfgs = &gmac_pll_cfg,
};
static const struct clk_hw *gmac_pll_clk_parent[] = {
@@ -423,6 +635,14 @@ static const struct clk_parent_data gmac_pll_clk_pd[] = {
{ .hw = &gmac_pll_clk.common.hw }
};
+static const struct ccu_pll_cfg video_pll_cfg = {
+ .freq = 792000000,
+ .fbdiv = 99,
+ .frac = 0,
+ .postdiv1 = 3,
+ .postdiv2 = 1,
+};
+
static struct ccu_pll video_pll_clk = {
.common = {
.clkid = CLK_VIDEO_PLL,
@@ -433,6 +653,9 @@ static struct ccu_pll video_pll_clk = {
&clk_pll_ops,
CLK_IS_CRITICAL),
},
+ .lock_sts_mask = BIT(7),
+ .cfgnum = 1,
+ .cfgs = &video_pll_cfg,
};
static const struct clk_hw *video_pll_clk_parent[] = {
@@ -443,6 +666,14 @@ static const struct clk_parent_data video_pll_clk_pd[] = {
{ .hw = &video_pll_clk.common.hw }
};
+static const struct ccu_pll_cfg dpu_pll_cfg = {
+ .freq = 1188000000,
+ .fbdiv = 99,
+ .frac = 0,
+ .postdiv1 = 2,
+ .postdiv2 = 1,
+};
+
static struct ccu_pll dpu0_pll_clk = {
.common = {
.clkid = CLK_DPU0_PLL,
@@ -453,6 +684,9 @@ static struct ccu_pll dpu0_pll_clk = {
&clk_pll_ops,
0),
},
+ .lock_sts_mask = BIT(8),
+ .cfgnum = 1,
+ .cfgs = &dpu_pll_cfg,
};
static const struct clk_hw *dpu0_pll_clk_parent[] = {
@@ -469,12 +703,23 @@ static struct ccu_pll dpu1_pll_clk = {
&clk_pll_ops,
0),
},
+ .lock_sts_mask = BIT(9),
+ .cfgnum = 1,
+ .cfgs = &dpu_pll_cfg,
};
static const struct clk_hw *dpu1_pll_clk_parent[] = {
&dpu1_pll_clk.common.hw
};
+static const struct ccu_pll_cfg tee_pll_cfg = {
+ .freq = 792000000,
+ .fbdiv = 99,
+ .frac = 0,
+ .postdiv1 = 3,
+ .postdiv2 = 1,
+};
+
static struct ccu_pll tee_pll_clk = {
.common = {
.clkid = CLK_TEE_PLL,
@@ -485,6 +730,9 @@ static struct ccu_pll tee_pll_clk = {
&clk_pll_ops,
CLK_IS_CRITICAL),
},
+ .lock_sts_mask = BIT(10),
+ .cfgnum = 1,
+ .cfgs = &tee_pll_cfg,
};
static const struct clk_parent_data c910_i0_parents[] = {
@@ -495,7 +743,8 @@ static const struct clk_parent_data c910_i0_parents[] = {
static struct ccu_mux c910_i0_clk = {
.clkid = CLK_C910_I0,
.reg = 0x100,
- .mux = TH_CCU_MUX("c910-i0", c910_i0_parents, 1, 1),
+ .mux = TH_CCU_MUX_FLAGS("c910-i0", c910_i0_parents, 1, 1,
+ CLK_SET_RATE_PARENT, CLK_MUX_ROUND_CLOSEST),
};
static const struct clk_parent_data c910_parents[] = {
@@ -506,7 +755,28 @@ static const struct clk_parent_data c910_parents[] = {
static struct ccu_mux c910_clk = {
.clkid = CLK_C910,
.reg = 0x100,
- .mux = TH_CCU_MUX("c910", c910_parents, 0, 1),
+ .mux = {
+ .mask = BIT(0),
+ .shift = 0,
+ .hw.init = CLK_HW_INIT_PARENTS_DATA("c910",
+ c910_parents,
+ &c910_clk_ops,
+ CLK_SET_RATE_PARENT),
+ },
+};
+
+static struct ccu_div c910_bus_clk = {
+ .enable = BIT(7),
+ .div_en = BIT(11),
+ .div = TH_CCU_DIV_FLAGS(8, 3, 0),
+ .common = {
+ .clkid = CLK_C910_BUS,
+ .cfg0 = 0x100,
+ .hw.init = CLK_HW_INIT_HW("c910-bus",
+ &c910_clk.mux.hw,
+ &ccu_div_ops,
+ CLK_IS_CRITICAL),
+ },
};
static const struct clk_parent_data ahb2_cpusys_parents[] = {
@@ -1021,6 +1291,7 @@ static struct ccu_common *th1520_pll_clks[] = {
};
static struct ccu_common *th1520_div_clks[] = {
+ &c910_bus_clk.common,
&ahb2_cpusys_hclk.common,
&apb3_cpusys_pclk.common,
&axi4_cpusys2_aclk.common,
@@ -1164,7 +1435,7 @@ static const struct th1520_plat_data th1520_ap_platdata = {
.th1520_mux_clks = th1520_mux_clks,
.th1520_gate_clks = th1520_gate_clks,
- .nr_clks = CLK_UART_SCLK + 1,
+ .nr_clks = CLK_C910_BUS + 1,
.nr_pll_clks = ARRAY_SIZE(th1520_pll_clks),
.nr_div_clks = ARRAY_SIZE(th1520_div_clks),
@@ -1180,11 +1451,69 @@ static const struct th1520_plat_data th1520_vo_platdata = {
.nr_gate_clks = ARRAY_SIZE(th1520_vo_gate_clks),
};
+/*
+ * Maintain clock rate of c910_bus_clk below TH1520_C910_BUS_MAX_RATE (750MHz)
+ * when its parent, c910_clk, changes the rate.
+ *
+ * Additionally, TRM is unclear about c910_bus_clk behavior when the divisor is
+ * set below 2, thus we should ensure the new divisor stays in (2, MAXDIVISOR).
+ */
+static unsigned long c910_bus_clk_divisor(struct ccu_div *cd,
+ unsigned long parent_rate)
+{
+ return clamp(DIV_ROUND_UP(parent_rate, TH1520_C910_BUS_MAX_RATE),
+ 2U, 1U << cd->div.width);
+}
+
+static int c910_clk_notifier_cb(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct clk_notifier_data *cnd = data;
+ unsigned long new_divisor, ref_rate;
+
+ if (action != PRE_RATE_CHANGE && action != POST_RATE_CHANGE)
+ return NOTIFY_DONE;
+
+ new_divisor = c910_bus_clk_divisor(&c910_bus_clk, cnd->new_rate);
+
+ if (cnd->new_rate > cnd->old_rate) {
+ /*
+ * Scaling up. Adjust c910_bus_clk divisor
+ * - before c910_clk rate change to ensure the constraints
+ * aren't broken after scaling to higher rates,
+ * - after c910_clk rate change to keep c910_bus_clk as high as
+ * possible
+ */
+ ref_rate = action == PRE_RATE_CHANGE ?
+ cnd->old_rate : cnd->new_rate;
+ clk_set_rate(c910_bus_clk.common.hw.clk,
+ ref_rate / new_divisor);
+ } else if (cnd->new_rate < cnd->old_rate &&
+ action == POST_RATE_CHANGE) {
+ /*
+ * Scaling down. Adjust c910_bus_clk divisor only after
+ * c910_clk rate change to keep c910_bus_clk as high as
+ * possible, Scaling down never breaks the constraints.
+ */
+ clk_set_rate(c910_bus_clk.common.hw.clk,
+ cnd->new_rate / new_divisor);
+ } else {
+ return NOTIFY_DONE;
+ }
+
+ return NOTIFY_OK;
+}
+
+static struct notifier_block c910_clk_notifier = {
+ .notifier_call = c910_clk_notifier_cb,
+};
+
static int th1520_clk_probe(struct platform_device *pdev)
{
const struct th1520_plat_data *plat_data;
struct device *dev = &pdev->dev;
struct clk_hw_onecell_data *priv;
+ struct clk *notifier_clk;
struct regmap *map;
void __iomem *base;
@@ -1271,6 +1600,13 @@ static int th1520_clk_probe(struct platform_device *pdev)
ret = devm_clk_hw_register(dev, &emmc_sdio_ref_clk.hw);
if (ret)
return ret;
+
+ notifier_clk = devm_clk_hw_get_clk(dev, &c910_clk.mux.hw,
+ "dvfs");
+ ret = devm_clk_notifier_register(dev, notifier_clk,
+ &c910_clk_notifier);
+ if (ret)
+ return ret;
}
ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, priv);
diff --git a/include/dt-bindings/clock/thead,th1520-clk-ap.h b/include/dt-bindings/clock/thead,th1520-clk-ap.h
index 09a9aa7b3ab1..68b35cc61204 100644
--- a/include/dt-bindings/clock/thead,th1520-clk-ap.h
+++ b/include/dt-bindings/clock/thead,th1520-clk-ap.h
@@ -93,6 +93,7 @@
#define CLK_SRAM3 83
#define CLK_PLL_GMAC_100M 84
#define CLK_UART_SCLK 85
+#define CLK_C910_BUS 86
/* VO clocks */
#define CLK_AXI4_VO_ACLK 0