* [PATCH 0/2] clk: ultrarisc: add DP1000 clock support
@ 2026-06-11 9:40 Jia Wang via B4 Relay
2026-06-11 9:40 ` [PATCH 1/2] dt-bindings: clock: ultrarisc: Add DP1000 Clock Controller Jia Wang via B4 Relay
2026-06-11 9:40 ` [PATCH 2/2] clk: ultrarisc: add DP1000 clock driver Jia Wang via B4 Relay
0 siblings, 2 replies; 4+ messages in thread
From: Jia Wang via B4 Relay @ 2026-06-11 9:40 UTC (permalink / raw)
To: Michael Turquette, Stephen Boyd, Brian Masney, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: linux-clk, devicetree, linux-kernel, Jia Wang
This series adds the devicetree binding and the clock driver for the
UltraRISC DP1000 SoC.
The clock tree is driven by a single SYSPLL and provides fixed-factor
clocks for the subsystem including PCIe, divider-based root clocks for
GMAC and the UART, I2C, and SPI blocks, and per-instance gate clocks
for UART0-3, I2C0-3, and SPI0-1.
Testing:
- dt_binding_check and dtbs_check on ultrarisc,dp1000-clk
- Kernel build for RISC-V and boot-tested on DP1000
Signed-off-by: Jia Wang <wangjia@ultrarisc.com>
---
Jia Wang (2):
dt-bindings: clock: ultrarisc: Add DP1000 Clock Controller
clk: ultrarisc: add DP1000 clock driver
.../bindings/clock/ultrarisc,dp1000-clk.yaml | 72 ++++
MAINTAINERS | 8 +
drivers/clk/Kconfig | 1 +
drivers/clk/Makefile | 1 +
drivers/clk/ultrarisc/Kconfig | 18 +
drivers/clk/ultrarisc/Makefile | 4 +
drivers/clk/ultrarisc/clk-dp1000.c | 152 ++++++++
drivers/clk/ultrarisc/clk-ultrarisc.c | 417 +++++++++++++++++++++
drivers/clk/ultrarisc/clk-ultrarisc.h | 73 ++++
include/dt-bindings/clock/ultrarisc,dp1000-clk.h | 29 ++
10 files changed, 775 insertions(+)
---
base-commit: 2d3090a8aeb596a26935db0955d46c9a5db5c6ce
change-id: 20260522-ultrarisc-clock-a1b7aa59f60b
Best regards,
--
Jia Wang <wangjia@ultrarisc.com>
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH 1/2] dt-bindings: clock: ultrarisc: Add DP1000 Clock Controller
2026-06-11 9:40 [PATCH 0/2] clk: ultrarisc: add DP1000 clock support Jia Wang via B4 Relay
@ 2026-06-11 9:40 ` Jia Wang via B4 Relay
2026-06-11 9:40 ` [PATCH 2/2] clk: ultrarisc: add DP1000 clock driver Jia Wang via B4 Relay
1 sibling, 0 replies; 4+ messages in thread
From: Jia Wang via B4 Relay @ 2026-06-11 9:40 UTC (permalink / raw)
To: Michael Turquette, Stephen Boyd, Brian Masney, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: linux-clk, devicetree, linux-kernel, Jia Wang
From: Jia Wang <wangjia@ultrarisc.com>
Add doc for the clock controller on the UltraRISC DP1000 RISC-V SoC.
Signed-off-by: Jia Wang <wangjia@ultrarisc.com>
---
.../bindings/clock/ultrarisc,dp1000-clk.yaml | 72 ++++++++++++++++++++++
MAINTAINERS | 7 +++
include/dt-bindings/clock/ultrarisc,dp1000-clk.h | 29 +++++++++
3 files changed, 108 insertions(+)
diff --git a/Documentation/devicetree/bindings/clock/ultrarisc,dp1000-clk.yaml b/Documentation/devicetree/bindings/clock/ultrarisc,dp1000-clk.yaml
new file mode 100644
index 000000000000..d4d3d851a079
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/ultrarisc,dp1000-clk.yaml
@@ -0,0 +1,72 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/clock/ultrarisc,dp1000-clk.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: UltraRISC DP1000 Clock Controller
+
+maintainers:
+ - Jia Wang <wangjia@ultrarisc.com>
+
+description: |
+ The UltraRISC DP1000 clock controller is driven from a single external
+ oscillator input. It provides a system PLL with fractional multiplier
+ and post-divider stages, several fixed-ratio derived clocks for
+ the on-chip subsystem, Clock Configuration Register (CCR) divider
+ outputs for GMAC and the UART, I2C, and SPI root clocks, and
+ per-instance gate clocks for UART0-3, I2C0-3, and SPI0-1.
+
+ All available clocks are defined as preprocessor macros in
+ include/dt-bindings/clock/ultrarisc,dp1000-clk.h
+
+properties:
+ compatible:
+ const: ultrarisc,dp1000-clk
+
+ reg:
+ maxItems: 1
+
+ clocks:
+ maxItems: 1
+ description:
+ External oscillator input clock used as the parent of the PLLs.
+
+ clock-names:
+ items:
+ - const: osc
+
+ "#clock-cells":
+ const: 1
+
+required:
+ - compatible
+ - reg
+ - clocks
+ - clock-names
+ - "#clock-cells"
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/clock/ultrarisc,dp1000-clk.h>
+
+ osc: oscillator {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <24000000>;
+ };
+
+ soc {
+ #address-cells = <2>;
+ #size-cells = <2>;
+
+ clock-controller@11080000 {
+ compatible = "ultrarisc,dp1000-clk";
+ reg = <0x0 0x11080000 0x0 0x1000>;
+ clocks = <&osc>;
+ clock-names = "osc";
+ #clock-cells = <1>;
+ };
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index e035a3be797c..3331f1edf002 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -27357,6 +27357,13 @@ S: Maintained
F: drivers/usb/common/ulpi.c
F: include/linux/ulpi/
+ULTRARISC DP1000 CLOCK DRIVER
+M: Jia Wang <wangjia@ultrarisc.com>
+L: linux-clk@vger.kernel.org
+S: Maintained
+F: Documentation/devicetree/bindings/clock/ultrarisc,dp1000-clk.yaml
+F: include/dt-bindings/clock/ultrarisc,dp1000-clk.h
+
ULTRATRONIK BOARD SUPPORT
M: Goran Rađenović <goran.radni@gmail.com>
M: Börge Strümpfel <boerge.struempfel@gmail.com>
diff --git a/include/dt-bindings/clock/ultrarisc,dp1000-clk.h b/include/dt-bindings/clock/ultrarisc,dp1000-clk.h
new file mode 100644
index 000000000000..5e484f652b08
--- /dev/null
+++ b/include/dt-bindings/clock/ultrarisc,dp1000-clk.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
+#ifndef _DT_BINDINGS_CLOCK_ULTRARISC_DP1000_CLK_H
+#define _DT_BINDINGS_CLOCK_ULTRARISC_DP1000_CLK_H
+
+#define DP1000_CLK_SYSPLL 0
+#define DP1000_CLK_SYSPLL_DIV2 1
+#define DP1000_CLK_SUBSYS 2
+#define DP1000_CLK_GMAC 3
+#define DP1000_CLK_UART_ROOT 4
+#define DP1000_CLK_I2C_ROOT 5
+#define DP1000_CLK_SPI_ROOT 6
+#define DP1000_CLK_PCIE_DBI 7
+#define DP1000_CLK_PCIEX4_CORE 8
+#define DP1000_CLK_PCIEX16_CORE 9
+#define DP1000_CLK_PCIE_AUX 10
+#define DP1000_CLK_UART0 11
+#define DP1000_CLK_UART1 12
+#define DP1000_CLK_UART2 13
+#define DP1000_CLK_UART3 14
+#define DP1000_CLK_I2C0 15
+#define DP1000_CLK_I2C1 16
+#define DP1000_CLK_I2C2 17
+#define DP1000_CLK_I2C3 18
+#define DP1000_CLK_SPI0 19
+#define DP1000_CLK_SPI1 20
+
+#define DP1000_CLK_NUM 21
+
+#endif /* _DT_BINDINGS_CLOCK_ULTRARISC_DP1000_CLK_H */
--
2.34.1
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH 2/2] clk: ultrarisc: add DP1000 clock driver
2026-06-11 9:40 [PATCH 0/2] clk: ultrarisc: add DP1000 clock support Jia Wang via B4 Relay
2026-06-11 9:40 ` [PATCH 1/2] dt-bindings: clock: ultrarisc: Add DP1000 Clock Controller Jia Wang via B4 Relay
@ 2026-06-11 9:40 ` Jia Wang via B4 Relay
2026-06-11 9:56 ` sashiko-bot
1 sibling, 1 reply; 4+ messages in thread
From: Jia Wang via B4 Relay @ 2026-06-11 9:40 UTC (permalink / raw)
To: Michael Turquette, Stephen Boyd, Brian Masney, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: linux-clk, devicetree, linux-kernel, Jia Wang
From: Jia Wang <wangjia@ultrarisc.com>
Add a clock driver for the UltraRISC DP1000 SoC.
The clock tree is driven by a SYSPLL and provides fixed-factor clocks for
the subsystem and PCIe, divider-based root clocks for GMAC and the UART,
I2C, and SPI blocks, and per-instance gate clocks for UART0-3, I2C0-3,
and SPI0-1.
Signed-off-by: Jia Wang <wangjia@ultrarisc.com>
---
MAINTAINERS | 1 +
drivers/clk/Kconfig | 1 +
drivers/clk/Makefile | 1 +
drivers/clk/ultrarisc/Kconfig | 18 ++
drivers/clk/ultrarisc/Makefile | 4 +
drivers/clk/ultrarisc/clk-dp1000.c | 152 +++++++++++++
drivers/clk/ultrarisc/clk-ultrarisc.c | 417 ++++++++++++++++++++++++++++++++++
drivers/clk/ultrarisc/clk-ultrarisc.h | 73 ++++++
8 files changed, 667 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 3331f1edf002..ed2827094d7f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -27362,6 +27362,7 @@ M: Jia Wang <wangjia@ultrarisc.com>
L: linux-clk@vger.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/clock/ultrarisc,dp1000-clk.yaml
+F: drivers/clk/ultrarisc/*
F: include/dt-bindings/clock/ultrarisc,dp1000-clk.h
ULTRATRONIK BOARD SUPPORT
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index b2efbe9f6acb..75e336858420 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -535,6 +535,7 @@ source "drivers/clk/tenstorrent/Kconfig"
source "drivers/clk/thead/Kconfig"
source "drivers/clk/stm32/Kconfig"
source "drivers/clk/ti/Kconfig"
+source "drivers/clk/ultrarisc/Kconfig"
source "drivers/clk/uniphier/Kconfig"
source "drivers/clk/visconti/Kconfig"
source "drivers/clk/x86/Kconfig"
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index a3e2862ebd7e..891272e8e1da 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -158,6 +158,7 @@ obj-$(CONFIG_ARCH_TEGRA) += tegra/
obj-y += tenstorrent/
obj-$(CONFIG_ARCH_THEAD) += thead/
obj-y += ti/
+obj-y += ultrarisc/
obj-$(CONFIG_CLK_UNIPHIER) += uniphier/
obj-$(CONFIG_ARCH_U8500) += ux500/
obj-y += versatile/
diff --git a/drivers/clk/ultrarisc/Kconfig b/drivers/clk/ultrarisc/Kconfig
new file mode 100644
index 000000000000..2eecc6ac3119
--- /dev/null
+++ b/drivers/clk/ultrarisc/Kconfig
@@ -0,0 +1,18 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+config CLK_ULTRARISC
+ tristate
+ depends on OF
+ depends on ARCH_ULTRARISC || COMPILE_TEST
+
+config CLK_ULTRARISC_DP1000
+ tristate "UltraRISC DP1000 clock controller"
+ select CLK_ULTRARISC
+ depends on OF && HAS_IOMEM
+ depends on ARCH_ULTRARISC || COMPILE_TEST
+ default ARCH_ULTRARISC
+ help
+ This driver provides the clock controller for the UltraRISC
+ DP1000 SoC. It exposes the PLL output, derived fixed-factor
+ clocks, programmable divider clocks, and peripheral gate
+ clocks to Linux consumers.
diff --git a/drivers/clk/ultrarisc/Makefile b/drivers/clk/ultrarisc/Makefile
new file mode 100644
index 000000000000..b013708c9444
--- /dev/null
+++ b/drivers/clk/ultrarisc/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+obj-$(CONFIG_CLK_ULTRARISC) += clk-ultrarisc.o
+obj-$(CONFIG_CLK_ULTRARISC_DP1000) += clk-dp1000.o
diff --git a/drivers/clk/ultrarisc/clk-dp1000.c b/drivers/clk/ultrarisc/clk-dp1000.c
new file mode 100644
index 000000000000..0d4531f30023
--- /dev/null
+++ b/drivers/clk/ultrarisc/clk-dp1000.c
@@ -0,0 +1,152 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2026 UltraRISC Technology (Shanghai) Co., Ltd.
+ */
+
+#include <linux/module.h>
+
+#include <dt-bindings/clock/ultrarisc,dp1000-clk.h>
+
+#include "clk-ultrarisc.h"
+
+#define DP1000_PLL_CFG1_OFFSET 0x400
+#define DP1000_PLL_CFG2_OFFSET 0x404
+
+#define DP1000_CCR_UART_OFFSET 0x220
+#define DP1000_CCR_I2C_OFFSET 0x224
+#define DP1000_CCR_GMAC_OFFSET 0x228
+#define DP1000_CCR_SPI_OFFSET 0x22c
+#define DP1000_PERI_CLKENA_OFFSET 0x270
+
+#define DP1000_CCR_LOAD BIT(16)
+
+#define DP1000_PERI_MAX_RATE 62500000UL
+
+static const struct ultrarisc_pll_layout dp1000_pll_layout = {
+ .cfg1_offset = DP1000_PLL_CFG1_OFFSET,
+ .cfg2_offset = DP1000_PLL_CFG2_OFFSET,
+ .frac_mask = GENMASK(23, 0),
+ .m_mask = GENMASK(23, 16),
+ .n_mask = GENMASK(11, 6),
+ .oddiv1_mask = GENMASK(4, 3),
+ .oddiv2_mask = GENMASK(1, 0),
+};
+
+static const struct ultrarisc_pll_desc dp1000_plls[] = {
+ {
+ .id = DP1000_CLK_SYSPLL,
+ .name = "syspll_clk",
+ },
+};
+
+#define DP1000_FIXED_FACTOR(_id, _name, _parent, _mult, _div) \
+ { \
+ .id = (_id), \
+ .name = (_name), \
+ .parent_id = (_parent), \
+ .mult = (_mult), \
+ .div = (_div), \
+ }
+
+#define DP1000_DIV(_id, _name, _offset, _parent, _max_rate) \
+ { \
+ .id = (_id), \
+ .name = (_name), \
+ .offset = (_offset), \
+ .parent_id = (_parent), \
+ .max_rate = (_max_rate), \
+ .load_mask = DP1000_CCR_LOAD, \
+ .div_shift = 8, \
+ .div_width = 4, \
+ .gate_bit = 0, \
+ .divider_flags = CLK_DIVIDER_ONE_BASED, \
+ .gate_flags = 0, \
+ }
+
+#define DP1000_GATE(_id, _name, _parent, _bit) \
+ { \
+ .id = (_id), \
+ .name = (_name), \
+ .offset = DP1000_PERI_CLKENA_OFFSET, \
+ .parent_id = (_parent), \
+ .gate_bit = (_bit), \
+ .gate_flags = 0, \
+ }
+
+static const struct ultrarisc_fixed_factor_desc dp1000_fixed_factor_clks[] = {
+ DP1000_FIXED_FACTOR(DP1000_CLK_SYSPLL_DIV2, "syspll_div2_clk",
+ DP1000_CLK_SYSPLL, 1, 2),
+ DP1000_FIXED_FACTOR(DP1000_CLK_SUBSYS, "subsys_clk",
+ DP1000_CLK_SYSPLL_DIV2, 1, 2),
+ DP1000_FIXED_FACTOR(DP1000_CLK_PCIE_DBI, "pcie_dbi_clk",
+ DP1000_CLK_SYSPLL, 1, 10),
+ DP1000_FIXED_FACTOR(DP1000_CLK_PCIEX4_CORE, "pciex4_core_clk",
+ DP1000_CLK_SYSPLL, 1, 2),
+ DP1000_FIXED_FACTOR(DP1000_CLK_PCIEX16_CORE, "pciex16_core_clk",
+ DP1000_CLK_SYSPLL, 1, 1),
+ DP1000_FIXED_FACTOR(DP1000_CLK_PCIE_AUX, "pcie_aux_clk",
+ DP1000_CLK_SYSPLL, 1, 40),
+};
+
+static const struct ultrarisc_divider_desc dp1000_divider_clks[] = {
+ DP1000_DIV(DP1000_CLK_GMAC, "gmac_clk", DP1000_CCR_GMAC_OFFSET,
+ DP1000_CLK_SYSPLL_DIV2, 0),
+ DP1000_DIV(DP1000_CLK_UART_ROOT, "uart_root_clk",
+ DP1000_CCR_UART_OFFSET, DP1000_CLK_SUBSYS,
+ DP1000_PERI_MAX_RATE),
+ DP1000_DIV(DP1000_CLK_I2C_ROOT, "i2c_root_clk",
+ DP1000_CCR_I2C_OFFSET, DP1000_CLK_SUBSYS,
+ DP1000_PERI_MAX_RATE),
+ DP1000_DIV(DP1000_CLK_SPI_ROOT, "spi_root_clk",
+ DP1000_CCR_SPI_OFFSET, DP1000_CLK_SUBSYS,
+ DP1000_PERI_MAX_RATE),
+};
+
+static const struct ultrarisc_gate_desc dp1000_gate_clks[] = {
+ DP1000_GATE(DP1000_CLK_UART0, "uart0_clk", DP1000_CLK_UART_ROOT, 0),
+ DP1000_GATE(DP1000_CLK_UART1, "uart1_clk", DP1000_CLK_UART_ROOT, 1),
+ DP1000_GATE(DP1000_CLK_UART2, "uart2_clk", DP1000_CLK_UART_ROOT, 2),
+ DP1000_GATE(DP1000_CLK_UART3, "uart3_clk", DP1000_CLK_UART_ROOT, 3),
+ DP1000_GATE(DP1000_CLK_I2C0, "i2c0_clk", DP1000_CLK_I2C_ROOT, 4),
+ DP1000_GATE(DP1000_CLK_I2C1, "i2c1_clk", DP1000_CLK_I2C_ROOT, 5),
+ DP1000_GATE(DP1000_CLK_I2C2, "i2c2_clk", DP1000_CLK_I2C_ROOT, 6),
+ DP1000_GATE(DP1000_CLK_I2C3, "i2c3_clk", DP1000_CLK_I2C_ROOT, 7),
+ DP1000_GATE(DP1000_CLK_SPI0, "spi0_clk", DP1000_CLK_SPI_ROOT, 8),
+ DP1000_GATE(DP1000_CLK_SPI1, "spi1_clk", DP1000_CLK_SPI_ROOT, 9),
+};
+
+static const struct ultrarisc_clk_soc_data dp1000_clk_soc_data = {
+ .num_clks = DP1000_CLK_NUM,
+ .pll_layout = &dp1000_pll_layout,
+ .plls = dp1000_plls,
+ .num_plls = ARRAY_SIZE(dp1000_plls),
+ .fixed_factors = dp1000_fixed_factor_clks,
+ .num_fixed_factors = ARRAY_SIZE(dp1000_fixed_factor_clks),
+ .dividers = dp1000_divider_clks,
+ .num_dividers = ARRAY_SIZE(dp1000_divider_clks),
+ .gates = dp1000_gate_clks,
+ .num_gates = ARRAY_SIZE(dp1000_gate_clks),
+};
+
+static int dp1000_clk_probe(struct platform_device *pdev)
+{
+ return ultrarisc_clk_probe(pdev, &dp1000_clk_soc_data);
+}
+
+static const struct of_device_id dp1000_clk_of_match[] = {
+ { .compatible = "ultrarisc,dp1000-clk" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, dp1000_clk_of_match);
+
+static struct platform_driver dp1000_clk_driver = {
+ .probe = dp1000_clk_probe,
+ .driver = {
+ .name = "ultrarisc-dp1000-clk",
+ .of_match_table = dp1000_clk_of_match,
+ },
+};
+module_platform_driver(dp1000_clk_driver);
+
+MODULE_DESCRIPTION("UltraRISC DP1000 clock controller");
+MODULE_LICENSE("GPL");
diff --git a/drivers/clk/ultrarisc/clk-ultrarisc.c b/drivers/clk/ultrarisc/clk-ultrarisc.c
new file mode 100644
index 000000000000..2b970565a4be
--- /dev/null
+++ b/drivers/clk/ultrarisc/clk-ultrarisc.c
@@ -0,0 +1,417 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2026 UltraRISC Technology (Shanghai) Co., Ltd.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk-provider.h>
+#include <linux/io.h>
+#include <linux/math64.h>
+#include <linux/of_clk.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include "clk-ultrarisc.h"
+
+struct ultrarisc_pll_clk {
+ struct clk_hw hw;
+ void __iomem *base;
+ const struct ultrarisc_pll_layout *layout;
+};
+
+struct ultrarisc_divider_clk {
+ struct clk_divider divider;
+ struct clk_gate gate;
+ u32 load_mask;
+};
+
+#define to_ultrarisc_pll_clk(_hw) \
+ container_of(_hw, struct ultrarisc_pll_clk, hw)
+
+static inline struct ultrarisc_divider_clk *
+to_ultrarisc_divider_clk(struct clk_hw *hw)
+{
+ struct clk_divider *divider = to_clk_divider(hw);
+
+ return container_of(divider, struct ultrarisc_divider_clk, divider);
+}
+
+static unsigned long ultrarisc_pll_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct ultrarisc_pll_clk *pll = to_ultrarisc_pll_clk(hw);
+ const struct ultrarisc_pll_layout *layout = pll->layout;
+ u32 oddiv1_div, oddiv2_div;
+ u64 mult, rate, den;
+ u32 frac, m, n;
+ u32 cfg1, cfg2;
+
+ cfg1 = readl_relaxed(pll->base + layout->cfg1_offset);
+ cfg2 = readl_relaxed(pll->base + layout->cfg2_offset);
+
+ frac = field_get(layout->frac_mask, cfg1);
+ m = field_get(layout->m_mask, cfg2);
+ n = field_get(layout->n_mask, cfg2);
+ oddiv1_div = 1U << field_get(layout->oddiv1_mask, cfg2);
+ oddiv2_div = 1U << field_get(layout->oddiv2_mask, cfg2);
+
+ if (!n)
+ return 0;
+
+ /*
+ * The output frequency is calculated as:
+ * fvco = parent * (m + frac / 2^24) / n
+ * fout = fvco / (2^oddiv1_raw * 2^oddiv2_raw)
+ *
+ * The output divider values are derived from the raw register field
+ * values as:
+ * oddivX_div = 1 << oddivX_raw
+ */
+ mult = ((u64)m << 24) + frac;
+ rate = (u64)parent_rate * mult;
+ den = ((u64)n << 24) * oddiv1_div * oddiv2_div;
+
+ return div64_u64(rate + (den >> 1), den);
+}
+
+static const struct clk_ops ultrarisc_pll_ro_ops = {
+ .recalc_rate = ultrarisc_pll_recalc_rate,
+};
+
+static unsigned long
+ultrarisc_divider_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
+{
+ struct clk_divider *divider = to_clk_divider(hw);
+ u32 val;
+
+ val = readl_relaxed(divider->reg) >> divider->shift;
+ val &= clk_div_mask(divider->width);
+
+ return divider_recalc_rate(hw, parent_rate, val, divider->table,
+ divider->flags, divider->width);
+}
+
+static int ultrarisc_divider_determine_rate(struct clk_hw *hw,
+ struct clk_rate_request *req)
+{
+ struct clk_divider *divider = to_clk_divider(hw);
+
+ return divider_determine_rate(hw, req, divider->table, divider->width,
+ divider->flags);
+}
+
+static int ultrarisc_divider_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct ultrarisc_divider_clk *divider_clk = to_ultrarisc_divider_clk(hw);
+ struct clk_divider *divider = ÷r_clk->divider;
+ int value;
+ u32 val;
+
+ value = divider_get_val(rate, parent_rate, divider->table,
+ divider->width, divider->flags);
+ if (value < 0)
+ return value;
+
+ scoped_guard(spinlock_irqsave, divider->lock) {
+ val = readl_relaxed(divider->reg);
+ val &= ~(clk_div_mask(divider->width) << divider->shift);
+ val |= value << divider->shift;
+ writel_relaxed(val, divider->reg);
+
+ if (divider_clk->load_mask) {
+ /*
+ * The divider update is triggered by writing 1 to the
+ * self-clearing load bit after programming the new
+ * divider field.
+ */
+ writel_relaxed(val | divider_clk->load_mask,
+ divider->reg);
+ }
+ }
+
+ return 0;
+}
+
+static const struct clk_ops ultrarisc_divider_ops = {
+ .recalc_rate = ultrarisc_divider_recalc_rate,
+ .determine_rate = ultrarisc_divider_determine_rate,
+ .set_rate = ultrarisc_divider_set_rate,
+};
+
+static struct clk_hw *ultrarisc_clk_register_pll(struct device *dev,
+ const struct ultrarisc_pll_desc *desc,
+ const struct ultrarisc_pll_layout *layout,
+ void __iomem *base)
+{
+ struct clk_parent_data pdata = { .index = 0 };
+ struct ultrarisc_pll_clk *pll;
+ struct clk_init_data init = {
+ .name = desc->name,
+ .ops = &ultrarisc_pll_ro_ops,
+ .parent_data = &pdata,
+ .num_parents = 1,
+ .flags = CLK_GET_RATE_NOCACHE,
+ };
+ int ret;
+
+ pll = devm_kzalloc(dev, sizeof(*pll), GFP_KERNEL);
+ if (!pll)
+ return ERR_PTR(-ENOMEM);
+
+ pll->base = base;
+ pll->layout = layout;
+ pll->hw.init = &init;
+
+ ret = devm_clk_hw_register(dev, &pll->hw);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return &pll->hw;
+}
+
+static struct clk_hw *
+ultrarisc_clk_register_divider(struct device *dev,
+ const struct ultrarisc_divider_desc *desc,
+ struct clk_hw *parent_hw, void __iomem *base,
+ spinlock_t *lock)
+{
+ struct clk_parent_data pdata = { .hw = parent_hw };
+ void __iomem *reg = base + desc->offset;
+ struct ultrarisc_divider_clk *divider;
+ const struct clk_ops *gate_ops = NULL;
+ struct clk_hw *gate_hw = NULL;
+
+ if (!desc->div_width)
+ return ERR_PTR(-EINVAL);
+
+ if (!lock)
+ return ERR_PTR(-EINVAL);
+
+ divider = devm_kzalloc(dev, sizeof(*divider), GFP_KERNEL);
+ if (!divider)
+ return ERR_PTR(-ENOMEM);
+
+ divider->divider.reg = reg;
+ divider->divider.shift = desc->div_shift;
+ divider->divider.width = desc->div_width;
+ divider->divider.flags = desc->divider_flags;
+ divider->divider.lock = lock;
+ divider->load_mask = desc->load_mask;
+
+ if (desc->gate_bit != ULTRARISC_CLK_NO_GATE) {
+ divider->gate.reg = reg;
+ divider->gate.bit_idx = desc->gate_bit;
+ divider->gate.flags = desc->gate_flags;
+ divider->gate.lock = lock;
+ gate_hw = ÷r->gate.hw;
+ gate_ops = &clk_gate_ops;
+ }
+
+ return devm_clk_hw_register_composite_pdata(dev, desc->name, &pdata, 1,
+ NULL, NULL,
+ ÷r->divider.hw,
+ &ultrarisc_divider_ops,
+ gate_hw, gate_ops,
+ CLK_GET_RATE_NOCACHE);
+}
+
+static int ultrarisc_clk_register_fixed_factors(struct device *dev,
+ struct clk_hw_onecell_data *clk_data,
+ const struct ultrarisc_clk_soc_data *soc_data)
+{
+ u32 i;
+
+ for (i = 0; i < soc_data->num_fixed_factors; i++) {
+ const struct ultrarisc_fixed_factor_desc *desc;
+ struct clk_hw *parent_hw;
+ struct clk_hw *hw;
+
+ desc = &soc_data->fixed_factors[i];
+ if (desc->id >= clk_data->num ||
+ desc->parent_id >= clk_data->num)
+ return -EINVAL;
+
+ parent_hw = clk_data->hws[desc->parent_id];
+ if (!parent_hw)
+ return -EINVAL;
+
+ hw = devm_clk_hw_register_fixed_factor_parent_hw(dev, desc->name,
+ parent_hw,
+ CLK_GET_RATE_NOCACHE,
+ desc->mult,
+ desc->div);
+ if (IS_ERR(hw))
+ return PTR_ERR(hw);
+
+ clk_data->hws[desc->id] = hw;
+ }
+
+ return 0;
+}
+
+static int ultrarisc_clk_register_plls(struct platform_device *pdev,
+ struct clk_hw_onecell_data *clk_data,
+ const struct ultrarisc_clk_soc_data *soc_data,
+ void __iomem *base)
+{
+ struct device *dev = &pdev->dev;
+ u32 i;
+
+ for (i = 0; i < soc_data->num_plls; i++) {
+ const struct ultrarisc_pll_desc *desc = &soc_data->plls[i];
+ struct clk_hw *hw;
+
+ if (desc->id >= clk_data->num)
+ return -EINVAL;
+
+ hw = ultrarisc_clk_register_pll(dev, desc, soc_data->pll_layout,
+ base);
+ if (IS_ERR(hw))
+ return PTR_ERR(hw);
+
+ clk_data->hws[desc->id] = hw;
+ }
+
+ return 0;
+}
+
+static int ultrarisc_clk_register_dividers(struct platform_device *pdev,
+ struct clk_hw_onecell_data *clk_data,
+ const struct ultrarisc_clk_soc_data *soc_data,
+ void __iomem *base,
+ spinlock_t *lock)
+{
+ struct device *dev = &pdev->dev;
+ u32 i;
+
+ for (i = 0; i < soc_data->num_dividers; i++) {
+ const struct ultrarisc_divider_desc *desc;
+ struct clk_hw *parent_hw;
+ struct clk_hw *hw;
+
+ desc = &soc_data->dividers[i];
+ if (desc->id >= clk_data->num ||
+ desc->parent_id >= clk_data->num)
+ return -EINVAL;
+
+ parent_hw = clk_data->hws[desc->parent_id];
+ if (!parent_hw)
+ return -EINVAL;
+
+ hw = ultrarisc_clk_register_divider(dev, desc, parent_hw, base,
+ lock);
+ if (IS_ERR(hw))
+ return PTR_ERR(hw);
+
+ if (desc->max_rate) {
+ unsigned long rate;
+
+ clk_hw_set_rate_range(hw, 0, desc->max_rate);
+
+ rate = clk_hw_get_rate(hw);
+ if (rate > desc->max_rate)
+ dev_warn(dev, "%s rate %lu exceeds max %lu\n",
+ desc->name, rate, desc->max_rate);
+ }
+
+ clk_data->hws[desc->id] = hw;
+ }
+
+ return 0;
+}
+
+static int ultrarisc_clk_register_gates(struct platform_device *pdev,
+ struct clk_hw_onecell_data *clk_data,
+ const struct ultrarisc_clk_soc_data *soc_data,
+ void __iomem *base,
+ spinlock_t *lock)
+{
+ struct device *dev = &pdev->dev;
+ u32 i;
+
+ for (i = 0; i < soc_data->num_gates; i++) {
+ const struct ultrarisc_gate_desc *desc;
+ struct clk_hw *parent_hw;
+ struct clk_hw *hw;
+
+ desc = &soc_data->gates[i];
+ if (desc->id >= clk_data->num ||
+ desc->parent_id >= clk_data->num)
+ return -EINVAL;
+
+ parent_hw = clk_data->hws[desc->parent_id];
+ if (!parent_hw)
+ return -EINVAL;
+
+ hw = devm_clk_hw_register_gate_parent_hw(dev, desc->name,
+ parent_hw,
+ CLK_GET_RATE_NOCACHE,
+ base + desc->offset,
+ desc->gate_bit,
+ desc->gate_flags,
+ lock);
+ if (IS_ERR(hw))
+ return PTR_ERR(hw);
+
+ clk_data->hws[desc->id] = hw;
+ }
+
+ return 0;
+}
+
+int ultrarisc_clk_probe(struct platform_device *pdev,
+ const struct ultrarisc_clk_soc_data *soc_data)
+{
+ struct clk_hw_onecell_data *clk_data;
+ struct device *dev = &pdev->dev;
+ void __iomem *base;
+ spinlock_t *lock;
+ int ret;
+
+ if (!soc_data)
+ return -EINVAL;
+
+ lock = devm_kzalloc(dev, sizeof(*lock), GFP_KERNEL);
+ if (!lock)
+ return -ENOMEM;
+
+ spin_lock_init(lock);
+
+ clk_data = devm_kzalloc(dev, struct_size(clk_data, hws,
+ soc_data->num_clks),
+ GFP_KERNEL);
+ if (!clk_data)
+ return -ENOMEM;
+
+ clk_data->num = soc_data->num_clks;
+
+ base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ ret = ultrarisc_clk_register_plls(pdev, clk_data, soc_data, base);
+ if (ret)
+ return ret;
+
+ ret = ultrarisc_clk_register_fixed_factors(dev, clk_data, soc_data);
+ if (ret)
+ return ret;
+
+ ret = ultrarisc_clk_register_dividers(pdev, clk_data, soc_data, base,
+ lock);
+ if (ret)
+ return ret;
+
+ ret = ultrarisc_clk_register_gates(pdev, clk_data, soc_data, base,
+ lock);
+ if (ret)
+ return ret;
+
+ return devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get,
+ clk_data);
+}
+EXPORT_SYMBOL_GPL(ultrarisc_clk_probe);
+
+MODULE_DESCRIPTION("UltraRISC clock core driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/clk/ultrarisc/clk-ultrarisc.h b/drivers/clk/ultrarisc/clk-ultrarisc.h
new file mode 100644
index 000000000000..1281196bb414
--- /dev/null
+++ b/drivers/clk/ultrarisc/clk-ultrarisc.h
@@ -0,0 +1,73 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef __ULTRARISC_CLK_ULTRARISC_H
+#define __ULTRARISC_CLK_ULTRARISC_H
+
+#include <linux/clk-provider.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+
+#define ULTRARISC_CLK_NO_GATE (-1)
+
+struct ultrarisc_pll_layout {
+ u32 cfg1_offset;
+ u32 cfg2_offset;
+ u32 frac_mask;
+ u32 m_mask;
+ u32 n_mask;
+ u32 oddiv1_mask;
+ u32 oddiv2_mask;
+};
+
+struct ultrarisc_pll_desc {
+ u32 id;
+ const char *name;
+};
+
+struct ultrarisc_fixed_factor_desc {
+ u32 id;
+ const char *name;
+ u32 parent_id;
+ u32 mult;
+ u32 div;
+};
+
+struct ultrarisc_divider_desc {
+ u32 id;
+ const char *name;
+ u32 offset;
+ u32 parent_id;
+ unsigned long max_rate;
+ u32 load_mask;
+ u8 div_shift;
+ u8 div_width;
+ s8 gate_bit;
+ u16 divider_flags;
+ u8 gate_flags;
+};
+
+struct ultrarisc_gate_desc {
+ u32 id;
+ const char *name;
+ u32 offset;
+ u32 parent_id;
+ u8 gate_bit;
+ u8 gate_flags;
+};
+
+struct ultrarisc_clk_soc_data {
+ const struct ultrarisc_pll_layout *pll_layout;
+ const struct ultrarisc_pll_desc *plls;
+ u32 num_plls;
+ const struct ultrarisc_fixed_factor_desc *fixed_factors;
+ u32 num_fixed_factors;
+ const struct ultrarisc_divider_desc *dividers;
+ u32 num_dividers;
+ const struct ultrarisc_gate_desc *gates;
+ u32 num_gates;
+ u32 num_clks;
+};
+
+int ultrarisc_clk_probe(struct platform_device *pdev,
+ const struct ultrarisc_clk_soc_data *soc_data);
+
+#endif /* __ULTRARISC_CLK_ULTRARISC_H */
--
2.34.1
^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH 2/2] clk: ultrarisc: add DP1000 clock driver
2026-06-11 9:40 ` [PATCH 2/2] clk: ultrarisc: add DP1000 clock driver Jia Wang via B4 Relay
@ 2026-06-11 9:56 ` sashiko-bot
0 siblings, 0 replies; 4+ messages in thread
From: sashiko-bot @ 2026-06-11 9:56 UTC (permalink / raw)
To: Jia Wang via B4 Relay; +Cc: devicetree, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] Missing clocks in `clk_data->hws` evaluate to `NULL` instead of an error pointer, creating dummy clocks.
- [High] Read-modify-write operations on clock control registers fail to mask the self-clearing load bit, risking spurious loads and preventing divider updates.
- [Low] Missing `<linux/module.h>` include.
--
commit 8cba5aa205eda7abb58c5b279348bd81f69fdc45
Author: Jia Wang <wangjia@ultrarisc.com>
clk: ultrarisc: add DP1000 clock driver
Add a clock driver for the UltraRISC DP1000 SoC.
The clock tree is driven by a SYSPLL and provides fixed-factor clocks for
the subsystem and PCIe, divider-based root clocks for GMAC and the UART,
I2C, and SPI blocks, and per-instance gate clocks for UART0-3, I2C0-3,
and SPI0-1.
> diff --git a/drivers/clk/ultrarisc/clk-ultrarisc.c b/drivers/clk/ultrarisc/clk-ultrarisc.c
> index 0000000000000..2b970565a4be8
> --- /dev/null
> +++ b/drivers/clk/ultrarisc/clk-ultrarisc.c
> @@ -0,0 +1,417 @@
[ ... ]
> +#include <linux/bitfield.h>
> +#include <linux/clk-provider.h>
[Severity: Low]
Should <linux/module.h> be explicitly included here?
The file uses module macros like MODULE_DESCRIPTION and EXPORT_SYMBOL_GPL(),
and relying on implicit inclusion can sometimes cause build failures.
[ ... ]
> +static int ultrarisc_divider_set_rate(struct clk_hw *hw, unsigned long rate,
> + unsigned long parent_rate)
> +{
[ ... ]
> + scoped_guard(spinlock_irqsave, divider->lock) {
> + val = readl_relaxed(divider->reg);
> + val &= ~(clk_div_mask(divider->width) << divider->shift);
> + val |= value << divider->shift;
> + writel_relaxed(val, divider->reg);
[Severity: High]
Does this read-modify-write sequence need to mask out divider_clk->load_mask
from the read value before the first write?
If the hardware hasn't cleared the bit from a previous operation, the first
write could write it as 1. Then the second write (below) would also write 1,
failing to provide the required 0->1 transition to trigger the hardware.
> +
> + if (divider_clk->load_mask) {
> + /*
> + * The divider update is triggered by writing 1 to the
> + * self-clearing load bit after programming the new
> + * divider field.
> + */
> + writel_relaxed(val | divider_clk->load_mask,
> + divider->reg);
> + }
> + }
> +
> + return 0;
> +}
[ ... ]
> +static struct clk_hw *
> +ultrarisc_clk_register_divider(struct device *dev,
> + const struct ultrarisc_divider_desc *desc,
> + struct clk_hw *parent_hw, void __iomem *base,
> + spinlock_t *lock)
> +{
[ ... ]
> + if (desc->gate_bit != ULTRARISC_CLK_NO_GATE) {
> + divider->gate.reg = reg;
> + divider->gate.bit_idx = desc->gate_bit;
> + divider->gate.flags = desc->gate_flags;
> + divider->gate.lock = lock;
> + gate_hw = ÷r->gate.hw;
> + gate_ops = &clk_gate_ops;
[Severity: High]
Can using the standard clk_gate_ops here cause spurious loads?
The standard gate ops perform a generic read-modify-write. If a gate
operation executes while the load_mask bit is still 1, it will blindly
write 1 back to the load bit, potentially triggering a spurious hardware
reload or corrupting the update state machine.
> + }
> +
> + return devm_clk_hw_register_composite_pdata(dev, desc->name, &pdata, 1,
> + NULL, NULL,
> + ÷r->divider.hw,
> + &ultrarisc_divider_ops,
> + gate_hw, gate_ops,
> + CLK_GET_RATE_NOCACHE);
> +}
[ ... ]
> +static int ultrarisc_clk_register_fixed_factors(struct device *dev,
> + struct clk_hw_onecell_data *clk_data,
> + const struct ultrarisc_clk_soc_data *soc_data)
> +{
[ ... ]
> + parent_hw = clk_data->hws[desc->parent_id];
> + if (!parent_hw)
> + return -EINVAL;
[Severity: High]
If the clk_data initialization (below) is fixed to use error pointers
instead of NULL, should this validation check use IS_ERR_OR_NULL() or
IS_ERR() to avoid dereferencing an ERR_PTR?
> +
> + hw = devm_clk_hw_register_fixed_factor_parent_hw(dev, desc->name,
> + parent_hw,
[ ... ]
> +int ultrarisc_clk_probe(struct platform_device *pdev,
> + const struct ultrarisc_clk_soc_data *soc_data)
> +{
[ ... ]
> + clk_data = devm_kzalloc(dev, struct_size(clk_data, hws,
> + soc_data->num_clks),
> + GFP_KERNEL);
[Severity: High]
Since this array is zero-initialized, any unpopulated clock IDs will remain
NULL. Does this need to be explicitly filled with ERR_PTR(-ENOENT)?
When a consumer requests an invalid clock index, of_clk_hw_onecell_get()
returns this NULL pointer. The clock framework considers NULL a valid
optional dummy clock, allowing consumer drivers to silently succeed at
enabling the clock instead of failing. If they then access peripheral
registers without the clock actually being enabled, it could trigger a bus
fault.
> + if (!clk_data)
> + return -ENOMEM;
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260611-ultrarisc-clock-v1-0-2d93ebb4cc13@ultrarisc.com?part=2
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-06-11 9:56 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-11 9:40 [PATCH 0/2] clk: ultrarisc: add DP1000 clock support Jia Wang via B4 Relay
2026-06-11 9:40 ` [PATCH 1/2] dt-bindings: clock: ultrarisc: Add DP1000 Clock Controller Jia Wang via B4 Relay
2026-06-11 9:40 ` [PATCH 2/2] clk: ultrarisc: add DP1000 clock driver Jia Wang via B4 Relay
2026-06-11 9:56 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox