* [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