[PATCH 3/5] clk: anlogic: add cru support for Anlogic DR1V90 SoC

Junhui Liu posted 5 patches 1 week, 2 days ago
[PATCH 3/5] clk: anlogic: add cru support for Anlogic DR1V90 SoC
Posted by Junhui Liu 1 week, 2 days ago
The Clock and Reset Unit (CRU) in the Anlogic DR1V90 SoC provides
management for the clock and reset.

The clock driver includes support for:
- Generic clocks: fixed-factor, divider, mux and gate.
- PLL: "nm" type (parent * n / m) and "c" type (parent / c). These PLLs
  are set up by the FSBL and mared as "don't touch" in the datasheet, so
  only the recal_rate() op is provided.
- Divider with gate: support both division and gating (by setting value
  to 0); some of them require a minimum divider value to avoid timing
  issues.

This also prepares the structure for the reset controller support,
registering an auxiliary device for resets.

Signed-off-by: Junhui Liu <junhui.liu@pigmoral.tech>
---
 drivers/clk/Kconfig              |   1 +
 drivers/clk/Makefile             |   1 +
 drivers/clk/anlogic/Kconfig      |   9 ++
 drivers/clk/anlogic/Makefile     |   5 +
 drivers/clk/anlogic/cru-dr1v90.c | 190 ++++++++++++++++++++++++++++
 drivers/clk/anlogic/cru_dr1.c    | 258 +++++++++++++++++++++++++++++++++++++++
 drivers/clk/anlogic/cru_dr1.h    | 117 ++++++++++++++++++
 7 files changed, 581 insertions(+)

diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index 4d56475f94fc1e28823fe6aee626a96847d4e6d5..c5a29c55cddac8b136b2dc1511cc11ff69f0a55f 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -503,6 +503,7 @@ config COMMON_CLK_SP7021
 
 source "drivers/clk/actions/Kconfig"
 source "drivers/clk/analogbits/Kconfig"
+source "drivers/clk/anlogic/Kconfig"
 source "drivers/clk/baikal-t1/Kconfig"
 source "drivers/clk/bcm/Kconfig"
 source "drivers/clk/hisilicon/Kconfig"
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 18ed29cfdc1133b6c254190c6092eb263366d5ac..9ce3fdc12aa6b7b9a47f80bfe43b2af5635cb0bf 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -113,6 +113,7 @@ obj-$(CONFIG_COMMON_CLK_XGENE)		+= clk-xgene.o
 # please keep this section sorted lexicographically by directory path name
 obj-y					+= actions/
 obj-y					+= analogbits/
+obj-y					+= anlogic/
 obj-$(CONFIG_COMMON_CLK_AT91)		+= at91/
 obj-$(CONFIG_ARCH_ARTPEC)		+= axis/
 obj-$(CONFIG_ARC_PLAT_AXS10X)		+= axs10x/
diff --git a/drivers/clk/anlogic/Kconfig b/drivers/clk/anlogic/Kconfig
new file mode 100644
index 0000000000000000000000000000000000000000..63cf08d43ba85d160dcefc9c67eadf679af3c08e
--- /dev/null
+++ b/drivers/clk/anlogic/Kconfig
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0
+
+config ANLOGIC_DR1V90_CRU
+	tristate "Anlogic DR1V90 clock support"
+	depends on ARCH_ANLOGIC || COMPILE_TEST
+	select AUXILIARY_BUS
+	default ARCH_ANLOGIC
+	help
+	  Support for the Clock and Reset Unit in Anlogic DR1V90 SoCs.
diff --git a/drivers/clk/anlogic/Makefile b/drivers/clk/anlogic/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..b16d93b2e190ce075e52fc767998662ef28ee270
--- /dev/null
+++ b/drivers/clk/anlogic/Makefile
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-$(CONFIG_ANLOGIC_DR1V90_CRU)	+= anlogic-dr1v90-cru.o
+anlogic-dr1v90-cru-y			+= cru_dr1.o
+anlogic-dr1v90-cru-y			+= cru-dr1v90.o
diff --git a/drivers/clk/anlogic/cru-dr1v90.c b/drivers/clk/anlogic/cru-dr1v90.c
new file mode 100644
index 0000000000000000000000000000000000000000..c538289c2ee3e24642412cf66c39f605335d668d
--- /dev/null
+++ b/drivers/clk/anlogic/cru-dr1v90.c
@@ -0,0 +1,190 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2025 Shanghai Anlogic Infotech Co., Ltd.
+ * Copyright (c) 2025 Junhui Liu <junhui.liu@pigmoral.tech>
+ */
+
+#include <linux/array_size.h>
+#include <linux/clk-provider.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include "cru_dr1.h"
+
+#include <dt-bindings/clock/anlogic,dr1v90-cru.h>
+
+static const struct clk_div_table cru_div_table_24[] = {
+	{ 0xFFFFFF, 1 },  { 0x555555, 2 },  { 0x249249, 3 },  { 0x111111, 4 },
+	{ 0x084210, 5 },  { 0x041041, 6 },  { 0x020408, 7 },  { 0x010101, 8 },
+	{ 0x008040, 9 },  { 0x004010, 10 }, { 0x002004, 11 }, { 0x001001, 12 },
+	{ 0x000800, 13 }, { 0x000400, 14 }, { 0x000200, 15 }, { 0x000100, 16 },
+	{ 0x000080, 17 }, { 0x000040, 18 }, { 0x000020, 19 }, { 0x000010, 20 },
+	{ 0x000008, 21 }, { 0x000004, 22 }, { 0x000002, 23 }, { 0x000001, 24 },
+	{ /* sentinel */ }
+};
+
+static const struct clk_div_table cru_div_table_32[] = {
+	{ 0xFFFFFFFF, 1 },  { 0x55555555, 2 },	{ 0x24924924, 3 },
+	{ 0x11111111, 4 },  { 0x08421084, 5 },	{ 0x04104104, 6 },
+	{ 0x02040810, 7 },  { 0x01010101, 8 },	{ 0x00804020, 9 },
+	{ 0x00401004, 10 }, { 0x00200400, 11 }, { 0x00100100, 12 },
+	{ 0x00080040, 13 }, { 0x00040010, 14 }, { 0x00020004, 15 },
+	{ 0x00010001, 16 }, { 0x00008000, 17 }, { 0x00004000, 18 },
+	{ 0x00002000, 19 }, { 0x00001000, 20 }, { 0x00000800, 21 },
+	{ 0x00000400, 22 }, { 0x00000200, 23 }, { 0x00000100, 24 },
+	{ 0x00000080, 25 }, { 0x00000040, 26 }, { 0x00000020, 27 },
+	{ 0x00000010, 28 }, { 0x00000008, 29 }, { 0x00000004, 30 },
+	{ 0x00000002, 31 }, { 0x00000001, 32 }, { /* sentinel */ }
+};
+
+CLK_FIXED_FACTOR_FW_NAME(osc_div2, "osc_div2", "osc_33m", 2, 1, 0);
+
+CRU_PLL_NM_DEFINE(cpu_pll, CRU_PARENT_NAME(osc_33m), 0x120);
+CRU_PLL_C_DEFINE(cpu_pll_4x, CRU_PARENT_HW(cpu_pll), 0x14c);
+
+CRU_DIV_DEFINE(cpu_4x_div1, CRU_PARENT_HW(cpu_pll_4x), 0x010, 0, 24,
+	       cru_div_table_24, CLK_DIVIDER_READ_ONLY);
+CRU_DIV_DEFINE(cpu_4x_div2, CRU_PARENT_HW(cpu_pll_4x), 0x014, 0, 24,
+	       cru_div_table_24, CLK_DIVIDER_READ_ONLY);
+CRU_DIV_DEFINE(cpu_4x_div4, CRU_PARENT_HW(cpu_pll_4x), 0x018, 0, 24,
+	       cru_div_table_24, CLK_DIVIDER_READ_ONLY);
+
+CRU_PLL_NM_DEFINE(io_pll, CRU_PARENT_NAME(osc_33m), 0x220);
+CRU_PLL_C_DEFINE(io_1000m, CRU_PARENT_HW(io_pll), 0x248);
+CRU_PLL_C_DEFINE(io_400m, CRU_PARENT_HW(io_pll), 0x24c);
+CRU_PLL_C_DEFINE(io_25m, CRU_PARENT_HW(io_pll), 0x250);
+CRU_PLL_C_DEFINE(io_80m, CRU_PARENT_HW(io_pll), 0x254);
+
+CRU_DIV_DEFINE(io_400m_div2, CRU_PARENT_HW(io_400m), 0x020, 0, 32,
+	       cru_div_table_32, CLK_DIVIDER_READ_ONLY);
+CRU_DIV_DEFINE(io_400m_div4, CRU_PARENT_HW(io_400m), 0x024, 0, 32,
+	       cru_div_table_32, CLK_DIVIDER_READ_ONLY);
+CRU_DIV_DEFINE(io_400m_div8, CRU_PARENT_HW(io_400m), 0x028, 0, 32,
+	       cru_div_table_32, CLK_DIVIDER_READ_ONLY);
+CRU_DIV_DEFINE(io_400m_div16, CRU_PARENT_HW(io_400m), 0x02c, 0, 32,
+	       cru_div_table_32, CLK_DIVIDER_READ_ONLY);
+
+CRU_DIV_GATE_DEFINE(qspi, CRU_PARENT_HW(io_1000m), 0x030, 0, 6, NULL, 0, 2);
+CRU_DIV_GATE_DEFINE(spi, CRU_PARENT_HW(io_1000m), 0x030, 8, 6, NULL, 0, 4);
+CRU_DIV_GATE_DEFINE(smc, CRU_PARENT_HW(io_1000m), 0x030, 16, 6, NULL, 0, 4);
+CRU_DIV_DEFINE(sdio, CRU_PARENT_HW(io_400m), 0x030, 24, 6, NULL, 0);
+
+CRU_DIV_GATE_DEFINE(gpio_db, CRU_PARENT_HW(io_25m), 0x034, 0, 6, NULL, 0, 1);
+CRU_DIV_GATE_DEFINE(efuse, CRU_PARENT_HW(io_25m), 0x034, 8, 6, NULL, 0, 1);
+CRU_DIV_GATE_DEFINE(tvs, CRU_PARENT_HW(io_25m), 0x034, 16, 6, NULL, 0, 1);
+CRU_DIV_GATE_DEFINE(trng, CRU_PARENT_HW(io_25m), 0x034, 24, 7, NULL, 0, 1);
+
+CRU_DIV_GATE_DEFINE(osc_div, CRU_PARENT_NAME(osc_33m), 0x038, 0, 6, NULL, 0, 1);
+CRU_DIV_GATE_DEFINE(pwm, CRU_PARENT_NAME(osc_33m), 0x038, 8, 12, NULL, 0, 1);
+
+CRU_DIV_GATE_DEFINE(fclk0, CRU_PARENT_HW(io_400m), 0x03c, 0, 6, NULL, 0, 1);
+CRU_DIV_GATE_DEFINE(fclk1, CRU_PARENT_HW(io_400m), 0x03c, 8, 6, NULL, 0, 1);
+CRU_DIV_GATE_DEFINE(fclk2, CRU_PARENT_HW(io_400m), 0x03c, 16, 6, NULL, 0, 1);
+CRU_DIV_GATE_DEFINE(fclk3, CRU_PARENT_HW(io_400m), 0x03c, 24, 6, NULL, 0, 1);
+
+static const struct clk_parent_data wdt_parents[] = {
+	CRU_PARENT_HW(osc_div2),
+	CRU_PARENT_NAME(wdt_ext)
+};
+CRU_MUX_DEFINE(wdt_sel, wdt_parents, 0x040, 1, 1);
+
+static const struct clk_parent_data efuse_parents[] = {
+	CRU_PARENT_NAME(osc_33m),
+	CRU_PARENT_DIV_HW(efuse)
+};
+CRU_MUX_DEFINE(efuse_sel, efuse_parents, 0x040, 2, 1);
+
+static const struct clk_parent_data can_parents[] = {
+	CRU_PARENT_HW(io_80m),
+	CRU_PARENT_NAME(can_ext)
+};
+CRU_MUX_DEFINE(can_sel, can_parents, 0x040, 3, 1);
+
+static const struct clk_parent_data cpu_parents[] = {
+	CRU_PARENT_HW(cpu_4x_div1),
+	CRU_PARENT_HW(cpu_4x_div2)
+};
+CRU_MUX_DEFINE(cpu_sel, cpu_parents, 0x040, 5, 1);
+
+CRU_GATE_DEFINE(can0, CRU_PARENT_HW(can_sel), 0x08c, 20, CLK_GATE_SET_TO_DISABLE);
+CRU_GATE_DEFINE(can1, CRU_PARENT_HW(can_sel), 0x08c, 21, CLK_GATE_SET_TO_DISABLE);
+
+static const struct cru_clk dr1v90_cru_clks[] = {
+	[CLK_OSC_DIV2]		= { &osc_div2.hw,	NULL },
+	[CLK_CPU_PLL]		= { &cpu_pll.hw,	&cpu_pll.reg },
+	[CLK_CPU_PLL_4X]	= { &cpu_pll_4x.hw,	&cpu_pll_4x.reg },
+	[CLK_CPU_4X]		= { &cpu_4x_div1.hw,	&cpu_4x_div1.reg },
+	[CLK_CPU_2X]		= { &cpu_4x_div2.hw,	&cpu_4x_div2.reg },
+	[CLK_CPU_1X]		= { &cpu_4x_div4.hw,	&cpu_4x_div4.reg },
+	[CLK_IO_PLL]		= { &io_pll.hw,		&io_pll.reg },
+	[CLK_IO_1000M]		= { &io_1000m.hw,	&io_1000m.reg },
+	[CLK_IO_400M]		= { &io_400m.hw,	&io_400m.reg },
+	[CLK_IO_25M]		= { &io_25m.hw,		&io_25m.reg },
+	[CLK_IO_80M]		= { &io_80m.hw,		&io_80m.reg },
+	[CLK_IO_400M_DIV2]	= { &io_400m_div2.hw,	&io_400m_div2.reg },
+	[CLK_IO_400M_DIV4]	= { &io_400m_div4.hw,	&io_400m_div4.reg },
+	[CLK_IO_400M_DIV8]	= { &io_400m_div8.hw,	&io_400m_div8.reg },
+	[CLK_IO_400M_DIV16]	= { &io_400m_div16.hw,	&io_400m_div16.reg },
+	[CLK_QSPI]		= { &qspi.divider.hw,	&qspi.divider.reg },
+	[CLK_SPI]		= { &spi.divider.hw,	&spi.divider.reg },
+	[CLK_SMC]		= { &smc.divider.hw,	&smc.divider.reg },
+	[CLK_SDIO]		= { &sdio.hw,		&sdio.reg },
+	[CLK_GPIO_DB]		= { &gpio_db.divider.hw, &gpio_db.divider.reg },
+	[CLK_EFUSE]		= { &efuse.divider.hw,	&efuse.divider.reg },
+	[CLK_TVS]		= { &tvs.divider.hw,	&tvs.divider.reg },
+	[CLK_TRNG]		= { &trng.divider.hw,	&trng.divider.reg },
+	[CLK_OSC_DIV]		= { &osc_div.divider.hw, &osc_div.divider.reg },
+	[CLK_PWM]		= { &pwm.divider.hw,	&pwm.divider.reg },
+	[CLK_FCLK0]		= { &fclk0.divider.hw,	&fclk0.divider.reg },
+	[CLK_FCLK1]		= { &fclk1.divider.hw,	&fclk1.divider.reg },
+	[CLK_FCLK2]		= { &fclk2.divider.hw,	&fclk2.divider.reg },
+	[CLK_FCLK3]		= { &fclk3.divider.hw,	&fclk3.divider.reg },
+	[CLK_WDT_SEL]		= { &wdt_sel.hw,	&wdt_sel.reg },
+	[CLK_EFUSE_SEL]		= { &efuse_sel.hw,	&efuse_sel.reg },
+	[CLK_CAN_SEL]		= { &can_sel.hw,	&can_sel.reg },
+	[CLK_CPU_SEL]		= { &cpu_sel.hw,	&cpu_sel.reg },
+	[CLK_CAN0]		= { &can0.hw,		&can0.reg },
+	[CLK_CAN1]		= { &can1.hw,		&can1.reg }
+};
+
+static int dr1v90_cru_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	void __iomem *base;
+	int ret;
+
+	base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(base))
+		return PTR_ERR(base);
+
+	ret = dr1_cru_clk_register(dev, base, dr1v90_cru_clks,
+				   ARRAY_SIZE(dr1v90_cru_clks));
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to register clocks\n");
+
+	ret = dr1_cru_reset_register(dev);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to register resets\n");
+
+	return 0;
+}
+
+static const struct of_device_id dr1v90_cru_ids[] = {
+	{ .compatible = "anlogic,dr1v90-cru" },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, dr1v90_cru_ids);
+
+static struct platform_driver dr1v90_cru_driver = {
+	.driver = {
+		.name = "dr1v90-cru",
+		.of_match_table = dr1v90_cru_ids,
+	},
+	.probe = dr1v90_cru_probe,
+};
+module_platform_driver(dr1v90_cru_driver);
+
+MODULE_AUTHOR("Junhui Liu <junhui.liu@pigmoral.tech>");
+MODULE_DESCRIPTION("Anlogic DR1V90 CRU driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/clk/anlogic/cru_dr1.c b/drivers/clk/anlogic/cru_dr1.c
new file mode 100644
index 0000000000000000000000000000000000000000..5645149fd8cd9195b9075eaa278f37bb3cb118e7
--- /dev/null
+++ b/drivers/clk/anlogic/cru_dr1.c
@@ -0,0 +1,258 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2025 Shanghai Anlogic Infotech Co., Ltd.
+ * Copyright (c) 2025 Junhui Liu <junhui.liu@pigmoral.tech>
+ */
+
+#include <linux/auxiliary_bus.h>
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/clk-provider.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+
+#include "cru_dr1.h"
+
+static unsigned long cru_pll_nm_recalc_rate(struct clk_hw *hw,
+					    unsigned long parent_rate)
+{
+	struct cru_pll *pll = hw_to_cru_pll(hw);
+	u32 mult, div;
+
+	div = FIELD_GET(GENMASK(6, 0), readl(pll->reg)) + 1;
+	mult = FIELD_GET(GENMASK(6, 0), readl(pll->reg + 4)) + 1;
+
+	return parent_rate * mult / div;
+}
+
+const struct clk_ops dr1_cru_pll_nm_ops = {
+	.recalc_rate = cru_pll_nm_recalc_rate,
+};
+
+static unsigned long cru_pll_c_recalc_rate(struct clk_hw *hw,
+					   unsigned long parent_rate)
+{
+	struct cru_pll *pll = hw_to_cru_pll(hw);
+	u32 div;
+
+	div = FIELD_GET(GENMASK(30, 24), readl(pll->reg)) + 1;
+
+	return parent_rate / div;
+}
+
+const struct clk_ops dr1_cru_pll_c_ops = {
+	.recalc_rate = cru_pll_c_recalc_rate,
+};
+
+static void cru_div_gate_endisable(struct clk_hw *hw, int enable)
+{
+	struct cru_div_gate *div_gate = hw_to_cru_div_gate(hw);
+	struct clk_divider *divider = &div_gate->divider;
+	u32 reg;
+
+	reg = readl(divider->reg);
+	reg &= ~(clk_div_mask(divider->width) << divider->shift);
+
+	if (enable)
+		reg |= div_gate->val << divider->shift;
+
+	writel(reg, divider->reg);
+}
+
+static int cru_div_gate_enable(struct clk_hw *hw)
+{
+	cru_div_gate_endisable(hw, 1);
+
+	return 0;
+}
+
+static void cru_div_gate_disable(struct clk_hw *hw)
+{
+	cru_div_gate_endisable(hw, 0);
+}
+
+static int cru_div_gate_is_enabled(struct clk_hw *hw)
+{
+	struct cru_div_gate *div_gate = hw_to_cru_div_gate(hw);
+	struct clk_divider *divider = &div_gate->divider;
+	u32 val;
+
+	val = readl(divider->reg) >> divider->shift;
+	val &= clk_div_mask(divider->width);
+
+	return !!val;
+}
+
+static unsigned long cru_div_gate_recalc_rate(struct clk_hw *hw,
+					      unsigned long parent_rate)
+{
+	struct cru_div_gate *div_gate = hw_to_cru_div_gate(hw);
+	struct clk_divider *divider = &div_gate->divider;
+	unsigned int val;
+
+	val = readl(divider->reg) >> divider->shift;
+	val &= clk_div_mask(divider->width);
+
+	if (val < div_gate->min)
+		return 0;
+
+	return divider_recalc_rate(hw, parent_rate, val, divider->table,
+				   divider->flags, divider->width);
+}
+
+static long cru_div_gate_round_rate(struct clk_hw *hw, unsigned long rate,
+				    unsigned long *prate)
+{
+	struct clk_divider *divider = to_clk_divider(hw);
+
+	return divider_round_rate(hw, rate, prate, divider->table,
+				  divider->width, divider->flags);
+}
+
+static int cru_div_gate_determine_rate(struct clk_hw *hw,
+				       struct clk_rate_request *req)
+{
+	struct cru_div_gate *div_gate = hw_to_cru_div_gate(hw);
+	struct clk_divider *divider = &div_gate->divider;
+	unsigned long maxdiv, mindiv;
+	int div = 0;
+
+	maxdiv = clk_div_mask(divider->width) + 1;
+	mindiv = div_gate->min + 1;
+
+	div = DIV_ROUND_UP_ULL(req->best_parent_rate, req->rate);
+	div = div > maxdiv ? maxdiv : div;
+	div = div < mindiv ? mindiv : div;
+
+	req->rate = DIV_ROUND_UP_ULL(req->best_parent_rate, div);
+
+	return 0;
+}
+
+static int cru_div_gate_set_rate(struct clk_hw *hw, unsigned long rate,
+				 unsigned long parent_rate)
+{
+	struct cru_div_gate *div_gate = hw_to_cru_div_gate(hw);
+	struct clk_divider *divider = &div_gate->divider;
+	int value;
+	u32 reg;
+
+	if (!__clk_get_enable_count(hw->clk))
+		return 0;
+
+	value = divider_get_val(rate, parent_rate, divider->table,
+				divider->width, divider->flags);
+	if (value < 0)
+		return value;
+
+	if (value < div_gate->min)
+		value = div_gate->min;
+
+	reg = readl(divider->reg);
+	reg &= ~(clk_div_mask(divider->width) << divider->shift);
+	reg |= (u32)value << divider->shift;
+	writel(reg, divider->reg);
+
+	div_gate->val = reg;
+
+	return 0;
+}
+
+static int cru_div_gate_init(struct clk_hw *hw)
+{
+	struct cru_div_gate *div_gate = hw_to_cru_div_gate(hw);
+	struct clk_divider *divider = &div_gate->divider;
+	u32 val;
+
+	val = readl(divider->reg) >> divider->shift;
+	val &= clk_div_mask(divider->width);
+	div_gate->val = val;
+
+	return 0;
+}
+
+const struct clk_ops dr1_cru_div_gate_ops = {
+	.enable = cru_div_gate_enable,
+	.disable = cru_div_gate_disable,
+	.is_enabled = cru_div_gate_is_enabled,
+	.recalc_rate = cru_div_gate_recalc_rate,
+	.round_rate = cru_div_gate_round_rate,
+	.determine_rate = cru_div_gate_determine_rate,
+	.set_rate = cru_div_gate_set_rate,
+	.init = cru_div_gate_init,
+};
+
+int dr1_cru_clk_register(struct device *dev, void __iomem *base,
+			 const struct cru_clk *clks, int nr_clks)
+{
+	struct clk_hw_onecell_data *priv;
+	int i, ret;
+
+	priv = devm_kzalloc(dev, struct_size(priv, hws, nr_clks), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	for (i = 0; i < nr_clks; i++) {
+		const struct cru_clk *clk = &clks[i];
+
+		if (clk->reg)
+			*(clk->reg) += (uintptr_t)base;
+
+		ret = devm_clk_hw_register(dev, clk->hw);
+		if (ret)
+			return ret;
+
+		priv->hws[i] = clk->hw;
+	}
+
+	priv->num = nr_clks;
+
+	ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, priv);
+	if (ret)
+		dev_err(dev, "failed to add clock hardware provider\n");
+
+	return ret;
+}
+
+static void dr1_cru_cadev_release(struct device *dev)
+{
+	struct auxiliary_device *adev = to_auxiliary_dev(dev);
+
+	kfree(adev);
+}
+
+static void dr1_cru_adev_unregister(void *_adev)
+{
+	struct auxiliary_device *adev = _adev;
+
+	auxiliary_device_delete(adev);
+	auxiliary_device_uninit(adev);
+}
+
+int dr1_cru_reset_register(struct device *dev)
+{
+	struct auxiliary_device *adev __free(kfree);
+	int ret;
+
+	adev = kzalloc(sizeof(*adev), GFP_KERNEL);
+	if (!adev)
+		return -ENOMEM;
+
+	adev->name = "reset";
+	adev->dev.parent = dev;
+	adev->dev.release = dr1_cru_cadev_release;
+
+	ret = auxiliary_device_init(adev);
+	if (ret)
+		return ret;
+
+	ret = auxiliary_device_add(adev);
+	if (ret) {
+		auxiliary_device_uninit(adev);
+		return ret;
+	}
+
+	return devm_add_action_or_reset(dev, dr1_cru_adev_unregister, adev);
+}
diff --git a/drivers/clk/anlogic/cru_dr1.h b/drivers/clk/anlogic/cru_dr1.h
new file mode 100644
index 0000000000000000000000000000000000000000..4599a3c36d08e8d20105a225336b87426821143b
--- /dev/null
+++ b/drivers/clk/anlogic/cru_dr1.h
@@ -0,0 +1,117 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2025 Shanghai Anlogic Infotech Co., Ltd.
+ * Copyright (c) 2025 Junhui Liu <junhui.liu@pigmoral.tech>
+ */
+
+#ifndef _CRU_DR1_H_
+#define _CRU_DR1_H_
+
+#include "linux/clk-provider.h"
+
+struct cru_pll {
+	struct clk_hw hw;
+	void __iomem *reg;
+};
+
+struct cru_div_gate {
+	struct clk_divider divider;
+	u32 val;
+	u8 min; /* Minimum divider value to avoid timing issues */
+};
+
+struct cru_clk {
+	struct clk_hw *hw;
+	void **reg;
+};
+
+#define CRU_PARENT_NAME(_name)		{ .fw_name = #_name }
+#define CRU_PARENT_HW(_parent)		{ .hw = &_parent.hw }
+#define CRU_PARENT_DIV_HW(_parent)	{ .hw = &_parent.divider.hw }
+
+#define CRU_INITHW(_name, _parent, _ops)				\
+	.hw.init = &(struct clk_init_data) {				\
+		.name		= #_name,				\
+		.parent_data	= (const struct clk_parent_data[])	\
+					{ _parent },			\
+		.num_parents	= 1,					\
+		.ops		= &_ops,				\
+	}
+
+#define CRU_INITHW_PARENTS(_name, _parents, _ops)			\
+	.hw.init = CLK_HW_INIT_PARENTS_DATA(#_name, _parents, &_ops, 0)
+
+#define CRU_PLL_NM_DEFINE(_name, _parent, _reg)				\
+static struct cru_pll _name = {						\
+	.reg = (void __iomem *)(_reg),					\
+	CRU_INITHW(_name, _parent, dr1_cru_pll_nm_ops),			\
+}
+
+#define CRU_PLL_C_DEFINE(_name, _parent, _reg)				\
+static struct cru_pll _name = {						\
+	.reg = (void __iomem *)(_reg),					\
+	CRU_INITHW(_name, _parent, dr1_cru_pll_c_ops),			\
+}
+
+#define CRU_DIV_DEFINE(_name, _parent, _reg, _shift, _width, _table,	\
+		       _flags)						\
+static struct clk_divider _name = {					\
+	.shift = _shift,						\
+	.width = _width,						\
+	.flags = _flags,						\
+	.table = _table,						\
+	.reg = (void __iomem *)(_reg),					\
+	CRU_INITHW(_name, _parent, clk_divider_ops),			\
+}
+
+#define CRU_DIV_GATE_DEFINE(_name, _parent, _reg, _shift, _width,	\
+			    _table, _flags, _min)			\
+static struct cru_div_gate _name = {					\
+	.min = _min,							\
+	.divider = {							\
+		.shift = _shift,					\
+		.width = _width,					\
+		.flags = _flags,					\
+		.table = _table,					\
+		.reg = (void __iomem *)(_reg),				\
+		CRU_INITHW(_name, _parent, dr1_cru_div_gate_ops),	\
+	}								\
+}
+
+#define CRU_MUX_DEFINE(_name, _parents, _reg, _shift, _width)		\
+static struct clk_mux _name = {						\
+	.shift = _shift,						\
+	.mask = GENMASK(_width - 1, 0),					\
+	.reg = (void __iomem *)(_reg),					\
+	CRU_INITHW_PARENTS(_name, _parents, clk_mux_ops)		\
+}
+
+#define CRU_GATE_DEFINE(_name, _parent, _reg, _bit_idx, _flags)		\
+static struct clk_gate _name = {					\
+	.bit_idx = _bit_idx,						\
+	.flags = _flags,						\
+	.reg = (void __iomem *)(_reg),					\
+	CRU_INITHW(_name, _parent, clk_gate_ops)			\
+}
+
+static inline struct cru_pll *hw_to_cru_pll(struct clk_hw *hw)
+{
+	return container_of(hw, struct cru_pll, hw);
+}
+
+static inline struct cru_div_gate *hw_to_cru_div_gate(struct clk_hw *hw)
+{
+	struct clk_divider *divider = to_clk_divider(hw);
+
+	return container_of(divider, struct cru_div_gate, divider);
+}
+
+extern const struct clk_ops dr1_cru_pll_nm_ops;
+extern const struct clk_ops dr1_cru_pll_c_ops;
+extern const struct clk_ops dr1_cru_div_gate_ops;
+
+int dr1_cru_clk_register(struct device *dev, void __iomem *base,
+			 const struct cru_clk *clks, int nr_clks);
+int dr1_cru_reset_register(struct device *dev);
+
+#endif /* _CRU_DR1_H_ */

-- 
2.51.0