mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH 0/3] clk: sunxi-ng: describe CCUs as tables, add the Allwinner B288
@ 2026-11-08 15:56 Pablo Mazzini
  2026-11-08 15:56 ` [PATCH 1/3] dt-bindings: clock: sun4i-a10-ccu: add " Pablo Mazzini
                   ` (3 more replies)
  0 siblings, 4 replies; 5+ messages in thread
From: Pablo Mazzini @ 2026-11-08 15:56 UTC (permalink / raw)
  To: Chen-Yu Tsai, Jernej Skrabec, Samuel Holland
  Cc: Pablo Mazzini, Andre Przywara, devicetree, linux-arm-kernel,
	linux-clk, linux-kernel, linux-sunxi

sunxi-ng drivers describe each clock as a static structure, list it
twice (ccu_clks and hw_clks) and keep a separate reset table. This adds
a table front end: a SoC lists its clocks once, indexed by their binding
clock ID, and the core builds the usual ccu_* clocks from that table.
Bus resets come from the gate entries, and clocks that a SoC generation
shares come from templates. Existing drivers and bindings are not
affected.

The first user is the B288. Its driver is 212 lines here, against 749
in the current style.

This replaces patches 01 and 02 of the B288 series v4:
https://lore.kernel.org/r/20261012100816.331033-1-pmazzini@gmail.com

Pablo Mazzini (3):
  dt-bindings: clock: sun4i-a10-ccu: add Allwinner B288
  clk: sunxi-ng: add a table front end
  clk: sunxi-ng: add Allwinner B288 CCU driver

 .../clock/allwinner,sun4i-a10-ccu.yaml        |   1 +
 drivers/clk/sunxi-ng/Kconfig                  |   5 +
 drivers/clk/sunxi-ng/Makefile                 |   2 +
 drivers/clk/sunxi-ng/ccu-sun8i-b288.c         | 212 ++++++++
 drivers/clk/sunxi-ng/ccu-sun8i-b288.h         |  11 +
 drivers/clk/sunxi-ng/ccu_table.c              | 510 ++++++++++++++++++
 drivers/clk/sunxi-ng/ccu_table.h              | 233 ++++++++
 include/dt-bindings/clock/sun8i-b288-ccu.h    |  82 +++
 include/dt-bindings/reset/sun8i-b288-ccu.h    |  45 ++
 9 files changed, 1101 insertions(+)
 create mode 100644 drivers/clk/sunxi-ng/ccu-sun8i-b288.c
 create mode 100644 drivers/clk/sunxi-ng/ccu-sun8i-b288.h
 create mode 100644 drivers/clk/sunxi-ng/ccu_table.c
 create mode 100644 drivers/clk/sunxi-ng/ccu_table.h
 create mode 100644 include/dt-bindings/clock/sun8i-b288-ccu.h
 create mode 100644 include/dt-bindings/reset/sun8i-b288-ccu.h


base-commit: 6628e7d4c6b25c8d18f1345e3a20d21829aae7e4
-- 
2.52.0


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH 1/3] dt-bindings: clock: sun4i-a10-ccu: add Allwinner B288
  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 ` Pablo Mazzini
  2026-11-08 15:57 ` [PATCH 2/3] clk: sunxi-ng: add a table front end Pablo Mazzini
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 5+ messages in thread
From: Pablo Mazzini @ 2026-11-08 15:56 UTC (permalink / raw)
  To: Brian Masney, Chen-Yu Tsai, Conor Dooley, Jernej Skrabec,
	Jerome Brunet, Krzysztof Kozlowski, Maxime Ripard, Philipp Zabel,
	Rob Herring, Samuel Holland, Stephen Boyd
  Cc: Pablo Mazzini, Krzysztof Kozlowski, Andre Przywara, devicetree,
	linux-arm-kernel, linux-clk, linux-kernel, linux-sunxi

The B288 (sun8iw10p1) is a dual Cortex-A7 SoC used in e-book readers.
Its CCU needs only the two oscillator inputs, so it fits the existing
schema without changes.

Signed-off-by: Pablo Mazzini <pmazzini@gmail.com>
Acked-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
---
Moved unchanged from the B288 series v4.
---
 .../clock/allwinner,sun4i-a10-ccu.yaml        |  1 +
 include/dt-bindings/clock/sun8i-b288-ccu.h    | 82 +++++++++++++++++++
 include/dt-bindings/reset/sun8i-b288-ccu.h    | 45 ++++++++++
 3 files changed, 128 insertions(+)
 create mode 100644 include/dt-bindings/clock/sun8i-b288-ccu.h
 create mode 100644 include/dt-bindings/reset/sun8i-b288-ccu.h

diff --git a/Documentation/devicetree/bindings/clock/allwinner,sun4i-a10-ccu.yaml b/Documentation/devicetree/bindings/clock/allwinner,sun4i-a10-ccu.yaml
index 1690b9d99c3d..cc3f62d3554c 100644
--- a/Documentation/devicetree/bindings/clock/allwinner,sun4i-a10-ccu.yaml
+++ b/Documentation/devicetree/bindings/clock/allwinner,sun4i-a10-ccu.yaml
@@ -28,6 +28,7 @@ properties:
       - allwinner,sun8i-a33-ccu
       - allwinner,sun8i-a83t-ccu
       - allwinner,sun8i-a83t-r-ccu
+      - allwinner,sun8i-b288-ccu
       - allwinner,sun8i-h3-ccu
       - allwinner,sun8i-h3-r-ccu
       - allwinner,sun8i-r40-ccu
diff --git a/include/dt-bindings/clock/sun8i-b288-ccu.h b/include/dt-bindings/clock/sun8i-b288-ccu.h
new file mode 100644
index 000000000000..c9b9eaf979ad
--- /dev/null
+++ b/include/dt-bindings/clock/sun8i-b288-ccu.h
@@ -0,0 +1,82 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
+#ifndef _DT_BINDINGS_CLK_SUN8I_B288_H_
+#define _DT_BINDINGS_CLK_SUN8I_B288_H_
+
+#define CLK_PLL_CPUX	0
+#define CLK_PLL_AUDIO_BASE	1
+#define CLK_PLL_VIDEO0	2
+#define CLK_PLL_DDR0	3
+#define CLK_PLL_PERIPH0	4
+#define CLK_PLL_VIDEO1	5
+#define CLK_PLL_24M	6
+#define CLK_PLL_PERIPH1	7
+#define CLK_PLL_DE	8
+#define CLK_PLL_DDR1	9
+
+#define CLK_PLL_PERIPH0_2X	10
+#define CLK_PLL_PERIPH1_2X	11
+#define CLK_CPUX	12
+#define CLK_AXI	13
+#define CLK_CPUAPB	14
+#define CLK_AHB1	15
+#define CLK_APB1	16
+#define CLK_APB2	17
+
+#define CLK_NAND	18
+#define CLK_MMC0	19
+#define CLK_MMC1	20
+#define CLK_MMC2	21
+#define CLK_MMC3	22
+#define CLK_SPI0	23
+#define CLK_SPI1	24
+#define CLK_SPI2	25
+#define CLK_DRAM	26
+#define CLK_DE	27
+#define CLK_EE	28
+#define CLK_EDMA	29
+#define CLK_TCON0	30
+#define CLK_MBUS	31
+
+#define CLK_BUS_THS	32
+#define CLK_BUS_NAND	33
+#define CLK_BUS_SDMMC0_BUS	34
+#define CLK_BUS_SDMMC1_BUS	35
+#define CLK_BUS_SDMMC2_BUS	36
+#define CLK_BUS_SDMMC3_BUS	37
+#define CLK_BUS_SPI0	38
+#define CLK_BUS_SPI1	39
+#define CLK_BUS_SPI2	40
+#define CLK_BUS_I2S0	41
+#define CLK_BUS_I2S1	42
+#define CLK_BUS_SPDIF	43
+#define CLK_BUS_DSD	44
+#define CLK_BUS_DMIC	45
+#define CLK_BUS_USBOHCI0	46
+#define CLK_BUS_USBEHCI0	47
+#define CLK_BUS_USBOTG	48
+#define CLK_BUS_DE	49
+#define CLK_BUS_EE	50
+#define CLK_BUS_TCON0	51
+#define CLK_BUS_CSI	52
+#define CLK_BUS_ADDA	53
+#define CLK_BUS_SDRAM	54
+#define CLK_BUS_PSRAM	55
+#define CLK_BUS_DMA	56
+#define CLK_BUS_UART0	57
+#define CLK_BUS_UART1	58
+#define CLK_BUS_UART2	59
+#define CLK_BUS_UART3	60
+#define CLK_BUS_UART4	61
+#define CLK_BUS_UART5	62
+#define CLK_BUS_TWI0	63
+#define CLK_BUS_TWI1	64
+#define CLK_BUS_TWI2	65
+#define CLK_BUS_PIO	66
+#define CLK_BUS_GPADC	67
+#define CLK_BUS_KEYADC	68
+#define CLK_MMC0_SAMPLE	69
+#define CLK_MMC0_OUTPUT	70
+#define CLK_MMC1_SAMPLE	71
+#define CLK_MMC1_OUTPUT	72
+
+#endif
diff --git a/include/dt-bindings/reset/sun8i-b288-ccu.h b/include/dt-bindings/reset/sun8i-b288-ccu.h
new file mode 100644
index 000000000000..675145654548
--- /dev/null
+++ b/include/dt-bindings/reset/sun8i-b288-ccu.h
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
+#ifndef _DT_BINDINGS_RST_SUN8I_B288_H_
+#define _DT_BINDINGS_RST_SUN8I_B288_H_
+
+#define RST_BUS_THS	0
+#define RST_BUS_NAND	1
+#define RST_BUS_MMC0	2
+#define RST_BUS_MMC1	3
+#define RST_BUS_MMC2	4
+#define RST_BUS_MMC3	5
+#define RST_BUS_SPI0	6
+#define RST_BUS_SPI1	7
+#define RST_BUS_SPI2	8
+#define RST_BUS_I2S0	9
+#define RST_BUS_I2S1	10
+#define RST_BUS_SPDIF	11
+#define RST_BUS_DSD	12
+#define RST_BUS_DMIC	13
+#define RST_USBPHY0	14
+#define RST_BUS_OHCI0	15
+#define RST_BUS_EHCI0	16
+#define RST_BUS_OTG	17
+#define RST_BUS_DE	18
+#define RST_BUS_EE	19
+#define RST_BUS_TCON0	20
+#define RST_BUS_CSI	21
+#define RST_BUS_ADDA	22
+#define RST_BUS_WLAN	23
+#define RST_MBUS	24
+#define RST_BUS_DRAM	25
+#define RST_BUS_PSRAM	26
+#define RST_BUS_DMA	27
+#define RST_BUS_UART0	28
+#define RST_BUS_UART1	29
+#define RST_BUS_UART2	30
+#define RST_BUS_UART3	31
+#define RST_BUS_UART4	32
+#define RST_BUS_UART5	33
+#define RST_BUS_I2C0	34
+#define RST_BUS_I2C1	35
+#define RST_BUS_I2C2	36
+#define RST_BUS_GPADC	37
+#define RST_BUS_KEYADC	38
+
+#endif
-- 
2.52.0


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH 2/3] clk: sunxi-ng: add a table front end
  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
  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
  3 siblings, 0 replies; 5+ messages in thread
