mirror of https://lore.kernel.org/lkml/
 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: 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®