From: Pablo Mazzini <pmazzini@gmail.com>
To: Brian Masney <bmasney+clk@redhat.com>,
Chen-Yu Tsai <wens@kernel.org>,
Jernej Skrabec <jernej.skrabec@gmail.com>,
Jerome Brunet <jbrunet+clk@baylibre.com>,
Samuel Holland <samuel@sholland.org>,
Stephen Boyd <sboyd@kernel.org>
Cc: Pablo Mazzini <pmazzini@gmail.com>,
Andre Przywara <andre.przywara@arm.com>,
linux-arm-kernel@lists.infradead.org, linux-clk@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-sunxi@lists.linux.dev
Subject: [PATCH 2/3] clk: sunxi-ng: add a table front end
Date: Sun, 8 Nov 2026 15:57:00 +0000 [thread overview]
Message-ID: <20261108155702.361022-3-pmazzini@gmail.com> (raw)
In-Reply-To: <20261108155702.361022-1-pmazzini@gmail.com>
Let a SoC describe its CCU as one table indexed by DT clock ID, instead
of a structure per clock plus the ccu_clks and hw_clks lists and a reset
table. The core builds the usual ccu_* clocks from it at probe time,
derives each bus gate's reset from the generation's register layout, and
provides the clocks that a generation shares as templates.
Only the A31 generation is described for now.
Signed-off-by: Pablo Mazzini <pmazzini@gmail.com>
---
drivers/clk/sunxi-ng/Makefile | 1 +
drivers/clk/sunxi-ng/ccu_table.c | 510 +++++++++++++++++++++++++++++++
drivers/clk/sunxi-ng/ccu_table.h | 233 ++++++++++++++
3 files changed, 744 insertions(+)
create mode 100644 drivers/clk/sunxi-ng/ccu_table.c
create mode 100644 drivers/clk/sunxi-ng/ccu_table.h
diff --git a/drivers/clk/sunxi-ng/Makefile b/drivers/clk/sunxi-ng/Makefile
index a1c4087d7241..796f443f4d76 100644
--- a/drivers/clk/sunxi-ng/Makefile
+++ b/drivers/clk/sunxi-ng/Makefile
@@ -6,6 +6,7 @@ obj-$(CONFIG_SUNXI_CCU) += sunxi-ccu.o
sunxi-ccu-y += ccu_common.o
sunxi-ccu-y += ccu_mmc_timing.o
sunxi-ccu-y += ccu_reset.o
+sunxi-ccu-y += ccu_table.o
# Base clock types
sunxi-ccu-y += ccu_div.o
diff --git a/drivers/clk/sunxi-ng/ccu_table.c b/drivers/clk/sunxi-ng/ccu_table.c
new file mode 100644
index 000000000000..7be783e623f7
--- /dev/null
+++ b/drivers/clk/sunxi-ng/ccu_table.c
@@ -0,0 +1,510 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Table front end for the sunxi-ng clock types.
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/slab.h>
+
+#include "ccu_common.h"
+#include "ccu_div.h"
+#include "ccu_gate.h"
+#include "ccu_mp.h"
+#include "ccu_mult.h"
+#include "ccu_mux.h"
+#include "ccu_nk.h"
+#include "ccu_nkmp.h"
+#include "ccu_nm.h"
+#include "ccu_phase.h"
+#include "ccu_table.h"
+
+/*
+ * A31 generation (A31, A23/A33, A83T, H3/H5, R40, V3s, A64, ...): a bus
+ * gate's reset is the same bit of a paired register.
+ */
+static const struct ccu_tbl_rst_pair ccu_tbl_a31_rst_pairs[] = {
+ { 0x060, 0x2c0 },
+ { 0x064, 0x2c4 },
+ { 0x068, 0x2d0 },
+ { 0x06c, 0x2d8 },
+};
+
+static struct ccu_sdm_setting ccu_tbl_a31_audio_sdm_table[] = {
+ { .rate = 22579200, .pattern = 0xc0010d84, .m = 8, .n = 7 },
+ { .rate = 24576000, .pattern = 0xc000ac02, .m = 14, .n = 14 },
+};
+
+static const struct ccu_sdm_internal ccu_tbl_a31_audio_sdm =
+ _SUNXI_CCU_SDM(ccu_tbl_a31_audio_sdm_table, BIT(24), 0x284, BIT(31));
+
+/* AHB1 divides PLL_PERIPH0 by a pre-divider at [7:6] */
+static const struct ccu_mux_var_prediv ccu_tbl_a31_ahb1_predivs[] = {
+ { .index = 3, .shift = 6, .width = 2 },
+};
+
+static struct clk_div_table ccu_tbl_a31_apb1_div_table[] = {
+ { .val = 0, .div = 2 },
+ { .val = 1, .div = 2 },
+ { .val = 2, .div = 4 },
+ { .val = 3, .div = 8 },
+ { /* Sentinel */ },
+};
+
+static const struct ccu_tbl_clk ccu_tbl_a31_std[CCU_TBL_STD_NUM] = {
+ [CCU_TBL_STD_PLL_CPU] = {
+ .type = CCU_TBL_TYPE_PLL_NKMP,
+ .reg = 0x000,
+ .n = { 8, 5 },
+ .k = { 4, 2 },
+ .m = { 0, 2 },
+ .p = { 16, 2, 0, 4 },
+ },
+ [CCU_TBL_STD_PLL_AUDIO] = {
+ .type = CCU_TBL_TYPE_PLL_NM,
+ .reg = 0x008,
+ .n = { 8, 7 },
+ .m = { 0, 5 },
+ .sdm = &ccu_tbl_a31_audio_sdm,
+ /* the sigma-delta table assumes the P divider is 1 */
+ .init_clear = GENMASK(19, 16),
+ },
+ [CCU_TBL_STD_PLL_VIDEO] = {
+ .type = CCU_TBL_TYPE_PLL_NM,
+ .n = { 8, 7 },
+ .m = { 0, 4 },
+ .frac = true,
+ },
+ [CCU_TBL_STD_CPUX] = {
+ .type = CCU_TBL_TYPE_MUX,
+ .reg = 0x050,
+ .mux = { 16, 2 },
+ },
+ [CCU_TBL_STD_AXI] = {
+ .type = CCU_TBL_TYPE_DIV,
+ .reg = 0x050,
+ .m = { 0, 2 },
+ },
+ [CCU_TBL_STD_AHB1] = {
+ .type = CCU_TBL_TYPE_DIV,
+ .reg = 0x054,
+ .m = { 4, 2 },
+ .m_flags = CLK_DIVIDER_POWER_OF_TWO,
+ .mux = { 12, 2 },
+ .var_predivs = ccu_tbl_a31_ahb1_predivs,
+ .n_var_predivs = ARRAY_SIZE(ccu_tbl_a31_ahb1_predivs),
+ },
+ [CCU_TBL_STD_APB1] = {
+ .type = CCU_TBL_TYPE_DIV,
+ .reg = 0x054,
+ .m = { 8, 2 },
+ .m_table = ccu_tbl_a31_apb1_div_table,
+ },
+ [CCU_TBL_STD_APB2] = {
+ .type = CCU_TBL_TYPE_MP,
+ .reg = 0x058,
+ .m = { 0, 5 },
+ .p = { 16, 2 },
+ .mux = { 24, 2 },
+ },
+};
+
+const struct ccu_tbl_gen ccu_tbl_gen_a31 = {
+ .rst_pairs = ccu_tbl_a31_rst_pairs,
+ .num_rst_pairs = ARRAY_SIZE(ccu_tbl_a31_rst_pairs),
+ .mod_m = { 0, 4 },
+ .mod_p = { 16, 2 },
+ .mod_mux = { 24, 2 },
+ .mod_gate = BIT(31),
+ .pll_enable = BIT(31),
+ .pll_lock = BIT(28),
+ .pll_frac = _SUNXI_CCU_FRAC(BIT(24), BIT(25), 270000000, 297000000),
+ .std = ccu_tbl_a31_std,
+};
+EXPORT_SYMBOL_NS_GPL(ccu_tbl_gen_a31, "SUNXI_CCU");
+
+static struct ccu_mult_internal ccu_tbl_mult(const struct ccu_tbl_field *f)
+{
+ return (struct ccu_mult_internal)
+ _SUNXI_CCU_MULT_OFFSET_MIN_MAX(f->shift, f->width, 1,
+ f->min ?: 1, 0);
+}
+
+static struct ccu_div_internal ccu_tbl_div(const struct ccu_tbl_field *f)
+{
+ return (struct ccu_div_internal)
+ _SUNXI_CCU_DIV_MAX(f->shift, f->width, f->max);
+}
+
+static struct ccu_mux_internal ccu_tbl_mux(const struct ccu_tbl_field *f)
+{
+ return (struct ccu_mux_internal)_SUNXI_CCU_MUX(f->shift, f->width);
+}
+
+/* Expand a template entry into a full one */
+static const struct ccu_tbl_clk *ccu_tbl_resolve(const struct ccu_tbl_gen *gen,
+ const struct ccu_tbl_clk *c,
+ struct ccu_tbl_clk *buf)
+{
+ if (c->type != CCU_TBL_TYPE_STD)
+ return c;
+
+ if (c->std >= CCU_TBL_STD_NUM || !gen->std || !gen->std[c->std].type)
+ return NULL;
+
+ *buf = gen->std[c->std];
+ buf->name = c->name;
+ buf->parents = c->parents;
+ buf->parent = c->parent;
+ buf->flags = c->flags;
+ if (c->reg_set)
+ buf->reg = c->reg;
+
+ return buf;
+}
+
+static struct ccu_common *ccu_tbl_alloc_ccu(struct device *dev,
+ const struct ccu_tbl_gen *gen,
+ const struct ccu_tbl_clk *c,
+ const struct clk_ops **ops)
+{
+ struct ccu_phase *phase;
+ struct ccu_nkmp *nkmp;
+ struct ccu_gate *gate;
+ struct ccu_div *div;
+ struct ccu_mux *mux;
+ struct ccu_mp *mp;
+ struct ccu_nk *nk;
+ struct ccu_nm *nm;
+
+ switch (c->type) {
+ case CCU_TBL_TYPE_GATE:
+ gate = devm_kzalloc(dev, sizeof(*gate), GFP_KERNEL);
+ if (!gate)
+ return ERR_PTR(-ENOMEM);
+ gate->enable = c->gate;
+ *ops = &ccu_gate_ops;
+ return &gate->common;
+
+ case CCU_TBL_TYPE_DIV:
+ div = devm_kzalloc(dev, sizeof(*div), GFP_KERNEL);
+ if (!div)
+ return ERR_PTR(-ENOMEM);
+ div->enable = c->gate;
+ if (c->m_table)
+ div->div = (struct ccu_div_internal)
+ _SUNXI_CCU_DIV_TABLE_FLAGS(c->m.shift, c->m.width,
+ c->m_table,
+ c->m_flags);
+ else
+ div->div = (struct ccu_div_internal)
+ _SUNXI_CCU_DIV_FLAGS(c->m.shift, c->m.width,
+ c->m_flags);
+ div->mux = ccu_tbl_mux(&c->mux);
+ if (c->n_var_predivs) {
+ div->mux.var_predivs = c->var_predivs;
+ div->mux.n_var_predivs = c->n_var_predivs;
+ div->common.features = CCU_FEATURE_VARIABLE_PREDIV;
+ }
+ *ops = &ccu_div_ops;
+ return &div->common;
+
+ case CCU_TBL_TYPE_MUX:
+ mux = devm_kzalloc(dev, sizeof(*mux), GFP_KERNEL);
+ if (!mux)
+ return ERR_PTR(-ENOMEM);
+ mux->enable = c->gate;
+ mux->mux = ccu_tbl_mux(&c->mux);
+ *ops = &ccu_mux_ops;
+ return &mux->common;
+
+ case CCU_TBL_TYPE_MP:
+ case CCU_TBL_TYPE_MOD: {
+ bool std = c->type == CCU_TBL_TYPE_MOD;
+
+ mp = devm_kzalloc(dev, sizeof(*mp), GFP_KERNEL);
+ if (!mp)
+ return ERR_PTR(-ENOMEM);
+ mp->enable = std ? gen->mod_gate : c->gate;
+ mp->m = ccu_tbl_div(std ? &gen->mod_m : &c->m);
+ mp->p = ccu_tbl_div(std ? &gen->mod_p : &c->p);
+ mp->mux = ccu_tbl_mux(std ? &gen->mod_mux : &c->mux);
+ if (c->postdiv) {
+ mp->fixed_post_div = c->postdiv;
+ mp->common.features = CCU_FEATURE_FIXED_POSTDIV;
+ }
+ *ops = &ccu_mp_ops;
+ return &mp->common;
+ }
+
+ case CCU_TBL_TYPE_PHASE:
+ phase = devm_kzalloc(dev, sizeof(*phase), GFP_KERNEL);
+ if (!phase)
+ return ERR_PTR(-ENOMEM);
+ phase->shift = c->m.shift;
+ phase->width = c->m.width;
+ *ops = &ccu_phase_ops;
+ return &phase->common;
+
+ case CCU_TBL_TYPE_PLL_NK:
+ nk = devm_kzalloc(dev, sizeof(*nk), GFP_KERNEL);
+ if (!nk)
+ return ERR_PTR(-ENOMEM);
+ nk->enable = gen->pll_enable;
+ nk->lock = gen->pll_lock;
+ nk->n = ccu_tbl_mult(&c->n);
+ nk->k = ccu_tbl_mult(&c->k);
+ if (c->postdiv) {
+ nk->fixed_post_div = c->postdiv;
+ nk->common.features = CCU_FEATURE_FIXED_POSTDIV;
+ }
+ *ops = &ccu_nk_ops;
+ return &nk->common;
+
+ case CCU_TBL_TYPE_PLL_NM:
+ nm = devm_kzalloc(dev, sizeof(*nm), GFP_KERNEL);
+ if (!nm)
+ return ERR_PTR(-ENOMEM);
+ nm->enable = gen->pll_enable;
+ nm->lock = gen->pll_lock;
+ nm->n = ccu_tbl_mult(&c->n);
+ nm->m = ccu_tbl_div(&c->m);
+ if (c->frac) {
+ nm->frac = gen->pll_frac;
+ nm->common.features = CCU_FEATURE_FRACTIONAL;
+ }
+ if (c->sdm) {
+ nm->sdm = *c->sdm;
+ nm->common.features = CCU_FEATURE_SIGMA_DELTA_MOD;
+ }
+ *ops = &ccu_nm_ops;
+ return &nm->common;
+
+ case CCU_TBL_TYPE_PLL_NKMP:
+ nkmp = devm_kzalloc(dev, sizeof(*nkmp), GFP_KERNEL);
+ if (!nkmp)
+ return ERR_PTR(-ENOMEM);
+ nkmp->enable = gen->pll_enable;
+ nkmp->lock = gen->pll_lock;
+ nkmp->n = ccu_tbl_mult(&c->n);
+ nkmp->k = ccu_tbl_mult(&c->k);
+ nkmp->m = ccu_tbl_div(&c->m);
+ nkmp->p = ccu_tbl_div(&c->p);
+ *ops = &ccu_nkmp_ops;
+ return &nkmp->common;
+ }
+
+ return ERR_PTR(-EINVAL);
+}
+
+static struct clk_hw *ccu_tbl_alloc(struct device *dev,
+ const struct ccu_tbl_gen *gen,
+ const struct ccu_tbl_clk *c,
+ struct ccu_common **common,
+ const struct clk_ops **ops)
+{
+ struct clk_fixed_factor *ff;
+ struct clk_fixed_rate *fr;
+
+ *common = NULL;
+
+ switch (c->type) {
+ case CCU_TBL_TYPE_FIXED_RATE:
+ fr = devm_kzalloc(dev, sizeof(*fr), GFP_KERNEL);
+ if (!fr)
+ return ERR_PTR(-ENOMEM);
+ fr->fixed_rate = c->rate;
+ *ops = &clk_fixed_rate_ops;
+ return &fr->hw;
+
+ case CCU_TBL_TYPE_FIXED_FACTOR:
+ ff = devm_kzalloc(dev, sizeof(*ff), GFP_KERNEL);
+ if (!ff)
+ return ERR_PTR(-ENOMEM);
+ ff->mult = c->mult;
+ ff->div = c->div;
+ *ops = &clk_fixed_factor_ops;
+ return &ff->hw;
+ }
+
+ *common = ccu_tbl_alloc_ccu(dev, gen, c, ops);
+ if (IS_ERR(*common))
+ return ERR_CAST(*common);
+
+ (*common)->reg = c->reg;
+
+ return &(*common)->hw;
+}
+
+static int ccu_tbl_set_parents(struct device *dev,
+ const struct sunxi_ccu_table *tbl,
+ const struct ccu_tbl_clk *c,
+ struct clk_hw **hws, struct clk_init_data *init)
+{
+ const u16 *ids = c->parents ? c->parents->ids : &c->parent;
+ unsigned int i, num = c->parents ? c->parents->num : 1;
+ struct clk_parent_data *pd;
+
+ pd = devm_kcalloc(dev, num, sizeof(*pd), GFP_KERNEL);
+ if (!pd)
+ return -ENOMEM;
+
+ for (i = 0; i < num; i++) {
+ switch (ids[i]) {
+ case CCU_TBL_HOSC:
+ pd[i].fw_name = "hosc";
+ break;
+ case CCU_TBL_LOSC:
+ pd[i].fw_name = "losc";
+ break;
+ case CCU_TBL_IOSC:
+ pd[i].fw_name = "iosc";
+ break;
+ default:
+ if (ids[i] >= tbl->num_clks || !hws[ids[i]]) {
+ dev_err(dev, "%s: invalid parent %u\n",
+ c->name, ids[i]);
+ return -EINVAL;
+ }
+ pd[i].hw = hws[ids[i]];
+ }
+ }
+
+ init->parent_data = pd;
+ init->num_parents = num;
+
+ return 0;
+}
+
+static struct ccu_reset_map *ccu_tbl_resets(struct device *dev,
+ const struct sunxi_ccu_table *tbl)
+{
+ const struct ccu_tbl_gen *gen = tbl->gen;
+ struct ccu_reset_map *map;
+ unsigned int i, j;
+
+ map = devm_kcalloc(dev, tbl->num_resets, sizeof(*map), GFP_KERNEL);
+ if (!map)
+ return ERR_PTR(-ENOMEM);
+ if (tbl->resets)
+ memcpy(map, tbl->resets, tbl->num_resets * sizeof(*map));
+
+ for (i = 0; i < tbl->num_clks; i++) {
+ const struct ccu_tbl_clk *c = &tbl->clks[i];
+
+ if (c->type != CCU_TBL_TYPE_GATE || c->rst == CCU_TBL_NO_RST)
+ continue;
+
+ for (j = 0; j < gen->num_rst_pairs; j++)
+ if (gen->rst_pairs[j].gate_reg == c->reg)
+ break;
+
+ if (c->rst >= tbl->num_resets || j == gen->num_rst_pairs ||
+ map[c->rst].bit) {
+ dev_err(dev, "%s: invalid reset %u\n", c->name, c->rst);
+ return ERR_PTR(-EINVAL);
+ }
+
+ map[c->rst].reg = gen->rst_pairs[j].rst_reg;
+ map[c->rst].bit = c->gate;
+ }
+
+ return map;
+}
+
+int devm_sunxi_ccu_table_probe(struct device *dev, void __iomem *reg,
+ const struct sunxi_ccu_table *tbl)
+{
+ struct clk_hw_onecell_data *hw_clks;
+ struct ccu_common **ccu_clks;
+ struct clk_init_data *inits;
+ struct sunxi_ccu_desc *desc;
+ struct ccu_reset_map *resets;
+ unsigned int i, n = 0;
+ int ret;
+
+ desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL);
+ hw_clks = devm_kzalloc(dev, struct_size(hw_clks, hws, tbl->num_clks),
+ GFP_KERNEL);
+ ccu_clks = devm_kcalloc(dev, tbl->num_clks, sizeof(*ccu_clks),
+ GFP_KERNEL);
+ inits = devm_kcalloc(dev, tbl->num_clks, sizeof(*inits), GFP_KERNEL);
+ if (!desc || !hw_clks || !ccu_clks || !inits)
+ return -ENOMEM;
+
+ hw_clks->num = tbl->num_clks;
+
+ /* Create all clocks first, so that parents can be looked up by ID */
+ for (i = 0; i < tbl->num_clks; i++) {
+ struct ccu_common *common;
+ const struct ccu_tbl_clk *c;
+ struct ccu_tbl_clk buf;
+ struct clk_hw *hw;
+
+ if (!tbl->clks[i].type)
+ continue;
+
+ c = ccu_tbl_resolve(tbl->gen, &tbl->clks[i], &buf);
+ if (!c) {
+ dev_err(dev, "%s: no such template\n",
+ tbl->clks[i].name);
+ return -EINVAL;
+ }
+
+ hw = ccu_tbl_alloc(dev, tbl->gen, c, &common, &inits[i].ops);
+ if (IS_ERR(hw))
+ return PTR_ERR(hw);
+
+ if (c->init_clear)
+ writel(readl(reg + c->reg) & ~c->init_clear,
+ reg + c->reg);
+
+ inits[i].name = c->name;
+ inits[i].flags = c->flags;
+ hw->init = &inits[i];
+
+ hw_clks->hws[i] = hw;
+ if (common)
+ ccu_clks[n++] = common;
+ }
+
+ for (i = 0; i < tbl->num_clks; i++) {
+ if (!hw_clks->hws[i])
+ continue;
+
+ ret = ccu_tbl_set_parents(dev, tbl, &tbl->clks[i],
+ hw_clks->hws, &inits[i]);
+ if (ret)
+ return ret;
+ }
+
+ resets = ccu_tbl_resets(dev, tbl);
+ if (IS_ERR(resets))
+ return PTR_ERR(resets);
+
+ desc->ccu_clks = ccu_clks;
+ desc->num_ccu_clks = n;
+ desc->hw_clks = hw_clks;
+ desc->resets = resets;
+ desc->num_resets = tbl->num_resets;
+
+ return devm_sunxi_ccu_probe(dev, reg, desc);
+}
+EXPORT_SYMBOL_NS_GPL(devm_sunxi_ccu_table_probe, "SUNXI_CCU");
+
+/* Probe for a CCU platform device whose match data is its table */
+int sunxi_ccu_table_platform_probe(struct platform_device *pdev)
+{
+ void __iomem *reg;
+
+ reg = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(reg))
+ return PTR_ERR(reg);
+
+ return devm_sunxi_ccu_table_probe(&pdev->dev, reg,
+ device_get_match_data(&pdev->dev));
+}
+EXPORT_SYMBOL_NS_GPL(sunxi_ccu_table_platform_probe, "SUNXI_CCU");
diff --git a/drivers/clk/sunxi-ng/ccu_table.h b/drivers/clk/sunxi-ng/ccu_table.h
new file mode 100644
index 000000000000..8ceaff55fdd2
--- /dev/null
+++ b/drivers/clk/sunxi-ng/ccu_table.h
@@ -0,0 +1,233 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Table front end for the sunxi-ng clock types: a SoC lists its clocks
+ * as one table indexed by DT clock ID, and the core builds the usual
+ * ccu_* structures from it at probe time. Clocks that are the same on a
+ * whole SoC generation come from the generation's templates.
+ */
+
+#ifndef _CCU_TABLE_H_
+#define _CCU_TABLE_H_
+
+#include <linux/clk-provider.h>
+
+#include "ccu_common.h"
+#include "ccu_frac.h"
+#include "ccu_mux.h"
+#include "ccu_reset.h"
+#include "ccu_sdm.h"
+
+struct platform_device;
+
+/* Parent IDs naming the CCU inputs from the device tree */
+#define CCU_TBL_HOSC 0xfff0
+#define CCU_TBL_LOSC 0xfff1
+#define CCU_TBL_IOSC 0xfff2
+
+#define CCU_TBL_NO_RST 0xffff
+
+struct ccu_tbl_parents {
+ const u16 *ids;
+ u8 num;
+};
+
+#define CCU_TBL_PARENTS(_name, ...) \
+ static const u16 _name##_ids[] = { __VA_ARGS__ }; \
+ static const struct ccu_tbl_parents _name = { \
+ .ids = _name##_ids, \
+ .num = ARRAY_SIZE(_name##_ids), \
+ }
+
+/* A register field; min and max only apply to some of them */
+struct ccu_tbl_field {
+ u8 shift;
+ u8 width;
+ u8 min;
+ u8 max;
+};
+
+enum ccu_tbl_type {
+ CCU_TBL_TYPE_FIXED_RATE = 1,
+ CCU_TBL_TYPE_FIXED_FACTOR,
+ CCU_TBL_TYPE_GATE, /* ccu_gate, reset from the generation rule */
+ CCU_TBL_TYPE_DIV, /* ccu_div: M, optional mux and gate */
+ CCU_TBL_TYPE_MUX, /* ccu_mux: mux, optional gate */
+ CCU_TBL_TYPE_MP, /* ccu_mp: M and P, mux, optional gate */
+ CCU_TBL_TYPE_MOD, /* ccu_mp with the generation's layout */
+ CCU_TBL_TYPE_PHASE, /* ccu_phase */
+ CCU_TBL_TYPE_PLL_NK, /* ccu_nk */
+ CCU_TBL_TYPE_PLL_NM, /* ccu_nm, optional frac or sdm */
+ CCU_TBL_TYPE_PLL_NKMP, /* ccu_nkmp */
+ CCU_TBL_TYPE_STD, /* one of the generation's templates */
+};
+
+/* Templates every generation may provide */
+enum ccu_tbl_std {
+ CCU_TBL_STD_PLL_CPU,
+ CCU_TBL_STD_PLL_AUDIO,
+ CCU_TBL_STD_PLL_VIDEO,
+ CCU_TBL_STD_CPUX,
+ CCU_TBL_STD_AXI,
+ CCU_TBL_STD_AHB1,
+ CCU_TBL_STD_APB1,
+ CCU_TBL_STD_APB2,
+ CCU_TBL_STD_NUM
+};
+
+struct ccu_tbl_clk {
+ const char *name;
+ u8 type;
+ unsigned long flags;
+
+ /* Either a set of parents, or a single parent ID */
+ const struct ccu_tbl_parents *parents;
+ u16 parent;
+
+ u16 reg;
+ bool reg_set; /* overrides a template's */
+ u8 std;
+ u32 gate;
+ u16 rst;
+
+ struct ccu_tbl_field n;
+ struct ccu_tbl_field k;
+ struct ccu_tbl_field m;
+ struct ccu_tbl_field p;
+ struct ccu_tbl_field mux;
+ u32 m_flags;
+ struct clk_div_table *m_table;
+ const struct ccu_mux_var_prediv *var_predivs;
+ u8 n_var_predivs;
+ u8 postdiv;
+
+ bool frac;
+ const struct ccu_sdm_internal *sdm;
+
+ /* Bits of reg cleared before the clock is registered */
+ u32 init_clear;
+
+ unsigned long rate;
+ unsigned int mult;
+ unsigned int div;
+};
+
+/*
+ * Layout shared by a SoC generation: where a bus gate's reset lives, the
+ * standard module clock register, the PLL control bits, and the clocks
+ * that are the same on all of its SoCs.
+ */
+struct ccu_tbl_rst_pair {
+ u16 gate_reg;
+ u16 rst_reg;
+};
+
+struct ccu_tbl_gen {
+ const struct ccu_tbl_rst_pair *rst_pairs;
+ u8 num_rst_pairs;
+
+ struct ccu_tbl_field mod_m;
+ struct ccu_tbl_field mod_p;
+ struct ccu_tbl_field mod_mux;
+ u32 mod_gate;
+
+ u32 pll_enable;
+ u32 pll_lock;
+ struct ccu_frac_internal pll_frac;
+
+ const struct ccu_tbl_clk *std;
+};
+
+extern const struct ccu_tbl_gen ccu_tbl_gen_a31;
+
+struct sunxi_ccu_table {
+ const struct ccu_tbl_gen *gen;
+
+ const struct ccu_tbl_clk *clks;
+ unsigned int num_clks;
+
+ /* Resets without a bus gate, indexed by reset ID */
+ const struct ccu_reset_map *resets;
+ unsigned int num_resets;
+};
+
+int devm_sunxi_ccu_table_probe(struct device *dev, void __iomem *reg,
+ const struct sunxi_ccu_table *tbl);
+int sunxi_ccu_table_platform_probe(struct platform_device *pdev);
+
+/* Table entries */
+
+#define CCU_TBL_FIXED_RATE(_id, _name, _parent, _rate) \
+ [_id] = { .type = CCU_TBL_TYPE_FIXED_RATE, .name = _name, \
+ .parent = _parent, .rate = _rate }
+
+#define CCU_TBL_FIXED_FACTOR(_id, _name, _parent, _mult, _div) \
+ [_id] = { .type = CCU_TBL_TYPE_FIXED_FACTOR, .name = _name, \
+ .parent = _parent, .mult = _mult, .div = _div }
+
+#define CCU_TBL_GATE(_id, _name, _parent, _reg, _bit, _rst) \
+ [_id] = { .type = CCU_TBL_TYPE_GATE, .name = _name, \
+ .parent = _parent, .reg = _reg, .gate = BIT(_bit), \
+ .rst = _rst }
+
+#define CCU_TBL_DIV(_id, _name, _parent, _reg, _ms, _mw, _flags) \
+ [_id] = { .type = CCU_TBL_TYPE_DIV, .name = _name, \
+ .parent = _parent, .reg = _reg, \
+ .m = { _ms, _mw }, .flags = _flags }
+
+#define CCU_TBL_DIV_MUX_GATE(_id, _name, _parents, _reg, _ms, _mw, \
+ _xs, _xw, _gate, _flags) \
+ [_id] = { .type = CCU_TBL_TYPE_DIV, .name = _name, \
+ .parents = &(_parents), .reg = _reg, \
+ .m = { _ms, _mw }, .mux = { _xs, _xw }, \
+ .gate = _gate, .flags = _flags }
+
+#define CCU_TBL_MUX_GATE(_id, _name, _parents, _reg, _xs, _xw, _gate, \
+ _flags) \
+ [_id] = { .type = CCU_TBL_TYPE_MUX, .name = _name, \
+ .parents = &(_parents), .reg = _reg, \
+ .mux = { _xs, _xw }, .gate = _gate, .flags = _flags }
+
+#define CCU_TBL_MOD(_id, _name, _parents, _reg, _flags) \
+ [_id] = { .type = CCU_TBL_TYPE_MOD, .name = _name, \
+ .parents = &(_parents), .reg = _reg, .flags = _flags }
+
+#define CCU_TBL_MOD_POSTDIV(_id, _name, _parents, _reg, _postdiv, \
+ _flags) \
+ [_id] = { .type = CCU_TBL_TYPE_MOD, .name = _name, \
+ .parents = &(_parents), .reg = _reg, \
+ .postdiv = _postdiv, .flags = _flags }
+
+#define CCU_TBL_PHASE(_id, _name, _parent, _reg, _shift, _width) \
+ [_id] = { .type = CCU_TBL_TYPE_PHASE, .name = _name, \
+ .parent = _parent, .reg = _reg, \
+ .m = { _shift, _width } }
+
+/* PLLs; the enable and lock bits come from the generation */
+#define CCU_TBL_PLL_NK(_id, _name, _parent, _reg, _ns, _nw, _ks, _kw, \
+ _kmin, _postdiv, _flags) \
+ [_id] = { .type = CCU_TBL_TYPE_PLL_NK, .name = _name, \
+ .parent = _parent, .reg = _reg, \
+ .n = { _ns, _nw }, .k = { _ks, _kw, _kmin }, \
+ .postdiv = _postdiv, .flags = _flags }
+
+#define CCU_TBL_PLL_NM(_id, _name, _parent, _reg, _ns, _nw, _ms, _mw, \
+ _flags) \
+ [_id] = { .type = CCU_TBL_TYPE_PLL_NM, .name = _name, \
+ .parent = _parent, .reg = _reg, \
+ .n = { _ns, _nw }, .m = { _ms, _mw }, .flags = _flags }
+
+/* Generation templates: at their usual register, or at _reg */
+#define CCU_TBL_STD(_id, _name, _std, _parents, _flags) \
+ [_id] = { .type = CCU_TBL_TYPE_STD, .std = CCU_TBL_STD_##_std, \
+ .name = _name, .parents = &(_parents), .flags = _flags }
+
+#define CCU_TBL_STD1(_id, _name, _std, _parent, _flags) \
+ [_id] = { .type = CCU_TBL_TYPE_STD, .std = CCU_TBL_STD_##_std, \
+ .name = _name, .parent = _parent, .flags = _flags }
+
+#define CCU_TBL_STD_AT(_id, _name, _std, _reg, _parent, _flags) \
+ [_id] = { .type = CCU_TBL_TYPE_STD, .std = CCU_TBL_STD_##_std, \
+ .name = _name, .parent = _parent, .reg = _reg, \
+ .reg_set = true, .flags = _flags }
+
+#endif /* _CCU_TABLE_H_ */
--
2.52.0
next prev parent reply other threads:[~2026-11-08 15:58 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-08 15:56 [PATCH 0/3] clk: sunxi-ng: describe CCUs as tables, add the Allwinner B288 Pablo Mazzini
2026-11-08 15:56 ` [PATCH 1/3] dt-bindings: clock: sun4i-a10-ccu: add " Pablo Mazzini
2026-11-08 15:57 ` Pablo Mazzini [this message]
2026-11-08 15:57 ` [PATCH 3/3] clk: sunxi-ng: add Allwinner B288 CCU driver Pablo Mazzini
2026-11-08 16:51 ` [PATCH 0/3] clk: sunxi-ng: describe CCUs as tables, add the Allwinner B288 Chen-Yu Tsai
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261108155702.361022-3-pmazzini@gmail.com \
--to=pmazzini@gmail.com \
--cc=andre.przywara@arm.com \
--cc=bmasney+clk@redhat.com \
--cc=jbrunet+clk@baylibre.com \
--cc=jernej.skrabec@gmail.com \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-clk@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-sunxi@lists.linux.dev \
--cc=samuel@sholland.org \
--cc=sboyd@kernel.org \
--cc=wens@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®