[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <20250922-dr1v90-cru-v1-3-e393d758de4e@pigmoral.tech>
Date: Mon, 22 Sep 2025 22:51:49 +0800
From: Junhui Liu <junhui.liu@...moral.tech>
To: Michael Turquette <mturquette@...libre.com>,
Stephen Boyd <sboyd@...nel.org>, Rob Herring <robh@...nel.org>,
Krzysztof Kozlowski <krzk+dt@...nel.org>,
Conor Dooley <conor+dt@...nel.org>, Junhui Liu <junhui.liu@...moral.tech>,
Philipp Zabel <p.zabel@...gutronix.de>,
Paul Walmsley <paul.walmsley@...ive.com>,
Palmer Dabbelt <palmer@...belt.com>, Albert Ou <aou@...s.berkeley.edu>,
Alexandre Ghiti <alex@...ti.fr>
Cc: linux-clk@...r.kernel.org, linux-kernel@...r.kernel.org,
devicetree@...r.kernel.org, linux-riscv@...ts.infradead.org,
"fushan.zeng" <fushan.zeng@...ogic.com>
Subject: [PATCH 3/5] clk: anlogic: add cru support for Anlogic DR1V90 SoC
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@...moral.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@...moral.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@...moral.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@...moral.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@...moral.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
Powered by blists - more mailing lists