Devicetree
 help / color / mirror / Atom feed
* [PATCH v7 00/13] ZTE zx297520v3 clock bindings and driver
@ 2026-07-16 21:35 Stefan Dösinger
  2026-07-16 21:35 ` [PATCH v7 01/13] dt-bindings: phy: Add zx297520v3 USB phy documentation Stefan Dösinger
                   ` (9 more replies)
  0 siblings, 10 replies; 17+ messages in thread
From: Stefan Dösinger @ 2026-07-16 21:35 UTC (permalink / raw)
  To: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Philipp Zabel, Brian Masney, Vinod Koul,
	Neil Armstrong, Russell King
  Cc: linux-clk, devicetree, linux-kernel, linux-arm-kernel, linux-phy,
	Stefan Dösinger

Hi,

I am sending version 7 of my zx297520v3 clock patch. The main change vs 
v6 is the reverse engineering of the PLL input and forward gates in 
top+0x140 and according reduction of the input clocks in the matrix 
binding.

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 v7:

*) Moved DT bindings back to clock/ (Krzysztof)
*) Added a usb-phy child node to top to expose the USB status register
*) Add clock gates that control input of PLL clocks into topcrm and 
forward to matrixcrm. Strip down matrixcrm inputs in the bindings to the 
known clocks in this register
*) Add DDR clocks and resets, arm arch timer clock. They are marked 
critical and tell CCF not to gate off the aforementioned input/forward 
gates
*) Remove syscon from matrixcrm (Krzysztof). The DDR mailbox system is 
on a different controller (soc_sys at 0x140000). ZTE's code merely 
controlled clocks in matrixcrm, which doesn't require syscon on this 
controller.
*) Consistenly use double quotes in bindings and full include paths 
(Krzysztof)

The individual patches call out changes in more detail.

- Link to v6: https://lore.kernel.org/r/20260702-zx29clk-v6-0-377b704f80c4@gmail.com

Changes in v6:
*) Use MFD for all 3 controllers - I hope both Conor and Philipp will 
agree. I kept top and matrix bindings in soc/zte and lsp in clock/ 
though.

*) Clean up issues found by Sashiko. I pointed them out in the individual 
patches. They are localized fixes and don't affect the overall design

*)small code consistency: Changed "zx297520v3_lsp" to use "-" , "rst" in 
driver names to "reset"

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 (13):
      dt-bindings: phy: Add zx297520v3 USB phy documentation
      dt-bindings: clk: zte: Add zx297520v3 top clock and reset controller
      dt-bindings: clk: zte: Add zx297520v3 matrix clock and reset controller
      dt-bindings: clk: zte: Add zx297520v3 LSP clock and reset controller
      mfd: zx297520v3: Add a clock and reset MFD driver
      clk: zte: Add Clock registration infrastructure
      clk: zte: Add regmap based clocks
      clk: zte: Add zx PLL support infrastructure
      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
      reset: zte: Add a zx297520v3 reset driver
      ARM: dts: zte: Declare zx297520v3 CRM device nodes

 .../bindings/clock/zte,zx297520v3-lspcrm.yaml      |  101 ++
 .../bindings/clock/zte,zx297520v3-matrixcrm.yaml   |   91 ++
 .../bindings/clock/zte,zx297520v3-topcrm.yaml      |  123 +++
 .../bindings/phy/zte,zx297520v3-usb-phy.yaml       |   96 ++
 MAINTAINERS                                        |   10 +
 arch/arm/boot/dts/zte/zx297520v3.dtsi              |   92 +-
 drivers/clk/Kconfig                                |    1 +
 drivers/clk/Makefile                               |    1 +
 drivers/clk/zte/Kconfig                            |   27 +
 drivers/clk/zte/Makefile                           |    6 +
 drivers/clk/zte/clk-regmap.c                       |  246 +++++
 drivers/clk/zte/clk-zx.c                           |  155 +++
 drivers/clk/zte/clk-zx.h                           |   90 ++
 drivers/clk/zte/clk-zx297520v3.c                   | 1045 ++++++++++++++++++++
 drivers/clk/zte/pll-zx.c                           |  520 ++++++++++
 drivers/reset/Kconfig                              |   10 +
 drivers/reset/Makefile                             |    1 +
 drivers/reset/reset-zte-zx297520v3.c               |  237 +++++
 drivers/soc/Kconfig                                |    1 +
 drivers/soc/Makefile                               |    1 +
 drivers/soc/zte/Kconfig                            |   20 +
 drivers/soc/zte/Makefile                           |    3 +
 drivers/soc/zte/zx297520v3-crm.c                   |   99 ++
 include/dt-bindings/clock/zte,zx297520v3-clk.h     |  144 +++
 include/dt-bindings/phy/phy-zte-zx297520v3-usb.h   |   12 +
 include/dt-bindings/reset/zte,zx297520v3-reset.h   |   63 ++
 26 files changed, 3187 insertions(+), 8 deletions(-)
---
base-commit: bee763d5f341b99cf472afeb508d4988f62a6ca1
change-id: 20260510-zx29clk-2e4d39e3128c

Best regards,
-- 
Stefan Dösinger <stefandoesinger@gmail.com>


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH v7 01/13] dt-bindings: phy: Add zx297520v3 USB phy documentation
  2026-07-16 21:35 [PATCH v7 00/13] ZTE zx297520v3 clock bindings and driver Stefan Dösinger
@ 2026-07-16 21:35 ` Stefan Dösinger
  2026-07-16 22:24   ` sashiko-bot
  2026-07-16 21:35 ` [PATCH v7 02/13] dt-bindings: clk: zte: Add zx297520v3 top clock and reset controller Stefan Dösinger
                   ` (8 subsequent siblings)
  9 siblings, 1 reply; 17+ messages in thread
From: Stefan Dösinger @ 2026-07-16 21:35 UTC (permalink / raw)
  To: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Philipp Zabel, Brian Masney, Vinod Koul,
	Neil Armstrong, Russell King
  Cc: linux-clk, devicetree, linux-kernel, linux-arm-kernel, linux-phy,
	Stefan Dösinger

This binding will be used as a subnode of topcrm, which will be added in
the next patch.

Why is this a child node and not #phy-cells added to topcrm itself,
like clocks and resets as writing-bindings.rst suggests? Because
of_phy_provider_register checks if the of_node the PHY provider is added
to belongs to the device or is a child of the device, so I can't put the
PHY driver into an MFD (or AUX) child without its own node.

Signed-off-by: Stefan Dösinger <stefandoesinger@gmail.com>

---

How do I handle examples? It would be identical to the example in
topcrm. The example needs the reset defines from the next patch in
either case.
---
 .../bindings/phy/zte,zx297520v3-usb-phy.yaml       | 96 ++++++++++++++++++++++
 MAINTAINERS                                        |  2 +
 include/dt-bindings/phy/phy-zte-zx297520v3-usb.h   | 12 +++
 3 files changed, 110 insertions(+)

diff --git a/Documentation/devicetree/bindings/phy/zte,zx297520v3-usb-phy.yaml b/Documentation/devicetree/bindings/phy/zte,zx297520v3-usb-phy.yaml
new file mode 100644
index 000000000000..8bad9365d5cc
--- /dev/null
+++ b/Documentation/devicetree/bindings/phy/zte,zx297520v3-usb-phy.yaml
@@ -0,0 +1,96 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/phy/zte,zx297520v3-usb-phy.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: ZTE zx297520v3 USB and HSIC PHY
+
+maintainers:
+  - Stefan Dösinger <stefandoesinger@gmail.com>
+
+description: |
+  This PHY is found on zx297520v3 boards. It has no configurability on its own,
+  but it does require a correct reset and wait sequence to initialize. It can
+  provide interrupt notification when USB is connected and disconnected.
+
+  The phy is a component of the board's topcrm controller. The hardware needs to
+  be declared as a child node of the zte,zx297520v3-topcrm node.
+
+  The register space and IRQs always account for two PHYs: One USB 2.0 OTG phy
+  and a HSIC PHY. Not all boards have both. If one is missing, the corresponding
+  ready flag will never be set.
+
+  The "include/dt-bindings/phy/phy-zte-zx297520v3-usb.h" header contains the
+  definition for the PHY indices.
+
+properties:
+  compatible:
+    const: zte,zx297520v3-usb-phy
+
+  reg:
+    maxItems: 1
+
+  interrupts:
+    items:
+      - description: IRQ reporting USB connection
+      - description: IRQ reporting USB disconnection
+      - description: IRQ reporting HSIC connection
+      - description: IRQ reporting HSIC disconnection
+
+  interrupt-names:
+    items:
+      - const: usb-up
+      - const: usb-down
+      - const: hsic-up
+      - const: hsic-down
+
+  resets:
+    items:
+      - description: USB phy reset
+      - description: HSIC phy reset
+
+  reset-names:
+    items:
+      - const: usb
+      - const: hsic
+
+  "#phy-cells":
+    const: 1
+
+required:
+  - compatible
+  - reg
+  - interrupts
+  - interrupt-names
+  - resets
+  - reset-names
+  - "#phy-cells"
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/arm-gic.h>
+    #include <dt-bindings/phy/phy-zte-zx297520v3-usb.h>
+
+    topcrm: clock-controller {
+        compatible = "zte,zx297520v3-topcrm", "syscon";
+        #address-cells = <1>;
+        #size-cells = <0>;
+        #reset-cells = <1>;
+
+        usb-phy@84 {
+          compatible = "zte,zx297520v3-usb-phy";
+          reg = <0x84>;
+          interrupts = <GIC_SPI 42 IRQ_TYPE_EDGE_RISING>,
+            <GIC_SPI 43 IRQ_TYPE_EDGE_RISING>,
+            <GIC_SPI 45 IRQ_TYPE_EDGE_RISING>,
+            <GIC_SPI 46 IRQ_TYPE_EDGE_RISING>;
+          interrupt-names = "usb-up", "usb-down", "hsic-up", "hsic-down";
+          resets = <&topcrm 18>,
+            <&topcrm 20>;
+          reset-names = "usb", "hsic";
+          #phy-cells = <1>;
+        };
+    };
diff --git a/MAINTAINERS b/MAINTAINERS
index 8729cea57c3d..cb50c2e3b4ea 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3878,8 +3878,10 @@ 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/phy/zte,zx297520v3-usb-phy.yaml
 F:	arch/arm/boot/dts/zte/
 F:	arch/arm/mach-zte/
+F:	include/dt-bindings/phy/phy-zte-zx297520v3-usb.h
 
 ARM/ZYNQ ARCHITECTURE
 M:	Michal Simek <michal.simek@amd.com>
diff --git a/include/dt-bindings/phy/phy-zte-zx297520v3-usb.h b/include/dt-bindings/phy/phy-zte-zx297520v3-usb.h
new file mode 100644
index 000000000000..8a0a3ccbdd63
--- /dev/null
+++ b/include/dt-bindings/phy/phy-zte-zx297520v3-usb.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
+/*
+ * Copyright (C) Stefan Dösinger.
+ */
+
+#ifndef __DT_BINDINGS_PHY_USB_ZX297520V3_H
+#define __DT_BINDINGS_PHY_USB_ZX297520V3_H
+
+#define ZX297520V3_USB_PHY	0
+#define ZX297520V3_HSIC_PHY	1
+
+#endif /* __DT_BINDINGS_PHY_USB_ZX297520V3_H */

