All of lore.kernel.org
 help / color / mirror / Atom feed
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


  parent reply	other threads:[~2026-11-08 15:58 UTC|newest]

Thread overview: 9+ 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 16:11   ` sashiko-bot
2026-11-08 16:46     ` 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:14   ` sashiko-bot
2026-11-08 16:47     ` 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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.