From: Pablo Mazzini @ 2026-11-08 15:57 UTC (permalink / raw)
  To: Brian Masney, Chen-Yu Tsai, Jernej Skrabec, Jerome Brunet,
	Samuel Holland, Stephen Boyd
  Cc: Pablo Mazzini, Andre Przywara, linux-arm-kernel, linux-clk,
	linux-kernel, linux-sunxi

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


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH 3/3] clk: sunxi-ng: add Allwinner B288 CCU driver
  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 ` [PATCH 2/3] clk: sunxi-ng: add a table front end Pablo Mazzini
@ 2026-11-08 15:57 ` Pablo Mazzini
  2026-11-08 16:51 ` [PATCH 0/3] clk: sunxi-ng: describe CCUs as tables, add the Allwinner B288 Chen-Yu Tsai
  3 siblings, 0 replies; 5+ messages in thread
From: Pablo Mazzini @ 2026-11-08 15:57 UTC (permalink / raw)
  To: Brian Masney, Chen-Yu Tsai, Jernej Skrabec, Jerome Brunet,
	Samuel Holland, Stephen Boyd
  Cc: Pablo Mazzini, Andre Przywara, linux-arm-kernel, linux-clk,
	linux-kernel, linux-sunxi

Covers the PLLs, the CPU/AHB/APB tree, the module clocks and the bus
gates and resets, described as a table.

Signed-off-by: Pablo Mazzini <pmazzini@gmail.com>
---
 drivers/clk/sunxi-ng/Kconfig          |   5 +
 drivers/clk/sunxi-ng/Makefile         |   1 +
 drivers/clk/sunxi-ng/ccu-sun8i-b288.c | 212 ++++++++++++++++++++++++++
 drivers/clk/sunxi-ng/ccu-sun8i-b288.h |  11 ++
 4 files changed, 229 insertions(+)
 create mode 100644 drivers/clk/sunxi-ng/ccu-sun8i-b288.c
 create mode 100644 drivers/clk/sunxi-ng/ccu-sun8i-b288.h

diff --git a/drivers/clk/sunxi-ng/Kconfig b/drivers/clk/sunxi-ng/Kconfig
index 6af2d020e03e..5bdfb8574794 100644
--- a/drivers/clk/sunxi-ng/Kconfig
+++ b/drivers/clk/sunxi-ng/Kconfig
@@ -88,6 +88,11 @@ config SUN6I_RTC_CCU
 	default ARCH_SUNXI
 	depends on MACH_SUN8I || ARM64 || RISCV || COMPILE_TEST
 
+config SUN8I_B288_CCU
+	tristate "Support for the Allwinner B288 CCU"
+	default ARCH_SUNXI
+	depends on MACH_SUN8I || COMPILE_TEST
+
 config SUN8I_A23_CCU
 	tristate "Support for the Allwinner A23 CCU"
 	default ARCH_SUNXI
diff --git a/drivers/clk/sunxi-ng/Makefile b/drivers/clk/sunxi-ng/Makefile
index 796f443f4d76..debae396555f 100644
--- a/drivers/clk/sunxi-ng/Makefile
+++ b/drivers/clk/sunxi-ng/Makefile
@@ -41,6 +41,7 @@ obj-$(CONFIG_SUN4I_A10_CCU)	+= sun4i-a10-ccu.o
 obj-$(CONFIG_SUN5I_CCU)		+= sun5i-ccu.o
 obj-$(CONFIG_SUN6I_A31_CCU)	+= sun6i-a31-ccu.o
 obj-$(CONFIG_SUN6I_RTC_CCU)	+= sun6i-rtc-ccu.o
+obj-$(CONFIG_SUN8I_B288_CCU)	+= ccu-sun8i-b288.o
 obj-$(CONFIG_SUN8I_A23_CCU)	+= sun8i-a23-ccu.o
 obj-$(CONFIG_SUN8I_A33_CCU)	+= sun8i-a33-ccu.o
 obj-$(CONFIG_SUN8I_A83T_CCU)	+= sun8i-a83t-ccu.o
diff --git a/drivers/clk/sunxi-ng/ccu-sun8i-b288.c b/drivers/clk/sunxi-ng/ccu-sun8i-b288.c
new file mode 100644
index 000000000000..6e12ed8248f2
--- /dev/null
+++ b/drivers/clk/sunxi-ng/ccu-sun8i-b288.c
@@ -0,0 +1,212 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Clock Control Unit driver for the Allwinner B288 (sun8iw10p1).
+ *
+ * Closely related to the A64; most of its PLLs are bit-identical.
+ * Transcribed from the BSP clk-sun8iw10.{c,h} and cross-checked against
+ * ccu-sun50i-a64.c; divergences are called out inline.
+ *
+ * The audio clock tree (i2s/spdif/dsd/dmic/adda) is not implemented.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include "ccu_table.h"
+
+#include "ccu-sun8i-b288.h"
+
+/*
+ * APB2 mux index 2 selects PLL_24M here rather than PLL_PERIPH0_2X as on
+ * the H3.
+ */
+CCU_TBL_PARENTS(cpux_parents, CCU_TBL_LOSC, CCU_TBL_HOSC, CLK_PLL_CPUX,
+		CLK_PLL_CPUX);
+CCU_TBL_PARENTS(ahb1_parents, CCU_TBL_LOSC, CCU_TBL_HOSC, CLK_AXI,
+		CLK_PLL_PERIPH0);
+CCU_TBL_PARENTS(apb2_parents, CCU_TBL_LOSC, CCU_TBL_HOSC, CLK_PLL_24M,
+		CLK_PLL_PERIPH0_2X);
+CCU_TBL_PARENTS(mod0_parents, CCU_TBL_HOSC, CLK_PLL_PERIPH0_2X,
+		CLK_PLL_PERIPH1_2X);
+CCU_TBL_PARENTS(de_parents, CLK_PLL_PERIPH0_2X, CLK_PLL_DE);
+CCU_TBL_PARENTS(tcon0_parents, CLK_PLL_VIDEO0, CLK_PLL_VIDEO1);
+CCU_TBL_PARENTS(mbus_parents, CCU_TBL_HOSC, CLK_PLL_PERIPH0_2X, CLK_PLL_DDR0,
+		CLK_PLL_DDR1);
+CCU_TBL_PARENTS(dram_parents, CLK_PLL_DDR0, CLK_PLL_DDR1, CLK_PLL_PERIPH0_2X);
+
+#define CRIT		CLK_IS_CRITICAL
+#define UNGATE		CLK_SET_RATE_UNGATE
+#define RATE_PARENT	CLK_SET_RATE_PARENT
+
+static const struct ccu_tbl_clk sun8i_b288_clks[CLK_NUMBER] = {
+	/*
+	 * PLL_24M @ 0x034 -- B288 only. It feeds the PLLs 24 MHz whatever
+	 * the crystal: it converts a 26 MHz one and is bypassed with a
+	 * 24 MHz one.
+	 */
+	CCU_TBL_FIXED_RATE(CLK_PLL_24M, "pll-24m", CCU_TBL_HOSC, 24000000),
+
+	CCU_TBL_STD1(CLK_PLL_CPUX, "pll-cpux", PLL_CPU, CLK_PLL_24M, UNGATE),
+	CCU_TBL_STD1(CLK_PLL_AUDIO_BASE, "pll-audio-base", PLL_AUDIO,
+		     CLK_PLL_24M, UNGATE),
+	CCU_TBL_STD_AT(CLK_PLL_VIDEO0, "pll-video0", PLL_VIDEO, 0x010,
+		       CLK_PLL_24M, UNGATE),
+	CCU_TBL_STD_AT(CLK_PLL_VIDEO1, "pll-video1", PLL_VIDEO, 0x030,
+		       CLK_PLL_24M, UNGATE),
+	CCU_TBL_STD_AT(CLK_PLL_DE, "pll-de", PLL_VIDEO, 0x048,
+		       CLK_PLL_24M, UNGATE),
+	/* PLL_DDR0 @ 0x020 -- unlike the A64 there is no K, and N is 7 bits */
+	CCU_TBL_PLL_NM(CLK_PLL_DDR0, "pll-ddr0", CLK_PLL_24M, 0x020,
+		       8, 7, 0, 2, UNGATE),
+	/* PLL_DDR1 @ 0x04c -- B288 only, same layout; this one feeds DRAM */
+	CCU_TBL_PLL_NM(CLK_PLL_DDR1, "pll-ddr1", CLK_PLL_24M, 0x04c,
+		       8, 7, 0, 2, UNGATE),
+	/* PLL_PERIPH{0,1} -- as on the A64, post-div 2: 2x is the raw output */
+	CCU_TBL_PLL_NK(CLK_PLL_PERIPH0, "pll-periph0", CLK_PLL_24M, 0x028,
+		       8, 5, 4, 2, 2, 2, 0),
+	CCU_TBL_PLL_NK(CLK_PLL_PERIPH1, "pll-periph1", CLK_PLL_24M, 0x044,
+		       8, 5, 4, 2, 2, 2, 0),
+	CCU_TBL_FIXED_FACTOR(CLK_PLL_PERIPH0_2X, "pll-periph0-2x",
+			     CLK_PLL_PERIPH0, 2, 1),
+	CCU_TBL_FIXED_FACTOR(CLK_PLL_PERIPH1_2X, "pll-periph1-2x",
+			     CLK_PLL_PERIPH1, 2, 1),
+
+	CCU_TBL_STD(CLK_CPUX, "cpux", CPUX, cpux_parents, CRIT | RATE_PARENT),
+	CCU_TBL_STD1(CLK_AXI, "axi", AXI, CLK_CPUX, 0),
+	/* CPU_CFG @ 0x050 -- CPUAPB divider [9:8] */
+	CCU_TBL_DIV(CLK_CPUAPB, "cpuapb", CLK_CPUX, 0x050, 8, 2, 0),
+	CCU_TBL_STD(CLK_AHB1, "ahb1", AHB1, ahb1_parents, 0),
+	CCU_TBL_STD1(CLK_APB1, "apb1", APB1, CLK_AHB1, 0),
+	CCU_TBL_STD(CLK_APB2, "apb2", APB2, apb2_parents, 0),
+
+	CCU_TBL_MOD(CLK_NAND, "nand", mod0_parents, 0x080, 0),
+	/* mmc0/mmc1 use the old timing mode and need the phase clocks */
+	CCU_TBL_MOD(CLK_MMC0, "mmc0", mod0_parents, 0x088, 0),
+	CCU_TBL_PHASE(CLK_MMC0_SAMPLE, "mmc0_sample", CLK_MMC0, 0x088, 20, 3),
+	CCU_TBL_PHASE(CLK_MMC0_OUTPUT, "mmc0_output", CLK_MMC0, 0x088, 8, 3),
+	CCU_TBL_MOD(CLK_MMC1, "mmc1", mod0_parents, 0x08c, 0),
+	CCU_TBL_PHASE(CLK_MMC1_SAMPLE, "mmc1_sample", CLK_MMC1, 0x08c, 20, 3),
+	CCU_TBL_PHASE(CLK_MMC1_OUTPUT, "mmc1_output", CLK_MMC1, 0x08c, 8, 3),
+	/* mmc2/mmc3 halve the clock internally */
+	CCU_TBL_MOD_POSTDIV(CLK_MMC2, "mmc2", mod0_parents, 0x090, 2, 0),
+	CCU_TBL_MOD_POSTDIV(CLK_MMC3, "mmc3", mod0_parents, 0x094, 2, 0),
+	CCU_TBL_MOD(CLK_SPI0, "spi0", mod0_parents, 0x0a0, 0),
+	CCU_TBL_MOD(CLK_SPI1, "spi1", mod0_parents, 0x0a4, 0),
+	CCU_TBL_MOD(CLK_SPI2, "spi2", mod0_parents, 0x0a8, 0),
+
+	/* DRAM_CFG @ 0x0f4 -- M[1:0] mux[21:20] gate 31 */
+	CCU_TBL_DIV_MUX_GATE(CLK_DRAM, "dram", dram_parents, 0x0f4,
+			     0, 2, 20, 2, BIT(31), CRIT),
+	/* DE_CFG @ 0x104, EE_CFG @ 0x108, EDMA_CFG @ 0x10c -- M[3:0] mux[26:24] */
+	CCU_TBL_DIV_MUX_GATE(CLK_DE, "de", de_parents, 0x104,
+			     0, 4, 24, 3, BIT(31), RATE_PARENT),
+	CCU_TBL_DIV_MUX_GATE(CLK_EE, "ee", de_parents, 0x108,
+			     0, 4, 24, 3, BIT(31), RATE_PARENT),
+	CCU_TBL_DIV_MUX_GATE(CLK_EDMA, "edma", de_parents, 0x10c,
+			     0, 4, 24, 3, BIT(31), RATE_PARENT),
+	/* TCON_CFG @ 0x118 -- mux[26:24] gate 31, no divider */
+	CCU_TBL_MUX_GATE(CLK_TCON0, "tcon0", tcon0_parents, 0x118, 24, 3,
+			 BIT(31), RATE_PARENT),
+	/* MBUS_CFG @ 0x15c -- M[2:0] mux[25:24] gate 31 */
+	CCU_TBL_DIV_MUX_GATE(CLK_MBUS, "mbus", mbus_parents, 0x15c,
+			     0, 3, 24, 2, BIT(31), CRIT),
+
+	/* Bus gates; each reset is the same bit of the paired reset register */
+	CCU_TBL_GATE(CLK_BUS_DMA, "bus-dma", CLK_AHB1, 0x060, 6, RST_BUS_DMA),
+	CCU_TBL_GATE(CLK_BUS_SDMMC0_BUS, "bus-sdmmc0-bus", CLK_AHB1, 0x060, 8,
+		     RST_BUS_MMC0),
+	CCU_TBL_GATE(CLK_BUS_SDMMC1_BUS, "bus-sdmmc1-bus", CLK_AHB1, 0x060, 9,
+		     RST_BUS_MMC1),
+	CCU_TBL_GATE(CLK_BUS_SDMMC2_BUS, "bus-sdmmc2-bus", CLK_AHB1, 0x060, 10,
+		     RST_BUS_MMC2),
+	CCU_TBL_GATE(CLK_BUS_SDMMC3_BUS, "bus-sdmmc3-bus", CLK_AHB1, 0x060, 11,
+		     RST_BUS_MMC3),
+	CCU_TBL_GATE(CLK_BUS_NAND, "bus-nand", CLK_AHB1, 0x060, 13,
+		     RST_BUS_NAND),
+	CCU_TBL_GATE(CLK_BUS_SDRAM, "bus-sdram", CLK_AHB1, 0x060, 14,
+		     RST_BUS_DRAM),
+	CCU_TBL_GATE(CLK_BUS_PSRAM, "bus-psram", CLK_AHB1, 0x060, 15,
+		     RST_BUS_PSRAM),
+	CCU_TBL_GATE(CLK_BUS_SPI0, "bus-spi0", CLK_AHB1, 0x060, 20, RST_BUS_SPI0),
+	CCU_TBL_GATE(CLK_BUS_SPI1, "bus-spi1", CLK_AHB1, 0x060, 21, RST_BUS_SPI1),
+	CCU_TBL_GATE(CLK_BUS_SPI2, "bus-spi2", CLK_AHB1, 0x060, 22, RST_BUS_SPI2),
+	CCU_TBL_GATE(CLK_BUS_USBOTG, "bus-usbotg", CLK_AHB1, 0x060, 24,
+		     RST_BUS_OTG),
+	CCU_TBL_GATE(CLK_BUS_USBEHCI0, "bus-usbehci0", CLK_AHB1, 0x060, 26,
+		     RST_BUS_EHCI0),
+	CCU_TBL_GATE(CLK_BUS_USBOHCI0, "bus-usbohci0", CLK_AHB1, 0x060, 29,
+		     RST_BUS_OHCI0),
+	CCU_TBL_GATE(CLK_BUS_TCON0, "bus-tcon0", CLK_AHB1, 0x064, 4,
+		     RST_BUS_TCON0),
+	/* csi_s, csi_m and csi_misc share one bus gate, as on the A64. */
+	CCU_TBL_GATE(CLK_BUS_CSI, "bus-csi", CLK_AHB1, 0x064, 8, RST_BUS_CSI),
+	CCU_TBL_GATE(CLK_BUS_DE, "bus-de", CLK_AHB1, 0x064, 12, RST_BUS_DE),
+	CCU_TBL_GATE(CLK_BUS_EE, "bus-ee", CLK_AHB1, 0x064, 13, RST_BUS_EE),
+	CCU_TBL_GATE(CLK_BUS_ADDA, "bus-adda", CLK_APB1, 0x068, 0, RST_BUS_ADDA),
+	CCU_TBL_GATE(CLK_BUS_SPDIF, "bus-spdif", CLK_APB1, 0x068, 1,
+		     RST_BUS_SPDIF),
+	CCU_TBL_GATE(CLK_BUS_DSD, "bus-dsd", CLK_APB1, 0x068, 2, RST_BUS_DSD),
+	CCU_TBL_GATE(CLK_BUS_DMIC, "bus-dmic", CLK_APB1, 0x068, 3, RST_BUS_DMIC),
+	CCU_TBL_GATE(CLK_BUS_PIO, "bus-pio", CLK_APB1, 0x068, 5, CCU_TBL_NO_RST),
+	CCU_TBL_GATE(CLK_BUS_THS, "bus-ths", CLK_APB1, 0x068, 8, RST_BUS_THS),
+	CCU_TBL_GATE(CLK_BUS_KEYADC, "bus-keyadc", CLK_APB1, 0x068, 9,
+		     RST_BUS_KEYADC),
+	CCU_TBL_GATE(CLK_BUS_GPADC, "bus-gpadc", CLK_APB1, 0x068, 10,
+		     RST_BUS_GPADC),
+	CCU_TBL_GATE(CLK_BUS_I2S0, "bus-i2s0", CLK_APB1, 0x068, 12, RST_BUS_I2S0),
+	CCU_TBL_GATE(CLK_BUS_I2S1, "bus-i2s1", CLK_APB1, 0x068, 13, RST_BUS_I2S1),
+	CCU_TBL_GATE(CLK_BUS_TWI0, "bus-twi0", CLK_APB2, 0x06c, 0, RST_BUS_I2C0),
+	CCU_TBL_GATE(CLK_BUS_TWI1, "bus-twi1", CLK_APB2, 0x06c, 1, RST_BUS_I2C1),
+	CCU_TBL_GATE(CLK_BUS_TWI2, "bus-twi2", CLK_APB2, 0x06c, 2, RST_BUS_I2C2),
+	CCU_TBL_GATE(CLK_BUS_UART0, "bus-uart0", CLK_APB2, 0x06c, 16,
+		     RST_BUS_UART0),
+	CCU_TBL_GATE(CLK_BUS_UART1, "bus-uart1", CLK_APB2, 0x06c, 17,
+		     RST_BUS_UART1),
+	CCU_TBL_GATE(CLK_BUS_UART2, "bus-uart2", CLK_APB2, 0x06c, 18,
+		     RST_BUS_UART2),
+	CCU_TBL_GATE(CLK_BUS_UART3, "bus-uart3", CLK_APB2, 0x06c, 19,
+		     RST_BUS_UART3),
+	CCU_TBL_GATE(CLK_BUS_UART4, "bus-uart4", CLK_APB2, 0x06c, 20,
+		     RST_BUS_UART4),
+	CCU_TBL_GATE(CLK_BUS_UART5, "bus-uart5", CLK_APB2, 0x06c, 21,
+		     RST_BUS_UART5),
+	/* losc_out is the RTC's osc32k-out */
+};
+
+/* Resets without a bus gate */
+static const struct ccu_reset_map sun8i_b288_resets[RST_NUMBER] = {
+	[RST_USBPHY0]	= { 0x0cc, BIT(0) },
+	[RST_MBUS]	= { 0x0fc, BIT(31) },
+	[RST_BUS_WLAN]	= { 0x2c4, BIT(1) },
+};
+
+static const struct sunxi_ccu_table sun8i_b288_ccu_table = {
+	.gen		= &ccu_tbl_gen_a31,
+	.clks		= sun8i_b288_clks,
+	.num_clks	= CLK_NUMBER,
+	.resets		= sun8i_b288_resets,
+	.num_resets	= RST_NUMBER,
+};
+
+static const struct of_device_id sun8i_b288_ccu_ids[] = {
+	{
+		.compatible	= "allwinner,sun8i-b288-ccu",
+		.data		= &sun8i_b288_ccu_table,
+	},
+	{ }
+};
+MODULE_DEVICE_TABLE(of, sun8i_b288_ccu_ids);
+
+static struct platform_driver sun8i_b288_ccu_driver = {
+	.probe	= sunxi_ccu_table_platform_probe,
+	.driver	= {
+		.name			= "sun8i-b288-ccu",
+		.suppress_bind_attrs	= true,
+		.of_match_table		= sun8i_b288_ccu_ids,
+	},
+};
+module_platform_driver(sun8i_b288_ccu_driver);
+
+MODULE_IMPORT_NS("SUNXI_CCU");
+MODULE_DESCRIPTION("Allwinner B288 (sun8iw10p1) CCU driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/clk/sunxi-ng/ccu-sun8i-b288.h b/drivers/clk/sunxi-ng/ccu-sun8i-b288.h
new file mode 100644
index 000000000000..976e205f4d95
--- /dev/null
+++ b/drivers/clk/sunxi-ng/ccu-sun8i-b288.h
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _CCU_SUN8I_B288_H_
+#define _CCU_SUN8I_B288_H_
+
+#include <dt-bindings/clock/sun8i-b288-ccu.h>
+#include <dt-bindings/reset/sun8i-b288-ccu.h>
+
+#define CLK_NUMBER		(CLK_MMC1_OUTPUT + 1)
+#define RST_NUMBER		(RST_BUS_KEYADC + 1)
+
+#endif /* _CCU_SUN8I_B288_H_ */
-- 
2.52.0


^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH 0/3] clk: sunxi-ng: describe CCUs as tables, add the Allwinner B288
  2026-11-08 15:56 [PATCH 0/3] clk: sunxi-ng: describe CCUs as tables, add the Allwinner B288 Pablo Mazzini
                   ` (2 preceding siblings ...)
  2026-11-08 15:57 ` [PATCH 3/3] clk: sunxi-ng: add Allwinner B288 CCU driver Pablo Mazzini
@ 2026-11-08 16:51 ` Chen-Yu Tsai
  3 siblings, 0 replies; 5+ messages in thread
From: Chen-Yu Tsai @ 2026-11-08 16:51 UTC (permalink / raw)
  To: Pablo Mazzini
  Cc: Jernej Skrabec, Samuel Holland, Andre Przywara, devicetree,
	linux-arm-kernel, linux-clk, linux-kernel, linux-sunxi

On Mon, Nov 9, 2026 at 12:58 AM Pablo Mazzini <pmazzini@gmail.com> wrote:
>
> sunxi-ng drivers describe each clock as a static structure, list it
> twice (ccu_clks and hw_clks) and keep a separate reset table. This adds
> a table front end: a SoC lists its clocks once, indexed by their binding
> clock ID, and the core builds the usual ccu_* clocks from that table.
> Bus resets come from the gate entries, and clocks that a SoC generation
> shares come from templates. Existing drivers and bindings are not
> affected.

I think you're going to have to give more justification. What do we gain?
What are the trade-offs? Building all the data at run-time means we end
up consuming even more memory. The tables from init time typically aren't
dropped when a driver is built as a module.


ChenYu

> The first user is the B288. Its driver is 212 lines here, against 749
> in the current style.
>
> This replaces patches 01 and 02 of the B288 series v4:
> https://lore.kernel.org/r/20261012100816.331033-1-pmazzini@gmail.com
>
> Pablo Mazzini (3):
>   dt-bindings: clock: sun4i-a10-ccu: add Allwinner B288
>   clk: sunxi-ng: add a table front end
>   clk: sunxi-ng: add Allwinner B288 CCU driver
>
>  .../clock/allwinner,sun4i-a10-ccu.yaml        |   1 +
>  drivers/clk/sunxi-ng/Kconfig                  |   5 +
>  drivers/clk/sunxi-ng/Makefile                 |   2 +
>  drivers/clk/sunxi-ng/ccu-sun8i-b288.c         | 212 ++++++++
>  drivers/clk/sunxi-ng/ccu-sun8i-b288.h         |  11 +
>  drivers/clk/sunxi-ng/ccu_table.c              | 510 ++++++++++++++++++
>  drivers/clk/sunxi-ng/ccu_table.h              | 233 ++++++++
>  include/dt-bindings/clock/sun8i-b288-ccu.h    |  82 +++
>  include/dt-bindings/reset/sun8i-b288-ccu.h    |  45 ++
>  9 files changed, 1101 insertions(+)
>  create mode 100644 drivers/clk/sunxi-ng/ccu-sun8i-b288.c
>  create mode 100644 drivers/clk/sunxi-ng/ccu-sun8i-b288.h
>  create mode 100644 drivers/clk/sunxi-ng/ccu_table.c
>  create mode 100644 drivers/clk/sunxi-ng/ccu_table.h
>  create mode 100644 include/dt-bindings/clock/sun8i-b288-ccu.h
>  create mode 100644 include/dt-bindings/reset/sun8i-b288-ccu.h
>
>
> base-commit: 6628e7d4c6b25c8d18f1345e3a20d21829aae7e4
> --
> 2.52.0
>
>

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-11-08 16:51 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
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 ` [PATCH 2/3] clk: sunxi-ng: add a table front end Pablo Mazzini
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

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®