-- 
2.54.0


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v7 02/13] dt-bindings: clk: zte: Add zx297520v3 top clock and reset controller
  2026-07-16 21:35 [PATCH v7 00/13] ZTE zx297520v3 clock bindings and driver Stefan Dösinger
  2026-07-16 21:35 ` [PATCH v7 01/13] dt-bindings: phy: Add zx297520v3 USB phy documentation Stefan Dösinger
@ 2026-07-16 21:35 ` Stefan Dösinger
  2026-07-16 21:35 ` [PATCH v7 03/13] dt-bindings: clk: zte: Add zx297520v3 matrix " Stefan Dösinger
                   ` (7 subsequent siblings)
  9 siblings, 0 replies; 17+ messages in thread
From: Stefan Dösinger @ 2026-07-16 21:35 UTC (permalink / raw)
  To: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Philipp Zabel, Brian Masney, Vinod Koul,
	Neil Armstrong, Russell King
  Cc: linux-clk, devicetree, linux-kernel, linux-arm-kernel, linux-phy,
	Stefan Dösinger

These SoCs have 3 clock and reset controllers: Top, Matrix and LSP.
Clocks go from oscillator -> top -> matrix -> LSP, with a register in
top controlling most clocks that get passed to matrix and a register in
matrix controlling all the clocks that get passed to LSP.

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 v6->v7:
*) Moved the bingings back into clock/ (Krzysztof). use clock-controller@
in example and in the later DTSI patch.

*) With as lucky find in strings in LTE's cpko.ko blob I could make sense
of the register that controls which clocks get passed from PLLs into
the clock distribution inside topcrm. Importantly that also narrows
which clocks can possibly be passed to Matrix, so I could slim down the
bindings considerably.

*) Add a usb phy child node with the phy schema added in the previous
patch. I don't expect the USB status register to ever be in another
place, so the binding expresses the @84 expectation.

Changes v5->v6:
Set value for syscon-reboot example (Sashiko). It was my intention to
set only the lowest bit, and I think Sashiko is right that without
'value' being set, all other bits are actively set to 0. It shouldn't
matter given my understanding of the hardware (afaics all other bits are
ignored), but actively clearing bits was not my intention.

I haven't changed the name match for "syscon-reboot". I see plenty of
examples of hardcoding this string as opposed to having a regex for
syscon-reboot@12345678 in other bindings.

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/clock/zte,zx297520v3-topcrm.yaml      | 123 +++++++++++++++++++++
 MAINTAINERS                                        |   3 +
 include/dt-bindings/clock/zte,zx297520v3-clk.h     |  66 +++++++++++
 include/dt-bindings/reset/zte,zx297520v3-reset.h   |  32 ++++++
 4 files changed, 224 insertions(+)

diff --git a/Documentation/devicetree/bindings/clock/zte,zx297520v3-topcrm.yaml b/Documentation/devicetree/bindings/clock/zte,zx297520v3-topcrm.yaml
new file mode 100644
index 000000000000..454997063573
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/zte,zx297520v3-topcrm.yaml
@@ -0,0 +1,123 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/clock/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 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
+  PLL output frequencies to downstream clock controllers.
+
+  The USB PHY functionality is exposed as a child node documented in
+  zte,zx297520v3-usb-phy.yaml.
+
+  All available clocks are defined as preprocessor macros in the
+  "include/dt-bindings/clock/zte,zx297520v3-clk.h" header. The resets are
+  defined in the "include/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
+
+  "#address-cells":
+    const: 1
+
+  "#size-cells":
+    const: 0
+
+  "#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.
+
+  usb-phy@84:
+    type: object
+    $ref: /schemas/phy/zte,zx297520v3-usb-phy.yaml#
+    description:
+      USB and HSIC PHY controller.
+
+required:
+  - compatible
+  - reg
+  - clocks
+  - clock-names
+  - syscon-reboot
+  - usb-phy@84
+  - "#address-cells"
+  - "#size-cells"
+  - "#clock-cells"
+  - "#reset-cells"
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/arm-gic.h>
+    #include <dt-bindings/reset/zte,zx297520v3-reset.h>
+    #include <dt-bindings/phy/phy-zte-zx297520v3-usb.h>
+    #include <dt-bindings/clock/zte,zx297520v3-clk.h>
+
+    topcrm: clock-controller@13b000 {
+        compatible = "zte,zx297520v3-topcrm", "syscon";
+        reg = <0x0013b000 0x400>;
+        clocks = <&osc26m>, <&osc32k>;
+        clock-names = "osc26m", "osc32k";
+        #address-cells = <1>;
+        #size-cells = <0>;
+        #clock-cells = <1>;
+        #reset-cells = <1>;
+
+        syscon-reboot {
+          compatible = "syscon-reboot";
+          offset = <0x0>;
+          mask = <0x1>;
+          value = <0x1>;
+        };
+
+        usb-phy@84 {
+          compatible = "zte,zx297520v3-usb-phy";
+          reg = <0x84>;
+          interrupts = <GIC_SPI 42 IRQ_TYPE_EDGE_RISING>,
+            <GIC_SPI 43 IRQ_TYPE_EDGE_RISING>,
+            <GIC_SPI 45 IRQ_TYPE_EDGE_RISING>,
+            <GIC_SPI 46 IRQ_TYPE_EDGE_RISING>;
+          interrupt-names = "usb-up", "usb-down", "hsic-up", "hsic-down";
+          resets = <&topcrm ZX297520V3_USB_PHY_RESET>,
+            <&topcrm ZX297520V3_HSIC_PHY_RESET>;
+          reset-names = "usb", "hsic";
+          #phy-cells = <1>;
+        };
+    };
diff --git a/MAINTAINERS b/MAINTAINERS
index cb50c2e3b4ea..5f962b9947ab 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3878,10 +3878,13 @@ 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-topcrm.yaml
 F:	Documentation/devicetree/bindings/phy/zte,zx297520v3-usb-phy.yaml
 F:	arch/arm/boot/dts/zte/
 F:	arch/arm/mach-zte/
+F:	include/dt-bindings/clock/zte,zx297520v3-clk.h
 F:	include/dt-bindings/phy/phy-zte-zx297520v3-usb.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..1f010ff4e6ca
--- /dev/null
+++ b/include/dt-bindings/clock/zte,zx297520v3-clk.h
@@ -0,0 +1,66 @@
+/* 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_D5			23
+#define ZX297520V3_DPLL				24
+#define ZX297520V3_GPLL				25
+#define ZX297520V3_GPLL_D2			26
+#define ZX297520V3_GATED_OSC26M			27
+#define ZX297520V3_PMM_WCLK			28
+#define ZX297520V3_PMM_PCLK			29
+#define ZX297520V3_OUT0_WCLK			30
+#define ZX297520V3_OUT1_WCLK			31
+#define ZX297520V3_OUT2_WCLK			32
+#define ZX297520V3_OUT32K_WCLK			33
+#define ZX297520V3_RMIIPHY_WCLK			34
+#define ZX297520V3_TIMER_T12_WCLK		35
+#define ZX297520V3_TIMER_T12_PCLK		36
+#define ZX297520V3_TIMER_T13_WCLK		37
+#define ZX297520V3_TIMER_T13_PCLK		38
+#define ZX297520V3_TIMER_T14_WCLK		39
+#define ZX297520V3_TIMER_T14_PCLK		40
+#define ZX297520V3_TIMER_T15_WCLK		41
+#define ZX297520V3_TIMER_T15_PCLK		42
+#define ZX297520V3_TIMER_T16_WCLK		43
+#define ZX297520V3_TIMER_T16_PCLK		44
+#define ZX297520V3_TIMER_T17_WCLK		45
+#define ZX297520V3_TIMER_T17_PCLK		46
+#define ZX297520V3_WDT_T18_WCLK			47
+#define ZX297520V3_WDT_T18_PCLK			48
+#define ZX297520V3_USIM1_WCLK			49
+#define ZX297520V3_USIM1_PCLK			50
+#define ZX297520V3_AHB_WCLK			51
+#define ZX297520V3_AHB_PCLK			52
+#define ZX297520V3_USB_WCLK			53
+#define ZX297520V3_USB_PCLK			54
+#define ZX297520V3_HSIC_WCLK			55
+#define ZX297520V3_HSIC_PCLK			56
+
+#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.54.0


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v7 03/13] dt-bindings: clk: zte: Add zx297520v3 matrix clock and reset controller
  2026-07-16 21:35 [PATCH v7 00/13] ZTE zx297520v3 clock bindings and driver Stefan Dösinger
  2026-07-16 21:35 ` [PATCH v7 01/13] dt-bindings: phy: Add zx297520v3 USB phy documentation Stefan Dösinger
  2026-07-16 21:35 ` [PATCH v7 02/13] dt-bindings: clk: zte: Add zx297520v3 top clock and reset controller Stefan Dösinger
@ 2026-07-16 21:35 ` Stefan Dösinger
  2026-07-16 22:25   ` sashiko-bot
  2026-07-16 21:35 ` [PATCH v7 04/13] dt-bindings: clk: zte: Add zx297520v3 LSP " Stefan Dösinger
                   ` (6 subsequent siblings)
  9 siblings, 1 reply; 17+ messages in thread
From: Stefan Dösinger @ 2026-07-16 21:35 UTC (permalink / raw)
  To: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Philipp Zabel, Brian Masney, Vinod Koul,
	Neil Armstrong, Russell King
  Cc: linux-clk, devicetree, linux-kernel, linux-arm-kernel, linux-phy,
	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.

I don't know much about the hwlocks yet other than that they exist, so
there is no header yet adding meaning to their indices. Presumably that
meaning doesn't exist in hardware and is de facto defined by ZTE's
firmware blobs running on the rprocs.

A simple MFD driver will instantiate independent clock, reset and hwlock
drivers.

Signed-off-by: Stefan Dösinger <stefandoesinger@gmail.com>

---

v6->v7:

*) Moved it back to clock/

*) Remove syscon compatible again. I looked into remaining unknown
registers and I am convinced that there is no odd misc register left
that can't be exposed as a proper subsystem provider.

The controls in 0x100 that ZTE's DRAM reading code exercises are auto
clock gating controls and the actual mailbox is in a different
controller.

Unknown registers in 0x120-0x12c are a frequency voting mechanism for
the AXI bus clock. The driver will participate in that voting rather
than set the frequency via 0x0. If need be it could be exposed
as #interconnect-cells, but it won't necessitate "syscon".

*) Reduce the number of input clocks to match register controls in
topclk's input control that affect matrix and LSP.

*) Add DDR mux and gates. Not to change them, but to make sure CCF knows
about which PLL is feeding the sysmem memory

Changes
v5->v6:
Add syscon compatible - Conor suggested this after v4 and I forgot it in v5.

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/clock/zte,zx297520v3-matrixcrm.yaml   | 91 ++++++++++++++++++++++
 MAINTAINERS                                        |  1 +
 include/dt-bindings/clock/zte,zx297520v3-clk.h     | 41 ++++++++++
 include/dt-bindings/reset/zte,zx297520v3-reset.h   | 12 +++
 4 files changed, 145 insertions(+)

diff --git a/Documentation/devicetree/bindings/clock/zte,zx297520v3-matrixcrm.yaml b/Documentation/devicetree/bindings/clock/zte,zx297520v3-matrixcrm.yaml
new file mode 100644
index 000000000000..55b1a42836c7
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/zte,zx297520v3-matrixcrm.yaml
@@ -0,0 +1,91 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/clock/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
+  "include/dt-bindings/clock/zte,zx297520v3-clk.h" header. Resets are defined in
+  the "include/dt-bindings/reset/zte,zx297520v3-reset.h" header.
+
+properties:
+  compatible:
+    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 5
+      - description: Dpll output from topcrm (usually 491.52 MHz)
+      - description: Gpll output from topcrm (usually 200 MHz)
+      - description: Gpll subdivision factor 2
+      - description: osc26m work clock from topcrm
+
+  clock-names:
+    items:
+      - const: osc26m
+      - const: osc32k
+      - const: mpll
+      - const: mpll-d5
+      - const: dpll
+      - const: gpll
+      - const: gpll-d2
+      - const: wclk-osc26m
+
+  "#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_D5>,
+          <&topcrm ZX297520V3_DPLL>, <&topcrm ZX297520V3_GPLL>,
+          <&topcrm ZX297520V3_GPLL_D2>, <&topcrm ZX297520V3_GATED_OSC26M>;
+        clock-names = "osc26m", "osc32k", "mpll", "mpll-d5", "dpll",
+          "gpll", "gpll-d2", "wclk-osc26m";
+        #clock-cells = <1>;
+        #hwlock-cells = <1>;
+        #reset-cells = <1>;
+    };
diff --git a/MAINTAINERS b/MAINTAINERS
index 5f962b9947ab..6d153d2cba79 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3878,6 +3878,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-matrixcrm.yaml
 F:	Documentation/devicetree/bindings/clock/zte,zx297520v3-topcrm.yaml
 F:	Documentation/devicetree/bindings/phy/zte,zx297520v3-usb-phy.yaml
 F:	arch/arm/boot/dts/zte/
diff --git a/include/dt-bindings/clock/zte,zx297520v3-clk.h b/include/dt-bindings/clock/zte,zx297520v3-clk.h
index 1f010ff4e6ca..09ee3de679d9 100644
--- a/include/dt-bindings/clock/zte,zx297520v3-clk.h
+++ b/include/dt-bindings/clock/zte,zx297520v3-clk.h
@@ -63,4 +63,45 @@
 #define ZX297520V3_HSIC_WCLK			55
 #define ZX297520V3_HSIC_PCLK			56
 
+#define ZX297520V3_SYS_TIMER_WCLK		1
+#define ZX297520V3_CPU_WCLK			2
+#define ZX297520V3_CPU_PCLK			3
+#define ZX297520V3_ZSP_WCLK			4
+#define ZX297520V3_DDR_CTRL_PCLK		5
+#define ZX297520V3_DDR_CTRL_WCLK		6
+#define ZX297520V3_EDCP_WCLK			7
+#define ZX297520V3_EDCP_PCLK			8
+#define ZX297520V3_SD0_WCLK			9
+#define ZX297520V3_SD0_PCLK			10
+#define ZX297520V3_SD0_CDET			11
+#define ZX297520V3_SD1_WCLK			12
+#define ZX297520V3_SD1_PCLK			13
+#define ZX297520V3_SD1_CDET			14
+#define ZX297520V3_NAND_WCLK			15
+#define ZX297520V3_NAND_PCLK			16
+#define ZX297520V3_DMA_PCLK			17
+#define ZX297520V3_MBOX_PCLK			18
+#define ZX297520V3_PDCFG_WCLK			19
+#define ZX297520V3_PDCFG_PCLK			20
+#define ZX297520V3_SSC_WCLK			21
+#define ZX297520V3_SSC_PCLK			22
+#define ZX297520V3_AXI_WCLK			23
+#define ZX297520V3_GMAC_WCLK			24
+#define ZX297520V3_GMAC_PCLK			25
+#define ZX297520V3_GMAC_AHB			26
+#define ZX297520V3_VOU_WCLK			27
+#define ZX297520V3_VOU_PCLK			28
+#define ZX297520V3_LSP_MPLL_D5_WCLK		29
+#define ZX297520V3_LSP_MPLL_D4_WCLK		30
+#define ZX297520V3_LSP_MPLL_D6_WCLK		31
+#define ZX297520V3_LSP_MPLL_D8_WCLK		32
+#define ZX297520V3_LSP_MPLL_D12_WCLK		33
+#define ZX297520V3_LSP_OSC26M_WCLK		34
+#define ZX297520V3_LSP_OSC32K_WCLK		35
+#define ZX297520V3_LSP_PCLK			36
+#define ZX297520V3_LSP_TDM_WCLK			37
+#define ZX297520V3_LSP_DPLL_D4_WCLK		38
+#define ZX297520V3_SRAM0_PCLK			39
+#define ZX297520V3_GSM_CFG_PCLK			40
+
 #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..4682d4a599c6 100644
--- a/include/dt-bindings/reset/zte,zx297520v3-reset.h
+++ b/include/dt-bindings/reset/zte,zx297520v3-reset.h
@@ -29,4 +29,16 @@
 #define ZX297520V3_HSIC_PHY_RESET		20
 #define ZX297520V3_HSIC_RESET			21
 
+#define ZX297520V3_CPU_RESET			0
+#define ZX297520V3_DDR_CTRL_RESET		1
+#define ZX297520V3_EDCP_RESET			2
+#define ZX297520V3_SD0_RESET			3
+#define ZX297520V3_SD1_RESET			4
+#define ZX297520V3_NAND_RESET			5
+#define ZX297520V3_PDCFG_RESET			6
+#define ZX297520V3_SSC_RESET			7
+#define ZX297520V3_GMAC_RESET			8
+#define ZX297520V3_VOU_RESET			9
+#define ZX297520V3_LSP_RESET			10
+
 #endif /* __DT_BINDINGS_RESET_ZX297520V3_H */

-- 
2.54.0


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v7 04/13] dt-bindings: clk: zte: Add zx297520v3 LSP clock and reset controller
  2026-07-16 21:35 [PATCH v7 00/13] ZTE zx297520v3 clock bindings and driver Stefan Dösinger
                   ` (2 preceding siblings ...)
  2026-07-16 21:35 ` [PATCH v7 03/13] dt-bindings: clk: zte: Add zx297520v3 matrix " Stefan Dösinger
@ 2026-07-16 21:35 ` Stefan Dösinger
  2026-07-16 21:35 ` [PATCH v7 05/13] mfd: zx297520v3: Add a clock and reset MFD driver Stefan Dösinger
                   ` (5 subsequent siblings)
  9 siblings, 0 replies; 17+ messages in thread
From: Stefan Dösinger @ 2026-07-16 21:35 UTC (permalink / raw)
  To: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Philipp Zabel, Brian Masney, Vinod Koul,
	Neil Armstrong, Russell King
  Cc: linux-clk, devicetree, linux-kernel, linux-arm-kernel, linux-phy,
	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      | 101 +++++++++++++++++++++
 MAINTAINERS                                        |   1 +
 include/dt-bindings/clock/zte,zx297520v3-clk.h     |  37 ++++++++
 include/dt-bindings/reset/zte,zx297520v3-reset.h   |  19 ++++
 4 files changed, 158 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..3ef8fad3c93a
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/zte,zx297520v3-lspcrm.yaml
@@ -0,0 +1,101 @@
+# 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 signals from the matrix controller and need
+  to be declared as clock inputs.
+
+  All available clocks are defined as preprocessor macros in the
+  "include/dt-bindings/clock/zte,zx297520v3-clk.h" header. Resets are defined in
+  the "include/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
+
+  resets:
+    maxItems: 1
+
+  "#clock-cells":
+    const: 1
+
+  "#reset-cells":
+    const: 1
+
+required:
+  - compatible
+  - reg
+  - clocks
+  - clock-names
+  - resets
+  - "#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";
+        resets = <&matrixcrm ZX297520V3_LSP_RESET>;
+        #clock-cells = <1>;
+        #reset-cells = <1>;
+    };
diff --git a/MAINTAINERS b/MAINTAINERS
index 6d153d2cba79..a135ec070314 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3878,6 +3878,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/clock/zte,zx297520v3-matrixcrm.yaml
 F:	Documentation/devicetree/bindings/clock/zte,zx297520v3-topcrm.yaml
 F:	Documentation/devicetree/bindings/phy/zte,zx297520v3-usb-phy.yaml
diff --git a/include/dt-bindings/clock/zte,zx297520v3-clk.h b/include/dt-bindings/clock/zte,zx297520v3-clk.h
index 09ee3de679d9..2c947f467373 100644
--- a/include/dt-bindings/clock/zte,zx297520v3-clk.h
+++ b/include/dt-bindings/clock/zte,zx297520v3-clk.h
@@ -104,4 +104,41 @@
 #define ZX297520V3_SRAM0_PCLK			39
 #define ZX297520V3_GSM_CFG_PCLK			40
 
+#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 4682d4a599c6..69eef84b2f29 100644
--- a/include/dt-bindings/reset/zte,zx297520v3-reset.h
+++ b/include/dt-bindings/reset/zte,zx297520v3-reset.h
@@ -41,4 +41,23 @@
 #define ZX297520V3_VOU_RESET			9
 #define ZX297520V3_LSP_RESET			10
 
+#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.54.0


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v7 05/13] mfd: zx297520v3: Add a clock and reset MFD driver
  2026-07-16 21:35 [PATCH v7 00/13] ZTE zx297520v3 clock bindings and driver Stefan Dösinger
                   ` (3 preceding siblings ...)
  2026-07-16 21:35 ` [PATCH v7 04/13] dt-bindings: clk: zte: Add zx297520v3 LSP " Stefan Dösinger
@ 2026-07-16 21:35 ` Stefan Dösinger
  2026-07-16 22:24   ` sashiko-bot
  2026-07-16 21:35 ` [PATCH v7 06/13] clk: zte: Add Clock registration infrastructure Stefan Dösinger
                   ` (4 subsequent siblings)
  9 siblings, 1 reply; 17+ messages in thread
From: Stefan Dösinger @ 2026-07-16 21:35 UTC (permalink / raw)
  To: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Philipp Zabel, Brian Masney, Vinod Koul,
	Neil Armstrong, Russell King
  Cc: linux-clk, devicetree, linux-kernel, linux-arm-kernel, linux-phy,
	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 v6: Make the ZTE SoC driver section depend on HAS_IOMEM
(Sashiko). The entire MFD section, which contains MFD_CORE, depends on
HAS_IOMEM even with COMPILE_TEST.

Add a NULL ptr check for of_device_get_match_data (Sashiko). While not
uniform, rave-sp, rohm-bd9576, atc260x, da9052-i2c protect against
incorrect manual attachment that way.

Add lspclk here as well in an attempt to satisfy both Conor Dooley, who
asks for MFD for top and matrix, and Philipp Zabel, who prefers aux but
at least wants the reset driver limited to one driver type.

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 | 99 ++++++++++++++++++++++++++++++++++++++++
 6 files changed, 125 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index a135ec070314..6ca3312aa746 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3884,6 +3884,7 @@ F:	Documentation/devicetree/bindings/clock/zte,zx297520v3-topcrm.yaml
 F:	Documentation/devicetree/bindings/phy/zte,zx297520v3-usb-phy.yaml
 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/phy/phy-zte-zx297520v3-usb.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..0e954e6ce2a9
--- /dev/null
+++ b/drivers/soc/zte/Kconfig
@@ -0,0 +1,20 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+if HAS_IOMEM && (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..8b82ccba4e90
--- /dev/null
+++ b/drivers/soc/zte/zx297520v3-crm.c
@@ -0,0 +1,99 @@
+// 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/errno.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-topreset",
+	},
+	{
+		.name = "syscon-reboot",
+		.of_compatible = "syscon-reboot",
+	},
+	{
+		.name = "zx297520v3-usb-phy",
+		.of_compatible = "zte,zx297520v3-usb-phy",
+	},
+};
+
+static const struct zx297520v3_crm_data zx297520v3_topcrm_data = {
+	.cells = zx297520v3_topcrm_devs,
+	.num_cells = ARRAY_SIZE(zx297520v3_topcrm_devs),
+};
+
+static const struct mfd_cell zx297520v3_matrixcrm_devs[] = {
+	{
+		.name = "zx297520v3-matrixclk",
+	},
+	{
+		.name = "zx297520v3-matrixreset",
+	},
+	/* A set of hwlock controllers is found here as well, but no driver is implemented yet */
+};
+
+static const struct zx297520v3_crm_data zx297520v3_matrixcrm_data = {
+	.cells = zx297520v3_matrixcrm_devs,
+	.num_cells = ARRAY_SIZE(zx297520v3_matrixcrm_devs),
+};
+
+static const struct mfd_cell zx297520v3_lspcrm_devs[] = {
+	{
+		.name = "zx297520v3-lspclk",
+	},
+	{
+		.name = "zx297520v3-lspreset",
+	},
+};
+
+static const struct zx297520v3_crm_data zx297520v3_lspcrm_data = {
+	.cells = zx297520v3_lspcrm_devs,
+	.num_cells = ARRAY_SIZE(zx297520v3_lspcrm_devs),
+};
+
+static int zx297520v3_crm_probe(struct platform_device *pdev)
+{
+	const struct zx297520v3_crm_data *data;
+
+	data = of_device_get_match_data(&pdev->dev);
+	if (!data)
+		return -ENODEV;
+
+	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 },
+	{ .compatible = "zte,zx297520v3-lspcrm", .data = &zx297520v3_lspcrm_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.54.0


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v7 06/13] clk: zte: Add Clock registration infrastructure
  2026-07-16 21:35 [PATCH v7 00/13] ZTE zx297520v3 clock bindings and driver Stefan Dösinger
                   ` (4 preceding siblings ...)
  2026-07-16 21:35 ` [PATCH v7 05/13] mfd: zx297520v3: Add a clock and reset MFD driver Stefan Dösinger
@ 2026-07-16 21:35 ` Stefan Dösinger
  2026-07-16 22:26   ` sashiko-bot
  2026-07-16 21:35 ` [PATCH v7 07/13] clk: zte: Add regmap based clocks Stefan Dösinger
                   ` (3 subsequent siblings)
  9 siblings, 1 reply; 17+ messages in thread
From: Stefan Dösinger @ 2026-07-16 21:35 UTC (permalink / raw)
  To: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Philipp Zabel, Brian Masney, Vinod Koul,
	Neil Armstrong, Russell King
  Cc: linux-clk, devicetree, linux-kernel, linux-arm-kernel, linux-phy,
	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 7:
*) Add fixed dividers to handle PLL subdivisions
*) Never register PLLs directly as exported clocks - everything on this
SoC goes through a gate before it leaves a controller.

Version 6:
*) Remove auxdev now that LSP clocks also use MFD
*) Error codepath fixes pointed out by Sashiko.

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      |  16 +++++
 drivers/clk/zte/Makefile     |   5 ++
 drivers/clk/zte/clk-regmap.c |  34 ++++++++++
 drivers/clk/zte/clk-zx.c     | 155 +++++++++++++++++++++++++++++++++++++++++++
 drivers/clk/zte/clk-zx.h     |  90 +++++++++++++++++++++++++
 drivers/clk/zte/pll-zx.c     |  16 +++++
 9 files changed, 319 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 6ca3312aa746..f88a2eda737a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3884,6 +3884,7 @@ F:	Documentation/devicetree/bindings/clock/zte,zx297520v3-topcrm.yaml
 F:	Documentation/devicetree/bindings/phy/zte,zx297520v3-usb-phy.yaml
 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/phy/phy-zte-zx297520v3-usb.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..0222549dd211
--- /dev/null
+++ b/drivers/clk/zte/Kconfig
@@ -0,0 +1,16 @@
+# 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 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..984abeb45ab2
--- /dev/null
+++ b/drivers/clk/zte/clk-regmap.c
@@ -0,0 +1,34 @@
+// 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 <linux/clk-provider.h>
+#include <linux/device.h>
+#include <linux/regmap.h>
+#include <linux/errno.h>
+
+#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)
+{
+	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..db4fad2b4b05
--- /dev/null
+++ b/drivers/clk/zte/clk-zx.c
@@ -0,0 +1,155 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2026 Stefan Dösinger
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+
+#include "clk-zx.h"
+
+static int zx_clk_register_fixed_dividers(struct device *dev, struct regmap *regmap,
+					  const struct zx_fixed_divider_desc *desc,
+					  unsigned int num)
+{
+	struct clk_hw *clk;
+	unsigned int i;
+
+	for (i = 0; i < num; ++i) {
+		clk = devm_clk_hw_register_fixed_factor(dev, desc[i].name, desc[i].parent,
+							CLK_SET_RATE_PARENT, 1, desc[i].div);
+		if (IS_ERR(clk)) {
+			return dev_err_probe(dev, PTR_ERR(clk), "Failed to register clk %s\n",
+					     desc[i].name);
+		}
+	}
+
+	return 0;
+}
+
+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 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_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_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, res, "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]);
+		}
+	}
+
+	if (data->init) {
+		res = data->init(map);
+		if (res)
+			return dev_err_probe(dev, PTR_ERR(clk), "Controller init failure\n");
+	}
+
+	res = zx_clk_register_plls(dev, map, data->plls, data->num_plls);
+	if (res)
+		return res;
+
+	res = zx_clk_register_fixed_dividers(dev, map, data->fixed_divs, data->num_fixed_divs);
+	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);
+	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;
+	return devm_add_action_or_reset(dev, zx_delete_clk_provider, of_node);
+}
+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..b30e4a29d951
--- /dev/null
+++ b/drivers/clk/zte/clk-zx.h
@@ -0,0 +1,90 @@
+/* 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>
+
+#define ZX297520V3_PLL_PREPARE_IS_ENABLE	1
+
+/* PLLs, divs and fixed div/factor clocks are never exported directly, only
+ * through a gate.
+ */
+
+struct zx_pll_desc {
+	const char *name;
+	const char * const *parents;
+	unsigned int num_parents;
+	unsigned long rate;
+	u16 reg;
+	u16 flags;
+};
+
+struct zx_fixed_divider_desc {
+	const char	*name;
+	const char	*parent;
+	unsigned int	div;
+};
+
+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 {
+	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);
+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);
+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 {
+	int (*init)(struct regmap *map);
+	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_fixed_divider_desc *fixed_divs;
+	unsigned int num_fixed_divs;
+	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;
+};
+
+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..f8ddb4c7dff6
--- /dev/null
+++ b/drivers/clk/zte/pll-zx.c
@@ -0,0 +1,16 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2026 Stefan Dösinger
+ */
+#include <linux/clk-provider.h>
+#include <linux/device.h>
+#include <linux/regmap.h>
+#include <linux/errno.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)
+{
+	return -ENODEV;
+}

-- 
2.54.0


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v7 07/13] clk: zte: Add regmap based clocks
  2026-07-16 21:35 [PATCH v7 00/13] ZTE zx297520v3 clock bindings and driver Stefan Dösinger
                   ` (5 preceding siblings ...)
  2026-07-16 21:35 ` [PATCH v7 06/13] clk: zte: Add Clock registration infrastructure Stefan Dösinger
@ 2026-07-16 21:35 ` Stefan Dösinger
  2026-07-16 21:35 ` [PATCH v7 08/13] clk: zte: Add zx PLL support infrastructure Stefan Dösinger
                   ` (2 subsequent siblings)
  9 siblings, 0 replies; 17+ messages in thread
From: Stefan Dösinger @ 2026-07-16 21:35 UTC (permalink / raw)
  To: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Philipp Zabel, Brian Masney, Vinod Koul,
	Neil Armstrong, Russell King
  Cc: linux-clk, devicetree, linux-kernel, linux-arm-kernel, linux-phy,
	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 6: Remove stray regmap (Sashiko)

Version 5: Use regmap_test_bits in zte_clk_regmap_gate_is_enabled
---
 drivers/clk/zte/clk-regmap.c | 218 ++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 215 insertions(+), 3 deletions(-)

diff --git a/drivers/clk/zte/clk-regmap.c b/drivers/clk/zte/clk-regmap.c
index 984abeb45ab2..198628f73b92 100644
--- a/drivers/clk/zte/clk-regmap.c
+++ b/drivers/clk/zte/clk-regmap.c
@@ -7,28 +7,240 @@
  */
 
 #include <linux/clk-provider.h>
+#include <linux/container_of.h>
 #include <linux/device.h>
 #include <linux/regmap.h>
+#include <linux/types.h>
 #include <linux/errno.h>
+#include <linux/bits.h>
+#include <linux/err.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)
 {
-	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);
+	}
+
+	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.54.0


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v7 08/13] clk: zte: Add zx PLL support infrastructure
  2026-07-16 21:35 [PATCH v7 00/13] ZTE zx297520v3 clock bindings and driver Stefan Dösinger
                   ` (6 preceding siblings ...)
  2026-07-16 21:35 ` [PATCH v7 07/13] clk: zte: Add regmap based clocks Stefan Dösinger
@ 2026-07-16 21:35 ` Stefan Dösinger
  2026-07-16 22:34   ` sashiko-bot
  2026-07-16 21:35 ` [PATCH v7 09/13] clk: zte: Introduce a driver for zx297520v3 top clocks Stefan Dösinger
  2026-07-16 22:00 ` [PATCH v7 13/13] ARM: dts: zte: Declare zx297520v3 CRM device nodes Stefan Dösinger
  9 siblings, 1 reply; 17+ messages in thread
From: Stefan Dösinger @ 2026-07-16 21:35 UTC (permalink / raw)
  To: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Philipp Zabel, Brian Masney, Vinod Koul,
	Neil Armstrong, Russell King
  Cc: linux-clk, devicetree, linux-kernel, linux-arm-kernel, linux-phy,
	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 7:
*) Always keep unknownpll enabled when prepared so dpll can acquire a
lock in its prepare() function.
*) Clean up error reporting a bit (Sashiko)

Version 6:
*) Use abs_diff to compare target and candidate PLL rate (Sashiko).
*) Use req->best_parent_rate in zx29_pll_determine_rate. Add a TODO
comment about the parent rate flexibility.

Version 5: Fix Some issues pointed out by Sashiko: NULL dev,
zx29_pll_recalc_rate error handling, disable PLL again on enable error.
---
 drivers/clk/zte/pll-zx.c | 506 ++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 505 insertions(+), 1 deletion(-)

diff --git a/drivers/clk/zte/pll-zx.c b/drivers/clk/zte/pll-zx.c
index f8ddb4c7dff6..701e580609f2 100644
--- a/drivers/clk/zte/pll-zx.c
+++ b/drivers/clk/zte/pll-zx.c
@@ -3,14 +3,518 @@
  * Copyright (C) 2026 Stefan Dösinger
  */
 #include <linux/clk-provider.h>
+#include <linux/container_of.h>
+#include <linux/rational.h>
 #include <linux/device.h>
 #include <linux/regmap.h>
+#include <linux/units.h>
+#include <linux/types.h>
 #include <linux/errno.h>
+#include <linux/math.h>
+#include <linux/clk.h>
+#include <linux/err.h>
 
 #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 = 0;
+	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 prepare failed: %d. Config value 0x%08x\n",
+			clk_hw_get_name(&pll->hw), res, 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;
+			unsigned 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_diff(out, rate) > best)
+				continue;
+
+			if (abs_diff(out, rate) < best) {
+				postdiv1 = i;
+				postdiv2 = j;
+				best_fbdiv = fbdiv;
+				best_refdiv = refdiv;
+				best = abs_diff(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;
+	u32 setting;
+
+	/* TODO: DPLL can switch between two parents, one of which is another PLL. Take this into
+	 * account when searching the config space and set req->best_parent_rate.
+	 *
+	 * In practise it shouldn't matter though. DPLL is always configured to a fixed frequency.
+	 */
+	if (!req->best_parent_rate) {
+		dev_err(pll->dev, "Did not expect best_parent_rate=0\n");
+		return -EINVAL;
+	}
+
+	setting = zx29_pll_calc_values(pll, req->best_parent_rate, req->rate);
+	if (!setting)
+		return -EINVAL;
+
+	new_rate = zx29_pll_get_rate(pll, req->best_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;
+
+	/* PLLs can be changed while they are running and in testing the downstream hardware
+	 * handles it gracefully - as long as the new rate is a rate the hardware can operate at.
+	 */
+	setting = zx29_pll_calc_values(pll, parent_rate, rate);
+	if (!setting)
+		return -EINVAL;
+
+	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;
+}
+
+static 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,
+};
+
+/* A PLL that can be a parent of another PLL needs to produce an output signal when prepared,
+ * otherwise the downstream PLL's prepare() will fail to acquire a lock.
+ */
+static int zx29_pll_nodisable_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, prepare-is-enabled\n", name);
+
+	res = regmap_clear_bits(pll->map, pll->reg, ZX29_PLL_BYPASS);
+	if (res < 0)
+		return res;
+
+	return regmap_clear_bits(pll->map, pll->reg + ZX29_PLL_REG2_OFFSET,
+				 ZX29_PLL_POSTDIV_OUT_DISABLE);
+}
+
+static const struct clk_ops zx29_pll_nodisable_ops = {
+	.init		= zx29_pll_nodisable_init,
+	.is_prepared	= zx29_pll_is_prepared,
+	.prepare	= zx29_pll_prepare,
+	.unprepare	= zx29_pll_unprepare,
+	.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)
 {
-	return -ENODEV;
+	struct zx29_clk_pll *pll;
+	unsigned int i;
+	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;
+
+		if (desc[i].flags & ZX297520V3_PLL_PREPARE_IS_ENABLE)
+			init.ops = &zx29_pll_nodisable_ops;
+		else
+			init.ops = &zx29_pll_ops;
+
+		init.name = desc[i].name;
+		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;
+			}
+		}
+	}
+
+	return 0;
 }

-- 
2.54.0


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v7 09/13] clk: zte: Introduce a driver for zx297520v3 top clocks
  2026-07-16 21:35 [PATCH v7 00/13] ZTE zx297520v3 clock bindings and driver Stefan Dösinger
                   ` (7 preceding siblings ...)
  2026-07-16 21:35 ` [PATCH v7 08/13] clk: zte: Add zx PLL support infrastructure Stefan Dösinger
@ 2026-07-16 21:35 ` Stefan Dösinger
  2026-07-16 22:42   ` sashiko-bot
  2026-07-16 22:00 ` [PATCH v7 13/13] ARM: dts: zte: Declare zx297520v3 CRM device nodes Stefan Dösinger
  9 siblings, 1 reply; 17+ messages in thread
From: Stefan Dösinger @ 2026-07-16 21:35 UTC (permalink / raw)
  To: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Philipp Zabel, Brian Masney, Vinod Koul,
	Neil Armstrong, Russell King
  Cc: linux-clk, devicetree, linux-kernel, linux-arm-kernel, linux-phy,
	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 7:

The register at top+0x140 contains a series of gates that control PLL
input into the top controller and PLL and osc26m work clock forward into
matrix. This allowed further insight into the clock tree.

The PLL subdivision list was replaced with fixed factor clocks that
divide PLLs before and after the input gate (and some fixed factor divs
go into matrix in the next patch). The number of clocks exported to
Matrix is actually relatively small.

As for Sashiko's suggestion about out of bound parents for dpll and
unknownpll: I have never seen a parent != 0. If that happens on a device
I want to know about it. At this stage of development I'd rather have
clearly visible orphaned clock than silently continuing at an unexpected
frequency.

"osc26m" -> "clock-26m" as "oscXX", although widespread, is deprecated.
In doing so, change underscores in clock names to dashes (Sashiko).

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 | 544 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 556 insertions(+)

diff --git a/drivers/clk/zte/Kconfig b/drivers/clk/zte/Kconfig
index 0222549dd211..c01d2704c864 100644
--- a/drivers/clk/zte/Kconfig
+++ b/drivers/clk/zte/Kconfig
@@ -14,3 +14,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 && (ZTE_ZX297520V3_CRM || COMPILE_TEST)
+	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..20ff00ba5f67
--- /dev/null
+++ b/drivers/clk/zte/clk-zx297520v3.c
@@ -0,0 +1,544 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2026 Stefan Dösinger
+ *
+ * There are no public datasheets for zx297520v3. The controls on this clock controller have been
+ * extracted from ZTE's kernel and U-Boot sources and a LOT of manual testing. Some clocks can be
+ * found in the power management (evb_cpurpm.img) and LTE (cpko.ko) blobs shipped with the routers.
+ *
+ * Because ZTE's sources are wrong or misleading in some parts and testing can be misinterpreted,
+ * this file contains a LOT of comments on how particular clocks were tested and how the impact of
+ * changes to them can be observed.
+ */
+#include <dt-bindings/clock/zte,zx297520v3-clk.h>
+#include <linux/platform_device.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+
+#include "clk-zx.h"
+
+MODULE_IMPORT_NS("ZTE_CLK");
+
+/* Used for gates where we don't know the parent input(s). Assume general oscillator */
+static const char clk_unknown[] = "clock-26m";
+
+/* For clocks that use the ungated clock-26m input */
+static const char * const clk_main[] = {
+	"clock-26m",
+};
+
+static const char * const dpll_parents[] = {
+	"unknownpll-d2",
+	"clock-26m",
+};
+
+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 presumably enables automatic clock
+ * gating. Bit 3, if settable, 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 mpll-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.
+ *
+ * The geneneral clock flow on this board goes from clock-26m into the PLLs. The PLL output is
+ * branched into a small number of divided frequencies (mpll, mpll-d4, mpll-d5, mpll-d6, mpll-d8,
+ * gpll, gpll-d2, gpll-d4) at a high level. These divided frequencies as well as clock-26m go
+ * through a series of gates at offset 0x140 in topcrm. The output of these gates provide wclks for
+ * everything.
+ *
+ * At least 3 of those gate outputs (mpll, dpll, gpll-d2) are further divided behind those gates,
+ * presumably in the matrix controller. There are multiple paths how 104 and 78 MHz are derived,
+ * some specific to topcrm (0x140 bits 1 and 4), some specific to matrix (0x140 bit 9). There are
+ * likely 3 "copies" of the 78 MHz clock and two 50 MHz ones.
+ *
+ * Pclk is not gated by the 0x140 gates at all. Likewise clock-32k is fed to the timers without
+ * passing through this gate, even for consumers on matrix/LSP. Topcrm clock-26m wclks are not gated
+ * either, but top-consumed PLL clocks are.
+ */
+
+static const struct zx_fixed_divider_desc zx297520v3_top_fixed_divs[] = {
+	/* clock-26m division for clk_outX */
+	{"clock-26m-d2",		"clock-26m",		 2},
+
+	/* This one is a parent for dpll */
+	{"unknownpll-d2",		"unknownpll",		 2},
+
+	/* Pre-0x140 divisions. They enter topcrm through different bits in 0x140 */
+	{"mpll-d2",			"mpll",			 2},
+	{"mpll-d5",			"mpll",			 5},
+	{"mpll-d6",			"mpll",			 6},
+	{"mpll-d8",			"mpll",			 8},
+	{"upll-d12",			"upll",			12},
+	{"gpll-d2",			"gpll",			 2},
+	{"gpll-d4",			"gpll",			 4},
+
+	/* Post-140 divisions. They enter through the same bit and can be selected by different mux
+	 * values of their consumers or get piped to multiple consumers with different documented
+	 * rates - in particular, USB wclk (24 MHz according to ZTE) shares bit 2 with M0's 78MHz.
+	 */
+	{"top-mpll-d2-d4",		"top-mpll-d2",		 4},
+	{"top-mpll-d2-d13",		"top-mpll-d2",		13},
+	{"top-upll-d12-d2",		"top-upll-d12",		 2},
+	{"top-gpll-d4-d2",		"top-gpll-d4",		 2},
+};
+
+/* 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 and depend on the same bit (3) in 0x140.
+ * The mpll-d6 rate is gated off by the same bit in 0x140 as m0's mpll-d6, but the d8 is different.
+ * This may indicate that one of the two uses a different clock rate than documented in ZTE's
+ * kernel. The M0 speed is easy to measure, so I am confident in the M0's 78 MHz rate. AHB looks
+ * like 78 rather than 52, but confidence is lower because RAM read tests are influenced by CPU
+ * overhead too.
+ */
+static const char * const ahb_sel[] = {
+	"clock-26m",
+	"top-mpll-d6",		/* 104 mhz */
+	"top-mpll-d8",		/* 78 mhz */
+	"top-mpll-d8",		/* 78 mhz */
+};
+
+static const char * const timer_top_sel[] = {
+	"clock-32k",
+	"clock-26m",
+};
+
+static const char * const uart_top_sel[] = {
+	"clock-26m",
+	"top-mpll-d6",	/* 104 mhz, sharing M0's and ahb's 0x140 gate */
+};
+
+/* The Cortex M0 coprocessor. It is responsible for booting the board and runs some power management
+ * helper code on the stock firmware. The M0 rproc itself is not critical, but most of topcrm's
+ * registers become unreadable if this is gated off or become slow when clocked at 32khz, e.g. GPIO
+ * becomes borderline unusable at 32khz.
+ *
+ * There is no dedicated M0 gate. Disabling ahb-wclk looks like the M0 stops from A53's point of
+ * view, but it merely breaks the A53's ability to communicate with it, and M0's ability to
+ * communicate with the standard UART on LSP. M0 keeps running with both ahb-wclk and ahb-pclk
+ * gated off and can re-enable the gates.
+ */
+static const char * const m0_sel[] = {
+	"clock-26m",
+	"top-mpll-d6",		/* 104 mhz */
+	"top-mpll-d2-d4",	/* 78 mhz, but different gate than the ahb counterpart */
+	"clock-32k",		/* 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. Neither is reading from any PLL, they keep oscillating when all PLLs are disabled.
+ * I am fairly sure that 3 is just direct clock-26m because it perfectly matches what out2 is
+ * showing in its 26 MHz setting.
+ *
+ * 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. 0x1c0 also runs in lockstep, presumably 26 MHz.
+ *
+ * clk-out0 has a mux in bit 5. Value 0 most likely selects clock-26m. 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[] = {
+	"clock-26m",
+	"m0-mux",
+};
+
+static const char * const out1_sel[] = {
+	"top-upll-d12",		/* 40 MHz */
+	"top-upll-d12-d2",	/* 20 MHz */
+	"clock-26m",
+	"clock-26m-d2",
+};
+
+/* 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.
+ *
+ * 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[] = {
+	"top-gpll-d4-d2",	/* 25 MHz */
+	"top-gpll-d4",		/* 50 MHz */
+};
+
+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.
+	 */
+	{
+		.name = "mpll",
+		.parents = clk_main,
+		.num_parents = ARRAY_SIZE(clk_main),
+		.rate = 0,
+		.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. ZTE manipulates this PLL directly out
+	 * of the camera code, so it is unlikely to have another consumer.
+	 *
+	 * Long story short, shut it off.
+	 */
+	{
+		.name = "upll",
+		.parents = clk_main,
+		.num_parents = ARRAY_SIZE(clk_main),
+		.rate = 0,
+		.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.
+	 *
+	 * Since dpll's prepare may depend in this clock generating a usable signal, it needs to be
+	 * enabled when prepared.
+	 */
+	{
+		.name = "unknownpll",
+		.parents = clk_main,
+		.num_parents = ARRAY_SIZE(clk_main),
+		.rate = 0,
+		.reg = 0x100,
+		.flags = ZX297520V3_PLL_PREPARE_IS_ENABLE,
+	},
+
+	/* 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 rproc 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
+	 * clock-26m. 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.
+	 */
+	{
+		.name = "dpll",
+		.parents = dpll_parents,
+		.num_parents = ARRAY_SIZE(dpll_parents),
+		.rate = 491520000,
+		.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.
+	 */
+	{
+		.name = "gpll",
+		.parents = clk_main,
+		.num_parents = ARRAY_SIZE(clk_main),
+		.rate = 0,
+		.reg = 0x110,
+	},
+};
+
+#define MUX(_id, _name, _parents, _reg, _shift, _size) { \
+	_id, _name, _parents, ARRAY_SIZE(_parents), _reg, _shift, _size}
+
+#define DIV(_name, _parent, _reg, _shift, _size) { _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-mux",        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,  6, 2),
+	MUX(0,                             "rmiiphy-mux",   rmii_sel,         0x11c,  0, 2),
+};
+
+static const struct zx_div_desc zx297520v3_top_dividers[] = {
+	DIV("timer-t08-div", "timer-t08-mux",   0x4c,  8, 4),
+	DIV("timer-t09-div", "timer-t09-mux",   0x4c,  0, 4),
+	DIV("timer-t12-div", "timer-t12-mux",   0x48,  0, 4),
+	DIV("timer-t13-div", "timer-t13-mux",   0x50,  0, 4),
+	DIV("timer-t14-div", "timer-t14-mux",   0x50,  4, 4),
+	DIV("timer-t15-div", "timer-t15-mux",   0x48,  4, 4),
+	DIV("timer-t16-div", "timer-t16-mux",   0x50,  8, 4),
+	DIV("timer-t17-div", "timer-t17-mux",  0x124,  0, 4),
+	DIV("wdt-t18-div",   "wdt-t18-mux",     0x48,  8, 4),
+	DIV("usim1-div",     clk_unknown,       0x48, 12, 1),
+};
+
+static const struct zx_gate_desc zx297520v3_top_gates[] = {
+	/* topcrm input gates. These are complicated, but shed a lot of light on the board's clock
+	 * distribution.
+	 *
+	 * Settable mask: 0x017333FF. Generally one nibble per PLL, except for MPLL that has more.
+	 * One for clock-26m work clock. pclk and clock-32k are not gated globally here or anywhere
+	 * else as far as I can see.
+	 *
+	 * If you want to test your understanding of this board, try to manually configure it into a
+	 * setup where bit 24 or bit 9 are off without crashing it.
+	 */
+
+	/* Bit 0: No known consumer */
+	/* Bit 1: M0's 78 MHz selection, but somehow also involved in USB too */
+	GATE(0,                            "top-mpll-d2",    "mpll-d2",       0x140,  1, 0),
+	/* Bit 2: No known consumer, named mpll_48m_clk in ZTE's cpko.ko blob */
+	GATE(0,                            "top-mpll-d8",    "mpll-d8",       0x140,  3, 0),
+	GATE(0,                            "top-mpll-d6",    "mpll-d6",       0x140,  4, 0),
+	/* Bit 5: No known consumer. mpll_156m_clk in cpko.ko */
+	/* Bit 6: AXI voting candidate, might be selected by rprocs without notification */
+	GATE(ZX297520V3_MPLL_D5,           "matrix-mpll-d5", "mpll-d5",       0x140,  6,
+		CLK_IS_CRITICAL),
+	/* Bit 7: No known consumer */
+	/* Bit 8: Has a consumer, LTE depends on it. But unknown what and how */
+	/* Bit 9: Big MPLL provider for Matrix. Candidate in the AXI election, thus critical */
+	GATE(ZX297520V3_MPLL,              "matrix-mpll",    "mpll",          0x140,  9,
+		CLK_IS_CRITICAL),
+	/* Bit 10: Always 0 */
+	/* Bit 11: Always 0 */
+	/* Bit 12: No known consumer. Existing patterns indicate a dpll consumer in top itself */
+	GATE(ZX297520V3_DPLL,              "matrix-dpll",    "dpll",          0x140, 13, 0),
+	/* Bit 14: Always 0 */
+	/* Bit 15: Always 0 */
+	GATE(0,                            "top-upll-d12",   "upll-d12",      0x140, 16, 0),
+	GATE(0,                            "top-upll",       "upll",          0x140, 17, 0),
+	/* Bit 18: Always 0 */
+	/* Bit 19: Always 0 */
+	GATE(0,                            "top-gpll-d4",    "gpll-d4",       0x140, 20, 0),
+	GATE(ZX297520V3_GPLL_D2,           "matrix-gpll-d2", "gpll-d2",       0x140, 21, 0),
+	GATE(ZX297520V3_GPLL,              "matrix-gpll",    "gpll",          0x140, 22, 0),
+	/* Bit 23: Always 0 */
+	/* Bit 24 covers all 26 MHz on matrix, but not pclks. It is an AXI voting candidate.
+	 * clock-26m consumers on topcrm to not use this, they get the ungated clock-26m.
+	 */
+	GATE(ZX297520V3_GATED_OSC26M,      "matrix-osc26m",  "clock-26m",     0x140, 24,
+		CLK_IS_CRITICAL),
+
+	/* AHB clock gates: Disabling these cuts off the A53 from register access into 0x130000/
+	 * 0x140000, including the topcrm region, so it can't be re-enabled. Mark them critical for
+	 * that reason.
+	 *
+	 * Gating this off does not stop the M0 rproc, nor does it stop M0 from accessing topcrm. It
+	 * does however gate off M0 from the AXI interconnect, so it can't read RAM, Matrix, etc. So
+	 * these bits here merely gate the bus, not the actual devices.
+	 */
+	GATE(ZX297520V3_AHB_PCLK,          "ahb-pclk",       "ahb-mux",        0x54, 12,
+		CLK_IS_CRITICAL),
+	GATE(ZX297520V3_AHB_WCLK,          "ahb-wclk",       "m0-mux",         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",      "top-mpll-d2-d13", 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. ZTE says 240 MHz, but 480 is the usual one for
+	 * HSIC.
+	 */
+	GATE(ZX297520V3_HSIC_WCLK,         "hsic-wclk",     "top-upll",        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,      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,      0x58, 10, 0),
+	GATE(ZX297520V3_LPM_GSM_PCLK,      "lpm-gsm-pclk",   clk_main[0],      0x58, 11, 0),
+	GATE(ZX297520V3_LPM_LTE_WCLK,      "lpm-lte-wclk",   clk_unknown,      0x58,  8, 0),
+	GATE(ZX297520V3_LPM_LTE_PCLK,      "lpm-lte-pclk",   clk_main[0],      0x58,  9, 0),
+	GATE(ZX297520V3_LPM_TD_WCLK,       "lpm-td-wclk",    clk_unknown,      0x58,  6, 0),
+	GATE(ZX297520V3_LPM_TD_PCLK,       "lpm-td-pclk",    clk_main[0],      0x58,  7, 0),
+	/* This one is called gsm_156m in cpko.ko, thus a candidate for an unknown 0x140 bit. */
+	GATE(ZX297520V3_LPM_W_WCLK,        "lpm-w-wclk",     clk_unknown,      0x58,  4, 0),
+	GATE(ZX297520V3_LPM_W_PCLK,        "lpm-w-pclk",     clk_main[0],      0x58,  5, 0),
+	/* cpko.ko has a clock "gsm_48m" on bit 2, a candidate for an unknown 0x140 bit. */
+
+	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",    "clock-32k",      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),
+	.fixed_divs = zx297520v3_top_fixed_divs,
+	.num_fixed_divs = ARRAY_SIZE(zx297520v3_top_fixed_divs),
+	.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 clk_zx297520v3_probe(struct platform_device *pdev)
+{
+	const struct platform_device_id *id = platform_get_device_id(pdev);
+
+	if (!id)
+		return -ENODEV;
+
+	return zx_clk_common_probe(&pdev->dev, pdev->dev.parent->of_node,
+				   (const struct zx_clk_data *)id->driver_data);
+}
+
+static const struct platform_device_id clk_zx297520v3_ids[] = {
+	{
+		.name = "zx297520v3-topclk",
+		.driver_data = (kernel_ulong_t)&zx297520v3_topclk_data,
+	},
+	{ }
+};
+MODULE_DEVICE_TABLE(platform, clk_zx297520v3_ids);
+
+static struct platform_driver clk_zx297520v3 = {
+	.probe = clk_zx297520v3_probe,
+	.driver = {
+		.name = "clk-zx297520v3",
+	},
+	.id_table = clk_zx297520v3_ids,
+};
+module_platform_driver(clk_zx297520v3);
+
+MODULE_AUTHOR("Stefan Dösinger <stefandoesinger@gmail.com>");
+MODULE_DESCRIPTION("ZTE zx297520v3 clock driver");
+MODULE_LICENSE("GPL");

-- 
2.54.0


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v7 13/13] ARM: dts: zte: Declare zx297520v3 CRM device nodes
  2026-07-16 21:35 [PATCH v7 00/13] ZTE zx297520v3 clock bindings and driver Stefan Dösinger
                   ` (8 preceding siblings ...)
  2026-07-16 21:35 ` [PATCH v7 09/13] clk: zte: Introduce a driver for zx297520v3 top clocks Stefan Dösinger
@ 2026-07-16 22:00 ` Stefan Dösinger
  9 siblings, 0 replies; 17+ messages in thread
From: Stefan Dösinger @ 2026-07-16 22:00 UTC (permalink / raw)
  To: linux-arm-kernel
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Linus Walleij,
	devicetree, linux-kernel

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 7:
Add phy nodes to topcrm
Remove syscon from matrixclk again
Adapt remove now-removed extra clk inputs from matrix
Add reset to LSP
osc26m -> clock-26m for the fixed rate clocks, likewise for 32k

Version 6:
Add syscon to matrixclk

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 | 92 ++++++++++++++++++++++++++++++++---
 1 file changed, 84 insertions(+), 8 deletions(-)

diff --git a/arch/arm/boot/dts/zte/zx297520v3.dtsi b/arch/arm/boot/dts/zte/zx297520v3.dtsi
index a16c30a164bb..b479d097f004 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: clock-26m {
 		compatible = "fixed-clock";
 		clock-frequency = <26000000>;
+		#clock-cells = <0>;
+	};
+
+	osc32k: clock-32k {
+		compatible = "fixed-clock";
+		clock-frequency = <32768>;
+		#clock-cells = <0>;
 	};
 
 	timer {
@@ -70,13 +75,82 @@ gic: interrupt-controller@f2000000 {
 			      <0xf2040000 0x20000>;
 		};
 
+		topcrm: clock-controller@13b000 {
+			compatible = "zte,zx297520v3-topcrm", "syscon";
+			reg = <0x0013b000 0x400>;
+			#address-cells = <1>;
+			#size-cells = <0>;
+			#clock-cells = <1>;
+			#reset-cells = <1>;
+			clocks = <&osc26m>, <&osc32k>;
+			clock-names = "osc26m", "osc32k";
+
+			syscon-reboot {
+				compatible = "syscon-reboot";
+				offset = <0x0>;
+				value = <0x1>;
+				mask = <0x1>;
+			};
+
+			usb-phy@84 {
+				compatible = "zte,zx297520v3-usb-phy";
+				reg = <0x84>;
+				interrupts = <GIC_SPI 42 IRQ_TYPE_EDGE_RISING>,
+					     <GIC_SPI 43 IRQ_TYPE_EDGE_RISING>,
+					     <GIC_SPI 45 IRQ_TYPE_EDGE_RISING>,
+					     <GIC_SPI 46 IRQ_TYPE_EDGE_RISING>;
+				interrupt-names = "usb-up", "usb-down", "hsic-up", "hsic-down";
+				resets = <&topcrm ZX297520V3_USB_PHY_RESET>,
+					 <&topcrm ZX297520V3_HSIC_PHY_RESET>;
+				reset-names = "usb", "hsic";
+				#phy-cells = <1>;
+			};
+		};
+
+		matrixcrm: clock-controller@1306000 {
+			compatible = "zte,zx297520v3-matrixcrm";
+			reg = <0x01306000 0x400>;
+			clocks = <&osc26m>, <&osc32k>,
+				 <&topcrm ZX297520V3_MPLL>, <&topcrm ZX297520V3_MPLL_D5>,
+				 <&topcrm ZX297520V3_DPLL>, <&topcrm ZX297520V3_GPLL>,
+				 <&topcrm ZX297520V3_GPLL_D2>, <&topcrm ZX297520V3_GATED_OSC26M>;
+			clock-names = "osc26m", "osc32k", "mpll", "mpll-d5", "dpll",
+				"gpll", "gpll-d2", "wclk-osc26m";
+			#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";
+			resets = <&matrixcrm ZX297520V3_LSP_RESET>;
+		};
+
+
 		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 +159,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 +170,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.54.0

^ permalink raw reply related	[flat|nested] 17+ messages in thread

* Re: [PATCH v7 01/13] dt-bindings: phy: Add zx297520v3 USB phy documentation
  2026-07-16 21:35 ` [PATCH v7 01/13] dt-bindings: phy: Add zx297520v3 USB phy documentation Stefan Dösinger
@ 2026-07-16 22:24   ` sashiko-bot
  0 siblings, 0 replies; 17+ messages in thread
From: sashiko-bot @ 2026-07-16 22:24 UTC (permalink / raw)
  To: Stefan Dösinger
  Cc: linux-phy, conor+dt, olteanv, devicetree, robh, neil.armstrong,
	vkoul

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] The example uses an undocumented compatible string 'zte,zx297520v3-topcrm', which will break `make dt_binding_check` for this commit.
--

--- Patch [1]: [PATCH v7 01/13] dt-bindings: phy: Add zx297520v3 USB phy documentation ---
commit 4c69b2717ebf9691d299b4744633e7788d392913
Author: Stefan Dösinger <stefandoesinger@gmail.com>

dt-bindings: phy: Add zx297520v3 USB phy documentation

This commit adds device tree binding documentation for the ZTE zx297520v3 USB
and HSIC PHY. It defines the required properties for the PHY, which acts as
a child node to the topcrm controller.

> diff --git a/Documentation/devicetree/bindings/phy/zte,zx297520v3-usb-phy.yaml b/Documentation/devicetree/bindings/phy/zte,zx297520v3-usb-phy.yaml
> new file mode 100644
> index 0000000000000..8bad9365d5cc0
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/phy/zte,zx297520v3-usb-phy.yaml
[ ... ]
> +examples:
> +  - |
> +    #include <dt-bindings/interrupt-controller/arm-gic.h>
> +    #include <dt-bindings/phy/phy-zte-zx297520v3-usb.h>
> +
> +    topcrm: clock-controller {
> +        compatible = "zte,zx297520v3-topcrm", "syscon";

[Severity: Low]
Will this undocumented compatible string cause a regression in
`make dt_binding_check` during bisection?

Since the "zte,zx297520v3-topcrm" binding is added in the next patch, evaluating
this commit on its own will result in validation failures because the string is
not documented in any schema and is not an allowed fallback in syscon.yaml.

Additionally, does the example node need a "reg" property since it includes
"syscon" in the compatible string? syscon.yaml requires it, which may lead to
further validation errors.

> +        #address-cells = <1>;
> +        #size-cells = <0>;
> +        #reset-cells = <1>;

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260717-zx29clk-v7-0-408411cfcf36@gmail.com?part=1

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH v7 05/13] mfd: zx297520v3: Add a clock and reset MFD driver
  2026-07-16 21:35 ` [PATCH v7 05/13] mfd: zx297520v3: Add a clock and reset MFD driver Stefan Dösinger
@ 2026-07-16 22:24   ` sashiko-bot
  0 siblings, 0 replies; 17+ messages in thread
From: sashiko-bot @ 2026-07-16 22:24 UTC (permalink / raw)
  To: Stefan Dösinger
  Cc: olteanv, neil.armstrong, devicetree, robh, linux-phy, conor+dt,
	vkoul

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] Complex platform data structures (`mfd_cell` arrays) are passed directly through the `.data` field of the `of_device_id` match table.
- [Medium] The MFD core driver is incorrectly located in `drivers/soc/zte/` instead of `drivers/mfd/`.
- [Medium] The driver uses `PLATFORM_DEVID_NONE` instead of `PLATFORM_DEVID_AUTO` for subdevice indexing.
--

--- Patch [5]: [PATCH v7 05/13] mfd: zx297520v3: Add a clock and reset MFD driver ---
commit 040c43d18f894269b77d87091f84cd5ad6b9755d
Author: Stefan Dösinger <stefandoesinger@gmail.com>

mfd: zx297520v3: Add a clock and reset MFD driver

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.

> diff --git a/drivers/soc/zte/Makefile b/drivers/soc/zte/Makefile
> new file mode 100644
> index 0000000000000..090ba8aa06c1e
> --- /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

[Severity: Medium]
Since this driver utilizes the devm_mfd_add_devices() API and registers
multiple child devices, should it be located in drivers/mfd/ instead of
drivers/soc/zte/?

The MFD guidelines state that placing MFD core drivers outside of drivers/mfd/
violates subsystem layering and bypasses proper subsystem maintainer review.

[ ... ]

> diff --git a/drivers/soc/zte/zx297520v3-crm.c b/drivers/soc/zte/zx297520v3-crm.c
> new file mode 100644
> index 0000000000000..8b82ccba4e90d
> --- /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);
> +	if (!data)
> +		return -ENODEV;
> +
> +	return devm_mfd_add_devices(&pdev->dev, PLATFORM_DEVID_NONE, data->cells,

[Severity: Medium]
Is it intentional to use PLATFORM_DEVID_NONE here?

The MFD subsystem guidelines prefer using PLATFORM_DEVID_AUTO for subdevice
indexing to avoid sysfs device naming collisions and driver load failures.

> +				    data->num_cells, NULL, 0, NULL);
> +}
> +
> +static const struct of_device_id of_match_zx297520v3_crm[] = {
> +	{ .compatible = "zte,zx297520v3-topcrm", .data = &zx297520v3_topcrm_data },

[Severity: High]
Does passing complex platform data structures (like the mfd_cell array inside
zx297520v3_topcrm_data) directly through the .data field of the of_device_id
match table pose a memory safety hazard and lead to initialization ordering
races?

The MFD guidelines suggest storing an integer ID or enum in the match table's
.data field, and using a switch statement in zx297520v3_crm_probe() to select
the correct static const mfd_cell array.

> +	{ .compatible = "zte,zx297520v3-matrixcrm", .data = &zx297520v3_matrixcrm_data },
> +	{ .compatible = "zte,zx297520v3-lspcrm", .data = &zx297520v3_lspcrm_data },
> +	{ }
> +};

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260717-zx29clk-v7-0-408411cfcf36@gmail.com?part=5

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH v7 03/13] dt-bindings: clk: zte: Add zx297520v3 matrix clock and reset controller
  2026-07-16 21:35 ` [PATCH v7 03/13] dt-bindings: clk: zte: Add zx297520v3 matrix " Stefan Dösinger
@ 2026-07-16 22:25   ` sashiko-bot
  0 siblings, 0 replies; 17+ messages in thread
From: sashiko-bot @ 2026-07-16 22:25 UTC (permalink / raw)
  To: Stefan Dösinger
  Cc: vkoul, linux-phy, neil.armstrong, olteanv, robh, conor+dt,
	devicetree

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] The newly added clock and reset macro definitions overlap with existing definitions for a different controller (topcrm) in the same header files, using the same `ZX297520V3_` prefix without disambiguation.
--

--- Patch [3]: [PATCH v7 03/13] dt-bindings: clk: zte: Add zx297520v3 matrix clock and reset controller ---
commit 5ec0de6373fd034a146a7bae84009d3814552ed6
Author: Stefan Dösinger <stefandoesinger@gmail.com>

dt-bindings: clk: zte: Add zx297520v3 matrix clock and reset controller

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.

> diff --git a/include/dt-bindings/clock/zte,zx297520v3-clk.h b/include/dt-bindings/clock/zte,zx297520v3-clk.h
> index 1f010ff4e6ca6..09ee3de679d96 100644
> --- a/include/dt-bindings/clock/zte,zx297520v3-clk.h
> +++ b/include/dt-bindings/clock/zte,zx297520v3-clk.h
> @@ -63,4 +63,45 @@
>  #define ZX297520V3_HSIC_WCLK			55
>  #define ZX297520V3_HSIC_PCLK			56
>  
> +#define ZX297520V3_SYS_TIMER_WCLK		1
> +#define ZX297520V3_CPU_WCLK			2

[Severity: Low]
Could these new clock macros overlap with the existing ones in this header
and cause confusion?

The existing topcrm clocks in this header also start at 1 and use the same
ZX297520V3_ prefix, for example ZX297520V3_M0_WCLK. Is there a risk that a
developer might accidentally use a matrixcrm clock macro for a topcrm node
or vice versa? If they do, the C preprocessor will substitute the
overlapping integer without error.

Would it be safer to add a distinct prefix for the matrixcrm clocks, such
as ZX297520V3_MATRIX_?

[ ... ]

> diff --git a/include/dt-bindings/reset/zte,zx297520v3-reset.h b/include/dt-bindings/reset/zte,zx297520v3-reset.h
> index 43db72bb59de0..4682d4a599c6a 100644
> --- a/include/dt-bindings/reset/zte,zx297520v3-reset.h
> +++ b/include/dt-bindings/reset/zte,zx297520v3-reset.h
> @@ -29,4 +29,16 @@
>  #define ZX297520V3_HSIC_PHY_RESET		20
>  #define ZX297520V3_HSIC_RESET			21
>  
> +#define ZX297520V3_CPU_RESET			0
> +#define ZX297520V3_DDR_CTRL_RESET		1

[Severity: Low]
Does this identical prefix also cause an overlap risk for reset definitions?

The new matrixcrm resets start at 0 and use the ZX297520V3_ prefix, which
overlaps with the existing topcrm resets like ZX297520V3_ZSP_RESET.

Could adding a matrixcrm-specific prefix here also help prevent potential
device tree configuration errors?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260717-zx29clk-v7-0-408411cfcf36@gmail.com?part=3

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH v7 06/13] clk: zte: Add Clock registration infrastructure
  2026-07-16 21:35 ` [PATCH v7 06/13] clk: zte: Add Clock registration infrastructure Stefan Dösinger
@ 2026-07-16 22:26   ` sashiko-bot
  0 siblings, 0 replies; 17+ messages in thread
From: sashiko-bot @ 2026-07-16 22:26 UTC (permalink / raw)
  To: Stefan Dösinger
  Cc: vkoul, linux-phy, robh, conor+dt, devicetree, olteanv,
	neil.armstrong

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] In `zx_clk_common_probe()`, the error path for `data->init(map)` incorrectly returns `PTR_ERR(clk)` instead of propagating the actual error code `res`.
--

--- Patch [6]: [PATCH v7 06/13] clk: zte: Add Clock registration infrastructure ---
commit 377a6af7769b840d87a85b32af3001fd5f4431dd
Author: Stefan Dösinger <stefandoesinger@gmail.com>

clk: zte: Add Clock registration infrastructure

This commit adds the common clock registration infrastructure for ZTE SoCs.
It includes support for registering PLLs, muxes, dividers, and gates using
regmap, and introduces a common probe function to initialize them.

> diff --git a/drivers/clk/zte/clk-zx.c b/drivers/clk/zte/clk-zx.c
> new file mode 100644
> index 0000000000000..db4fad2b4b056
> --- /dev/null
> +++ b/drivers/clk/zte/clk-zx.c
[ ... ]
> @@ -0,0 +1,155 @@
[ ... ]
> +int zx_clk_common_probe(struct device *dev, struct device_node *of_node,
> +			const struct zx_clk_data *data)
> +{
[ ... ]
> +	if (data->init) {
> +		res = data->init(map);
> +		if (res)
> +			return dev_err_probe(dev, PTR_ERR(clk), "Controller init failure\n");

[Severity: High]
Does this code pass the wrong error code to dev_err_probe() in the
zx_clk_common_probe() error path?

When data->init(map) fails, it returns a non-zero error code into res.
However, the error handling block incorrectly evaluates and returns
PTR_ERR(clk).

Since clk is merely an iteration variable from the preceding loops, it could
be completely uninitialized if num_inputs_enable and num_inputs are 0, which
might trick the driver core into assuming the probe succeeded despite
initialization failing.

If the loops did execute successfully, clk holds a valid pointer, and
PTR_ERR(clk) will return a random large number depending on the pointer's
memory address, resulting in a meaningless error code.

Should this return res instead of PTR_ERR(clk)?

> +	}
> +
> +	res = zx_clk_register_plls(dev, map, data->plls, data->num_plls);
> +	if (res)
> +		return res;
[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260717-zx29clk-v7-0-408411cfcf36@gmail.com?part=6

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH v7 08/13] clk: zte: Add zx PLL support infrastructure
  2026-07-16 21:35 ` [PATCH v7 08/13] clk: zte: Add zx PLL support infrastructure Stefan Dösinger
@ 2026-07-16 22:34   ` sashiko-bot
  0 siblings, 0 replies; 17+ messages in thread
From: sashiko-bot @ 2026-07-16 22:34 UTC (permalink / raw)
  To: Stefan Dösinger
  Cc: conor+dt, devicetree, neil.armstrong, linux-phy, olteanv, vkoul,
	robh

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] zx29_pll_calc_values fails to clamp out-of-bounds frequencies, causing rate determination to abort instead of returning the closest achievable rate.
- [High] .determine_rate does not enforce req->min_rate and req->max_rate boundaries.
- [Medium] Using `dev_info` inside `zx29_pll_set_rate` will spam the kernel log.
--

--- Patch [8]: [PATCH v7 08/13] clk: zte: Add zx PLL support infrastructure ---
commit c3e5de791970b113941346866ebf54af4b186d7d
Author: Stefan Dösinger <stefandoesinger@gmail.com>

clk: zte: Add zx PLL support infrastructure

This commit adds PLL support infrastructure for ZTE zx chips, specifically 
tested with the zx297520v3. It provides the standard clock operations 
including rate calculation, parent selection, and enabling/disabling 
of the PLLs.

> diff --git a/drivers/clk/zte/pll-zx.c b/drivers/clk/zte/pll-zx.c
> index f8ddb4c7dff66..701e580609f2b 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)
> +{

[ ... ]

> +	for (j = 1; j <= postdiv2_max; j++) {
> +		for (i = 1; i <= postdiv1_max; i++) {
> +			u64 vco = (u64)rate * i * j;
> +			unsigned long out;
> +
> +			if (vco > ZX29_PLL_VCO_MAX_FREQ || vco < ZX29_PLL_VCO_MIN_FREQ)
> +				continue;

[Severity: High]
Does this logic fail to clamp out-of-bounds frequencies?

If a consumer requests a clock rate where the maximum evaluated VCO 
(e.g., 10 MHz * 7 * 7 = 490 MHz) is less than ZX29_PLL_VCO_MIN_FREQ, 
this loop will just continue and return 0.

Should the requested rate be clamped to the physical minimum and maximum 
achievable bounds before computing the VCO to ensure it returns the closest 
supported rate?

[ ... ]

> +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;
> +	u32 setting;

[ ... ]

> +	setting = zx29_pll_calc_values(pll, req->best_parent_rate, req->rate);
> +	if (!setting)
> +		return -EINVAL;
> +
> +	new_rate = zx29_pll_get_rate(pll, req->best_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;

[Severity: High]
Does this assignment violate the caller's min/max rate constraints?

If the closest hardware rate falls outside the req->min_rate and req->max_rate 
boundaries, assigning it back to req->rate could cause the clock framework to 
reject the rate change entirely, even if another slightly less optimal but 
in-bounds rate was available.

> +	}
> +
> +	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;
> +
> +	/* PLLs can be changed while they are running and in testing the downstream hardware
> +	 * handles it gracefully - as long as the new rate is a rate the hardware can operate at.
> +	 */
> +	setting = zx29_pll_calc_values(pll, parent_rate, rate);
> +	if (!setting)
> +		return -EINVAL;
> +
> +	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);

[Severity: Medium]
Could this dev_info call cause log spam during dynamic frequency scaling?

If this PLL is used for scaling operations where .set_rate is called 
hundreds of times per second, this will rapidly flood the kernel log.
Would dev_dbg be more appropriate for this hot path?

> +	}
> +
> +	return res;
> +}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260717-zx29clk-v7-0-408411cfcf36@gmail.com?part=8

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH v7 09/13] clk: zte: Introduce a driver for zx297520v3 top clocks
  2026-07-16 21:35 ` [PATCH v7 09/13] clk: zte: Introduce a driver for zx297520v3 top clocks Stefan Dösinger
@ 2026-07-16 22:42   ` sashiko-bot
  0 siblings, 0 replies; 17+ messages in thread
From: sashiko-bot @ 2026-07-16 22:42 UTC (permalink / raw)
  To: Stefan Dösinger
  Cc: devicetree, olteanv, robh, vkoul, neil.armstrong, linux-phy,
	conor+dt

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] Using `clk_main[0]` to initialize the `.parent` field in the `zx297520v3_top_gates` static array is a violation of ISO C rules for constant expressions, resulting in a hard compile error.
--

--- Patch [9]: [PATCH v7 09/13] clk: zte: Introduce a driver for zx297520v3 top clocks ---
commit 8a7fee3879cf08b60157cde425eed4bbaa05fcb1
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 SoC top clocks.
It supports core devices such as PLLs, the AHB bus, timers, the USB
controller, and the Cortex M0 processor.

> diff --git a/drivers/clk/zte/clk-zx297520v3.c b/drivers/clk/zte/clk-zx297520v3.c
> new file mode 100644
> index 0000000000000..20ff00ba5f670
> --- /dev/null
> +++ b/drivers/clk/zte/clk-zx297520v3.c

[ ... ]

> +static const struct zx_gate_desc zx297520v3_top_gates[] = {

[ ... ]

> +	/* AHB clock gates: Disabling these cuts off the A53 from register access into 0x130000/
> +	 * 0x140000, including the topcrm region, so it can't be re-enabled. Mark them critical for
> +	 * that reason.
> +	 *
> +	 * Gating this off does not stop the M0 rproc, nor does it stop M0 from accessing topcrm. It
> +	 * does however gate off M0 from the AXI interconnect, so it can't read RAM, Matrix, etc. So
> +	 * these bits here merely gate the bus, not the actual devices.
> +	 */
> +	GATE(ZX297520V3_AHB_PCLK,          "ahb-pclk",       "ahb-mux",        0x54, 12,
> +		CLK_IS_CRITICAL),
> +	GATE(ZX297520V3_AHB_WCLK,          "ahb-wclk",       "m0-mux",         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),

[Severity: Low]
Does using clk_main[0] to initialize elements of a static array cause a build
failure?

In C, array accesses are not treated as constant expressions. Initializing the
zx297520v3_top_gates static array with clk_main[0] will cause the compiler
to fail with an "initializer element is not constant" error.

Could a string literal like "clock-26m" be used directly instead?

> +	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),

[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260717-zx29clk-v7-0-408411cfcf36@gmail.com?part=9

^ permalink raw reply	[flat|nested] 17+ messages in thread

end of thread, other threads:[~2026-07-16 22:42 UTC | newest]

Thread overview: 17+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-16 21:35 [PATCH v7 00/13] ZTE zx297520v3 clock bindings and driver Stefan Dösinger
2026-07-16 21:35 ` [PATCH v7 01/13] dt-bindings: phy: Add zx297520v3 USB phy documentation Stefan Dösinger
2026-07-16 22:24   ` sashiko-bot
2026-07-16 21:35 ` [PATCH v7 02/13] dt-bindings: clk: zte: Add zx297520v3 top clock and reset controller Stefan Dösinger
2026-07-16 21:35 ` [PATCH v7 03/13] dt-bindings: clk: zte: Add zx297520v3 matrix " Stefan Dösinger
2026-07-16 22:25   ` sashiko-bot
2026-07-16 21:35 ` [PATCH v7 04/13] dt-bindings: clk: zte: Add zx297520v3 LSP " Stefan Dösinger
2026-07-16 21:35 ` [PATCH v7 05/13] mfd: zx297520v3: Add a clock and reset MFD driver Stefan Dösinger
2026-07-16 22:24   ` sashiko-bot
2026-07-16 21:35 ` [PATCH v7 06/13] clk: zte: Add Clock registration infrastructure Stefan Dösinger
2026-07-16 22:26   ` sashiko-bot
2026-07-16 21:35 ` [PATCH v7 07/13] clk: zte: Add regmap based clocks Stefan Dösinger
2026-07-16 21:35 ` [PATCH v7 08/13] clk: zte: Add zx PLL support infrastructure Stefan Dösinger
2026-07-16 22:34   ` sashiko-bot
2026-07-16 21:35 ` [PATCH v7 09/13] clk: zte: Introduce a driver for zx297520v3 top clocks Stefan Dösinger
2026-07-16 22:42   ` sashiko-bot
2026-07-16 22:00 ` [PATCH v7 13/13] 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