* [PATCH RFC v5 00/12] ZTE zx297520v3 clock bindings and driver
@ 2026-06-28 19:58 Stefan Dösinger
2026-06-28 19:58 ` [PATCH RFC v5 01/12] dt-bindings: soc: zte: Add zx297520v3 top clock and reset bindings Stefan Dösinger
` (11 more replies)
0 siblings, 12 replies; 23+ messages in thread
From: Stefan Dösinger @ 2026-06-28 19:58 UTC (permalink / raw)
To: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel, Brian Masney
Cc: linux-clk, devicetree, linux-kernel, linux-arm-kernel,
Stefan Dösinger
Hi,
I am sending version 5 of my zx297520v3 clock patch. The major change is
using regmaps rather than raw mmio to access the clocks and moving reset
handling into its own mfd/aux bus driver.
I think the list of clocks in my driver is fairly complete; It is
certainly a lot better than what the downstream ZTE drivers have. I
deduced a lot of it by trial and error. I am sure there are some clocks
missing that will need to be added to the binding later. Afaiu adding
clocks is not an issue, but removing or reordering them is an ABI break.
Signed-off-by: Stefan Dösinger <stefandoesinger@gmail.com>
---
Changes in v5:
*) Use MFD instead of aux bus for top and matrix clocks
*) Move top and matrix bindings to soc/zte
*) Give USB PHY its own resets
*) Other localized changes are noted in the individual patches
- Link to v4: https://lore.kernel.org/r/20260616-zx29clk-v4-0-ca994bd22e9d@gmail.com
Changes in v4:
*) Use syscon and regmap instead of raw IO
*) Move reset to its own driver on the aux bus, but keep reset and clk
in the same binding as it matches the way the hardware works
*) Go back to having matrixclk in its own device because syscon deals
poorly with multi io reg devices. List all PLL outputs from topclk as
inputs to matrixclk
*) Some more hardware research: Figure out the parents of the 4 possible
GPIO clock outputs and declare them in the driver. They are unused on
the hardware I have, but they show that all PLLs can be used.
- Link to v3: https://lore.kernel.org/r/20260529-zx29clk-v3-0-c7fe54ea388f@gmail.com
Changes in v3:
Model top and matrix clocks as one device
Add PLL driver
Fixed a few issues found by Sashiko: register lock, some missing devm_,
error handling
v2: Fix build issues introduced by checkpatch.pl fixes that I didn't
spot earlier.
---
Stefan Dösinger (12):
dt-bindings: soc: zte: Add zx297520v3 top clock and reset bindings
dt-bindings: soc: zte: Add zx297520v3 matrix clock and reset bindings
dt-bindings: clk: zte: Add zx297520v3 LSP clock and reset bindings
mfd: zx297520v3: Add a clock and reset MFD driver.
clk: zte: Add Clock registration infrastructure.
clk: zte: Add zx PLL support infrastructure
clk: zte: Add regmap based clocks
clk: zte: Introduce a driver for zx297520v3 top clocks
clk: zte: Introduce a driver for zx297520v3 matrix clocks
clk: zte: Introduce a driver for zx297520v3 LSP clocks and resets
reset: zte: Add a zx297520v3 reset driver
ARM: dts: zte: Declare zx297520v3 CRM device nodes
.../bindings/clock/zte,zx297520v3-lspcrm.yaml | 96 +++
.../bindings/soc/zte/zte,zx297520v3-matrixcrm.yaml | 177 +++++
.../bindings/soc/zte/zte,zx297520v3-topcrm.yaml | 85 +++
MAINTAINERS | 7 +
arch/arm/boot/dts/zte/zx297520v3.dtsi | 97 ++-
drivers/clk/Kconfig | 1 +
drivers/clk/Makefile | 1 +
drivers/clk/zte/Kconfig | 28 +
drivers/clk/zte/Makefile | 6 +
drivers/clk/zte/clk-regmap.c | 245 ++++++
drivers/clk/zte/clk-zx.c | 192 +++++
drivers/clk/zte/clk-zx.h | 81 ++
drivers/clk/zte/clk-zx297520v3.c | 848 +++++++++++++++++++++
drivers/clk/zte/pll-zx.c | 485 ++++++++++++
drivers/reset/Kconfig | 11 +
drivers/reset/Makefile | 1 +
drivers/reset/reset-zte-zx297520v3.c | 274 +++++++
drivers/soc/Kconfig | 1 +
drivers/soc/Makefile | 1 +
drivers/soc/zte/Kconfig | 20 +
drivers/soc/zte/Makefile | 3 +
drivers/soc/zte/zx297520v3-crm.c | 76 ++
include/dt-bindings/clock/zte,zx297520v3-clk.h | 171 +++++
include/dt-bindings/reset/zte,zx297520v3-reset.h | 61 ++
24 files changed, 2960 insertions(+), 8 deletions(-)
---
base-commit: c1ecb239fa3456529a32255359fc78b69eb9d847
change-id: 20260510-zx29clk-2e4d39e3128c
Best regards,
--
Stefan Dösinger <stefandoesinger@gmail.com>
^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH RFC v5 01/12] dt-bindings: soc: zte: Add zx297520v3 top clock and reset bindings
2026-06-28 19:58 [PATCH RFC v5 00/12] ZTE zx297520v3 clock bindings and driver Stefan Dösinger
@ 2026-06-28 19:58 ` Stefan Dösinger
2026-06-28 20:12 ` sashiko-bot
2026-06-28 19:58 ` [PATCH RFC v5 02/12] dt-bindings: soc: zte: Add zx297520v3 matrix " Stefan Dösinger
` (10 subsequent siblings)
11 siblings, 1 reply; 23+ messages in thread
From: Stefan Dösinger @ 2026-06-28 19:58 UTC (permalink / raw)
To: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel, Brian Masney
Cc: linux-clk, devicetree, linux-kernel, linux-arm-kernel,
Stefan Dösinger
These SoCs have 3 clock and reset controllers: Top, Matrix and LSP. The
separation of concerns between Top and Matrix and the interface between
them is poorly defined in the hardware, so the bindings list all
potential PLL clocks that might be passed between them.
Generally every device has two clocks (one work clock, and one that
connects it to the bus, I call it PCLK), two reset bits (I don't know
what the difference is - sometimes asserting one is enough to reset the
device, sometimes both need to be asserted). PCLK and WCLK are
controlled by individual gates. Some devices have a mux and/or a
divider for their work clock. Some devices, like the GPIO controller,
only have reset bits and no clocks.
The top clock controller is fed by a 26mhz external oscillator and has 4
PLLs to generate other clock rates. ZTE's kernel mostly relies on the
boot ROM to set up PLLs, but one LTE-Related PLL is not configured
on some boards. Therefore my driver contains code to program PLLs. It
produces identical settings as the boot ROM for the pre-programmed
frequencies.
Not all clocks will have an explicit user in the end. I am defining a
lot of them simply to shut them off. The boot loader sets up a few of
the proprietary timers, which will send regular IRQs (although the
kernel of course doesn't need to listen to them). I don't plan to add a
driver for the proprietary timer as I see no use for them - the ARM arch
timer works just fine. I will add a driver for the very similar
proprietary watchdog though.
The clock list in this patch is pretty complete but not exhaustive.
There are other bits that are enabled, but I couldn't deduce what they
are controlling by trial and error. Some of them seem to do nothing.
Others cause an instant hang of the board when disabled. It is quite
likely that a handful more clocks will be added in the future, but not a
large number.
Signed-off-by: Stefan Dösinger <stefandoesinger@gmail.com>
---
Changes v4->v5:
Rename from zte,zx297520v3-topclk to zte,zx297520v3-topcrm and move to
soc/zte
Fix path in MAINTAINERS
Add syscon-reboot node to the binding
Give the USB and HSIC PHY resets their own reset control
---
.../bindings/soc/zte/zte,zx297520v3-topcrm.yaml | 85 +++++++++++++++++++
MAINTAINERS | 3 +
include/dt-bindings/clock/zte,zx297520v3-clk.h | 97 ++++++++++++++++++++++
include/dt-bindings/reset/zte,zx297520v3-reset.h | 32 +++++++
4 files changed, 217 insertions(+)
diff --git a/Documentation/devicetree/bindings/soc/zte/zte,zx297520v3-topcrm.yaml b/Documentation/devicetree/bindings/soc/zte/zte,zx297520v3-topcrm.yaml
new file mode 100644
index 000000000000..6baab5c98d25
--- /dev/null
+++ b/Documentation/devicetree/bindings/soc/zte/zte,zx297520v3-topcrm.yaml
@@ -0,0 +1,85 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/soc/zte/zte,zx297520v3-topcrm.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: ZTE zx297520v3 SoC top clock and reset controller
+
+maintainers:
+ - Stefan Dösinger <stefandoesinger@gmail.com>
+
+description: |
+ The zx297520v3's top clock and reset controller generates clocks for core
+ devices on the board like the main bus, USB and timers. In addition to clocks
+ it has reset controls for peripherals, a global board reset, watchdog reset
+ controls and a USB status register.
+
+ The controller has two clock inputs: a 26 MHz and a 32 KHz external
+ oscillator. They need to be provided as input clocks. The controller provides
+ clocks to the downstream Matrix clock controller.
+
+ All available clocks are defined as preprocessor macros in the
+ 'dt-bindings/clock/zte,zx297520v3-clk.h' header. The resets are defined in the
+ 'dt-bindings/reset/zte,zx297520v3-reset.h' header.
+
+properties:
+ compatible:
+ items:
+ - const: zte,zx297520v3-topcrm
+ - const: syscon
+
+ reg:
+ maxItems: 1
+
+ clocks:
+ items:
+ - description: 26 MHz external oscillator
+ - description: 32 KHz external oscillator
+
+ clock-names:
+ items:
+ - const: osc26m
+ - const: osc32k
+
+ "#clock-cells":
+ const: 1
+
+ "#reset-cells":
+ const: 1
+
+ syscon-reboot:
+ type: object
+ $ref: /schemas/power/reset/syscon-reboot.yaml#
+ description:
+ Reboot method for the SoC.
+
+required:
+ - compatible
+ - reg
+ - clocks
+ - clock-names
+ - '#clock-cells'
+ - '#reset-cells'
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/clock/zte,zx297520v3-clk.h>
+ #include <dt-bindings/reset/zte,zx297520v3-reset.h>
+
+ clock-controller@13b000 {
+ compatible = "zte,zx297520v3-topcrm", "syscon";
+ reg = <0x0013b000 0x400>;
+ clocks = <&osc26m>, <&osc32k>;
+ clock-names = "osc26m", "osc32k";
+ #clock-cells = <1>;
+ #reset-cells = <1>;
+
+ syscon-reboot {
+ compatible = "syscon-reboot";
+ offset = <0x0>;
+ mask = <0x1>;
+ };
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index 8629ed2aa82f..0f9e588e4839 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3867,8 +3867,11 @@ L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
S: Odd fixes
F: Documentation/arch/arm/zte/
F: Documentation/devicetree/bindings/arm/zte.yaml
+F: Documentation/devicetree/bindings/soc/zte/
F: arch/arm/boot/dts/zte/
F: arch/arm/mach-zte/
+F: include/dt-bindings/clock/zte,zx297520v3-clk.h
+F: include/dt-bindings/reset/zte,zx297520v3-reset.h
ARM/ZYNQ ARCHITECTURE
M: Michal Simek <michal.simek@amd.com>
diff --git a/include/dt-bindings/clock/zte,zx297520v3-clk.h b/include/dt-bindings/clock/zte,zx297520v3-clk.h
new file mode 100644
index 000000000000..de1c08b6a5a9
--- /dev/null
+++ b/include/dt-bindings/clock/zte,zx297520v3-clk.h
@@ -0,0 +1,97 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
+/*
+ * Copyright (C) Stefan Dösinger.
+ */
+
+#ifndef __DT_BINDINGS_CLOCK_ZX297520V3_H
+#define __DT_BINDINGS_CLOCK_ZX297520V3_H
+
+#define ZX297520V3_M0_WCLK 1
+#define ZX297520V3_SRAM1_PCLK 2
+#define ZX297520V3_SRAM2_PCLK 3
+#define ZX297520V3_UART0_WCLK 4
+#define ZX297520V3_UART0_PCLK 5
+#define ZX297520V3_I2C0_WCLK 6
+#define ZX297520V3_I2C0_PCLK 7
+#define ZX297520V3_RTC_WCLK 8
+#define ZX297520V3_RTC_PCLK 9
+#define ZX297520V3_LPM_GSM_WCLK 10
+#define ZX297520V3_LPM_GSM_PCLK 11
+#define ZX297520V3_LPM_LTE_WCLK 12
+#define ZX297520V3_LPM_LTE_PCLK 13
+#define ZX297520V3_LPM_TD_WCLK 14
+#define ZX297520V3_LPM_TD_PCLK 15
+#define ZX297520V3_LPM_W_WCLK 16
+#define ZX297520V3_LPM_W_PCLK 17
+#define ZX297520V3_TIMER_T08_WCLK 18
+#define ZX297520V3_TIMER_T08_PCLK 19
+#define ZX297520V3_TIMER_T09_WCLK 20
+#define ZX297520V3_TIMER_T09_PCLK 21
+#define ZX297520V3_MPLL 22
+#define ZX297520V3_MPLL_D2 23
+#define ZX297520V3_MPLL_D3 24
+#define ZX297520V3_MPLL_D4 25
+#define ZX297520V3_MPLL_D5 26
+#define ZX297520V3_MPLL_D6 27
+#define ZX297520V3_MPLL_D8 28
+#define ZX297520V3_MPLL_D12 29
+#define ZX297520V3_MPLL_D16 30
+#define ZX297520V3_MPLL_D26 31
+#define ZX297520V3_UPLL 32
+#define ZX297520V3_UPLL_D2 33
+#define ZX297520V3_UPLL_D3 34
+#define ZX297520V3_UPLL_D4 35
+#define ZX297520V3_UPLL_D5 36
+#define ZX297520V3_UPLL_D6 37
+#define ZX297520V3_UPLL_D8 38
+#define ZX297520V3_UPLL_D12 39
+#define ZX297520V3_UPLL_D16 40
+#define ZX297520V3_DPLL 41
+#define ZX297520V3_DPLL_D2 42
+#define ZX297520V3_DPLL_D3 43
+#define ZX297520V3_DPLL_D4 44
+#define ZX297520V3_DPLL_D5 45
+#define ZX297520V3_DPLL_D6 46
+#define ZX297520V3_DPLL_D8 47
+#define ZX297520V3_DPLL_D12 48
+#define ZX297520V3_DPLL_D16 49
+#define ZX297520V3_GPLL 50
+#define ZX297520V3_GPLL_D2 51
+#define ZX297520V3_GPLL_D3 52
+#define ZX297520V3_GPLL_D4 53
+#define ZX297520V3_GPLL_D5 54
+#define ZX297520V3_GPLL_D6 55
+#define ZX297520V3_GPLL_D8 56
+#define ZX297520V3_GPLL_D12 57
+#define ZX297520V3_GPLL_D16 58
+#define ZX297520V3_PMM_WCLK 59
+#define ZX297520V3_PMM_PCLK 60
+#define ZX297520V3_OUT0_WCLK 61
+#define ZX297520V3_OUT1_WCLK 62
+#define ZX297520V3_OUT2_WCLK 63
+#define ZX297520V3_OUT32K_WCLK 64
+#define ZX297520V3_RMIIPHY_WCLK 65
+#define ZX297520V3_TIMER_T12_WCLK 66
+#define ZX297520V3_TIMER_T12_PCLK 67
+#define ZX297520V3_TIMER_T13_WCLK 68
+#define ZX297520V3_TIMER_T13_PCLK 69
+#define ZX297520V3_TIMER_T14_WCLK 70
+#define ZX297520V3_TIMER_T14_PCLK 71
+#define ZX297520V3_TIMER_T15_WCLK 72
+#define ZX297520V3_TIMER_T15_PCLK 73
+#define ZX297520V3_TIMER_T16_WCLK 74
+#define ZX297520V3_TIMER_T16_PCLK 75
+#define ZX297520V3_TIMER_T17_WCLK 76
+#define ZX297520V3_TIMER_T17_PCLK 77
+#define ZX297520V3_WDT_T18_WCLK 78
+#define ZX297520V3_WDT_T18_PCLK 79
+#define ZX297520V3_USIM1_WCLK 80
+#define ZX297520V3_USIM1_PCLK 81
+#define ZX297520V3_AHB_WCLK 82
+#define ZX297520V3_AHB_PCLK 83
+#define ZX297520V3_USB_WCLK 84
+#define ZX297520V3_USB_PCLK 85
+#define ZX297520V3_HSIC_WCLK 86
+#define ZX297520V3_HSIC_PCLK 87
+
+#endif /* __DT_BINDINGS_CLOCK_ZX297520V3_H */
diff --git a/include/dt-bindings/reset/zte,zx297520v3-reset.h b/include/dt-bindings/reset/zte,zx297520v3-reset.h
new file mode 100644
index 000000000000..43db72bb59de
--- /dev/null
+++ b/include/dt-bindings/reset/zte,zx297520v3-reset.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
+/*
+ * Copyright (C) Stefan Dösinger.
+ */
+
+#ifndef __DT_BINDINGS_RESET_ZX297520V3_H
+#define __DT_BINDINGS_RESET_ZX297520V3_H
+
+#define ZX297520V3_ZSP_RESET 0
+#define ZX297520V3_UART0_RESET 1
+#define ZX297520V3_I2C0_RESET 2
+#define ZX297520V3_RTC_RESET 3
+#define ZX297520V3_TIMER_T08_RESET 4
+#define ZX297520V3_TIMER_T09_RESET 5
+#define ZX297520V3_PMM_RESET 6
+#define ZX297520V3_GPIO_RESET 7
+#define ZX297520V3_GPIO8_RESET 8
+#define ZX297520V3_TIMER_T12_RESET 9
+#define ZX297520V3_TIMER_T13_RESET 10
+#define ZX297520V3_TIMER_T14_RESET 11
+#define ZX297520V3_TIMER_T15_RESET 12
+#define ZX297520V3_TIMER_T16_RESET 13
+#define ZX297520V3_TIMER_T17_RESET 14
+#define ZX297520V3_WDT_T18_RESET 15
+#define ZX297520V3_USIM1_RESET 16
+#define ZX297520V3_AHB_RESET 17
+#define ZX297520V3_USB_PHY_RESET 18
+#define ZX297520V3_USB_RESET 19
+#define ZX297520V3_HSIC_PHY_RESET 20
+#define ZX297520V3_HSIC_RESET 21
+
+#endif /* __DT_BINDINGS_RESET_ZX297520V3_H */
--
2.53.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH RFC v5 02/12] dt-bindings: soc: zte: Add zx297520v3 matrix clock and reset bindings
2026-06-28 19:58 [PATCH RFC v5 00/12] ZTE zx297520v3 clock bindings and driver Stefan Dösinger
2026-06-28 19:58 ` [PATCH RFC v5 01/12] dt-bindings: soc: zte: Add zx297520v3 top clock and reset bindings Stefan Dösinger
@ 2026-06-28 19:58 ` Stefan Dösinger
2026-06-28 20:09 ` sashiko-bot
2026-06-28 19:58 ` [PATCH RFC v5 03/12] dt-bindings: clk: zte: Add zx297520v3 LSP " Stefan Dösinger
` (9 subsequent siblings)
11 siblings, 1 reply; 23+ messages in thread
From: Stefan Dösinger @ 2026-06-28 19:58 UTC (permalink / raw)
To: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel, Brian Masney
Cc: linux-clk, devicetree, linux-kernel, linux-arm-kernel,
Stefan Dösinger
This controller contains clocks and resets for high speed devices on the
zx297520v3 board and hardware spinlocks that I expect will be necessary
to communicate correctly with the LTE DSP firmware blob.
A simple MFD driver will instantiate independent clock, reset and hwlock
drivers.
Signed-off-by: Stefan Dösinger <stefandoesinger@gmail.com>
---
Changes
v4->v5:
Move binding to soc/zte
Remove topclk from the example
Add #hwlock-cells for hw spinlock registers
Add more clocks I stumbled into: sram0 and another LTE related device
v3->v4:
Split matrixclk into its own controller again because syscon/regmap
deals poorly with device nodes that have more than one memory region. As
a consequence I am passing all PLL outputs generated on Topclk down to
Matrixclk.
---
.../bindings/soc/zte/zte,zx297520v3-matrixcrm.yaml | 177 +++++++++++++++++++++
include/dt-bindings/clock/zte,zx297520v3-clk.h | 37 +++++
include/dt-bindings/reset/zte,zx297520v3-reset.h | 10 ++
3 files changed, 224 insertions(+)
diff --git a/Documentation/devicetree/bindings/soc/zte/zte,zx297520v3-matrixcrm.yaml b/Documentation/devicetree/bindings/soc/zte/zte,zx297520v3-matrixcrm.yaml
new file mode 100644
index 000000000000..bbec0eb837da
--- /dev/null
+++ b/Documentation/devicetree/bindings/soc/zte/zte,zx297520v3-matrixcrm.yaml
@@ -0,0 +1,177 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/soc/zte/zte,zx297520v3-matrixcrm.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: ZTE zx297520v3 SoC matrix clock and reset controller
+
+maintainers:
+ - Stefan Dösinger <stefandoesinger@gmail.com>
+
+description: |
+ This controller contains clock and reset controls for high speed devices on
+ the zx297520v3 board: The CPU, RAM, SDIO and Ethernet clocks and resets are
+ found here. This controller requires PLL-generated clocks from Topcrm as well
+ as the fixed 26 MHz and 32 KHz oscillators found on this board.
+
+ This controller also contains hardware mutex registers for synchronization
+ with different processors on this board.
+
+ All available clocks are defined as preprocessor macros in the
+ 'dt-bindings/clock/zte,zx297520v3-clk.h' header. Resets are defined in the
+ 'dt-bindings/reset/zte,zx297520v3-reset.h' header.
+
+properties:
+ compatible:
+ items:
+ - const: zte,zx297520v3-matrixcrm
+
+ reg:
+ maxItems: 1
+
+ clocks:
+ items:
+ - description: 26 MHz external oscillator
+ - description: 32 KHz external oscillator
+ - description: Main PLL output from topcrm (usually 624 MHz)
+ - description: Main PLL subdivision factor 2
+ - description: Main PLL subdivision factor 3
+ - description: Main PLL subdivision factor 4
+ - description: Main PLL subdivision factor 5
+ - description: Main PLL subdivision factor 6
+ - description: Main PLL subdivision factor 8
+ - description: Main PLL subdivision factor 12
+ - description: Main PLL subdivision factor 16
+ - description: Main PLL subdivision factor 26
+ - description: Upll output from topcrm (Usually 480 MHz)
+ - description: Upll subdivision factor 2
+ - description: Upll subdivision factor 3
+ - description: Upll subdivision factor 4
+ - description: Upll subdivision factor 5
+ - description: Upll subdivision factor 6
+ - description: Upll subdivision factor 8
+ - description: Upll subdivision factor 12
+ - description: Upll subdivision factor 16
+ - description: Dpll output from topcrm (usually 492.88 MHz)
+ - description: Dpll subdivision factor 2
+ - description: Dpll subdivision factor 3
+ - description: Dpll subdivision factor 4
+ - description: Dpll subdivision factor 5
+ - description: Dpll subdivision factor 6
+ - description: Dpll subdivision factor 8
+ - description: Dpll subdivision factor 12
+ - description: Dpll subdivision factor 16
+ - description: Gpll output from topcrm (usually 200 MHz)
+ - description: Gpll subdivision factor 2
+ - description: Gpll subdivision factor 3
+ - description: Gpll subdivision factor 4
+ - description: Gpll subdivision factor 5
+ - description: Gpll subdivision factor 6
+ - description: Gpll subdivision factor 8
+ - description: Gpll subdivision factor 12
+ - description: Gpll subdivision factor 16
+
+ clock-names:
+ items:
+ - const: osc26m
+ - const: osc32k
+ - const: mpll
+ - const: mpll_d2
+ - const: mpll_d3
+ - const: mpll_d4
+ - const: mpll_d5
+ - const: mpll_d6
+ - const: mpll_d8
+ - const: mpll_d12
+ - const: mpll_d16
+ - const: mpll_d26
+ - const: upll
+ - const: upll_d2
+ - const: upll_d3
+ - const: upll_d4
+ - const: upll_d5
+ - const: upll_d6
+ - const: upll_d8
+ - const: upll_d12
+ - const: upll_d16
+ - const: dpll
+ - const: dpll_d2
+ - const: dpll_d3
+ - const: dpll_d4
+ - const: dpll_d5
+ - const: dpll_d6
+ - const: dpll_d8
+ - const: dpll_d12
+ - const: dpll_d16
+ - const: gpll
+ - const: gpll_d2
+ - const: gpll_d3
+ - const: gpll_d4
+ - const: gpll_d5
+ - const: gpll_d6
+ - const: gpll_d8
+ - const: gpll_d12
+ - const: gpll_d16
+
+ "#clock-cells":
+ const: 1
+
+ "#hwlock-cells":
+ const: 1
+
+ "#reset-cells":
+ const: 1
+
+required:
+ - compatible
+ - reg
+ - clocks
+ - clock-names
+ - '#clock-cells'
+ - "#hwlock-cells"
+ - '#reset-cells'
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/clock/zte,zx297520v3-clk.h>
+ #include <dt-bindings/reset/zte,zx297520v3-reset.h>
+
+ clock-controller@1306000 {
+ compatible = "zte,zx297520v3-matrixcrm";
+ reg = <0x01306000 0x400>;
+ clocks = <&osc26m>, <&osc32k>,
+ <&topcrm ZX297520V3_MPLL>, <&topcrm ZX297520V3_MPLL_D2>,
+ <&topcrm ZX297520V3_MPLL_D3>, <&topcrm ZX297520V3_MPLL_D4>,
+ <&topcrm ZX297520V3_MPLL_D5>, <&topcrm ZX297520V3_MPLL_D6>,
+ <&topcrm ZX297520V3_MPLL_D8>, <&topcrm ZX297520V3_MPLL_D12>,
+ <&topcrm ZX297520V3_MPLL_D16>, <&topcrm ZX297520V3_MPLL_D26>,
+ <&topcrm ZX297520V3_UPLL>, <&topcrm ZX297520V3_UPLL_D2>,
+ <&topcrm ZX297520V3_UPLL_D3>, <&topcrm ZX297520V3_UPLL_D4>,
+ <&topcrm ZX297520V3_UPLL_D5>, <&topcrm ZX297520V3_UPLL_D6>,
+ <&topcrm ZX297520V3_UPLL_D8>, <&topcrm ZX297520V3_UPLL_D12>,
+ <&topcrm ZX297520V3_UPLL_D16>,
+ <&topcrm ZX297520V3_DPLL>, <&topcrm ZX297520V3_DPLL_D2>,
+ <&topcrm ZX297520V3_DPLL_D3>, <&topcrm ZX297520V3_DPLL_D4>,
+ <&topcrm ZX297520V3_DPLL_D5>, <&topcrm ZX297520V3_DPLL_D6>,
+ <&topcrm ZX297520V3_DPLL_D8>, <&topcrm ZX297520V3_DPLL_D12>,
+ <&topcrm ZX297520V3_DPLL_D16>,
+ <&topcrm ZX297520V3_GPLL>, <&topcrm ZX297520V3_GPLL_D2>,
+ <&topcrm ZX297520V3_GPLL_D3>, <&topcrm ZX297520V3_GPLL_D4>,
+ <&topcrm ZX297520V3_GPLL_D5>, <&topcrm ZX297520V3_GPLL_D6>,
+ <&topcrm ZX297520V3_GPLL_D8>, <&topcrm ZX297520V3_GPLL_D12>,
+ <&topcrm ZX297520V3_GPLL_D16>;
+ clock-names = "osc26m", "osc32k", "mpll", "mpll_d2", "mpll_d3",
+ "mpll_d4", "mpll_d5", "mpll_d6", "mpll_d8", "mpll_d12",
+ "mpll_d16", "mpll_d26", "upll", "upll_d2", "upll_d3",
+ "upll_d4", "upll_d5", "upll_d6", "upll_d8", "upll_d12",
+ "upll_d16", "dpll", "dpll_d2", "dpll_d3", "dpll_d4",
+ "dpll_d5", "dpll_d6", "dpll_d8", "dpll_d12", "dpll_d16",
+ "gpll", "gpll_d2", "gpll_d3", "gpll_d4", "gpll_d5",
+ "gpll_d6", "gpll_d8", "gpll_d12", "gpll_d16";
+ #clock-cells = <1>;
+ #hwlock-cells = <1>;
+ #reset-cells = <1>;
+ };
diff --git a/include/dt-bindings/clock/zte,zx297520v3-clk.h b/include/dt-bindings/clock/zte,zx297520v3-clk.h
index de1c08b6a5a9..8a6aa456a708 100644
--- a/include/dt-bindings/clock/zte,zx297520v3-clk.h
+++ b/include/dt-bindings/clock/zte,zx297520v3-clk.h
@@ -94,4 +94,41 @@
#define ZX297520V3_HSIC_WCLK 86
#define ZX297520V3_HSIC_PCLK 87
+#define ZX297520V3_CPU_WCLK 1
+#define ZX297520V3_CPU_PCLK 2
+#define ZX297520V3_ZSP_WCLK 3
+#define ZX297520V3_EDCP_WCLK 4
+#define ZX297520V3_EDCP_PCLK 5
+#define ZX297520V3_SD0_WCLK 6
+#define ZX297520V3_SD0_PCLK 7
+#define ZX297520V3_SD0_CDET 8
+#define ZX297520V3_SD1_WCLK 9
+#define ZX297520V3_SD1_PCLK 10
+#define ZX297520V3_SD1_CDET 11
+#define ZX297520V3_NAND_WCLK 12
+#define ZX297520V3_NAND_PCLK 13
+#define ZX297520V3_DMA_PCLK 14
+#define ZX297520V3_MBOX_PCLK 15
+#define ZX297520V3_PDCFG_WCLK 16
+#define ZX297520V3_PDCFG_PCLK 17
+#define ZX297520V3_SSC_WCLK 18
+#define ZX297520V3_SSC_PCLK 19
+#define ZX297520V3_GMAC_WCLK 20
+#define ZX297520V3_GMAC_PCLK 21
+#define ZX297520V3_GMAC_AHB 22
+#define ZX297520V3_VOU_WCLK 23
+#define ZX297520V3_VOU_PCLK 24
+#define ZX297520V3_LSP_MPLL_D5_WCLK 25
+#define ZX297520V3_LSP_MPLL_D4_WCLK 26
+#define ZX297520V3_LSP_MPLL_D6_WCLK 27
+#define ZX297520V3_LSP_MPLL_D8_WCLK 28
+#define ZX297520V3_LSP_MPLL_D12_WCLK 29
+#define ZX297520V3_LSP_OSC26M_WCLK 30
+#define ZX297520V3_LSP_OSC32K_WCLK 31
+#define ZX297520V3_LSP_PCLK 32
+#define ZX297520V3_LSP_TDM_WCLK 33
+#define ZX297520V3_LSP_DPLL_D4_WCLK 34
+#define ZX297520V3_SRAM0_PCLK 35
+#define ZX297520V3_GSM_CFG_PCLK 36
+
#endif /* __DT_BINDINGS_CLOCK_ZX297520V3_H */
diff --git a/include/dt-bindings/reset/zte,zx297520v3-reset.h b/include/dt-bindings/reset/zte,zx297520v3-reset.h
index 43db72bb59de..81ffc8bc34c5 100644
--- a/include/dt-bindings/reset/zte,zx297520v3-reset.h
+++ b/include/dt-bindings/reset/zte,zx297520v3-reset.h
@@ -29,4 +29,14 @@
#define ZX297520V3_HSIC_PHY_RESET 20
#define ZX297520V3_HSIC_RESET 21
+#define ZX297520V3_CPU_RESET 0
+#define ZX297520V3_EDCP_RESET 1
+#define ZX297520V3_SD0_RESET 2
+#define ZX297520V3_SD1_RESET 3
+#define ZX297520V3_NAND_RESET 4
+#define ZX297520V3_PDCFG_RESET 5
+#define ZX297520V3_SSC_RESET 6
+#define ZX297520V3_GMAC_RESET 7
+#define ZX297520V3_VOU_RESET 8
+
#endif /* __DT_BINDINGS_RESET_ZX297520V3_H */
--
2.53.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH RFC v5 03/12] dt-bindings: clk: zte: Add zx297520v3 LSP clock and reset bindings
2026-06-28 19:58 [PATCH RFC v5 00/12] ZTE zx297520v3 clock bindings and driver Stefan Dösinger
2026-06-28 19:58 ` [PATCH RFC v5 01/12] dt-bindings: soc: zte: Add zx297520v3 top clock and reset bindings Stefan Dösinger
2026-06-28 19:58 ` [PATCH RFC v5 02/12] dt-bindings: soc: zte: Add zx297520v3 matrix " Stefan Dösinger
@ 2026-06-28 19:58 ` Stefan Dösinger
2026-06-28 19:58 ` [PATCH RFC v5 04/12] mfd: zx297520v3: Add a clock and reset MFD driver Stefan Dösinger
` (8 subsequent siblings)
11 siblings, 0 replies; 23+ messages in thread
From: Stefan Dösinger @ 2026-06-28 19:58 UTC (permalink / raw)
To: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel, Brian Masney
Cc: linux-clk, devicetree, linux-kernel, linux-arm-kernel,
Stefan Dösinger
The clock controller of the zx297520v3 Low Speed Peripherals is
relatively clean. One register per device with gates, muxes and resets
and for some devices a divider. There are even bits in the matrix
controller to control propagation of clock lines down to LSP.
The clocks are sorted by register address and I am convinced that the
device list is complete. There are however a few more registers that are
likely extra dividers for TDM and I2S devices
Signed-off-by: Stefan Dösinger <stefandoesinger@gmail.com>
---
Patch changelog:
v5:
Rename from lspclk to lspcrm
Remove matrixcrm from example
v4: Order properties compatible->reg->clocks->clock->names->#cells
---
.../bindings/clock/zte,zx297520v3-lspcrm.yaml | 96 ++++++++++++++++++++++
MAINTAINERS | 1 +
include/dt-bindings/clock/zte,zx297520v3-clk.h | 37 +++++++++
include/dt-bindings/reset/zte,zx297520v3-reset.h | 19 +++++
4 files changed, 153 insertions(+)
diff --git a/Documentation/devicetree/bindings/clock/zte,zx297520v3-lspcrm.yaml b/Documentation/devicetree/bindings/clock/zte,zx297520v3-lspcrm.yaml
new file mode 100644
index 000000000000..c510129068de
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/zte,zx297520v3-lspcrm.yaml
@@ -0,0 +1,96 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/clock/zte,zx297520v3-lspcrm.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: ZTE zx297520v3 SoC LSP clock and reset controller
+
+maintainers:
+ - Stefan Dösinger <stefandoesinger@gmail.com>
+
+description: |
+ This clock and reset controller controls low speed peripherals on the board.
+ This is a relatively isolated subsystem containing UART, I2C, I2S and SPI
+ devices. The controller is responsible for bringing the devices out of reset
+ and enabling their clocks as needed.
+
+ The controller receives its clock signal from the matrix controller and need
+ to be declared as clock inputs.
+
+ All available clocks are defined as preprocessor macros in the
+ 'dt-bindings/clock/zte,zx297520v3-clk.h' header. Resets are defined in the
+ 'dt-bindings/reset/zte,zx297520v3-reset.h' header.
+
+properties:
+ compatible:
+ const: zte,zx297520v3-lspcrm
+
+ reg:
+ maxItems: 1
+
+ clocks:
+ items:
+ - description: Main PLL divided by 5 output from matrixcrm (124.8 MHz)
+ - description: Main PLL divided by 4 output from matrixcrm (156 MHz)
+ - description: Main PLL divided by 6 output from matrixcrm (104 MHz)
+ - description: Main PLL divided by 8 output from matrixcrm (78 MHz)
+ - description: Main PLL divided by 12 output from matrixcrm (52 MHz)
+ - description: Main oscillator output from matrixcrm (26 MHz)
+ - description: Timer oscillator output from matrixcrm (32 KHz)
+ - description: LSP pclk output from matrixcrm (26 MHz)
+ - description: TDM wclk mux output from matrixcrm
+ - description: DPLL divided by 4 output from matrixcrm (122.88 MHz)
+
+ clock-names:
+ items:
+ - const: mpll_d5
+ - const: mpll_d4
+ - const: mpll_d6
+ - const: mpll_d8
+ - const: mpll_d12
+ - const: osc26m
+ - const: osc32k
+ - const: pclk
+ - const: tdm_wclk
+ - const: dpll_d4
+
+ "#clock-cells":
+ const: 1
+
+ "#reset-cells":
+ const: 1
+
+required:
+ - compatible
+ - reg
+ - clocks
+ - clock-names
+ - '#clock-cells'
+ - '#reset-cells'
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/clock/zte,zx297520v3-clk.h>
+ #include <dt-bindings/reset/zte,zx297520v3-reset.h>
+
+ clock-controller@1400000 {
+ compatible = "zte,zx297520v3-lspcrm";
+ reg = <0x01400000 0x100>;
+ clocks = <&matrixcrm ZX297520V3_LSP_MPLL_D5_WCLK>,
+ <&matrixcrm ZX297520V3_LSP_MPLL_D4_WCLK>,
+ <&matrixcrm ZX297520V3_LSP_MPLL_D6_WCLK>,
+ <&matrixcrm ZX297520V3_LSP_MPLL_D8_WCLK>,
+ <&matrixcrm ZX297520V3_LSP_MPLL_D12_WCLK>,
+ <&matrixcrm ZX297520V3_LSP_OSC26M_WCLK>,
+ <&matrixcrm ZX297520V3_LSP_OSC32K_WCLK>,
+ <&matrixcrm ZX297520V3_LSP_PCLK>,
+ <&matrixcrm ZX297520V3_LSP_TDM_WCLK>,
+ <&matrixcrm ZX297520V3_LSP_DPLL_D4_WCLK>;
+ clock-names = "mpll_d5", "mpll_d4", "mpll_d6", "mpll_d8", "mpll_d12",
+ "osc26m", "osc32k", "pclk", "tdm_wclk", "dpll_d4";
+ #clock-cells = <1>;
+ #reset-cells = <1>;
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index 0f9e588e4839..ee585982b859 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3867,6 +3867,7 @@ L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
S: Odd fixes
F: Documentation/arch/arm/zte/
F: Documentation/devicetree/bindings/arm/zte.yaml
+F: Documentation/devicetree/bindings/clock/zte,zx297520v3-lspcrm.yaml
F: Documentation/devicetree/bindings/soc/zte/
F: arch/arm/boot/dts/zte/
F: arch/arm/mach-zte/
diff --git a/include/dt-bindings/clock/zte,zx297520v3-clk.h b/include/dt-bindings/clock/zte,zx297520v3-clk.h
index 8a6aa456a708..3c390211f897 100644
--- a/include/dt-bindings/clock/zte,zx297520v3-clk.h
+++ b/include/dt-bindings/clock/zte,zx297520v3-clk.h
@@ -131,4 +131,41 @@
#define ZX297520V3_SRAM0_PCLK 35
#define ZX297520V3_GSM_CFG_PCLK 36
+#define ZX297520V3_TIMER_L1_WCLK 1
+#define ZX297520V3_TIMER_L1_PCLK 2
+#define ZX297520V3_WDT_L2_WCLK 3
+#define ZX297520V3_WDT_L2_PCLK 4
+#define ZX297520V3_WDT_L3_WCLK 5
+#define ZX297520V3_WDT_L3_PCLK 6
+#define ZX297520V3_PWM_WCLK 7
+#define ZX297520V3_PWM_PCLK 8
+#define ZX297520V3_I2S0_WCLK 9
+#define ZX297520V3_I2S0_PCLK 10
+#define ZX297520V3_I2S1_WCLK 11
+#define ZX297520V3_I2S1_PCLK 12
+#define ZX297520V3_QSPI_WCLK 13
+#define ZX297520V3_QSPI_PCLK 14
+#define ZX297520V3_UART1_WCLK 15
+#define ZX297520V3_UART1_PCLK 16
+#define ZX297520V3_I2C1_WCLK 17
+#define ZX297520V3_I2C1_PCLK 18
+#define ZX297520V3_SPI0_WCLK 19
+#define ZX297520V3_SPI0_PCLK 20
+#define ZX297520V3_TIMER_LB_WCLK 21
+#define ZX297520V3_TIMER_LB_PCLK 22
+#define ZX297520V3_TIMER_LC_WCLK 23
+#define ZX297520V3_TIMER_LC_PCLK 24
+#define ZX297520V3_UART2_WCLK 25
+#define ZX297520V3_UART2_PCLK 26
+#define ZX297520V3_WDT_LE_WCLK 27
+#define ZX297520V3_WDT_LE_PCLK 28
+#define ZX297520V3_TIMER_LF_WCLK 29
+#define ZX297520V3_TIMER_LF_PCLK 30
+#define ZX297520V3_SPI1_WCLK 31
+#define ZX297520V3_SPI1_PCLK 32
+#define ZX297520V3_TIMER_L11_WCLK 33
+#define ZX297520V3_TIMER_L11_PCLK 34
+#define ZX297520V3_TDM_WCLK 35
+#define ZX297520V3_TDM_PCLK 36
+
#endif /* __DT_BINDINGS_CLOCK_ZX297520V3_H */
diff --git a/include/dt-bindings/reset/zte,zx297520v3-reset.h b/include/dt-bindings/reset/zte,zx297520v3-reset.h
index 81ffc8bc34c5..85a2f0707cdc 100644
--- a/include/dt-bindings/reset/zte,zx297520v3-reset.h
+++ b/include/dt-bindings/reset/zte,zx297520v3-reset.h
@@ -39,4 +39,23 @@
#define ZX297520V3_GMAC_RESET 7
#define ZX297520V3_VOU_RESET 8
+#define ZX297520V3_TIMER_L1_RESET 0
+#define ZX297520V3_WDT_L2_RESET 1
+#define ZX297520V3_WDT_L3_RESET 2
+#define ZX297520V3_PWM_RESET 3
+#define ZX297520V3_I2S0_RESET 4
+#define ZX297520V3_I2S1_RESET 5
+#define ZX297520V3_QSPI_RESET 6
+#define ZX297520V3_UART1_RESET 7
+#define ZX297520V3_I2C1_RESET 8
+#define ZX297520V3_SPI0_RESET 9
+#define ZX297520V3_TIMER_LB_RESET 10
+#define ZX297520V3_TIMER_LC_RESET 11
+#define ZX297520V3_UART2_RESET 12
+#define ZX297520V3_WDT_LE_RESET 13
+#define ZX297520V3_TIMER_LF_RESET 14
+#define ZX297520V3_SPI1_RESET 15
+#define ZX297520V3_TIMER_L11_RESET 16
+#define ZX297520V3_TDM_RESET 17
+
#endif /* __DT_BINDINGS_RESET_ZX297520V3_H */
--
2.53.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH RFC v5 04/12] mfd: zx297520v3: Add a clock and reset MFD driver.
2026-06-28 19:58 [PATCH RFC v5 00/12] ZTE zx297520v3 clock bindings and driver Stefan Dösinger
` (2 preceding siblings ...)
2026-06-28 19:58 ` [PATCH RFC v5 03/12] dt-bindings: clk: zte: Add zx297520v3 LSP " Stefan Dösinger
@ 2026-06-28 19:58 ` Stefan Dösinger
2026-06-28 20:10 ` sashiko-bot
2026-06-28 19:59 ` [PATCH RFC v5 05/12] clk: zte: Add Clock registration infrastructure Stefan Dösinger
` (7 subsequent siblings)
11 siblings, 1 reply; 23+ messages in thread
From: Stefan Dösinger @ 2026-06-28 19:58 UTC (permalink / raw)
To: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel, Brian Masney
Cc: linux-clk, devicetree, linux-kernel, linux-arm-kernel,
Stefan Dösinger
This driver registers child devices for the zx297520v3 clock and reset
controllers. The clk-zx297520v3 and reset-zte-zx297520v3 submitted in
the next patches will drive the respective functionalities.
Signed-off-by: Stefan Dösinger <stefandoesinger@gmail.com>
---
Changes v5: Use MFD instead of Aux bus for top and matrix crm because of
extra functionality: Reboot in top, hwlock in Matrix.
LSP clocks stay with the aux bus and are thus not handled in this
driver. The clk driver will bind directly to the lspcrm node.
---
MAINTAINERS | 1 +
drivers/soc/Kconfig | 1 +
drivers/soc/Makefile | 1 +
drivers/soc/zte/Kconfig | 20 +++++++++++
drivers/soc/zte/Makefile | 3 ++
drivers/soc/zte/zx297520v3-crm.c | 76 ++++++++++++++++++++++++++++++++++++++++
6 files changed, 102 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index ee585982b859..57af566030db 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3871,6 +3871,7 @@ F: Documentation/devicetree/bindings/clock/zte,zx297520v3-lspcrm.yaml
F: Documentation/devicetree/bindings/soc/zte/
F: arch/arm/boot/dts/zte/
F: arch/arm/mach-zte/
+F: drivers/soc/zte/
F: include/dt-bindings/clock/zte,zx297520v3-clk.h
F: include/dt-bindings/reset/zte,zx297520v3-reset.h
diff --git a/drivers/soc/Kconfig b/drivers/soc/Kconfig
index a2d65adffb80..5cc1ade4ce52 100644
--- a/drivers/soc/Kconfig
+++ b/drivers/soc/Kconfig
@@ -31,5 +31,6 @@ source "drivers/soc/ux500/Kconfig"
source "drivers/soc/versatile/Kconfig"
source "drivers/soc/vt8500/Kconfig"
source "drivers/soc/xilinx/Kconfig"
+source "drivers/soc/zte/Kconfig"
endmenu
diff --git a/drivers/soc/Makefile b/drivers/soc/Makefile
index c9e689080ceb..63b3f340256c 100644
--- a/drivers/soc/Makefile
+++ b/drivers/soc/Makefile
@@ -37,3 +37,4 @@ obj-$(CONFIG_ARCH_U8500) += ux500/
obj-y += versatile/
obj-y += vt8500/
obj-y += xilinx/
+obj-y += zte/
diff --git a/drivers/soc/zte/Kconfig b/drivers/soc/zte/Kconfig
new file mode 100644
index 000000000000..1016fb99cf73
--- /dev/null
+++ b/drivers/soc/zte/Kconfig
@@ -0,0 +1,20 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+if ARCH_ZTE || COMPILE_TEST
+
+menu "ZTE SoC drivers"
+
+config ZTE_ZX297520V3_CRM
+ tristate "ZTE zx297520v3 Clock and Reset Manager"
+ select MFD_CORE
+ default SOC_ZX297520V3
+ help
+ Say yes here to enable the driver for the ZTE zx297520v3 clock and
+ reset manager MFD driver. This driver provides the host device for
+ the clock and reset drivers and is required to boot the SoC. You
+ will also need to enable CLK_ZTE_ZX297520V3 and RESET_ZTE_ZX297520V3
+ to build the actual clock and reset submodule drivers.
+
+endmenu
+
+endif
diff --git a/drivers/soc/zte/Makefile b/drivers/soc/zte/Makefile
new file mode 100644
index 000000000000..090ba8aa06c1
--- /dev/null
+++ b/drivers/soc/zte/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+obj-$(CONFIG_ZTE_ZX297520V3_CRM) += zx297520v3-crm.o
diff --git a/drivers/soc/zte/zx297520v3-crm.c b/drivers/soc/zte/zx297520v3-crm.c
new file mode 100644
index 000000000000..bf5a71bc7706
--- /dev/null
+++ b/drivers/soc/zte/zx297520v3-crm.c
@@ -0,0 +1,76 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2026 Stefan Dösinger
+ */
+
+#include <linux/platform_device.h>
+#include <linux/mfd/core.h>
+#include <linux/module.h>
+#include <linux/of.h>
+
+struct zx297520v3_crm_data {
+ const struct mfd_cell *cells;
+ unsigned int num_cells;
+};
+
+static const struct mfd_cell zx297520v3_topcrm_devs[] = {
+ {
+ .name = "zx297520v3-topclk",
+ },
+ {
+ .name = "zx297520v3-toprst",
+ },
+ {
+ .name = "zx297520v3-reboot",
+ .of_compatible = "syscon-reboot",
+ },
+};
+
+static const struct zx297520v3_crm_data zx297520v3_topcrm_data = {
+ zx297520v3_topcrm_devs,
+ ARRAY_SIZE(zx297520v3_topcrm_devs),
+};
+
+static const struct mfd_cell zx297520v3_matrixcrm_devs[] = {
+ {
+ .name = "zx297520v3-matrixclk",
+ },
+ {
+ .name = "zx297520v3-matrixrst",
+ },
+};
+
+static const struct zx297520v3_crm_data zx297520v3_matrixcrm_data = {
+ zx297520v3_matrixcrm_devs,
+ ARRAY_SIZE(zx297520v3_matrixcrm_devs),
+};
+
+static int zx297520v3_crm_probe(struct platform_device *pdev)
+{
+ const struct zx297520v3_crm_data *data;
+
+ data = of_device_get_match_data(&pdev->dev);
+ return devm_mfd_add_devices(&pdev->dev, PLATFORM_DEVID_NONE, data->cells,
+ data->num_cells, NULL, 0, NULL);
+}
+
+static const struct of_device_id of_match_zx297520v3_crm[] = {
+ { .compatible = "zte,zx297520v3-topcrm", .data = &zx297520v3_topcrm_data },
+ { .compatible = "zte,zx297520v3-matrixcrm", .data = &zx297520v3_matrixcrm_data },
+ { }
+};
+MODULE_DEVICE_TABLE(of, of_match_zx297520v3_crm);
+
+static struct platform_driver zx297520v3_crm = {
+ .probe = zx297520v3_crm_probe,
+ .driver = {
+ .name = "zx297520v3-crm",
+ .of_match_table = of_match_zx297520v3_crm,
+ },
+};
+
+module_platform_driver(zx297520v3_crm);
+
+MODULE_AUTHOR("Stefan Dösinger <stefandoesinger@gmail.com>");
+MODULE_DESCRIPTION("ZTE zx297520v3 CRM MFD host driver");
+MODULE_LICENSE("GPL");
--
2.53.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH RFC v5 05/12] clk: zte: Add Clock registration infrastructure.
2026-06-28 19:58 [PATCH RFC v5 00/12] ZTE zx297520v3 clock bindings and driver Stefan Dösinger
` (3 preceding siblings ...)
2026-06-28 19:58 ` [PATCH RFC v5 04/12] mfd: zx297520v3: Add a clock and reset MFD driver Stefan Dösinger
@ 2026-06-28 19:59 ` Stefan Dösinger
2026-06-28 20:10 ` sashiko-bot
2026-06-28 19:59 ` [PATCH RFC v5 06/12] clk: zte: Add zx PLL support infrastructure Stefan Dösinger
` (6 subsequent siblings)
11 siblings, 1 reply; 23+ messages in thread
From: Stefan Dösinger @ 2026-06-28 19:59 UTC (permalink / raw)
To: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel, Brian Masney
Cc: linux-clk, devicetree, linux-kernel, linux-arm-kernel,
Stefan Dösinger
The next patches will implement the regmap clocks and PLL driver. The
actual hardware specific clock listing will live in a separate module.
Signed-off-by: Stefan Dösinger <stefandoesinger@gmail.com>
---
Version 5:
*) Pass the static clk data instead of calling get_match_data to prepare
for operating as an MFD child.
*) Don't use devm_kzalloc to allocate the auxiliary_device
structure. I guess Sashiko is right, and that's what "Because once the
device is placed on the bus the parent driver can not tell what other
code may have a reference to this data" is trying to dell me.
*) Fix error check for device_node_to_regmap.
---
MAINTAINERS | 1 +
drivers/clk/Kconfig | 1 +
drivers/clk/Makefile | 1 +
drivers/clk/zte/Kconfig | 17 ++++
drivers/clk/zte/Makefile | 5 ++
drivers/clk/zte/clk-regmap.c | 30 +++++++
drivers/clk/zte/clk-zx.c | 192 +++++++++++++++++++++++++++++++++++++++++++
drivers/clk/zte/clk-zx.h | 81 ++++++++++++++++++
drivers/clk/zte/pll-zx.c | 19 +++++
9 files changed, 347 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 57af566030db..297c15a2c860 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3871,6 +3871,7 @@ F: Documentation/devicetree/bindings/clock/zte,zx297520v3-lspcrm.yaml
F: Documentation/devicetree/bindings/soc/zte/
F: arch/arm/boot/dts/zte/
F: arch/arm/mach-zte/
+F: drivers/clk/zte/
F: drivers/soc/zte/
F: include/dt-bindings/clock/zte,zx297520v3-clk.h
F: include/dt-bindings/reset/zte,zx297520v3-reset.h
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index 1717ce75a907..6f0a863951ca 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -545,6 +545,7 @@ source "drivers/clk/uniphier/Kconfig"
source "drivers/clk/visconti/Kconfig"
source "drivers/clk/x86/Kconfig"
source "drivers/clk/xilinx/Kconfig"
+source "drivers/clk/zte/Kconfig"
source "drivers/clk/zynqmp/Kconfig"
# Kunit test cases
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index cc108a75a900..13a5478f1112 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -167,5 +167,6 @@ ifeq ($(CONFIG_COMMON_CLK), y)
obj-$(CONFIG_X86) += x86/
endif
obj-y += xilinx/
+obj-$(CONFIG_COMMON_CLK_ZTE) += zte/
obj-$(CONFIG_ARCH_ZYNQ) += zynq/
obj-$(CONFIG_COMMON_CLK_ZYNQMP) += zynqmp/
diff --git a/drivers/clk/zte/Kconfig b/drivers/clk/zte/Kconfig
new file mode 100644
index 000000000000..b7b65a2172a9
--- /dev/null
+++ b/drivers/clk/zte/Kconfig
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# ZTE Clock Drivers
+#
+
+config COMMON_CLK_ZTE
+ tristate "Clock driver for ZTE SoCs"
+ depends on ARCH_ZTE || COMPILE_TEST
+ default ARCH_ZTE
+ select AUXILIARY_BUS
+ select MFD_SYSCON
+ help
+ This option selects common clock infrastructure for ZTE based SoCs.
+ You will need to enable one or more SoC specific drivers to make use
+ of this.
+
+ Enable this if you are building a kernel for a ZTE designed board.
diff --git a/drivers/clk/zte/Makefile b/drivers/clk/zte/Makefile
new file mode 100644
index 000000000000..27db07293165
--- /dev/null
+++ b/drivers/clk/zte/Makefile
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+obj-$(CONFIG_COMMON_CLK_ZTE) += clk-zte.o
+
+clk-zte-y += clk-zx.o pll-zx.o clk-regmap.o
diff --git a/drivers/clk/zte/clk-regmap.c b/drivers/clk/zte/clk-regmap.c
new file mode 100644
index 000000000000..7908f1562f63
--- /dev/null
+++ b/drivers/clk/zte/clk-regmap.c
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2014 MediaTek Inc.
+ * Copyright (c) 2018 BayLibre, SAS.
+ * Copyright (c) 2026 Stefan Dösinger.
+ * Author: Stefan Dösinger <stefandoesinger@gmail.com>
+ */
+
+#include "clk-zx.h"
+
+int zx_clk_register_gates(struct device *dev, struct regmap *regmap,
+ const struct zx_gate_desc *desc, unsigned int num,
+ struct clk_hw_onecell_data *clocks)
+{
+ return -ENODEV;
+}
+
+int zx_clk_register_dividers(struct device *dev, struct regmap *regmap,
+ const struct zx_div_desc *desc, unsigned int num,
+ struct clk_hw_onecell_data *clocks)
+{
+ return -ENODEV;
+}
+
+int zx_clk_register_muxes(struct device *dev, struct regmap *regmap,
+ const struct zx_mux_desc *desc, unsigned int num,
+ struct clk_hw_onecell_data *clocks)
+{
+ return -ENODEV;
+}
diff --git a/drivers/clk/zte/clk-zx.c b/drivers/clk/zte/clk-zx.c
new file mode 100644
index 000000000000..d098243145ce
--- /dev/null
+++ b/drivers/clk/zte/clk-zx.c
@@ -0,0 +1,192 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2026 Stefan Dösinger
+ */
+
+#include <linux/auxiliary_bus.h>
+#include <linux/clk-provider.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/clk.h>
+
+#include "clk-zx.h"
+
+static void zx_adev_release(struct device *dev)
+{
+ struct auxiliary_device *adev = to_auxiliary_dev(dev);
+
+ kfree(adev);
+}
+
+static void zx_adev_unregister(void *data)
+{
+ struct auxiliary_device *adev = data;
+
+ auxiliary_device_delete(adev);
+ auxiliary_device_uninit(adev);
+}
+
+static void zx_delete_clk_provider(void *data)
+{
+ of_clk_del_provider(data);
+}
+
+static void zx_clk_disable_unprepare_put(void *data)
+{
+ clk_disable_unprepare(data);
+ clk_put(data);
+}
+
+int zx_clk_common_probe(struct device *dev, struct device_node *of_node,
+ const struct zx_clk_data *data)
+{
+ unsigned int public_clk_count = 1, highest_id = 0;
+ struct clk_hw_onecell_data *clocks;
+ struct auxiliary_device *adev;
+ struct regmap *map;
+ struct clk *clk;
+ unsigned int i;
+ int res;
+
+ map = device_node_to_regmap(of_node);
+ if (IS_ERR(map))
+ return PTR_ERR(map);
+
+ for (i = 0; i < data->num_plls; ++i) {
+ if (data->plls[i].id) {
+ unsigned int last_idx = data->plls[i].id + data->plls[i].num_postdivs - 1;
+
+ if (last_idx > highest_id)
+ highest_id = last_idx;
+ public_clk_count += data->plls[i].num_postdivs;
+ }
+ }
+ for (i = 0; i < data->num_muxes; ++i) {
+ if (data->muxes[i].id) {
+ if (data->muxes[i].id > highest_id)
+ highest_id = data->muxes[i].id;
+ public_clk_count++;
+ }
+ }
+ for (i = 0; i < data->num_divs; ++i) {
+ if (data->divs[i].id) {
+ if (data->divs[i].id > highest_id)
+ highest_id = data->divs[i].id;
+ public_clk_count++;
+ }
+ }
+ for (i = 0; i < data->num_gates; ++i) {
+ if (data->gates[i].id) {
+ if (data->gates[i].id > highest_id)
+ highest_id = data->gates[i].id;
+ public_clk_count++;
+ }
+ }
+
+ if (WARN_ON(public_clk_count != highest_id + 1))
+ return -EINVAL;
+
+ clocks = devm_kzalloc(dev, struct_size(clocks, hws, public_clk_count), GFP_KERNEL);
+ if (!clocks)
+ return -ENOMEM;
+ clocks->num = public_clk_count;
+
+ for (i = 0; i < data->num_inputs_enable; ++i) {
+ clk = of_clk_get_by_name(of_node, data->inputs_enable[i]);
+ if (IS_ERR(clk)) {
+ return dev_err_probe(dev, PTR_ERR(clk), "Input clk %s failure\n",
+ data->inputs_enable[i]);
+ }
+
+ res = clk_prepare_enable(clk);
+ if (res) {
+ clk_put(clk);
+ return dev_err_probe(dev, PTR_ERR(clk), "Input clk %s enable failure\n",
+ data->inputs_enable[i]);
+ }
+ res = devm_add_action_or_reset(dev, zx_clk_disable_unprepare_put, clk);
+ if (res)
+ return res;
+ }
+ for (i = 0; i < data->num_inputs; ++i) {
+ /* FIXME: devm_get_clk_from_child doesn't do any tree traversal, so it works here
+ * whether "of_node" belongs to "dev" or a parent of "dev". Is it supposed to be
+ * used that way though?
+ */
+ clk = devm_get_clk_from_child(dev, of_node, data->inputs[i]);
+ if (IS_ERR(clk)) {
+ return dev_err_probe(dev, PTR_ERR(clk), "Input clk %s failure\n",
+ data->inputs[i]);
+ }
+ }
+
+ res = zx_clk_register_plls(dev, map, data->plls, data->num_plls, clocks);
+ if (res)
+ return res;
+
+ res = zx_clk_register_muxes(dev, map, data->muxes, data->num_muxes, clocks);
+ if (res)
+ return res;
+
+ res = zx_clk_register_dividers(dev, map, data->divs, data->num_divs, clocks);
+ if (res)
+ return res;
+
+ res = zx_clk_register_gates(dev, map, data->gates, data->num_gates, clocks);
+ if (res)
+ return res;
+
+ /* This is to catch holes in the tables rather than registration errors. The count vs
+ * highest ID should catch most static issues. This check here will trigger if an ID is
+ * reused by accident.
+ */
+ for (i = 1; i < public_clk_count; i++) {
+ if (WARN(!clocks->hws[i], "Clock %u not registered\n", i))
+ return -EINVAL;
+ }
+
+ res = of_clk_add_hw_provider(of_node, of_clk_hw_onecell_get, clocks);
+ if (res)
+ return res;
+ res = devm_add_action_or_reset(dev, zx_delete_clk_provider, of_node);
+ if (res)
+ return res;
+
+ if (!data->reset_auxdev_name)
+ return 0;
+
+ adev = kzalloc_obj(*adev);
+ if (!adev)
+ return -ENOMEM;
+
+ adev->name = data->reset_auxdev_name;
+ adev->dev.parent = dev;
+ adev->dev.release = zx_adev_release;
+ adev->dev.of_node = of_node;
+
+ res = auxiliary_device_init(adev);
+ if (res) {
+ dev_err_probe(dev, res, "Failed to init aux dev %s\n", adev->name);
+ goto adev_free;
+ }
+
+ res = auxiliary_device_add(adev);
+ if (res) {
+ dev_err_probe(dev, res, "Failed to add aux dev %s\n", adev->name);
+ goto adev_uninit;
+ }
+
+ return devm_add_action_or_reset(dev, zx_adev_unregister, adev);
+
+adev_uninit:
+ auxiliary_device_uninit(adev);
+adev_free:
+ kfree(adev);
+ return res;
+}
+EXPORT_SYMBOL_NS_GPL(zx_clk_common_probe, "ZTE_CLK");
+
+MODULE_AUTHOR("Stefan Dösinger <stefandoesinger@gmail.com>");
+MODULE_DESCRIPTION("ZTE common clock driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/clk/zte/clk-zx.h b/drivers/clk/zte/clk-zx.h
new file mode 100644
index 000000000000..dabb71f27c16
--- /dev/null
+++ b/drivers/clk/zte/clk-zx.h
@@ -0,0 +1,81 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2026 Stefan Dösinger
+ */
+
+#ifndef __DRV_CLK_ZX_H
+#define __DRV_CLK_ZX_H
+
+#include <linux/platform_device.h>
+#include <linux/clk-provider.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+
+struct zx_pll_desc {
+ unsigned int id;
+ const char *name;
+ const char * const *parents;
+ unsigned int num_parents;
+ unsigned long rate;
+ const unsigned int *postdivs;
+ unsigned int num_postdivs;
+ u16 reg;
+};
+
+struct zx_mux_desc {
+ unsigned int id;
+ const char *name;
+ const char * const *parents;
+ unsigned int num_parents;
+ u16 reg;
+ u8 shift, size;
+};
+
+struct zx_div_desc {
+ unsigned int id;
+ const char *name, *parent;
+ u16 reg;
+ u8 shift, size;
+};
+
+struct zx_gate_desc {
+ unsigned int id;
+ const char *name, *parent;
+ unsigned long flags;
+ u16 reg;
+ u8 shift;
+};
+
+int zx_clk_register_plls(struct device *dev, struct regmap *regmap,
+ const struct zx_pll_desc *desc, unsigned int num,
+ struct clk_hw_onecell_data *clocks);
+int zx_clk_register_muxes(struct device *dev, struct regmap *regmap,
+ const struct zx_mux_desc *desc, unsigned int num,
+ struct clk_hw_onecell_data *clocks);
+int zx_clk_register_dividers(struct device *dev, struct regmap *regmap,
+ const struct zx_div_desc *desc, unsigned int num,
+ struct clk_hw_onecell_data *clocks);
+int zx_clk_register_gates(struct device *dev, struct regmap *regmap,
+ const struct zx_gate_desc *desc, unsigned int num,
+ struct clk_hw_onecell_data *clocks);
+
+struct zx_clk_data {
+ const char * const *inputs_enable;
+ unsigned int num_inputs_enable;
+ const char * const *inputs;
+ unsigned int num_inputs;
+ const struct zx_pll_desc *plls;
+ unsigned int num_plls;
+ const struct zx_mux_desc *muxes;
+ unsigned int num_muxes;
+ const struct zx_div_desc *divs;
+ unsigned int num_divs;
+ const struct zx_gate_desc *gates;
+ unsigned int num_gates;
+ const char *reset_auxdev_name;
+};
+
+int zx_clk_common_probe(struct device *dev, struct device_node *of_node,
+ const struct zx_clk_data *data);
+
+#endif /* __DRV_CLK_ZX_H */
diff --git a/drivers/clk/zte/pll-zx.c b/drivers/clk/zte/pll-zx.c
new file mode 100644
index 000000000000..c0475d5441fb
--- /dev/null
+++ b/drivers/clk/zte/pll-zx.c
@@ -0,0 +1,19 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2026 Stefan Dösinger
+ */
+#include <linux/clk-provider.h>
+#include <linux/rational.h>
+#include <linux/device.h>
+#include <linux/regmap.h>
+#include <linux/units.h>
+#include <linux/clk.h>
+
+#include "clk-zx.h"
+
+int zx_clk_register_plls(struct device *dev, struct regmap *regmap,
+ const struct zx_pll_desc *desc, unsigned int num,
+ struct clk_hw_onecell_data *clocks)
+{
+ return -ENODEV;
+}
--
2.53.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH RFC v5 06/12] clk: zte: Add zx PLL support infrastructure
2026-06-28 19:58 [PATCH RFC v5 00/12] ZTE zx297520v3 clock bindings and driver Stefan Dösinger
` (4 preceding siblings ...)
2026-06-28 19:59 ` [PATCH RFC v5 05/12] clk: zte: Add Clock registration infrastructure Stefan Dösinger
@ 2026-06-28 19:59 ` Stefan Dösinger
2026-06-28 20:14 ` sashiko-bot
2026-06-28 19:59 ` [PATCH RFC v5 07/12] clk: zte: Add regmap based clocks Stefan Dösinger
` (5 subsequent siblings)
11 siblings, 1 reply; 23+ messages in thread
From: Stefan Dösinger @ 2026-06-28 19:59 UTC (permalink / raw)
To: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel, Brian Masney
Cc: linux-clk, devicetree, linux-kernel, linux-arm-kernel,
Stefan Dösinger
I am guessing how much of this is reusable among other zx chips or even
differently named ZTE platforms (if there are any). From reading the old
zx2967 code, I think the PLL code would be reusable there, maybe with
platform specific bitmasks but otherwise the same logic.
Signed-off-by: Stefan Dösinger <stefandoesinger@gmail.com>
---
Version 5: Fix Some issues pointed out by Sashiko: NULL dev,
zx29_pll_recalc_rate error handling, disable PLL again on enable error.
I am not sure how to deal with best_parent_rate I added a FIXME and I'm
hoping for human suggestions.
---
drivers/clk/zte/pll-zx.c | 468 ++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 467 insertions(+), 1 deletion(-)
diff --git a/drivers/clk/zte/pll-zx.c b/drivers/clk/zte/pll-zx.c
index c0475d5441fb..1482caeea999 100644
--- a/drivers/clk/zte/pll-zx.c
+++ b/drivers/clk/zte/pll-zx.c
@@ -11,9 +11,475 @@
#include "clk-zx.h"
+/* This code has only been tested with zx297520v3 PLLs, but from reading the zx296718 clock code it
+ * looks like PLL registers are similar. ZTE's sources explain the PLL register contents only in a
+ * .cmm file (A Lauterback TRACE32 script) and some unused headers in their U-Boot code dump, which
+ * may not be accurate. When calculating the frequencies from the default PLL configuration the
+ * results match the fixed rate clocks from their clock driver.
+ *
+ * The 26mhz and 32khz clocks can be easily observed with the timers. The 104mhz output can be
+ * observed through the UART. One 122.88 PLL can be observed through the TDM device. All others can
+ * only be indirectly infered, e.g. by comparing CPU speed or SDIO transfer rate between the fixed
+ * 26 MHz oscillator and the provided PLL frequency.
+ *
+ * The formula to calculate the clock is ((ref / refdiv) * fbdiv) / postdiv1 / postdiv2. The masks
+ * are given below. There are a few control flags:
+ *
+ * Bit 31: Disables the PLL, but passes the reference through unmodified. If POSTDIV_OUT_DISABLE
+ * still matters is different between PLLs.
+ * Bit 30: Returns if the PLL is locked
+ * Bit 29: Not named in ZTE's code, but can be set. There is no obvious impact. Lock times are
+ * unchanged, so it doesn't influence or bypass lock detection. It doesn't raise any IRQs or
+ * influence GPIOs.
+ * Bit 27: Given its name it likely disables the Delta-Sigma Modulator, if one exists at all. The
+ * boot ROM sets it on every PLL. Unsetting it marginally decreases the time it takes to
+ * lock to the reference clock (from ~400us to ~300us). Regardless of this bit I could not
+ * make the supposed fractional part in register 2 work.
+ * Bit 24: Bypasses the VCO, but still applies refdiv and postdiv. Doesn't matter if PLL_DISABLE=1.
+ */
+
+#define ZX29_PLL_DISABLE BIT(31)
+#define ZX29_PLL_LOCKED BIT(30)
+#define ZX29_PLL_LOCK_FILTER BIT(29)
+#define ZX29_PLL_DSM_DISABLE BIT(27)
+#define ZX29_PLL_PARENT_MASK GENMASK(26, 25)
+#define ZX29_PLL_PARENT_SHIFT 25
+#define ZX29_PLL_BYPASS BIT(24)
+#define ZX29_PLL_REFDIV_MASK GENMASK(23, 18)
+#define ZX29_PLL_REFDIV_SHIFT 18
+#define ZX29_PLL_FBDIV_MASK GENMASK(17, 6)
+#define ZX29_PLL_FBDIV_SHIFT 6
+#define ZX29_PLL_POSTDIV1_MASK GENMASK(5, 3)
+#define ZX29_PLL_POSTDIV1_SHIFT 3
+#define ZX29_PLL_POSTDIV2_MASK GENMASK(2, 0)
+#define ZX29_PLL_POSTDIV2_SHIFT 0
+
+/* The second register is supposed to have another 24 bit value that gets added to fbdiv but it is
+ * always 0 in the preconfigured values. I could not observe any effect from setting it to something
+ * other than 0, regardless of the DSM disable bit. It is possible that it is only supported by
+ * dpll, which is a possible parent for i2s.
+ *
+ * Bits 28:25 contain more flags:
+ *
+ * Bit 27: Setting ZX29_PLL_DACAP slows down the lock time and obivates the speed gained from
+ * !DSM_DISABLE. No other effect observed.
+ *
+ * Bit 26: ZX29_PLL_4PHASE_OUT_DISABLE is set on some PLLs on boot but not on others. It is set on
+ * boot on mpll and upll, but not gpll, dpll or unknownpll. I am not sure what it does
+ * either. The SDIO devices break if they are fed from gpll with this flag set, but they
+ * work ok if they are fed from mpll without this flag set.
+ *
+ * Bit 25: ZX29_PLL_POSTDIV_OUT_DISABLE seems to disable the PLL output entirely. Whether it is
+ * bypassed by PLL_DISABLE differs between PLLs. gpll still produces an output clock if
+ * PLL_DISABLE = 1 and POSTDIV_DISABLE = 1, but produces no output if PLL_DISABLE = 0 and
+ * POSTDIV_DISABLE = 1. The dpll feeder ("unknownpll") at 0x100 produces no output clock
+ * if both PLL_DISABLE and POSTDIV_DISABLE are set to 1.
+ *
+ * Bit 24: ZX29_PLL_VCO_OUT_DISABLE probably disables the output of the VCO clock without
+ * post-VCO-dividers, but the raw VCO output is not a possible parent of any consumer clock,
+ * so I could not confirm this. It does not disable the VCO entirely - that's what
+ * PLL_DISABLE does.
+ *
+ * A spinlock should not be needed. PLLs don't share their registers with anything else and the
+ * global prepare mutex and enable spinlock should be enough. Beware of conflicts in reg2 between
+ * POSTDIV_OUT_DISABLE and the fractional value in case you find out how fractional dividers work
+ * and add support for them.
+ */
+#define ZX29_PLL_REG2_OFFSET 4
+#define ZX29_PLL_DACAP BIT(27)
+#define ZX29_PLL_4PHASE_OUT_DISABLE BIT(26)
+#define ZX29_PLL_POSTDIV_OUT_DISABLE BIT(25)
+#define ZX29_PLL_VCO_OUT_DISABLE BIT(24)
+
+/* The VCO's frequency range is limited. The stock settings run the VCO between 960 and 1248 MHz.
+ * Ad-hoc testing with gpll suggests that at least this PLL remains stable down to about 7 MHz and
+ * up to 2 GHz and produces a clock that can be used by the SDIO controller. Attempting to run the
+ * mpll VCO at 624 MHz and setting postdiv1 = postdiv2 = 1 - which should result in the same output
+ * frequency - or running it at 1872 MHz with an effective post divider of 3 crashes the CPU. Most
+ * likely the PLLs become unstable outside their core range and the SDIO controller is much more
+ * forgiving than CPU and DRAM are.
+ */
+#define ZX29_PLL_VCO_MAX_FREQ (1300*HZ_PER_MHZ)
+#define ZX29_PLL_VCO_MIN_FREQ (900*HZ_PER_MHZ)
+
+struct zx29_clk_pll {
+ struct clk_hw hw;
+ struct device *dev;
+ struct regmap *map;
+ u16 reg;
+};
+
+static inline struct zx29_clk_pll *to_zx29_clk_pll(struct clk_hw *hw)
+{
+ return container_of(hw, struct zx29_clk_pll, hw);
+}
+
+static int zx29_pll_is_prepared(struct clk_hw *hw)
+{
+ struct zx29_clk_pll *pll = to_zx29_clk_pll(hw);
+ int res;
+
+ res = regmap_test_bits(pll->map, pll->reg, ZX29_PLL_DISABLE);
+ if (res < 0)
+ return res;
+
+ return !res;
+}
+
+static int zx29_pll_prepare(struct clk_hw *hw)
+{
+ struct zx29_clk_pll *pll = to_zx29_clk_pll(hw);
+ u32 val;
+ int res;
+
+ res = regmap_clear_bits(pll->map, pll->reg, ZX29_PLL_DISABLE);
+ if (res < 0)
+ return res;
+
+ /* Lock duration is usually between 300us to 500us */
+ res = regmap_read_poll_timeout(pll->map, pll->reg, val, val & ZX29_PLL_LOCKED, 50, 2000);
+ if (res) {
+ regmap_set_bits(pll->map, pll->reg, ZX29_PLL_DISABLE);
+ dev_err(pll->dev, "%s: PLL enable failed: %d\n", clk_hw_get_name(&pll->hw), val);
+ }
+ return res;
+}
+
+static void zx29_pll_unprepare(struct clk_hw *hw)
+{
+ struct zx29_clk_pll *pll = to_zx29_clk_pll(hw);
+
+ regmap_set_bits(pll->map, pll->reg, ZX29_PLL_DISABLE);
+}
+
+static int zx29_pll_is_enabled(struct clk_hw *hw)
+{
+ struct zx29_clk_pll *pll = to_zx29_clk_pll(hw);
+ int res;
+
+ res = regmap_test_bits(pll->map, pll->reg + ZX29_PLL_REG2_OFFSET,
+ ZX29_PLL_POSTDIV_OUT_DISABLE);
+ if (res < 0)
+ return res;
+
+ return !res;
+}
+
+static int zx29_pll_enable(struct clk_hw *hw)
+{
+ struct zx29_clk_pll *pll = to_zx29_clk_pll(hw);
+
+ return regmap_clear_bits(pll->map, pll->reg + ZX29_PLL_REG2_OFFSET,
+ ZX29_PLL_POSTDIV_OUT_DISABLE);
+}
+
+static void zx29_pll_disable(struct clk_hw *hw)
+{
+ struct zx29_clk_pll *pll = to_zx29_clk_pll(hw);
+
+ regmap_set_bits(pll->map, pll->reg + ZX29_PLL_REG2_OFFSET,
+ ZX29_PLL_POSTDIV_OUT_DISABLE);
+}
+
+static unsigned long zx29_pll_get_rate(const struct zx29_clk_pll *pll, unsigned long parent_rate,
+ u32 setting)
+{
+ unsigned long refdiv, fbdiv, postdiv1, postdiv2, freq;
+ const char *name = clk_hw_get_name(&pll->hw);
+ u64 vco;
+
+ refdiv = (setting & ZX29_PLL_REFDIV_MASK) >> ZX29_PLL_REFDIV_SHIFT;
+ fbdiv = (setting & ZX29_PLL_FBDIV_MASK) >> ZX29_PLL_FBDIV_SHIFT;
+ postdiv1 = (setting & ZX29_PLL_POSTDIV1_MASK) >> ZX29_PLL_POSTDIV1_SHIFT;
+ postdiv2 = (setting & ZX29_PLL_POSTDIV2_MASK) >> ZX29_PLL_POSTDIV2_SHIFT;
+ dev_dbg(pll->dev, "%s: reference clock %lu HZ, PLL setting 0x%08x\n",
+ name, parent_rate, setting);
+
+ if (!refdiv || !postdiv1 || !postdiv2) {
+ dev_err(pll->dev, "%s: divide by zero (%lu, %lu, %lu)\n", name, refdiv, postdiv1,
+ postdiv2);
+ return 0;
+ }
+
+ vco = div_u64((u64)parent_rate * fbdiv, refdiv);
+ freq = div_u64(div_u64(vco, postdiv1), postdiv2);
+ dev_dbg(pll->dev, "%s: refdiv %lu fbdiv %lu\n", name, refdiv, fbdiv);
+ dev_dbg(pll->dev, "%s: postdiv1 %lu postdiv2 %lu\n", name, postdiv1, postdiv2);
+
+ dev_dbg(pll->dev, "%s: %lu MHZ\n", name, freq / HZ_PER_MHZ);
+
+ return freq;
+}
+
+static unsigned long zx29_pll_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
+{
+ struct zx29_clk_pll *pll = to_zx29_clk_pll(hw);
+ u32 val;
+ int res;
+
+ res = regmap_read(pll->map, pll->reg, &val);
+ if (res < 0) {
+ dev_err(pll->dev, "%s: Failed to read PLL settings\n", clk_hw_get_name(&pll->hw));
+ return 0;
+ }
+
+ return zx29_pll_get_rate(pll, parent_rate, val);
+}
+
+static u32 zx29_pll_calc_values(const struct zx29_clk_pll *pll, unsigned long parent_rate,
+ unsigned long rate)
+{
+ const unsigned int postdiv1_max = (1 << hweight32(ZX29_PLL_POSTDIV1_MASK)) - 1;
+ const unsigned int postdiv2_max = (1 << hweight32(ZX29_PLL_POSTDIV2_MASK)) - 1;
+ unsigned long fbdiv, refdiv, best_fbdiv = 0, best_refdiv = 0;
+ u32 postdiv1 = 0, postdiv2 = 0, i, j, setting;
+ const char *name = clk_hw_get_name(&pll->hw);
+ long best = LONG_MAX;
+
+ /* This code produces the same VCO settings that the boot loader and stock firmware use for
+ * the standard frequencies. It has seen only very little manual testing beyond that.
+ *
+ * The goal is to find a VCO setting that gets us as close as possible to the desired output
+ * rate, while being within the VCO's operating limits and achievable with the input value
+ * range. It is iterating over possible post-VCO diver values (1-7)*(1-7) to look for valid
+ * VCO target frequencies and then looks for refdiv and fbdiv values to achieve the VCO
+ * frequency from the reference frequency.
+ */
+ for (j = 1; j <= postdiv2_max; j++) {
+ for (i = 1; i <= postdiv1_max; i++) {
+ u64 vco = (u64)rate * i * j;
+ long out;
+
+ if (vco > ZX29_PLL_VCO_MAX_FREQ || vco < ZX29_PLL_VCO_MIN_FREQ)
+ continue;
+
+ rational_best_approximation(vco, parent_rate,
+ (1 << hweight32(ZX29_PLL_FBDIV_MASK)) - 1,
+ (1 << hweight32(ZX29_PLL_REFDIV_MASK)) - 1,
+ &fbdiv, &refdiv);
+ setting = fbdiv << ZX29_PLL_FBDIV_SHIFT;
+ setting |= refdiv << ZX29_PLL_REFDIV_SHIFT;
+ setting |= i << ZX29_PLL_POSTDIV1_SHIFT;
+ setting |= j << ZX29_PLL_POSTDIV2_SHIFT;
+ out = zx29_pll_get_rate(pll, parent_rate, setting);
+
+ if (abs(out - rate) > best)
+ continue;
+
+ if (abs(out - rate) < best) {
+ postdiv1 = i;
+ postdiv2 = j;
+ best_fbdiv = fbdiv;
+ best_refdiv = refdiv;
+ best = abs(out - rate);
+
+ if (!best)
+ goto search_done;
+ }
+ }
+ }
+search_done:
+
+ if (!postdiv1) {
+ dev_err(pll->dev, "Did not find a setting for %lu Hz, parent %lu Hz\n",
+ rate, parent_rate);
+ return 0;
+ }
+
+ dev_dbg(pll->dev, "%s: parent rate %lu\n", name, parent_rate);
+ dev_dbg(pll->dev, "%s: found VCO dividers %u and %u\n", name, postdiv1, postdiv2);
+ dev_dbg(pll->dev, "%s: VCO target rate %lu\n", name, rate * postdiv1 * postdiv2);
+
+ dev_dbg(pll->dev, "%s: Got fbdiv = %lu refdiv = %lu\n", name, best_fbdiv, best_refdiv);
+
+ setting = best_fbdiv << ZX29_PLL_FBDIV_SHIFT;
+ setting |= best_refdiv << ZX29_PLL_REFDIV_SHIFT;
+ setting |= postdiv1 << ZX29_PLL_POSTDIV1_SHIFT;
+ setting |= postdiv2 << ZX29_PLL_POSTDIV2_SHIFT;
+ dev_dbg(pll->dev, "%s: Final setting 0x%08x\n", name, setting);
+
+ return setting;
+}
+
+static int zx29_pll_determine_rate(struct clk_hw *hw, struct clk_rate_request *req)
+{
+ struct zx29_clk_pll *pll = to_zx29_clk_pll(hw);
+ unsigned long new_rate, parent_rate = clk_hw_get_rate(clk_hw_get_parent(&pll->hw));
+ u32 setting;
+
+ setting = zx29_pll_calc_values(pll, parent_rate, req->rate);
+ if (!setting)
+ return -EINVAL;
+
+ new_rate = zx29_pll_get_rate(pll, parent_rate, setting);
+ if (new_rate != req->rate) {
+ dev_warn(pll->dev, "Did not find an exact match. Want %lu, got %lu\n",
+ req->rate, new_rate);
+ req->rate = new_rate;
+ }
+
+ return 0;
+}
+
+static int zx29_pll_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct zx29_clk_pll *pll = to_zx29_clk_pll(hw);
+ int res = -EINVAL;
+ u32 setting;
+
+ setting = zx29_pll_calc_values(pll, parent_rate, rate);
+ if (zx29_pll_get_rate(pll, parent_rate, setting) == rate) {
+ res = regmap_update_bits(pll->map, pll->reg, 0x00ffffff, setting);
+ dev_info(pll->dev, "%s: Setting rate: 0x%08x\n", clk_hw_get_name(hw), setting);
+ }
+
+ return res;
+}
+
+static u8 zx29_pll_get_parent(struct clk_hw *hw)
+{
+ struct zx29_clk_pll *pll = to_zx29_clk_pll(hw);
+ u32 val;
+ int res;
+
+ res = regmap_read(pll->map, pll->reg, &val);
+ if (res < 0)
+ return 0xff;
+
+ val = (val & ZX29_PLL_PARENT_MASK) >> ZX29_PLL_PARENT_SHIFT;
+ dev_dbg(pll->dev, "%s: Parent 0x%x\n", clk_hw_get_name(hw), val);
+
+ return val;
+}
+
+static int zx29_pll_set_parent(struct clk_hw *hw, u8 index)
+{
+ struct zx29_clk_pll *pll = to_zx29_clk_pll(hw);
+ u32 idx_shift = index << ZX29_PLL_PARENT_SHIFT;
+ int res;
+ u32 val;
+
+ res = regmap_update_bits(pll->map, pll->reg, ZX29_PLL_PARENT_MASK, idx_shift);
+ if (res < 0)
+ return res;
+
+ res = regmap_read(pll->map, pll->reg, &val);
+ if (res < 0)
+ return res;
+
+ if ((val & ZX29_PLL_PARENT_MASK) != idx_shift) {
+ dev_err(pll->dev, "Hardware rejected PLL parent %u\n", index);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int zx29_pll_init(struct clk_hw *hw)
+{
+ struct zx29_clk_pll *pll = to_zx29_clk_pll(hw);
+ const char *name = clk_hw_get_name(hw);
+ int res;
+
+ dev_dbg(pll->dev, "%s: initializing\n", name);
+
+ /* Remove the bypass flag so we don't have to bother with it in enable/disable. I have
+ * never seen it set by the earlier boot stages anyhow.
+ */
+ res = regmap_clear_bits(pll->map, pll->reg, ZX29_PLL_BYPASS);
+ if (res < 0)
+ return res;
+
+ if (regmap_test_bits(pll->map, pll->reg, ZX29_PLL_DISABLE) > 0) {
+ /* Set ZX29_PLL_POSTDIV_OUT_DISABLE for PLLs that have ZX29_PLL_DISABLE for
+ * consistency with .enable and .prepare. This ensures that .prepare doesn't
+ * inadvertedly enable PLLs without .enable being called.
+ */
+ res = regmap_set_bits(pll->map, pll->reg + ZX29_PLL_REG2_OFFSET,
+ ZX29_PLL_POSTDIV_OUT_DISABLE);
+ if (res < 0)
+ return res;
+ }
+
+ return 0;
+}
+
+const struct clk_ops zx29_pll_ops = {
+ .init = zx29_pll_init,
+ .is_prepared = zx29_pll_is_prepared,
+ .prepare = zx29_pll_prepare,
+ .unprepare = zx29_pll_unprepare,
+ .is_enabled = zx29_pll_is_enabled,
+ .enable = zx29_pll_enable,
+ .disable = zx29_pll_disable,
+ .recalc_rate = zx29_pll_recalc_rate,
+ .determine_rate = zx29_pll_determine_rate,
+ .get_parent = zx29_pll_get_parent,
+ .set_parent = zx29_pll_set_parent,
+ .set_rate = zx29_pll_set_rate,
+};
+
int zx_clk_register_plls(struct device *dev, struct regmap *regmap,
const struct zx_pll_desc *desc, unsigned int num,
struct clk_hw_onecell_data *clocks)
{
- return -ENODEV;
+ struct zx29_clk_pll *pll;
+ unsigned int i, f;
+ struct clk_hw *hw;
+ char plldiv[32];
+ int res;
+
+ for (i = 0; i < num; ++i) {
+ struct clk_init_data init = {};
+
+ pll = devm_kzalloc(dev, sizeof(*pll), GFP_KERNEL);
+ if (!pll)
+ return -ENOMEM;
+
+ init.name = desc[i].name;
+ init.ops = &zx29_pll_ops;
+ init.parent_names = desc[i].parents;
+ init.num_parents = desc[i].num_parents;
+ pll->hw.init = &init;
+ pll->dev = dev;
+ pll->map = regmap;
+ pll->reg = desc[i].reg;
+
+ res = devm_clk_hw_register(dev, &pll->hw);
+ if (res)
+ return res;
+
+ /* Set the PLL rate only if the bootloader left it disabled. Keep the bootloader
+ * setup otherwise.
+ */
+ if (regmap_test_bits(pll->map, pll->reg, ZX29_PLL_DISABLE) > 0 && desc[i].rate) {
+ dev_dbg(pll->dev, "%s: Setting to %lu Hz\n", desc[i].name, desc[i].rate);
+ res = clk_set_rate(pll->hw.clk, desc[i].rate);
+ if (res) {
+ dev_err(dev, "%s: Failed to set rate.\n", desc[i].name);
+ return res;
+ }
+ }
+
+ if (desc[i].id && desc[i].postdivs && desc[i].postdivs[0] == 1)
+ clocks->hws[desc[i].id] = &pll->hw;
+
+ for (f = 0; f < desc[i].num_postdivs; ++f) {
+ if (desc[i].postdivs[f] == 1)
+ continue;
+
+ snprintf(plldiv, sizeof(plldiv), "%s_d%u", desc[i].name,
+ desc[i].postdivs[f]);
+ hw = devm_clk_hw_register_fixed_factor(dev, plldiv, desc[i].name,
+ 0, 1, desc[i].postdivs[f]);
+ if (IS_ERR(hw))
+ return PTR_ERR(hw);
+ dev_dbg(pll->dev, "%s: %lu hz\n", clk_hw_get_name(hw), clk_hw_get_rate(hw));
+
+ if (desc[i].id)
+ clocks->hws[desc[i].id + f] = hw;
+ }
+ }
+
+ return 0;
}
--
2.53.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH RFC v5 07/12] clk: zte: Add regmap based clocks
2026-06-28 19:58 [PATCH RFC v5 00/12] ZTE zx297520v3 clock bindings and driver Stefan Dösinger
` (5 preceding siblings ...)
2026-06-28 19:59 ` [PATCH RFC v5 06/12] clk: zte: Add zx PLL support infrastructure Stefan Dösinger
@ 2026-06-28 19:59 ` Stefan Dösinger
2026-06-28 20:28 ` sashiko-bot
2026-06-28 19:59 ` [PATCH RFC v5 08/12] clk: zte: Introduce a driver for zx297520v3 top clocks Stefan Dösinger
` (4 subsequent siblings)
11 siblings, 1 reply; 23+ messages in thread
From: Stefan Dösinger @ 2026-06-28 19:59 UTC (permalink / raw)
To: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel, Brian Masney
Cc: linux-clk, devicetree, linux-kernel, linux-arm-kernel,
Stefan Dösinger
This is based on meson/clk-regmap.c, although slightly simplified. I
have kept the copyright lines at the top of the file to indicate its
origin.
I see that numerous clock drivers have their own incarnation of regmap
based mux/div/gate clocks. If there is any version of it that is likely
to be elevated to shared code liks clk-gate.c I'll copy that and try to
use it as unmodified as possible.
Signed-off-by: Stefan Dösinger <stefandoesinger@gmail.com>
---
Version 5: Use regmap_test_bits in zte_clk_regmap_gate_is_enabled
---
drivers/clk/zte/clk-regmap.c | 221 ++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 218 insertions(+), 3 deletions(-)
diff --git a/drivers/clk/zte/clk-regmap.c b/drivers/clk/zte/clk-regmap.c
index 7908f1562f63..903998ca9508 100644
--- a/drivers/clk/zte/clk-regmap.c
+++ b/drivers/clk/zte/clk-regmap.c
@@ -6,25 +6,240 @@
* Author: Stefan Dösinger <stefandoesinger@gmail.com>
*/
+#include <linux/clk-provider.h>
+#include <linux/regmap.h>
+#include <linux/device.h>
+
#include "clk-zx.h"
+struct zte_clk_regmap {
+ struct clk_hw hw;
+ struct regmap *map;
+ u16 reg;
+ u8 shift;
+ u8 size;
+};
+
+static inline struct zte_clk_regmap *to_zte_clk_regmap(struct clk_hw *hw)
+{
+ return container_of(hw, struct zte_clk_regmap, hw);
+}
+
+static int zte_clk_regmap_gate_enable(struct clk_hw *hw)
+{
+ struct zte_clk_regmap *clk = to_zte_clk_regmap(hw);
+
+ return regmap_set_bits(clk->map, clk->reg, BIT(clk->shift));
+}
+
+static void zte_clk_regmap_gate_disable(struct clk_hw *hw)
+{
+ struct zte_clk_regmap *clk = to_zte_clk_regmap(hw);
+
+ regmap_clear_bits(clk->map, clk->reg, BIT(clk->shift));
+}
+
+static int zte_clk_regmap_gate_is_enabled(struct clk_hw *hw)
+{
+ struct zte_clk_regmap *clk = to_zte_clk_regmap(hw);
+
+ return regmap_test_bits(clk->map, clk->reg, BIT(clk->shift));
+}
+
+static const struct clk_ops zte_clk_regmap_gate_ops = {
+ .enable = zte_clk_regmap_gate_enable,
+ .disable = zte_clk_regmap_gate_disable,
+ .is_enabled = zte_clk_regmap_gate_is_enabled,
+};
+
int zx_clk_register_gates(struct device *dev, struct regmap *regmap,
const struct zx_gate_desc *desc, unsigned int num,
struct clk_hw_onecell_data *clocks)
{
- return -ENODEV;
+ struct zte_clk_regmap *clk;
+ unsigned int i;
+ int res;
+
+ for (i = 0; i < num; ++i) {
+ struct clk_init_data init = {};
+
+ clk = devm_kzalloc(dev, sizeof(*clk), GFP_KERNEL);
+ if (!clk)
+ return -ENOMEM;
+
+ init.name = desc[i].name;
+ init.ops = &zte_clk_regmap_gate_ops;
+ init.parent_names = &desc[i].parent;
+ init.num_parents = 1;
+ init.flags = CLK_SET_RATE_PARENT | desc[i].flags;
+ clk->hw.init = &init;
+ clk->map = regmap;
+ clk->reg = desc[i].reg;
+ clk->shift = desc[i].shift;
+ clk->size = 1;
+
+ res = devm_clk_hw_register(dev, &clk->hw);
+ if (res)
+ return dev_err_probe(dev, res, "Failed to register clk %s\n", desc[i].name);
+
+ if (desc[i].id)
+ clocks->hws[desc[i].id] = &clk->hw;
+ }
+
+ return 0;
+}
+
+static unsigned long zte_clk_regmap_div_recalc_rate(struct clk_hw *hw,
+ unsigned long prate)
+{
+ struct zte_clk_regmap *clk = to_zte_clk_regmap(hw);
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(clk->map, clk->reg, &val);
+ if (ret)
+ /* Gives a hint that something is wrong */
+ return 0;
+
+ val >>= clk->shift;
+ val &= clk_div_mask(clk->size);
+ return divider_recalc_rate(hw, prate, val, NULL, 0, clk->size);
}
+static int zte_clk_regmap_div_determine_rate(struct clk_hw *hw,
+ struct clk_rate_request *req)
+{
+ struct zte_clk_regmap *clk = to_zte_clk_regmap(hw);
+
+ return divider_determine_rate(hw, req, NULL, clk->size, 0);
+}
+
+static int zte_clk_regmap_div_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct zte_clk_regmap *clk = to_zte_clk_regmap(hw);
+ unsigned int val;
+ int ret;
+
+ ret = divider_get_val(rate, parent_rate, NULL, clk->size, 0);
+ if (ret < 0)
+ return ret;
+
+ val = (unsigned int)ret << clk->shift;
+ return regmap_update_bits(clk->map, clk->reg, clk_div_mask(clk->size) << clk->shift, val);
+};
+
+static const struct clk_ops zte_clk_regmap_divider_ops = {
+ .recalc_rate = zte_clk_regmap_div_recalc_rate,
+ .determine_rate = zte_clk_regmap_div_determine_rate,
+ .set_rate = zte_clk_regmap_div_set_rate,
+};
+
int zx_clk_register_dividers(struct device *dev, struct regmap *regmap,
const struct zx_div_desc *desc, unsigned int num,
struct clk_hw_onecell_data *clocks)
{
- return -ENODEV;
+ struct zte_clk_regmap *clk;
+ unsigned int i;
+ int res;
+
+ for (i = 0; i < num; ++i) {
+ struct clk_init_data init = {};
+
+ clk = devm_kzalloc(dev, sizeof(*clk), GFP_KERNEL);
+ if (!clk)
+ return -ENOMEM;
+
+ init.name = desc[i].name;
+ init.ops = &zte_clk_regmap_divider_ops;
+ init.parent_names = &desc[i].parent;
+ init.num_parents = 1;
+ init.flags = CLK_SET_RATE_PARENT;
+ clk->hw.init = &init;
+ clk->map = regmap;
+ clk->reg = desc[i].reg;
+ clk->shift = desc[i].shift;
+ clk->size = desc[i].size;
+
+ res = devm_clk_hw_register(dev, &clk->hw);
+ if (res)
+ return dev_err_probe(dev, res, "Failed to register clk %s\n", desc[i].name);
+
+ if (desc[i].id)
+ clocks->hws[desc[i].id] = &clk->hw;
+ }
+
+ return 0;
}
+static u8 zte_clk_regmap_mux_get_parent(struct clk_hw *hw)
+{
+ struct zte_clk_regmap *clk = to_zte_clk_regmap(hw);
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(clk->map, clk->reg, &val);
+ if (ret)
+ return 0xff;
+
+ val >>= clk->shift;
+ val &= GENMASK(clk->size - 1, 0);
+ return clk_mux_val_to_index(hw, NULL, 0, val);
+}
+
+static int zte_clk_regmap_mux_set_parent(struct clk_hw *hw, u8 index)
+{
+ struct zte_clk_regmap *clk = to_zte_clk_regmap(hw);
+ unsigned int val = clk_mux_index_to_val(NULL, 0, index);
+
+ return regmap_update_bits(clk->map, clk->reg,
+ GENMASK(clk->size - 1, 0) << clk->shift,
+ val << clk->shift);
+}
+
+static int zte_clk_regmap_mux_determine_rate(struct clk_hw *hw, struct clk_rate_request *req)
+{
+ return clk_mux_determine_rate_flags(hw, req, 0);
+}
+
+static const struct clk_ops zte_clk_regmap_mux_ops = {
+ .get_parent = zte_clk_regmap_mux_get_parent,
+ .set_parent = zte_clk_regmap_mux_set_parent,
+ .determine_rate = zte_clk_regmap_mux_determine_rate,
+};
+
int zx_clk_register_muxes(struct device *dev, struct regmap *regmap,
const struct zx_mux_desc *desc, unsigned int num,
struct clk_hw_onecell_data *clocks)
{
- return -ENODEV;
+ struct zte_clk_regmap *clk;
+ unsigned int i;
+ int res;
+
+ for (i = 0; i < num; ++i) {
+ struct clk_init_data init = {};
+
+ clk = devm_kzalloc(dev, sizeof(*clk), GFP_KERNEL);
+ if (!clk)
+ return -ENOMEM;
+
+ init.name = desc[i].name;
+ init.ops = &zte_clk_regmap_mux_ops;
+ init.parent_names = desc[i].parents;
+ init.num_parents = desc[i].num_parents;
+ clk->hw.init = &init;
+ clk->map = regmap;
+ clk->reg = desc[i].reg;
+ clk->shift = desc[i].shift;
+ clk->size = desc[i].size;
+
+ res = devm_clk_hw_register(dev, &clk->hw);
+ if (res)
+ return dev_err_probe(dev, res, "Failed to register clk %s\n", desc[i].name);
+
+ if (desc[i].id)
+ clocks->hws[desc[i].id] = &clk->hw;
+ }
+
+ return 0;
}
--
2.53.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH RFC v5 08/12] clk: zte: Introduce a driver for zx297520v3 top clocks
2026-06-28 19:58 [PATCH RFC v5 00/12] ZTE zx297520v3 clock bindings and driver Stefan Dösinger
` (6 preceding siblings ...)
2026-06-28 19:59 ` [PATCH RFC v5 07/12] clk: zte: Add regmap based clocks Stefan Dösinger
@ 2026-06-28 19:59 ` Stefan Dösinger
2026-06-28 20:16 ` sashiko-bot
2026-06-28 19:59 ` [PATCH RFC v5 09/12] clk: zte: Introduce a driver for zx297520v3 matrix clocks Stefan Dösinger
` (3 subsequent siblings)
11 siblings, 1 reply; 23+ messages in thread
From: Stefan Dösinger @ 2026-06-28 19:59 UTC (permalink / raw)
To: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel, Brian Masney
Cc: linux-clk, devicetree, linux-kernel, linux-arm-kernel,
Stefan Dösinger
This register space controls core devices: PLLs, the AHB bus, a lot of
timers, the USB controller, the Cortex M0 processor that boots the board
and a few other devices. For some reason the LTE coprocessor is also
partially controlled by it. The main application processor and DDR
memory are not found here though.
The register to reboot the board is found here, as well as a register to
control of watchdog expiries cause a board reset.
Signed-off-by: Stefan Dösinger <stefandoesinger@gmail.com>
---
Version 5:
*) Make it a MFD child driver instead of binding to the node directly
*) Correct parents for dpll
*) Correct HSIC work clock parent
---
drivers/clk/zte/Kconfig | 11 +
drivers/clk/zte/Makefile | 1 +
drivers/clk/zte/clk-zx297520v3.c | 454 +++++++++++++++++++++++++++++++++++++++
3 files changed, 466 insertions(+)
diff --git a/drivers/clk/zte/Kconfig b/drivers/clk/zte/Kconfig
index b7b65a2172a9..12906212ec1e 100644
--- a/drivers/clk/zte/Kconfig
+++ b/drivers/clk/zte/Kconfig
@@ -15,3 +15,14 @@ config COMMON_CLK_ZTE
of this.
Enable this if you are building a kernel for a ZTE designed board.
+
+config CLK_ZTE_ZX297520V3
+ tristate "Clock driver for ZTE zx297520v3 based SoCs"
+ depends on COMMON_CLK_ZTE
+ default SOC_ZX297520V3
+ help
+ This driver supports ZTE zx297520v3 basic clocks.
+
+ Enable this if you want to build a kernel that is able to run on
+ boards based on this SoC. You can safely enable multiple clock
+ drivers. The one(s) matching the device tree will be used.
diff --git a/drivers/clk/zte/Makefile b/drivers/clk/zte/Makefile
index 27db07293165..2c073512e919 100644
--- a/drivers/clk/zte/Makefile
+++ b/drivers/clk/zte/Makefile
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_COMMON_CLK_ZTE) += clk-zte.o
+obj-$(CONFIG_CLK_ZTE_ZX297520V3) += clk-zx297520v3.o
clk-zte-y += clk-zx.o pll-zx.o clk-regmap.o
diff --git a/drivers/clk/zte/clk-zx297520v3.c b/drivers/clk/zte/clk-zx297520v3.c
new file mode 100644
index 000000000000..bb3d64eff909
--- /dev/null
+++ b/drivers/clk/zte/clk-zx297520v3.c
@@ -0,0 +1,454 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2026 Stefan Dösinger
+ */
+#include <dt-bindings/clock/zte,zx297520v3-clk.h>
+#include <linux/platform_device.h>
+#include <linux/clk-provider.h>
+#include <linux/module.h>
+
+#include "clk-zx.h"
+
+MODULE_IMPORT_NS("ZTE_CLK");
+
+/* Used for gates where we don't know the parent input(s). Assume general bus clock. */
+static const char * const clk_unknown[] = {
+ "osc26m",
+};
+
+/* Used for gates where we know it is using the 26 mhz main clock. */
+static const char * const clk_main[] = {
+ "osc26m",
+};
+
+static const char * const dpll_parents[] = {
+ "unknownpll_d2",
+ "osc26m",
+};
+
+static const char * const zx297520v3_top_inputs[] = {
+ "osc26m",
+ "osc32k"
+};
+
+/* Top and matrix clocks are chaotic - I haven't found a consistent pattern behind their register
+ * and bit locations. Generally there are two gates (pclk, wclk), one mux, two resets and sometimes
+ * one divider, but exceptions apply. For some devices there is only a reset and some general
+ * (parent) clocks need setup. This structure plus macro handles the somewhat regular parts.
+ *
+ * There are some patterns that can be observed.
+ * mux 0x3c, div 0x48, gate 0x54
+ * mux 0x40, div 0x4c, gate 0x5c
+ * mux 0x44, div 0x50, gate 0x60
+ *
+ * For a 0 - 0xc - 0x18 pattern. Muxes from 0x3c to 0x44, dividers from 0x48 to 0x50, gates 0x54 to
+ * 0x60. The pattern is broken for timer t17 though.
+ *
+ * Gates have 4 bits per clock - bit 0 for wclk, bit 1 for pclk, bit 2 for something the ZTE kernel
+ * calls "gate" (the bits we use here are called "en"), which I don't know what it does, and bit 3
+ * seems unused. E.g. offset 0x54 accepts all bits in 0xF77F7F7F - suggesting RTC, I2C0 have an
+ * extra gate bit.
+ *
+ * The default mpll settings multiply the 26 MHz reference clock times 24. A mux selection of 26 MHz
+ * could mean using the 26 MHz oscillator directly, or passing it through the PLL and divide by 24.
+ *
+ * If a UART is set to mpl_d6 (default 104 MHz), changing the mpll multipliers does affect UART
+ * timing as it should. This does not happen when the UART is set to 26 MHz input or timers that
+ * read 26 MHz input. This suggests 26 MHz clocks use the reference clock directly.
+ */
+
+/* AHB: The clock mux works and impact can be tested e.g. with iperf speed testing of the USB
+ * network connection. Values 2 and 3 give the same speed.
+ */
+static const char * const ahb_sel[] = {
+ "osc26m",
+ "mpll_d6", /* 104 mhz */
+ "mpll_d8", /* 78 mhz */
+ "mpll_d8", /* 78 mhz */
+};
+
+static const char * const timer_top_sel[] = {
+ "osc32k",
+ "osc26m",
+};
+
+static const char * const uart_top_sel[] = {
+ "osc26m",
+ "mpll_d6", /* 104 mhz */
+};
+
+/* The Cortex M0 coprocessor. It is responsible for booting the board and runs some power management
+ * helper code on the stock firmware, but isn't critical. We can run custom code on it but currently
+ * do not. These bits control the speed and the values are mentioned in ZTE's uboot. It isn't clear
+ * to me if this is directly responsible for the m0 clock, or if it is the input to another clock.
+ * Most likely it is the latter - setting it to osc32k slows down GPIO reads done on the Cortex A53
+ * a lot, although the speed of the A53 and DRAM access remains unaffected.
+ *
+ * I also haven't found a gate that shuts the m0 off and allows restarting. There don't seem to be
+ * resets either.
+ */
+static const char * const m0_sel[] = {
+ "osc26m",
+ "mpll_d6", /* 104 mhz */
+ "mpll_d8", /* 78 mhz */
+ "osc32k", /* Yes, tested. It is SLLLLOOOOOWWW. */
+};
+
+/* Clk_out0/1/2/32k: These clocks are exposed on GPIOs 15, 16, 17 and 18 respectively. They are used
+ * in ZTE's camera and sound code, by directly poking into the clock registers from the device
+ * drivers. Until the respective devices are supported they can safely be switched off.
+ *
+ * For clk_out1 ZTE's camera code says the following:
+ *
+ * 0 -> 20 MHz
+ * 1 -> 40 MHz
+ * 2 -> 13 MHz
+ * 3 -> 26 MHz
+ *
+ * 0 and 1 read from upll. I confirmed their rates (upll_d24 and upll_d12) by setting upll to a very
+ * low frequency and sampling the clock by GPIO reads. The outputs of 2 and 3 are way too high to
+ * test that way. Both are not reading from any PLL. I am fairly sure that 3 is just direct osc26m
+ * because it perfectly matches what out2 is showing in its 26 MHz setting. Setting 2 is an enigma.
+ * It is not from any PLL (disable postdiv_out on all of them and the clock will keep oscillating).
+ *
+ * Probably the best way us to model this as a mux (bit 7) and divider (bit 6), but since this is
+ * not a plain val+1 divider like the rest of the divs I am leaving the divider out until an actual
+ * hardware user is found. It would need support for divider tables or flags in the regmap div
+ * clocks.
+ *
+ * Clk_out2 is similar, but it has only one control bit in top 0x34 bit 8. Neither setting selects
+ * a PLL output. When setting *0x34 = 0x080, clk_out1 and clk_out2 are in lockstep, presumably
+ * running at 13 MHz (clk_out1 mux select 26m, both have their divider set to 2). 0x1c0 also runs in
+ * lockstep (clk_out1 select 26m, both div 1).
+ *
+ * clk_out0 has a mux in bit 5. Value 0 most likely selects osc26m. I am not entirely sure about
+ * value 1 (which is the default), but the m0 mux has an impact on it. It looks like a debug pin
+ * that exposes some core clock.
+ */
+
+static const char * const out0_sel[] = {
+ "osc26m",
+ "m0_wclk",
+};
+
+static const char * const out1_sel[] = {
+ "upll_d12", /* 40 MHz */
+ "osc26m",
+};
+
+/* Clk_o is similar to clk_out*, providing a clock on GPIO 20, presumably for driving a (R)MII phy.
+ * The 50 MHz value is documented in a stray comment in ZTE's GMAC driver. I tested it similarly to
+ * the above pins. Mux setting 0 gives half the count as setting 1 and setting 1 gives gpll divided
+ * by 4 - matching the 50 MHz suggested by the ZTE comment at the default gpll settings. Gating gpll
+ * stops the clock for values 0 and 1.
+ *
+ * Settings 2 and 3 are possible, but seem to return garbage. It is generally pulsing up and down,
+ * except if both gpll and upll are stopped. I suspect it just reads random electrical fluctuation
+ * from other places in the board. Yes, I had a pull-down enabled when testing this.
+ *
+ * This could also be a case of mux + inverse div, but since the settings we might possibly need are
+ * standard gpll outputs just model it as a mux.
+ *
+ * I am not aware of any board that uses this though. The Ethernet equipped ones I have all run the
+ * phy with its own oscillator.
+ */
+static const char * const rmii_sel[] = {
+ "gpll_d8", /* 25 MHz */
+ "gpll_d4", /* 50 MHz */
+};
+
+static const unsigned int mpll_postdivs[] = {1, 2, 3, 4, 5, 6, 8, 12, 16, 26};
+static const unsigned int pll_postdivs[] = {1, 2, 3, 4, 5, 6, 8, 12, 16};
+static const unsigned int unknownpll_postdivs[] = {2};
+
+static const struct zx_pll_desc zx297520v3_plls[] = {
+ /* Default setting: 0x48040c11. 624/312/156. Only a single possible parent. This is the
+ * PLL for pretty much everything, including CPU, RAM and USB.
+ *
+ * Changing this PLL makes it possible to overclock the CPU or do more fine grained
+ * underclocking than the CPU's mux allows. It does run into two problems though: The USB
+ * device uses this PLL's output directly and is *very* sensitive to differences. DRAM
+ * is also fed by this clock and needs to be re-trained on larger changes, which needs to
+ * be done by the stage 1 boot loader.
+ */
+ {
+ .id = ZX297520V3_MPLL,
+ .name = "mpll",
+ .parents = clk_main,
+ .num_parents = ARRAY_SIZE(clk_main),
+ .rate = 0,
+ .postdivs = mpll_postdivs,
+ .num_postdivs = ARRAY_SIZE(mpll_postdivs),
+ .reg = 0x8
+ },
+
+ /* ZTE's code calls this PLL "upll". The only possible consumers I found are clk_out1,
+ * which outputs this clock on GPIO 16 and HSIC. HSIC doesn't have anything connected to
+ * it on the devices I tested. The device that consumes clk_out1 seems to be an SPI
+ * camera, which I haven't seen in any device so far.
+ *
+ * Long story short, shut it off.
+ */
+ {
+ .id = ZX297520V3_UPLL,
+ .name = "upll",
+ .parents = clk_main,
+ .num_parents = ARRAY_SIZE(clk_main),
+ .rate = 0,
+ .postdivs = pll_postdivs,
+ .num_postdivs = ARRAY_SIZE(pll_postdivs),
+ .reg = 0x10,
+ },
+
+ /* Default value 0x4834902d. Feeds dpll. 46.08 MHz. Bit 25 can be set, so two parents are
+ * possible. It looks like both values select the 26 MHz oscillator though.
+ */
+ {
+ .id = 0,
+ .name = "unknownpll",
+ .parents = clk_main,
+ .num_parents = ARRAY_SIZE(clk_main),
+ .rate = 0,
+ .postdivs = unknownpll_postdivs,
+ .num_postdivs = ARRAY_SIZE(unknownpll_postdivs),
+ .reg = 0x100,
+ },
+
+ /* The documentation says 491.52 MHz and measurement with the LSP TDM device supports this.
+ * The default value is 0x480c2011, but not all boot loaders set it up. To get to 491.52
+ * with these settings it needs a 23.04 MHz reference clock, which matches unknownpll_d2.
+ * If unknownpll is disabled, dpll loses its lock. We set the frequency on this PLL if we
+ * find it is not enabled by the boot loader.
+ *
+ * The proprietary LTE driver or coproc enables and disables it. TDM and I2S can use it.
+ *
+ * It accepts parent values 0, 1, 2 and 3. Parent 0 is unknownpll_d2. The others look like
+ * osc26m. With a parent != 0 dpll never loses its lock even when all other PLLs are off
+ * and the TDM counter register increases at a rate consistent with a 26.0/23.04 clock
+ * increase.
+ */
+ {
+ .id = ZX297520V3_DPLL,
+ .name = "dpll",
+ .parents = dpll_parents,
+ .num_parents = ARRAY_SIZE(dpll_parents),
+ .rate = 491520000,
+ .postdivs = pll_postdivs,
+ .num_postdivs = ARRAY_SIZE(pll_postdivs),
+ .reg = 0x18,
+ },
+
+ /* "g" is either for "general" or "gigahertz". The VCO runs at 1GHz. Output clocks are 200,
+ * 100, 50, 25, ... MHz. It is used optionally by SDIO and QSPI and can drive a GPIO clock
+ * output for RMII, so it doesn't seem very general.
+ */
+ {
+ .id = ZX297520V3_GPLL,
+ .name = "gpll",
+ .parents = clk_main,
+ .num_parents = ARRAY_SIZE(clk_main),
+ .rate = 0,
+ .postdivs = pll_postdivs,
+ .num_postdivs = ARRAY_SIZE(pll_postdivs),
+ .reg = 0x110,
+ },
+};
+
+#define MUX(_id, _name, _parents, _reg, _shift, _size) { \
+ _id, _name, _parents, ARRAY_SIZE(_parents), _reg, _shift, _size}
+
+#define DIV(_id, _name, _parent, _reg, _shift, _size) { _id, _name, _parent, _reg, _shift, _size }
+
+#define GATE(_id, _name, _parent, _reg, _shift, _flags) { \
+ .id = _id, \
+ .name = _name, \
+ .parent = _parent, \
+ .flags = _flags, \
+ .reg = _reg, \
+ .shift = _shift, \
+}
+
+static const struct zx_mux_desc zx297520v3_top_muxes[] = {
+ MUX(ZX297520V3_M0_WCLK, "m0_wclk", m0_sel, 0x38, 0, 2),
+ MUX(0, "ahb_mux", ahb_sel, 0x3c, 4, 2),
+ MUX(0, "timer_t08_mux", timer_top_sel, 0x40, 1, 1),
+ MUX(0, "timer_t09_mux", timer_top_sel, 0x40, 0, 1),
+ MUX(0, "timer_t12_mux", timer_top_sel, 0x3c, 0, 1),
+ MUX(0, "timer_t13_mux", timer_top_sel, 0x44, 0, 1),
+ MUX(0, "timer_t14_mux", timer_top_sel, 0x44, 1, 1),
+ MUX(0, "timer_t15_mux", timer_top_sel, 0x3c, 3, 1),
+ MUX(0, "timer_t16_mux", timer_top_sel, 0x44, 2, 1),
+ MUX(0, "timer_t17_mux", timer_top_sel, 0x120, 0, 1),
+ MUX(0, "wdt_t18_mux", timer_top_sel, 0x3c, 6, 1),
+ MUX(0, "i2c0_mux", uart_top_sel, 0x3c, 1, 1),
+ MUX(0, "uart0_mux", uart_top_sel, 0x40, 2, 1),
+ MUX(0, "out0_mux", out0_sel, 0x34, 5, 1),
+ MUX(0, "out1_mux", out1_sel, 0x34, 7, 1),
+ MUX(0, "rmiiphy_mux", rmii_sel, 0x11c, 0, 2),
+};
+
+static const struct zx_div_desc zx297520v3_top_dividers[] = {
+ DIV(0, "timer_t08_div", "timer_t08_mux", 0x4c, 8, 4),
+ DIV(0, "timer_t09_div", "timer_t09_mux", 0x4c, 0, 4),
+ DIV(0, "timer_t12_div", "timer_t12_mux", 0x48, 0, 4),
+ DIV(0, "timer_t13_div", "timer_t13_mux", 0x50, 0, 4),
+ DIV(0, "timer_t14_div", "timer_t14_mux", 0x50, 4, 4),
+ DIV(0, "timer_t15_div", "timer_t15_mux", 0x48, 4, 4),
+ DIV(0, "timer_t16_div", "timer_t16_mux", 0x50, 8, 4),
+ DIV(0, "timer_t17_div", "timer_t17_mux", 0x124, 0, 4),
+ DIV(0, "wdt_t18_div", "wdt_t18_mux", 0x48, 8, 4),
+ DIV(0, "usim1_div", clk_main[0], 0x48, 12, 1),
+};
+
+static const struct zx_gate_desc zx297520v3_top_gates[] = {
+ /* Turning off this clock crashes the device. */
+ GATE(ZX297520V3_AHB_WCLK, "ahb_wclk", "ahb_mux", 0x54, 12,
+ CLK_IS_CRITICAL),
+ GATE(ZX297520V3_AHB_PCLK, "ahb_pclk", clk_main[0], 0x54, 13,
+ CLK_IS_CRITICAL),
+
+ /* SRAM1 and 2 clocks. Leave them on for now, as turning them off carelessly hangs the M0 */
+ GATE(ZX297520V3_SRAM1_PCLK, "sram1_pclk", clk_main[0], 0x54, 18,
+ CLK_IS_CRITICAL),
+ GATE(ZX297520V3_SRAM2_PCLK, "sram2_pclk", clk_main[0], 0x54, 16,
+ CLK_IS_CRITICAL),
+
+ /* Pinmux (AON, TOP, IOCFG but not PDCFG). Critical as well until we have a driver that
+ * consumes it. I don't think we'll realistically shut this off ever.
+ *
+ * Setting either bit 0 or 1 in register 0x58 makes the device work.
+ */
+ GATE(ZX297520V3_PMM_WCLK, "pmm_wclk", clk_main[0], 0x58, 0,
+ CLK_IS_CRITICAL),
+ GATE(ZX297520V3_PMM_PCLK, "pmm_pclk", clk_main[0], 0x58, 1,
+ CLK_IS_CRITICAL),
+
+ /* Timers. We don't use any of them, just shut them off. The timers are named and sorted
+ * by the IO address of the main timer controls. Some of the controls are documented in
+ * ZTE's kernel, the others I found by trial and error.
+ *
+ * Timer T17 is used by the ZSP firmware. The rproc driver will enable it as needed.
+ */
+ GATE(ZX297520V3_TIMER_T08_WCLK, "timer_t08_wclk", "timer_t08_div", 0x5c, 8, 0),
+ GATE(ZX297520V3_TIMER_T08_PCLK, "timer_t08_pclk", clk_main[0], 0x5c, 9, 0),
+ GATE(ZX297520V3_TIMER_T09_WCLK, "timer_t09_wclk", "timer_t09_div", 0x5c, 4, 0),
+ GATE(ZX297520V3_TIMER_T09_PCLK, "timer_t09_pclk", clk_main[0], 0x5c, 5, 0),
+ GATE(ZX297520V3_TIMER_T12_WCLK, "timer_t12_wclk", "timer_t12_div", 0x54, 4, 0),
+ GATE(ZX297520V3_TIMER_T12_PCLK, "timer_t12_pclk", clk_main[0], 0x54, 5, 0),
+ GATE(ZX297520V3_TIMER_T13_WCLK, "timer_t13_wclk", "timer_t13_div", 0x60, 0, 0),
+ GATE(ZX297520V3_TIMER_T13_PCLK, "timer_t13_pclk", clk_main[0], 0x60, 1, 0),
+ GATE(ZX297520V3_TIMER_T14_WCLK, "timer_t14_wclk", "timer_t14_div", 0x60, 4, 0),
+ GATE(ZX297520V3_TIMER_T14_PCLK, "timer_t14_pclk", clk_main[0], 0x60, 5, 0),
+ GATE(ZX297520V3_TIMER_T15_WCLK, "timer_t15_wclk", "timer_t15_div", 0x54, 20, 0),
+ GATE(ZX297520V3_TIMER_T15_PCLK, "timer_t15_pclk", clk_main[0], 0x54, 21, 0),
+ GATE(ZX297520V3_TIMER_T16_WCLK, "timer_t16_wclk", "timer_t16_div", 0x60, 8, 0),
+ GATE(ZX297520V3_TIMER_T16_PCLK, "timer_t16_pclk", clk_main[0], 0x60, 9, 0),
+ GATE(ZX297520V3_TIMER_T17_WCLK, "timer_t17_wclk", "timer_t17_div", 0x128, 0, 0),
+ GATE(ZX297520V3_TIMER_T17_PCLK, "timer_t17_pclk", clk_main[0], 0x128, 1, 0),
+ /* This watchdog is set up by the bootloader and in normal operation the m0 firmware will
+ * feed the dog. The m0 firmware in turn wants to be fed in its own way. Since we normally
+ * don't run any m0 firmware we shut it off by default and expose it to userspace via the
+ * watchdog driver.
+ */
+ GATE(ZX297520V3_WDT_T18_WCLK, "wdt_t18_wclk", "wdt_t18_div", 0x54, 24, 0),
+ GATE(ZX297520V3_WDT_T18_PCLK, "wdt_t18_pclk", clk_main[0], 0x54, 25, 0),
+
+ GATE(ZX297520V3_I2C0_WCLK, "i2c0_wclk", "i2c0_mux", 0x54, 8, 0),
+ GATE(ZX297520V3_I2C0_PCLK, "i2c0_pclk", clk_main[0], 0x54, 9, 0),
+ GATE(ZX297520V3_UART0_WCLK, "uart0_wclk", "uart0_mux", 0x5c, 12, 0),
+ GATE(ZX297520V3_UART0_PCLK, "uart0_pclk", clk_main[0], 0x5c, 13, 0),
+
+ /* ZTE says the USB input is a 24 MHz clock based on mpll. Testing shows that Upll is not
+ * involved. The USB register space can be accessed with ahb_pclk gated, but not with
+ * ahb_wclk gated. ZTE also lists ahb_wclk as parent for the second clock.
+ *
+ * There doesn't seem to be a separate PHY clock. usb_wclk stops controller registers from
+ * updating, but doesn't cause the USB device to disconnect like asserting the PHY reset
+ * does. Bit 0 in this register can't be set, so no PHY clock is hiding there either.
+ */
+ GATE(ZX297520V3_USB_WCLK, "usb_wclk", "mpll_d26", 0x6c, 3, 0),
+ GATE(ZX297520V3_USB_PCLK, "usb_pclk", "ahb_wclk", 0x6c, 4, 0),
+ /* The HSIC hardware is listed in ZTE's code with a physical address of 0x01600000. Unlike
+ * the USB controller, it does consume upll, presumably upll_d2 for 240 MHz.
+ */
+ GATE(ZX297520V3_HSIC_WCLK, "hsic_wclk", "upll_d2", 0x6c, 1, 0),
+ GATE(ZX297520V3_HSIC_PCLK, "hsic_pclk", "ahb_wclk", 0x6c, 2, 0),
+
+ /* How does this RTC work? I don't know, the ZTE kernel does not talk to it. The actual RTC
+ * is on the I2C connected PMIC.
+ */
+ GATE(ZX297520V3_RTC_WCLK, "rtc_wclk", clk_unknown[0], 0x54, 0, 0),
+ GATE(ZX297520V3_RTC_PCLK, "rtc_pclk", clk_main[0], 0x54, 1, 0),
+
+ GATE(ZX297520V3_USIM1_WCLK, "usim1_wclk", "usim1_div", 0x54, 28, 0),
+ GATE(ZX297520V3_USIM1_PCLK, "usim1_pclk", clk_main[0], 0x54, 29, 0),
+
+ /* LTE: gate only as far as I can see. I looked for resets and did not find any. There may
+ * be mux/div, but without understanding the behavior of this hardware it is impossible to
+ * tell. They are sorted by physical MMIO address of the devices, which happens to be the
+ * inverse order of the bits.
+ *
+ * I don't know what "LPM", "TD" and "W" mean. I copied them from ZTE's names.
+ */
+ GATE(ZX297520V3_LPM_GSM_WCLK, "lpm_gsm_wclk", clk_unknown[0], 0x58, 10, 0),
+ GATE(ZX297520V3_LPM_GSM_PCLK, "lpm_gsm_pclk", clk_unknown[0], 0x58, 11, 0),
+ GATE(ZX297520V3_LPM_LTE_WCLK, "lpm_lte_wclk", clk_unknown[0], 0x58, 8, 0),
+ GATE(ZX297520V3_LPM_LTE_PCLK, "lpm_lte_pclk", clk_unknown[0], 0x58, 9, 0),
+ GATE(ZX297520V3_LPM_TD_WCLK, "lpm_td_wclk", clk_unknown[0], 0x58, 6, 0),
+ GATE(ZX297520V3_LPM_TD_PCLK, "lpm_td_pclk", clk_unknown[0], 0x58, 7, 0),
+ GATE(ZX297520V3_LPM_W_WCLK, "lpm_w_wclk", clk_unknown[0], 0x58, 4, 0),
+ GATE(ZX297520V3_LPM_W_PCLK, "lpm_w_pclk", clk_unknown[0], 0x58, 5, 0),
+
+ GATE(ZX297520V3_OUT0_WCLK, "out0_wclk", "out0_mux", 0x34, 0, 0),
+ GATE(ZX297520V3_OUT1_WCLK, "out1_wclk", "out1_mux", 0x90, 2, 0),
+ GATE(ZX297520V3_OUT2_WCLK, "out2_wclk", clk_main[0], 0x94, 2, 0),
+ GATE(ZX297520V3_OUT32K_WCLK, "out32k_wclk", "osc32k", 0x34, 1, 0),
+ GATE(ZX297520V3_RMIIPHY_WCLK, "rmiiphy_wclk", "rmiiphy_mux", 0x11c, 2, 0),
+};
+
+static const struct zx_clk_data zx297520v3_topclk_data = {
+ .inputs = zx297520v3_top_inputs,
+ .num_inputs = ARRAY_SIZE(zx297520v3_top_inputs),
+ .plls = zx297520v3_plls,
+ .num_plls = ARRAY_SIZE(zx297520v3_plls),
+ .muxes = zx297520v3_top_muxes,
+ .num_muxes = ARRAY_SIZE(zx297520v3_top_muxes),
+ .divs = zx297520v3_top_dividers,
+ .num_divs = ARRAY_SIZE(zx297520v3_top_dividers),
+ .gates = zx297520v3_top_gates,
+ .num_gates = ARRAY_SIZE(zx297520v3_top_gates),
+};
+
+static int zx297520v3_topclk_probe(struct platform_device *pdev)
+{
+ return zx_clk_common_probe(&pdev->dev, pdev->dev.parent->of_node, &zx297520v3_topclk_data);
+}
+
+static struct platform_driver clk_zx297520v3_top = {
+ .probe = zx297520v3_topclk_probe,
+ .driver = {
+ .name = "zx297520v3-topclk",
+ },
+};
+
+static struct platform_driver * const clk_zx297520v3_drivers[] = {
+ &clk_zx297520v3_top,
+};
+
+static int __init clk_zx297520v3_init(void)
+{
+ return platform_register_drivers(clk_zx297520v3_drivers,
+ ARRAY_SIZE(clk_zx297520v3_drivers));
+}
+
+static void __exit clk_zx297520v3_exit(void)
+{
+ platform_unregister_drivers(clk_zx297520v3_drivers, ARRAY_SIZE(clk_zx297520v3_drivers));
+}
+
+module_init(clk_zx297520v3_init);
+module_exit(clk_zx297520v3_exit);
+
+MODULE_AUTHOR("Stefan Dösinger <stefandoesinger@gmail.com>");
+MODULE_DESCRIPTION("ZTE zx297520v3 clock driver");
+MODULE_LICENSE("GPL");
--
2.53.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH RFC v5 09/12] clk: zte: Introduce a driver for zx297520v3 matrix clocks
2026-06-28 19:58 [PATCH RFC v5 00/12] ZTE zx297520v3 clock bindings and driver Stefan Dösinger
` (7 preceding siblings ...)
2026-06-28 19:59 ` [PATCH RFC v5 08/12] clk: zte: Introduce a driver for zx297520v3 top clocks Stefan Dösinger
@ 2026-06-28 19:59 ` Stefan Dösinger
2026-06-28 20:12 ` sashiko-bot
2026-06-28 19:59 ` [PATCH RFC v5 10/12] clk: zte: Introduce a driver for zx297520v3 LSP clocks and resets Stefan Dösinger
` (2 subsequent siblings)
11 siblings, 1 reply; 23+ messages in thread
From: Stefan Dösinger @ 2026-06-28 19:59 UTC (permalink / raw)
To: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel, Brian Masney
Cc: linux-clk, devicetree, linux-kernel, linux-arm-kernel,
Stefan Dösinger
This clock controller controls high speed devices: CPU, DMA, RAM, SDIO,
Ethernet. The only non-clock, non-reset registers I have spotted here
are hardware spinlocks.
I put the entire set of PLL generated frequencies as consumed clocks in
the binding. Due to lack of a data sheet I can't rule out that the any
of the PLL subdivions are used.
Signed-off-by: Stefan Dösinger <stefandoesinger@gmail.com>
---
Version 5:
Make it an MFD child device, remove aux device
Fix zx297529 -> zx297520 typos
Fix AHB_wclk -> ahb_wclk. That was a leftover of my old composite structs
Add SRAM0 and GSM_CFG clocks
---
drivers/clk/zte/clk-zx297520v3.c | 186 +++++++++++++++++++++++++++++++++++++++
1 file changed, 186 insertions(+)
diff --git a/drivers/clk/zte/clk-zx297520v3.c b/drivers/clk/zte/clk-zx297520v3.c
index bb3d64eff909..17e1f741b48c 100644
--- a/drivers/clk/zte/clk-zx297520v3.c
+++ b/drivers/clk/zte/clk-zx297520v3.c
@@ -431,8 +431,194 @@ static struct platform_driver clk_zx297520v3_top = {
},
};
+static const char * const cpu_sel[] = {
+ "osc26m",
+ "mpll", /* 624 MHz */
+ "mpll_d2", /* 312 MHz */
+ "mpll_d4", /* 156 MHz */
+};
+
+static const char * const sd0_sel[] = {
+ "osc26m",
+ "mpll_d4", /* 156 MHz */
+ "gpll_d2", /* 100 MHz */
+ "mpll_d8", /* 78 MHz */
+ "gpll_d4", /* 50 MHz */
+ "gpll_d8", /* 25 MHz */
+};
+
+static const char * const sd1_sel[] = {
+ "osc26m",
+ "gpll_d2", /* 100 MHz */
+ "mpll_d8", /* 78 MHz */
+ "gpll_d4", /* 50 MHz */
+ "mpll_d16", /* 39 MHz */
+ "gpll_d8", /* 25 MHz */
+};
+
+static const char * const nand_sel[] = {
+ "mpll_d4", /* 156 MHz */
+ "osc26m",
+};
+
+static const char * const edcp_sel[] = {
+ "osc26m",
+ "mpll_d4", /* 156 MHz */
+ "mpll_d5", /* 124.8 MHz */
+ "mpll_d6", /* 104 MHz */
+};
+
+static const char * const tdm_sel[] = {
+ "osc26m",
+ "dpll_d4", /* 122.88 MHz */
+ "mpll_d6", /* 104 MHz */
+};
+
+static const struct zx_mux_desc zx297520v3_matrix_muxes[] = {
+ MUX(0, "cpu_mux", cpu_sel, 0x20, 0, 2),
+ MUX(0, "sd0_mux", sd0_sel, 0x50, 4, 3),
+ MUX(0, "sd1_mux", sd1_sel, 0x50, 8, 3),
+ MUX(0, "nand_mux", nand_sel, 0x50, 12, 2),
+ MUX(0, "edcp_mux", edcp_sel, 0x50, 16, 2),
+ MUX(0, "tdm_mux", tdm_sel, 0x50, 24, 2),
+};
+
+static const struct zx_gate_desc zx297520v3_matrix_gates[] = {
+ /* Both 0x24 and 0x28 bits 1 and 2 stop the CPU. There is also a bit in topclk+0x138, which
+ * ZTE's uboot calls "A53 reset", which also stops the CPU. I can't really tell the
+ * difference between matrix+28 and top+138. The clock (maxtrix+0x24) can be disabled and
+ * enabled from the Cortex M0 and it will nicely stop and restart the A53, retaining all
+ * state.
+ *
+ * 0x50, bits 0-3 have the DDR clock. A lot of DDR gates and resets are in 0x100.
+ */
+ GATE(ZX297520V3_CPU_WCLK, "cpu_wclk", "cpu_mux", 0x24, 1,
+ CLK_IS_CRITICAL),
+ GATE(ZX297520V3_CPU_PCLK, "cpu_pclk", clk_main[0], 0x24, 2,
+ CLK_IS_CRITICAL),
+
+ /* ZSP aka LTE DSP clock. I think there is a mux at matrix+0x30, but I have no idea
+ * about the frequencies it selects.
+ */
+ GATE(ZX297520V3_ZSP_WCLK, "zsp_wclk", clk_unknown[0], 0x3c, 0, 0),
+
+ GATE(ZX297520V3_SD0_WCLK, "sd0_wclk", "sd0_mux", 0x54, 12, 0),
+ GATE(ZX297520V3_SD0_PCLK, "sd0_pclk", clk_main[0], 0x54, 13, 0),
+ GATE(ZX297520V3_SD0_CDET, "sd0_cdet", "osc32k", 0x54, 14, 0),
+ GATE(ZX297520V3_SD1_WCLK, "sd1_wclk", "sd1_mux", 0x54, 4, 0),
+ GATE(ZX297520V3_SD1_PCLK, "sd1_pclk", clk_main[0], 0x54, 5, 0),
+ /* I don't know how the cdet clock works. Card detection in the way the dwc,mmc driver uses
+ * it appears broken no matter this clock's setting.
+ */
+ GATE(ZX297520V3_SD1_CDET, "sd1_cdet", "osc32k", 0x54, 6, 0),
+
+ /* This is some "denali" NAND, not the qspi connected one */
+ GATE(ZX297520V3_NAND_WCLK, "nand_wclk", "nand_mux", 0x54, 20, 0),
+ GATE(ZX297520V3_NAND_PCLK, "nand_pclk", clk_main[0], 0x54, 21, 0),
+ GATE(ZX297520V3_SSC_WCLK, "ssc_wclk", clk_unknown[0], 0x84, 1, 0),
+ GATE(ZX297520V3_SSC_PCLK, "ssc_pclk", clk_main[0], 0x84, 2, 0),
+
+ /* Yes, WCLK bit > PCLK bit for EDCP */
+ GATE(ZX297520V3_EDCP_WCLK, "edcp_wclk", "edcp_mux", 0x64, 2, 0),
+ GATE(ZX297520V3_EDCP_PCLK, "edcp_pclk", clk_main[0], 0x64, 1, 0),
+
+ /* There are a lot more VOU related controls in these registers, but turning off the main
+ * clock seems to shut off the entire VOU MMIO range.
+ */
+ GATE(ZX297520V3_VOU_WCLK, "vou_wclk", clk_unknown[0], 0x168, 0, 0),
+ GATE(ZX297520V3_VOU_PCLK, "vou_pclk", clk_main[0], 0x168, 1, 0),
+
+ /* PDCFG. Like PMM, either clock bit will allow the device to function. */
+ GATE(ZX297520V3_PDCFG_WCLK, "pdcfg_wclk", clk_unknown[0], 0x88, 0,
+ CLK_IS_CRITICAL),
+ GATE(ZX297520V3_PDCFG_PCLK, "pdcfg_pclk", clk_main[0], 0x88, 1,
+ CLK_IS_CRITICAL),
+
+ /* ZTE's driver has a statemt to the effect of *(top->base+0x11c) = 5, with a comment
+ * suggesting that this sets a 50 mhz clock. The clock code itself lists gmac clocks in
+ * matrix+110 and lists the parents of these clock as 50mhz gpll output, but the GMAC
+ * driver never enables the clocks. It turns out ZTE's code is highly misleading.
+ *
+ * The GMAC's work clock is definitly not any gpll output because it keeps working fine with
+ * gpll disabled. The MDIO speed is mostly unaffected by mpll speed changes, so it is most
+ * likely not fed by mpll either. All other PLLs can be disabled without breaking GMAC, so
+ * osc26m is the only possible remaining parent.
+ *
+ * The GMAC Gates are left enabled by the boot loader and are required for the GMAC to work.
+ *
+ * As for the 50 MHz comment: See rmiiphy_wclk.
+ */
+ GATE(ZX297520V3_GMAC_WCLK, "gmac_wclk", clk_main[0], 0x110, 0, 0),
+ GATE(ZX297520V3_GMAC_PCLK, "gmac_pclk", clk_main[0], 0x110, 1, 0),
+ GATE(ZX297520V3_GMAC_AHB, "gmac_ahb", "ahb_wclk", 0x110, 2, 0),
+
+ GATE(ZX297520V3_MBOX_PCLK, "mbox_pclk", clk_main[0], 0x88, 2, 0),
+ GATE(ZX297520V3_SRAM0_PCLK, "sram0_pclk", clk_main[0], 0x88, 4, 0),
+ GATE(ZX297520V3_GSM_CFG_PCLK, "gsm_cfg_pclk", clk_main[0], 0x88, 8, 0),
+ GATE(ZX297520V3_DMA_PCLK, "dma_pclk", clk_main[0], 0x94, 3, 0),
+
+ /* LSP uplink clocks. The PCLK is fairly obvious (disabling it shuts off the entire LSP
+ * register area). The WCLK speeds were deduced by setting timers and qspi muxes to a
+ * specific speed and seeing which bit in matrix+0x7c needs to be enabled for the device
+ * to work.
+ *
+ * Due to the timers I am certain about the 26mhz and 32khz clocks. I cannot directly
+ * observe the qspi mux frequency, so the clock rates depend on ZTE's qspi mux selection
+ * being correct.
+ *
+ * Two additional bits are specific to sound components - the mux for the LSP's TDM IP is
+ * in matrixclk and gets passed down. I2S has a mux in LSP, which can select the dpll_d4
+ * clock.
+ */
+ GATE(ZX297520V3_LSP_MPLL_D5_WCLK, "lsp_mpll_d5", "mpll_d5", 0x7c, 0, 0),
+ GATE(ZX297520V3_LSP_MPLL_D4_WCLK, "lsp_mpll_d4", "mpll_d4", 0x7c, 1, 0),
+ GATE(ZX297520V3_LSP_MPLL_D6_WCLK, "lsp_mpll_d6", "mpll_d6", 0x7c, 2, 0),
+ GATE(ZX297520V3_LSP_MPLL_D8_WCLK, "lsp_mpll_d8", "mpll_d8", 0x7c, 3, 0),
+ GATE(ZX297520V3_LSP_MPLL_D12_WCLK, "lsp_mpll_d12", "mpll_d12", 0x7c, 4, 0),
+ GATE(ZX297520V3_LSP_OSC26M_WCLK, "lsp_osc26m", clk_main[0], 0x7c, 5, 0),
+ GATE(ZX297520V3_LSP_OSC32K_WCLK, "lsp_osc32k", "osc32k", 0x7c, 6, 0),
+ GATE(ZX297520V3_LSP_PCLK, "lsp_pclk", clk_main[0], 0x7c, 7, 0),
+ GATE(ZX297520V3_LSP_TDM_WCLK, "lsp_tdm_wclk", "tdm_mux", 0x7c, 8, 0),
+ GATE(ZX297520V3_LSP_DPLL_D4_WCLK, "lsp_dpll_d4", "dpll_d4", 0x7c, 9, 0),
+};
+
+static const char * const zx297520v3_matrix_inputs[] = {
+ "osc26m", "osc32k",
+ "mpll", "mpll_d2", "mpll_d3", "mpll_d4", "mpll_d5", "mpll_d6", "mpll_d8", "mpll_d12",
+ "mpll_d16", "mpll_d26",
+ "upll", "upll_d2", "upll_d3", "upll_d4", "upll_d5", "upll_d6", "upll_d8", "upll_d12",
+ "upll_d16",
+ "dpll", "dpll_d2", "dpll_d3", "dpll_d4", "dpll_d5", "dpll_d6", "dpll_d8", "dpll_d12",
+ "dpll_d16",
+ "gpll", "gpll_d2", "gpll_d3", "gpll_d4", "gpll_d5", "gpll_d6", "gpll_d8", "gpll_d12",
+ "gpll_d16",
+};
+
+static const struct zx_clk_data zx297520v3_matrixclk_data = {
+ .inputs = zx297520v3_matrix_inputs,
+ .num_inputs = ARRAY_SIZE(zx297520v3_matrix_inputs),
+ .muxes = zx297520v3_matrix_muxes,
+ .num_muxes = ARRAY_SIZE(zx297520v3_matrix_muxes),
+ .gates = zx297520v3_matrix_gates,
+ .num_gates = ARRAY_SIZE(zx297520v3_matrix_gates),
+};
+
+static int zx297520v3_matrixclk_probe(struct platform_device *pdev)
+{
+ return zx_clk_common_probe(&pdev->dev, pdev->dev.parent->of_node,
+ &zx297520v3_matrixclk_data);
+}
+
+static struct platform_driver clk_zx297520v3_matrix = {
+ .probe = zx297520v3_matrixclk_probe,
+ .driver = {
+ .name = "zx297520v3-matrixclk",
+ },
+};
+
static struct platform_driver * const clk_zx297520v3_drivers[] = {
&clk_zx297520v3_top,
+ &clk_zx297520v3_matrix,
};
static int __init clk_zx297520v3_init(void)
--
2.53.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH RFC v5 10/12] clk: zte: Introduce a driver for zx297520v3 LSP clocks and resets
2026-06-28 19:58 [PATCH RFC v5 00/12] ZTE zx297520v3 clock bindings and driver Stefan Dösinger
` (8 preceding siblings ...)
2026-06-28 19:59 ` [PATCH RFC v5 09/12] clk: zte: Introduce a driver for zx297520v3 matrix clocks Stefan Dösinger
@ 2026-06-28 19:59 ` Stefan Dösinger
2026-06-28 20:18 ` sashiko-bot
2026-06-28 19:59 ` [PATCH RFC v5 11/12] reset: zte: Add a zx297520v3 reset driver Stefan Dösinger
2026-06-28 19:59 ` [PATCH RFC v5 12/12] ARM: dts: zte: Declare zx297520v3 CRM device nodes Stefan Dösinger
11 siblings, 1 reply; 23+ messages in thread
From: Stefan Dösinger @ 2026-06-28 19:59 UTC (permalink / raw)
To: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel, Brian Masney
Cc: linux-clk, devicetree, linux-kernel, linux-arm-kernel,
Stefan Dösinger
"LSP" is ZTE's term for this part of the SoC, I suspect it stands for
"low speed peripherals". The main UART is here, together with the flash
controller and more surplus proprietary timers.
It also has one more I2C controller that supposedly connects to a
battery charger, SPI for displays and I2S for analog telephones. The
boards I have don't have any of these components though.
Signed-off-by: Stefan Dösinger <stefandoesinger@gmail.com>
---
Version 5:
Removed unused tdm_lsp_sel. Fix zx297529 vs zx297520 typo.
Adjusted .compatible to zte,zx297520v3-lspcrm
---
drivers/clk/zte/clk-zx297520v3.c | 208 +++++++++++++++++++++++++++++++++++++++
1 file changed, 208 insertions(+)
diff --git a/drivers/clk/zte/clk-zx297520v3.c b/drivers/clk/zte/clk-zx297520v3.c
index 17e1f741b48c..f4ab065ee496 100644
--- a/drivers/clk/zte/clk-zx297520v3.c
+++ b/drivers/clk/zte/clk-zx297520v3.c
@@ -616,9 +616,217 @@ static struct platform_driver clk_zx297520v3_matrix = {
},
};
+/* LSP clock entries have a common pattern: Bit 0 for WCLK, Bit 1 for PCLK. Bit 4 (and sometimes
+ * more) for WCLK mux.
+ *
+ * Bit 8 and 9 are resets handled by the reset-zte-zx297520v3 driver.
+ *
+ * Bits 15:12 can be a divisor, but not all clocks have it. Some clocks have a divisor in 19:16.
+ *
+ * The ID given in this table is the first register in the device's MMIO space. ZTE's drivers
+ * usually call this a version register, but it looks more like a device identifier.
+ *
+ * It looks like the registers map to devices like this:
+ *
+ * Timer reg function div dev offset(lsp + xxxx) ID
+ * 0x0: Read-only, probably device identifier 0x00752100
+ * 0x4: timer_l1 Y 0x1000 0x02020000
+ * 0x8: watchdog_l2 Y 0x2000 0x02020000
+ * 0xc: watchdog_l3 Y 0x3000 0x02020000
+ * 0x10: pwm N 0x4000 0x01020000
+ * 0x14: i2s0 Yh 0x5000 0x01030000
+ * 0x18: always 0 - - -
+ * 0x1c: i2s1 Yh 0x6000 0x01030000
+ * 0x20: always 0 - - -
+ * 0x24: qspi N 0x7000 0x01040000
+ * 0x28: uart1 N 0x8000 0x01060000
+ * 0x2c: i2c1 N 0x9000 0x01020000
+ * 0x30: spi0 Y 0xa000 0x01040000
+ * 0x34: timer_lb Y 0xb000 0x02020000
+ * 0x38: timer_lc Y 0xc000 0x02020000
+ * 0x3c: uart2 N 0xd000 0x01060000
+ * 0x40: watchdog_le Y 0xe000 0x02020000
+ * 0x44: timer_lf Y 0xf000 0x02020000
+ * 0x48: spi1 Y 0x10000 0x01040000
+ * 0x4c: timer_l11 Y 0x11000 0x02020000
+ * 0x50: tdm Yh 0x12000 0x01040000
+ *
+ * Registers 0x58, 0x5c, 0x60, 0x64, 0x68 seem to contain more controls for i2s and tdm.
+ *
+ * I am not sure about the device at offset 0x4000 (clk reg 0x10). The ID matches that of i2c, but
+ * it has a larger register set. I suspect it is a PWM device, but I have not seen any ZTE kernel
+ * operate it - even devices with displays only use a GPIO to control the backlight.
+ */
+
+static const char * const timer_lsp_sel[] = {
+ "lsp_osc32k",
+ "lsp_osc26m",
+};
+
+static const char * const uart_lsp_sel[] = {
+ "lsp_osc26m",
+ "lsp_mpll_d6",
+};
+
+static const char * const i2s_lsp_sel[] = {
+ "lsp_osc26m",
+ "lsp_dpll_d4",
+ "lsp_mpll_d6",
+ /* Unknown */
+};
+
+static const char * const spi_lsp_sel[] = {
+ "lsp_osc26m",
+ "lsp_mpll_d4",
+ "lsp_mpll_d6",
+ /* Unknown */
+};
+
+static const char * const qspi_lsp_sel[] = {
+ "lsp_osc26m",
+ "lsp_mpll_d4",
+ "lsp_mpll_d5",
+ "lsp_mpll_d6",
+ "lsp_mpll_d8",
+ "lsp_mpll_d12",
+ "lsp_osc26m",
+ "lsp_osc26m",
+};
+
+static const struct zx_mux_desc zx297520v3_lsp_muxes[] = {
+ MUX(0, "timer_l1_mux", timer_lsp_sel, 0x04, 4, 1),
+ MUX(0, "wdt_l2_mux", timer_lsp_sel, 0x08, 4, 1),
+ MUX(0, "wdt_l3_mux", timer_lsp_sel, 0x0c, 4, 1),
+ /* PWM: No mux bit can be set */
+ MUX(0, "i2s0_mux", i2s_lsp_sel, 0x14, 4, 2),
+ /* 0x18: Always 0 */
+ MUX(0, "i2s1_mux", i2s_lsp_sel, 0x1c, 4, 2),
+ /* 0x20: Always 0 */
+ MUX(0, "qspi_mux", qspi_lsp_sel, 0x24, 4, 3),
+ MUX(0, "uart1_mux", uart_lsp_sel, 0x28, 4, 1),
+ MUX(0, "i2c1_mux", uart_lsp_sel, 0x2c, 4, 1),
+ MUX(0, "spi0_mux", spi_lsp_sel, 0x30, 4, 2),
+ MUX(0, "timer_lb_mux", timer_lsp_sel, 0x34, 4, 1),
+ MUX(0, "timer_lc_mux", timer_lsp_sel, 0x38, 4, 1),
+ MUX(0, "uart2_mux", uart_lsp_sel, 0x3c, 4, 1),
+ MUX(0, "wdt_le_mux", timer_lsp_sel, 0x40, 4, 1),
+ MUX(0, "timer_lf_mux", timer_lsp_sel, 0x44, 4, 1),
+ MUX(0, "spi1_mux", spi_lsp_sel, 0x48, 4, 2),
+ MUX(0, "timer_l11_mux", timer_lsp_sel, 0x4c, 4, 1),
+ /* TDM: No mux in LSP. Instead, it is in matrix with a separate clk line to LSP */
+};
+
+static const struct zx_div_desc zx297520v3_lsp_dividers[] = {
+ DIV(0, "timer_l1_div", "timer_l1_mux", 0x04, 12, 4),
+ DIV(0, "wdt_l2_div", "wdt_l2_mux", 0x08, 12, 4),
+ DIV(0, "wdt_l3_div", "wdt_l3_mux", 0x0c, 12, 4),
+ /* PWM: No div */
+ DIV(0, "i2s0_div", "i2s0_mux", 0x14, 16, 4),
+ /* 0x18: Always 0 */
+ DIV(0, "i2s1_div", "i2s1_mux", 0x1c, 16, 4),
+ /* 0x20: Always 0 */
+ /* qspi, uart1, i2c1: No div */
+ DIV(0, "spi0_div", "spi0_mux", 0x30, 12, 4),
+ DIV(0, "timer_lb_div", "timer_lb_mux", 0x34, 12, 4),
+ DIV(0, "timer_lc_div", "timer_lc_mux", 0x38, 12, 4),
+ /* uart2: No div */
+ DIV(0, "wdt_le_div", "wdt_le_mux", 0x40, 12, 4),
+ DIV(0, "timer_lf_div", "timer_lf_mux", 0x44, 12, 4),
+ DIV(0, "spi1_div", "spi1_mux", 0x48, 12, 4),
+ DIV(0, "timer_l11_div", "timer_l11_mux", 0x4c, 12, 4),
+ DIV(0, "tdm_div", "lsp_tdm_wclk", 0x50, 16, 4),
+};
+
+static const struct zx_gate_desc zx297520v3_lsp_gates[] = {
+ GATE(ZX297520V3_TIMER_L1_WCLK, "timer_l1_wclk", "timer_l1_div", 0x04, 0, 0),
+ GATE(ZX297520V3_TIMER_L1_PCLK, "timer_l1_pclk", "lsp_pclk", 0x04, 1, 0),
+ GATE(ZX297520V3_WDT_L2_WCLK, "wdt_l2_wclk", "wdt_l2_div", 0x08, 0, 0),
+ GATE(ZX297520V3_WDT_L2_PCLK, "wdt_l2_pclk", "lsp_pclk", 0x08, 1, 0),
+ GATE(ZX297520V3_WDT_L3_WCLK, "wdt_l3_wclk", "wdt_l3_div", 0x0c, 0, 0),
+ GATE(ZX297520V3_WDT_L3_PCLK, "wdt_l3_pclk", "lsp_pclk", 0x0c, 1, 0),
+ /* I don't know the LSP parent. It must be one of the LSP inputs though. */
+ GATE(ZX297520V3_PWM_WCLK, "pwm_wclk", "lsp_osc26m", 0x10, 0, 0),
+ GATE(ZX297520V3_PWM_PCLK, "pwm_pclk", "lsp_pclk", 0x10, 1, 0),
+ GATE(ZX297520V3_I2S0_WCLK, "i2s0_wclk", "i2s0_div", 0x14, 0, 0),
+ GATE(ZX297520V3_I2S0_PCLK, "i2s0_pclk", "lsp_pclk", 0x14, 1, 0),
+ /* 0x1c: Always 0 */
+ GATE(ZX297520V3_I2S1_WCLK, "i2s1_wclk", "i2s1_div", 0x1c, 0, 0),
+ GATE(ZX297520V3_I2S1_PCLK, "i2s1_pclk", "lsp_pclk", 0x1c, 1, 0),
+ /* 0x20: Always 0 */
+ GATE(ZX297520V3_QSPI_WCLK, "qspi_wclk", "qspi_mux", 0x24, 0, 0),
+ GATE(ZX297520V3_QSPI_PCLK, "qspi_pclk", "lsp_pclk", 0x24, 1, 0),
+ GATE(ZX297520V3_UART1_WCLK, "uart1_wclk", "uart1_mux", 0x28, 0, 0),
+ GATE(ZX297520V3_UART1_PCLK, "uart1_pclk", "lsp_pclk", 0x28, 1, 0),
+ GATE(ZX297520V3_I2C1_WCLK, "i2c1_wclk", "i2c1_mux", 0x2c, 0, 0),
+ GATE(ZX297520V3_I2C1_PCLK, "i2c1_pclk", "lsp_pclk", 0x2c, 1, 0),
+ GATE(ZX297520V3_SPI0_WCLK, "spi0_wclk", "spi0_div", 0x30, 0, 0),
+ GATE(ZX297520V3_SPI0_PCLK, "spi0_pclk", "lsp_pclk", 0x30, 1, 0),
+ GATE(ZX297520V3_TIMER_LB_WCLK, "timer_lb_wclk", "timer_lb_div", 0x34, 0, 0),
+ GATE(ZX297520V3_TIMER_LB_PCLK, "timer_lb_pclk", "lsp_pclk", 0x34, 1, 0),
+ GATE(ZX297520V3_TIMER_LC_WCLK, "timer_lc_wclk", "timer_lc_div", 0x38, 0, 0),
+ GATE(ZX297520V3_TIMER_LC_PCLK, "timer_lc_pclk", "lsp_pclk", 0x38, 1, 0),
+ GATE(ZX297520V3_UART2_WCLK, "uart2_wclk", "uart2_mux", 0x3c, 0, 0),
+ GATE(ZX297520V3_UART2_PCLK, "uart2_pclk", "lsp_pclk", 0x3c, 1, 0),
+ GATE(ZX297520V3_WDT_LE_WCLK, "wdt_le_wclk", "wdt_le_div", 0x40, 0, 0),
+ GATE(ZX297520V3_WDT_LE_PCLK, "wdt_le_pclk", "lsp_pclk", 0x40, 1, 0),
+ GATE(ZX297520V3_TIMER_LF_WCLK, "timer_lf_wclk", "timer_lf_div", 0x44, 0, 0),
+ GATE(ZX297520V3_TIMER_LF_PCLK, "timer_lf_pclk", "lsp_pclk", 0x44, 1, 0),
+ GATE(ZX297520V3_SPI1_WCLK, "spi1_wclk", "spi1_div", 0x48, 0, 0),
+ GATE(ZX297520V3_SPI1_PCLK, "spi1_pclk", "lsp_pclk", 0x48, 1, 0),
+ GATE(ZX297520V3_TIMER_L11_WCLK, "timer_l11_wclk", "timer_l11_div", 0x4c, 0, 0),
+ GATE(ZX297520V3_TIMER_L11_PCLK, "timer_l11_pclk", "lsp_pclk", 0x4c, 1, 0),
+ GATE(ZX297520V3_TDM_WCLK, "tdm_wclk", "tdm_div", 0x50, 0, 0),
+ GATE(ZX297520V3_TDM_PCLK, "tdm_pclk", "lsp_pclk", 0x50, 1, 0),
+};
+
+static const char * const zx297520v3_lsp_inputs[] = {
+ "mpll_d5", "mpll_d4", "mpll_d6", "mpll_d8", "mpll_d12",
+ "osc26m", "osc32k", "tdm_wclk", "dpll_d4"
+};
+
+static const char * const zx297520v3_lsp_inputs_enable[] = {
+ "pclk"
+};
+
+static const struct zx_clk_data zx297520v3_lsprm_data = {
+ .inputs_enable = zx297520v3_lsp_inputs_enable,
+ .num_inputs_enable = ARRAY_SIZE(zx297520v3_lsp_inputs_enable),
+ .inputs = zx297520v3_lsp_inputs,
+ .num_inputs = ARRAY_SIZE(zx297520v3_lsp_inputs),
+ .muxes = zx297520v3_lsp_muxes,
+ .num_muxes = ARRAY_SIZE(zx297520v3_lsp_muxes),
+ .divs = zx297520v3_lsp_dividers,
+ .num_divs = ARRAY_SIZE(zx297520v3_lsp_dividers),
+ .gates = zx297520v3_lsp_gates,
+ .num_gates = ARRAY_SIZE(zx297520v3_lsp_gates),
+ .reset_auxdev_name = "zx297520v3_lsprst"
+};
+
+static const struct of_device_id of_match_zx297520v3[] = {
+ { .compatible = "zte,zx297520v3-lspcrm", .data = &zx297520v3_lsprm_data },
+ { }
+};
+MODULE_DEVICE_TABLE(of, of_match_zx297520v3);
+
+static int zx297520v3_lspclk_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+
+ return zx_clk_common_probe(dev, dev->of_node, device_get_match_data(dev));
+}
+
+static struct platform_driver clk_zx297520v3_lsp = {
+ .probe = zx297520v3_lspclk_probe,
+ .driver = {
+ .name = "clk-zx297520v3",
+ .of_match_table = of_match_zx297520v3,
+ },
+};
+
static struct platform_driver * const clk_zx297520v3_drivers[] = {
&clk_zx297520v3_top,
&clk_zx297520v3_matrix,
+ &clk_zx297520v3_lsp,
};
static int __init clk_zx297520v3_init(void)
--
2.53.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH RFC v5 11/12] reset: zte: Add a zx297520v3 reset driver
2026-06-28 19:58 [PATCH RFC v5 00/12] ZTE zx297520v3 clock bindings and driver Stefan Dösinger
` (9 preceding siblings ...)
2026-06-28 19:59 ` [PATCH RFC v5 10/12] clk: zte: Introduce a driver for zx297520v3 LSP clocks and resets Stefan Dösinger
@ 2026-06-28 19:59 ` Stefan Dösinger
2026-06-28 20:23 ` sashiko-bot
2026-06-28 19:59 ` [PATCH RFC v5 12/12] ARM: dts: zte: Declare zx297520v3 CRM device nodes Stefan Dösinger
11 siblings, 1 reply; 23+ messages in thread
From: Stefan Dösinger @ 2026-06-28 19:59 UTC (permalink / raw)
To: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel, Brian Masney
Cc: linux-clk, devicetree, linux-kernel, linux-arm-kernel,
Stefan Dösinger
This drives the MFD child devices created by the zx297520v3-crm driver
as well as the aux device created by the zx297520v3-lspclk driver.
Signed-off-by: Stefan Dösinger <stefandoesinger@gmail.com>
---
v5:
Make top and matrix MFD children instead of aux devices
Split USB PHY reset into its own reset ID
Remove USB reset wait code - this will be handled via syscon from a
future minimal phy-zx29-usb driver
---
MAINTAINERS | 1 +
drivers/reset/Kconfig | 11 ++
drivers/reset/Makefile | 1 +
drivers/reset/reset-zte-zx297520v3.c | 274 +++++++++++++++++++++++++++++++++++
4 files changed, 287 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 297c15a2c860..d96bfb51c90e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3872,6 +3872,7 @@ F: Documentation/devicetree/bindings/soc/zte/
F: arch/arm/boot/dts/zte/
F: arch/arm/mach-zte/
F: drivers/clk/zte/
+F: drivers/reset/reset-zte-zx297520v3.c
F: drivers/soc/zte/
F: include/dt-bindings/clock/zte,zx297520v3-clk.h
F: include/dt-bindings/reset/zte,zx297520v3-reset.h
diff --git a/drivers/reset/Kconfig b/drivers/reset/Kconfig
index d009eb0849a3..116dd23f1b8e 100644
--- a/drivers/reset/Kconfig
+++ b/drivers/reset/Kconfig
@@ -404,6 +404,17 @@ config RESET_UNIPHIER_GLUE
on UniPhier SoCs. Say Y if you want to control reset signals
provided by the glue layer.
+config RESET_ZTE_ZX297520V3
+ tristate "ZTE zx297520v3 Reset Driver"
+ depends on (ARCH_ZTE || COMPILE_TEST)
+ default CLK_ZTE_ZX297520V3
+ select AUXILIARY_BUS
+ help
+ This enables the reset controller for ZTE zx297520v3 SoCs. The reset
+ controller is part of the clock controller on this SoC. This driver
+ operates on an auxiliary device exposed by the clock driver. Enable
+ this driver if you plan to boot the kernel on a zx297520v3 based SoC.
+
config RESET_ZYNQ
bool "ZYNQ Reset Driver" if COMPILE_TEST
default ARCH_ZYNQ
diff --git a/drivers/reset/Makefile b/drivers/reset/Makefile
index 3e52569bd276..9a8a48d44dc4 100644
--- a/drivers/reset/Makefile
+++ b/drivers/reset/Makefile
@@ -50,5 +50,6 @@ obj-$(CONFIG_RESET_TI_TPS380X) += reset-tps380x.o
obj-$(CONFIG_RESET_TN48M_CPLD) += reset-tn48m.o
obj-$(CONFIG_RESET_UNIPHIER) += reset-uniphier.o
obj-$(CONFIG_RESET_UNIPHIER_GLUE) += reset-uniphier-glue.o
+obj-$(CONFIG_RESET_ZTE_ZX297520V3) += reset-zte-zx297520v3.o
obj-$(CONFIG_RESET_ZYNQ) += reset-zynq.o
obj-$(CONFIG_RESET_ZYNQMP) += reset-zynqmp.o
diff --git a/drivers/reset/reset-zte-zx297520v3.c b/drivers/reset/reset-zte-zx297520v3.c
new file mode 100644
index 000000000000..8ef434904230
--- /dev/null
+++ b/drivers/reset/reset-zte-zx297520v3.c
@@ -0,0 +1,274 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2026 Stefan Dösinger
+ */
+#include <dt-bindings/reset/zte,zx297520v3-reset.h>
+#include <linux/reset-controller.h>
+#include <linux/platform_device.h>
+#include <linux/auxiliary_bus.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
+#include <linux/iopoll.h>
+
+/* Most devices on the zx297520v3 SoC have one reset bit per clock line. As a rule of thumb, the
+ * lower bit disconnects the device from the bus, similarly to turning off PCLK - registers read 0
+ * or hang indefinitely. Unlike PCLK, this reset may have a lingering effect after deasserting.
+ * E.g. timers will be disabled, but retain their counter value.
+ *
+ * The other bit resets the actual device registers.
+ *
+ * For some devices, e.g. GMAC, the reset bits behave in the same way: They disconnect the device
+ * and registers will have their default state after deasserting. For devices that have both reset
+ * bits, both need to be deasserted for the device to function.
+ */
+struct zte_reset_reg {
+ u32 mask;
+ u16 reg;
+};
+
+struct zte_reset_info {
+ const struct zte_reset_reg *resets;
+ unsigned int num;
+};
+
+struct zte_reset {
+ struct reset_controller_dev rcdev;
+ struct regmap *map;
+ const struct zte_reset_reg *resets;
+};
+
+static inline struct zte_reset *to_zte_reset(struct reset_controller_dev *rcdev)
+{
+ return container_of(rcdev, struct zte_reset, rcdev);
+}
+
+static int zx29_rst_assert(struct reset_controller_dev *rcdev, unsigned long id)
+{
+ struct zte_reset *rst = to_zte_reset(rcdev);
+
+ return regmap_clear_bits(rst->map, rst->resets[id].reg, rst->resets[id].mask);
+}
+
+static int zx29_rst_deassert(struct reset_controller_dev *rcdev, unsigned long id)
+{
+ struct zte_reset *rst = to_zte_reset(rcdev);
+
+ return regmap_set_bits(rst->map, rst->resets[id].reg, rst->resets[id].mask);
+}
+
+static int zx29_rst_status(struct reset_controller_dev *rcdev, unsigned long id)
+{
+ struct zte_reset *rst = to_zte_reset(rcdev);
+ int res;
+
+ res = regmap_test_bits(rst->map, rst->resets[id].reg, rst->resets[id].mask);
+ if (res < 0)
+ return res;
+
+ return !res;
+}
+
+static const struct reset_control_ops zx29_rst_ops = {
+ .assert = zx29_rst_assert,
+ .deassert = zx29_rst_deassert,
+ .status = zx29_rst_status,
+};
+
+static const struct zte_reset_reg zx297520v3_top_resets[] = {
+ /* This bit is set by ZTE's cpko.ko blob, it looks like a reset bit for the LTE DSP
+ * coprocessor. Clocks for it are in matrixcrm.
+ */
+ [ZX297520V3_ZSP_RESET] = { .reg = 0x13c, .mask = BIT(0) },
+
+ [ZX297520V3_UART0_RESET] = { .reg = 0x78, .mask = BIT(6) | BIT(7) },
+ [ZX297520V3_I2C0_RESET] = { .reg = 0x74, .mask = BIT(8) | BIT(9) },
+ /* Only one reset. Bit 5 is settable but does not do anything observable */
+ [ZX297520V3_RTC_RESET] = { .reg = 0x74, .mask = BIT(4) },
+ [ZX297520V3_TIMER_T08_RESET] = { .reg = 0x78, .mask = BIT(4) | BIT(5) },
+ [ZX297520V3_TIMER_T09_RESET] = { .reg = 0x78, .mask = BIT(2) | BIT(3) },
+ /* Only one reset. Bit 0 is settable but does not do anything observable */
+ [ZX297520V3_PMM_RESET] = { .reg = 0x74, .mask = BIT(1) },
+
+ /* I haven't found any clocks for GPIO. It probably wouldn't make much
+ * sense anyway. Only one reset bit per controller.
+ */
+ [ZX297520V3_GPIO_RESET] = { .reg = 0x74, .mask = BIT(3) },
+ [ZX297520V3_GPIO8_RESET] = { .reg = 0x74, .mask = BIT(2) },
+
+ [ZX297520V3_TIMER_T12_RESET] = { .reg = 0x74, .mask = BIT(6) | BIT(7) },
+ [ZX297520V3_TIMER_T13_RESET] = { .reg = 0x7c, .mask = BIT(0) | BIT(1) },
+ [ZX297520V3_TIMER_T14_RESET] = { .reg = 0x7c, .mask = BIT(2) | BIT(3) },
+ [ZX297520V3_TIMER_T15_RESET] = { .reg = 0x74, .mask = BIT(10) | BIT(11) },
+ [ZX297520V3_TIMER_T16_RESET] = { .reg = 0x7c, .mask = BIT(4) | BIT(5) },
+ [ZX297520V3_TIMER_T17_RESET] = { .reg = 0x12c, .mask = BIT(0) | BIT(1) },
+ [ZX297520V3_WDT_T18_RESET] = { .reg = 0x74, .mask = BIT(12) | BIT(13) },
+ [ZX297520V3_USIM1_RESET] = { .reg = 0x74, .mask = BIT(14) | BIT(15) },
+ [ZX297520V3_AHB_RESET] = { .reg = 0x70, .mask = BIT(0) | BIT(1) },
+
+ /* USB reset. 0x84 returns the USB device status (0x1 for HSIC up, 0x2 for USB up, but
+ * all 3 bits (PCLK, WCLK, PHY) need to be deasserted for the device to report ready.
+ * Thus polling the status is the responsibility of the USB PHY driver.
+ */
+ [ZX297520V3_USB_PHY_RESET] = { .reg = 0x80, .mask = BIT(3) },
+ [ZX297520V3_USB_RESET] = { .reg = 0x80, .mask = BIT(4) | BIT(5) },
+ [ZX297520V3_HSIC_PHY_RESET] = { .reg = 0x80, .mask = BIT(0) },
+ [ZX297520V3_HSIC_RESET] = { .reg = 0x80, .mask = BIT(1) | BIT(2) },
+};
+
+static const struct zte_reset_info zx297520v3_top_info = {
+ .resets = zx297520v3_top_resets,
+ .num = ARRAY_SIZE(zx297520v3_top_resets),
+};
+
+static const struct zte_reset_reg zx297520v3_matrix_resets[] = {
+ [ZX297520V3_CPU_RESET] = { .reg = 0x28, .mask = BIT(1) },
+ [ZX297520V3_EDCP_RESET] = { .reg = 0x68, .mask = BIT(0) },
+ [ZX297520V3_SD0_RESET] = { .reg = 0x58, .mask = BIT(1) },
+ [ZX297520V3_SD1_RESET] = { .reg = 0x58, .mask = BIT(0) },
+ [ZX297520V3_NAND_RESET] = { .reg = 0x58, .mask = BIT(4) },
+ [ZX297520V3_PDCFG_RESET] = { .reg = 0x94, .mask = BIT(20) },
+ [ZX297520V3_SSC_RESET] = { .reg = 0x94, .mask = BIT(24) },
+ [ZX297520V3_GMAC_RESET] = { .reg = 0x114, .mask = BIT(0) | BIT(1) },
+ [ZX297520V3_VOU_RESET] = { .reg = 0x16c, .mask = BIT(0) },
+};
+
+static const struct zte_reset_info zx297520v3_matrix_info = {
+ .resets = zx297520v3_matrix_resets,
+ .num = ARRAY_SIZE(zx297520v3_matrix_resets),
+};
+
+static const struct zte_reset_reg zx297520v3_lsp_resets[] = {
+ [ZX297520V3_TIMER_L1_RESET] = { .reg = 0x04, .mask = BIT(8) | BIT(9) },
+ [ZX297520V3_WDT_L2_RESET] = { .reg = 0x08, .mask = BIT(8) | BIT(9) },
+ [ZX297520V3_WDT_L3_RESET] = { .reg = 0x0c, .mask = BIT(8) | BIT(9) },
+ [ZX297520V3_PWM_RESET] = { .reg = 0x10, .mask = BIT(8) | BIT(9) },
+ [ZX297520V3_I2S0_RESET] = { .reg = 0x14, .mask = BIT(8) | BIT(9) },
+ /* 0x18: Not writeable */
+ [ZX297520V3_I2S1_RESET] = { .reg = 0x1c, .mask = BIT(8) | BIT(9) },
+ /* 0x20: Not writeable */
+ [ZX297520V3_QSPI_RESET] = { .reg = 0x24, .mask = BIT(8) | BIT(9) },
+ [ZX297520V3_UART1_RESET] = { .reg = 0x28, .mask = BIT(8) | BIT(9) },
+ [ZX297520V3_I2C1_RESET] = { .reg = 0x2c, .mask = BIT(8) | BIT(9) },
+ [ZX297520V3_SPI0_RESET] = { .reg = 0x30, .mask = BIT(8) | BIT(9) },
+ [ZX297520V3_TIMER_LB_RESET] = { .reg = 0x34, .mask = BIT(8) | BIT(9) },
+ [ZX297520V3_TIMER_LC_RESET] = { .reg = 0x38, .mask = BIT(8) | BIT(9) },
+ [ZX297520V3_UART2_RESET] = { .reg = 0x3c, .mask = BIT(8) | BIT(9) },
+ [ZX297520V3_WDT_LE_RESET] = { .reg = 0x40, .mask = BIT(8) | BIT(9) },
+ [ZX297520V3_TIMER_LF_RESET] = { .reg = 0x44, .mask = BIT(8) | BIT(9) },
+ [ZX297520V3_SPI1_RESET] = { .reg = 0x48, .mask = BIT(8) | BIT(9) },
+ [ZX297520V3_TIMER_L11_RESET] = { .reg = 0x4c, .mask = BIT(8) | BIT(9) },
+ [ZX297520V3_TDM_RESET] = { .reg = 0x50, .mask = BIT(8) | BIT(9) },
+};
+
+static const struct zte_reset_info zx297520v3_lsp_info = {
+ .resets = zx297520v3_lsp_resets,
+ .num = ARRAY_SIZE(zx297520v3_lsp_resets),
+};
+
+static int reset_zx297520v3_common_probe(struct device *dev,
+ struct device_node *of_node,
+ const struct zte_reset_info *drv_info)
+{
+ struct zte_reset *rst;
+
+ rst = devm_kzalloc(dev, sizeof(*rst), GFP_KERNEL);
+ if (!rst)
+ return -ENOMEM;
+
+ rst->resets = drv_info->resets;
+ rst->rcdev.owner = THIS_MODULE;
+ rst->rcdev.nr_resets = drv_info->num;
+ rst->rcdev.ops = &zx29_rst_ops;
+ rst->rcdev.of_node = of_node;
+ rst->rcdev.dev = dev;
+
+ rst->map = device_node_to_regmap(of_node);
+ if (IS_ERR(rst->map))
+ return dev_err_probe(dev, PTR_ERR(rst->map), "Cannot get parent syscon regmap\n");
+
+ return devm_reset_controller_register(dev, &rst->rcdev);
+
+}
+
+static int reset_zx297520v3_aux_probe(struct auxiliary_device *adev,
+ const struct auxiliary_device_id *id)
+{
+ return reset_zx297520v3_common_probe(&adev->dev, adev->dev.of_node,
+ (const struct zte_reset_info *)id->driver_data);
+}
+
+static int reset_zx297520v3_top_probe(struct platform_device *pdev)
+{
+ return reset_zx297520v3_common_probe(&pdev->dev, pdev->dev.parent->of_node,
+ &zx297520v3_top_info);
+}
+
+static struct platform_driver reset_zx297520v3_top = {
+ .probe = reset_zx297520v3_top_probe,
+ .driver = {
+ .name = "zx297520v3-toprst",
+ },
+};
+
+static int reset_zx297520v3_matrix_probe(struct platform_device *pdev)
+{
+ return reset_zx297520v3_common_probe(&pdev->dev, pdev->dev.parent->of_node,
+ &zx297520v3_matrix_info);
+}
+
+static struct platform_driver reset_zx297520v3_matrix = {
+ .probe = reset_zx297520v3_matrix_probe,
+ .driver = {
+ .name = "zx297520v3-matrixrst",
+ },
+};
+
+static const struct auxiliary_device_id reset_zx297520v3_ids[] = {
+ {
+ .name = "clk_zte.zx297520v3_lsprst",
+ .driver_data = (kernel_ulong_t)&zx297520v3_lsp_info,
+ },
+ { },
+};
+MODULE_DEVICE_TABLE(auxiliary, reset_zx297520v3_ids);
+
+static struct auxiliary_driver reset_zx297520v3_auxdrv = {
+ .name = "zx297520v3_lsp_reset",
+ .id_table = reset_zx297520v3_ids,
+ .probe = reset_zx297520v3_aux_probe,
+};
+
+static struct platform_driver * const reset_zx297520v3_mfddrv[] = {
+ &reset_zx297520v3_top,
+ &reset_zx297520v3_matrix,
+};
+
+static int __init reset_zx297520v3_init(void)
+{
+ int res;
+
+ res = auxiliary_driver_register(&reset_zx297520v3_auxdrv);
+ if (res)
+ return res;
+
+ res = platform_register_drivers(reset_zx297520v3_mfddrv,
+ ARRAY_SIZE(reset_zx297520v3_mfddrv));
+ if (res)
+ auxiliary_driver_unregister(&reset_zx297520v3_auxdrv);
+
+ return res;
+}
+
+static void __exit reset_zx297520v3_exit(void)
+{
+ platform_unregister_drivers(reset_zx297520v3_mfddrv,
+ ARRAY_SIZE(reset_zx297520v3_mfddrv));
+ auxiliary_driver_unregister(&reset_zx297520v3_auxdrv);
+}
+
+module_init(reset_zx297520v3_init);
+module_exit(reset_zx297520v3_exit);
+
+MODULE_AUTHOR("Stefan Dösinger <stefandoesinger@gmail.com>");
+MODULE_DESCRIPTION("ZTE zx297520v3 reset driver");
+MODULE_LICENSE("GPL");
--
2.53.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH RFC v5 12/12] ARM: dts: zte: Declare zx297520v3 CRM device nodes
2026-06-28 19:58 [PATCH RFC v5 00/12] ZTE zx297520v3 clock bindings and driver Stefan Dösinger
` (10 preceding siblings ...)
2026-06-28 19:59 ` [PATCH RFC v5 11/12] reset: zte: Add a zx297520v3 reset driver Stefan Dösinger
@ 2026-06-28 19:59 ` Stefan Dösinger
11 siblings, 0 replies; 23+ messages in thread
From: Stefan Dösinger @ 2026-06-28 19:59 UTC (permalink / raw)
To: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel, Brian Masney
Cc: linux-clk, devicetree, linux-kernel, linux-arm-kernel,
Stefan Dösinger
This makes use of the driver added in the previous patches. It wires up
the uart clocks and resets and allows getting rid of the placeholder
uartclk node.
Signed-off-by: Stefan Dösinger <stefandoesinger@gmail.com>
---
Version 5:
Re-name from *clk to *crm
Add the syscon-reboot node here because the binding requires it
Re-add accidentally dropped uart2 IRQ
---
arch/arm/boot/dts/zte/zx297520v3.dtsi | 97 ++++++++++++++++++++++++++++++++---
1 file changed, 89 insertions(+), 8 deletions(-)
diff --git a/arch/arm/boot/dts/zte/zx297520v3.dtsi b/arch/arm/boot/dts/zte/zx297520v3.dtsi
index a16c30a164bb..2ae6b78bc034 100644
--- a/arch/arm/boot/dts/zte/zx297520v3.dtsi
+++ b/arch/arm/boot/dts/zte/zx297520v3.dtsi
@@ -4,6 +4,8 @@
*/
#include <dt-bindings/interrupt-controller/arm-gic.h>
+#include <dt-bindings/reset/zte,zx297520v3-reset.h>
+#include <dt-bindings/clock/zte,zx297520v3-clk.h>
/ {
#address-cells = <1>;
@@ -20,13 +22,16 @@ cpu@0 {
};
};
- /* Base bus clock and default for the UART. It will be replaced once a clock driver has
- * been added.
- */
- uartclk: uartclk-26000000 {
- #clock-cells = <0>;
+ osc26m: osc26m {
compatible = "fixed-clock";
clock-frequency = <26000000>;
+ #clock-cells = <0>;
+ };
+
+ osc32k: osc32k {
+ compatible = "fixed-clock";
+ clock-frequency = <32768>;
+ #clock-cells = <0>;
};
timer {
@@ -70,13 +75,87 @@ gic: interrupt-controller@f2000000 {
<0xf2040000 0x20000>;
};
+ topcrm: syscon@13b000 {
+ compatible = "zte,zx297520v3-topcrm", "syscon";
+ reg = <0x0013b000 0x400>;
+ #clock-cells = <1>;
+ #reset-cells = <1>;
+ clocks = <&osc26m>, <&osc32k>;
+ clock-names = "osc26m", "osc32k";
+
+ syscon-reboot {
+ compatible = "syscon-reboot";
+ offset = <0x0>;
+ mask = <0x1>;
+ };
+ };
+
+ matrixcrm: syscon@1306000 {
+ compatible = "zte,zx297520v3-matrixcrm";
+ reg = <0x01306000 0x400>;
+ clocks = <&osc26m>, <&osc32k>,
+ <&topcrm ZX297520V3_MPLL>, <&topcrm ZX297520V3_MPLL_D2>,
+ <&topcrm ZX297520V3_MPLL_D3>, <&topcrm ZX297520V3_MPLL_D4>,
+ <&topcrm ZX297520V3_MPLL_D5>, <&topcrm ZX297520V3_MPLL_D6>,
+ <&topcrm ZX297520V3_MPLL_D8>, <&topcrm ZX297520V3_MPLL_D12>,
+ <&topcrm ZX297520V3_MPLL_D16>, <&topcrm ZX297520V3_MPLL_D26>,
+ <&topcrm ZX297520V3_UPLL>, <&topcrm ZX297520V3_UPLL_D2>,
+ <&topcrm ZX297520V3_UPLL_D3>, <&topcrm ZX297520V3_UPLL_D4>,
+ <&topcrm ZX297520V3_UPLL_D5>, <&topcrm ZX297520V3_UPLL_D6>,
+ <&topcrm ZX297520V3_UPLL_D8>, <&topcrm ZX297520V3_UPLL_D12>,
+ <&topcrm ZX297520V3_UPLL_D16>,
+ <&topcrm ZX297520V3_DPLL>, <&topcrm ZX297520V3_DPLL_D2>,
+ <&topcrm ZX297520V3_DPLL_D3>, <&topcrm ZX297520V3_DPLL_D4>,
+ <&topcrm ZX297520V3_DPLL_D5>, <&topcrm ZX297520V3_DPLL_D6>,
+ <&topcrm ZX297520V3_DPLL_D8>, <&topcrm ZX297520V3_DPLL_D12>,
+ <&topcrm ZX297520V3_DPLL_D16>,
+ <&topcrm ZX297520V3_GPLL>, <&topcrm ZX297520V3_GPLL_D2>,
+ <&topcrm ZX297520V3_GPLL_D3>, <&topcrm ZX297520V3_GPLL_D4>,
+ <&topcrm ZX297520V3_GPLL_D5>, <&topcrm ZX297520V3_GPLL_D6>,
+ <&topcrm ZX297520V3_GPLL_D8>, <&topcrm ZX297520V3_GPLL_D12>,
+ <&topcrm ZX297520V3_GPLL_D16>;
+ clock-names = "osc26m", "osc32k", "mpll", "mpll_d2", "mpll_d3", "mpll_d4",
+ "mpll_d5", "mpll_d6", "mpll_d8", "mpll_d12", "mpll_d16",
+ "mpll_d26", "upll", "upll_d2", "upll_d3", "upll_d4",
+ "upll_d5", "upll_d6", "upll_d8", "upll_d12", "upll_d16",
+ "dpll", "dpll_d2", "dpll_d3", "dpll_d4", "dpll_d5", "dpll_d6",
+ "dpll_d8", "dpll_d12", "dpll_d16", "gpll", "gpll_d2",
+ "gpll_d3", "gpll_d4", "gpll_d5", "gpll_d6", "gpll_d8",
+ "gpll_d12", "gpll_d16";
+ #clock-cells = <1>;
+ #hwlock-cells = <1>;
+ #reset-cells = <1>;
+ };
+
+ lspcrm: clock-controller@1400000 {
+ compatible = "zte,zx297520v3-lspcrm";
+ reg = <0x01400000 0x100>;
+ #clock-cells = <1>;
+ #reset-cells = <1>;
+
+ clocks = <&matrixcrm ZX297520V3_LSP_MPLL_D5_WCLK>,
+ <&matrixcrm ZX297520V3_LSP_MPLL_D4_WCLK>,
+ <&matrixcrm ZX297520V3_LSP_MPLL_D6_WCLK>,
+ <&matrixcrm ZX297520V3_LSP_MPLL_D8_WCLK>,
+ <&matrixcrm ZX297520V3_LSP_MPLL_D12_WCLK>,
+ <&matrixcrm ZX297520V3_LSP_OSC26M_WCLK>,
+ <&matrixcrm ZX297520V3_LSP_OSC32K_WCLK>,
+ <&matrixcrm ZX297520V3_LSP_PCLK>,
+ <&matrixcrm ZX297520V3_LSP_TDM_WCLK>,
+ <&matrixcrm ZX297520V3_LSP_DPLL_D4_WCLK>;
+ clock-names = "mpll_d5", "mpll_d4", "mpll_d6", "mpll_d8", "mpll_d12",
+ "osc26m", "osc32k", "pclk", "tdm_wclk", "dpll_d4";
+ };
+
+
uart0: serial@131000 {
compatible = "arm,pl011", "arm,primecell";
arm,primecell-periphid = <0x0018c011>;
reg = <0x00131000 0x1000>;
interrupts = <GIC_SPI 0 IRQ_TYPE_LEVEL_HIGH>;
- clocks = <&uartclk>, <&uartclk>;
+ clocks = <&topcrm ZX297520V3_UART0_WCLK>, <&topcrm ZX297520V3_UART0_PCLK>;
clock-names = "uartclk", "apb_pclk";
+ resets = <&topcrm ZX297520V3_UART0_RESET>;
status = "disabled";
};
@@ -85,8 +164,9 @@ uart1: serial@1408000 {
arm,primecell-periphid = <0x0018c011>;
reg = <0x01408000 0x1000>;
interrupts = <GIC_SPI 1 IRQ_TYPE_LEVEL_HIGH>;
- clocks = <&uartclk>, <&uartclk>;
+ clocks = <&lspcrm ZX297520V3_UART1_WCLK>, <&lspcrm ZX297520V3_UART1_PCLK>;
clock-names = "uartclk", "apb_pclk";
+ resets = <&lspcrm ZX297520V3_UART1_RESET>;
status = "disabled";
};
@@ -95,8 +175,9 @@ uart2: serial@140d000 {
arm,primecell-periphid = <0x0018c011>;
reg = <0x0140d000 0x1000>;
interrupts = <GIC_SPI 2 IRQ_TYPE_LEVEL_HIGH>;
- clocks = <&uartclk>, <&uartclk>;
+ clocks = <&lspcrm ZX297520V3_UART2_WCLK>, <&lspcrm ZX297520V3_UART2_PCLK>;
clock-names = "uartclk", "apb_pclk";
+ resets = <&lspcrm ZX297520V3_UART2_RESET>;
status = "disabled";
};
};
--
2.53.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* Re: [PATCH RFC v5 02/12] dt-bindings: soc: zte: Add zx297520v3 matrix clock and reset bindings
2026-06-28 19:58 ` [PATCH RFC v5 02/12] dt-bindings: soc: zte: Add zx297520v3 matrix " Stefan Dösinger
@ 2026-06-28 20:09 ` sashiko-bot
0 siblings, 0 replies; 23+ messages in thread
From: sashiko-bot @ 2026-06-28 20:09 UTC (permalink / raw)
To: Stefan Dösinger; +Cc: robh, conor+dt, devicetree
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] The dt-bindings headers for the new matrixcrm clocks and resets reuse the exact same indices and generic prefix (`ZX297520V3_`) as the existing topcrm clocks and resets, creating a risk of silent device tree misconfigurations.
--
commit 5a96f2683fa1c9255c9f0d3649cfb566a763da11
Author: Stefan Dösinger <stefandoesinger@gmail.com>
dt-bindings: soc: zte: Add zx297520v3 matrix clock and reset bindings
This adds the device tree binding schema for the ZTE zx297520v3 matrix clock
and reset controller. It describes the clock inputs, subdivisions, and hardware
spinlocks. It also appends the new clock and reset indices to the corresponding
dt-binding headers.
> diff --git a/include/dt-bindings/clock/zte,zx297520v3-clk.h b/include/dt-bindings/clock/zte,zx297520v3-clk.h
> index de1c08b6a5a9f..8a6aa456a7089 100644
> --- a/include/dt-bindings/clock/zte,zx297520v3-clk.h
> +++ b/include/dt-bindings/clock/zte,zx297520v3-clk.h
[ ... ]
> @@ -94,4 +94,41 @@
> #define ZX297520V3_HSIC_WCLK 86
> #define ZX297520V3_HSIC_PCLK 87
>
> +#define ZX297520V3_CPU_WCLK 1
> +#define ZX297520V3_CPU_PCLK 2
[Severity: Low]
Is there a risk of silent device tree misconfigurations by reusing the exact
same generic ZX297520V3_ prefix for these new matrixcrm clocks?
Since they restart the index values at 1, they overlap with the existing topcrm
clock macros. If a device tree author mistakenly uses a phandle to topcrm with
one of these matrixcrm macros like <&topcrm ZX297520V3_CPU_WCLK>, it will
silently compile to <&topcrm 1>, which configures a completely different
hardware clock at runtime without any warning.
Would it be safer to use a distinct prefix like ZX297520V3_MATRIX_ for these
new macros to prevent mixing them up with the topcrm ones?
[ ... ]
> diff --git a/include/dt-bindings/reset/zte,zx297520v3-reset.h b/include/dt-bindings/reset/zte,zx297520v3-reset.h
> index 43db72bb59de0..81ffc8bc34c5c 100644
> --- a/include/dt-bindings/reset/zte,zx297520v3-reset.h
> +++ b/include/dt-bindings/reset/zte,zx297520v3-reset.h
[ ... ]
> @@ -29,4 +29,14 @@
> #define ZX297520V3_HSIC_PHY_RESET 20
> #define ZX297520V3_HSIC_RESET 21
>
> +#define ZX297520V3_CPU_RESET 0
> +#define ZX297520V3_EDCP_RESET 1
[Severity: Low]
Does this have the same overlapping index issue as the clocks above?
These resets also use the generic ZX297520V3_ prefix while starting over at
index 0, creating a similar risk where a device tree author might accidentally
mix macros from different reset controllers without getting any compiler
warnings.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260628-zx29clk-v5-0-79ff044e4192@gmail.com?part=2
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH RFC v5 05/12] clk: zte: Add Clock registration infrastructure.
2026-06-28 19:59 ` [PATCH RFC v5 05/12] clk: zte: Add Clock registration infrastructure Stefan Dösinger
@ 2026-06-28 20:10 ` sashiko-bot
0 siblings, 0 replies; 23+ messages in thread
From: sashiko-bot @ 2026-06-28 20:10 UTC (permalink / raw)
To: Stefan Dösinger; +Cc: robh, devicetree, conor+dt
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Double free of the auxiliary device structure on the `auxiliary_device_add()` error path.
- [Medium] Passing a valid pointer to `dev_err_probe` instead of the actual error code on `clk_prepare_enable()` failure.
--
commit c7ef137ca7f89acf9208fa4b616fde3c0b54b8fc
Author: Stefan Dösinger <stefandoesinger@gmail.com>
clk: zte: Add Clock registration infrastructure.
This patch adds common clock registration infrastructure for ZTE SoCs,
implementing the registration and probe routines for various clock types
and preparing for the regmap clocks and PLL drivers.
> diff --git a/drivers/clk/zte/clk-zx.c b/drivers/clk/zte/clk-zx.c
> --- /dev/null
> +++ b/drivers/clk/zte/clk-zx.c
[ ... ]
> +int zx_clk_common_probe(struct device *dev, struct device_node *of_node,
> + const struct zx_clk_data *data)
> +{
[ ... ]
> + res = clk_prepare_enable(clk);
> + if (res) {
> + clk_put(clk);
> + return dev_err_probe(dev, PTR_ERR(clk), "Input clk %s enable failure\n",
> + data->inputs_enable[i]);
> + }
[Severity: Medium]
Should this pass the actual error code res to dev_err_probe instead of
PTR_ERR(clk)?
Since clk is a valid pointer here (having successfully returned from
of_clk_get_by_name earlier), PTR_ERR(clk) will return a casted kernel address
rather than the actual error code.
[ ... ]
> + res = auxiliary_device_add(adev);
> + if (res) {
> + dev_err_probe(dev, res, "Failed to add aux dev %s\n", adev->name);
> + goto adev_uninit;
> + }
> +
> + return devm_add_action_or_reset(dev, zx_adev_unregister, adev);
> +
> +adev_uninit:
> + auxiliary_device_uninit(adev);
> +adev_free:
> + kfree(adev);
> + return res;
> +}
[Severity: High]
Will this result in a double free if auxiliary_device_add fails?
When jumping to adev_uninit, auxiliary_device_uninit drops the reference
count to 0, which triggers the zx_adev_release callback:
static void zx_adev_release(struct device *dev)
{
struct auxiliary_device *adev = to_auxiliary_dev(dev);
kfree(adev);
}
The code then falls through to the adev_free label and explicitly calls
kfree on the adev pointer again.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260628-zx29clk-v5-0-79ff044e4192@gmail.com?part=5
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH RFC v5 04/12] mfd: zx297520v3: Add a clock and reset MFD driver.
2026-06-28 19:58 ` [PATCH RFC v5 04/12] mfd: zx297520v3: Add a clock and reset MFD driver Stefan Dösinger
@ 2026-06-28 20:10 ` sashiko-bot
0 siblings, 0 replies; 23+ messages in thread
From: sashiko-bot @ 2026-06-28 20:10 UTC (permalink / raw)
To: Stefan Dösinger; +Cc: conor+dt, devicetree, robh
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Missing NULL check for the return value of of_device_get_match_data() leads to a NULL pointer dereference if the platform device is instantiated manually via sysfs.
- [Low] ZTE_ZX297520V3_CRM selects MFD_CORE without inheriting its dependency on HAS_IOMEM, causing Kconfig unmet dependency warnings on architectures like UML.
--
commit 495318b62d6f1914184cf6bb01fdd361eef66cc1
Author: Stefan Dösinger <stefandoesinger@gmail.com>
mfd: zx297520v3: Add a clock and reset MFD driver.
This commit adds a new MFD driver for the ZTE zx297520v3 clock and reset
controllers. It registers the child devices required to drive these
functionalities.
> diff --git a/drivers/soc/zte/Kconfig b/drivers/soc/zte/Kconfig
> new file mode 100644
> index 0000000000000..1016fb99cf734
> --- /dev/null
> +++ b/drivers/soc/zte/Kconfig
> @@ -0,0 +1,20 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +
> +if ARCH_ZTE || COMPILE_TEST
> +
> +menu "ZTE SoC drivers"
> +
> +config ZTE_ZX297520V3_CRM
> + tristate "ZTE zx297520v3 Clock and Reset Manager"
> + select MFD_CORE
[Severity: Low]
Will this select statement cause a Kconfig unmet dependency warning on
architectures without HAS_IOMEM (like UML)?
Since MFD_CORE depends on HAS_IOMEM, and this driver can be built with
COMPILE_TEST, it seems this config should also inherit the HAS_IOMEM
dependency to prevent silent build configuration issues.
> + default SOC_ZX297520V3
> + help
> + Say yes here to enable the driver for the ZTE zx297520v3 clock and
[ ... ]
> diff --git a/drivers/soc/zte/zx297520v3-crm.c b/drivers/soc/zte/zx297520v3-crm.c
> new file mode 100644
> index 0000000000000..bf5a71bc7706b
> --- /dev/null
> +++ b/drivers/soc/zte/zx297520v3-crm.c
[ ... ]
> +static int zx297520v3_crm_probe(struct platform_device *pdev)
> +{
> + const struct zx297520v3_crm_data *data;
> +
> + data = of_device_get_match_data(&pdev->dev);
> + return devm_mfd_add_devices(&pdev->dev, PLATFORM_DEVID_NONE, data->cells,
[Severity: High]
Can data be NULL here?
If a privileged user instantiates this platform device manually via sysfs
(for instance, by writing to driver_override and binding it), there will be
no OF node. In that case, of_device_get_match_data() returns NULL, which
would lead to a NULL pointer dereference when accessing data->cells.
Does the return value of of_device_get_match_data() need to be checked?
> + data->num_cells, NULL, 0, NULL);
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260628-zx29clk-v5-0-79ff044e4192@gmail.com?part=4
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH RFC v5 09/12] clk: zte: Introduce a driver for zx297520v3 matrix clocks
2026-06-28 19:59 ` [PATCH RFC v5 09/12] clk: zte: Introduce a driver for zx297520v3 matrix clocks Stefan Dösinger
@ 2026-06-28 20:12 ` sashiko-bot
0 siblings, 0 replies; 23+ messages in thread
From: sashiko-bot @ 2026-06-28 20:12 UTC (permalink / raw)
To: Stefan Dösinger; +Cc: conor+dt, devicetree, robh
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] Double free of the `adev` structure in the error handling path of `auxiliary_device_add()`.
--
commit a1d3be99b150f10999dcacbb8abe5f5789b34aee
Author: Stefan Dösinger <stefandoesinger@gmail.com>
clk: zte: Introduce a driver for zx297520v3 matrix clocks
This commit introduces a clock driver for the zx297520v3 matrix clocks.
It registers several multiplexers, gates, and inputs by utilizing the
shared zx_clk_common_probe() initialization routine.
> diff --git a/drivers/clk/zte/clk-zx297520v3.c b/drivers/clk/zte/clk-zx297520v3.c
> index bb3d64eff9096..17e1f741b48c9 100644
> --- a/drivers/clk/zte/clk-zx297520v3.c
> +++ b/drivers/clk/zte/clk-zx297520v3.c
[ ... ]
> +static int zx297520v3_matrixclk_probe(struct platform_device *pdev)
> +{
> + return zx_clk_common_probe(&pdev->dev, pdev->dev.parent->of_node,
> + &zx297520v3_matrixclk_data);
> +}
[Severity: High]
This is a pre-existing issue, but does this error path result in a double
free if auxiliary_device_add() fails during probe?
Looking at zx_clk_common_probe() in drivers/clk/zte/clk-zx.c, the error
path handles a failure from auxiliary_device_add() by jumping to
adev_uninit:
drivers/clk/zte/clk-zx.c:zx_clk_common_probe() {
...
res = auxiliary_device_add(adev);
if (res) {
dev_err_probe(dev, res, "Failed to add aux dev %s\n", adev->name);
goto adev_uninit;
}
return devm_add_action_or_reset(dev, zx_adev_unregister, adev);
adev_uninit:
auxiliary_device_uninit(adev);
adev_free:
kfree(adev);
return res;
}
When auxiliary_device_uninit(adev) is called, it drops the reference count,
which triggers the assigned release callback zx_adev_release(). This
callback legitimately frees the structure:
drivers/clk/zte/clk-zx.c:zx_adev_release() {
struct auxiliary_device *adev = to_auxiliary_dev(dev);
kfree(adev);
}
However, after returning from auxiliary_device_uninit(), the error path
unconditionally falls through to the adev_free label, calling kfree(adev)
a second time.
Since the structure is automatically freed in the release callback, is it safe
to fall through to kfree(adev) here, or should the manual kfree be removed?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260628-zx29clk-v5-0-79ff044e4192@gmail.com?part=9
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH RFC v5 01/12] dt-bindings: soc: zte: Add zx297520v3 top clock and reset bindings
2026-06-28 19:58 ` [PATCH RFC v5 01/12] dt-bindings: soc: zte: Add zx297520v3 top clock and reset bindings Stefan Dösinger
@ 2026-06-28 20:12 ` sashiko-bot
0 siblings, 0 replies; 23+ messages in thread
From: sashiko-bot @ 2026-06-28 20:12 UTC (permalink / raw)
To: Stefan Dösinger; +Cc: conor+dt, devicetree, robh
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] The `syscon-reboot` node example omits the `value` property and uses `mask` alone, triggering a deprecated legacy fallback in the driver that overwrites the entire register.
- [Low] The child node is explicitly named `syscon-reboot`, violating the Devicetree Specification for generic node names.
--
commit fc119f64e363e1f9ec8efce39f051382b3c78be5
Author: Stefan Dösinger <stefandoesinger@gmail.com>
dt-bindings: soc: zte: Add zx297520v3 top clock and reset bindings
This commit introduces device tree bindings for the Zte zx297520v3 SoC top
clock and reset controller. It defines the yaml schema for the controller,
including clocks, resets, and a syscon-reboot child node. It also adds the
corresponding clock and reset header definitions.
> diff --git a/Documentation/devicetree/bindings/soc/zte/zte,zx297520v3-topcrm.yaml b/Documentation/devicetree/bindings/soc/zte/zte,zx297520v3-topcrm.yaml
> new file mode 100644
> index 0000000000000..6baab5c98d25e
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/soc/zte/zte,zx297520v3-topcrm.yaml
> @@ -0,0 +1,85 @@
[ ... ]
> + "#reset-cells":
> + const: 1
> +
> + syscon-reboot:
[Severity: Low]
Does this child node name violate the Devicetree Specification for generic
node names? By explicitly restricting the node name to syscon-reboot,
validating a device tree that uses the standard generic name reboot will
fail since additional properties are not allowed. Could this be renamed to
match the generic class of the device?
> + type: object
> + $ref: /schemas/power/reset/syscon-reboot.yaml#
> + description:
> + Reboot method for the SoC.
> +
> +required:
[ ... ]
> + #clock-cells = <1>;
> + #reset-cells = <1>;
> +
> + syscon-reboot {
> + compatible = "syscon-reboot";
> + offset = <0x0>;
> + mask = <0x1>;
[Severity: High]
Is it intentional to omit the value property in this example?
In drivers/power/reset/syscon-reboot.c, if the value property is omitted,
the driver interprets mask as the legacy value and forces the actual update
mask to 0xffffffff. When a reboot is requested, it executes
regmap_update_bits(map, 0x0, 0xffffffff, 0x1), which clears all bits except
bit 0.
Since the topcrm register at offset 0x0 controls core clocks and resets,
would overwriting the entire register hang the system ungracefully before
the reboot can proceed? Adding the value property would ensure the mask
operates as intended.
> + };
> + };
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260628-zx29clk-v5-0-79ff044e4192@gmail.com?part=1
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH RFC v5 06/12] clk: zte: Add zx PLL support infrastructure
2026-06-28 19:59 ` [PATCH RFC v5 06/12] clk: zte: Add zx PLL support infrastructure Stefan Dösinger
@ 2026-06-28 20:14 ` sashiko-bot
0 siblings, 0 replies; 23+ messages in thread
From: sashiko-bot @ 2026-06-28 20:14 UTC (permalink / raw)
To: Stefan Dösinger; +Cc: robh, devicetree, conor+dt
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] Calling `clk_hw_get_rate(clk_hw_get_parent(&pll->hw))` inside `zx29_pll_determine_rate` will cause a NULL pointer dereference if the clock is an orphan.
- [Medium] `zx29_pll_determine_rate` reads the currently committed hardware parent rate instead of using the CCF transaction state, breaking rate propagation.
- [Low] Using `abs(out - rate)` with an `unsigned long` rate causes a compilation error and logic failure due to unsigned integer promotion.
--
commit f9e2c9def84f461469ea25564325f1d1985e1a5f
Author: Stefan Dösinger <stefandoesinger@gmail.com>
clk: zte: Add zx PLL support infrastructure
I am guessing how much of this is reusable among other zx chips or even
differently named ZTE platforms (if there are any). From reading the old
zx2967 code, I think the PLL code would be reusable there, maybe with
platform specific bitmasks but otherwise the same logic.
> diff --git a/drivers/clk/zte/pll-zx.c b/drivers/clk/zte/pll-zx.c
> index c0475d5441fbc..1482caeea9995 100644
> --- a/drivers/clk/zte/pll-zx.c
> +++ b/drivers/clk/zte/pll-zx.c
[ ... ]
> +static u32 zx29_pll_calc_values(const struct zx29_clk_pll *pll, unsigned long parent_rate,
> + unsigned long rate)
> +{
[ ... ]
> + setting |= i << ZX29_PLL_POSTDIV1_SHIFT;
> + setting |= j << ZX29_PLL_POSTDIV2_SHIFT;
> + out = zx29_pll_get_rate(pll, parent_rate, setting);
> +
> + if (abs(out - rate) > best)
[Severity: Low]
Could this fail to compile or produce a logic error due to unsigned integer
promotion?
The abs() macro in the kernel evaluates to ((void)0) when passed an
unsigned type like rate, which would result in a build failure.
If it were to compile, the unsigned subtraction out - rate could wrap around
for values below the target rate, causing valid frequencies to be incorrectly
discarded.
> + continue;
> +
> + if (abs(out - rate) < best) {
> + postdiv1 = i;
> + postdiv2 = j;
[ ... ]
> +static int zx29_pll_determine_rate(struct clk_hw *hw, struct clk_rate_request *req)
> +{
> + struct zx29_clk_pll *pll = to_zx29_clk_pll(hw);
> + unsigned long new_rate, parent_rate = clk_hw_get_rate(clk_hw_get_parent(&pll->hw));
[Severity: High]
Is it possible to trigger a NULL pointer dereference here if the clock is an
orphan?
If the parent is not yet registered during system initialization,
clk_hw_get_parent() returns NULL, which clk_hw_get_rate() unconditionally
dereferences.
[Severity: Medium]
Additionally, does reading the currently committed hardware parent rate break
rate propagation?
By calling clk_hw_get_rate() instead of using req->best_parent_rate, the driver
reads the old hardware rate of the parent rather than the proposed transaction
state. This bypasses the Common Clock Framework's two-phase transactional model
and could calculate incorrect hardware dividers when the clock tree is being
reparented or rate-adjusted.
> + u32 setting;
> +
> + setting = zx29_pll_calc_values(pll, parent_rate, req->rate);
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260628-zx29clk-v5-0-79ff044e4192@gmail.com?part=6
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH RFC v5 08/12] clk: zte: Introduce a driver for zx297520v3 top clocks
2026-06-28 19:59 ` [PATCH RFC v5 08/12] clk: zte: Introduce a driver for zx297520v3 top clocks Stefan Dösinger
@ 2026-06-28 20:16 ` sashiko-bot
0 siblings, 0 replies; 23+ messages in thread
From: sashiko-bot @ 2026-06-28 20:16 UTC (permalink / raw)
To: Stefan Dösinger; +Cc: robh, conor+dt, devicetree
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] Double free of `adev` in the error path of `auxiliary_device_add()`.
--
commit 5d8045f8cc5525227ffeb82da9b3791181d56dbb
Author: Stefan Dösinger <stefandoesinger@gmail.com>
clk: zte: Introduce a driver for zx297520v3 top clocks
This commit introduces a clock driver for the ZTE zx297520v3 basic top clocks.
It registers the PLLs, muxes, dividers, and gates for core devices such as
the AHB bus, timers, and the USB controller.
> diff --git a/drivers/clk/zte/clk-zx297520v3.c b/drivers/clk/zte/clk-zx297520v3.c
> new file mode 100644
> index 0000000000000..bb3d64eff9096
> --- /dev/null
> +++ b/drivers/clk/zte/clk-zx297520v3.c
[ ... ]
> +static int zx297520v3_topclk_probe(struct platform_device *pdev)
> +{
> + return zx_clk_common_probe(&pdev->dev, pdev->dev.parent->of_node, &zx297520v3_topclk_data);
[Severity: High]
This isn't a bug introduced by this patch, but calling zx_clk_common_probe()
can trigger a double free in its error handling path.
Looking at zx_clk_common_probe() in drivers/clk/zte/clk-zx.c, if
auxiliary_device_add() fails, it jumps to the following error path:
adev_uninit:
auxiliary_device_uninit(adev);
adev_free:
kfree(adev);
return res;
Since auxiliary_device_uninit() drops the final reference and triggers the
zx_adev_release() callback which frees adev, doesn't the fall-through to
kfree(adev) result in a double free?
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260628-zx29clk-v5-0-79ff044e4192@gmail.com?part=8
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH RFC v5 10/12] clk: zte: Introduce a driver for zx297520v3 LSP clocks and resets
2026-06-28 19:59 ` [PATCH RFC v5 10/12] clk: zte: Introduce a driver for zx297520v3 LSP clocks and resets Stefan Dösinger
@ 2026-06-28 20:18 ` sashiko-bot
0 siblings, 0 replies; 23+ messages in thread
From: sashiko-bot @ 2026-06-28 20:18 UTC (permalink / raw)
To: Stefan Dösinger; +Cc: robh, conor+dt, devicetree
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
New issues:
- [High] Missing NULL check on device_get_match_data() return value.
Pre-existing issues:
- [High] Double-free of 'adev' during the error path of 'auxiliary_device_add()' in 'zx_clk_common_probe()'.
--
commit 8049f2134eca9c93f09de3be7599856e5b1b9d3c
Author: Stefan Dösinger <stefandoesinger@gmail.com>
clk: zte: Introduce a driver for zx297520v3 LSP clocks and resets
This commit introduces a new driver for the ZTE zx297520v3 LSP clocks and
resets. It defines multiplexers, dividers, and gates for the subsystem. It
also registers the clock provider and optionally an auxiliary device for
the resets.
> diff --git a/drivers/clk/zte/clk-zx297520v3.c b/drivers/clk/zte/clk-zx297520v3.c
> index 17e1f741b48c9..f4ab065ee4969 100644
> --- a/drivers/clk/zte/clk-zx297520v3.c
> +++ b/drivers/clk/zte/clk-zx297520v3.c
[ ... ]
> +static const struct zx_clk_data zx297520v3_lsprm_data = {
> + .inputs_enable = zx297520v3_lsp_inputs_enable,
> + .num_inputs_enable = ARRAY_SIZE(zx297520v3_lsp_inputs_enable),
> + .inputs = zx297520v3_lsp_inputs,
> + .num_inputs = ARRAY_SIZE(zx297520v3_lsp_inputs),
> + .muxes = zx297520v3_lsp_muxes,
> + .num_muxes = ARRAY_SIZE(zx297520v3_lsp_muxes),
> + .divs = zx297520v3_lsp_dividers,
> + .num_divs = ARRAY_SIZE(zx297520v3_lsp_dividers),
> + .gates = zx297520v3_lsp_gates,
> + .num_gates = ARRAY_SIZE(zx297520v3_lsp_gates),
> + .reset_auxdev_name = "zx297520v3_lsprst"
> +};
[Severity: High]
This isn't a bug introduced by this patch, but populating reset_auxdev_name
here exposes a double-free bug in the error handling path of
zx_clk_common_probe() in drivers/clk/zte/clk-zx.c.
If auxiliary_device_add() fails during probe, the code jumps to adev_uninit:
drivers/clk/zte/clk-zx.c:zx_clk_common_probe() {
...
adev_uninit:
auxiliary_device_uninit(adev);
adev_free:
kfree(adev);
return res;
}
The call to auxiliary_device_uninit() triggers the release callback,
zx_adev_release(), which frees adev. Execution then falls through to adev_free
and calls kfree(adev) a second time.
Could this lead to memory corruption or a kernel panic if probe fails?
[ ... ]
> +static int zx297520v3_lspclk_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> +
> + return zx_clk_common_probe(dev, dev->of_node, device_get_match_data(dev));
> +}
[Severity: High]
Could device_get_match_data() return NULL here and cause a NULL pointer
dereference?
If a user binds the driver via sysfs (using driver_override and bind) to a
device that does not match the driver's of_match_table, device_get_match_data()
will return NULL.
Since zx_clk_common_probe() assumes the data pointer is valid and dereferences
it unconditionally once device_node_to_regmap() succeeds (e.g., data->num_plls),
would this lead to a kernel panic?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260628-zx29clk-v5-0-79ff044e4192@gmail.com?part=10
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH RFC v5 11/12] reset: zte: Add a zx297520v3 reset driver
2026-06-28 19:59 ` [PATCH RFC v5 11/12] reset: zte: Add a zx297520v3 reset driver Stefan Dösinger
@ 2026-06-28 20:23 ` sashiko-bot
0 siblings, 0 replies; 23+ messages in thread
From: sashiko-bot @ 2026-06-28 20:23 UTC (permalink / raw)
To: Stefan Dösinger; +Cc: robh, conor+dt, devicetree
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Medium] Missing `MODULE_ALIAS` for the platform drivers prevents module autoloading if the auxiliary device is not present.
- [Medium] The Kconfig entry `RESET_ZTE_ZX297520V3` fails to `select MFD_SYSCON` or `depends on MFD_SYSCON`.
- [Low] The driver is missing the `#include <linux/module.h>` header, leading to a build failure.
--
commit 2568a484547168ba23bd6f47affd8cc3aa186393
Author: Stefan Dösinger <stefandoesinger@gmail.com>
reset: zte: Add a zx297520v3 reset driver
This commit adds a reset driver for ZTE zx297520v3 SoCs. It registers two
platform drivers and one auxiliary driver to handle resets across different
hardware blocks.
> diff --git a/drivers/reset/Kconfig b/drivers/reset/Kconfig
> index d009eb0849a3b..116dd23f1b8e3 100644
> --- a/drivers/reset/Kconfig
> +++ b/drivers/reset/Kconfig
[ ... ]
> @@ -404,6 +404,17 @@ config RESET_UNIPHIER_GLUE
> on UniPhier SoCs. Say Y if you want to control reset signals
> provided by the glue layer.
>
> +config RESET_ZTE_ZX297520V3
> + tristate "ZTE zx297520v3 Reset Driver"
> + depends on (ARCH_ZTE || COMPILE_TEST)
> + default CLK_ZTE_ZX297520V3
> + select AUXILIARY_BUS
[Severity: Medium]
Since this driver uses device_node_to_regmap() to acquire the parent syscon
regmap, should this config also select or depend on MFD_SYSCON?
If MFD_SYSCON is disabled, device_node_to_regmap() acts as a stub returning
-ENOTSUPP, which will cause the reset driver to unconditionally fail to probe.
> + help
> + This enables the reset controller for ZTE zx297520v3 SoCs. The reset
> + controller is part of the clock controller on this SoC. This driver
> + operates on an auxiliary device exposed by the clock driver. Enable
> + this driver if you plan to boot the kernel on a zx297520v3 based SoC.
> +
> config RESET_ZYNQ
> bool "ZYNQ Reset Driver" if COMPILE_TEST
> default ARCH_ZYNQ
> diff --git a/drivers/reset/reset-zte-zx297520v3.c b/drivers/reset/reset-zte-zx297520v3.c
> new file mode 100644
> index 0000000000000..8ef4349042308
> --- /dev/null
> +++ b/drivers/reset/reset-zte-zx297520v3.c
[ ... ]
> +static struct platform_driver * const reset_zx297520v3_mfddrv[] = {
> + &reset_zx297520v3_top,
> + &reset_zx297520v3_matrix,
> +};
[Severity: Medium]
Since these platform drivers do not have an .of_match_table and match purely by
name, do they need explicit MODULE_ALIAS declarations (such as
MODULE_ALIAS("platform:zx297520v3-toprst")) for proper udev module autoloading?
Without these aliases, the module might only autoload when the auxiliary device
appears, creating a dependency on the LSP clock driver being loaded first.
[ ... ]
> +static void __exit reset_zx297520v3_exit(void)
> +{
> + platform_unregister_drivers(reset_zx297520v3_mfddrv,
> + ARRAY_SIZE(reset_zx297520v3_mfddrv));
> + auxiliary_driver_unregister(&reset_zx297520v3_auxdrv);
> +}
> +
> +module_init(reset_zx297520v3_init);
> +module_exit(reset_zx297520v3_exit);
[Severity: Low]
Does this file need to include <linux/module.h>?
Without it, building this as a loadable module could fail because macros like
module_init, module_exit, MODULE_AUTHOR, and MODULE_DESCRIPTION are not
provided by the other included headers.
> +
> +MODULE_AUTHOR("Stefan Dösinger <stefandoesinger@gmail.com>");
> +MODULE_DESCRIPTION("ZTE zx297520v3 reset driver");
> +MODULE_LICENSE("GPL");
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260628-zx29clk-v5-0-79ff044e4192@gmail.com?part=11
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH RFC v5 07/12] clk: zte: Add regmap based clocks
2026-06-28 19:59 ` [PATCH RFC v5 07/12] clk: zte: Add regmap based clocks Stefan Dösinger
@ 2026-06-28 20:28 ` sashiko-bot
0 siblings, 0 replies; 23+ messages in thread
From: sashiko-bot @ 2026-06-28 20:28 UTC (permalink / raw)
To: Stefan Dösinger; +Cc: conor+dt, devicetree, robh
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] Trailing semicolon at the end of the `zte_clk_regmap_div_set_rate` function definition.
--
commit 4685e6891643c422560d2af4689a1b287aa1ac18
Author: Stefan Dösinger <stefandoesinger@gmail.com>
clk: zte: Add regmap based clocks
This commit introduces regmap-based gate, divider, and mux clock
implementations for ZTE devices. It adds helper functions to register
these clocks using regmap structures and descriptions.
> diff --git a/drivers/clk/zte/clk-regmap.c b/drivers/clk/zte/clk-regmap.c
> index 7908f1562f639..903998ca95082 100644
> --- a/drivers/clk/zte/clk-regmap.c
> +++ b/drivers/clk/zte/clk-regmap.c
[ ... ]
> +static int zte_clk_regmap_div_set_rate(struct clk_hw *hw, unsigned long rate,
> + unsigned long parent_rate)
> +{
> + struct zte_clk_regmap *clk = to_zte_clk_regmap(hw);
> + unsigned int val;
> + int ret;
> +
> + ret = divider_get_val(rate, parent_rate, NULL, clk->size, 0);
> + if (ret < 0)
> + return ret;
> +
> + val = (unsigned int)ret << clk->shift;
> + return regmap_update_bits(clk->map, clk->reg, clk_div_mask(clk->size) << clk->shift, val);
> +};
[Severity: Low]
This isn't a bug, but there is an unnecessary trailing semicolon at the end of
this function definition.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260628-zx29clk-v5-0-79ff044e4192@gmail.com?part=7
^ permalink raw reply [flat|nested] 23+ messages in thread
end of thread, other threads:[~2026-06-28 20:28 UTC | newest]
Thread overview: 23+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-28 19:58 [PATCH RFC v5 00/12] ZTE zx297520v3 clock bindings and driver Stefan Dösinger
2026-06-28 19:58 ` [PATCH RFC v5 01/12] dt-bindings: soc: zte: Add zx297520v3 top clock and reset bindings Stefan Dösinger
2026-06-28 20:12 ` sashiko-bot
2026-06-28 19:58 ` [PATCH RFC v5 02/12] dt-bindings: soc: zte: Add zx297520v3 matrix " Stefan Dösinger
2026-06-28 20:09 ` sashiko-bot
2026-06-28 19:58 ` [PATCH RFC v5 03/12] dt-bindings: clk: zte: Add zx297520v3 LSP " Stefan Dösinger
2026-06-28 19:58 ` [PATCH RFC v5 04/12] mfd: zx297520v3: Add a clock and reset MFD driver Stefan Dösinger
2026-06-28 20:10 ` sashiko-bot
2026-06-28 19:59 ` [PATCH RFC v5 05/12] clk: zte: Add Clock registration infrastructure Stefan Dösinger
2026-06-28 20:10 ` sashiko-bot
2026-06-28 19:59 ` [PATCH RFC v5 06/12] clk: zte: Add zx PLL support infrastructure Stefan Dösinger
2026-06-28 20:14 ` sashiko-bot
2026-06-28 19:59 ` [PATCH RFC v5 07/12] clk: zte: Add regmap based clocks Stefan Dösinger
2026-06-28 20:28 ` sashiko-bot
2026-06-28 19:59 ` [PATCH RFC v5 08/12] clk: zte: Introduce a driver for zx297520v3 top clocks Stefan Dösinger
2026-06-28 20:16 ` sashiko-bot
2026-06-28 19:59 ` [PATCH RFC v5 09/12] clk: zte: Introduce a driver for zx297520v3 matrix clocks Stefan Dösinger
2026-06-28 20:12 ` sashiko-bot
2026-06-28 19:59 ` [PATCH RFC v5 10/12] clk: zte: Introduce a driver for zx297520v3 LSP clocks and resets Stefan Dösinger
2026-06-28 20:18 ` sashiko-bot
2026-06-28 19:59 ` [PATCH RFC v5 11/12] reset: zte: Add a zx297520v3 reset driver Stefan Dösinger
2026-06-28 20:23 ` sashiko-bot
2026-06-28 19:59 ` [PATCH RFC v5 12/12] ARM: dts: zte: Declare zx297520v3 CRM device nodes Stefan Dösinger
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox