Linux-ARM-Kernel Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v6 00/13] Ambarella CV75 SoC minimal bring-up
@ 2026-09-04  6:38 Long Zhao via B4 Relay
  2026-09-04  6:38 ` [PATCH v6 01/13] dt-bindings: arm: add Ambarella CV75 platforms Long Zhao via B4 Relay
                   ` (13 more replies)
  0 siblings, 14 replies; 28+ messages in thread
From: Long Zhao via B4 Relay @ 2026-09-04  6:38 UTC (permalink / raw)
  To: Arnd Bergmann, Krzysztof Kozlowski, Alexandre Belloni, soc,
	linux-arm-kernel
  Cc: Long Zhao, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Michael Turquette, Stephen Boyd, Linus Walleij,
	Bartosz Golaszewski, Greg Kroah-Hartman, Jiri Slaby,
	Andy Shevchenko, Ilpo Järvinen, Catalin Marinas, Will Deacon,
	Long Zhao, Lee Jones, mfd, devicetree, linux-clk, linux-gpio,
	linux-serial, linux-kernel, Krzysztof Kozlowski

This series adds minimal Ambarella CV75 support for early bring-up with
a serial console: DT bindings, RCT/CCU clocks, pinctrl, PL061 GPIO via
gpio-regmap, 8250_dw UART quirks, ARCH_AMBARELLA, CV75 EVK DT, and
MAINTAINERS.

This is a single unified series. Please apply via the SoC tree;
subsystem maintainers are Cc'd for their pieces.

Verified with:
- scripts/checkpatch.pl --strict (0 errors)
- make dt_binding_check DT_SCHEMA_FILES=ambarella:pl061-gpio.yaml:syscon.yaml:snps-dw-apb-uart.yaml
- make CHECK_DTBS=y ambarella/cv75-evk.dtb
- arm64 build of new drivers/DTS
- CV75 EVK: console via 8250_dw (dw-apb-uart); GPIO edge IRQ
  (gpio-hammer / gpio-event-mon)

Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
Changes in v6:
- gpio: convert gpio-pl061 data path to gpio-regmap; keep PL061 irqchip
  (Andy, Linus). Add write_data_after_dir + girq helpers to gpio-regmap.
  Ambarella layout uses JEP106 periphid 0x000e8061; no AFSEL clearing in
  the GPIO driver; Ambarella skips unverified GPIO PM save/restore
- serial: restore 8250_dw Ambarella quirks from v3; drop standalone
  8250_ambarella (Andy). Commit message clarifies Ambarella IP with a
  DW-like register map needing only a thin quirk. Binding under
  snps-dw-apb-uart; DT uses ambarella,cv75-uart + snps,dw-apb-uart with
  reg-shift/reg-io-width
- clock/RCT: document #reset-cells (Krzysztof); soft-reset ID header and
  reset provider deferred (no resets= consumers in this series)
- arm64/Kconfig: do not select ARM_PSCI / HAVE_ARM_ARCH_TIMER (Sashiko)
- pinctrl: no Ambarella suspend/resume claim without validated PM

Link to v5:
https://lore.kernel.org/r/20260818-cv75-v5-v5-0-7bbb12f0360f@ambarella.com
Link to v2:
https://lore.kernel.org/r/20260806-longzhao-upstream-cv75-v2-v2-0-6b09707c5fe9@ambarella.com
Link to v3-A:
https://lore.kernel.org/r/20260813-cv75-v3-a-v3-0-cfdd20b1b4dd@ambarella.com
Link to v3-B:
https://lore.kernel.org/r/20260813-cv75-v3-b-v3-0-1d82e18da09b@ambarella.com
Link to v3-C:
https://lore.kernel.org/r/20260813-cv75-v3-c-v3-0-9352b3fd6026@ambarella.com
Link to v4-B:
https://lore.kernel.org/r/20260814-cv75-v4-b-v4-0-622e63153d97@ambarella.com

To: Arnd Bergmann <arnd@arndb.de>
To: Krzysztof Kozlowski <krzk@kernel.org>
To: Alexandre Belloni <alexandre.belloni@bootlin.com>
To: soc@lists.linux.dev
To: linux-arm-kernel@lists.infradead.org
Cc: Rob Herring <robh@kernel.org>
Cc: Krzysztof Kozlowski <krzk+dt@kernel.org>
Cc: Conor Dooley <conor+dt@kernel.org>
Cc: Michael Turquette <mturquette@baylibre.com>
Cc: Stephen Boyd <sboyd@kernel.org>
Cc: Linus Walleij <linusw@kernel.org>
Cc: Bartosz Golaszewski <brgl@kernel.org>
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Jiri Slaby <jirislaby@kernel.org>
Cc: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Cc: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Long Zhao <zl020895@163.com>
Cc: Lee Jones <lee@kernel.org>
Cc: mfd@lists.linux.dev
Cc: devicetree@vger.kernel.org
Cc: linux-clk@vger.kernel.org
Cc: linux-gpio@vger.kernel.org
Cc: linux-serial@vger.kernel.org
Cc: linux-kernel@vger.kernel.org

---
Long Zhao (13):
      dt-bindings: arm: add Ambarella CV75 platforms
      dt-bindings: mfd: syscon: add Ambarella CV75 secure scratchpad
      dt-bindings: clock: add Ambarella CV75 RCT
      dt-bindings: pinctrl: add Ambarella CV75 pinctrl
      dt-bindings: gpio: pl061: add Ambarella CV75 variant
      dt-bindings: serial: snps-dw-apb-uart: add ambarella,cv75-uart
      clk: ambarella: add CV75 CCU driver
      gpio: regmap: support write_data_after_dir and girq
      gpio: pl061: use gpio-regmap and add Ambarella layout
      pinctrl: ambarella: add CV75 pin controller
      serial: 8250_dw: add Ambarella CV75 quirks
      arm64: ambarella: add ARCH_AMBARELLA and CV75 EVK DT
      MAINTAINERS: add ARM/AMBARELLA SoC support

 .../devicetree/bindings/arm/ambarella.yaml         |  24 +
 .../bindings/clock/ambarella,cv75-rct.yaml         |  64 ++
 .../devicetree/bindings/gpio/pl061-gpio.yaml       |  32 +-
 Documentation/devicetree/bindings/mfd/syscon.yaml  |   1 +
 .../bindings/pinctrl/ambarella,cv75-pinctrl.yaml   | 239 +++++++
 .../bindings/serial/snps-dw-apb-uart.yaml          |   1 +
 .../devicetree/bindings/vendor-prefixes.yaml       |   2 +
 MAINTAINERS                                        |  14 +
 arch/arm64/Kconfig.platforms                       |   6 +
 arch/arm64/boot/dts/Makefile                       |   1 +
 arch/arm64/boot/dts/ambarella/Makefile             |   3 +
 arch/arm64/boot/dts/ambarella/cv75-evk.dts         |  30 +
 arch/arm64/boot/dts/ambarella/cv75.dtsi            | 177 +++++
 arch/arm64/configs/defconfig                       |   1 +
 drivers/clk/Kconfig                                |   1 +
 drivers/clk/Makefile                               |   1 +
 drivers/clk/ambarella/Kconfig                      |  13 +
 drivers/clk/ambarella/Makefile                     |   6 +
 drivers/clk/ambarella/ccu-cv75.c                   | 296 ++++++++
 drivers/clk/ambarella/ccu_common.c                 |  60 ++
 drivers/clk/ambarella/ccu_common.h                 |  25 +
 drivers/clk/ambarella/ccu_mux_div.c                | 234 +++++++
 drivers/clk/ambarella/ccu_mux_div.h                |  35 +
 drivers/clk/ambarella/ccu_pll.c                    | 421 ++++++++++++
 drivers/clk/ambarella/ccu_pll.h                    |  70 ++
 drivers/gpio/Kconfig                               |   2 +
 drivers/gpio/gpio-pl061.c                          | 342 +++++----
 drivers/gpio/gpio-regmap.c                         |  24 +-
 drivers/pinctrl/Kconfig                            |  15 +
 drivers/pinctrl/Makefile                           |   2 +
 drivers/pinctrl/pinctrl-ambarella-cv75.c           | 561 +++++++++++++++
 drivers/pinctrl/pinctrl-ambarella.c                | 761 +++++++++++++++++++++
 drivers/pinctrl/pinctrl-ambarella.h                |  50 ++
 drivers/tty/serial/8250/8250_dw.c                  |  34 +
 include/dt-bindings/clock/ambarella,cv75-rct.h     |  15 +
 include/linux/amba/bus.h                           |   1 +
 include/linux/gpio/regmap.h                        |  13 +
 37 files changed, 3439 insertions(+), 138 deletions(-)
---
base-commit: 34cf6dafc47441dfb6b356a095b89c3585a93714
change-id: 20260818-cv75-v5-9e2039b349bc

Best regards,
--  
Long Zhao <longzhao@ambarella.com>




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

* [PATCH v6 01/13] dt-bindings: arm: add Ambarella CV75 platforms
  2026-09-04  6:38 [PATCH v6 00/13] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
@ 2026-09-04  6:38 ` Long Zhao via B4 Relay
  2026-09-04 22:39   ` Linus Walleij
  2026-09-04  6:38 ` [PATCH v6 02/13] dt-bindings: mfd: syscon: add Ambarella CV75 secure scratchpad Long Zhao via B4 Relay
                   ` (12 subsequent siblings)
  13 siblings, 1 reply; 28+ messages in thread
From: Long Zhao via B4 Relay @ 2026-09-04  6:38 UTC (permalink / raw)
  To: Arnd Bergmann, Krzysztof Kozlowski, Alexandre Belloni, soc,
	linux-arm-kernel
  Cc: Long Zhao, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Michael Turquette, Stephen Boyd, Linus Walleij,
	Bartosz Golaszewski, Greg Kroah-Hartman, Jiri Slaby,
	Andy Shevchenko, Ilpo Järvinen, Catalin Marinas, Will Deacon,
	Long Zhao, Lee Jones, mfd, devicetree, linux-clk, linux-gpio,
	linux-serial, linux-kernel, Krzysztof Kozlowski

From: Long Zhao <longzhao@ambarella.com>

Add the Ambarella vendor prefix and CV75 EVK board compatible for the
initial SoC bring-up.

Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
 .../devicetree/bindings/arm/ambarella.yaml         | 24 ++++++++++++++++++++++
 .../devicetree/bindings/vendor-prefixes.yaml       |  2 ++
 2 files changed, 26 insertions(+)

diff --git a/Documentation/devicetree/bindings/arm/ambarella.yaml b/Documentation/devicetree/bindings/arm/ambarella.yaml
new file mode 100644
index 000000000000..72946e94fa76
--- /dev/null
+++ b/Documentation/devicetree/bindings/arm/ambarella.yaml
@@ -0,0 +1,24 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/arm/ambarella.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Ambarella SoC platforms
+
+maintainers:
+  - Long Zhao <longzhao@ambarella.com>
+
+properties:
+  $nodename:
+    const: "/"
+
+  compatible:
+    oneOf:
+      - description: Ambarella CV75 boards
+        items:
+          - enum:
+              - ambarella,cv75-evk
+          - const: ambarella,cv75
+
+additionalProperties: true
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml
index 85a27937832e..f48aa6dabbe9 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
+++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
@@ -112,6 +112,8 @@ patternProperties:
     description: Amarula Solutions
   "^amazon,.*":
     description: Amazon.com, Inc.
+  "^ambarella,.*":
+    description: Ambarella, Inc.
   "^amcc,.*":
     description: Applied Micro Circuits Corporation (APM, formally AMCC)
   "^amd,.*":

-- 
2.34.1




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

* [PATCH v6 02/13] dt-bindings: mfd: syscon: add Ambarella CV75 secure scratchpad
  2026-09-04  6:38 [PATCH v6 00/13] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
  2026-09-04  6:38 ` [PATCH v6 01/13] dt-bindings: arm: add Ambarella CV75 platforms Long Zhao via B4 Relay
@ 2026-09-04  6:38 ` Long Zhao via B4 Relay
  2026-09-04  6:38 ` [PATCH v6 03/13] dt-bindings: clock: add Ambarella CV75 RCT Long Zhao via B4 Relay
                   ` (11 subsequent siblings)
  13 siblings, 0 replies; 28+ messages in thread
From: Long Zhao via B4 Relay @ 2026-09-04  6:38 UTC (permalink / raw)
  To: Arnd Bergmann, Krzysztof Kozlowski, Alexandre Belloni, soc,
	linux-arm-kernel
  Cc: Long Zhao, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Michael Turquette, Stephen Boyd, Linus Walleij,
	Bartosz Golaszewski, Greg Kroah-Hartman, Jiri Slaby,
	Andy Shevchenko, Ilpo Järvinen, Catalin Marinas, Will Deacon,
	Long Zhao, Lee Jones, mfd, devicetree, linux-clk, linux-gpio,
	linux-serial, linux-kernel

From: Long Zhao <longzhao@ambarella.com>

Allow ambarella,cv75-sec-scratchpad as a syscon for the shared secure
scratchpad registers on CV75.

Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
 Documentation/devicetree/bindings/mfd/syscon.yaml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/Documentation/devicetree/bindings/mfd/syscon.yaml b/Documentation/devicetree/bindings/mfd/syscon.yaml
index fe882d3f828b..9ebf702c06f1 100644
--- a/Documentation/devicetree/bindings/mfd/syscon.yaml
+++ b/Documentation/devicetree/bindings/mfd/syscon.yaml
@@ -32,6 +32,7 @@ properties:
               - allwinner,sun50i-a64-system-controller
               - altr,l3regs
               - altr,sdr-ctl
+              - ambarella,cv75-sec-scratchpad
               - amd,pensando-elba-syscon
               - amlogic,meson-mx-assist
               - amlogic,meson-mx-bootrom

-- 
2.34.1




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

* [PATCH v6 03/13] dt-bindings: clock: add Ambarella CV75 RCT
  2026-09-04  6:38 [PATCH v6 00/13] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
  2026-09-04  6:38 ` [PATCH v6 01/13] dt-bindings: arm: add Ambarella CV75 platforms Long Zhao via B4 Relay
  2026-09-04  6:38 ` [PATCH v6 02/13] dt-bindings: mfd: syscon: add Ambarella CV75 secure scratchpad Long Zhao via B4 Relay
@ 2026-09-04  6:38 ` Long Zhao via B4 Relay
  2026-09-04  6:38 ` [PATCH v6 04/13] dt-bindings: pinctrl: add Ambarella CV75 pinctrl Long Zhao via B4 Relay
                   ` (10 subsequent siblings)
  13 siblings, 0 replies; 28+ messages in thread
From: Long Zhao via B4 Relay @ 2026-09-04  6:38 UTC (permalink / raw)
  To: Arnd Bergmann, Krzysztof Kozlowski, Alexandre Belloni, soc,
	linux-arm-kernel
  Cc: Long Zhao, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Michael Turquette, Stephen Boyd, Linus Walleij,
	Bartosz Golaszewski, Greg Kroah-Hartman, Jiri Slaby,
	Andy Shevchenko, Ilpo Järvinen, Catalin Marinas, Will Deacon,
	Long Zhao, Lee Jones, mfd, devicetree, linux-clk, linux-gpio,
	linux-serial, linux-kernel

From: Long Zhao <longzhao@ambarella.com>

Document the CV75 RCT clock controller and the clock ID header used by
DT consumers.

Document #reset-cells for the RCT reset-provider role. Soft-reset line
IDs and the reset provider driver are deferred; this series has no
resets= consumers.

Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
 .../bindings/clock/ambarella,cv75-rct.yaml         | 64 ++++++++++++++++++++++
 include/dt-bindings/clock/ambarella,cv75-rct.h     | 15 +++++
 2 files changed, 79 insertions(+)

diff --git a/Documentation/devicetree/bindings/clock/ambarella,cv75-rct.yaml b/Documentation/devicetree/bindings/clock/ambarella,cv75-rct.yaml
new file mode 100644
index 000000000000..be89bd75f9e2
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/ambarella,cv75-rct.yaml
@@ -0,0 +1,64 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/clock/ambarella,cv75-rct.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Ambarella CV75 RCT Clock Controller
+
+maintainers:
+  - Long Zhao <longzhao@ambarella.com>
+
+description: |
+  The RCT (Reset and Clock Tree) block on the Ambarella CV75 SoC provides
+  PLLs, clock muxes, dividers and soft-reset controls for on-chip
+  peripherals.
+
+  The same register region is also exposed as a syscon for other SoC
+  blocks (e.g. pin drive-strength), hence the "syscon" compatible.
+
+  Clock indices are defined in include/dt-bindings/clock/ambarella,cv75-rct.h.
+
+properties:
+
+  compatible:
+    items:
+      - const: ambarella,cv75-rct
+      - const: syscon
+
+  reg:
+    maxItems: 1
+
+  clocks:
+    description: Reference crystal oscillator (typically 24 MHz)
+    maxItems: 1
+
+  clock-names:
+    const: osc
+
+  "#clock-cells":
+    const: 1
+
+  "#reset-cells":
+    const: 1
+
+required:
+  - compatible
+  - reg
+  - clocks
+  - clock-names
+  - "#clock-cells"
+  - "#reset-cells"
+
+additionalProperties: false
+
+examples:
+  - |
+    clock-controller@ed080000 {
+        compatible = "ambarella,cv75-rct", "syscon";
+        reg = <0xed080000 0x1000>;
+        clocks = <&osc>;
+        clock-names = "osc";
+        #clock-cells = <1>;
+        #reset-cells = <1>;
+    };
diff --git a/include/dt-bindings/clock/ambarella,cv75-rct.h b/include/dt-bindings/clock/ambarella,cv75-rct.h
new file mode 100644
index 000000000000..0a6e946c9108
--- /dev/null
+++ b/include/dt-bindings/clock/ambarella,cv75-rct.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause */
+/*
+ * Copyright (C) 2026 Ambarella, Inc.
+ */
+
+#ifndef _DT_BINDINGS_CLOCK_AMBARELLA_CV75_RCT_H
+#define _DT_BINDINGS_CLOCK_AMBARELLA_CV75_RCT_H
+
+/* Boot / bus clocks */
+#define CV75_GCLK_UART0		0
+#define CV75_GCLK_CORE		1
+#define CV75_GCLK_AHB		2
+#define CV75_GCLK_APB		3
+
+#endif

-- 
2.34.1




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

* [PATCH v6 04/13] dt-bindings: pinctrl: add Ambarella CV75 pinctrl
  2026-09-04  6:38 [PATCH v6 00/13] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
                   ` (2 preceding siblings ...)
  2026-09-04  6:38 ` [PATCH v6 03/13] dt-bindings: clock: add Ambarella CV75 RCT Long Zhao via B4 Relay
@ 2026-09-04  6:38 ` Long Zhao via B4 Relay
  2026-09-04 22:40   ` Linus Walleij
  2026-09-04  6:38 ` [PATCH v6 05/13] dt-bindings: gpio: pl061: add Ambarella CV75 variant Long Zhao via B4 Relay
                   ` (9 subsequent siblings)
  13 siblings, 1 reply; 28+ messages in thread
From: Long Zhao via B4 Relay @ 2026-09-04  6:38 UTC (permalink / raw)
  To: Arnd Bergmann, Krzysztof Kozlowski, Alexandre Belloni, soc,
	linux-arm-kernel
  Cc: Long Zhao, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Michael Turquette, Stephen Boyd, Linus Walleij,
	Bartosz Golaszewski, Greg Kroah-Hartman, Jiri Slaby,
	Andy Shevchenko, Ilpo Järvinen, Catalin Marinas, Will Deacon,
	Long Zhao, Lee Jones, mfd, devicetree, linux-clk, linux-gpio,
	linux-serial, linux-kernel

From: Long Zhao <longzhao@ambarella.com>

Document the CV75 pin controller binding using generic function and
groups properties for pinmux configuration.

Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
 .../bindings/pinctrl/ambarella,cv75-pinctrl.yaml   | 239 +++++++++++++++++++++
 1 file changed, 239 insertions(+)

diff --git a/Documentation/devicetree/bindings/pinctrl/ambarella,cv75-pinctrl.yaml b/Documentation/devicetree/bindings/pinctrl/ambarella,cv75-pinctrl.yaml
new file mode 100644
index 000000000000..34054bc56c3b
--- /dev/null
+++ b/Documentation/devicetree/bindings/pinctrl/ambarella,cv75-pinctrl.yaml
@@ -0,0 +1,239 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/pinctrl/ambarella,cv75-pinctrl.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Ambarella CV75 Pin Controller
+
+maintainers:
+  - Long Zhao <longzhao@ambarella.com>
+
+description:
+  The Ambarella CV75 pin controller configures pin multiplexing, bias and
+  drive strength for the main GPIO banks. Pin groups and their mux settings
+  are defined by the pin controller driver and selected by function and group
+  names.
+
+allOf:
+  - $ref: pinctrl.yaml#
+
+properties:
+  compatible:
+    const: ambarella,cv75-pinctrl
+
+  reg:
+    maxItems: 1
+    description: IOMUX registers
+
+  ambarella,drive-strength-syscon:
+    $ref: /schemas/types.yaml#/definitions/phandle
+    description: RCT syscon containing the drive-strength registers.
+
+  ambarella,pull-syscon:
+    $ref: /schemas/types.yaml#/definitions/phandle
+    description: Secure scratchpad syscon containing the pull registers.
+
+patternProperties:
+  "^[a-z0-9-]+-pins$":
+    type: object
+    allOf:
+      - $ref: pinmux-node.yaml#
+      - $ref: pincfg-node.yaml#
+    additionalProperties: false
+
+    properties:
+      function:
+        enum:
+          - can0
+          - can1
+          - clk_au
+          - dmic0
+          - enet0
+          - hsync0
+          - hsync1
+          - i2c0
+          - i2c1
+          - i2c2
+          - i2c3
+          - i2cs
+          - i2s0
+          - i2s1
+          - ir
+          - pwm0
+          - pwm1
+          - pwm2
+          - pwm3
+          - pwm4
+          - pwm5
+          - pwm6
+          - pwm7
+          - pwm8
+          - pwm9
+          - pwm10
+          - pwm11
+          - sdmmc0
+          - sdmmc1
+          - snand
+          - spi0
+          - spi1
+          - spi2
+          - spi3
+          - spi_slave
+          - spinor
+          - uart0
+          - uart1
+          - uart2
+          - uart3
+          - uart4
+          - vin_master_sync
+          - vsync0
+          - vsync1
+          - vsync2
+          - vsync3
+          - wdt
+
+      groups:
+        items:
+          enum:
+            - can0
+            - can1
+            - clk_au
+            - dmic0
+            - enet_2nd_ref_clk_a
+            - enet_2nd_ref_clk_b
+            - enet_ext_osc_clk
+            - enet0_ptp_pps_o
+            - hsync0
+            - hsync1
+            - i2c0_a
+            - i2c0_b
+            - i2c1_a
+            - i2c1_b
+            - i2c2
+            - i2c3_a
+            - i2c3_b
+            - i2c3_c
+            - i2cs_a
+            - i2cs_b
+            - i2cs_c
+            - i2cs_d
+            - i2s0
+            - i2s1
+            - ir
+            - pwm0
+            - pwm1
+            - pwm2
+            - pwm3
+            - pwm4_a
+            - pwm4_b
+            - pwm5_a
+            - pwm5_b
+            - pwm6_a
+            - pwm6_b
+            - pwm7_a
+            - pwm7_b
+            - pwm8_a
+            - pwm8_b
+            - pwm9_a
+            - pwm9_b
+            - pwm10_a
+            - pwm10_b
+            - pwm11_a
+            - pwm11_b
+            - rgmii0
+            - rmii0
+            - sdmmc0_1bit
+            - sdmmc0_4bit
+            - sdmmc0_cd
+            - sdmmc0_hs_sel
+            - sdmmc0_reset
+            - sdmmc0_wp
+            - sdmmc1_1bit
+            - sdmmc1_4bit
+            - sdmmc1_cd
+            - sdmmc1_hs_sel
+            - sdmmc1_reset
+            - sdmmc1_wp
+            - snand
+            - spi0
+            - spi1
+            - spi2
+            - spi3_a
+            - spi3_b
+            - spi3_c
+            - spi_slave_a
+            - spi_slave_b
+            - spi_slave_c
+            - spi_slave_d
+            - spi_slave_e
+            - spinor
+            - uart0
+            - uart1
+            - uart1_flow
+            - uart2_a
+            - uart2_b
+            - uart2_c
+            - uart2_flow_a
+            - uart2_flow_b
+            - uart3_a
+            - uart3_b
+            - uart3_flow_a
+            - uart3_flow_b
+            - uart4_a
+            - uart4_b
+            - uart4_flow_a
+            - uart4_flow_b
+            - vin_master_sync_a
+            - vin_master_sync_b
+            - vin_master_sync_c
+            - vin_master_sync_d
+            - vin_master_sync_e
+            - vsync0
+            - vsync1
+            - vsync2
+            - vsync3
+            - wdt_a
+            - wdt_b
+            - wdt_c
+            - wdt_d
+            - wdt_e
+            - wdt_f
+        minItems: 1
+        maxItems: 6
+        uniqueItems: true
+
+      bias-disable: true
+      bias-pull-down: true
+      bias-pull-up: true
+
+      drive-strength:
+        enum: [3, 4, 5, 6, 7, 8, 9, 12]
+
+    required:
+      - function
+      - groups
+
+required:
+  - compatible
+  - reg
+  - ambarella,drive-strength-syscon
+  - ambarella,pull-syscon
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    pinctrl: pinctrl@e4010000 {
+        compatible = "ambarella,cv75-pinctrl";
+        reg = <0xe4010000 0x1000>;
+        ambarella,drive-strength-syscon = <&rct>;
+        ambarella,pull-syscon = <&scratchpad>;
+
+        uart0-pins {
+            function = "uart0";
+            groups = "uart0";
+            bias-disable;
+            drive-strength = <8>;
+        };
+    };

-- 
2.34.1




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

* [PATCH v6 05/13] dt-bindings: gpio: pl061: add Ambarella CV75 variant
  2026-09-04  6:38 [PATCH v6 00/13] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
                   ` (3 preceding siblings ...)
  2026-09-04  6:38 ` [PATCH v6 04/13] dt-bindings: pinctrl: add Ambarella CV75 pinctrl Long Zhao via B4 Relay
@ 2026-09-04  6:38 ` Long Zhao via B4 Relay
  2026-09-04 14:45   ` Rob Herring
  2026-09-04  6:38 ` [PATCH v6 06/13] dt-bindings: serial: snps-dw-apb-uart: add ambarella,cv75-uart Long Zhao via B4 Relay
                   ` (8 subsequent siblings)
  13 siblings, 1 reply; 28+ messages in thread
From: Long Zhao via B4 Relay @ 2026-09-04  6:38 UTC (permalink / raw)
  To: Arnd Bergmann, Krzysztof Kozlowski, Alexandre Belloni, soc,
	linux-arm-kernel
  Cc: Long Zhao, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Michael Turquette, Stephen Boyd, Linus Walleij,
	Bartosz Golaszewski, Greg Kroah-Hartman, Jiri Slaby,
	Andy Shevchenko, Ilpo Järvinen, Catalin Marinas, Will Deacon,
	Long Zhao, Lee Jones, mfd, devicetree, linux-clk, linux-gpio,
	linux-serial, linux-kernel

From: Long Zhao <longzhao@ambarella.com>

Document the Ambarella CV75 GPIO bank compatible and the synthetic
arm,primecell-periphid used with the PL061 binding.

Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
 .../devicetree/bindings/gpio/pl061-gpio.yaml       | 32 +++++++++++++++++++---
 1 file changed, 28 insertions(+), 4 deletions(-)

diff --git a/Documentation/devicetree/bindings/gpio/pl061-gpio.yaml b/Documentation/devicetree/bindings/gpio/pl061-gpio.yaml
index 4d970e55104b..5a6d95e84df2 100644
--- a/Documentation/devicetree/bindings/gpio/pl061-gpio.yaml
+++ b/Documentation/devicetree/bindings/gpio/pl061-gpio.yaml
@@ -15,7 +15,9 @@ select:
   properties:
     compatible:
       contains:
-        const: arm,pl061
+        enum:
+          - arm,pl061
+          - ambarella,cv75-gpio
   required:
     - compatible
 
@@ -24,9 +26,13 @@ properties:
     pattern: "^gpio@[0-9a-f]+$"
 
   compatible:
-    items:
-      - const: arm,pl061
-      - const: arm,primecell
+    oneOf:
+      - items:
+          - const: arm,pl061
+          - const: arm,primecell
+      - items:
+          - const: ambarella,cv75-gpio
+          - const: arm,primecell
 
   reg:
     maxItems: 1
@@ -46,6 +52,10 @@ properties:
 
   clock-names: true
 
+  arm,primecell-periphid:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    description: Override for the PrimeCell peripheral ID.
+
   "#gpio-cells":
     const: 2
 
@@ -64,6 +74,20 @@ required:
   - "#gpio-cells"
   - gpio-controller
 
+allOf:
+  - if:
+      properties:
+        compatible:
+          contains:
+            const: ambarella,cv75-gpio
+    then:
+      properties:
+        arm,primecell-periphid:
+          const: 0x000e8061
+      required:
+        - arm,primecell-periphid
+        - gpio-ranges
+
 additionalProperties: false
 
 ...

-- 
2.34.1




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

* [PATCH v6 06/13] dt-bindings: serial: snps-dw-apb-uart: add ambarella,cv75-uart
  2026-09-04  6:38 [PATCH v6 00/13] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
                   ` (4 preceding siblings ...)
  2026-09-04  6:38 ` [PATCH v6 05/13] dt-bindings: gpio: pl061: add Ambarella CV75 variant Long Zhao via B4 Relay
@ 2026-09-04  6:38 ` Long Zhao via B4 Relay
  2026-09-04 22:41   ` Linus Walleij
  2026-09-04  6:38 ` [PATCH v6 07/13] clk: ambarella: add CV75 CCU driver Long Zhao via B4 Relay
                   ` (7 subsequent siblings)
  13 siblings, 1 reply; 28+ messages in thread
From: Long Zhao via B4 Relay @ 2026-09-04  6:38 UTC (permalink / raw)
  To: Arnd Bergmann, Krzysztof Kozlowski, Alexandre Belloni, soc,
	linux-arm-kernel
  Cc: Long Zhao, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Michael Turquette, Stephen Boyd, Linus Walleij,
	Bartosz Golaszewski, Greg Kroah-Hartman, Jiri Slaby,
	Andy Shevchenko, Ilpo Järvinen, Catalin Marinas, Will Deacon,
	Long Zhao, Lee Jones, mfd, devicetree, linux-clk, linux-gpio,
	linux-serial, linux-kernel

From: Long Zhao <longzhao@ambarella.com>

Add the Ambarella CV75 compatible string to the Synopsys DW APB UART
binding. The block is Ambarella IP with a DW-like register map, so it
is described as a DW APB UART variant.

Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
 Documentation/devicetree/bindings/serial/snps-dw-apb-uart.yaml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/Documentation/devicetree/bindings/serial/snps-dw-apb-uart.yaml b/Documentation/devicetree/bindings/serial/snps-dw-apb-uart.yaml
index 49f51b002879..cec5c679f1a9 100644
--- a/Documentation/devicetree/bindings/serial/snps-dw-apb-uart.yaml
+++ b/Documentation/devicetree/bindings/serial/snps-dw-apb-uart.yaml
@@ -51,6 +51,7 @@ properties:
           - const: renesas,rzn1-uart
       - items:
           - enum:
+              - ambarella,cv75-uart
               - anlogic,dr1v90-uart
               - brcm,bcm11351-dw-apb-uart
               - brcm,bcm21664-dw-apb-uart

-- 
2.34.1




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

* [PATCH v6 07/13] clk: ambarella: add CV75 CCU driver
  2026-09-04  6:38 [PATCH v6 00/13] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
                   ` (5 preceding siblings ...)
  2026-09-04  6:38 ` [PATCH v6 06/13] dt-bindings: serial: snps-dw-apb-uart: add ambarella,cv75-uart Long Zhao via B4 Relay
@ 2026-09-04  6:38 ` Long Zhao via B4 Relay
  2026-09-04  7:44   ` Jerome Brunet
  2026-09-04  6:38 ` [PATCH v6 08/13] gpio: regmap: support write_data_after_dir and girq Long Zhao via B4 Relay
                   ` (6 subsequent siblings)
  13 siblings, 1 reply; 28+ messages in thread
From: Long Zhao via B4 Relay @ 2026-09-04  6:38 UTC (permalink / raw)
  To: Arnd Bergmann, Krzysztof Kozlowski, Alexandre Belloni, soc,
	linux-arm-kernel
  Cc: Long Zhao, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Michael Turquette, Stephen Boyd, Linus Walleij,
	Bartosz Golaszewski, Greg Kroah-Hartman, Jiri Slaby,
	Andy Shevchenko, Ilpo Järvinen, Catalin Marinas, Will Deacon,
	Long Zhao, Lee Jones, mfd, devicetree, linux-clk, linux-gpio,
	linux-serial, linux-kernel

From: Long Zhao <longzhao@ambarella.com>

Add the Ambarella CV75 clock driver covering the core PLL and the
UART0 / AHB / APB clocks needed for early console bring-up.

Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
 drivers/clk/Kconfig                 |   1 +
 drivers/clk/Makefile                |   1 +
 drivers/clk/ambarella/Kconfig       |  13 ++
 drivers/clk/ambarella/Makefile      |   6 +
 drivers/clk/ambarella/ccu-cv75.c    | 296 +++++++++++++++++++++++++
 drivers/clk/ambarella/ccu_common.c  |  60 +++++
 drivers/clk/ambarella/ccu_common.h  |  25 +++
 drivers/clk/ambarella/ccu_mux_div.c | 234 ++++++++++++++++++++
 drivers/clk/ambarella/ccu_mux_div.h |  35 +++
 drivers/clk/ambarella/ccu_pll.c     | 421 ++++++++++++++++++++++++++++++++++++
 drivers/clk/ambarella/ccu_pll.h     |  70 ++++++
 11 files changed, 1162 insertions(+)

diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index 1717ce75a907..fbbf4963716c 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -506,6 +506,7 @@ config COMMON_CLK_RPMI
 	  the RISC-V platform management interface (RPMI) specification.
 
 source "drivers/clk/actions/Kconfig"
+source "drivers/clk/ambarella/Kconfig"
 source "drivers/clk/analogbits/Kconfig"
 source "drivers/clk/aspeed/Kconfig"
 source "drivers/clk/bcm/Kconfig"
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index cc108a75a900..30d823ee606f 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -112,6 +112,7 @@ obj-$(CONFIG_COMMON_CLK_XGENE)		+= clk-xgene.o
 
 # please keep this section sorted lexicographically by directory path name
 obj-y					+= actions/
+obj-y					+= ambarella/
 obj-y					+= analogbits/
 obj-y					+= aspeed/
 obj-$(CONFIG_COMMON_CLK_AT91)		+= at91/
diff --git a/drivers/clk/ambarella/Kconfig b/drivers/clk/ambarella/Kconfig
new file mode 100644
index 000000000000..d79bb4bd6df5
--- /dev/null
+++ b/drivers/clk/ambarella/Kconfig
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+config CLK_AMBARELLA_CV75
+	bool "Ambarella CV75 RCT clock controller"
+	depends on ARCH_AMBARELLA || COMPILE_TEST
+	select CLK_AMBARELLA_CCU
+	default ARCH_AMBARELLA
+	help
+	  Say Y to enable the Ambarella CV75 RCT clock controller.
+
+config CLK_AMBARELLA_CCU
+	bool
+	select REGMAP_MMIO
diff --git a/drivers/clk/ambarella/Makefile b/drivers/clk/ambarella/Makefile
new file mode 100644
index 000000000000..36e96326ea97
--- /dev/null
+++ b/drivers/clk/ambarella/Makefile
@@ -0,0 +1,6 @@
+#
+# Makefile for ambarella specific clk
+#
+
+obj-$(CONFIG_CLK_AMBARELLA_CCU) += ccu_common.o ccu_mux_div.o ccu_pll.o
+obj-$(CONFIG_CLK_AMBARELLA_CV75) += ccu-cv75.o
diff --git a/drivers/clk/ambarella/ccu-cv75.c b/drivers/clk/ambarella/ccu-cv75.c
new file mode 100644
index 000000000000..c4828be598be
--- /dev/null
+++ b/drivers/clk/ambarella/ccu-cv75.c
@@ -0,0 +1,296 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2026 Ambarella, Inc.
+ *
+ * CV75 RCT clock controller for boot clocks.
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <dt-bindings/clock/ambarella,cv75-rct.h>
+
+#include "ccu_common.h"
+#include "ccu_mux_div.h"
+#include "ccu_pll.h"
+
+enum amb_cv75_clk_type {
+	AMB_CV75_CLK_FIXED_RATE,
+	AMB_CV75_CLK_FIXED_FACTOR,
+	AMB_CV75_CLK_PLL,
+	AMB_CV75_CLK_DIV,
+	AMB_CV75_CLK_MUX_DIV,
+};
+
+enum amb_cv75_clk_ref {
+	AMB_CV75_CLK_REF_OSC = -1,
+	AMB_CV75_CLK_REF_DUMMY = -2,
+};
+
+/* PLL version for CV75 (ambarella,clkpll-v1 in vendor DTS) */
+static const struct amb_pll_soc_data cv75_pll_soc_data = {
+	.pll_version	= 1,
+	.fsout_mask	= CTRL2_FSOUT_DIV2,
+	.fsout_val	= CTRL2_FSOUT_DIV2,
+	.fsdiv_mask	= CTRL2_FSDIV_DIV2,
+	.fsdiv_val	= CTRL2_FSDIV_DIV2,
+	.vcodiv_mask	= CTRL2_VCODIV_DIV2,
+	.vcodiv_val	= CTRL2_VCODIV_DIV2,
+	.vco_max_mhz	= 2600UL,
+	.vco_min_mhz	= 850UL,
+	.vco_range	= { 1800UL, 1400UL, 1100UL, 0UL },
+};
+
+struct amb_cv75_clk_desc {
+	int id;
+	enum amb_cv75_clk_type type;
+	const char *name;
+	int parent;
+
+	union {
+		struct {
+			unsigned long rate;
+		} fixed_rate;
+		struct {
+			unsigned long flags;
+			unsigned int mult;
+			unsigned int div;
+		} fixed_factor;
+		struct {
+			u32 reg_offset[REG_NUM];
+			const struct amb_pll_soc_data *soc_data;
+		} pll;
+		struct {
+			u32 reg;
+			u32 shift;
+			u32 width;
+			u32 flags;
+			u32 fix_divider;
+		} div;
+		struct amb_mux_div_desc mux_div;
+	};
+};
+
+static const struct amb_cv75_clk_desc cv75_clks[] = {
+	{
+		.id = AMB_CV75_CLK_REF_DUMMY,
+		.type = AMB_CV75_CLK_FIXED_RATE,
+		.name = "dummy",
+		.fixed_rate.rate = 0,
+	},
+	{
+		.id = CV75_GCLK_CORE,
+		.type = AMB_CV75_CLK_PLL,
+		.name = "core",
+		.parent = AMB_CV75_CLK_REF_OSC,
+		.pll = {
+			.reg_offset = {
+				0x000, 0x004, 0x100,
+				0x104, 0x000, 0x000,
+			},
+			.soc_data = &cv75_pll_soc_data,
+		},
+	},
+	{
+		.id = CV75_GCLK_AHB,
+		.type = AMB_CV75_CLK_FIXED_FACTOR,
+		.name = "ahb",
+		.parent = CV75_GCLK_CORE,
+		.fixed_factor = {
+			.flags = 0,
+			.mult = 1,
+			.div = 2,
+		},
+	},
+	{
+		.id = CV75_GCLK_APB,
+		.type = AMB_CV75_CLK_FIXED_FACTOR,
+		.name = "apb",
+		.parent = CV75_GCLK_CORE,
+		.fixed_factor = {
+			.flags = 0,
+			.mult = 1,
+			.div = 4,
+		},
+	},
+	{
+		.id = CV75_GCLK_UART0,
+		.type = AMB_CV75_CLK_MUX_DIV,
+		.name = "uart0",
+		.mux_div = {
+			.name = "uart0",
+			.parents = (const int[]) {
+				AMB_CV75_CLK_REF_OSC, CV75_GCLK_CORE,
+				AMB_CV75_CLK_REF_DUMMY, AMB_CV75_CLK_REF_DUMMY,
+			},
+			.num_parents = 4,
+			.mux_reg = 0x1c8,
+			.mux_shift = 0,
+			.mux_mask = 0x3,
+			.div_reg = 0x038,
+			.div_shift = 0,
+			.div_width = 24,
+			.div_flags = CLK_DIVIDER_ONE_BASED,
+			.fix_divider = 1,
+		},
+	},
+};
+
+static struct clk_hw *amb_cv75_get_parent(struct amb_ccu *ccu,
+					  struct clk_hw *osc,
+					  struct clk_hw *dummy,
+					  int parent)
+{
+	if (parent == AMB_CV75_CLK_REF_OSC)
+		return osc;
+	if (parent == AMB_CV75_CLK_REF_DUMMY)
+		return dummy;
+
+	if (parent < 0 || parent >= ccu->data->num)
+		return ERR_PTR(-EINVAL);
+
+	if (!ccu->data->hws[parent])
+		return ERR_PTR(-EPROBE_DEFER);
+
+	return ccu->data->hws[parent];
+}
+
+static struct clk_hw *amb_cv75_register_mux_div(struct device *dev,
+						struct amb_ccu *ccu,
+						const struct amb_cv75_clk_desc *desc,
+						struct clk_hw *osc,
+						struct clk_hw *dummy)
+{
+	const struct amb_mux_div_desc *md = &desc->mux_div;
+	struct clk_parent_data *pdata;
+	struct clk_hw *parent;
+	u8 i;
+
+	pdata = devm_kcalloc(dev, md->num_parents, sizeof(*pdata), GFP_KERNEL);
+	if (!pdata)
+		return ERR_PTR(-ENOMEM);
+
+	for (i = 0; i < md->num_parents; i++) {
+		if (md->parents[i] == AMB_CV75_CLK_REF_OSC) {
+			pdata[i].fw_name = "osc";
+			continue;
+		}
+
+		parent = amb_cv75_get_parent(ccu, osc, dummy, md->parents[i]);
+		if (IS_ERR(parent))
+			return parent;
+
+		pdata[i].hw = parent;
+	}
+
+	return amb_mux_div_register(dev, ccu->map, md, pdata);
+}
+
+static struct clk_hw *amb_cv75_register_clk(struct device *dev,
+					    struct amb_ccu *ccu,
+					    const struct amb_cv75_clk_desc *desc,
+					    struct clk_hw *osc,
+					    struct clk_hw *dummy)
+{
+	struct amb_pll_desc pll_desc;
+	struct clk_hw *parent;
+
+	switch (desc->type) {
+	case AMB_CV75_CLK_FIXED_RATE:
+		return devm_clk_hw_register_fixed_rate(dev, desc->name, NULL, 0,
+						       desc->fixed_rate.rate);
+	case AMB_CV75_CLK_FIXED_FACTOR:
+		parent = amb_cv75_get_parent(ccu, osc, dummy, desc->parent);
+		if (IS_ERR(parent))
+			return parent;
+
+		return devm_clk_hw_register_fixed_factor_parent_hw(dev,
+				desc->name, parent,
+				desc->fixed_factor.flags,
+				desc->fixed_factor.mult,
+				desc->fixed_factor.div);
+	case AMB_CV75_CLK_PLL:
+		parent = amb_cv75_get_parent(ccu, osc, dummy, desc->parent);
+		if (IS_ERR(parent))
+			return parent;
+
+		pll_desc.name = desc->name;
+		pll_desc.parent = parent;
+		memcpy(pll_desc.reg_offset, desc->pll.reg_offset,
+		       sizeof(pll_desc.reg_offset));
+		pll_desc.soc_data = desc->pll.soc_data;
+		pll_desc.frac_mode = false;
+
+		return amb_pll_register(dev, ccu->map, &pll_desc);
+	case AMB_CV75_CLK_DIV:
+		parent = amb_cv75_get_parent(ccu, osc, dummy, desc->parent);
+		if (IS_ERR(parent))
+			return parent;
+
+		return amb_div_register(dev, ccu->map, desc->name, parent,
+					desc->div.reg, desc->div.shift,
+					desc->div.width, desc->div.flags,
+					desc->div.fix_divider);
+	case AMB_CV75_CLK_MUX_DIV:
+		return amb_cv75_register_mux_div(dev, ccu, desc, osc, dummy);
+	default:
+		return ERR_PTR(-EINVAL);
+	}
+}
+
+static int amb_cv75_rct_probe(struct platform_device *pdev)
+{
+	struct amb_ccu *ccu;
+	struct clk *osc_clk;
+	struct clk_hw *osc, *dummy = NULL, *hw;
+	int i;
+
+	ccu = amb_ccu_init(pdev, ARRAY_SIZE(cv75_clks));
+	if (IS_ERR(ccu))
+		return PTR_ERR(ccu);
+
+	osc_clk = devm_clk_get(&pdev->dev, "osc");
+	if (IS_ERR(osc_clk))
+		return dev_err_probe(&pdev->dev, PTR_ERR(osc_clk),
+				     "missing osc clock\n");
+	osc = __clk_get_hw(osc_clk);
+
+	for (i = 0; i < ARRAY_SIZE(cv75_clks); i++) {
+		hw = amb_cv75_register_clk(&pdev->dev, ccu, &cv75_clks[i],
+					   osc, dummy);
+		if (IS_ERR(hw))
+			return dev_err_probe(&pdev->dev, PTR_ERR(hw),
+					     "failed to register %s\n",
+					     cv75_clks[i].name);
+
+		if (cv75_clks[i].id == AMB_CV75_CLK_REF_DUMMY)
+			dummy = hw;
+		else
+			ccu->data->hws[cv75_clks[i].id] = hw;
+	}
+
+	return amb_ccu_register(ccu);
+}
+
+static const struct of_device_id amb_cv75_rct_match[] = {
+	{ .compatible = "ambarella,cv75-rct" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, amb_cv75_rct_match);
+
+static struct platform_driver amb_cv75_rct_driver = {
+	.probe	= amb_cv75_rct_probe,
+	.driver = {
+		.name = "ambarella-cv75-rct",
+		.of_match_table = amb_cv75_rct_match,
+	},
+};
+module_platform_driver(amb_cv75_rct_driver);
+
+MODULE_AUTHOR("Ambarella Inc.");
+MODULE_DESCRIPTION("Ambarella CV75 RCT clock controller");
+MODULE_LICENSE("GPL");
diff --git a/drivers/clk/ambarella/ccu_common.c b/drivers/clk/ambarella/ccu_common.c
new file mode 100644
index 000000000000..78587c5f2ce1
--- /dev/null
+++ b/drivers/clk/ambarella/ccu_common.c
@@ -0,0 +1,60 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2026 Ambarella, Inc.
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#include "ccu_common.h"
+
+static const struct regmap_config amb_rct_regmap_config = {
+	.reg_bits = 32,
+	.val_bits = 32,
+	.reg_stride = 4,
+	.max_register = AMB_RCT_REG_SIZE - 4,
+};
+
+struct amb_ccu *amb_ccu_init(struct platform_device *pdev,
+			     unsigned int num_clks)
+{
+	struct amb_ccu *ccu;
+	void __iomem *base;
+
+	if (!num_clks)
+		return ERR_PTR(-EINVAL);
+
+	ccu = devm_kzalloc(&pdev->dev, sizeof(*ccu), GFP_KERNEL);
+	if (!ccu)
+		return ERR_PTR(-ENOMEM);
+
+	ccu->dev = &pdev->dev;
+
+	ccu->data = devm_kzalloc(&pdev->dev,
+				 struct_size(ccu->data, hws, num_clks),
+				 GFP_KERNEL);
+	if (!ccu->data)
+		return ERR_PTR(-ENOMEM);
+
+	ccu->data->num = num_clks;
+
+	base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(base))
+		return ERR_CAST(base);
+
+	ccu->map = devm_regmap_init_mmio(&pdev->dev, base,
+					 &amb_rct_regmap_config);
+	if (IS_ERR(ccu->map))
+		return ERR_CAST(ccu->map);
+
+	return ccu;
+}
+
+int amb_ccu_register(struct amb_ccu *ccu)
+{
+	return devm_of_clk_add_hw_provider(ccu->dev, of_clk_hw_onecell_get,
+					   ccu->data);
+}
diff --git a/drivers/clk/ambarella/ccu_common.h b/drivers/clk/ambarella/ccu_common.h
new file mode 100644
index 000000000000..f2bf9d1f06d7
--- /dev/null
+++ b/drivers/clk/ambarella/ccu_common.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2026 Ambarella, Inc.
+ */
+
+#ifndef __CCU_COMMON_H
+#define __CCU_COMMON_H
+
+#include <linux/clk-provider.h>
+#include <linux/device.h>
+#include <linux/regmap.h>
+
+#define AMB_RCT_REG_SIZE	0x1000
+
+struct amb_ccu {
+	struct device *dev;
+	struct regmap *map;
+	struct clk_hw_onecell_data *data;
+};
+
+struct amb_ccu *amb_ccu_init(struct platform_device *pdev,
+			     unsigned int num_clks);
+int amb_ccu_register(struct amb_ccu *ccu);
+
+#endif
diff --git a/drivers/clk/ambarella/ccu_mux_div.c b/drivers/clk/ambarella/ccu_mux_div.c
new file mode 100644
index 000000000000..d5259bc347e4
--- /dev/null
+++ b/drivers/clk/ambarella/ccu_mux_div.c
@@ -0,0 +1,234 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2026 Ambarella, Inc.
+ *
+ * Regmap-backed mux + divider, derived from the vendor composite-clock driver.
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/device.h>
+#include <linux/math64.h>
+#include <linux/regmap.h>
+
+#include "ccu_mux_div.h"
+
+struct amb_mux {
+	struct clk_hw hw;
+	struct regmap *map;
+	u32 offset;
+	u32 mask;
+	u32 shift;
+};
+
+struct amb_div {
+	struct clk_hw hw;
+	struct regmap *map;
+	u32 offset;
+	u32 shift;
+	u32 width;
+	u32 flags;
+	u32 fix_divider;
+};
+
+#define to_amb_mux(_hw)	container_of(_hw, struct amb_mux, hw)
+#define to_amb_div(_hw)	container_of(_hw, struct amb_div, hw)
+
+static u8 amb_mux_get_parent(struct clk_hw *hw)
+{
+	struct amb_mux *mux = to_amb_mux(hw);
+	u32 val;
+
+	regmap_read(mux->map, mux->offset, &val);
+	val >>= mux->shift;
+	val &= mux->mask;
+
+	return val;
+}
+
+static int amb_mux_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct amb_mux *mux = to_amb_mux(hw);
+
+	return regmap_update_bits(mux->map, mux->offset,
+				  mux->mask << mux->shift,
+				  index << mux->shift);
+}
+
+static const struct clk_ops amb_mux_ops = {
+	.get_parent = amb_mux_get_parent,
+	.set_parent = amb_mux_set_parent,
+};
+
+static unsigned long amb_div_recalc_rate(struct clk_hw *hw,
+					 unsigned long parent_rate)
+{
+	struct amb_div *div = to_amb_div(hw);
+	unsigned long rate;
+	u32 val;
+
+	regmap_read(div->map, div->offset, &val);
+
+	/* Divider reset/disable bit sits above the field at shift+width. */
+	if (val & (BIT(div->width) << div->shift))
+		return 0;
+
+	val >>= div->shift;
+	val &= clk_div_mask(div->width);
+
+	rate = divider_recalc_rate(hw, parent_rate, val, NULL,
+				   div->flags, div->width);
+	if (div->fix_divider)
+		rate = div_u64(rate, div->fix_divider);
+
+	return rate;
+}
+
+static int amb_div_determine_rate(struct clk_hw *hw,
+				  struct clk_rate_request *req)
+{
+	struct amb_div *div = to_amb_div(hw);
+	struct clk_rate_request scaled = *req;
+	int ret;
+
+	if (!req->rate && (div->flags & CLK_DIVIDER_ONE_BASED))
+		return 0;
+
+	if (div->fix_divider) {
+		scaled.rate = min(req->rate,
+				  ULONG_MAX / div->fix_divider) *
+			      div->fix_divider;
+		scaled.min_rate = min(req->min_rate,
+				      ULONG_MAX / div->fix_divider) *
+				  div->fix_divider;
+		scaled.max_rate = min(req->max_rate,
+				      ULONG_MAX / div->fix_divider) *
+				  div->fix_divider;
+	}
+
+	ret = divider_determine_rate(hw, &scaled, NULL, div->width,
+				     div->flags);
+	if (ret)
+		return ret;
+
+	req->rate = scaled.rate;
+	req->best_parent_rate = scaled.best_parent_rate;
+	req->best_parent_hw = scaled.best_parent_hw;
+	if (div->fix_divider)
+		req->rate = div_u64(req->rate, div->fix_divider);
+
+	return 0;
+}
+
+static int amb_div_set_rate(struct clk_hw *hw, unsigned long rate,
+			    unsigned long parent_rate)
+{
+	struct amb_div *div = to_amb_div(hw);
+	int val, mask;
+
+	if (div->fix_divider)
+		rate *= div->fix_divider;
+
+	if (!rate) {
+		/* Assert the reset/disable bit above the divider field. */
+		val = BIT(div->width);
+		mask = clk_div_mask(div->width + 1);
+	} else {
+		val = divider_get_val(rate, parent_rate, NULL,
+				      div->width, div->flags);
+		if (val < 0)
+			return val;
+
+		/*
+		 * Include the disable bit in the mask so a later non-zero
+		 * set_rate clears a previous rate==0 disable assert.
+		 */
+		mask = clk_div_mask(div->width + 1);
+	}
+
+	regmap_update_bits(div->map, div->offset, mask << div->shift,
+			   val << div->shift);
+
+	/*
+	 * Non-ONE_BASED dividers use bit 0 as a write-enable strobe. Skip the
+	 * pulse when shift == 0 so we do not corrupt the divider field itself.
+	 */
+	if (!(div->flags & CLK_DIVIDER_ONE_BASED) && div->shift) {
+		regmap_update_bits(div->map, div->offset, BIT(0), BIT(0));
+		regmap_update_bits(div->map, div->offset, BIT(0), 0);
+	}
+
+	return 0;
+}
+
+static const struct clk_ops amb_div_ops = {
+	.recalc_rate = amb_div_recalc_rate,
+	.determine_rate = amb_div_determine_rate,
+	.set_rate = amb_div_set_rate,
+};
+
+struct clk_hw *amb_div_register(struct device *dev, struct regmap *map,
+				const char *name, const struct clk_hw *parent,
+				u32 div_reg, u32 div_shift, u32 div_width,
+				u32 div_flags, u32 fix_divider)
+{
+	struct amb_div *div;
+	struct clk_init_data init = {};
+	int ret;
+
+	div = devm_kzalloc(dev, sizeof(*div), GFP_KERNEL);
+	if (!div)
+		return ERR_PTR(-ENOMEM);
+
+	div->map = map;
+	div->offset = div_reg;
+	div->shift = div_shift;
+	div->width = div_width;
+	div->flags = div_flags;
+	div->fix_divider = fix_divider;
+
+	init.name = name;
+	init.ops = &amb_div_ops;
+	init.parent_hws = &parent;
+	init.num_parents = 1;
+
+	div->hw.init = &init;
+
+	ret = devm_clk_hw_register(dev, &div->hw);
+	if (ret)
+		return ERR_PTR(ret);
+
+	return &div->hw;
+}
+
+struct clk_hw *amb_mux_div_register(struct device *dev, struct regmap *map,
+				    const struct amb_mux_div_desc *desc,
+				    const struct clk_parent_data *parent_data)
+{
+	struct amb_mux *mux;
+	struct amb_div *div;
+
+	mux = devm_kzalloc(dev, sizeof(*mux), GFP_KERNEL);
+	div = devm_kzalloc(dev, sizeof(*div), GFP_KERNEL);
+	if (!mux || !div)
+		return ERR_PTR(-ENOMEM);
+
+	mux->map = map;
+	mux->offset = desc->mux_reg;
+	mux->shift = desc->mux_shift;
+	mux->mask = desc->mux_mask;
+
+	div->map = map;
+	div->offset = desc->div_reg;
+	div->shift = desc->div_shift;
+	div->width = desc->div_width;
+	div->flags = desc->div_flags;
+	div->fix_divider = desc->fix_divider;
+
+	return devm_clk_hw_register_composite_pdata(dev, desc->name,
+						    parent_data,
+						    desc->num_parents,
+						    &mux->hw, &amb_mux_ops,
+						    &div->hw, &amb_div_ops,
+						    NULL, NULL,
+						    CLK_SET_RATE_NO_REPARENT);
+}
diff --git a/drivers/clk/ambarella/ccu_mux_div.h b/drivers/clk/ambarella/ccu_mux_div.h
new file mode 100644
index 000000000000..abaefff1201a
--- /dev/null
+++ b/drivers/clk/ambarella/ccu_mux_div.h
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2026 Ambarella, Inc.
+ */
+
+#ifndef __CCU_MUX_DIV_H
+#define __CCU_MUX_DIV_H
+
+#include <linux/clk-provider.h>
+#include <linux/regmap.h>
+
+struct amb_mux_div_desc {
+	const char *name;
+	const int *parents;
+	u8 num_parents;
+	u32 mux_reg;
+	u32 mux_shift;
+	u32 mux_mask;
+	u32 div_reg;
+	u32 div_shift;
+	u32 div_width;
+	u32 div_flags;
+	u32 fix_divider;
+};
+
+struct clk_hw *amb_mux_div_register(struct device *dev, struct regmap *map,
+				    const struct amb_mux_div_desc *desc,
+				    const struct clk_parent_data *parent_data);
+
+struct clk_hw *amb_div_register(struct device *dev, struct regmap *map,
+				const char *name, const struct clk_hw *parent,
+				u32 div_reg, u32 div_shift, u32 div_width,
+				u32 div_flags, u32 fix_divider);
+
+#endif
diff --git a/drivers/clk/ambarella/ccu_pll.c b/drivers/clk/ambarella/ccu_pll.c
new file mode 100644
index 000000000000..76452003bb0d
--- /dev/null
+++ b/drivers/clk/ambarella/ccu_pll.c
@@ -0,0 +1,421 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2026 Ambarella, Inc.
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/math64.h>
+#include <linux/rational.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+#include "ccu_pll.h"
+
+#define AMB_PLL_MAX_SOUT	16UL
+#define AMB_PLL_MAX_SDIV	16UL
+
+struct amb_pll {
+	struct clk_hw hw;
+	struct regmap *map;
+	u32 reg_offset[REG_NUM];
+	const struct amb_pll_soc_data *soc_data;
+	u32 fix_divider;
+	bool frac_mode;
+};
+
+#define to_amb_pll(_hw) container_of(_hw, struct amb_pll, hw)
+
+static void amb_pll_write_enable(struct regmap *map, u32 offset, u32 val)
+{
+	regmap_write(map, offset, val);
+	regmap_write(map, offset, val | CTRL_WRITE_ENABLE);
+	regmap_write(map, offset, val);
+}
+
+static unsigned long amb_pll_calc_vco(struct amb_pll *pll,
+				      unsigned long parent_rate)
+{
+	const struct amb_pll_soc_data *soc_data = pll->soc_data;
+	u32 *reg = pll->reg_offset;
+	u32 pre_scaler = 1;
+	u32 ctrl_val, ctrl2_val = 0, frac_val;
+	u32 intp, sdiv, vcodiv, fsdiv;
+	u64 frac = 0, vco;
+
+	if (reg[PRES_OFFSET]) {
+		regmap_read(pll->map, reg[PRES_OFFSET], &pre_scaler);
+		pre_scaler = (pre_scaler >> 4) + 1;
+	}
+
+	regmap_read(pll->map, reg[CTRL_OFFSET], &ctrl_val);
+	intp = ((ctrl_val >> 24) & 0x7f) + 1;
+	sdiv = ((ctrl_val >> 12) & 0xf) + 1;
+
+	if (soc_data->pll_version >= 2) {
+		vcodiv = (ctrl_val & soc_data->vcodiv_mask) ==
+			 soc_data->vcodiv_val ? 2 : 1;
+		fsdiv = (ctrl_val & soc_data->fsdiv_mask) ==
+			soc_data->fsdiv_val ? 2 : 1;
+	} else {
+		regmap_read(pll->map, reg[CTRL2_OFFSET], &ctrl2_val);
+		vcodiv = (ctrl2_val & soc_data->vcodiv_mask) ==
+			 soc_data->vcodiv_val ? 2 : 1;
+		fsdiv = (ctrl2_val & soc_data->fsdiv_mask) ==
+			soc_data->fsdiv_val ? 2 : 1;
+	}
+
+	vco = (u64)parent_rate * vcodiv * fsdiv * intp * sdiv;
+	vco = div_u64(vco, pre_scaler);
+
+	if (ctrl_val & CTRL_FRAC_MODE) {
+		regmap_read(pll->map, reg[FRAC_OFFSET], &frac_val);
+		frac = (u64)parent_rate * vcodiv * fsdiv * sdiv * frac_val;
+		frac = div_u64(frac, pre_scaler) >> 32;
+	}
+
+	return vco + frac;
+}
+
+static unsigned long amb_pll_recalc_rate(struct clk_hw *hw,
+					 unsigned long parent_rate)
+{
+	struct amb_pll *pll = to_amb_pll(hw);
+	const struct amb_pll_soc_data *soc_data = pll->soc_data;
+	u32 *reg = pll->reg_offset;
+	u32 pre_scaler = 1, post_scaler = 1;
+	u32 ctrl_val, ctrl2_val = 0;
+	u32 vcodiv, fsout, sout;
+	u64 rate;
+
+	regmap_read(pll->map, reg[CTRL_OFFSET], &ctrl_val);
+	if (ctrl_val & (CTRL_POWER_DOWN | CTRL_HALT_VCO | CTRL_FORCE_RESET))
+		return 0;
+
+	if (reg[PRES_OFFSET]) {
+		regmap_read(pll->map, reg[PRES_OFFSET], &pre_scaler);
+		pre_scaler = (pre_scaler >> 4) + 1;
+	}
+
+	if (reg[POST_OFFSET]) {
+		regmap_read(pll->map, reg[POST_OFFSET], &post_scaler);
+		post_scaler = (post_scaler >> 4) + 1;
+	}
+
+	if (ctrl_val & CTRL_BYPASS)
+		return parent_rate / pre_scaler / post_scaler;
+
+	if (soc_data->pll_version >= 2) {
+		vcodiv = (ctrl_val & soc_data->vcodiv_mask) ==
+			 soc_data->vcodiv_val ? 2 : 1;
+		fsout = (ctrl_val & soc_data->fsout_mask) ==
+			soc_data->fsout_val ? 2 : 1;
+	} else {
+		regmap_read(pll->map, reg[CTRL2_OFFSET], &ctrl2_val);
+		vcodiv = (ctrl2_val & soc_data->vcodiv_mask) ==
+			 soc_data->vcodiv_val ? 2 : 1;
+		fsout = (ctrl2_val & soc_data->fsout_mask) ==
+			soc_data->fsout_val ? 2 : 1;
+	}
+
+	sout = ((ctrl_val >> 16) & 0xf) + 1;
+	rate = amb_pll_calc_vco(pll, parent_rate);
+
+	if (soc_data->pll_version >= 2) {
+		if (!(ctrl_val & CTRL_BYPASS_HSDIV))
+			rate = div_u64(rate, vcodiv * fsout * sout);
+	} else {
+		if (!(ctrl2_val & CTRL2_BYPASS_HSDIV))
+			rate = div_u64(rate, vcodiv * fsout * sout);
+	}
+
+	rate = div_u64(rate, pll->fix_divider * post_scaler);
+
+	return rate;
+}
+
+static int amb_pll_calc_params(struct amb_pll *pll, unsigned long rate,
+			       unsigned long parent_rate, u32 ctrl2_val,
+			       u32 *intp, u32 *sdiv, u32 *sout,
+			       u32 *vcodiv, u32 *fsdiv, u32 *fsout)
+{
+	const struct amb_pll_soc_data *soc_data = pll->soc_data;
+	unsigned long max_numerator, max_denominator;
+	unsigned long intp_ul, sout_ul, rate_tmp, parent_scaled;
+	u32 ctrl_val;
+
+	if (!parent_rate)
+		return -EINVAL;
+
+	*sdiv = 1;
+
+	if (soc_data->pll_version >= 2) {
+		ctrl_val = 0;
+		*vcodiv = (ctrl_val & soc_data->vcodiv_mask) ==
+			  soc_data->vcodiv_val ? 2 : 1;
+		*fsdiv = (ctrl_val & soc_data->fsdiv_mask) ==
+			 soc_data->fsdiv_val ? 2 : 1;
+		*fsout = (ctrl_val & soc_data->fsout_mask) ==
+			 soc_data->fsout_val ? 2 : 1;
+	} else {
+		*vcodiv = (ctrl2_val & soc_data->vcodiv_mask) ==
+			  soc_data->vcodiv_val ? 2 : 1;
+		*fsdiv = (ctrl2_val & soc_data->fsdiv_mask) ==
+			 soc_data->fsdiv_val ? 2 : 1;
+		*fsout = (ctrl2_val & soc_data->fsout_mask) ==
+			 soc_data->fsout_val ? 2 : 1;
+	}
+
+	if (rate < parent_rate)
+		return -EINVAL;
+
+	max_numerator = soc_data->vco_max_mhz;
+	max_numerator = div_u64(max_numerator * 1000000ULL, parent_rate);
+	max_numerator = div_u64(max_numerator, *vcodiv * *fsdiv);
+	max_numerator = min(128UL, max_numerator);
+	if (!max_numerator)
+		return -EINVAL;
+
+	/*
+	 * Approximate intp/sout against the fsdiv/fsout-scaled parent so the
+	 * initial guess matches the hardware frequency equation.
+	 */
+	max_denominator = AMB_PLL_MAX_SOUT;
+	parent_scaled = parent_rate * *fsdiv / *fsout;
+	if (!parent_scaled)
+		return -EINVAL;
+	rate_tmp = rate;
+	rational_best_approximation(rate_tmp, parent_scaled, max_numerator,
+				    max_denominator, &intp_ul, &sout_ul);
+
+	while (parent_rate * *fsdiv * intp_ul * *sdiv / *fsout / sout_ul >
+	       rate) {
+		unsigned long resolution = parent_scaled / AMB_PLL_MAX_SOUT;
+
+		/* Avoid an infinite loop when parent_rate < AMB_PLL_MAX_SOUT. */
+		if (!resolution)
+			resolution = 1;
+
+		if (rate_tmp <= resolution)
+			return -EINVAL;
+
+		rate_tmp -= resolution;
+		rational_best_approximation(rate_tmp, parent_scaled,
+					    max_numerator, max_denominator,
+					    &intp_ul, &sout_ul);
+	}
+
+	while (parent_rate / 1000000 * *vcodiv * *fsdiv * intp_ul * *sdiv <
+	       soc_data->vco_min_mhz) {
+		if (sout_ul > 8 || intp_ul > 64)
+			break;
+
+		intp_ul *= 2;
+		sout_ul *= 2;
+	}
+
+	if (intp_ul > max_numerator || sout_ul > max_denominator ||
+	    *sdiv > AMB_PLL_MAX_SDIV)
+		return -EINVAL;
+
+	*intp = intp_ul;
+	*sout = sout_ul;
+
+	return 0;
+}
+
+static int amb_pll_determine_rate(struct clk_hw *hw,
+				  struct clk_rate_request *req)
+{
+	struct amb_pll *pll = to_amb_pll(hw);
+	const struct amb_pll_soc_data *soc_data = pll->soc_data;
+	u32 ctrl2_val = 0;
+	u32 intp, sdiv, sout, vcodiv, fsdiv, fsout;
+	unsigned long rate;
+	u64 rounded;
+	int ret;
+
+	if (!req->best_parent_rate)
+		return -EINVAL;
+
+	if (!req->rate || pll->frac_mode)
+		return 0;
+
+	if (req->rate > ULONG_MAX / pll->fix_divider)
+		return -ERANGE;
+
+	rate = req->rate * pll->fix_divider;
+	if (soc_data->ctrl2_val)
+		ctrl2_val = soc_data->ctrl2_val;
+	else
+		regmap_read(pll->map, pll->reg_offset[CTRL2_OFFSET],
+			    &ctrl2_val);
+
+	ret = amb_pll_calc_params(pll, rate, req->best_parent_rate,
+				  ctrl2_val, &intp, &sdiv, &sout, &vcodiv,
+				  &fsdiv, &fsout);
+	if (ret)
+		return ret;
+
+	rounded = (u64)req->best_parent_rate * fsdiv * intp * sdiv;
+	rounded = div64_u64(rounded, (u64)fsout * sout);
+	rounded = div64_u64(rounded, pll->fix_divider);
+	if (rounded < req->min_rate || rounded > req->max_rate)
+		return -EINVAL;
+
+	req->rate = rounded;
+
+	return 0;
+}
+
+static int amb_pll_set_rate(struct clk_hw *hw, unsigned long rate,
+			    unsigned long parent_rate)
+{
+	struct amb_pll *pll = to_amb_pll(hw);
+	const struct amb_pll_soc_data *soc_data = pll->soc_data;
+	u32 *reg = pll->reg_offset;
+	u32 ctrl_val, ctrl2_val = 0, ctrl3_val, frac_val = 0;
+	u32 intp, sdiv, sout, vcodiv, fsdiv, fsout;
+	unsigned long old_rate, new_rate, rate_tmp;
+	int ret;
+
+	if (!rate) {
+		regmap_read(pll->map, reg[CTRL_OFFSET], &ctrl_val);
+		ctrl_val |= CTRL_POWER_DOWN | CTRL_HALT_VCO;
+		amb_pll_write_enable(pll->map, reg[CTRL_OFFSET], ctrl_val);
+		return 0;
+	}
+
+	if (!parent_rate)
+		return -EINVAL;
+
+	rate *= pll->fix_divider;
+
+	if (soc_data->ctrl2_val)
+		ctrl2_val = soc_data->ctrl2_val;
+	else
+		regmap_read(pll->map, reg[CTRL2_OFFSET], &ctrl2_val);
+
+	ret = amb_pll_calc_params(pll, rate, parent_rate, ctrl2_val,
+				  &intp, &sdiv, &sout, &vcodiv, &fsdiv,
+				  &fsout);
+	if (ret)
+		return ret;
+
+	if (soc_data->ctrl2_val)
+		regmap_write(pll->map, reg[CTRL2_OFFSET], soc_data->ctrl2_val);
+
+	ctrl_val = ((intp - 1) & 0x7f) << 24;
+	ctrl_val |= ((sdiv - 1) & 0xf) << 12;
+	ctrl_val |= ((sout - 1) & 0xf) << 16;
+	if (soc_data->pll_version >= 2) {
+		ctrl_val |= vcodiv == 2 ? soc_data->vcodiv_val : 0;
+		ctrl_val |= fsdiv == 2 ? soc_data->fsdiv_val : 0;
+		ctrl_val |= fsout == 2 ? soc_data->fsout_val : 0;
+	}
+
+	regmap_write(pll->map, reg[CTRL_OFFSET], ctrl_val);
+	regmap_write(pll->map, reg[FRAC_OFFSET], 0);
+
+	old_rate = amb_pll_recalc_rate(hw, parent_rate) * pll->fix_divider;
+	rate_tmp = old_rate > rate ? 0 : rate - old_rate;
+	if (rate_tmp && pll->frac_mode) {
+		u64 dividend, divider;
+
+		dividend = (u64)rate_tmp * sout * fsout;
+		dividend <<= 32;
+		divider = (u64)sdiv * fsdiv * parent_rate;
+		frac_val = DIV64_U64_ROUND_CLOSEST(dividend, divider);
+		regmap_write(pll->map, reg[FRAC_OFFSET], frac_val);
+		ctrl_val |= CTRL_FRAC_MODE;
+	}
+
+	if (soc_data->pll_version >= 2) {
+		ctrl3_val = soc_data->ctrl3_val;
+		regmap_write(pll->map, reg[CTRL3_OFFSET],
+			     ctrl3_val | CTRL3_VCO_CLAMP);
+		regmap_write(pll->map, reg[CTRL_OFFSET],
+			     ctrl_val | CTRL_FORCE_RESET);
+		ndelay(100);
+		regmap_write(pll->map, reg[CTRL_OFFSET],
+			     ctrl_val & ~CTRL_FORCE_RESET);
+		ndelay(100);
+		regmap_write(pll->map, reg[CTRL3_OFFSET],
+			     ctrl3_val & ~CTRL3_VCO_CLAMP);
+	} else {
+		u32 fvco_mhz, range;
+
+		fvco_mhz = amb_pll_calc_vco(pll, parent_rate) / 1000000UL;
+		for (range = 0; range < ARRAY_SIZE(soc_data->vco_range);
+		     range++) {
+			if (fvco_mhz > soc_data->vco_range[range])
+				break;
+		}
+		if (range == ARRAY_SIZE(soc_data->vco_range))
+			range = 0;
+		else
+			range = ARRAY_SIZE(soc_data->vco_range) - range - 1;
+
+		regmap_read(pll->map, reg[CTRL3_OFFSET], &ctrl3_val);
+		ctrl3_val &= ~CTRL3_VCO_RANGE_MASK;
+		ctrl3_val |= range << 1;
+		regmap_write(pll->map, reg[CTRL3_OFFSET], ctrl3_val);
+
+		if (frac_val) {
+			ctrl_val |= CTRL_FORCE_RESET;
+			amb_pll_write_enable(pll->map, reg[CTRL_OFFSET],
+					     ctrl_val);
+		}
+
+		ctrl_val &= ~CTRL_FORCE_RESET;
+		amb_pll_write_enable(pll->map, reg[CTRL_OFFSET], ctrl_val);
+	}
+
+	new_rate = amb_pll_recalc_rate(hw, parent_rate);
+	rate_tmp = rate / pll->fix_divider;
+	if (max(new_rate, rate_tmp) - min(new_rate, rate_tmp) > 10)
+		pr_warn("%s: requested %lu, got %lu\n", clk_hw_get_name(hw),
+			rate_tmp, new_rate);
+
+	return 0;
+}
+
+static const struct clk_ops amb_pll_ops = {
+	.recalc_rate = amb_pll_recalc_rate,
+	.determine_rate = amb_pll_determine_rate,
+	.set_rate = amb_pll_set_rate,
+};
+
+struct clk_hw *amb_pll_register(struct device *dev, struct regmap *map,
+				const struct amb_pll_desc *desc)
+{
+	struct amb_pll *pll;
+	struct clk_init_data init = {};
+	const struct clk_hw *parent = desc->parent;
+	int ret;
+
+	pll = devm_kzalloc(dev, sizeof(*pll), GFP_KERNEL);
+	if (!pll)
+		return ERR_PTR(-ENOMEM);
+
+	pll->map = map;
+	memcpy(pll->reg_offset, desc->reg_offset, sizeof(pll->reg_offset));
+	pll->soc_data = desc->soc_data;
+	pll->fix_divider = 1;
+	pll->frac_mode = desc->frac_mode;
+
+	init.name = desc->name;
+	init.ops = &amb_pll_ops;
+	init.flags = CLK_GET_RATE_NOCACHE | CLK_IS_CRITICAL;
+	init.parent_hws = &parent;
+	init.num_parents = 1;
+
+	pll->hw.init = &init;
+
+	ret = devm_clk_hw_register(dev, &pll->hw);
+	if (ret)
+		return ERR_PTR(ret);
+
+	return &pll->hw;
+}
diff --git a/drivers/clk/ambarella/ccu_pll.h b/drivers/clk/ambarella/ccu_pll.h
new file mode 100644
index 000000000000..6d901e88f410
--- /dev/null
+++ b/drivers/clk/ambarella/ccu_pll.h
@@ -0,0 +1,70 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2026 Ambarella, Inc.
+ */
+
+#ifndef __CCU_PLL_H
+#define __CCU_PLL_H
+
+#include <linux/bits.h>
+#include <linux/clk-provider.h>
+#include <linux/device.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+
+enum {
+	CTRL_OFFSET = 0,
+	FRAC_OFFSET,
+	CTRL2_OFFSET,
+	CTRL3_OFFSET,
+	PRES_OFFSET,
+	POST_OFFSET,
+	REG_NUM,
+};
+
+#define CTRL_BYPASS		BIT(2)
+#define CTRL_WRITE_ENABLE	BIT(0)
+#define CTRL_FRAC_MODE		BIT(3)
+#define CTRL_FORCE_RESET	BIT(4)
+#define CTRL_POWER_DOWN		BIT(5)
+#define CTRL_HALT_VCO		BIT(6)
+#define CTRL_VCODIV_DIV2	BIT(8)
+#define CTRL_FSDIV_DIV2		BIT(9)
+#define CTRL_FSOUT_DIV2		BIT(10)
+#define CTRL_BYPASS_HSDIV	BIT(11)
+
+#define CTRL2_VCODIV_DIV2	BIT(8)
+#define CTRL2_FSDIV_DIV2	BIT(9)
+#define CTRL2_FSOUT_DIV2	BIT(11)
+#define CTRL2_BYPASS_HSDIV	BIT(12)
+
+#define CTRL3_VCO_RANGE_MASK	0x6
+#define CTRL3_VCO_CLAMP		0x8
+
+struct amb_pll_soc_data {
+	u32 pll_version;
+	u32 fsout_mask;
+	u32 fsout_val;
+	u32 fsdiv_mask;
+	u32 fsdiv_val;
+	u32 vcodiv_mask;
+	u32 vcodiv_val;
+	u32 vco_max_mhz;
+	u32 vco_min_mhz;
+	u32 vco_range[4];
+	u32 ctrl2_val;
+	u32 ctrl3_val;
+};
+
+struct amb_pll_desc {
+	const char *name;
+	const struct clk_hw *parent;
+	u32 reg_offset[REG_NUM];
+	const struct amb_pll_soc_data *soc_data;
+	bool frac_mode;
+};
+
+struct clk_hw *amb_pll_register(struct device *dev, struct regmap *map,
+				const struct amb_pll_desc *desc);
+
+#endif

-- 
2.34.1




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

* [PATCH v6 08/13] gpio: regmap: support write_data_after_dir and girq
  2026-09-04  6:38 [PATCH v6 00/13] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
                   ` (6 preceding siblings ...)
  2026-09-04  6:38 ` [PATCH v6 07/13] clk: ambarella: add CV75 CCU driver Long Zhao via B4 Relay
@ 2026-09-04  6:38 ` Long Zhao via B4 Relay
  2026-09-04 11:55   ` Andy Shevchenko
  2026-09-04  6:38 ` [PATCH v6 09/13] gpio: pl061: use gpio-regmap and add Ambarella layout Long Zhao via B4 Relay
                   ` (5 subsequent siblings)
  13 siblings, 1 reply; 28+ messages in thread
From: Long Zhao via B4 Relay @ 2026-09-04  6:38 UTC (permalink / raw)
  To: Arnd Bergmann, Krzysztof Kozlowski, Alexandre Belloni, soc,
	linux-arm-kernel
  Cc: Long Zhao, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Michael Turquette, Stephen Boyd, Linus Walleij,
	Bartosz Golaszewski, Greg Kroah-Hartman, Jiri Slaby,
	Andy Shevchenko, Ilpo Järvinen, Catalin Marinas, Will Deacon,
	Long Zhao, Lee Jones, mfd, devicetree, linux-clk, linux-gpio,
	linux-serial, linux-kernel

From: Long Zhao <longzhao@ambarella.com>

Add an optional write-after-direction-output quirk for controllers
that ignore data-register writes while a line is still configured as
input, and allow drivers to pass an existing gpio_irq_chip through
gpio_regmap_register() so IRQ setup can stay with the caller.

Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
 drivers/gpio/gpio-regmap.c  | 24 +++++++++++++++++++++++-
 include/linux/gpio/regmap.h | 13 +++++++++++++
 2 files changed, 36 insertions(+), 1 deletion(-)

diff --git a/drivers/gpio/gpio-regmap.c b/drivers/gpio/gpio-regmap.c
index 51b4d69b8740..b4b492a1765c 100644
--- a/drivers/gpio/gpio-regmap.c
+++ b/drivers/gpio/gpio-regmap.c
@@ -33,6 +33,7 @@ struct gpio_regmap {
 	unsigned int reg_dir_out_base;
 	unsigned long *fixed_direction_mask;
 	unsigned long *fixed_direction_output;
+	bool write_data_after_dir;
 
 #ifdef CONFIG_REGMAP_IRQ
 	int regmap_irq_line;
@@ -273,7 +274,15 @@ static int gpio_regmap_direction_output(struct gpio_chip *chip,
 
 	gpio_regmap_set(chip, offset, value);
 
-	return gpio_regmap_set_direction(chip, offset, true);
+	ret = gpio_regmap_set_direction(chip, offset, true);
+	if (ret)
+		return ret;
+
+	/* Some controllers ignore data writes while the line is still an input. */
+	if (gpio->write_data_after_dir)
+		gpio_regmap_set(chip, offset, value);
+
+	return 0;
 }
 
 void *gpio_regmap_get_drvdata(struct gpio_regmap *gpio)
@@ -311,6 +320,14 @@ struct gpio_regmap *gpio_regmap_register(const struct gpio_regmap_config *config
 	if (config->reg_dir_out_base && config->reg_dir_in_base)
 		return ERR_PTR(-EINVAL);
 
+	if (config->girq && config->irq_domain)
+		return ERR_PTR(-EINVAL);
+
+#ifdef CONFIG_REGMAP_IRQ
+	if (config->girq && config->regmap_irq_chip)
+		return ERR_PTR(-EINVAL);
+#endif
+
 	gpio = kzalloc_obj(*gpio);
 	if (!gpio)
 		return ERR_PTR(-ENOMEM);
@@ -376,6 +393,8 @@ struct gpio_regmap *gpio_regmap_register(const struct gpio_regmap_config *config
 			    config->fixed_direction_output, chip->ngpio);
 	}
 
+	gpio->write_data_after_dir = config->write_data_after_dir;
+
 	/* if not set, assume there is only one register */
 	gpio->ngpio_per_reg = config->ngpio_per_reg;
 	if (!gpio->ngpio_per_reg)
@@ -390,6 +409,9 @@ struct gpio_regmap *gpio_regmap_register(const struct gpio_regmap_config *config
 	if (!gpio->reg_mask_xlate)
 		gpio->reg_mask_xlate = gpio_regmap_simple_xlate;
 
+	if (config->girq)
+		chip->irq = *config->girq;
+
 	ret = gpiochip_add_data(chip, gpio);
 	if (ret < 0)
 		goto err_free_bitmap_output;
diff --git a/include/linux/gpio/regmap.h b/include/linux/gpio/regmap.h
index 06255756710d..f94dd95d330f 100644
--- a/include/linux/gpio/regmap.h
+++ b/include/linux/gpio/regmap.h
@@ -7,6 +7,7 @@ struct device;
 struct fwnode_handle;
 struct gpio_regmap;
 struct gpio_chip;
+struct gpio_irq_chip;
 struct irq_domain;
 struct regmap;
 
@@ -34,6 +35,11 @@ struct regmap;
  * @ngpio_per_reg:	(Optional) Number of GPIOs per register
  * @irq_domain:		(Optional) IRQ domain if the controller is
  *			interrupt-capable
+ * @girq:		(Optional) Interrupt chip settings for the gpio_chip,
+ *			prepared by the driver as for a direct gpiochip
+ *			registration. The gpio_chip will then create and
+ *			manage its own IRQ domain. Mutually exclusive with
+ *			@irq_domain and @regmap_irq_chip.
  * @reg_mask_xlate:     (Optional) Translates base address and GPIO
  *			offset to a register/bitmask pair. If not
  *			given the default gpio_regmap_simple_xlate()
@@ -48,6 +54,11 @@ struct regmap;
  *			(Optional) Bitmap representing the fixed direction of
  *			the GPIO lines. Useful when there are GPIO lines with a
  *			fixed direction mixed together in the same register.
+ * @write_data_after_dir:
+ *			(Optional) Write the output value again after
+ *			switching a line to output in ->direction_output().
+ *			Needed for hardware which ignores data register
+ *			writes while the line is configured as an input.
  * @drvdata:		(Optional) Pointer to driver specific data which is
  *			not used by gpio-remap but is provided "as is" to the
  *			driver callback(s).
@@ -95,8 +106,10 @@ struct gpio_regmap_config {
 	int reg_stride;
 	int ngpio_per_reg;
 	struct irq_domain *irq_domain;
+	const struct gpio_irq_chip *girq;
 	unsigned long *fixed_direction_mask;
 	unsigned long *fixed_direction_output;
+	bool write_data_after_dir;
 
 #ifdef CONFIG_REGMAP_IRQ
 	struct regmap_irq_chip *regmap_irq_chip;

-- 
2.34.1




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

* [PATCH v6 09/13] gpio: pl061: use gpio-regmap and add Ambarella layout
  2026-09-04  6:38 [PATCH v6 00/13] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
                   ` (7 preceding siblings ...)
  2026-09-04  6:38 ` [PATCH v6 08/13] gpio: regmap: support write_data_after_dir and girq Long Zhao via B4 Relay
@ 2026-09-04  6:38 ` Long Zhao via B4 Relay
  2026-09-04 13:14   ` Andy Shevchenko
  2026-09-04  6:38 ` [PATCH v6 10/13] pinctrl: ambarella: add CV75 pin controller Long Zhao via B4 Relay
                   ` (4 subsequent siblings)
  13 siblings, 1 reply; 28+ messages in thread
From: Long Zhao via B4 Relay @ 2026-09-04  6:38 UTC (permalink / raw)
  To: Arnd Bergmann, Krzysztof Kozlowski, Alexandre Belloni, soc,
	linux-arm-kernel
  Cc: Long Zhao, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Michael Turquette, Stephen Boyd, Linus Walleij,
	Bartosz Golaszewski, Greg Kroah-Hartman, Jiri Slaby,
	Andy Shevchenko, Ilpo Järvinen, Catalin Marinas, Will Deacon,
	Long Zhao, Lee Jones, mfd, devicetree, linux-clk, linux-gpio,
	linux-serial, linux-kernel

From: Long Zhao <longzhao@ambarella.com>

Convert PL061 data and direction handling to gpio-regmap while
keeping the existing PL061 irqchip path. Add an Ambarella CV75
register layout variant on the AMBA bus, without claiming unverified
GPIO suspend/resume support for Ambarella.

Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
 drivers/gpio/Kconfig      |   2 +
 drivers/gpio/gpio-pl061.c | 342 ++++++++++++++++++++++++++++------------------
 include/linux/amba/bus.h  |   1 +
 3 files changed, 212 insertions(+), 133 deletions(-)

diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index f03c05288376..55a129836158 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -583,6 +583,8 @@ config GPIO_PL061
 	depends on ARM_AMBA || COMPILE_TEST
 	select IRQ_DOMAIN
 	select GPIOLIB_IRQCHIP
+	select GPIO_REGMAP
+	select REGMAP_MMIO
 	help
 	  Say yes here to support the PrimeCell PL061 GPIO device.
 
diff --git a/drivers/gpio/gpio-pl061.c b/drivers/gpio/gpio-pl061.c
index 919cf86fd590..6b09031f8664 100644
--- a/drivers/gpio/gpio-pl061.c
+++ b/drivers/gpio/gpio-pl061.c
@@ -13,6 +13,7 @@
 #include <linux/device.h>
 #include <linux/errno.h>
 #include <linux/gpio/driver.h>
+#include <linux/gpio/regmap.h>
 #include <linux/init.h>
 #include <linux/interrupt.h>
 #include <linux/io.h>
@@ -22,20 +23,36 @@
 #include <linux/module.h>
 #include <linux/pinctrl/consumer.h>
 #include <linux/pm.h>
+#include <linux/regmap.h>
 #include <linux/seq_file.h>
 #include <linux/slab.h>
 #include <linux/spinlock.h>
 
-#define GPIODIR 0x400
-#define GPIOIS  0x404
-#define GPIOIBE 0x408
-#define GPIOIEV 0x40C
-#define GPIOIE  0x410
-#define GPIORIS 0x414
-#define GPIOMIS 0x418
-#define GPIOIC  0x41C
-
-#define PL061_GPIO_NR	8
+#define PL061_REG_NONE		((unsigned int)-1)
+
+#define PL061_GPIO_NR		8
+#define AMBARELLA_GPIO_NR	32
+
+struct pl061_variant_data {
+	unsigned int data;
+	unsigned int dir;
+	unsigned int is;
+	unsigned int ibe;
+	unsigned int iev;
+	unsigned int ie;
+	unsigned int ris;
+	unsigned int mis;
+	unsigned int ic;
+	unsigned int mask;
+	unsigned int enable;
+	unsigned int ngpio;
+	bool access_32bit;
+	bool masked_data_address;
+	bool write_data_after_dir;
+	bool clear_irq_on_type;
+	bool pm_save_restore;
+	const struct regmap_config *regmap_config;
+};
 
 struct pl061_context_save_regs {
 	u8 gpio_data;
@@ -48,74 +65,47 @@ struct pl061_context_save_regs {
 
 struct pl061 {
 	raw_spinlock_t		lock;
-
 	void __iomem		*base;
-	struct gpio_chip	gc;
+	const struct pl061_variant_data *variant;
+	struct gpio_irq_chip	girq;
 	int			parent_irq;
 
 	struct pl061_context_save_regs csave_regs;
 };
 
-static int pl061_get_direction(struct gpio_chip *gc, unsigned offset)
-{
-	struct pl061 *pl061 = gpiochip_get_data(gc);
-
-	if (readb(pl061->base + GPIODIR) & BIT(offset))
-		return GPIO_LINE_DIRECTION_OUT;
-
-	return GPIO_LINE_DIRECTION_IN;
-}
-
-static int pl061_direction_input(struct gpio_chip *gc, unsigned offset)
+static struct pl061 *pl061_from_gpio_chip(struct gpio_chip *gc)
 {
-	struct pl061 *pl061 = gpiochip_get_data(gc);
-	unsigned long flags;
-	unsigned char gpiodir;
-
-	raw_spin_lock_irqsave(&pl061->lock, flags);
-	gpiodir = readb(pl061->base + GPIODIR);
-	gpiodir &= ~(BIT(offset));
-	writeb(gpiodir, pl061->base + GPIODIR);
-	raw_spin_unlock_irqrestore(&pl061->lock, flags);
-
-	return 0;
+	return gpio_regmap_get_drvdata(gpiochip_get_data(gc));
 }
 
-static int pl061_direction_output(struct gpio_chip *gc, unsigned offset,
-		int value)
+static u32 pl061_read(struct pl061 *pl061, unsigned int reg)
 {
-	struct pl061 *pl061 = gpiochip_get_data(gc);
-	unsigned long flags;
-	unsigned char gpiodir;
+	if (pl061->variant->access_32bit)
+		return readl(pl061->base + reg);
 
-	raw_spin_lock_irqsave(&pl061->lock, flags);
-	writeb(!!value << offset, pl061->base + (BIT(offset + 2)));
-	gpiodir = readb(pl061->base + GPIODIR);
-	gpiodir |= BIT(offset);
-	writeb(gpiodir, pl061->base + GPIODIR);
-
-	/*
-	 * gpio value is set again, because pl061 doesn't allow to set value of
-	 * a gpio pin before configuring it in OUT mode.
-	 */
-	writeb(!!value << offset, pl061->base + (BIT(offset + 2)));
-	raw_spin_unlock_irqrestore(&pl061->lock, flags);
-
-	return 0;
+	return readb(pl061->base + reg);
 }
 
-static int pl061_get_value(struct gpio_chip *gc, unsigned offset)
+static void pl061_write(struct pl061 *pl061, u32 value, unsigned int reg)
 {
-	struct pl061 *pl061 = gpiochip_get_data(gc);
-
-	return !!readb(pl061->base + (BIT(offset + 2)));
+	if (pl061->variant->access_32bit)
+		writel(value, pl061->base + reg);
+	else
+		writeb(value, pl061->base + reg);
 }
 
-static int pl061_set_value(struct gpio_chip *gc, unsigned int offset, int value)
+static int pl061_reg_mask_xlate(struct gpio_regmap *gpio, unsigned int base,
+				unsigned int offset, unsigned int *reg,
+				unsigned int *mask)
 {
-	struct pl061 *pl061 = gpiochip_get_data(gc);
+	struct pl061 *pl061 = gpio_regmap_get_drvdata(gpio);
 
-	writeb(!!value << offset, pl061->base + (BIT(offset + 2)));
+	/* ARM PL061 data bits use masked addresses: bits [9:2] select pins. */
+	if (base == pl061->variant->data)
+		*reg = BIT(offset + 2);
+	else
+		*reg = base;
+	*mask = BIT(offset);
 
 	return 0;
 }
@@ -123,18 +113,17 @@ static int pl061_set_value(struct gpio_chip *gc, unsigned int offset, int value)
 static int pl061_irq_type(struct irq_data *d, unsigned trigger)
 {
 	struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
-	struct pl061 *pl061 = gpiochip_get_data(gc);
+	struct pl061 *pl061 = pl061_from_gpio_chip(gc);
+	const struct pl061_variant_data *variant = pl061->variant;
 	int offset = irqd_to_hwirq(d);
 	unsigned long flags;
-	u8 gpiois, gpioibe, gpioiev;
-	u8 bit = BIT(offset);
+	u32 gpiois, gpioibe, gpioiev;
+	u32 bit = BIT(offset);
 
-	if (offset < 0 || offset >= PL061_GPIO_NR)
+	if (offset < 0 || offset >= (int)gc->ngpio)
 		return -EINVAL;
 
-	if ((trigger & (IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_LEVEL_LOW)) &&
-	    (trigger & (IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING)))
-	{
+	if ((trigger & IRQ_TYPE_LEVEL_MASK) && (trigger & IRQ_TYPE_EDGE_BOTH)) {
 		dev_err(gc->parent,
 			"trying to configure line %d for both level and edge "
 			"detection, choose one!\n",
@@ -142,14 +131,13 @@ static int pl061_irq_type(struct irq_data *d, unsigned trigger)
 		return -EINVAL;
 	}
 
-
 	raw_spin_lock_irqsave(&pl061->lock, flags);
 
-	gpioiev = readb(pl061->base + GPIOIEV);
-	gpiois = readb(pl061->base + GPIOIS);
-	gpioibe = readb(pl061->base + GPIOIBE);
+	gpioiev = pl061_read(pl061, variant->iev);
+	gpiois = pl061_read(pl061, variant->is);
+	gpioibe = pl061_read(pl061, variant->ibe);
 
-	if (trigger & (IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_LEVEL_LOW)) {
+	if (trigger & IRQ_TYPE_LEVEL_MASK) {
 		bool polarity = trigger & IRQ_TYPE_LEVEL_HIGH;
 
 		/* Disable edge detection */
@@ -199,9 +187,11 @@ static int pl061_irq_type(struct irq_data *d, unsigned trigger)
 			 offset);
 	}
 
-	writeb(gpiois, pl061->base + GPIOIS);
-	writeb(gpioibe, pl061->base + GPIOIBE);
-	writeb(gpioiev, pl061->base + GPIOIEV);
+	pl061_write(pl061, gpiois, variant->is);
+	pl061_write(pl061, gpioibe, variant->ibe);
+	pl061_write(pl061, gpioiev, variant->iev);
+	if (variant->clear_irq_on_type)
+		pl061_write(pl061, bit, variant->ic);
 
 	raw_spin_unlock_irqrestore(&pl061->lock, flags);
 
@@ -213,16 +203,15 @@ static void pl061_irq_handler(struct irq_desc *desc)
 	unsigned long pending;
 	int offset;
 	struct gpio_chip *gc = irq_desc_get_handler_data(desc);
-	struct pl061 *pl061 = gpiochip_get_data(gc);
+	struct pl061 *pl061 = pl061_from_gpio_chip(gc);
 	struct irq_chip *irqchip = irq_desc_get_chip(desc);
 
 	chained_irq_enter(irqchip, desc);
 
-	pending = readb(pl061->base + GPIOMIS);
+	pending = pl061_read(pl061, pl061->variant->mis);
 	if (pending) {
-		for_each_set_bit(offset, &pending, PL061_GPIO_NR)
-			generic_handle_domain_irq(gc->irq.domain,
-						  offset);
+		for_each_set_bit(offset, &pending, gc->ngpio)
+			generic_handle_domain_irq(gc->irq.domain, offset);
 	}
 
 	chained_irq_exit(irqchip, desc);
@@ -231,13 +220,13 @@ static void pl061_irq_handler(struct irq_desc *desc)
 static void pl061_irq_mask(struct irq_data *d)
 {
 	struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
-	struct pl061 *pl061 = gpiochip_get_data(gc);
-	u8 mask = BIT(irqd_to_hwirq(d) % PL061_GPIO_NR);
-	u8 gpioie;
+	struct pl061 *pl061 = pl061_from_gpio_chip(gc);
+	u32 mask = BIT(irqd_to_hwirq(d) % gc->ngpio);
+	u32 gpioie;
 
 	raw_spin_lock(&pl061->lock);
-	gpioie = readb(pl061->base + GPIOIE) & ~mask;
-	writeb(gpioie, pl061->base + GPIOIE);
+	gpioie = pl061_read(pl061, pl061->variant->ie) & ~mask;
+	pl061_write(pl061, gpioie, pl061->variant->ie);
 	raw_spin_unlock(&pl061->lock);
 
 	gpiochip_disable_irq(gc, d->hwirq);
@@ -246,15 +235,15 @@ static void pl061_irq_mask(struct irq_data *d)
 static void pl061_irq_unmask(struct irq_data *d)
 {
 	struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
-	struct pl061 *pl061 = gpiochip_get_data(gc);
-	u8 mask = BIT(irqd_to_hwirq(d) % PL061_GPIO_NR);
-	u8 gpioie;
+	struct pl061 *pl061 = pl061_from_gpio_chip(gc);
+	u32 mask = BIT(irqd_to_hwirq(d) % gc->ngpio);
+	u32 gpioie;
 
 	gpiochip_enable_irq(gc, d->hwirq);
 
 	raw_spin_lock(&pl061->lock);
-	gpioie = readb(pl061->base + GPIOIE) | mask;
-	writeb(gpioie, pl061->base + GPIOIE);
+	gpioie = pl061_read(pl061, pl061->variant->ie) | mask;
+	pl061_write(pl061, gpioie, pl061->variant->ie);
 	raw_spin_unlock(&pl061->lock);
 }
 
@@ -269,18 +258,18 @@ static void pl061_irq_unmask(struct irq_data *d)
 static void pl061_irq_ack(struct irq_data *d)
 {
 	struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
-	struct pl061 *pl061 = gpiochip_get_data(gc);
-	u8 mask = BIT(irqd_to_hwirq(d) % PL061_GPIO_NR);
+	struct pl061 *pl061 = pl061_from_gpio_chip(gc);
+	u32 mask = BIT(irqd_to_hwirq(d) % gc->ngpio);
 
 	raw_spin_lock(&pl061->lock);
-	writeb(mask, pl061->base + GPIOIC);
+	pl061_write(pl061, mask, pl061->variant->ic);
 	raw_spin_unlock(&pl061->lock);
 }
 
 static int pl061_irq_set_wake(struct irq_data *d, unsigned int state)
 {
 	struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
-	struct pl061 *pl061 = gpiochip_get_data(gc);
+	struct pl061 *pl061 = pl061_from_gpio_chip(gc);
 
 	return irq_set_irq_wake(pl061->parent_irq, state);
 }
@@ -306,42 +295,41 @@ static const struct irq_chip pl061_irq_chip = {
 static int pl061_probe(struct amba_device *adev, const struct amba_id *id)
 {
 	struct device *dev = &adev->dev;
+	struct gpio_regmap_config config = {};
+	const struct pl061_variant_data *variant = id->data;
 	struct pl061 *pl061;
 	struct gpio_irq_chip *girq;
+	struct regmap *regmap;
 	int ret, irq;
 
 	pl061 = devm_kzalloc(dev, sizeof(*pl061), GFP_KERNEL);
 	if (pl061 == NULL)
 		return -ENOMEM;
 
+	pl061->variant = variant;
+
 	pl061->base = devm_ioremap_resource(dev, &adev->res);
 	if (IS_ERR(pl061->base))
 		return PTR_ERR(pl061->base);
 
 	raw_spin_lock_init(&pl061->lock);
-	pl061->gc.request = gpiochip_generic_request;
-	pl061->gc.free = gpiochip_generic_free;
-	pl061->gc.base = -1;
-	pl061->gc.get_direction = pl061_get_direction;
-	pl061->gc.direction_input = pl061_direction_input;
-	pl061->gc.direction_output = pl061_direction_output;
-	pl061->gc.get = pl061_get_value;
-	pl061->gc.set = pl061_set_value;
-	pl061->gc.ngpio = PL061_GPIO_NR;
-	pl061->gc.label = dev_name(dev);
-	pl061->gc.parent = dev;
-	pl061->gc.owner = THIS_MODULE;
-
-	/*
-	 * irq_chip support
-	 */
-	writeb(0, pl061->base + GPIOIE); /* disable irqs */
+
+	regmap = devm_regmap_init_mmio(dev, pl061->base, variant->regmap_config);
+	if (IS_ERR(regmap))
+		return PTR_ERR(regmap);
+
+	pl061_write(pl061, 0, variant->ie); /* disable irqs */
+	if (variant->mask != PL061_REG_NONE)
+		pl061_write(pl061, GENMASK(variant->ngpio - 1, 0), variant->mask);
+	if (variant->enable != PL061_REG_NONE)
+		pl061_write(pl061, GENMASK(variant->ngpio - 1, 0), variant->enable);
+
 	irq = adev->irq[0];
 	if (!irq)
 		dev_warn(&adev->dev, "IRQ support disabled\n");
 	pl061->parent_irq = irq;
 
-	girq = &pl061->gc.irq;
+	girq = &pl061->girq;
 	gpio_irq_chip_set_chip(girq, &pl061_irq_chip);
 	girq->parent_handler = pl061_irq_handler;
 	girq->num_parents = 1;
@@ -353,7 +341,19 @@ static int pl061_probe(struct amba_device *adev, const struct amba_id *id)
 	girq->default_type = IRQ_TYPE_NONE;
 	girq->handler = handle_bad_irq;
 
-	ret = devm_gpiochip_add_data(dev, &pl061->gc, pl061);
+	config.parent = dev;
+	config.regmap = regmap;
+	config.ngpio = variant->ngpio;
+	config.reg_dat_base = GPIO_REGMAP_ADDR(variant->data);
+	config.reg_set_base = GPIO_REGMAP_ADDR(variant->data);
+	config.reg_dir_out_base = variant->dir;
+	if (variant->masked_data_address)
+		config.reg_mask_xlate = pl061_reg_mask_xlate;
+	config.write_data_after_dir = variant->write_data_after_dir;
+	config.girq = girq;
+	config.drvdata = pl061;
+
+	ret = PTR_ERR_OR_ZERO(devm_gpio_regmap_register(dev, &config));
 	if (ret)
 		return ret;
 
@@ -366,19 +366,23 @@ static int pl061_probe(struct amba_device *adev, const struct amba_id *id)
 static int pl061_suspend(struct device *dev)
 {
 	struct pl061 *pl061 = dev_get_drvdata(dev);
+	const struct pl061_variant_data *variant = pl061->variant;
 	int offset;
 
+	if (!variant->pm_save_restore)
+		return 0;
+
 	pl061->csave_regs.gpio_data = 0;
-	pl061->csave_regs.gpio_dir = readb(pl061->base + GPIODIR);
-	pl061->csave_regs.gpio_is = readb(pl061->base + GPIOIS);
-	pl061->csave_regs.gpio_ibe = readb(pl061->base + GPIOIBE);
-	pl061->csave_regs.gpio_iev = readb(pl061->base + GPIOIEV);
-	pl061->csave_regs.gpio_ie = readb(pl061->base + GPIOIE);
-
-	for (offset = 0; offset < PL061_GPIO_NR; offset++) {
-		if (pl061->csave_regs.gpio_dir & (BIT(offset)))
+	pl061->csave_regs.gpio_dir = pl061_read(pl061, variant->dir);
+	pl061->csave_regs.gpio_is = pl061_read(pl061, variant->is);
+	pl061->csave_regs.gpio_ibe = pl061_read(pl061, variant->ibe);
+	pl061->csave_regs.gpio_iev = pl061_read(pl061, variant->iev);
+	pl061->csave_regs.gpio_ie = pl061_read(pl061, variant->ie);
+
+	for (offset = 0; offset < variant->ngpio; offset++) {
+		if (pl061->csave_regs.gpio_dir & BIT(offset))
 			pl061->csave_regs.gpio_data |=
-				pl061_get_value(&pl061->gc, offset) << offset;
+				pl061_read(pl061, BIT(offset + 2));
 	}
 
 	return 0;
@@ -387,31 +391,103 @@ static int pl061_suspend(struct device *dev)
 static int pl061_resume(struct device *dev)
 {
 	struct pl061 *pl061 = dev_get_drvdata(dev);
+	const struct pl061_variant_data *variant = pl061->variant;
 	int offset;
 
-	for (offset = 0; offset < PL061_GPIO_NR; offset++) {
-		if (pl061->csave_regs.gpio_dir & (BIT(offset)))
-			pl061_direction_output(&pl061->gc, offset,
-					pl061->csave_regs.gpio_data &
-					(BIT(offset)));
-		else
-			pl061_direction_input(&pl061->gc, offset);
+	if (!variant->pm_save_restore)
+		return 0;
+
+	for (offset = 0; offset < variant->ngpio; offset++) {
+		u32 dir = pl061_read(pl061, variant->dir);
+
+		if (pl061->csave_regs.gpio_dir & BIT(offset)) {
+			u32 value = !!(pl061->csave_regs.gpio_data & BIT(offset)) << offset;
+
+			pl061_write(pl061, value, BIT(offset + 2));
+			pl061_write(pl061, dir | BIT(offset), variant->dir);
+			/*
+			 * gpio value is set again, because pl061 doesn't allow to set value of
+			 * a gpio pin before configuring it in OUT mode.
+			 */
+			pl061_write(pl061, value, BIT(offset + 2));
+		} else {
+			pl061_write(pl061, dir & ~BIT(offset), variant->dir);
+		}
 	}
 
-	writeb(pl061->csave_regs.gpio_is, pl061->base + GPIOIS);
-	writeb(pl061->csave_regs.gpio_ibe, pl061->base + GPIOIBE);
-	writeb(pl061->csave_regs.gpio_iev, pl061->base + GPIOIEV);
-	writeb(pl061->csave_regs.gpio_ie, pl061->base + GPIOIE);
+	pl061_write(pl061, pl061->csave_regs.gpio_is, variant->is);
+	pl061_write(pl061, pl061->csave_regs.gpio_ibe, variant->ibe);
+	pl061_write(pl061, pl061->csave_regs.gpio_iev, variant->iev);
+	pl061_write(pl061, pl061->csave_regs.gpio_ie, variant->ie);
 
 	return 0;
 }
 
 static DEFINE_SIMPLE_DEV_PM_OPS(pl061_dev_pm_ops, pl061_suspend, pl061_resume);
 
+static const struct regmap_config pl061_arm_regmap_config = {
+	.reg_bits = 16,
+	.val_bits = 8,
+	.reg_stride = 4,
+	.max_register = 0x400,
+	.fast_io = true,
+};
+
+static const struct regmap_config pl061_ambarella_regmap_config = {
+	.reg_bits = 32,
+	.val_bits = 32,
+	.reg_stride = 4,
+	.max_register = 0x2c,
+	.fast_io = true,
+};
+
+static struct pl061_variant_data pl061_arm = {
+	.data = 0x000,
+	.dir = 0x400,
+	.is = 0x404,
+	.ibe = 0x408,
+	.iev = 0x40c,
+	.ie = 0x410,
+	.ris = 0x414,
+	.mis = 0x418,
+	.ic = 0x41c,
+	.mask = PL061_REG_NONE,
+	.enable = PL061_REG_NONE,
+	.ngpio = PL061_GPIO_NR,
+	.masked_data_address = true,
+	.write_data_after_dir = true,
+	.pm_save_restore = true,
+	.regmap_config = &pl061_arm_regmap_config,
+};
+
+static struct pl061_variant_data pl061_ambarella = {
+	.data = 0x00,
+	.dir = 0x04,
+	.is = 0x08,
+	.ibe = 0x0c,
+	.iev = 0x10,
+	.ie = 0x14,
+	.ris = 0x1c,
+	.mis = 0x20,
+	.ic = 0x24,
+	.mask = 0x28,
+	.enable = 0x2c,
+	.ngpio = AMBARELLA_GPIO_NR,
+	.access_32bit = true,
+	.clear_irq_on_type = true,
+	.regmap_config = &pl061_ambarella_regmap_config,
+};
+
 static const struct amba_id pl061_ids[] = {
 	{
 		.id	= 0x00041061,
 		.mask	= 0x000fffff,
+		.data	= &pl061_arm,
+	},
+	{
+		.id	= (AMBA_VENDOR_AMBARELLA << 12) | 0x061,
+		.mask	= 0x000fffff,
+		.data	= &pl061_ambarella,
 	},
 	{ 0, 0 },
 };
diff --git a/include/linux/amba/bus.h b/include/linux/amba/bus.h
index 80a74cd2da7e..411533bfa61d 100644
--- a/include/linux/amba/bus.h
+++ b/include/linux/amba/bus.h
@@ -98,6 +98,7 @@ enum amba_vendor {
 	AMBA_VENDOR_ST = 0x80,
 	AMBA_VENDOR_QCOM = 0x51,
 	AMBA_VENDOR_LSI = 0xb6,
+	AMBA_VENDOR_AMBARELLA = 0xe8,
 };
 
 extern const struct bus_type amba_bustype;

-- 
2.34.1




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

* [PATCH v6 10/13] pinctrl: ambarella: add CV75 pin controller
  2026-09-04  6:38 [PATCH v6 00/13] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
                   ` (8 preceding siblings ...)
  2026-09-04  6:38 ` [PATCH v6 09/13] gpio: pl061: use gpio-regmap and add Ambarella layout Long Zhao via B4 Relay
@ 2026-09-04  6:38 ` Long Zhao via B4 Relay
  2026-09-04 13:29   ` Andy Shevchenko
  2026-09-04  6:38 ` [PATCH v6 11/13] serial: 8250_dw: add Ambarella CV75 quirks Long Zhao via B4 Relay
                   ` (3 subsequent siblings)
  13 siblings, 1 reply; 28+ messages in thread
From: Long Zhao via B4 Relay @ 2026-09-04  6:38 UTC (permalink / raw)
  To: Arnd Bergmann, Krzysztof Kozlowski, Alexandre Belloni, soc,
	linux-arm-kernel
  Cc: Long Zhao, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Michael Turquette, Stephen Boyd, Linus Walleij,
	Bartosz Golaszewski, Greg Kroah-Hartman, Jiri Slaby,
	Andy Shevchenko, Ilpo Järvinen, Catalin Marinas, Will Deacon,
	Long Zhao, Lee Jones, mfd, devicetree, linux-clk, linux-gpio,
	linux-serial, linux-kernel

From: Long Zhao <longzhao@ambarella.com>

Add an Ambarella pinmux-only pinctrl driver with CV75 function/group
tables. GPIO is handled by the PL061 driver.

Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
 drivers/pinctrl/Kconfig                  |  15 +
 drivers/pinctrl/Makefile                 |   2 +
 drivers/pinctrl/pinctrl-ambarella-cv75.c | 561 +++++++++++++++++++++++
 drivers/pinctrl/pinctrl-ambarella.c      | 761 +++++++++++++++++++++++++++++++
 drivers/pinctrl/pinctrl-ambarella.h      |  50 ++
 5 files changed, 1389 insertions(+)

diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig
index c2cdd7b2c49b..da423792b5fa 100644
--- a/drivers/pinctrl/Kconfig
+++ b/drivers/pinctrl/Kconfig
@@ -37,6 +37,21 @@ config DEBUG_PINCTRL
 	help
 	  Say Y here to add some extra checks and diagnostics to PINCTRL calls.
 
+config PINCTRL_AMB
+	bool "Ambarella pin controller support"
+	depends on ARCH_AMBARELLA || COMPILE_TEST
+	depends on HAS_IOMEM
+	default y if ARCH_AMBARELLA
+	select GENERIC_PINCONF
+	select GPIOLIB
+	select GPIOLIB_IRQCHIP
+	select GPIO_PL061
+	select MFD_SYSCON
+	select PINCONF
+	select PINMUX
+	help
+	  Say Y here to enable the Ambarella pin controller driver.
+
 config PINCTRL_AMD
 	bool "AMD GPIO pin control"
 	depends on HAS_IOMEM
diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile
index a35d71135abf..ce0825f4fc1b 100644
--- a/drivers/pinctrl/Makefile
+++ b/drivers/pinctrl/Makefile
@@ -10,6 +10,8 @@ obj-$(CONFIG_GENERIC_PINCONF)	+= pinconf-generic.o
 obj-$(CONFIG_GENERIC_PINCTRL)	+= pinctrl-generic.o
 obj-$(CONFIG_OF)		+= devicetree.o
 
+obj-$(CONFIG_PINCTRL_AMB)	+= pinctrl-ambarella.o
+obj-$(CONFIG_PINCTRL_AMB)	+= pinctrl-ambarella-cv75.o
 obj-$(CONFIG_PINCTRL_AMD)	+= pinctrl-amd.o
 obj-$(CONFIG_PINCTRL_AMDISP)	+= pinctrl-amdisp.o
 obj-$(CONFIG_PINCTRL_APPLE_GPIO) += pinctrl-apple-gpio.o
diff --git a/drivers/pinctrl/pinctrl-ambarella-cv75.c b/drivers/pinctrl/pinctrl-ambarella-cv75.c
new file mode 100644
index 000000000000..e617f0717b31
--- /dev/null
+++ b/drivers/pinctrl/pinctrl-ambarella-cv75.c
@@ -0,0 +1,561 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Ambarella CV75 pinctrl data
+ *
+ * Copyright (C) 2026, Ambarella, Inc.
+ */
+
+#include <linux/kernel.h>
+
+#include "pinctrl-ambarella.h"
+
+#define CV75_PINMUX_GROUP(_name, ...)					\
+	static const u32 cv75_##_name##_pinmux[] = { __VA_ARGS__ }
+
+#define CV75_GROUP(_name)						\
+	{								\
+		.name = #_name,						\
+		.pinmux = cv75_##_name##_pinmux,				\
+		.num_pins = ARRAY_SIZE(cv75_##_name##_pinmux),		\
+	}
+
+#define CV75_FUNCTION(_name)						\
+	{								\
+		.name = #_name,						\
+		.groups = cv75_##_name##_groups,				\
+		.num_groups = ARRAY_SIZE(cv75_##_name##_groups),		\
+	}
+
+/* UART */
+CV75_PINMUX_GROUP(uart0, AMBA_PINMUX(44, 1), AMBA_PINMUX(45, 1));
+CV75_PINMUX_GROUP(uart1, AMBA_PINMUX(46, 1), AMBA_PINMUX(47, 1));
+CV75_PINMUX_GROUP(uart1_flow, AMBA_PINMUX(48, 1), AMBA_PINMUX(49, 1));
+CV75_PINMUX_GROUP(uart2_a, AMBA_PINMUX(46, 2), AMBA_PINMUX(47, 2));
+CV75_PINMUX_GROUP(uart2_b, AMBA_PINMUX(50, 2), AMBA_PINMUX(51, 2));
+CV75_PINMUX_GROUP(uart2_c, AMBA_PINMUX(66, 2), AMBA_PINMUX(68, 2));
+CV75_PINMUX_GROUP(uart2_flow_a, AMBA_PINMUX(48, 2), AMBA_PINMUX(49, 2));
+CV75_PINMUX_GROUP(uart2_flow_b, AMBA_PINMUX(65, 3), AMBA_PINMUX(67, 3));
+CV75_PINMUX_GROUP(uart3_a, AMBA_PINMUX(70, 3), AMBA_PINMUX(72, 3));
+CV75_PINMUX_GROUP(uart3_b, AMBA_PINMUX(80, 3), AMBA_PINMUX(79, 3));
+CV75_PINMUX_GROUP(uart3_flow_a, AMBA_PINMUX(71, 3), AMBA_PINMUX(69, 3));
+CV75_PINMUX_GROUP(uart3_flow_b, AMBA_PINMUX(81, 3), AMBA_PINMUX(82, 3));
+CV75_PINMUX_GROUP(uart4_a, AMBA_PINMUX(29, 3), AMBA_PINMUX(30, 3));
+CV75_PINMUX_GROUP(uart4_b, AMBA_PINMUX(76, 3), AMBA_PINMUX(77, 3));
+CV75_PINMUX_GROUP(uart4_flow_a, AMBA_PINMUX(28, 3), AMBA_PINMUX(31, 3));
+CV75_PINMUX_GROUP(uart4_flow_b, AMBA_PINMUX(74, 3), AMBA_PINMUX(75, 3));
+
+/* Flash */
+CV75_PINMUX_GROUP(snand, AMBA_PINMUX(79, 1), AMBA_PINMUX(80, 1),
+		  AMBA_PINMUX(81, 1), AMBA_PINMUX(82, 1),
+		  AMBA_PINMUX(83, 1), AMBA_PINMUX(84, 1));
+CV75_PINMUX_GROUP(spinor, AMBA_PINMUX(79, 2), AMBA_PINMUX(80, 2),
+		  AMBA_PINMUX(81, 2), AMBA_PINMUX(82, 2),
+		  AMBA_PINMUX(83, 2), AMBA_PINMUX(84, 2),
+		  AMBA_PINMUX(85, 2));
+
+/* SD/MMC */
+CV75_PINMUX_GROUP(sdmmc0_cd, AMBA_PINMUX(6, 1));
+CV75_PINMUX_GROUP(sdmmc0_wp, AMBA_PINMUX(7, 1));
+CV75_PINMUX_GROUP(sdmmc0_reset, AMBA_PINMUX(8, 1));
+CV75_PINMUX_GROUP(sdmmc0_hs_sel, AMBA_PINMUX(93, 1));
+CV75_PINMUX_GROUP(sdmmc0_1bit, AMBA_PINMUX(0, 1), AMBA_PINMUX(4, 1),
+		  AMBA_PINMUX(5, 1));
+CV75_PINMUX_GROUP(sdmmc0_4bit, AMBA_PINMUX(0, 1), AMBA_PINMUX(1, 1),
+		  AMBA_PINMUX(2, 1), AMBA_PINMUX(3, 1),
+		  AMBA_PINMUX(4, 1), AMBA_PINMUX(5, 1));
+CV75_PINMUX_GROUP(sdmmc1_cd, AMBA_PINMUX(15, 1));
+CV75_PINMUX_GROUP(sdmmc1_wp, AMBA_PINMUX(16, 1));
+CV75_PINMUX_GROUP(sdmmc1_reset, AMBA_PINMUX(17, 1));
+CV75_PINMUX_GROUP(sdmmc1_hs_sel, AMBA_PINMUX(94, 1));
+CV75_PINMUX_GROUP(sdmmc1_1bit, AMBA_PINMUX(9, 1), AMBA_PINMUX(13, 1),
+		  AMBA_PINMUX(14, 1));
+CV75_PINMUX_GROUP(sdmmc1_4bit, AMBA_PINMUX(9, 1), AMBA_PINMUX(10, 1),
+		  AMBA_PINMUX(11, 1), AMBA_PINMUX(12, 1),
+		  AMBA_PINMUX(13, 1), AMBA_PINMUX(14, 1));
+
+/* Ethernet */
+CV75_PINMUX_GROUP(enet_ext_osc_clk, AMBA_PINMUX(77, 1));
+CV75_PINMUX_GROUP(enet_2nd_ref_clk_a, AMBA_PINMUX(78, 1));
+CV75_PINMUX_GROUP(enet_2nd_ref_clk_b, AMBA_PINMUX(76, 2));
+CV75_PINMUX_GROUP(enet0_ptp_pps_o, AMBA_PINMUX(74, 1));
+CV75_PINMUX_GROUP(rgmii0, AMBA_PINMUX(62, 1), AMBA_PINMUX(63, 1),
+		  AMBA_PINMUX(64, 1), AMBA_PINMUX(65, 1),
+		  AMBA_PINMUX(66, 1), AMBA_PINMUX(67, 1),
+		  AMBA_PINMUX(68, 1), AMBA_PINMUX(69, 1),
+		  AMBA_PINMUX(70, 1), AMBA_PINMUX(71, 1),
+		  AMBA_PINMUX(72, 1), AMBA_PINMUX(73, 1),
+		  AMBA_PINMUX(75, 1), AMBA_PINMUX(76, 1));
+CV75_PINMUX_GROUP(rmii0, AMBA_PINMUX(62, 1), AMBA_PINMUX(63, 1),
+		  AMBA_PINMUX(64, 1), AMBA_PINMUX(67, 1),
+		  AMBA_PINMUX(68, 1), AMBA_PINMUX(71, 1),
+		  AMBA_PINMUX(72, 1), AMBA_PINMUX(73, 1),
+		  AMBA_PINMUX(75, 2));
+
+/* I2C */
+CV75_PINMUX_GROUP(i2c0_a, AMBA_PINMUX(67, 2), AMBA_PINMUX(68, 2));
+CV75_PINMUX_GROUP(i2c0_b, AMBA_PINMUX(86, 1), AMBA_PINMUX(87, 1));
+CV75_PINMUX_GROUP(i2c1_a, AMBA_PINMUX(22, 2), AMBA_PINMUX(23, 2));
+CV75_PINMUX_GROUP(i2c1_b, AMBA_PINMUX(69, 2), AMBA_PINMUX(70, 2));
+CV75_PINMUX_GROUP(i2c2, AMBA_PINMUX(88, 1), AMBA_PINMUX(89, 1));
+CV75_PINMUX_GROUP(i2c3_a, AMBA_PINMUX(24, 2), AMBA_PINMUX(25, 2));
+CV75_PINMUX_GROUP(i2c3_b, AMBA_PINMUX(48, 3), AMBA_PINMUX(49, 3));
+CV75_PINMUX_GROUP(i2c3_c, AMBA_PINMUX(58, 3), AMBA_PINMUX(59, 3));
+CV75_PINMUX_GROUP(i2cs_a, AMBA_PINMUX(19, 2), AMBA_PINMUX(21, 2));
+CV75_PINMUX_GROUP(i2cs_b, AMBA_PINMUX(60, 3), AMBA_PINMUX(61, 3));
+CV75_PINMUX_GROUP(i2cs_c, AMBA_PINMUX(71, 2), AMBA_PINMUX(72, 2));
+CV75_PINMUX_GROUP(i2cs_d, AMBA_PINMUX(86, 2), AMBA_PINMUX(87, 2));
+
+/* CAN, IR, WDT */
+CV75_PINMUX_GROUP(can0, AMBA_PINMUX(50, 1), AMBA_PINMUX(51, 1));
+CV75_PINMUX_GROUP(can1, AMBA_PINMUX(52, 1), AMBA_PINMUX(53, 1));
+CV75_PINMUX_GROUP(ir, AMBA_PINMUX(18, 1));
+CV75_PINMUX_GROUP(wdt_a, AMBA_PINMUX(20, 2));
+CV75_PINMUX_GROUP(wdt_b, AMBA_PINMUX(27, 3));
+CV75_PINMUX_GROUP(wdt_c, AMBA_PINMUX(39, 2));
+CV75_PINMUX_GROUP(wdt_d, AMBA_PINMUX(83, 3));
+CV75_PINMUX_GROUP(wdt_e, AMBA_PINMUX(85, 4));
+CV75_PINMUX_GROUP(wdt_f, AMBA_PINMUX(90, 1));
+
+/* I2S */
+CV75_PINMUX_GROUP(i2s0, AMBA_PINMUX(54, 1), AMBA_PINMUX(55, 1),
+		  AMBA_PINMUX(56, 1), AMBA_PINMUX(57, 1));
+CV75_PINMUX_GROUP(i2s1, AMBA_PINMUX(58, 1), AMBA_PINMUX(59, 1),
+		  AMBA_PINMUX(60, 1), AMBA_PINMUX(61, 1));
+CV75_PINMUX_GROUP(clk_au, AMBA_PINMUX(96, 1));
+CV75_PINMUX_GROUP(dmic0, AMBA_PINMUX(54, 2), AMBA_PINMUX(55, 2));
+
+/* PWM */
+CV75_PINMUX_GROUP(pwm0, AMBA_PINMUX(40, 1));
+CV75_PINMUX_GROUP(pwm1, AMBA_PINMUX(41, 1));
+CV75_PINMUX_GROUP(pwm2, AMBA_PINMUX(42, 1));
+CV75_PINMUX_GROUP(pwm3, AMBA_PINMUX(43, 1));
+CV75_PINMUX_GROUP(pwm4_a, AMBA_PINMUX(19, 3));
+CV75_PINMUX_GROUP(pwm4_b, AMBA_PINMUX(32, 4));
+CV75_PINMUX_GROUP(pwm5_a, AMBA_PINMUX(20, 3));
+CV75_PINMUX_GROUP(pwm5_b, AMBA_PINMUX(33, 4));
+CV75_PINMUX_GROUP(pwm6_a, AMBA_PINMUX(21, 3));
+CV75_PINMUX_GROUP(pwm6_b, AMBA_PINMUX(34, 4));
+CV75_PINMUX_GROUP(pwm7_a, AMBA_PINMUX(22, 3));
+CV75_PINMUX_GROUP(pwm7_b, AMBA_PINMUX(35, 4));
+CV75_PINMUX_GROUP(pwm8_a, AMBA_PINMUX(23, 3));
+CV75_PINMUX_GROUP(pwm8_b, AMBA_PINMUX(36, 4));
+CV75_PINMUX_GROUP(pwm9_a, AMBA_PINMUX(24, 3));
+CV75_PINMUX_GROUP(pwm9_b, AMBA_PINMUX(37, 4));
+CV75_PINMUX_GROUP(pwm10_a, AMBA_PINMUX(25, 3));
+CV75_PINMUX_GROUP(pwm10_b, AMBA_PINMUX(38, 4));
+CV75_PINMUX_GROUP(pwm11_a, AMBA_PINMUX(26, 3));
+CV75_PINMUX_GROUP(pwm11_b, AMBA_PINMUX(39, 4));
+
+/* SPI */
+CV75_PINMUX_GROUP(spi0, AMBA_PINMUX(19, 1), AMBA_PINMUX(20, 1),
+		  AMBA_PINMUX(21, 1));
+CV75_PINMUX_GROUP(spi1, AMBA_PINMUX(24, 1), AMBA_PINMUX(25, 1),
+		  AMBA_PINMUX(26, 1));
+CV75_PINMUX_GROUP(spi2, AMBA_PINMUX(28, 1), AMBA_PINMUX(29, 1),
+		  AMBA_PINMUX(30, 1));
+CV75_PINMUX_GROUP(spi3_a, AMBA_PINMUX(32, 5), AMBA_PINMUX(33, 5),
+		  AMBA_PINMUX(34, 5));
+CV75_PINMUX_GROUP(spi3_b, AMBA_PINMUX(40, 2), AMBA_PINMUX(41, 2),
+		  AMBA_PINMUX(43, 2));
+CV75_PINMUX_GROUP(spi3_c, AMBA_PINMUX(58, 2), AMBA_PINMUX(59, 2),
+		  AMBA_PINMUX(60, 2));
+CV75_PINMUX_GROUP(spi_slave_a, AMBA_PINMUX(24, 4), AMBA_PINMUX(25, 4),
+		  AMBA_PINMUX(26, 4), AMBA_PINMUX(27, 4));
+CV75_PINMUX_GROUP(spi_slave_b, AMBA_PINMUX(28, 2), AMBA_PINMUX(29, 2),
+		  AMBA_PINMUX(30, 2), AMBA_PINMUX(31, 2));
+CV75_PINMUX_GROUP(spi_slave_c, AMBA_PINMUX(36, 5), AMBA_PINMUX(37, 5),
+		  AMBA_PINMUX(38, 5), AMBA_PINMUX(39, 5));
+CV75_PINMUX_GROUP(spi_slave_d, AMBA_PINMUX(50, 3), AMBA_PINMUX(51, 3),
+		  AMBA_PINMUX(52, 3), AMBA_PINMUX(53, 3));
+CV75_PINMUX_GROUP(spi_slave_e, AMBA_PINMUX(81, 4), AMBA_PINMUX(82, 4),
+		  AMBA_PINMUX(83, 4), AMBA_PINMUX(84, 4));
+
+/* VIN master sync */
+CV75_PINMUX_GROUP(vin_master_sync_a, AMBA_PINMUX(91, 1),
+		  AMBA_PINMUX(92, 1));
+CV75_PINMUX_GROUP(vin_master_sync_b, AMBA_PINMUX(91, 2),
+		  AMBA_PINMUX(92, 2));
+CV75_PINMUX_GROUP(vin_master_sync_c, AMBA_PINMUX(40, 3),
+		  AMBA_PINMUX(41, 3));
+CV75_PINMUX_GROUP(vin_master_sync_d, AMBA_PINMUX(46, 3),
+		  AMBA_PINMUX(47, 3));
+CV75_PINMUX_GROUP(vin_master_sync_e, AMBA_PINMUX(79, 4),
+		  AMBA_PINMUX(80, 4));
+CV75_PINMUX_GROUP(vsync0, AMBA_PINMUX(32, 1));
+CV75_PINMUX_GROUP(vsync1, AMBA_PINMUX(33, 1));
+CV75_PINMUX_GROUP(vsync2, AMBA_PINMUX(34, 1));
+CV75_PINMUX_GROUP(vsync3, AMBA_PINMUX(35, 1));
+CV75_PINMUX_GROUP(hsync0, AMBA_PINMUX(36, 1));
+CV75_PINMUX_GROUP(hsync1, AMBA_PINMUX(37, 1));
+
+static const struct ambpin_group_desc cv75_pin_groups[] = {
+	CV75_GROUP(uart0),
+	CV75_GROUP(uart1), CV75_GROUP(uart1_flow),
+	CV75_GROUP(uart2_a), CV75_GROUP(uart2_b), CV75_GROUP(uart2_c),
+	CV75_GROUP(uart2_flow_a), CV75_GROUP(uart2_flow_b),
+	CV75_GROUP(uart3_a), CV75_GROUP(uart3_b),
+	CV75_GROUP(uart3_flow_a), CV75_GROUP(uart3_flow_b),
+	CV75_GROUP(uart4_a), CV75_GROUP(uart4_b),
+	CV75_GROUP(uart4_flow_a), CV75_GROUP(uart4_flow_b),
+	CV75_GROUP(snand), CV75_GROUP(spinor),
+	CV75_GROUP(sdmmc0_cd), CV75_GROUP(sdmmc0_wp),
+	CV75_GROUP(sdmmc0_reset), CV75_GROUP(sdmmc0_hs_sel),
+	CV75_GROUP(sdmmc0_1bit), CV75_GROUP(sdmmc0_4bit),
+	CV75_GROUP(sdmmc1_cd), CV75_GROUP(sdmmc1_wp),
+	CV75_GROUP(sdmmc1_reset), CV75_GROUP(sdmmc1_hs_sel),
+	CV75_GROUP(sdmmc1_1bit), CV75_GROUP(sdmmc1_4bit),
+	CV75_GROUP(enet_ext_osc_clk), CV75_GROUP(enet_2nd_ref_clk_a),
+	CV75_GROUP(enet_2nd_ref_clk_b), CV75_GROUP(enet0_ptp_pps_o),
+	CV75_GROUP(rgmii0), CV75_GROUP(rmii0),
+	CV75_GROUP(i2c0_a), CV75_GROUP(i2c0_b),
+	CV75_GROUP(i2c1_a), CV75_GROUP(i2c1_b), CV75_GROUP(i2c2),
+	CV75_GROUP(i2c3_a), CV75_GROUP(i2c3_b), CV75_GROUP(i2c3_c),
+	CV75_GROUP(i2cs_a), CV75_GROUP(i2cs_b),
+	CV75_GROUP(i2cs_c), CV75_GROUP(i2cs_d),
+	CV75_GROUP(can0), CV75_GROUP(can1), CV75_GROUP(ir),
+	CV75_GROUP(wdt_a), CV75_GROUP(wdt_b), CV75_GROUP(wdt_c),
+	CV75_GROUP(wdt_d), CV75_GROUP(wdt_e), CV75_GROUP(wdt_f),
+	CV75_GROUP(i2s0), CV75_GROUP(i2s1),
+	CV75_GROUP(clk_au), CV75_GROUP(dmic0),
+	CV75_GROUP(pwm0), CV75_GROUP(pwm1),
+	CV75_GROUP(pwm2), CV75_GROUP(pwm3),
+	CV75_GROUP(pwm4_a), CV75_GROUP(pwm4_b),
+	CV75_GROUP(pwm5_a), CV75_GROUP(pwm5_b),
+	CV75_GROUP(pwm6_a), CV75_GROUP(pwm6_b),
+	CV75_GROUP(pwm7_a), CV75_GROUP(pwm7_b),
+	CV75_GROUP(pwm8_a), CV75_GROUP(pwm8_b),
+	CV75_GROUP(pwm9_a), CV75_GROUP(pwm9_b),
+	CV75_GROUP(pwm10_a), CV75_GROUP(pwm10_b),
+	CV75_GROUP(pwm11_a), CV75_GROUP(pwm11_b),
+	CV75_GROUP(spi0), CV75_GROUP(spi1), CV75_GROUP(spi2),
+	CV75_GROUP(spi3_a), CV75_GROUP(spi3_b), CV75_GROUP(spi3_c),
+	CV75_GROUP(spi_slave_a), CV75_GROUP(spi_slave_b),
+	CV75_GROUP(spi_slave_c), CV75_GROUP(spi_slave_d),
+	CV75_GROUP(spi_slave_e),
+	CV75_GROUP(vin_master_sync_a), CV75_GROUP(vin_master_sync_b),
+	CV75_GROUP(vin_master_sync_c), CV75_GROUP(vin_master_sync_d),
+	CV75_GROUP(vin_master_sync_e),
+	CV75_GROUP(vsync0), CV75_GROUP(vsync1),
+	CV75_GROUP(vsync2), CV75_GROUP(vsync3),
+	CV75_GROUP(hsync0), CV75_GROUP(hsync1),
+};
+
+static const char * const cv75_uart0_groups[] = {
+	"uart0",
+};
+
+static const char * const cv75_uart1_groups[] = {
+	"uart1",
+	"uart1_flow",
+};
+
+static const char * const cv75_uart2_groups[] = {
+	"uart2_a",
+	"uart2_b",
+	"uart2_c",
+	"uart2_flow_a",
+	"uart2_flow_b",
+};
+
+static const char * const cv75_uart3_groups[] = {
+	"uart3_a",
+	"uart3_b",
+	"uart3_flow_a",
+	"uart3_flow_b",
+};
+
+static const char * const cv75_uart4_groups[] = {
+	"uart4_a",
+	"uart4_b",
+	"uart4_flow_a",
+	"uart4_flow_b",
+};
+
+static const char * const cv75_snand_groups[] = {
+	"snand",
+};
+
+static const char * const cv75_spinor_groups[] = {
+	"spinor",
+};
+
+static const char * const cv75_sdmmc0_groups[] = {
+	"sdmmc0_cd",
+	"sdmmc0_wp",
+	"sdmmc0_reset",
+	"sdmmc0_hs_sel",
+	"sdmmc0_1bit",
+	"sdmmc0_4bit",
+};
+
+static const char * const cv75_sdmmc1_groups[] = {
+	"sdmmc1_cd",
+	"sdmmc1_wp",
+	"sdmmc1_reset",
+	"sdmmc1_hs_sel",
+	"sdmmc1_1bit",
+	"sdmmc1_4bit",
+};
+
+static const char * const cv75_enet0_groups[] = {
+	"enet_ext_osc_clk",
+	"enet_2nd_ref_clk_a",
+	"enet_2nd_ref_clk_b",
+	"enet0_ptp_pps_o",
+	"rgmii0",
+	"rmii0",
+};
+
+static const char * const cv75_i2c0_groups[] = {
+	"i2c0_a",
+	"i2c0_b",
+};
+
+static const char * const cv75_i2c1_groups[] = {
+	"i2c1_a",
+	"i2c1_b",
+};
+
+static const char * const cv75_i2c2_groups[] = {
+	"i2c2",
+};
+
+static const char * const cv75_i2c3_groups[] = {
+	"i2c3_a",
+	"i2c3_b",
+	"i2c3_c",
+};
+
+static const char * const cv75_i2cs_groups[] = {
+	"i2cs_a",
+	"i2cs_b",
+	"i2cs_c",
+	"i2cs_d",
+};
+
+static const char * const cv75_can0_groups[] = {
+	"can0",
+};
+
+static const char * const cv75_can1_groups[] = {
+	"can1",
+};
+
+static const char * const cv75_ir_groups[] = {
+	"ir",
+};
+
+static const char * const cv75_wdt_groups[] = {
+	"wdt_a",
+	"wdt_b",
+	"wdt_c",
+	"wdt_d",
+	"wdt_e",
+	"wdt_f",
+};
+
+static const char * const cv75_i2s0_groups[] = {
+	"i2s0",
+};
+
+static const char * const cv75_i2s1_groups[] = {
+	"i2s1",
+};
+
+static const char * const cv75_clk_au_groups[] = {
+	"clk_au",
+};
+
+static const char * const cv75_dmic0_groups[] = {
+	"dmic0",
+};
+
+static const char * const cv75_pwm0_groups[] = {
+	"pwm0",
+};
+
+static const char * const cv75_pwm1_groups[] = {
+	"pwm1",
+};
+
+static const char * const cv75_pwm2_groups[] = {
+	"pwm2",
+};
+
+static const char * const cv75_pwm3_groups[] = {
+	"pwm3",
+};
+
+static const char * const cv75_pwm4_groups[] = {
+	"pwm4_a",
+	"pwm4_b",
+};
+
+static const char * const cv75_pwm5_groups[] = {
+	"pwm5_a",
+	"pwm5_b",
+};
+
+static const char * const cv75_pwm6_groups[] = {
+	"pwm6_a",
+	"pwm6_b",
+};
+
+static const char * const cv75_pwm7_groups[] = {
+	"pwm7_a",
+	"pwm7_b",
+};
+
+static const char * const cv75_pwm8_groups[] = {
+	"pwm8_a",
+	"pwm8_b",
+};
+
+static const char * const cv75_pwm9_groups[] = {
+	"pwm9_a",
+	"pwm9_b",
+};
+
+static const char * const cv75_pwm10_groups[] = {
+	"pwm10_a",
+	"pwm10_b",
+};
+
+static const char * const cv75_pwm11_groups[] = {
+	"pwm11_a",
+	"pwm11_b",
+};
+
+static const char * const cv75_spi0_groups[] = {
+	"spi0",
+};
+
+static const char * const cv75_spi1_groups[] = {
+	"spi1",
+};
+
+static const char * const cv75_spi2_groups[] = {
+	"spi2",
+};
+
+static const char * const cv75_spi3_groups[] = {
+	"spi3_a",
+	"spi3_b",
+	"spi3_c",
+};
+
+static const char * const cv75_spi_slave_groups[] = {
+	"spi_slave_a",
+	"spi_slave_b",
+	"spi_slave_c",
+	"spi_slave_d",
+	"spi_slave_e",
+};
+
+static const char * const cv75_vin_master_sync_groups[] = {
+	"vin_master_sync_a",
+	"vin_master_sync_b",
+	"vin_master_sync_c",
+	"vin_master_sync_d",
+	"vin_master_sync_e",
+};
+
+static const char * const cv75_vsync0_groups[] = {
+	"vsync0",
+};
+
+static const char * const cv75_vsync1_groups[] = {
+	"vsync1",
+};
+
+static const char * const cv75_vsync2_groups[] = {
+	"vsync2",
+};
+
+static const char * const cv75_vsync3_groups[] = {
+	"vsync3",
+};
+
+static const char * const cv75_hsync0_groups[] = {
+	"hsync0",
+};
+
+static const char * const cv75_hsync1_groups[] = {
+	"hsync1",
+};
+
+static const struct ambpin_function cv75_pin_functions[] = {
+	CV75_FUNCTION(uart0),
+	CV75_FUNCTION(uart1),
+	CV75_FUNCTION(uart2),
+	CV75_FUNCTION(uart3),
+	CV75_FUNCTION(uart4),
+	CV75_FUNCTION(snand),
+	CV75_FUNCTION(spinor),
+	CV75_FUNCTION(sdmmc0),
+	CV75_FUNCTION(sdmmc1),
+	CV75_FUNCTION(enet0),
+	CV75_FUNCTION(i2c0),
+	CV75_FUNCTION(i2c1),
+	CV75_FUNCTION(i2c2),
+	CV75_FUNCTION(i2c3),
+	CV75_FUNCTION(i2cs),
+	CV75_FUNCTION(can0),
+	CV75_FUNCTION(can1),
+	CV75_FUNCTION(ir),
+	CV75_FUNCTION(wdt),
+	CV75_FUNCTION(i2s0),
+	CV75_FUNCTION(i2s1),
+	CV75_FUNCTION(clk_au),
+	CV75_FUNCTION(dmic0),
+	CV75_FUNCTION(pwm0),
+	CV75_FUNCTION(pwm1),
+	CV75_FUNCTION(pwm2),
+	CV75_FUNCTION(pwm3),
+	CV75_FUNCTION(pwm4),
+	CV75_FUNCTION(pwm5),
+	CV75_FUNCTION(pwm6),
+	CV75_FUNCTION(pwm7),
+	CV75_FUNCTION(pwm8),
+	CV75_FUNCTION(pwm9),
+	CV75_FUNCTION(pwm10),
+	CV75_FUNCTION(pwm11),
+	CV75_FUNCTION(spi0),
+	CV75_FUNCTION(spi1),
+	CV75_FUNCTION(spi2),
+	CV75_FUNCTION(spi3),
+	CV75_FUNCTION(spi_slave),
+	CV75_FUNCTION(vin_master_sync),
+	CV75_FUNCTION(vsync0),
+	CV75_FUNCTION(vsync1),
+	CV75_FUNCTION(vsync2),
+	CV75_FUNCTION(vsync3),
+	CV75_FUNCTION(hsync0),
+	CV75_FUNCTION(hsync1),
+};
+
+const struct amb_pinctrl_data ambarella_cv75_pinctrl_data = {
+	.ds0 = {
+		0x314, 0x320, 0x32c,
+	},
+	.ds1 = {
+		0x318, 0x324, 0x330,
+	},
+	.ds2 = {
+		0x31c, 0x328, 0x334,
+	},
+	.pull_en = {
+		0x60, 0x64, 0x68,
+	},
+	.pull_dir = {
+		0x7c, 0x80, 0x84,
+	},
+	.have_ds2 = true,
+	.clk_au_dedicated_pin = 96,
+	.nr_banks = 3,
+	.npins = 97,
+	.groups = cv75_pin_groups,
+	.nr_groups = ARRAY_SIZE(cv75_pin_groups),
+	.functions = cv75_pin_functions,
+	.nr_functions = ARRAY_SIZE(cv75_pin_functions),
+};
diff --git a/drivers/pinctrl/pinctrl-ambarella.c b/drivers/pinctrl/pinctrl-ambarella.c
new file mode 100644
index 000000000000..a4a068271e8f
--- /dev/null
+++ b/drivers/pinctrl/pinctrl-ambarella.c
@@ -0,0 +1,761 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Pinctrl driver for Ambarella SoCs
+ *
+ * History:
+ *	2013/12/18 - [Cao Rongrong] created file
+ *
+ * Copyright (C) 2012-2026, Ambarella, Inc.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/bitmap.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/slab.h>
+#include <linux/regmap.h>
+#include <linux/mfd/syscon.h>
+#include <linux/pinctrl/pinctrl.h>
+#include <linux/pinctrl/pinmux.h>
+#include <linux/pinctrl/pinconf.h>
+#include <linux/pinctrl/pinconf-generic.h>
+#include <linux/seq_file.h>
+
+#include "pinconf.h"
+#include "pinctrl-ambarella.h"
+
+/* ==========================================================================*/
+
+#define IOMUX_OFFSET(bank, n)		(((bank) * 0xc) + ((n) * 4))
+#define IOMUX_CTRL_SET_OFFSET		0xf0
+
+/* ==========================================================================*/
+
+#define AMBA_MAX_PINS			(AMBA_MAX_BANKS * 32)
+
+#define PINID_TO_BANK(p)		((p) >> 5)
+#define PINID_TO_OFFSET(p)		((p) & 0x1f)
+
+struct ambpin_group {
+	const char		*name;
+	const u32		*pinmux;
+	unsigned int		*pins;
+	unsigned int		num_pins;
+};
+
+struct amb_pinctrl_soc_data {
+	struct device			*dev;
+	const struct amb_pinctrl_data	*data;
+	void __iomem			*iomux_base;
+	struct regmap			*ds_regmap;
+	struct regmap			*pull_regmap;
+	unsigned int			npins;
+	unsigned long			used[BITS_TO_LONGS(AMBA_MAX_PINS)];
+	raw_spinlock_t lock;
+
+	struct pinctrl_dev		*pctl;
+
+	const struct ambpin_function	*functions;
+	unsigned int			nr_functions;
+	struct ambpin_group		*groups;
+	unsigned int			nr_groups;
+};
+
+/* check if the selector is a valid pin group selector */
+static int amb_get_group_count(struct pinctrl_dev *pctldev)
+{
+	struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+
+	return soc->nr_groups;
+}
+
+/* return the name of the group selected by the group selector */
+static const char *amb_get_group_name(struct pinctrl_dev *pctldev,
+				      unsigned int selector)
+{
+	struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+
+	return soc->groups[selector].name;
+}
+
+/* return the pin numbers associated with the specified group */
+static int amb_get_group_pins(struct pinctrl_dev *pctldev,
+			      unsigned int selector, const unsigned int **pins,
+			      unsigned int *num_pins)
+{
+	struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+
+	*pins = soc->groups[selector].pins;
+	*num_pins = soc->groups[selector].num_pins;
+
+	return 0;
+}
+
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+static void amb_pin_dbg_show(struct pinctrl_dev *pctldev,
+			     struct seq_file *s, unsigned int pin)
+{
+	seq_printf(s, " %s", pinctrl_dev_get_devname(pctldev));
+}
+#endif
+
+/* list of pinctrl callbacks for the pinctrl core */
+static const struct pinctrl_ops amb_pctrl_ops = {
+	.get_groups_count	= amb_get_group_count,
+	.get_group_name		= amb_get_group_name,
+	.get_group_pins		= amb_get_group_pins,
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+	.pin_dbg_show		= amb_pin_dbg_show,
+#endif
+	.dt_node_to_map		= pinconf_generic_dt_node_to_map_all,
+	.dt_free_map		= pinconf_generic_dt_free_map,
+};
+
+/* check if the selector is a valid pin function selector */
+static int amb_pinmux_request(struct pinctrl_dev *pctldev, unsigned int pin)
+{
+	struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+
+	if (test_and_set_bit(pin, soc->used))
+		return -EBUSY;
+
+	return 0;
+}
+
+/* check if the selector is a valid pin function selector */
+static int amb_pinmux_free(struct pinctrl_dev *pctldev, unsigned int pin)
+{
+	struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+
+	clear_bit(pin, soc->used);
+
+	return 0;
+}
+
+/* check if the selector is a valid pin function selector */
+static int amb_pinmux_get_fcount(struct pinctrl_dev *pctldev)
+{
+	struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+
+	return soc->nr_functions;
+}
+
+/* return the name of the pin function specified */
+static const char *amb_pinmux_get_fname(struct pinctrl_dev *pctldev,
+					unsigned int selector)
+{
+	struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+
+	return soc->functions[selector].name;
+}
+
+/* return the groups associated for the specified function selector */
+static int amb_pinmux_get_groups(struct pinctrl_dev *pctldev,
+				 unsigned int selector,
+				 const char * const **groups,
+				 unsigned int * const num_groups)
+{
+	struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+
+	*groups = soc->functions[selector].groups;
+	*num_groups = soc->functions[selector].num_groups;
+
+	return 0;
+}
+
+static bool amb_iomux_accessible(const struct amb_pinctrl_soc_data *soc)
+{
+	return soc->data->hsm_domain_id == 0;
+}
+
+static void amb_iomux_commit(struct amb_pinctrl_soc_data *soc)
+{
+	if (!amb_iomux_accessible(soc))
+		return;
+
+	writel_relaxed(0x1, soc->iomux_base + IOMUX_CTRL_SET_OFFSET);
+	writel_relaxed(0x0, soc->iomux_base + IOMUX_CTRL_SET_OFFSET);
+}
+
+static void amb_pinmux_set_altfunc(struct amb_pinctrl_soc_data *soc,
+				   u32 bank, u32 offset, u32 altfunc)
+{
+	u32 i, data;
+
+	if (!amb_iomux_accessible(soc))
+		return;
+
+	for (i = 0; i < 3; i++) {
+		data = readl_relaxed(soc->iomux_base + IOMUX_OFFSET(bank, i));
+		data &= ~BIT(offset);
+		data |= ((altfunc >> i) & 1U) << offset;
+		writel_relaxed(data, soc->iomux_base + IOMUX_OFFSET(bank, i));
+	}
+}
+
+/* enable a specified pinmux by writing to registers */
+static int amb_pinmux_set_mux(struct pinctrl_dev *pctldev,
+			      unsigned int selector, unsigned int group)
+{
+	struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+	const struct ambpin_group *grp;
+	u32 i, pin, alt, bank, offset;
+	unsigned long flags;
+
+	grp = &soc->groups[group];
+
+	raw_spin_lock_irqsave(&soc->lock, flags);
+	for (i = 0; i < grp->num_pins; i++) {
+		pin = AMBA_PINMUX_TO_PIN(grp->pinmux[i]);
+		alt = AMBA_PINMUX_TO_ALT(grp->pinmux[i]);
+		bank = PINID_TO_BANK(pin);
+		offset = PINID_TO_OFFSET(pin);
+		amb_pinmux_set_altfunc(soc, bank, offset, alt);
+	}
+	/* One commit after the whole group is programmed. */
+	amb_iomux_commit(soc);
+	raw_spin_unlock_irqrestore(&soc->lock, flags);
+
+	return 0;
+}
+
+static int amb_pinmux_gpio_request_enable(struct pinctrl_dev *pctldev,
+					  struct pinctrl_gpio_range *range,
+					  unsigned int pin)
+{
+	struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+	u32 bank, offset;
+	unsigned long flags;
+
+	if (!range || !range->gc) {
+		dev_err(soc->dev, "invalid range: %p\n", range);
+		return -EINVAL;
+	}
+
+	if (test_and_set_bit(pin, soc->used))
+		return -EBUSY;
+
+	bank = PINID_TO_BANK(pin);
+	offset = PINID_TO_OFFSET(pin);
+
+	raw_spin_lock_irqsave(&soc->lock, flags);
+	amb_pinmux_set_altfunc(soc, bank, offset, 0);
+	amb_iomux_commit(soc);
+	raw_spin_unlock_irqrestore(&soc->lock, flags);
+
+	return 0;
+}
+
+static void amb_pinmux_gpio_disable_free(struct pinctrl_dev *pctldev,
+					 struct pinctrl_gpio_range *range,
+					 unsigned int pin)
+{
+	struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+
+	dev_dbg(soc->dev, "disable pin %u as GPIO\n", pin);
+	/* Set the pin to some default state, GPIO is usually default */
+
+	clear_bit(pin, soc->used);
+}
+
+/* list of pinmux callbacks for the pinmux vertical in pinctrl core */
+static const struct pinmux_ops amb_pinmux_ops = {
+	.request		= amb_pinmux_request,
+	.free			= amb_pinmux_free,
+	.get_functions_count	= amb_pinmux_get_fcount,
+	.get_function_name	= amb_pinmux_get_fname,
+	.get_function_groups	= amb_pinmux_get_groups,
+	.set_mux                = amb_pinmux_set_mux,
+	.gpio_request_enable	= amb_pinmux_gpio_request_enable,
+	.gpio_disable_free	= amb_pinmux_gpio_disable_free,
+};
+
+static int amb_drive_strength_to_reg(struct amb_pinctrl_soc_data *soc,
+				     u32 strength)
+{
+	if (soc->data->have_ds2) {
+		switch (strength) {
+		case 3:
+			return 0;
+		case 4:
+		case 5:
+			return 1;
+		case 6:
+			return 2;
+		case 7:
+		case 8:
+			return 3;
+		case 9:
+			return 4;
+		case 12:
+			return 5;
+		default:
+			return -EINVAL;
+		}
+	}
+
+	switch (strength) {
+	case 2:
+		return 0;
+	case 4:
+		return 1;
+	case 8:
+		return 2;
+	case 12:
+		return 3;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int amb_reg_to_drive_strength(struct amb_pinctrl_soc_data *soc, u32 ds)
+{
+	static const int ds2_ma[] = { 3, 4, 6, 8, 9, 12 };
+	static const int ds_ma[] = { 2, 4, 8, 12 };
+
+	if (soc->data->have_ds2) {
+		if (ds >= ARRAY_SIZE(ds2_ma))
+			return -EINVAL;
+
+		return ds2_ma[ds];
+	}
+
+	if (ds >= ARRAY_SIZE(ds_ma))
+		return -EINVAL;
+
+	return ds_ma[ds];
+}
+
+/* set the pin config settings for a specified pin */
+static int amb_pinconf_set(struct pinctrl_dev *pctldev, unsigned int pin,
+			   unsigned long *configs, unsigned int num_configs)
+{
+	struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+	u32 i, bank, offset;
+	unsigned long config;
+	enum pin_config_param param;
+	u32 arg;
+	int ds;
+
+	bank = PINID_TO_BANK(pin);
+	if (bank >= soc->data->nr_banks)
+		return -EINVAL;
+
+	offset = PINID_TO_OFFSET(pin);
+
+	for (i = 0; i < num_configs; i++) {
+		config = configs[i];
+		param = pinconf_to_config_param(config);
+		arg = pinconf_to_config_argument(config);
+
+		switch (param) {
+		case PIN_CONFIG_BIAS_DISABLE:
+			regmap_update_bits(soc->pull_regmap,
+					   soc->data->pull_en[bank], BIT(offset), 0);
+			break;
+		case PIN_CONFIG_BIAS_PULL_DOWN:
+		case PIN_CONFIG_BIAS_PULL_UP:
+			regmap_update_bits(soc->pull_regmap, soc->data->pull_dir[bank],
+					   BIT(offset),
+					   (param == PIN_CONFIG_BIAS_PULL_UP) ?
+					   BIT(offset) : 0);
+			regmap_update_bits(soc->pull_regmap, soc->data->pull_en[bank],
+					   BIT(offset), BIT(offset));
+			break;
+		case PIN_CONFIG_DRIVE_STRENGTH:
+			ds = amb_drive_strength_to_reg(soc, arg);
+			if (ds < 0)
+				return ds;
+			if (soc->data->have_ds2) {
+				regmap_update_bits(soc->ds_regmap,
+						   soc->data->ds0[bank], BIT(offset),
+						   (ds & BIT(0)) ? BIT(offset) : 0);
+				regmap_update_bits(soc->ds_regmap,
+						   soc->data->ds1[bank], BIT(offset),
+						   (ds & BIT(1)) ? BIT(offset) : 0);
+				regmap_update_bits(soc->ds_regmap,
+						   soc->data->ds2[bank], BIT(offset),
+						   (ds & BIT(2)) ? BIT(offset) : 0);
+			} else {
+				regmap_update_bits(soc->ds_regmap,
+						   soc->data->ds0[bank], BIT(offset),
+						   (ds & BIT(1)) ? BIT(offset) : 0);
+				regmap_update_bits(soc->ds_regmap,
+						   soc->data->ds1[bank], BIT(offset),
+						   (ds & BIT(0)) ? BIT(offset) : 0);
+			}
+			break;
+		default:
+			return -EOPNOTSUPP;
+		}
+	}
+
+	return 0;
+}
+
+static int amb_pinconf_group_set(struct pinctrl_dev *pctldev,
+				 unsigned int selector,
+				 unsigned long *configs,
+				 unsigned int num_configs)
+{
+	struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+	const struct ambpin_group *grp = &soc->groups[selector];
+	int ret;
+	u32 i;
+
+	for (i = 0; i < grp->num_pins; i++) {
+		ret = amb_pinconf_set(pctldev, grp->pins[i], configs,
+				      num_configs);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+/* get the pin config settings for a specified pin */
+static int amb_pinconf_get(struct pinctrl_dev *pctldev,
+			   unsigned int pin, unsigned long *config)
+{
+	struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+	enum pin_config_param param = pinconf_to_config_param(*config);
+	u32 bank, offset, pull_en, pull_dir, ds0, ds1, ds2, ds;
+	int ret, strength;
+
+	bank = PINID_TO_BANK(pin);
+	if (bank >= soc->data->nr_banks)
+		return -EINVAL;
+
+	offset = PINID_TO_OFFSET(pin);
+
+	switch (param) {
+	case PIN_CONFIG_BIAS_DISABLE:
+	case PIN_CONFIG_BIAS_PULL_DOWN:
+	case PIN_CONFIG_BIAS_PULL_UP:
+		ret = regmap_read(soc->pull_regmap, soc->data->pull_en[bank],
+				  &pull_en);
+		if (ret)
+			return ret;
+
+		ret = regmap_read(soc->pull_regmap, soc->data->pull_dir[bank],
+				  &pull_dir);
+		if (ret)
+			return ret;
+
+		pull_en = (pull_en >> offset) & 1;
+		pull_dir = (pull_dir >> offset) & 1;
+
+		if (param == PIN_CONFIG_BIAS_DISABLE) {
+			if (pull_en)
+				return -EINVAL;
+			*config = pinconf_to_config_packed(param, 0);
+			return 0;
+		}
+
+		if (!pull_en)
+			return -EINVAL;
+		if (param == PIN_CONFIG_BIAS_PULL_UP && !pull_dir)
+			return -EINVAL;
+		if (param == PIN_CONFIG_BIAS_PULL_DOWN && pull_dir)
+			return -EINVAL;
+
+		*config = pinconf_to_config_packed(param, 1);
+		return 0;
+
+	case PIN_CONFIG_DRIVE_STRENGTH:
+		ret = regmap_read(soc->ds_regmap, soc->data->ds0[bank], &ds0);
+		if (ret)
+			return ret;
+
+		ret = regmap_read(soc->ds_regmap, soc->data->ds1[bank], &ds1);
+		if (ret)
+			return ret;
+
+		ds0 = (ds0 >> offset) & 1;
+		ds1 = (ds1 >> offset) & 1;
+		if (soc->data->have_ds2) {
+			ret = regmap_read(soc->ds_regmap, soc->data->ds2[bank], &ds2);
+			if (ret)
+				return ret;
+
+			ds2 = (ds2 >> offset) & 1;
+			ds = (ds2 << 2) | (ds1 << 1) | ds0;
+		} else {
+			ds = (ds0 << 1) | ds1;
+		}
+
+		strength = amb_reg_to_drive_strength(soc, ds);
+		if (strength < 0)
+			return strength;
+
+		*config = pinconf_to_config_packed(param, strength);
+		return 0;
+
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+static void amb_pinconf_dbg_show(struct pinctrl_dev *pctldev,
+				 struct seq_file *s, unsigned int pin)
+{
+	struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+	u32 pull_en, pull_dir, ds0, ds1, ds2 = 0, ds;
+	u32 bank, offset;
+	int strength, ret;
+
+	bank = PINID_TO_BANK(pin);
+	if (bank >= soc->data->nr_banks) {
+		seq_puts(s, " (no pinconf)");
+		return;
+	}
+
+	offset = PINID_TO_OFFSET(pin);
+
+	ret = regmap_read(soc->pull_regmap, soc->data->pull_en[bank],
+			  &pull_en);
+	if (ret) {
+		seq_puts(s, " pull: <read error>");
+		return;
+	}
+	ret = regmap_read(soc->pull_regmap, soc->data->pull_dir[bank],
+			  &pull_dir);
+	if (ret) {
+		seq_puts(s, " pull: <read error>");
+		return;
+	}
+	pull_en = (pull_en >> offset) & 1;
+	pull_dir = (pull_dir >> offset) & 1;
+	seq_printf(s, " pull: %s,",
+		   pull_en ? (pull_dir ? "up" : "down") : "disable");
+
+	ret = regmap_read(soc->ds_regmap, soc->data->ds0[bank], &ds0);
+	if (ret) {
+		seq_puts(s, " drive-strength: <read error>");
+		return;
+	}
+	ret = regmap_read(soc->ds_regmap, soc->data->ds1[bank], &ds1);
+	if (ret) {
+		seq_puts(s, " drive-strength: <read error>");
+		return;
+	}
+	ds0 = (ds0 >> offset) & 1;
+	ds1 = (ds1 >> offset) & 1;
+	if (soc->data->have_ds2) {
+		ret = regmap_read(soc->ds_regmap, soc->data->ds2[bank], &ds2);
+		if (ret) {
+			seq_puts(s, " drive-strength: <read error>");
+			return;
+		}
+		ds2 = (ds2 >> offset) & 1;
+		ds = (ds2 << 2) | (ds1 << 1) | ds0;
+	} else {
+		ds = (ds0 << 1) | ds1;
+	}
+
+	strength = amb_reg_to_drive_strength(soc, ds);
+	if (strength < 0)
+		seq_puts(s, " drive-strength: invalid");
+	else
+		seq_printf(s, " drive-strength: %dmA", strength);
+}
+#endif
+
+/* list of pinconfig callbacks for pinconfig vertical in the pinctrl code */
+static const struct pinconf_ops amb_pinconf_ops = {
+	.is_generic		= true,
+	.pin_config_get		= amb_pinconf_get,
+	.pin_config_set		= amb_pinconf_set,
+	.pin_config_group_set	= amb_pinconf_group_set,
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+	.pin_config_dbg_show	= amb_pinconf_dbg_show,
+#endif
+};
+
+/* register the pinctrl interface with the pinctrl subsystem */
+static int amb_pinctrl_register(struct amb_pinctrl_soc_data *soc)
+{
+	struct pinctrl_pin_desc *pindesc;
+	struct pinctrl_desc *amb_pinctrl_desc;
+	unsigned int pin;
+
+	/* dynamically populate the pin number and pin name for pindesc */
+	pindesc = devm_kcalloc(soc->dev, soc->npins, sizeof(*pindesc),
+			       GFP_KERNEL);
+	if (!pindesc)
+		return -ENOMEM;
+
+	for (pin = 0; pin < soc->npins; pin++) {
+		pindesc[pin].number = pin;
+		pindesc[pin].name = devm_kasprintf(soc->dev, GFP_KERNEL,
+						   "io%u", pin);
+		if (!pindesc[pin].name)
+			return -ENOMEM;
+	}
+
+	amb_pinctrl_desc = devm_kzalloc(soc->dev, sizeof(*amb_pinctrl_desc), GFP_KERNEL);
+	if (!amb_pinctrl_desc)
+		return -ENOMEM;
+
+	amb_pinctrl_desc->name = dev_name(soc->dev);
+	amb_pinctrl_desc->pins = pindesc;
+	amb_pinctrl_desc->npins = soc->npins;
+	amb_pinctrl_desc->pctlops = &amb_pctrl_ops;
+	amb_pinctrl_desc->pmxops = &amb_pinmux_ops;
+	amb_pinctrl_desc->confops = &amb_pinconf_ops;
+	amb_pinctrl_desc->owner = THIS_MODULE;
+
+	soc->pctl = devm_pinctrl_register(soc->dev, amb_pinctrl_desc, soc);
+	if (IS_ERR(soc->pctl)) {
+		dev_err(soc->dev, "could not register pinctrl driver\n");
+		return PTR_ERR(soc->pctl);
+	}
+
+	return 0;
+}
+
+static int amb_pinctrl_probe(struct platform_device *pdev)
+{
+	struct amb_pinctrl_soc_data *soc;
+	struct device_node *np;
+	unsigned int *group_pins;
+	unsigned int group, group_pin;
+	size_t nr_group_pins = 0;
+	int rval;
+
+	soc = devm_kzalloc(&pdev->dev, sizeof(*soc), GFP_KERNEL);
+	if (!soc)
+		return -ENOMEM;
+
+	soc->dev = &pdev->dev;
+	soc->data = of_device_get_match_data(&pdev->dev);
+	if (!soc->data)
+		return dev_err_probe(&pdev->dev, -EINVAL, "missing soc data");
+	if (!soc->data->nr_banks ||
+	    soc->data->nr_banks > AMBA_MAX_BANKS ||
+	    !soc->data->npins ||
+	    soc->data->npins > AMBA_MAX_PINS ||
+	    soc->data->nr_banks * 32 > soc->data->npins ||
+	    soc->data->clk_au_dedicated_pin >= soc->data->npins ||
+	    PINID_TO_BANK(soc->data->clk_au_dedicated_pin) >= AMBA_MAX_BANKS)
+		return dev_err_probe(&pdev->dev, -EINVAL,
+				     "invalid bank or pin count\n");
+
+	soc->npins = soc->data->npins;
+
+	np = pdev->dev.of_node;
+	soc->iomux_base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(soc->iomux_base))
+		return dev_err_probe(&pdev->dev, PTR_ERR(soc->iomux_base),
+							 "couldn't get iomux reg");
+
+	soc->ds_regmap = syscon_regmap_lookup_by_phandle(np, "ambarella,drive-strength-syscon");
+	if (IS_ERR(soc->ds_regmap))
+		return dev_err_probe(&pdev->dev, PTR_ERR(soc->ds_regmap),
+				     "couldn't get drive-strength regmap");
+
+	soc->pull_regmap = syscon_regmap_lookup_by_phandle(np, "ambarella,pull-syscon");
+	if (IS_ERR(soc->pull_regmap))
+		return dev_err_probe(&pdev->dev, PTR_ERR(soc->pull_regmap),
+				     "couldn't get pull regmap");
+
+	soc->nr_groups = soc->data->nr_groups;
+	soc->functions = soc->data->functions;
+	soc->nr_functions = soc->data->nr_functions;
+	if (!soc->data->groups || !soc->nr_groups ||
+	    !soc->functions || !soc->nr_functions)
+		return dev_err_probe(&pdev->dev, -EINVAL,
+				     "missing pin groups or functions\n");
+
+	soc->groups = devm_kcalloc(&pdev->dev, soc->nr_groups,
+				   sizeof(*soc->groups), GFP_KERNEL);
+	if (!soc->groups)
+		return -ENOMEM;
+
+	for (group = 0; group < soc->nr_groups; group++) {
+		const struct ambpin_group_desc *desc =
+			&soc->data->groups[group];
+
+		if (!desc->name || !desc->pinmux || !desc->num_pins)
+			return dev_err_probe(&pdev->dev, -EINVAL,
+					     "invalid pin group %u\n", group);
+
+		nr_group_pins += desc->num_pins;
+	}
+
+	group_pins = devm_kcalloc(&pdev->dev, nr_group_pins,
+				  sizeof(*group_pins), GFP_KERNEL);
+	if (!group_pins)
+		return -ENOMEM;
+
+	for (group = 0; group < soc->nr_groups; group++) {
+		const struct ambpin_group_desc *desc =
+			&soc->data->groups[group];
+		struct ambpin_group *grp = &soc->groups[group];
+
+		grp->name = desc->name;
+		grp->pinmux = desc->pinmux;
+		grp->pins = group_pins;
+		grp->num_pins = desc->num_pins;
+		for (group_pin = 0;
+		     group_pin < grp->num_pins;
+		     group_pin++) {
+			u32 pinmux = grp->pinmux[group_pin];
+			unsigned int pin = AMBA_PINMUX_TO_PIN(pinmux);
+			unsigned int alt = AMBA_PINMUX_TO_ALT(pinmux);
+
+			if (pin >= soc->npins || alt > 7)
+				return dev_err_probe(&pdev->dev, -EINVAL,
+						     "group %s has invalid pinmux %#x\n",
+						     grp->name, pinmux);
+
+			grp->pins[group_pin] = pin;
+		}
+		group_pins += grp->num_pins;
+	}
+
+	raw_spin_lock_init(&soc->lock);
+
+	/* Mark all pins unavailable, then clear pins that exist. */
+	bitmap_fill(soc->used, AMBA_MAX_PINS);
+	bitmap_clear(soc->used, 0, soc->data->nr_banks * 32);
+	clear_bit(soc->data->clk_au_dedicated_pin, soc->used);
+
+	rval = amb_pinctrl_register(soc);
+	if (rval)
+		return dev_err_probe(&pdev->dev, rval, "pinctrl register failed!");
+
+	dev_info(&pdev->dev, "Ambarella pinctrl driver registered");
+
+	return 0;
+}
+
+static const struct of_device_id amb_pinctrl_dt_match[] = {
+	{
+		.compatible = "ambarella,cv75-pinctrl",
+		.data = &ambarella_cv75_pinctrl_data,
+	},
+	{},
+};
+MODULE_DEVICE_TABLE(of, amb_pinctrl_dt_match);
+
+static struct platform_driver amb_pinctrl_driver = {
+	.probe	= amb_pinctrl_probe,
+	.driver	= {
+		.name	= "ambarella-pinctrl",
+		.of_match_table = of_match_ptr(amb_pinctrl_dt_match),
+	},
+};
+
+static int __init amb_pinctrl_drv_register(void)
+{
+	return platform_driver_register(&amb_pinctrl_driver);
+}
+arch_initcall(amb_pinctrl_drv_register);
+
+MODULE_AUTHOR("Cao Rongrong <rrcao@ambarella.com>");
+MODULE_DESCRIPTION("Ambarella SoC pinctrl driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/pinctrl/pinctrl-ambarella.h b/drivers/pinctrl/pinctrl-ambarella.h
new file mode 100644
index 000000000000..094639627131
--- /dev/null
+++ b/drivers/pinctrl/pinctrl-ambarella.h
@@ -0,0 +1,50 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Ambarella pinctrl common data definitions
+ *
+ * Copyright (C) 2012-2026, Ambarella, Inc.
+ */
+
+#ifndef _PINCTRL_AMBARELLA_H
+#define _PINCTRL_AMBARELLA_H
+
+#include <linux/types.h>
+
+#define AMBA_MAX_BANKS			8
+
+#define AMBA_PINMUX(pin, alt)		(((alt) << 12) | (pin))
+#define AMBA_PINMUX_TO_PIN(mux)		((mux) & 0xfff)
+#define AMBA_PINMUX_TO_ALT(mux)		(((mux) >> 12) & 0xf)
+
+struct ambpin_group_desc {
+	const char		*name;
+	const u32		*pinmux;
+	unsigned int		num_pins;
+};
+
+struct ambpin_function {
+	const char		*name;
+	const char * const	*groups;
+	unsigned int		num_groups;
+};
+
+struct amb_pinctrl_data {
+	unsigned int		ds0[AMBA_MAX_BANKS];
+	unsigned int		ds1[AMBA_MAX_BANKS];
+	unsigned int		ds2[AMBA_MAX_BANKS];
+	unsigned int		pull_en[AMBA_MAX_BANKS];
+	unsigned int		pull_dir[AMBA_MAX_BANKS];
+	bool			have_ds2;
+	u32			hsm_domain_id;
+	u32			clk_au_dedicated_pin;
+	unsigned int		nr_banks;
+	unsigned int		npins;
+	const struct ambpin_group_desc *groups;
+	unsigned int		nr_groups;
+	const struct ambpin_function *functions;
+	unsigned int		nr_functions;
+};
+
+extern const struct amb_pinctrl_data ambarella_cv75_pinctrl_data;
+
+#endif /* _PINCTRL_AMBARELLA_H */

-- 
2.34.1




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

* [PATCH v6 11/13] serial: 8250_dw: add Ambarella CV75 quirks
  2026-09-04  6:38 [PATCH v6 00/13] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
                   ` (9 preceding siblings ...)
  2026-09-04  6:38 ` [PATCH v6 10/13] pinctrl: ambarella: add CV75 pin controller Long Zhao via B4 Relay
@ 2026-09-04  6:38 ` Long Zhao via B4 Relay
  2026-09-04 22:43   ` Linus Walleij
  2026-09-04  6:38 ` [PATCH v6 12/13] arm64: ambarella: add ARCH_AMBARELLA and CV75 EVK DT Long Zhao via B4 Relay
                   ` (2 subsequent siblings)
  13 siblings, 1 reply; 28+ messages in thread
From: Long Zhao via B4 Relay @ 2026-09-04  6:38 UTC (permalink / raw)
  To: Arnd Bergmann, Krzysztof Kozlowski, Alexandre Belloni, soc,
	linux-arm-kernel
  Cc: Long Zhao, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Michael Turquette, Stephen Boyd, Linus Walleij,
	Bartosz Golaszewski, Greg Kroah-Hartman, Jiri Slaby,
	Andy Shevchenko, Ilpo Järvinen, Catalin Marinas, Will Deacon,
	Long Zhao, Lee Jones, mfd, devicetree, linux-clk, linux-gpio,
	linux-serial, linux-kernel

From: Long Zhao <longzhao@ambarella.com>

The Ambarella CV75 UART is Ambarella IP, not a Synopsys DesignWare
licensed block. At the register level it is close enough to DW APB UART
that a full custom UART driver is unnecessary: reuse 8250_dw with a
small platform quirk.

Quirks:
- keep IER ETOI (bit 5) set for RX timeout behaviour
- skip set_rate; baud clock is managed outside the UART block

Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
 drivers/tty/serial/8250/8250_dw.c | 34 ++++++++++++++++++++++++++++++++++
 1 file changed, 34 insertions(+)

diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c
index 5fba913f3301..b15de40d1e2a 100644
--- a/drivers/tty/serial/8250/8250_dw.c
+++ b/drivers/tty/serial/8250/8250_dw.c
@@ -43,6 +43,9 @@
 #define RZN1_UART_xDMACR_8_WORD_BURST	(2 << 1)
 #define RZN1_UART_xDMACR_BLK_SZ(x)	((x) << 3)
 
+/* Ambarella IER: receive timeout interrupt enable */
+#define AMBARELLA_UART_IER_ETOI		BIT(5)
+
 /* Quirks */
 #define DW_UART_QUIRK_OCTEON		BIT(0)
 #define DW_UART_QUIRK_ARMADA_38X	BIT(1)
@@ -60,6 +63,7 @@
 
 struct dw8250_platform_data {
 	u8 usr_reg;
+	u8 ier_mask;
 	u32 cpr_value;
 	unsigned int quirks;
 };
@@ -572,6 +576,27 @@ static void dw8250_prepare_rx_dma(struct uart_8250_port *p)
 	dw8250_writel_ext(up, RZN1_UART_RDMACR, val);
 }
 
+static int dw8250_ier_mask_startup(struct uart_port *p)
+{
+	struct dw8250_data *d = to_dw8250_data(p->private_data);
+	struct uart_8250_port *up = up_to_u8250p(p);
+	int ret;
+
+	ret = serial8250_do_startup(p);
+	if (ret)
+		return ret;
+
+	/*
+	 * Ambarella keeps variant IER bits (e.g. ETOI) set for correct RX
+	 * timeout behaviour. Force them into the 8250 IER shadow so later
+	 * generic IER updates do not clear them.
+	 */
+	up->ier |= d->pdata->ier_mask;
+	serial_port_out(p, UART_IER, up->ier);
+
+	return 0;
+}
+
 static void dw8250_quirks(struct uart_port *p, struct dw8250_data *data)
 {
 	unsigned int quirks = data->pdata->quirks;
@@ -606,6 +631,8 @@ static void dw8250_quirks(struct uart_port *p, struct dw8250_data *data)
 		p->serial_in = dw8250_serial_in32;
 		data->uart_16550_compatible = true;
 	}
+	if (data->pdata->ier_mask)
+		p->startup = dw8250_ier_mask_startup;
 }
 
 static void dw8250_reset_control_assert(void *data)
@@ -879,6 +906,12 @@ static const struct dw8250_platform_data dw8250_intc10ee = {
 	.quirks = DW_UART_QUIRK_IER_KICK,
 };
 
+static const struct dw8250_platform_data dw8250_ambarella_cv75_data = {
+	.usr_reg = DW_UART_USR,
+	.ier_mask = AMBARELLA_UART_IER_ETOI,
+	.quirks = DW_UART_QUIRK_SKIP_SET_RATE,
+};
+
 static const struct dw8250_platform_data dw8250_ultrarisc_dp1000_data = {
 	.usr_reg = DW_UART_USR,
 	.cpr_value = FIELD_PREP_CONST(DW_UART_CPR_ABP_DATA_WIDTH, 2) |
@@ -889,6 +922,7 @@ static const struct dw8250_platform_data dw8250_ultrarisc_dp1000_data = {
 };
 
 static const struct of_device_id dw8250_of_match[] = {
+	{ .compatible = "ambarella,cv75-uart", .data = &dw8250_ambarella_cv75_data },
 	{ .compatible = "snps,dw-apb-uart", .data = &dw8250_dw_apb },
 	{ .compatible = "cavium,octeon-3860-uart", .data = &dw8250_octeon_3860_data },
 	{ .compatible = "marvell,armada-38x-uart", .data = &dw8250_armada_38x_data },

-- 
2.34.1




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

* [PATCH v6 12/13] arm64: ambarella: add ARCH_AMBARELLA and CV75 EVK DT
  2026-09-04  6:38 [PATCH v6 00/13] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
                   ` (10 preceding siblings ...)
  2026-09-04  6:38 ` [PATCH v6 11/13] serial: 8250_dw: add Ambarella CV75 quirks Long Zhao via B4 Relay
@ 2026-09-04  6:38 ` Long Zhao via B4 Relay
  2026-09-04 22:45   ` Linus Walleij
  2026-09-04  6:38 ` [PATCH v6 13/13] MAINTAINERS: add ARM/AMBARELLA SoC support Long Zhao via B4 Relay
  2026-09-04  6:54 ` [PATCH v6 00/13] Ambarella CV75 SoC minimal bring-up Jerome Brunet
  13 siblings, 1 reply; 28+ messages in thread
From: Long Zhao via B4 Relay @ 2026-09-04  6:38 UTC (permalink / raw)
  To: Arnd Bergmann, Krzysztof Kozlowski, Alexandre Belloni, soc,
	linux-arm-kernel
  Cc: Long Zhao, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Michael Turquette, Stephen Boyd, Linus Walleij,
	Bartosz Golaszewski, Greg Kroah-Hartman, Jiri Slaby,
	Andy Shevchenko, Ilpo Järvinen, Catalin Marinas, Will Deacon,
	Long Zhao, Lee Jones, mfd, devicetree, linux-clk, linux-gpio,
	linux-serial, linux-kernel

From: Long Zhao <longzhao@ambarella.com>

Enable ARCH_AMBARELLA and add a minimal CV75 SoC / EVK device tree for
console bring-up with the RCT clocks, UART, pinctrl and GPIO banks.

Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
 arch/arm64/Kconfig.platforms               |   6 +
 arch/arm64/boot/dts/Makefile               |   1 +
 arch/arm64/boot/dts/ambarella/Makefile     |   3 +
 arch/arm64/boot/dts/ambarella/cv75-evk.dts |  30 +++++
 arch/arm64/boot/dts/ambarella/cv75.dtsi    | 177 +++++++++++++++++++++++++++++
 arch/arm64/configs/defconfig               |   1 +
 6 files changed, 218 insertions(+)

diff --git a/arch/arm64/Kconfig.platforms b/arch/arm64/Kconfig.platforms
index d2acfac73003..16d7e3a4c2d6 100644
--- a/arch/arm64/Kconfig.platforms
+++ b/arch/arm64/Kconfig.platforms
@@ -26,6 +26,12 @@ config ARCH_SUNXI
 	help
 	  This enables support for Allwinner sunxi based SoCs like the A64.
 
+config ARCH_AMBARELLA
+	bool "Ambarella SoC family"
+	select PINCTRL
+	help
+	  This enables support for the ARM64 based Ambarella SoCs like CV75.
+
 config ARCH_ALPINE
 	bool "Annapurna Labs Alpine platform"
 	select ALPINE_MSI if PCI
diff --git a/arch/arm64/boot/dts/Makefile b/arch/arm64/boot/dts/Makefile
index fc726b215f12..f1709eb5d915 100644
--- a/arch/arm64/boot/dts/Makefile
+++ b/arch/arm64/boot/dts/Makefile
@@ -4,6 +4,7 @@ subdir-y += airoha
 subdir-y += allwinner
 subdir-y += altera
 subdir-y += amazon
+subdir-y += ambarella
 subdir-y += amd
 subdir-y += amlogic
 subdir-y += apm
diff --git a/arch/arm64/boot/dts/ambarella/Makefile b/arch/arm64/boot/dts/ambarella/Makefile
new file mode 100644
index 000000000000..ddca3d7b18cc
--- /dev/null
+++ b/arch/arm64/boot/dts/ambarella/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+
+dtb-$(CONFIG_ARCH_AMBARELLA) += cv75-evk.dtb
diff --git a/arch/arm64/boot/dts/ambarella/cv75-evk.dts b/arch/arm64/boot/dts/ambarella/cv75-evk.dts
new file mode 100644
index 000000000000..42a0fe24b83b
--- /dev/null
+++ b/arch/arm64/boot/dts/ambarella/cv75-evk.dts
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Ambarella CV75 EVK board
+ */
+
+/dts-v1/;
+
+#include "cv75.dtsi"
+
+/ {
+	model = "Ambarella CV75 EVK Board";
+	compatible = "ambarella,cv75-evk", "ambarella,cv75";
+
+	aliases {
+		serial0 = &uart0;
+	};
+
+	chosen {
+		stdout-path = &uart0;
+	};
+
+	memory@200000 {
+		device_type = "memory";
+		reg = <0x0 0x00200000 0x0 0x07e00000>;
+	};
+};
+
+&uart0 {
+	status = "okay";
+};
diff --git a/arch/arm64/boot/dts/ambarella/cv75.dtsi b/arch/arm64/boot/dts/ambarella/cv75.dtsi
new file mode 100644
index 000000000000..c36b6af48237
--- /dev/null
+++ b/arch/arm64/boot/dts/ambarella/cv75.dtsi
@@ -0,0 +1,177 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Ambarella CV75 SoC
+ *
+ * Boot peripheral: UART0 console.
+ * Clocks: a single ambarella,cv75-rct provider.
+ */
+
+#include <dt-bindings/clock/ambarella,cv75-rct.h>
+#include <dt-bindings/interrupt-controller/arm-gic.h>
+
+/ {
+	compatible = "ambarella,cv75";
+	interrupt-parent = <&gic>;
+	#address-cells = <2>;
+	#size-cells = <2>;
+
+	cpus {
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		cpu@0 {
+			compatible = "arm,cortex-a76";
+			device_type = "cpu";
+			reg = <0x000>;
+			enable-method = "psci";
+		};
+
+		cpu@1 {
+			compatible = "arm,cortex-a76";
+			device_type = "cpu";
+			reg = <0x100>;
+			enable-method = "psci";
+		};
+	};
+
+	osc: oscillator {
+		compatible = "fixed-clock";
+		#clock-cells = <0>;
+		clock-frequency = <24000000>;
+		clock-output-names = "osc";
+	};
+
+	psci {
+		compatible = "arm,psci-0.2";
+		method = "smc";
+	};
+
+	soc {
+		compatible = "simple-bus";
+		#address-cells = <2>;
+		#size-cells = <2>;
+		ranges;
+
+		ahb@ffe0000000 {
+			compatible = "simple-bus";
+			reg = <0xff 0xe0000000 0x0 0x01000000>;
+			ranges = <0xe0000000 0xff 0xe0000000 0x01000000>;
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			s_scratchpad_syscon: scratchpad@e002e000 {
+				compatible = "ambarella,cv75-sec-scratchpad", "syscon";
+				reg = <0xe002e000 0x1000>;
+			};
+		};
+
+		apb@ffe4000000 {
+			compatible = "simple-bus";
+			reg = <0xff 0xe4000000 0x0 0x01000000>;
+			ranges = <0xe4000000 0xff 0xe4000000 0x01000000>;
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			uart0: serial@e4000000 {
+				compatible = "ambarella,cv75-uart", "snps,dw-apb-uart";
+				reg = <0xe4000000 0x1000>;
+				clocks = <&rct CV75_GCLK_UART0>;
+				interrupts = <GIC_SPI 170 IRQ_TYPE_LEVEL_HIGH>;
+				reg-shift = <2>;
+				reg-io-width = <4>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&uart0_pins>;
+
+				status = "disabled";
+			};
+
+			pinctrl: pinctrl@e4010000 {
+				compatible = "ambarella,cv75-pinctrl";
+				reg = <0xe4010000 0x1000>;
+				ambarella,drive-strength-syscon = <&rct>;
+				ambarella,pull-syscon = <&s_scratchpad_syscon>;
+
+				uart0_pins: uart0-pins {
+					function = "uart0";
+					groups = "uart0";
+				};
+			};
+
+			gpio0: gpio@e4013000 {
+				compatible = "ambarella,cv75-gpio", "arm,primecell";
+				reg = <0xe4013000 0x1000>;
+				clocks = <&rct CV75_GCLK_APB>;
+				clock-names = "apb_pclk";
+				interrupts = <GIC_SPI 163 IRQ_TYPE_LEVEL_HIGH>;
+				gpio-controller;
+				#gpio-cells = <2>;
+				gpio-ranges = <&pinctrl 0 0 32>;
+				interrupt-controller;
+				#interrupt-cells = <2>;
+				arm,primecell-periphid = <0x000e8061>;
+			};
+
+			gpio1: gpio@e4014000 {
+				compatible = "ambarella,cv75-gpio", "arm,primecell";
+				reg = <0xe4014000 0x1000>;
+				clocks = <&rct CV75_GCLK_APB>;
+				clock-names = "apb_pclk";
+				interrupts = <GIC_SPI 164 IRQ_TYPE_LEVEL_HIGH>;
+				gpio-controller;
+				#gpio-cells = <2>;
+				gpio-ranges = <&pinctrl 0 32 32>;
+				interrupt-controller;
+				#interrupt-cells = <2>;
+				arm,primecell-periphid = <0x000e8061>;
+			};
+
+			gpio2: gpio@e4015000 {
+				compatible = "ambarella,cv75-gpio", "arm,primecell";
+				reg = <0xe4015000 0x1000>;
+				clocks = <&rct CV75_GCLK_APB>;
+				clock-names = "apb_pclk";
+				interrupts = <GIC_SPI 165 IRQ_TYPE_LEVEL_HIGH>;
+				gpio-controller;
+				#gpio-cells = <2>;
+				gpio-ranges = <&pinctrl 0 64 32>;
+				interrupt-controller;
+				#interrupt-cells = <2>;
+				arm,primecell-periphid = <0x000e8061>;
+			};
+		};
+
+		rct: clock-controller@ffed080000 {
+			compatible = "ambarella,cv75-rct", "syscon";
+			reg = <0xff 0xed080000 0x0 0x1000>;
+			clocks = <&osc>;
+			clock-names = "osc";
+			#clock-cells = <1>;
+			#reset-cells = <1>;
+			assigned-clocks = <&rct CV75_GCLK_UART0>;
+			assigned-clock-parents = <&osc>;
+			assigned-clock-rates = <24000000>;
+		};
+
+		gic: interrupt-controller@fff0101000 {
+			compatible = "arm,gic-400";
+			reg = <0xff 0xf0101000 0x0 0x1000>,
+			      <0xff 0xf0102000 0x0 0x2000>,
+			      <0xff 0xf0104000 0x0 0x2000>,
+			      <0xff 0xf0106000 0x0 0x2000>;
+			interrupt-controller;
+			#interrupt-cells = <3>;
+			#address-cells = <0>;
+			interrupts = <GIC_PPI 9
+				      (GIC_CPU_MASK_SIMPLE(2) | IRQ_TYPE_LEVEL_HIGH)>;
+		};
+	};
+
+	timer {
+		compatible = "arm,armv8-timer";
+		interrupts = <GIC_PPI 13 (GIC_CPU_MASK_SIMPLE(2) | IRQ_TYPE_LEVEL_LOW)>,
+			     <GIC_PPI 14 (GIC_CPU_MASK_SIMPLE(2) | IRQ_TYPE_LEVEL_LOW)>,
+			     <GIC_PPI 11 (GIC_CPU_MASK_SIMPLE(2) | IRQ_TYPE_LEVEL_LOW)>,
+			     <GIC_PPI 10 (GIC_CPU_MASK_SIMPLE(2) | IRQ_TYPE_LEVEL_LOW)>,
+			     <GIC_PPI 12 (GIC_CPU_MASK_SIMPLE(2) | IRQ_TYPE_LEVEL_LOW)>;
+	};
+};
diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig
index ad3bd3d9507b..454f07a6a6a6 100644
--- a/arch/arm64/configs/defconfig
+++ b/arch/arm64/configs/defconfig
@@ -36,6 +36,7 @@ CONFIG_CRASH_DUMP=y
 CONFIG_ARCH_ACTIONS=y
 CONFIG_ARCH_AIROHA=y
 CONFIG_ARCH_SUNXI=y
+CONFIG_ARCH_AMBARELLA=y
 CONFIG_ARCH_ALPINE=y
 CONFIG_ARCH_APPLE=y
 CONFIG_ARCH_ARTPEC=y

-- 
2.34.1




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

* [PATCH v6 13/13] MAINTAINERS: add ARM/AMBARELLA SoC support
  2026-09-04  6:38 [PATCH v6 00/13] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
                   ` (11 preceding siblings ...)
  2026-09-04  6:38 ` [PATCH v6 12/13] arm64: ambarella: add ARCH_AMBARELLA and CV75 EVK DT Long Zhao via B4 Relay
@ 2026-09-04  6:38 ` Long Zhao via B4 Relay
  2026-09-04 22:45   ` Linus Walleij
  2026-09-04  6:54 ` [PATCH v6 00/13] Ambarella CV75 SoC minimal bring-up Jerome Brunet
  13 siblings, 1 reply; 28+ messages in thread
From: Long Zhao via B4 Relay @ 2026-09-04  6:38 UTC (permalink / raw)
  To: Arnd Bergmann, Krzysztof Kozlowski, Alexandre Belloni, soc,
	linux-arm-kernel
  Cc: Long Zhao, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Michael Turquette, Stephen Boyd, Linus Walleij,
	Bartosz Golaszewski, Greg Kroah-Hartman, Jiri Slaby,
	Andy Shevchenko, Ilpo Järvinen, Catalin Marinas, Will Deacon,
	Long Zhao, Lee Jones, mfd, devicetree, linux-clk, linux-gpio,
	linux-serial, linux-kernel

From: Long Zhao <longzhao@ambarella.com>

Add a maintainer entry covering Ambarella CV75 bindings, DTS and
related drivers.

Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
 MAINTAINERS | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 803b60ad628f..59e5b32d7a02 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2502,6 +2502,20 @@ F:	drivers/clocksource/asm9260_timer.c
 F:	drivers/rtc/rtc-asm9260.c
 F:	drivers/watchdog/asm9260_wdt.c
 
+ARM/AMBARELLA SOC SUPPORT
+M:	Long Zhao <longzhao@ambarella.com>
+L:	linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
+L:	devicetree@vger.kernel.org
+S:	Maintained
+F:	Documentation/devicetree/bindings/arm/ambarella.yaml
+F:	Documentation/devicetree/bindings/clock/ambarella,cv75-rct.yaml
+F:	Documentation/devicetree/bindings/pinctrl/ambarella,cv75-pinctrl.yaml
+F:	arch/arm64/boot/dts/ambarella/
+F:	drivers/clk/ambarella/
+F:	drivers/pinctrl/pinctrl-ambarella*
+F:	include/dt-bindings/clock/ambarella,cv75-rct.h
+N:	ambarella
+
 ARM/AMD PENSANDO ARM64 ARCHITECTURE
 M:	Brad Larson <blarson@amd.com>
 L:	linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)

-- 
2.34.1




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

* Re: [PATCH v6 00/13] Ambarella CV75 SoC minimal bring-up
  2026-09-04  6:38 [PATCH v6 00/13] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
                   ` (12 preceding siblings ...)
  2026-09-04  6:38 ` [PATCH v6 13/13] MAINTAINERS: add ARM/AMBARELLA SoC support Long Zhao via B4 Relay
@ 2026-09-04  6:54 ` Jerome Brunet
  2026-09-04  6:56   ` Krzysztof Kozlowski
  13 siblings, 1 reply; 28+ messages in thread
From: Jerome Brunet @ 2026-09-04  6:54 UTC (permalink / raw)
  To: Long Zhao via B4 Relay, Arnd Bergmann, Krzysztof Kozlowski,
	Alexandre Belloni, soc, linux-arm-kernel
  Cc: Long Zhao, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Michael Turquette, Stephen Boyd, Linus Walleij,
	Bartosz Golaszewski, Greg Kroah-Hartman, Jiri Slaby,
	Andy Shevchenko, Ilpo Järvinen, Catalin Marinas, Will Deacon,
	Long Zhao, Lee Jones, mfd, devicetree, linux-clk, linux-gpio,
	linux-serial, linux-kernel, Krzysztof Kozlowski

On ven. 04 sept. 2026 at 14:38, Long Zhao via B4 Relay <devnull+longzhao.ambarella.com@kernel.org> wrote:

> This series adds minimal Ambarella CV75 support for early bring-up with
> a serial console: DT bindings, RCT/CCU clocks, pinctrl, PL061 GPIO via
> gpio-regmap, 8250_dw UART quirks, ARCH_AMBARELLA, CV75 EVK DT, and
> MAINTAINERS.
>
> This is a single unified series. Please apply via the SoC tree;
> subsystem maintainers are Cc'd for their pieces.
>
> Verified with:
> - scripts/checkpatch.pl --strict (0 errors)
> - make dt_binding_check DT_SCHEMA_FILES=ambarella:pl061-gpio.yaml:syscon.yaml:snps-dw-apb-uart.yaml
> - make CHECK_DTBS=y ambarella/cv75-evk.dtb
> - arm64 build of new drivers/DTS
> - CV75 EVK: console via 8250_dw (dw-apb-uart); GPIO edge IRQ
>   (gpio-hammer / gpio-event-mon)
>
> Signed-off-by: Long Zhao <longzhao@ambarella.com>
> ---
> Changes in v6:
> - gpio: convert gpio-pl061 data path to gpio-regmap; keep PL061 irqchip
>   (Andy, Linus). Add write_data_after_dir + girq helpers to gpio-regmap.
>   Ambarella layout uses JEP106 periphid 0x000e8061; no AFSEL clearing in
>   the GPIO driver; Ambarella skips unverified GPIO PM save/restore
> - serial: restore 8250_dw Ambarella quirks from v3; drop standalone
>   8250_ambarella (Andy). Commit message clarifies Ambarella IP with a
>   DW-like register map needing only a thin quirk. Binding under
>   snps-dw-apb-uart; DT uses ambarella,cv75-uart + snps,dw-apb-uart with
>   reg-shift/reg-io-width
> - clock/RCT: document #reset-cells (Krzysztof); soft-reset ID header and
>   reset provider deferred (no resets= consumers in this series)
> - arm64/Kconfig: do not select ARM_PSCI / HAVE_ARM_ARCH_TIMER (Sashiko)
> - pinctrl: no Ambarella suspend/resume claim without validated PM
>
> Link to v5:
> https://lore.kernel.org/r/20260818-cv75-v5-v5-0-7bbb12f0360f@ambarella.com
> Link to v2:
> https://lore.kernel.org/r/20260806-longzhao-upstream-cv75-v2-v2-0-6b09707c5fe9@ambarella.com
> Link to v3-A:
> https://lore.kernel.org/r/20260813-cv75-v3-a-v3-0-cfdd20b1b4dd@ambarella.com
> Link to v3-B:
> https://lore.kernel.org/r/20260813-cv75-v3-b-v3-0-1d82e18da09b@ambarella.com
> Link to v3-C:
> https://lore.kernel.org/r/20260813-cv75-v3-c-v3-0-9352b3fd6026@ambarella.com
> Link to v4-B:
> https://lore.kernel.org/r/20260814-cv75-v4-b-v4-0-622e63153d97@ambarella.com
>

No change log ?

> To: Arnd Bergmann <arnd@arndb.de>
> To: Krzysztof Kozlowski <krzk@kernel.org>
> To: Alexandre Belloni <alexandre.belloni@bootlin.com>
> To: soc@lists.linux.dev
> To: linux-arm-kernel@lists.infradead.org
> Cc: Rob Herring <robh@kernel.org>
> Cc: Krzysztof Kozlowski <krzk+dt@kernel.org>
> Cc: Conor Dooley <conor+dt@kernel.org>
> Cc: Michael Turquette <mturquette@baylibre.com>
> Cc: Stephen Boyd <sboyd@kernel.org>
> Cc: Linus Walleij <linusw@kernel.org>
> Cc: Bartosz Golaszewski <brgl@kernel.org>
> Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
> Cc: Jiri Slaby <jirislaby@kernel.org>
> Cc: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
> Cc: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
> Cc: Catalin Marinas <catalin.marinas@arm.com>
> Cc: Will Deacon <will@kernel.org>
> Cc: Long Zhao <zl020895@163.com>
> Cc: Lee Jones <lee@kernel.org>
> Cc: mfd@lists.linux.dev
> Cc: devicetree@vger.kernel.org
> Cc: linux-clk@vger.kernel.org
> Cc: linux-gpio@vger.kernel.org
> Cc: linux-serial@vger.kernel.org
> Cc: linux-kernel@vger.kernel.org
>
> ---
> Long Zhao (13):
>       dt-bindings: arm: add Ambarella CV75 platforms
>       dt-bindings: mfd: syscon: add Ambarella CV75 secure scratchpad
>       dt-bindings: clock: add Ambarella CV75 RCT
>       dt-bindings: pinctrl: add Ambarella CV75 pinctrl
>       dt-bindings: gpio: pl061: add Ambarella CV75 variant
>       dt-bindings: serial: snps-dw-apb-uart: add ambarella,cv75-uart
>       clk: ambarella: add CV75 CCU driver
>       gpio: regmap: support write_data_after_dir and girq
>       gpio: pl061: use gpio-regmap and add Ambarella layout
>       pinctrl: ambarella: add CV75 pin controller
>       serial: 8250_dw: add Ambarella CV75 quirks
>       arm64: ambarella: add ARCH_AMBARELLA and CV75 EVK DT
>       MAINTAINERS: add ARM/AMBARELLA SoC support
>
>  .../devicetree/bindings/arm/ambarella.yaml         |  24 +
>  .../bindings/clock/ambarella,cv75-rct.yaml         |  64 ++
>  .../devicetree/bindings/gpio/pl061-gpio.yaml       |  32 +-
>  Documentation/devicetree/bindings/mfd/syscon.yaml  |   1 +
>  .../bindings/pinctrl/ambarella,cv75-pinctrl.yaml   | 239 +++++++
>  .../bindings/serial/snps-dw-apb-uart.yaml          |   1 +
>  .../devicetree/bindings/vendor-prefixes.yaml       |   2 +
>  MAINTAINERS                                        |  14 +
>  arch/arm64/Kconfig.platforms                       |   6 +
>  arch/arm64/boot/dts/Makefile                       |   1 +
>  arch/arm64/boot/dts/ambarella/Makefile             |   3 +
>  arch/arm64/boot/dts/ambarella/cv75-evk.dts         |  30 +
>  arch/arm64/boot/dts/ambarella/cv75.dtsi            | 177 +++++
>  arch/arm64/configs/defconfig                       |   1 +
>  drivers/clk/Kconfig                                |   1 +
>  drivers/clk/Makefile                               |   1 +
>  drivers/clk/ambarella/Kconfig                      |  13 +
>  drivers/clk/ambarella/Makefile                     |   6 +
>  drivers/clk/ambarella/ccu-cv75.c                   | 296 ++++++++
>  drivers/clk/ambarella/ccu_common.c                 |  60 ++
>  drivers/clk/ambarella/ccu_common.h                 |  25 +
>  drivers/clk/ambarella/ccu_mux_div.c                | 234 +++++++
>  drivers/clk/ambarella/ccu_mux_div.h                |  35 +
>  drivers/clk/ambarella/ccu_pll.c                    | 421 ++++++++++++
>  drivers/clk/ambarella/ccu_pll.h                    |  70 ++
>  drivers/gpio/Kconfig                               |   2 +
>  drivers/gpio/gpio-pl061.c                          | 342 +++++----
>  drivers/gpio/gpio-regmap.c                         |  24 +-
>  drivers/pinctrl/Kconfig                            |  15 +
>  drivers/pinctrl/Makefile                           |   2 +
>  drivers/pinctrl/pinctrl-ambarella-cv75.c           | 561 +++++++++++++++
>  drivers/pinctrl/pinctrl-ambarella.c                | 761 +++++++++++++++++++++
>  drivers/pinctrl/pinctrl-ambarella.h                |  50 ++
>  drivers/tty/serial/8250/8250_dw.c                  |  34 +
>  include/dt-bindings/clock/ambarella,cv75-rct.h     |  15 +
>  include/linux/amba/bus.h                           |   1 +
>  include/linux/gpio/regmap.h                        |  13 +
>  37 files changed, 3439 insertions(+), 138 deletions(-)

That is a lot of stuff mixed into a single series that could easily split

> ---
> base-commit: 34cf6dafc47441dfb6b356a095b89c3585a93714
> change-id: 20260818-cv75-v5-9e2039b349bc
>
> Best regards,
> --  
> Long Zhao <longzhao@ambarella.com>
>
>

-- 
Jerome


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

* Re: [PATCH v6 00/13] Ambarella CV75 SoC minimal bring-up
  2026-09-04  6:54 ` [PATCH v6 00/13] Ambarella CV75 SoC minimal bring-up Jerome Brunet
@ 2026-09-04  6:56   ` Krzysztof Kozlowski
  0 siblings, 0 replies; 28+ messages in thread
From: Krzysztof Kozlowski @ 2026-09-04  6:56 UTC (permalink / raw)
  To: Jerome Brunet, Long Zhao via B4 Relay, Arnd Bergmann,
	Alexandre Belloni, soc, linux-arm-kernel
  Cc: Long Zhao, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Michael Turquette, Stephen Boyd, Linus Walleij,
	Bartosz Golaszewski, Greg Kroah-Hartman, Jiri Slaby,
	Andy Shevchenko, Ilpo Järvinen, Catalin Marinas, Will Deacon,
	Long Zhao, Lee Jones, mfd, devicetree, linux-clk, linux-gpio,
	linux-serial, linux-kernel, Krzysztof Kozlowski

On 04/09/2026 08:54, Jerome Brunet wrote:
> On ven. 04 sept. 2026 at 14:38, Long Zhao via B4 Relay <devnull+longzhao.ambarella.com@kernel.org> wrote:
>>
>>  .../devicetree/bindings/arm/ambarella.yaml         |  24 +
>>  .../bindings/clock/ambarella,cv75-rct.yaml         |  64 ++
>>  .../devicetree/bindings/gpio/pl061-gpio.yaml       |  32 +-
>>  Documentation/devicetree/bindings/mfd/syscon.yaml  |   1 +
>>  .../bindings/pinctrl/ambarella,cv75-pinctrl.yaml   | 239 +++++++
>>  .../bindings/serial/snps-dw-apb-uart.yaml          |   1 +
>>  .../devicetree/bindings/vendor-prefixes.yaml       |   2 +
>>  MAINTAINERS                                        |  14 +
>>  arch/arm64/Kconfig.platforms                       |   6 +
>>  arch/arm64/boot/dts/Makefile                       |   1 +
>>  arch/arm64/boot/dts/ambarella/Makefile             |   3 +
>>  arch/arm64/boot/dts/ambarella/cv75-evk.dts         |  30 +
>>  arch/arm64/boot/dts/ambarella/cv75.dtsi            | 177 +++++
>>  arch/arm64/configs/defconfig                       |   1 +
>>  drivers/clk/Kconfig                                |   1 +
>>  drivers/clk/Makefile                               |   1 +
>>  drivers/clk/ambarella/Kconfig                      |  13 +
>>  drivers/clk/ambarella/Makefile                     |   6 +
>>  drivers/clk/ambarella/ccu-cv75.c                   | 296 ++++++++
>>  drivers/clk/ambarella/ccu_common.c                 |  60 ++
>>  drivers/clk/ambarella/ccu_common.h                 |  25 +
>>  drivers/clk/ambarella/ccu_mux_div.c                | 234 +++++++
>>  drivers/clk/ambarella/ccu_mux_div.h                |  35 +
>>  drivers/clk/ambarella/ccu_pll.c                    | 421 ++++++++++++
>>  drivers/clk/ambarella/ccu_pll.h                    |  70 ++
>>  drivers/gpio/Kconfig                               |   2 +
>>  drivers/gpio/gpio-pl061.c                          | 342 +++++----
>>  drivers/gpio/gpio-regmap.c                         |  24 +-
>>  drivers/pinctrl/Kconfig                            |  15 +
>>  drivers/pinctrl/Makefile                           |   2 +
>>  drivers/pinctrl/pinctrl-ambarella-cv75.c           | 561 +++++++++++++++
>>  drivers/pinctrl/pinctrl-ambarella.c                | 761 +++++++++++++++++++++
>>  drivers/pinctrl/pinctrl-ambarella.h                |  50 ++
>>  drivers/tty/serial/8250/8250_dw.c                  |  34 +
>>  include/dt-bindings/clock/ambarella,cv75-rct.h     |  15 +
>>  include/linux/amba/bus.h                           |   1 +
>>  include/linux/gpio/regmap.h                        |  13 +
>>  37 files changed, 3439 insertions(+), 138 deletions(-)
> 
> That is a lot of stuff mixed into a single series that could easily split

Well, not easily, because each split patchset would rely on non-existing
CONFIG which could trigger warnings.

Contributors follow here the correct and documented process of new SoC
platform, I don't get why now only this patchset has to be split...


Best regards,
Krzysztof


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

* Re: [PATCH v6 07/13] clk: ambarella: add CV75 CCU driver
  2026-09-04  6:38 ` [PATCH v6 07/13] clk: ambarella: add CV75 CCU driver Long Zhao via B4 Relay
@ 2026-09-04  7:44   ` Jerome Brunet
  2026-09-04  9:21     ` zl020895
  0 siblings, 1 reply; 28+ messages in thread
From: Jerome Brunet @ 2026-09-04  7:44 UTC (permalink / raw)
  To: Long Zhao via B4 Relay, Arnd Bergmann, Krzysztof Kozlowski,
	Alexandre Belloni, soc, linux-arm-kernel
  Cc: Long Zhao, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Michael Turquette, Stephen Boyd, Linus Walleij,
	Bartosz Golaszewski, Greg Kroah-Hartman, Jiri Slaby,
	Andy Shevchenko, Ilpo Järvinen, Catalin Marinas, Will Deacon,
	Long Zhao, Lee Jones, mfd, devicetree, linux-clk, linux-gpio,
	linux-serial, linux-kernel

On ven. 04 sept. 2026 at 14:38, Long Zhao via B4 Relay <devnull+longzhao.ambarella.com@kernel.org> wrote:

> From: Long Zhao <longzhao@ambarella.com>
>
> Add the Ambarella CV75 clock driver covering the core PLL and the
> UART0 / AHB / APB clocks needed for early console bring-up.
>
> Signed-off-by: Long Zhao <longzhao@ambarella.com>
> ---
>  drivers/clk/Kconfig                 |   1 +
>  drivers/clk/Makefile                |   1 +
>  drivers/clk/ambarella/Kconfig       |  13 ++
>  drivers/clk/ambarella/Makefile      |   6 +
>  drivers/clk/ambarella/ccu-cv75.c    | 296 +++++++++++++++++++++++++
>  drivers/clk/ambarella/ccu_common.c  |  60 +++++
>  drivers/clk/ambarella/ccu_common.h  |  25 +++
>  drivers/clk/ambarella/ccu_mux_div.c | 234 ++++++++++++++++++++
>  drivers/clk/ambarella/ccu_mux_div.h |  35 +++
>  drivers/clk/ambarella/ccu_pll.c     | 421 ++++++++++++++++++++++++++++++++++++
>  drivers/clk/ambarella/ccu_pll.h     |  70 ++++++
>  11 files changed, 1162 insertions(+)

Again seems like this could be split. AFAICT, you are adding 2 clock
driver and a controller

>
> diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
> index 1717ce75a907..fbbf4963716c 100644
> --- a/drivers/clk/Kconfig
> +++ b/drivers/clk/Kconfig
> @@ -506,6 +506,7 @@ config COMMON_CLK_RPMI
>  	  the RISC-V platform management interface (RPMI) specification.
>  
>  source "drivers/clk/actions/Kconfig"
> +source "drivers/clk/ambarella/Kconfig"
>  source "drivers/clk/analogbits/Kconfig"
>  source "drivers/clk/aspeed/Kconfig"
>  source "drivers/clk/bcm/Kconfig"
> diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
> index cc108a75a900..30d823ee606f 100644
> --- a/drivers/clk/Makefile
> +++ b/drivers/clk/Makefile
> @@ -112,6 +112,7 @@ obj-$(CONFIG_COMMON_CLK_XGENE)		+= clk-xgene.o
>  
>  # please keep this section sorted lexicographically by directory path name
>  obj-y					+= actions/
> +obj-y					+= ambarella/
>  obj-y					+= analogbits/
>  obj-y					+= aspeed/
>  obj-$(CONFIG_COMMON_CLK_AT91)		+= at91/
> diff --git a/drivers/clk/ambarella/Kconfig b/drivers/clk/ambarella/Kconfig
> new file mode 100644
> index 000000000000..d79bb4bd6df5
> --- /dev/null
> +++ b/drivers/clk/ambarella/Kconfig
> @@ -0,0 +1,13 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +
> +config CLK_AMBARELLA_CV75
> +	bool "Ambarella CV75 RCT clock controller"
> +	depends on ARCH_AMBARELLA || COMPILE_TEST
> +	select CLK_AMBARELLA_CCU
> +	default ARCH_AMBARELLA
> +	help
> +	  Say Y to enable the Ambarella CV75 RCT clock controller.
> +
> +config CLK_AMBARELLA_CCU
> +	bool
> +	select REGMAP_MMIO
> diff --git a/drivers/clk/ambarella/Makefile b/drivers/clk/ambarella/Makefile
> new file mode 100644
> index 000000000000..36e96326ea97
> --- /dev/null
> +++ b/drivers/clk/ambarella/Makefile
> @@ -0,0 +1,6 @@
> +#
> +# Makefile for ambarella specific clk
> +#
> +
> +obj-$(CONFIG_CLK_AMBARELLA_CCU) += ccu_common.o ccu_mux_div.o ccu_pll.o
> +obj-$(CONFIG_CLK_AMBARELLA_CV75) += ccu-cv75.o
> diff --git a/drivers/clk/ambarella/ccu-cv75.c b/drivers/clk/ambarella/ccu-cv75.c
> new file mode 100644
> index 000000000000..c4828be598be
> --- /dev/null
> +++ b/drivers/clk/ambarella/ccu-cv75.c
> @@ -0,0 +1,296 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2026 Ambarella, Inc.
> + *
> + * CV75 RCT clock controller for boot clocks.
> + */
> +
> +#include <linux/clk-provider.h>
> +#include <linux/clk.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +
> +#include <dt-bindings/clock/ambarella,cv75-rct.h>
> +
> +#include "ccu_common.h"
> +#include "ccu_mux_div.h"
> +#include "ccu_pll.h"
> +
> +enum amb_cv75_clk_type {
> +	AMB_CV75_CLK_FIXED_RATE,
> +	AMB_CV75_CLK_FIXED_FACTOR,
> +	AMB_CV75_CLK_PLL,
> +	AMB_CV75_CLK_DIV,
> +	AMB_CV75_CLK_MUX_DIV,
> +};
> +
> +enum amb_cv75_clk_ref {
> +	AMB_CV75_CLK_REF_OSC = -1,
> +	AMB_CV75_CLK_REF_DUMMY = -2,

This is fishy, needs a comment at least

> +};
> +
> +/* PLL version for CV75 (ambarella,clkpll-v1 in vendor DTS) */
> +static const struct amb_pll_soc_data cv75_pll_soc_data = {
> +	.pll_version	= 1,
> +	.fsout_mask	= CTRL2_FSOUT_DIV2,
> +	.fsout_val	= CTRL2_FSOUT_DIV2,
> +	.fsdiv_mask	= CTRL2_FSDIV_DIV2,
> +	.fsdiv_val	= CTRL2_FSDIV_DIV2,
> +	.vcodiv_mask	= CTRL2_VCODIV_DIV2,
> +	.vcodiv_val	= CTRL2_VCODIV_DIV2,
> +	.vco_max_mhz	= 2600UL,
> +	.vco_min_mhz	= 850UL,
> +	.vco_range	= { 1800UL, 1400UL, 1100UL, 0UL },
> +};
> +
> +struct amb_cv75_clk_desc {
> +	int id;
> +	enum amb_cv75_clk_type type;
> +	const char *name;
> +	int parent;
> +
> +	union {
> +		struct {
> +			unsigned long rate;
> +		} fixed_rate;
> +		struct {
> +			unsigned long flags;
> +			unsigned int mult;
> +			unsigned int div;
> +		} fixed_factor;
> +		struct {
> +			u32 reg_offset[REG_NUM];
> +			const struct amb_pll_soc_data *soc_data;
> +		} pll;
> +		struct {
> +			u32 reg;
> +			u32 shift;
> +			u32 width;
> +			u32 flags;
> +			u32 fix_divider;
> +		} div;
> +		struct amb_mux_div_desc mux_div;
> +	};
> +};
> +
> +static const struct amb_cv75_clk_desc cv75_clks[] = {
> +	{
> +		.id = AMB_CV75_CLK_REF_DUMMY,
> +		.type = AMB_CV75_CLK_FIXED_RATE,
> +		.name = "dummy",
> +		.fixed_rate.rate = 0,
> +	},

A clock that comes out of nowhere with a rate 0 is not OK.

> +	{
> +		.id = CV75_GCLK_CORE,
> +		.type = AMB_CV75_CLK_PLL,
> +		.name = "core",
> +		.parent = AMB_CV75_CLK_REF_OSC,
> +		.pll = {
> +			.reg_offset = {
> +				0x000, 0x004, 0x100,
> +				0x104, 0x000, 0x000,
> +			},
> +			.soc_data = &cv75_pll_soc_data,
> +		},
> +	},
> +	{
> +		.id = CV75_GCLK_AHB,
> +		.type = AMB_CV75_CLK_FIXED_FACTOR,
> +		.name = "ahb",
> +		.parent = CV75_GCLK_CORE,
> +		.fixed_factor = {
> +			.flags = 0,
> +			.mult = 1,
> +			.div = 2,
> +		},
> +	},
> +	{
> +		.id = CV75_GCLK_APB,
> +		.type = AMB_CV75_CLK_FIXED_FACTOR,
> +		.name = "apb",
> +		.parent = CV75_GCLK_CORE,
> +		.fixed_factor = {
> +			.flags = 0,
> +			.mult = 1,
> +			.div = 4,
> +		},
> +	},
> +	{
> +		.id = CV75_GCLK_UART0,
> +		.type = AMB_CV75_CLK_MUX_DIV,
> +		.name = "uart0",
> +		.mux_div = {
> +			.name = "uart0",
> +			.parents = (const int[]) {
> +				AMB_CV75_CLK_REF_OSC, CV75_GCLK_CORE,
> +				AMB_CV75_CLK_REF_DUMMY, AMB_CV75_CLK_REF_DUMMY,
> +			},
> +			.num_parents = 4,
> +			.mux_reg = 0x1c8,
> +			.mux_shift = 0,
> +			.mux_mask = 0x3,
> +			.div_reg = 0x038,
> +			.div_shift = 0,
> +			.div_width = 24,
> +			.div_flags = CLK_DIVIDER_ONE_BASED,
> +			.fix_divider = 1,
> +		},
> +	},
> +};
> +
> +static struct clk_hw *amb_cv75_get_parent(struct amb_ccu *ccu,
> +					  struct clk_hw *osc,
> +					  struct clk_hw *dummy,
> +					  int parent)
> +{
> +	if (parent == AMB_CV75_CLK_REF_OSC)
> +		return osc;
> +	if (parent == AMB_CV75_CLK_REF_DUMMY)
> +		return dummy;
> +
> +	if (parent < 0 || parent >= ccu->data->num)
> +		return ERR_PTR(-EINVAL);
> +
> +	if (!ccu->data->hws[parent])
> +		return ERR_PTR(-EPROBE_DEFER);
> +
> +	return ccu->data->hws[parent];
> +}
> +
> +static struct clk_hw *amb_cv75_register_mux_div(struct device *dev,
> +						struct amb_ccu *ccu,
> +						const struct amb_cv75_clk_desc *desc,
> +						struct clk_hw *osc,
> +						struct clk_hw *dummy)
> +{
> +	const struct amb_mux_div_desc *md = &desc->mux_div;
> +	struct clk_parent_data *pdata;
> +	struct clk_hw *parent;
> +	u8 i;
> +
> +	pdata = devm_kcalloc(dev, md->num_parents, sizeof(*pdata), GFP_KERNEL);
> +	if (!pdata)
> +		return ERR_PTR(-ENOMEM);
> +
> +	for (i = 0; i < md->num_parents; i++) {
> +		if (md->parents[i] == AMB_CV75_CLK_REF_OSC) {
> +			pdata[i].fw_name = "osc";
> +			continue;
> +		}
> +
> +		parent = amb_cv75_get_parent(ccu, osc, dummy, md->parents[i]);
> +		if (IS_ERR(parent))
> +			return parent;
> +
> +		pdata[i].hw = parent;
> +	}
> +
> +	return amb_mux_div_register(dev, ccu->map, md, pdata);
> +}
> +
> +static struct clk_hw *amb_cv75_register_clk(struct device *dev,
> +					    struct amb_ccu *ccu,
> +					    const struct amb_cv75_clk_desc *desc,
> +					    struct clk_hw *osc,
> +					    struct clk_hw *dummy)
> +{
> +	struct amb_pll_desc pll_desc;
> +	struct clk_hw *parent;
> +
> +	switch (desc->type) {
> +	case AMB_CV75_CLK_FIXED_RATE:
> +		return devm_clk_hw_register_fixed_rate(dev, desc->name, NULL, 0,
> +						       desc->fixed_rate.rate);
> +	case AMB_CV75_CLK_FIXED_FACTOR:
> +		parent = amb_cv75_get_parent(ccu, osc, dummy, desc->parent);
> +		if (IS_ERR(parent))
> +			return parent;
> +
> +		return devm_clk_hw_register_fixed_factor_parent_hw(dev,
> +				desc->name, parent,
> +				desc->fixed_factor.flags,
> +				desc->fixed_factor.mult,
> +				desc->fixed_factor.div);
> +	case AMB_CV75_CLK_PLL:
> +		parent = amb_cv75_get_parent(ccu, osc, dummy, desc->parent);
> +		if (IS_ERR(parent))
> +			return parent;
> +
> +		pll_desc.name = desc->name;
> +		pll_desc.parent = parent;
> +		memcpy(pll_desc.reg_offset, desc->pll.reg_offset,
> +		       sizeof(pll_desc.reg_offset));
> +		pll_desc.soc_data = desc->pll.soc_data;
> +		pll_desc.frac_mode = false;
> +
> +		return amb_pll_register(dev, ccu->map, &pll_desc);
> +	case AMB_CV75_CLK_DIV:
> +		parent = amb_cv75_get_parent(ccu, osc, dummy, desc->parent);
> +		if (IS_ERR(parent))
> +			return parent;
> +
> +		return amb_div_register(dev, ccu->map, desc->name, parent,
> +					desc->div.reg, desc->div.shift,
> +					desc->div.width, desc->div.flags,
> +					desc->div.fix_divider);
> +	case AMB_CV75_CLK_MUX_DIV:
> +		return amb_cv75_register_mux_div(dev, ccu, desc, osc, dummy);
> +	default:
> +		return ERR_PTR(-EINVAL);
> +	}
> +}
> +
> +static int amb_cv75_rct_probe(struct platform_device *pdev)
> +{
> +	struct amb_ccu *ccu;
> +	struct clk *osc_clk;
> +	struct clk_hw *osc, *dummy = NULL, *hw;
> +	int i;
> +
> +	ccu = amb_ccu_init(pdev, ARRAY_SIZE(cv75_clks));
> +	if (IS_ERR(ccu))
> +		return PTR_ERR(ccu);
> +
> +	osc_clk = devm_clk_get(&pdev->dev, "osc");
> +	if (IS_ERR(osc_clk))
> +		return dev_err_probe(&pdev->dev, PTR_ERR(osc_clk),
> +				     "missing osc clock\n");
> +	osc = __clk_get_hw(osc_clk);

If you properly declare your parent_data, you will not have to do this.
just use fw_name in the parent data.

> +
> +	for (i = 0; i < ARRAY_SIZE(cv75_clks); i++) {
> +		hw = amb_cv75_register_clk(&pdev->dev, ccu, &cv75_clks[i],
> +					   osc, dummy);
> +		if (IS_ERR(hw))
> +			return dev_err_probe(&pdev->dev, PTR_ERR(hw),
> +					     "failed to register %s\n",
> +					     cv75_clks[i].name);
> +
> +		if (cv75_clks[i].id == AMB_CV75_CLK_REF_DUMMY)
> +			dummy = hw;
> +		else
> +			ccu->data->hws[cv75_clks[i].id] = hw;
> +	}
> +
> +	return amb_ccu_register(ccu);
> +}

Overall, the controller seems a bit overcomplicated to register 4 clocks.

> +
> +static const struct of_device_id amb_cv75_rct_match[] = {
> +	{ .compatible = "ambarella,cv75-rct" },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, amb_cv75_rct_match);
> +
> +static struct platform_driver amb_cv75_rct_driver = {
> +	.probe	= amb_cv75_rct_probe,
> +	.driver = {
> +		.name = "ambarella-cv75-rct",
> +		.of_match_table = amb_cv75_rct_match,
> +	},
> +};
> +module_platform_driver(amb_cv75_rct_driver);
> +
> +MODULE_AUTHOR("Ambarella Inc.");
> +MODULE_DESCRIPTION("Ambarella CV75 RCT clock controller");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/clk/ambarella/ccu_common.c b/drivers/clk/ambarella/ccu_common.c
> new file mode 100644
> index 000000000000..78587c5f2ce1
> --- /dev/null
> +++ b/drivers/clk/ambarella/ccu_common.c
> @@ -0,0 +1,60 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2026 Ambarella, Inc.
> + */
> +
> +#include <linux/clk-provider.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +
> +#include "ccu_common.h"
> +
> +static const struct regmap_config amb_rct_regmap_config = {
> +	.reg_bits = 32,
> +	.val_bits = 32,
> +	.reg_stride = 4,
> +	.max_register = AMB_RCT_REG_SIZE - 4,
> +};
> +
> +struct amb_ccu *amb_ccu_init(struct platform_device *pdev,
> +			     unsigned int num_clks)
> +{
> +	struct amb_ccu *ccu;
> +	void __iomem *base;
> +
> +	if (!num_clks)
> +		return ERR_PTR(-EINVAL);
> +
> +	ccu = devm_kzalloc(&pdev->dev, sizeof(*ccu), GFP_KERNEL);
> +	if (!ccu)
> +		return ERR_PTR(-ENOMEM);
> +
> +	ccu->dev = &pdev->dev;
> +
> +	ccu->data = devm_kzalloc(&pdev->dev,
> +				 struct_size(ccu->data, hws, num_clks),
> +				 GFP_KERNEL);
> +	if (!ccu->data)
> +		return ERR_PTR(-ENOMEM);
> +
> +	ccu->data->num = num_clks;
> +
> +	base = devm_platform_ioremap_resource(pdev, 0);
> +	if (IS_ERR(base))
> +		return ERR_CAST(base);
> +
> +	ccu->map = devm_regmap_init_mmio(&pdev->dev, base,
> +					 &amb_rct_regmap_config);
> +	if (IS_ERR(ccu->map))
> +		return ERR_CAST(ccu->map);
> +
> +	return ccu;
> +}
> +
> +int amb_ccu_register(struct amb_ccu *ccu)
> +{
> +	return devm_of_clk_add_hw_provider(ccu->dev, of_clk_hw_onecell_get,
> +					   ccu->data);
> +}
> diff --git a/drivers/clk/ambarella/ccu_common.h b/drivers/clk/ambarella/ccu_common.h
> new file mode 100644
> index 000000000000..f2bf9d1f06d7
> --- /dev/null
> +++ b/drivers/clk/ambarella/ccu_common.h
> @@ -0,0 +1,25 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (C) 2026 Ambarella, Inc.
> + */
> +
> +#ifndef __CCU_COMMON_H
> +#define __CCU_COMMON_H
> +
> +#include <linux/clk-provider.h>
> +#include <linux/device.h>
> +#include <linux/regmap.h>
> +
> +#define AMB_RCT_REG_SIZE	0x1000
> +
> +struct amb_ccu {
> +	struct device *dev;
> +	struct regmap *map;
> +	struct clk_hw_onecell_data *data;
> +};
> +
> +struct amb_ccu *amb_ccu_init(struct platform_device *pdev,
> +			     unsigned int num_clks);
> +int amb_ccu_register(struct amb_ccu *ccu);
> +
> +#endif
> diff --git a/drivers/clk/ambarella/ccu_mux_div.c b/drivers/clk/ambarella/ccu_mux_div.c
> new file mode 100644
> index 000000000000..d5259bc347e4
> --- /dev/null
> +++ b/drivers/clk/ambarella/ccu_mux_div.c
> @@ -0,0 +1,234 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2026 Ambarella, Inc.
> + *
> + * Regmap-backed mux + divider, derived from the vendor composite-clock driver.
> + */
> +
> +#include <linux/clk-provider.h>
> +#include <linux/device.h>
> +#include <linux/math64.h>
> +#include <linux/regmap.h>
> +
> +#include "ccu_mux_div.h"
> +
> +struct amb_mux {
> +	struct clk_hw hw;
> +	struct regmap *map;
> +	u32 offset;
> +	u32 mask;
> +	u32 shift;
> +};
> +
> +struct amb_div {
> +	struct clk_hw hw;
> +	struct regmap *map;
> +	u32 offset;
> +	u32 shift;
> +	u32 width;
> +	u32 flags;
> +	u32 fix_divider;
> +};

Looks like these a really independent entities, so they don't really
need to be mixed.

> +
> +#define to_amb_mux(_hw)	container_of(_hw, struct amb_mux, hw)
> +#define to_amb_div(_hw)	container_of(_hw, struct amb_div, hw)
> +
> +static u8 amb_mux_get_parent(struct clk_hw *hw)
> +{
> +	struct amb_mux *mux = to_amb_mux(hw);
> +	u32 val;
> +
> +	regmap_read(mux->map, mux->offset, &val);
> +	val >>= mux->shift;
> +	val &= mux->mask;
> +
> +	return val;
> +}
> +
> +static int amb_mux_set_parent(struct clk_hw *hw, u8 index)
> +{
> +	struct amb_mux *mux = to_amb_mux(hw);
> +
> +	return regmap_update_bits(mux->map, mux->offset,
> +				  mux->mask << mux->shift,
> +				  index << mux->shift);
> +}
> +
> +static const struct clk_ops amb_mux_ops = {
> +	.get_parent = amb_mux_get_parent,
> +	.set_parent = amb_mux_set_parent,
> +};
> +
> +static unsigned long amb_div_recalc_rate(struct clk_hw *hw,
> +					 unsigned long parent_rate)
> +{
> +	struct amb_div *div = to_amb_div(hw);
> +	unsigned long rate;
> +	u32 val;
> +
> +	regmap_read(div->map, div->offset, &val);
> +
> +	/* Divider reset/disable bit sits above the field at shift+width. */
> +	if (val & (BIT(div->width) << div->shift))

field_get() ?

> +		return 0;

AFAIU, this divider gates at 0. You need to implement the enable/disable
ops. returning 0 rate on a gated clock is wrong. You'll probably need to
cache the divider value when the clock is gated.

> +
> +	val >>= div->shift;
> +	val &= clk_div_mask(div->width);
> +
> +	rate = divider_recalc_rate(hw, parent_rate, val, NULL,
> +				   div->flags, div->width);
> +	if (div->fix_divider)
> +		rate = div_u64(rate, div->fix_divider);
> +
> +	return rate;
> +}
> +
> +static int amb_div_determine_rate(struct clk_hw *hw,
> +				  struct clk_rate_request *req)
> +{
> +	struct amb_div *div = to_amb_div(hw);
> +	struct clk_rate_request scaled = *req;
> +	int ret;
> +
> +	if (!req->rate && (div->flags & CLK_DIVIDER_ONE_BASED))

You did not check that flag before ? do you need it or not ?

> +		return 0;

this is wrong

> +
> +	if (div->fix_divider) {
> +		scaled.rate = min(req->rate,
> +				  ULONG_MAX / div->fix_divider) *
> +			      div->fix_divider;
> +		scaled.min_rate = min(req->min_rate,
> +				      ULONG_MAX / div->fix_divider) *
> +				  div->fix_divider;
> +		scaled.max_rate = min(req->max_rate,
> +				      ULONG_MAX / div->fix_divider) *
> +				  div->fix_divider;
> +	}

What is this ?

> +
> +	ret = divider_determine_rate(hw, &scaled, NULL, div->width,
> +				     div->flags);
> +	if (ret)
> +		return ret;
> +
> +	req->rate = scaled.rate;
> +	req->best_parent_rate = scaled.best_parent_rate;
> +	req->best_parent_hw = scaled.best_parent_hw;
> +	if (div->fix_divider)
> +		req->rate = div_u64(req->rate, div->fix_divider);
> +

?

> +	return 0;
> +}
> +
> +static int amb_div_set_rate(struct clk_hw *hw, unsigned long rate,
> +			    unsigned long parent_rate)
> +{
> +	struct amb_div *div = to_amb_div(hw);
> +	int val, mask;
> +
> +	if (div->fix_divider)
> +		rate *= div->fix_divider;
> +

Your controller register with fix_divider = 1 ... I don't really get the
point ?

> +	if (!rate) {
> +		/* Assert the reset/disable bit above the divider field. */
> +		val = BIT(div->width);
> +		mask = clk_div_mask(div->width + 1);

Hijacking set_rate() to gate.

> +	} else {
> +		val = divider_get_val(rate, parent_rate, NULL,
> +				      div->width, div->flags);
> +		if (val < 0)
> +			return val;
> +
> +		/*
> +		 * Include the disable bit in the mask so a later non-zero
> +		 * set_rate clears a previous rate==0 disable assert.
> +		 */
> +		mask = clk_div_mask(div->width + 1);
> +	}
> +
> +	regmap_update_bits(div->map, div->offset, mask << div->shift,
> +			   val << div->shift);
> +
> +	/*
> +	 * Non-ONE_BASED dividers use bit 0 as a write-enable strobe. Skip the
> +	 * pulse when shift == 0 so we do not corrupt the divider field itself.
> +	 */
> +	if (!(div->flags & CLK_DIVIDER_ONE_BASED) && div->shift) {
> +		regmap_update_bits(div->map, div->offset, BIT(0), BIT(0));
> +		regmap_update_bits(div->map, div->offset, BIT(0), 0);
> +	}

Looks like you could simplify this A LOT by just have a regular gate on
this bit and splitting it out on its own clk_hw rather than mixing it
with the rest

> +
> +	return 0;
> +}
> +
> +static const struct clk_ops amb_div_ops = {
> +	.recalc_rate = amb_div_recalc_rate,
> +	.determine_rate = amb_div_determine_rate,
> +	.set_rate = amb_div_set_rate,
> +};
> +
> +struct clk_hw *amb_div_register(struct device *dev, struct regmap *map,
> +				const char *name, const struct clk_hw *parent,
> +				u32 div_reg, u32 div_shift, u32 div_width,
> +				u32 div_flags, u32 fix_divider)
> +{
> +	struct amb_div *div;
> +	struct clk_init_data init = {};
> +	int ret;
> +
> +	div = devm_kzalloc(dev, sizeof(*div), GFP_KERNEL);
> +	if (!div)
> +		return ERR_PTR(-ENOMEM);
> +
> +	div->map = map;
> +	div->offset = div_reg;
> +	div->shift = div_shift;
> +	div->width = div_width;
> +	div->flags = div_flags;
> +	div->fix_divider = fix_divider;
> +
> +	init.name = name;
> +	init.ops = &amb_div_ops;
> +	init.parent_hws = &parent;
> +	init.num_parents = 1;
> +
> +	div->hw.init = &init;
> +
> +	ret = devm_clk_hw_register(dev, &div->hw);
> +	if (ret)
> +		return ERR_PTR(ret);
> +
> +	return &div->hw;
> +}
> +
> +struct clk_hw *amb_mux_div_register(struct device *dev, struct regmap *map,
> +				    const struct amb_mux_div_desc *desc,
> +				    const struct clk_parent_data *parent_data)
> +{
> +	struct amb_mux *mux;
> +	struct amb_div *div;
> +
> +	mux = devm_kzalloc(dev, sizeof(*mux), GFP_KERNEL);
> +	div = devm_kzalloc(dev, sizeof(*div), GFP_KERNEL);
> +	if (!mux || !div)
> +		return ERR_PTR(-ENOMEM);
> +
> +	mux->map = map;
> +	mux->offset = desc->mux_reg;
> +	mux->shift = desc->mux_shift;
> +	mux->mask = desc->mux_mask;
> +
> +	div->map = map;
> +	div->offset = desc->div_reg;
> +	div->shift = desc->div_shift;
> +	div->width = desc->div_width;
> +	div->flags = desc->div_flags;
> +	div->fix_divider = desc->fix_divider;
> +
> +	return devm_clk_hw_register_composite_pdata(dev, desc->name,
> +						    parent_data,
> +						    desc->num_parents,
> +						    &mux->hw, &amb_mux_ops,
> +						    &div->hw, &amb_div_ops,
> +						    NULL, NULL,
> +						    CLK_SET_RATE_NO_REPARENT);

So none of your mux will ever reparent ? really ? Looks like a rather
specific mux driver. That is likely a flag your controller should pass
on when appropriate

> +}
> diff --git a/drivers/clk/ambarella/ccu_mux_div.h b/drivers/clk/ambarella/ccu_mux_div.h
> new file mode 100644
> index 000000000000..abaefff1201a
> --- /dev/null
> +++ b/drivers/clk/ambarella/ccu_mux_div.h
> @@ -0,0 +1,35 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (C) 2026 Ambarella, Inc.
> + */
> +
> +#ifndef __CCU_MUX_DIV_H
> +#define __CCU_MUX_DIV_H
> +
> +#include <linux/clk-provider.h>
> +#include <linux/regmap.h>
> +
> +struct amb_mux_div_desc {
> +	const char *name;
> +	const int *parents;
> +	u8 num_parents;
> +	u32 mux_reg;
> +	u32 mux_shift;
> +	u32 mux_mask;
> +	u32 div_reg;
> +	u32 div_shift;
> +	u32 div_width;
> +	u32 div_flags;
> +	u32 fix_divider;
> +};
> +
> +struct clk_hw *amb_mux_div_register(struct device *dev, struct regmap *map,
> +				    const struct amb_mux_div_desc *desc,
> +				    const struct clk_parent_data *parent_data);
> +
> +struct clk_hw *amb_div_register(struct device *dev, struct regmap *map,
> +				const char *name, const struct clk_hw *parent,
> +				u32 div_reg, u32 div_shift, u32 div_width,
> +				u32 div_flags, u32 fix_divider);
> +
> +#endif
> diff --git a/drivers/clk/ambarella/ccu_pll.c b/drivers/clk/ambarella/ccu_pll.c
> new file mode 100644
> index 000000000000..76452003bb0d
> --- /dev/null
> +++ b/drivers/clk/ambarella/ccu_pll.c
> @@ -0,0 +1,421 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2026 Ambarella, Inc.
> + */
> +
> +#include <linux/clk-provider.h>
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/math64.h>
> +#include <linux/rational.h>
> +#include <linux/regmap.h>
> +#include <linux/slab.h>
> +
> +#include "ccu_pll.h"
> +
> +#define AMB_PLL_MAX_SOUT	16UL
> +#define AMB_PLL_MAX_SDIV	16UL
> +
> +struct amb_pll {
> +	struct clk_hw hw;
> +	struct regmap *map;
> +	u32 reg_offset[REG_NUM];
> +	const struct amb_pll_soc_data *soc_data;
> +	u32 fix_divider;
> +	bool frac_mode;
> +};
> +
> +#define to_amb_pll(_hw) container_of(_hw, struct amb_pll, hw)
> +
> +static void amb_pll_write_enable(struct regmap *map, u32 offset, u32 val)
> +{
> +	regmap_write(map, offset, val);
> +	regmap_write(map, offset, val | CTRL_WRITE_ENABLE);
> +	regmap_write(map, offset, val);
> +}
> +
> +static unsigned long amb_pll_calc_vco(struct amb_pll *pll,
> +				      unsigned long parent_rate)
> +{
> +	const struct amb_pll_soc_data *soc_data = pll->soc_data;
> +	u32 *reg = pll->reg_offset;
> +	u32 pre_scaler = 1;
> +	u32 ctrl_val, ctrl2_val = 0, frac_val;
> +	u32 intp, sdiv, vcodiv, fsdiv;
> +	u64 frac = 0, vco;
> +
> +	if (reg[PRES_OFFSET]) {
> +		regmap_read(pll->map, reg[PRES_OFFSET], &pre_scaler);
> +		pre_scaler = (pre_scaler >> 4) + 1;
> +	}
> +
> +	regmap_read(pll->map, reg[CTRL_OFFSET], &ctrl_val);
> +	intp = ((ctrl_val >> 24) & 0x7f) + 1;
> +	sdiv = ((ctrl_val >> 12) & 0xf) + 1;

Magic numbers ?

> +
> +	if (soc_data->pll_version >= 2) {
> +		vcodiv = (ctrl_val & soc_data->vcodiv_mask) ==
> +			 soc_data->vcodiv_val ? 2 : 1;
> +		fsdiv = (ctrl_val & soc_data->fsdiv_mask) ==
> +			soc_data->fsdiv_val ? 2 : 1;
> +	} else {
> +		regmap_read(pll->map, reg[CTRL2_OFFSET], &ctrl2_val);
> +		vcodiv = (ctrl2_val & soc_data->vcodiv_mask) ==
> +			 soc_data->vcodiv_val ? 2 : 1;
> +		fsdiv = (ctrl2_val & soc_data->fsdiv_mask) ==
> +			soc_data->fsdiv_val ? 2 : 1;
> +	}

Looks like a copy of a vendor driver. Please clean it up. You do not
even have this version 2 ATM. This whole table thing seems rather
overcomplicated for what it does. Just define the field you need along
with their offsets. You won't have to have if clause like that then

> +
> +	vco = (u64)parent_rate * vcodiv * fsdiv * intp * sdiv;
> +	vco = div_u64(vco, pre_scaler);
> +
> +	if (ctrl_val & CTRL_FRAC_MODE) {
> +		regmap_read(pll->map, reg[FRAC_OFFSET], &frac_val);
> +		frac = (u64)parent_rate * vcodiv * fsdiv * sdiv * frac_val;
> +		frac = div_u64(frac, pre_scaler) >> 32;
> +	}
> +
> +	return vco + frac;
> +}
> +
> +static unsigned long amb_pll_recalc_rate(struct clk_hw *hw,
> +					 unsigned long parent_rate)
> +{
> +	struct amb_pll *pll = to_amb_pll(hw);
> +	const struct amb_pll_soc_data *soc_data = pll->soc_data;
> +	u32 *reg = pll->reg_offset;
> +	u32 pre_scaler = 1, post_scaler = 1;
> +	u32 ctrl_val, ctrl2_val = 0;
> +	u32 vcodiv, fsout, sout;
> +	u64 rate;
> +
> +	regmap_read(pll->map, reg[CTRL_OFFSET], &ctrl_val);
> +	if (ctrl_val & (CTRL_POWER_DOWN | CTRL_HALT_VCO | CTRL_FORCE_RESET))
> +		return 0;
> +
> +	if (reg[PRES_OFFSET]) {
> +		regmap_read(pll->map, reg[PRES_OFFSET], &pre_scaler);
> +		pre_scaler = (pre_scaler >> 4) + 1;
> +	}
> +
> +	if (reg[POST_OFFSET]) {
> +		regmap_read(pll->map, reg[POST_OFFSET], &post_scaler);
> +		post_scaler = (post_scaler >> 4) + 1;
> +	}
> +
> +	if (ctrl_val & CTRL_BYPASS)
> +		return parent_rate / pre_scaler / post_scaler;
> +
> +	if (soc_data->pll_version >= 2) {
> +		vcodiv = (ctrl_val & soc_data->vcodiv_mask) ==
> +			 soc_data->vcodiv_val ? 2 : 1;
> +		fsout = (ctrl_val & soc_data->fsout_mask) ==
> +			soc_data->fsout_val ? 2 : 1;
> +	} else {
> +		regmap_read(pll->map, reg[CTRL2_OFFSET], &ctrl2_val);
> +		vcodiv = (ctrl2_val & soc_data->vcodiv_mask) ==
> +			 soc_data->vcodiv_val ? 2 : 1;
> +		fsout = (ctrl2_val & soc_data->fsout_mask) ==
> +			soc_data->fsout_val ? 2 : 1;
> +	}
> +
> +	sout = ((ctrl_val >> 16) & 0xf) + 1;
> +	rate = amb_pll_calc_vco(pll, parent_rate);
> +
> +	if (soc_data->pll_version >= 2) {
> +		if (!(ctrl_val & CTRL_BYPASS_HSDIV))
> +			rate = div_u64(rate, vcodiv * fsout * sout);
> +	} else {
> +		if (!(ctrl2_val & CTRL2_BYPASS_HSDIV))
> +			rate = div_u64(rate, vcodiv * fsout * sout);
> +	}
> +
> +	rate = div_u64(rate, pll->fix_divider * post_scaler);
> +
> +	return rate;
> +}
> +
> +static int amb_pll_calc_params(struct amb_pll *pll, unsigned long rate,
> +			       unsigned long parent_rate, u32 ctrl2_val,
> +			       u32 *intp, u32 *sdiv, u32 *sout,
> +			       u32 *vcodiv, u32 *fsdiv, u32 *fsout)
> +{
> +	const struct amb_pll_soc_data *soc_data = pll->soc_data;
> +	unsigned long max_numerator, max_denominator;
> +	unsigned long intp_ul, sout_ul, rate_tmp, parent_scaled;
> +	u32 ctrl_val;
> +
> +	if (!parent_rate)
> +		return -EINVAL;
> +
> +	*sdiv = 1;
> +
> +	if (soc_data->pll_version >= 2) {
> +		ctrl_val = 0;
> +		*vcodiv = (ctrl_val & soc_data->vcodiv_mask) ==
> +			  soc_data->vcodiv_val ? 2 : 1;
> +		*fsdiv = (ctrl_val & soc_data->fsdiv_mask) ==
> +			 soc_data->fsdiv_val ? 2 : 1;
> +		*fsout = (ctrl_val & soc_data->fsout_mask) ==
> +			 soc_data->fsout_val ? 2 : 1;
> +	} else {
> +		*vcodiv = (ctrl2_val & soc_data->vcodiv_mask) ==
> +			  soc_data->vcodiv_val ? 2 : 1;
> +		*fsdiv = (ctrl2_val & soc_data->fsdiv_mask) ==
> +			 soc_data->fsdiv_val ? 2 : 1;
> +		*fsout = (ctrl2_val & soc_data->fsout_mask) ==
> +			 soc_data->fsout_val ? 2 : 1;
> +	}
> +
> +	if (rate < parent_rate)
> +		return -EINVAL;
> +
> +	max_numerator = soc_data->vco_max_mhz;
> +	max_numerator = div_u64(max_numerator * 1000000ULL, parent_rate);
> +	max_numerator = div_u64(max_numerator, *vcodiv * *fsdiv);
> +	max_numerator = min(128UL, max_numerator);
> +	if (!max_numerator)
> +		return -EINVAL;
> +
> +	/*
> +	 * Approximate intp/sout against the fsdiv/fsout-scaled parent so the
> +	 * initial guess matches the hardware frequency equation.
> +	 */
> +	max_denominator = AMB_PLL_MAX_SOUT;
> +	parent_scaled = parent_rate * *fsdiv / *fsout;
> +	if (!parent_scaled)
> +		return -EINVAL;
> +	rate_tmp = rate;
> +	rational_best_approximation(rate_tmp, parent_scaled, max_numerator,
> +				    max_denominator, &intp_ul, &sout_ul);
> +
> +	while (parent_rate * *fsdiv * intp_ul * *sdiv / *fsout / sout_ul >
> +	       rate) {
> +		unsigned long resolution = parent_scaled / AMB_PLL_MAX_SOUT;
> +
> +		/* Avoid an infinite loop when parent_rate < AMB_PLL_MAX_SOUT. */
> +		if (!resolution)
> +			resolution = 1;
> +
> +		if (rate_tmp <= resolution)
> +			return -EINVAL;
> +
> +		rate_tmp -= resolution;
> +		rational_best_approximation(rate_tmp, parent_scaled,
> +					    max_numerator, max_denominator,
> +					    &intp_ul, &sout_ul);
> +	}
> +
> +	while (parent_rate / 1000000 * *vcodiv * *fsdiv * intp_ul * *sdiv <
> +	       soc_data->vco_min_mhz) {
> +		if (sout_ul > 8 || intp_ul > 64)
> +			break;
> +
> +		intp_ul *= 2;
> +		sout_ul *= 2;
> +	}
> +
> +	if (intp_ul > max_numerator || sout_ul > max_denominator ||
> +	    *sdiv > AMB_PLL_MAX_SDIV)
> +		return -EINVAL;
> +
> +	*intp = intp_ul;
> +	*sout = sout_ul;
> +
> +	return 0;
> +}
> +
> +static int amb_pll_determine_rate(struct clk_hw *hw,
> +				  struct clk_rate_request *req)
> +{
> +	struct amb_pll *pll = to_amb_pll(hw);
> +	const struct amb_pll_soc_data *soc_data = pll->soc_data;
> +	u32 ctrl2_val = 0;
> +	u32 intp, sdiv, sout, vcodiv, fsdiv, fsout;
> +	unsigned long rate;
> +	u64 rounded;
> +	int ret;
> +
> +	if (!req->best_parent_rate)
> +		return -EINVAL;
> +
> +	if (!req->rate || pll->frac_mode)
> +		return 0;
> +
> +	if (req->rate > ULONG_MAX / pll->fix_divider)
> +		return -ERANGE;
> +
> +	rate = req->rate * pll->fix_divider;
> +	if (soc_data->ctrl2_val)
> +		ctrl2_val = soc_data->ctrl2_val;
> +	else
> +		regmap_read(pll->map, pll->reg_offset[CTRL2_OFFSET],
> +			    &ctrl2_val);
> +
> +	ret = amb_pll_calc_params(pll, rate, req->best_parent_rate,
> +				  ctrl2_val, &intp, &sdiv, &sout, &vcodiv,
> +				  &fsdiv, &fsout);
> +	if (ret)
> +		return ret;
> +
> +	rounded = (u64)req->best_parent_rate * fsdiv * intp * sdiv;
> +	rounded = div64_u64(rounded, (u64)fsout * sout);
> +	rounded = div64_u64(rounded, pll->fix_divider);
> +	if (rounded < req->min_rate || rounded > req->max_rate)
> +		return -EINVAL;
> +
> +	req->rate = rounded;
> +
> +	return 0;
> +}
> +
> +static int amb_pll_set_rate(struct clk_hw *hw, unsigned long rate,
> +			    unsigned long parent_rate)
> +{
> +	struct amb_pll *pll = to_amb_pll(hw);
> +	const struct amb_pll_soc_data *soc_data = pll->soc_data;
> +	u32 *reg = pll->reg_offset;
> +	u32 ctrl_val, ctrl2_val = 0, ctrl3_val, frac_val = 0;
> +	u32 intp, sdiv, sout, vcodiv, fsdiv, fsout;
> +	unsigned long old_rate, new_rate, rate_tmp;
> +	int ret;
> +
> +	if (!rate) {
> +		regmap_read(pll->map, reg[CTRL_OFFSET], &ctrl_val);
> +		ctrl_val |= CTRL_POWER_DOWN | CTRL_HALT_VCO;
> +		amb_pll_write_enable(pll->map, reg[CTRL_OFFSET], ctrl_val);
> +		return 0;
> +	}
> +
> +	if (!parent_rate)
> +		return -EINVAL;
> +
> +	rate *= pll->fix_divider;
> +
> +	if (soc_data->ctrl2_val)
> +		ctrl2_val = soc_data->ctrl2_val;
> +	else
> +		regmap_read(pll->map, reg[CTRL2_OFFSET], &ctrl2_val);
> +
> +	ret = amb_pll_calc_params(pll, rate, parent_rate, ctrl2_val,
> +				  &intp, &sdiv, &sout, &vcodiv, &fsdiv,
> +				  &fsout);
> +	if (ret)
> +		return ret;
> +
> +	if (soc_data->ctrl2_val)
> +		regmap_write(pll->map, reg[CTRL2_OFFSET], soc_data->ctrl2_val);
> +
> +	ctrl_val = ((intp - 1) & 0x7f) << 24;
> +	ctrl_val |= ((sdiv - 1) & 0xf) << 12;
> +	ctrl_val |= ((sout - 1) & 0xf) << 16;
> +	if (soc_data->pll_version >= 2) {
> +		ctrl_val |= vcodiv == 2 ? soc_data->vcodiv_val : 0;
> +		ctrl_val |= fsdiv == 2 ? soc_data->fsdiv_val : 0;
> +		ctrl_val |= fsout == 2 ? soc_data->fsout_val : 0;
> +	}
> +
> +	regmap_write(pll->map, reg[CTRL_OFFSET], ctrl_val);
> +	regmap_write(pll->map, reg[FRAC_OFFSET], 0);
> +
> +	old_rate = amb_pll_recalc_rate(hw, parent_rate) * pll->fix_divider;
> +	rate_tmp = old_rate > rate ? 0 : rate - old_rate;
> +	if (rate_tmp && pll->frac_mode) {
> +		u64 dividend, divider;
> +
> +		dividend = (u64)rate_tmp * sout * fsout;
> +		dividend <<= 32;
> +		divider = (u64)sdiv * fsdiv * parent_rate;
> +		frac_val = DIV64_U64_ROUND_CLOSEST(dividend, divider);
> +		regmap_write(pll->map, reg[FRAC_OFFSET], frac_val);
> +		ctrl_val |= CTRL_FRAC_MODE;
> +	}
> +
> +	if (soc_data->pll_version >= 2) {
> +		ctrl3_val = soc_data->ctrl3_val;
> +		regmap_write(pll->map, reg[CTRL3_OFFSET],
> +			     ctrl3_val | CTRL3_VCO_CLAMP);
> +		regmap_write(pll->map, reg[CTRL_OFFSET],
> +			     ctrl_val | CTRL_FORCE_RESET);
> +		ndelay(100);
> +		regmap_write(pll->map, reg[CTRL_OFFSET],
> +			     ctrl_val & ~CTRL_FORCE_RESET);
> +		ndelay(100);
> +		regmap_write(pll->map, reg[CTRL3_OFFSET],
> +			     ctrl3_val & ~CTRL3_VCO_CLAMP);
> +	} else {
> +		u32 fvco_mhz, range;
> +
> +		fvco_mhz = amb_pll_calc_vco(pll, parent_rate) / 1000000UL;
> +		for (range = 0; range < ARRAY_SIZE(soc_data->vco_range);
> +		     range++) {
> +			if (fvco_mhz > soc_data->vco_range[range])
> +				break;
> +		}
> +		if (range == ARRAY_SIZE(soc_data->vco_range))
> +			range = 0;
> +		else
> +			range = ARRAY_SIZE(soc_data->vco_range) - range - 1;
> +
> +		regmap_read(pll->map, reg[CTRL3_OFFSET], &ctrl3_val);
> +		ctrl3_val &= ~CTRL3_VCO_RANGE_MASK;
> +		ctrl3_val |= range << 1;
> +		regmap_write(pll->map, reg[CTRL3_OFFSET], ctrl3_val);
> +
> +		if (frac_val) {
> +			ctrl_val |= CTRL_FORCE_RESET;
> +			amb_pll_write_enable(pll->map, reg[CTRL_OFFSET],
> +					     ctrl_val);
> +		}
> +
> +		ctrl_val &= ~CTRL_FORCE_RESET;
> +		amb_pll_write_enable(pll->map, reg[CTRL_OFFSET], ctrl_val);
> +	}
> +
> +	new_rate = amb_pll_recalc_rate(hw, parent_rate);
> +	rate_tmp = rate / pll->fix_divider;
> +	if (max(new_rate, rate_tmp) - min(new_rate, rate_tmp) > 10)
> +		pr_warn("%s: requested %lu, got %lu\n", clk_hw_get_name(hw),
> +			rate_tmp, new_rate);
> +
> +	return 0;
> +}
> +
> +static const struct clk_ops amb_pll_ops = {
> +	.recalc_rate = amb_pll_recalc_rate,
> +	.determine_rate = amb_pll_determine_rate,
> +	.set_rate = amb_pll_set_rate,
> +};
> +
> +struct clk_hw *amb_pll_register(struct device *dev, struct regmap *map,
> +				const struct amb_pll_desc *desc)
> +{
> +	struct amb_pll *pll;
> +	struct clk_init_data init = {};
> +	const struct clk_hw *parent = desc->parent;
> +	int ret;
> +
> +	pll = devm_kzalloc(dev, sizeof(*pll), GFP_KERNEL);
> +	if (!pll)
> +		return ERR_PTR(-ENOMEM);
> +
> +	pll->map = map;
> +	memcpy(pll->reg_offset, desc->reg_offset, sizeof(pll->reg_offset));
> +	pll->soc_data = desc->soc_data;
> +	pll->fix_divider = 1;
> +	pll->frac_mode = desc->frac_mode;
> +
> +	init.name = desc->name;
> +	init.ops = &amb_pll_ops;
> +	init.flags = CLK_GET_RATE_NOCACHE | CLK_IS_CRITICAL;
> +	init.parent_hws = &parent;
> +	init.num_parents = 1;
> +
> +	pll->hw.init = &init;
> +
> +	ret = devm_clk_hw_register(dev, &pll->hw);
> +	if (ret)
> +		return ERR_PTR(ret);
> +
> +	return &pll->hw;
> +}
> diff --git a/drivers/clk/ambarella/ccu_pll.h b/drivers/clk/ambarella/ccu_pll.h
> new file mode 100644
> index 000000000000..6d901e88f410
> --- /dev/null
> +++ b/drivers/clk/ambarella/ccu_pll.h
> @@ -0,0 +1,70 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (C) 2026 Ambarella, Inc.
> + */
> +
> +#ifndef __CCU_PLL_H
> +#define __CCU_PLL_H
> +
> +#include <linux/bits.h>
> +#include <linux/clk-provider.h>
> +#include <linux/device.h>
> +#include <linux/regmap.h>
> +#include <linux/types.h>
> +
> +enum {
> +	CTRL_OFFSET = 0,
> +	FRAC_OFFSET,
> +	CTRL2_OFFSET,
> +	CTRL3_OFFSET,
> +	PRES_OFFSET,
> +	POST_OFFSET,
> +	REG_NUM,
> +};
> +
> +#define CTRL_BYPASS		BIT(2)
> +#define CTRL_WRITE_ENABLE	BIT(0)
> +#define CTRL_FRAC_MODE		BIT(3)
> +#define CTRL_FORCE_RESET	BIT(4)
> +#define CTRL_POWER_DOWN		BIT(5)
> +#define CTRL_HALT_VCO		BIT(6)
> +#define CTRL_VCODIV_DIV2	BIT(8)
> +#define CTRL_FSDIV_DIV2		BIT(9)
> +#define CTRL_FSOUT_DIV2		BIT(10)
> +#define CTRL_BYPASS_HSDIV	BIT(11)
> +
> +#define CTRL2_VCODIV_DIV2	BIT(8)
> +#define CTRL2_FSDIV_DIV2	BIT(9)
> +#define CTRL2_FSOUT_DIV2	BIT(11)
> +#define CTRL2_BYPASS_HSDIV	BIT(12)
> +
> +#define CTRL3_VCO_RANGE_MASK	0x6
> +#define CTRL3_VCO_CLAMP		0x8
> +
> +struct amb_pll_soc_data {
> +	u32 pll_version;
> +	u32 fsout_mask;
> +	u32 fsout_val;
> +	u32 fsdiv_mask;
> +	u32 fsdiv_val;
> +	u32 vcodiv_mask;
> +	u32 vcodiv_val;
> +	u32 vco_max_mhz;
> +	u32 vco_min_mhz;
> +	u32 vco_range[4];
> +	u32 ctrl2_val;
> +	u32 ctrl3_val;
> +};
> +
> +struct amb_pll_desc {
> +	const char *name;
> +	const struct clk_hw *parent;
> +	u32 reg_offset[REG_NUM];
> +	const struct amb_pll_soc_data *soc_data;
> +	bool frac_mode;
> +};
> +
> +struct clk_hw *amb_pll_register(struct device *dev, struct regmap *map,
> +				const struct amb_pll_desc *desc);
> +
> +#endif
>
> -- 
> 2.34.1
>
>

-- 
Jerome


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

* Re:Re: [PATCH v6 07/13] clk: ambarella: add CV75 CCU driver
  2026-09-04  7:44   ` Jerome Brunet
@ 2026-09-04  9:21     ` zl020895
  0 siblings, 0 replies; 28+ messages in thread
From: zl020895 @ 2026-09-04  9:21 UTC (permalink / raw)
  To: Jerome Brunet
  Cc: Long Zhao via B4 Relay, Arnd Bergmann, Krzysztof Kozlowski,
	Alexandre Belloni, soc, linux-arm-kernel, Long Zhao, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
	Stephen Boyd, Linus Walleij, Bartosz Golaszewski,
	Greg Kroah-Hartman, Jiri Slaby, Andy Shevchenko,
	Ilpo Järvinen, Catalin Marinas, Will Deacon, Lee Jones, mfd,
	devicetree, linux-clk, linux-gpio, linux-serial, linux-kernel

Hi Jerome,

Thanks for the review.

The split into common/pll/mux_div was meant to leave room for more RCT
clocks later, but I agree that for this bring-up series (only core/ahb/
apb/uart0) the controller is heavier than it needs to be.

I will simplify it in v7 and address the issues you raised.

Thanks,
Long

At 2026-09-04 15:44:33, "Jerome Brunet" <jbrunet@baylibre.com> wrote:
>On ven. 04 sept. 2026 at 14:38, Long Zhao via B4 Relay <devnull+longzhao.ambarella.com@kernel.org> wrote:
>
>> From: Long Zhao <longzhao@ambarella.com>
>>
>> Add the Ambarella CV75 clock driver covering the core PLL and the
>> UART0 / AHB / APB clocks needed for early console bring-up.
>>
>> Signed-off-by: Long Zhao <longzhao@ambarella.com>
>> ---
>>  drivers/clk/Kconfig                 |   1 +
>>  drivers/clk/Makefile                |   1 +
>>  drivers/clk/ambarella/Kconfig       |  13 ++
>>  drivers/clk/ambarella/Makefile      |   6 +
>>  drivers/clk/ambarella/ccu-cv75.c    | 296 +++++++++++++++++++++++++
>>  drivers/clk/ambarella/ccu_common.c  |  60 +++++
>>  drivers/clk/ambarella/ccu_common.h  |  25 +++
>>  drivers/clk/ambarella/ccu_mux_div.c | 234 ++++++++++++++++++++
>>  drivers/clk/ambarella/ccu_mux_div.h |  35 +++
>>  drivers/clk/ambarella/ccu_pll.c     | 421 ++++++++++++++++++++++++++++++++++++
>>  drivers/clk/ambarella/ccu_pll.h     |  70 ++++++
>>  11 files changed, 1162 insertions(+)
>
>Again seems like this could be split. AFAICT, you are adding 2 clock
>driver and a controller
>
>>
>> diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
>> index 1717ce75a907..fbbf4963716c 100644
>> --- a/drivers/clk/Kconfig
>> +++ b/drivers/clk/Kconfig
>> @@ -506,6 +506,7 @@ config COMMON_CLK_RPMI
>>  	  the RISC-V platform management interface (RPMI) specification.
>>  
>>  source "drivers/clk/actions/Kconfig"
>> +source "drivers/clk/ambarella/Kconfig"
>>  source "drivers/clk/analogbits/Kconfig"
>>  source "drivers/clk/aspeed/Kconfig"
>>  source "drivers/clk/bcm/Kconfig"
>> diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
>> index cc108a75a900..30d823ee606f 100644
>> --- a/drivers/clk/Makefile
>> +++ b/drivers/clk/Makefile
>> @@ -112,6 +112,7 @@ obj-$(CONFIG_COMMON_CLK_XGENE)		+= clk-xgene.o
>>  
>>  # please keep this section sorted lexicographically by directory path name
>>  obj-y					+= actions/
>> +obj-y					+= ambarella/
>>  obj-y					+= analogbits/
>>  obj-y					+= aspeed/
>>  obj-$(CONFIG_COMMON_CLK_AT91)		+= at91/
>> diff --git a/drivers/clk/ambarella/Kconfig b/drivers/clk/ambarella/Kconfig
>> new file mode 100644
>> index 000000000000..d79bb4bd6df5
>> --- /dev/null
>> +++ b/drivers/clk/ambarella/Kconfig
>> @@ -0,0 +1,13 @@
>> +# SPDX-License-Identifier: GPL-2.0-only
>> +
>> +config CLK_AMBARELLA_CV75
>> +	bool "Ambarella CV75 RCT clock controller"
>> +	depends on ARCH_AMBARELLA || COMPILE_TEST
>> +	select CLK_AMBARELLA_CCU
>> +	default ARCH_AMBARELLA
>> +	help
>> +	  Say Y to enable the Ambarella CV75 RCT clock controller.
>> +
>> +config CLK_AMBARELLA_CCU
>> +	bool
>> +	select REGMAP_MMIO
>> diff --git a/drivers/clk/ambarella/Makefile b/drivers/clk/ambarella/Makefile
>> new file mode 100644
>> index 000000000000..36e96326ea97
>> --- /dev/null
>> +++ b/drivers/clk/ambarella/Makefile
>> @@ -0,0 +1,6 @@
>> +#
>> +# Makefile for ambarella specific clk
>> +#
>> +
>> +obj-$(CONFIG_CLK_AMBARELLA_CCU) += ccu_common.o ccu_mux_div.o ccu_pll.o
>> +obj-$(CONFIG_CLK_AMBARELLA_CV75) += ccu-cv75.o
>> diff --git a/drivers/clk/ambarella/ccu-cv75.c b/drivers/clk/ambarella/ccu-cv75.c
>> new file mode 100644
>> index 000000000000..c4828be598be
>> --- /dev/null
>> +++ b/drivers/clk/ambarella/ccu-cv75.c
>> @@ -0,0 +1,296 @@
>> +// SPDX-License-Identifier: GPL-2.0-only
>> +/*
>> + * Copyright (C) 2026 Ambarella, Inc.
>> + *
>> + * CV75 RCT clock controller for boot clocks.
>> + */
>> +
>> +#include <linux/clk-provider.h>
>> +#include <linux/clk.h>
>> +#include <linux/module.h>
>> +#include <linux/of.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/slab.h>
>> +
>> +#include <dt-bindings/clock/ambarella,cv75-rct.h>
>> +
>> +#include "ccu_common.h"
>> +#include "ccu_mux_div.h"
>> +#include "ccu_pll.h"
>> +
>> +enum amb_cv75_clk_type {
>> +	AMB_CV75_CLK_FIXED_RATE,
>> +	AMB_CV75_CLK_FIXED_FACTOR,
>> +	AMB_CV75_CLK_PLL,
>> +	AMB_CV75_CLK_DIV,
>> +	AMB_CV75_CLK_MUX_DIV,
>> +};
>> +
>> +enum amb_cv75_clk_ref {
>> +	AMB_CV75_CLK_REF_OSC = -1,
>> +	AMB_CV75_CLK_REF_DUMMY = -2,
>
>This is fishy, needs a comment at least
>
>> +};
>> +
>> +/* PLL version for CV75 (ambarella,clkpll-v1 in vendor DTS) */
>> +static const struct amb_pll_soc_data cv75_pll_soc_data = {
>> +	.pll_version	= 1,
>> +	.fsout_mask	= CTRL2_FSOUT_DIV2,
>> +	.fsout_val	= CTRL2_FSOUT_DIV2,
>> +	.fsdiv_mask	= CTRL2_FSDIV_DIV2,
>> +	.fsdiv_val	= CTRL2_FSDIV_DIV2,
>> +	.vcodiv_mask	= CTRL2_VCODIV_DIV2,
>> +	.vcodiv_val	= CTRL2_VCODIV_DIV2,
>> +	.vco_max_mhz	= 2600UL,
>> +	.vco_min_mhz	= 850UL,
>> +	.vco_range	= { 1800UL, 1400UL, 1100UL, 0UL },
>> +};
>> +
>> +struct amb_cv75_clk_desc {
>> +	int id;
>> +	enum amb_cv75_clk_type type;
>> +	const char *name;
>> +	int parent;
>> +
>> +	union {
>> +		struct {
>> +			unsigned long rate;
>> +		} fixed_rate;
>> +		struct {
>> +			unsigned long flags;
>> +			unsigned int mult;
>> +			unsigned int div;
>> +		} fixed_factor;
>> +		struct {
>> +			u32 reg_offset[REG_NUM];
>> +			const struct amb_pll_soc_data *soc_data;
>> +		} pll;
>> +		struct {
>> +			u32 reg;
>> +			u32 shift;
>> +			u32 width;
>> +			u32 flags;
>> +			u32 fix_divider;
>> +		} div;
>> +		struct amb_mux_div_desc mux_div;
>> +	};
>> +};
>> +
>> +static const struct amb_cv75_clk_desc cv75_clks[] = {
>> +	{
>> +		.id = AMB_CV75_CLK_REF_DUMMY,
>> +		.type = AMB_CV75_CLK_FIXED_RATE,
>> +		.name = "dummy",
>> +		.fixed_rate.rate = 0,
>> +	},
>
>A clock that comes out of nowhere with a rate 0 is not OK.
>
>> +	{
>> +		.id = CV75_GCLK_CORE,
>> +		.type = AMB_CV75_CLK_PLL,
>> +		.name = "core",
>> +		.parent = AMB_CV75_CLK_REF_OSC,
>> +		.pll = {
>> +			.reg_offset = {
>> +				0x000, 0x004, 0x100,
>> +				0x104, 0x000, 0x000,
>> +			},
>> +			.soc_data = &cv75_pll_soc_data,
>> +		},
>> +	},
>> +	{
>> +		.id = CV75_GCLK_AHB,
>> +		.type = AMB_CV75_CLK_FIXED_FACTOR,
>> +		.name = "ahb",
>> +		.parent = CV75_GCLK_CORE,
>> +		.fixed_factor = {
>> +			.flags = 0,
>> +			.mult = 1,
>> +			.div = 2,
>> +		},
>> +	},
>> +	{
>> +		.id = CV75_GCLK_APB,
>> +		.type = AMB_CV75_CLK_FIXED_FACTOR,
>> +		.name = "apb",
>> +		.parent = CV75_GCLK_CORE,
>> +		.fixed_factor = {
>> +			.flags = 0,
>> +			.mult = 1,
>> +			.div = 4,
>> +		},
>> +	},
>> +	{
>> +		.id = CV75_GCLK_UART0,
>> +		.type = AMB_CV75_CLK_MUX_DIV,
>> +		.name = "uart0",
>> +		.mux_div = {
>> +			.name = "uart0",
>> +			.parents = (const int[]) {
>> +				AMB_CV75_CLK_REF_OSC, CV75_GCLK_CORE,
>> +				AMB_CV75_CLK_REF_DUMMY, AMB_CV75_CLK_REF_DUMMY,
>> +			},
>> +			.num_parents = 4,
>> +			.mux_reg = 0x1c8,
>> +			.mux_shift = 0,
>> +			.mux_mask = 0x3,
>> +			.div_reg = 0x038,
>> +			.div_shift = 0,
>> +			.div_width = 24,
>> +			.div_flags = CLK_DIVIDER_ONE_BASED,
>> +			.fix_divider = 1,
>> +		},
>> +	},
>> +};
>> +
>> +static struct clk_hw *amb_cv75_get_parent(struct amb_ccu *ccu,
>> +					  struct clk_hw *osc,
>> +					  struct clk_hw *dummy,
>> +					  int parent)
>> +{
>> +	if (parent == AMB_CV75_CLK_REF_OSC)
>> +		return osc;
>> +	if (parent == AMB_CV75_CLK_REF_DUMMY)
>> +		return dummy;
>> +
>> +	if (parent < 0 || parent >= ccu->data->num)
>> +		return ERR_PTR(-EINVAL);
>> +
>> +	if (!ccu->data->hws[parent])
>> +		return ERR_PTR(-EPROBE_DEFER);
>> +
>> +	return ccu->data->hws[parent];
>> +}
>> +
>> +static struct clk_hw *amb_cv75_register_mux_div(struct device *dev,
>> +						struct amb_ccu *ccu,
>> +						const struct amb_cv75_clk_desc *desc,
>> +						struct clk_hw *osc,
>> +						struct clk_hw *dummy)
>> +{
>> +	const struct amb_mux_div_desc *md = &desc->mux_div;
>> +	struct clk_parent_data *pdata;
>> +	struct clk_hw *parent;
>> +	u8 i;
>> +
>> +	pdata = devm_kcalloc(dev, md->num_parents, sizeof(*pdata), GFP_KERNEL);
>> +	if (!pdata)
>> +		return ERR_PTR(-ENOMEM);
>> +
>> +	for (i = 0; i < md->num_parents; i++) {
>> +		if (md->parents[i] == AMB_CV75_CLK_REF_OSC) {
>> +			pdata[i].fw_name = "osc";
>> +			continue;
>> +		}
>> +
>> +		parent = amb_cv75_get_parent(ccu, osc, dummy, md->parents[i]);
>> +		if (IS_ERR(parent))
>> +			return parent;
>> +
>> +		pdata[i].hw = parent;
>> +	}
>> +
>> +	return amb_mux_div_register(dev, ccu->map, md, pdata);
>> +}
>> +
>> +static struct clk_hw *amb_cv75_register_clk(struct device *dev,
>> +					    struct amb_ccu *ccu,
>> +					    const struct amb_cv75_clk_desc *desc,
>> +					    struct clk_hw *osc,
>> +					    struct clk_hw *dummy)
>> +{
>> +	struct amb_pll_desc pll_desc;
>> +	struct clk_hw *parent;
>> +
>> +	switch (desc->type) {
>> +	case AMB_CV75_CLK_FIXED_RATE:
>> +		return devm_clk_hw_register_fixed_rate(dev, desc->name, NULL, 0,
>> +						       desc->fixed_rate.rate);
>> +	case AMB_CV75_CLK_FIXED_FACTOR:
>> +		parent = amb_cv75_get_parent(ccu, osc, dummy, desc->parent);
>> +		if (IS_ERR(parent))
>> +			return parent;
>> +
>> +		return devm_clk_hw_register_fixed_factor_parent_hw(dev,
>> +				desc->name, parent,
>> +				desc->fixed_factor.flags,
>> +				desc->fixed_factor.mult,
>> +				desc->fixed_factor.div);
>> +	case AMB_CV75_CLK_PLL:
>> +		parent = amb_cv75_get_parent(ccu, osc, dummy, desc->parent);
>> +		if (IS_ERR(parent))
>> +			return parent;
>> +
>> +		pll_desc.name = desc->name;
>> +		pll_desc.parent = parent;
>> +		memcpy(pll_desc.reg_offset, desc->pll.reg_offset,
>> +		       sizeof(pll_desc.reg_offset));
>> +		pll_desc.soc_data = desc->pll.soc_data;
>> +		pll_desc.frac_mode = false;
>> +
>> +		return amb_pll_register(dev, ccu->map, &pll_desc);
>> +	case AMB_CV75_CLK_DIV:
>> +		parent = amb_cv75_get_parent(ccu, osc, dummy, desc->parent);
>> +		if (IS_ERR(parent))
>> +			return parent;
>> +
>> +		return amb_div_register(dev, ccu->map, desc->name, parent,
>> +					desc->div.reg, desc->div.shift,
>> +					desc->div.width, desc->div.flags,
>> +					desc->div.fix_divider);
>> +	case AMB_CV75_CLK_MUX_DIV:
>> +		return amb_cv75_register_mux_div(dev, ccu, desc, osc, dummy);
>> +	default:
>> +		return ERR_PTR(-EINVAL);
>> +	}
>> +}
>> +
>> +static int amb_cv75_rct_probe(struct platform_device *pdev)
>> +{
>> +	struct amb_ccu *ccu;
>> +	struct clk *osc_clk;
>> +	struct clk_hw *osc, *dummy = NULL, *hw;
>> +	int i;
>> +
>> +	ccu = amb_ccu_init(pdev, ARRAY_SIZE(cv75_clks));
>> +	if (IS_ERR(ccu))
>> +		return PTR_ERR(ccu);
>> +
>> +	osc_clk = devm_clk_get(&pdev->dev, "osc");
>> +	if (IS_ERR(osc_clk))
>> +		return dev_err_probe(&pdev->dev, PTR_ERR(osc_clk),
>> +				     "missing osc clock\n");
>> +	osc = __clk_get_hw(osc_clk);
>
>If you properly declare your parent_data, you will not have to do this.
>just use fw_name in the parent data.
>
>> +
>> +	for (i = 0; i < ARRAY_SIZE(cv75_clks); i++) {
>> +		hw = amb_cv75_register_clk(&pdev->dev, ccu, &cv75_clks[i],
>> +					   osc, dummy);
>> +		if (IS_ERR(hw))
>> +			return dev_err_probe(&pdev->dev, PTR_ERR(hw),
>> +					     "failed to register %s\n",
>> +					     cv75_clks[i].name);
>> +
>> +		if (cv75_clks[i].id == AMB_CV75_CLK_REF_DUMMY)
>> +			dummy = hw;
>> +		else
>> +			ccu->data->hws[cv75_clks[i].id] = hw;
>> +	}
>> +
>> +	return amb_ccu_register(ccu);
>> +}
>
>Overall, the controller seems a bit overcomplicated to register 4 clocks.
>
>> +
>> +static const struct of_device_id amb_cv75_rct_match[] = {
>> +	{ .compatible = "ambarella,cv75-rct" },
>> +	{ }
>> +};
>> +MODULE_DEVICE_TABLE(of, amb_cv75_rct_match);
>> +
>> +static struct platform_driver amb_cv75_rct_driver = {
>> +	.probe	= amb_cv75_rct_probe,
>> +	.driver = {
>> +		.name = "ambarella-cv75-rct",
>> +		.of_match_table = amb_cv75_rct_match,
>> +	},
>> +};
>> +module_platform_driver(amb_cv75_rct_driver);
>> +
>> +MODULE_AUTHOR("Ambarella Inc.");
>> +MODULE_DESCRIPTION("Ambarella CV75 RCT clock controller");
>> +MODULE_LICENSE("GPL");
>> diff --git a/drivers/clk/ambarella/ccu_common.c b/drivers/clk/ambarella/ccu_common.c
>> new file mode 100644
>> index 000000000000..78587c5f2ce1
>> --- /dev/null
>> +++ b/drivers/clk/ambarella/ccu_common.c
>> @@ -0,0 +1,60 @@
>> +// SPDX-License-Identifier: GPL-2.0-only
>> +/*
>> + * Copyright (C) 2026 Ambarella, Inc.
>> + */
>> +
>> +#include <linux/clk-provider.h>
>> +#include <linux/module.h>
>> +#include <linux/of.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/regmap.h>
>> +
>> +#include "ccu_common.h"
>> +
>> +static const struct regmap_config amb_rct_regmap_config = {
>> +	.reg_bits = 32,
>> +	.val_bits = 32,
>> +	.reg_stride = 4,
>> +	.max_register = AMB_RCT_REG_SIZE - 4,
>> +};
>> +
>> +struct amb_ccu *amb_ccu_init(struct platform_device *pdev,
>> +			     unsigned int num_clks)
>> +{
>> +	struct amb_ccu *ccu;
>> +	void __iomem *base;
>> +
>> +	if (!num_clks)
>> +		return ERR_PTR(-EINVAL);
>> +
>> +	ccu = devm_kzalloc(&pdev->dev, sizeof(*ccu), GFP_KERNEL);
>> +	if (!ccu)
>> +		return ERR_PTR(-ENOMEM);
>> +
>> +	ccu->dev = &pdev->dev;
>> +
>> +	ccu->data = devm_kzalloc(&pdev->dev,
>> +				 struct_size(ccu->data, hws, num_clks),
>> +				 GFP_KERNEL);
>> +	if (!ccu->data)
>> +		return ERR_PTR(-ENOMEM);
>> +
>> +	ccu->data->num = num_clks;
>> +
>> +	base = devm_platform_ioremap_resource(pdev, 0);
>> +	if (IS_ERR(base))
>> +		return ERR_CAST(base);
>> +
>> +	ccu->map = devm_regmap_init_mmio(&pdev->dev, base,
>> +					 &amb_rct_regmap_config);
>> +	if (IS_ERR(ccu->map))
>> +		return ERR_CAST(ccu->map);
>> +
>> +	return ccu;
>> +}
>> +
>> +int amb_ccu_register(struct amb_ccu *ccu)
>> +{
>> +	return devm_of_clk_add_hw_provider(ccu->dev, of_clk_hw_onecell_get,
>> +					   ccu->data);
>> +}
>> diff --git a/drivers/clk/ambarella/ccu_common.h b/drivers/clk/ambarella/ccu_common.h
>> new file mode 100644
>> index 000000000000..f2bf9d1f06d7
>> --- /dev/null
>> +++ b/drivers/clk/ambarella/ccu_common.h
>> @@ -0,0 +1,25 @@
>> +/* SPDX-License-Identifier: GPL-2.0-only */
>> +/*
>> + * Copyright (C) 2026 Ambarella, Inc.
>> + */
>> +
>> +#ifndef __CCU_COMMON_H
>> +#define __CCU_COMMON_H
>> +
>> +#include <linux/clk-provider.h>
>> +#include <linux/device.h>
>> +#include <linux/regmap.h>
>> +
>> +#define AMB_RCT_REG_SIZE	0x1000
>> +
>> +struct amb_ccu {
>> +	struct device *dev;
>> +	struct regmap *map;
>> +	struct clk_hw_onecell_data *data;
>> +};
>> +
>> +struct amb_ccu *amb_ccu_init(struct platform_device *pdev,
>> +			     unsigned int num_clks);
>> +int amb_ccu_register(struct amb_ccu *ccu);
>> +
>> +#endif
>> diff --git a/drivers/clk/ambarella/ccu_mux_div.c b/drivers/clk/ambarella/ccu_mux_div.c
>> new file mode 100644
>> index 000000000000..d5259bc347e4
>> --- /dev/null
>> +++ b/drivers/clk/ambarella/ccu_mux_div.c
>> @@ -0,0 +1,234 @@
>> +// SPDX-License-Identifier: GPL-2.0-only
>> +/*
>> + * Copyright (C) 2026 Ambarella, Inc.
>> + *
>> + * Regmap-backed mux + divider, derived from the vendor composite-clock driver.
>> + */
>> +
>> +#include <linux/clk-provider.h>
>> +#include <linux/device.h>
>> +#include <linux/math64.h>
>> +#include <linux/regmap.h>
>> +
>> +#include "ccu_mux_div.h"
>> +
>> +struct amb_mux {
>> +	struct clk_hw hw;
>> +	struct regmap *map;
>> +	u32 offset;
>> +	u32 mask;
>> +	u32 shift;
>> +};
>> +
>> +struct amb_div {
>> +	struct clk_hw hw;
>> +	struct regmap *map;
>> +	u32 offset;
>> +	u32 shift;
>> +	u32 width;
>> +	u32 flags;
>> +	u32 fix_divider;
>> +};
>
>Looks like these a really independent entities, so they don't really
>need to be mixed.
>
>> +
>> +#define to_amb_mux(_hw)	container_of(_hw, struct amb_mux, hw)
>> +#define to_amb_div(_hw)	container_of(_hw, struct amb_div, hw)
>> +
>> +static u8 amb_mux_get_parent(struct clk_hw *hw)
>> +{
>> +	struct amb_mux *mux = to_amb_mux(hw);
>> +	u32 val;
>> +
>> +	regmap_read(mux->map, mux->offset, &val);
>> +	val >>= mux->shift;
>> +	val &= mux->mask;
>> +
>> +	return val;
>> +}
>> +
>> +static int amb_mux_set_parent(struct clk_hw *hw, u8 index)
>> +{
>> +	struct amb_mux *mux = to_amb_mux(hw);
>> +
>> +	return regmap_update_bits(mux->map, mux->offset,
>> +				  mux->mask << mux->shift,
>> +				  index << mux->shift);
>> +}
>> +
>> +static const struct clk_ops amb_mux_ops = {
>> +	.get_parent = amb_mux_get_parent,
>> +	.set_parent = amb_mux_set_parent,
>> +};
>> +
>> +static unsigned long amb_div_recalc_rate(struct clk_hw *hw,
>> +					 unsigned long parent_rate)
>> +{
>> +	struct amb_div *div = to_amb_div(hw);
>> +	unsigned long rate;
>> +	u32 val;
>> +
>> +	regmap_read(div->map, div->offset, &val);
>> +
>> +	/* Divider reset/disable bit sits above the field at shift+width. */
>> +	if (val & (BIT(div->width) << div->shift))
>
>field_get() ?
>
>> +		return 0;
>
>AFAIU, this divider gates at 0. You need to implement the enable/disable
>ops. returning 0 rate on a gated clock is wrong. You'll probably need to
>cache the divider value when the clock is gated.
>
>> +
>> +	val >>= div->shift;
>> +	val &= clk_div_mask(div->width);
>> +
>> +	rate = divider_recalc_rate(hw, parent_rate, val, NULL,
>> +				   div->flags, div->width);
>> +	if (div->fix_divider)
>> +		rate = div_u64(rate, div->fix_divider);
>> +
>> +	return rate;
>> +}
>> +
>> +static int amb_div_determine_rate(struct clk_hw *hw,
>> +				  struct clk_rate_request *req)
>> +{
>> +	struct amb_div *div = to_amb_div(hw);
>> +	struct clk_rate_request scaled = *req;
>> +	int ret;
>> +
>> +	if (!req->rate && (div->flags & CLK_DIVIDER_ONE_BASED))
>
>You did not check that flag before ? do you need it or not ?
>
>> +		return 0;
>
>this is wrong
>
>> +
>> +	if (div->fix_divider) {
>> +		scaled.rate = min(req->rate,
>> +				  ULONG_MAX / div->fix_divider) *
>> +			      div->fix_divider;
>> +		scaled.min_rate = min(req->min_rate,
>> +				      ULONG_MAX / div->fix_divider) *
>> +				  div->fix_divider;
>> +		scaled.max_rate = min(req->max_rate,
>> +				      ULONG_MAX / div->fix_divider) *
>> +				  div->fix_divider;
>> +	}
>
>What is this ?
>
>> +
>> +	ret = divider_determine_rate(hw, &scaled, NULL, div->width,
>> +				     div->flags);
>> +	if (ret)
>> +		return ret;
>> +
>> +	req->rate = scaled.rate;
>> +	req->best_parent_rate = scaled.best_parent_rate;
>> +	req->best_parent_hw = scaled.best_parent_hw;
>> +	if (div->fix_divider)
>> +		req->rate = div_u64(req->rate, div->fix_divider);
>> +
>
>?
>
>> +	return 0;
>> +}
>> +
>> +static int amb_div_set_rate(struct clk_hw *hw, unsigned long rate,
>> +			    unsigned long parent_rate)
>> +{
>> +	struct amb_div *div = to_amb_div(hw);
>> +	int val, mask;
>> +
>> +	if (div->fix_divider)
>> +		rate *= div->fix_divider;
>> +
>
>Your controller register with fix_divider = 1 ... I don't really get the
>point ?
>
>> +	if (!rate) {
>> +		/* Assert the reset/disable bit above the divider field. */
>> +		val = BIT(div->width);
>> +		mask = clk_div_mask(div->width + 1);
>
>Hijacking set_rate() to gate.
>
>> +	} else {
>> +		val = divider_get_val(rate, parent_rate, NULL,
>> +				      div->width, div->flags);
>> +		if (val < 0)
>> +			return val;
>> +
>> +		/*
>> +		 * Include the disable bit in the mask so a later non-zero
>> +		 * set_rate clears a previous rate==0 disable assert.
>> +		 */
>> +		mask = clk_div_mask(div->width + 1);
>> +	}
>> +
>> +	regmap_update_bits(div->map, div->offset, mask << div->shift,
>> +			   val << div->shift);
>> +
>> +	/*
>> +	 * Non-ONE_BASED dividers use bit 0 as a write-enable strobe. Skip the
>> +	 * pulse when shift == 0 so we do not corrupt the divider field itself.
>> +	 */
>> +	if (!(div->flags & CLK_DIVIDER_ONE_BASED) && div->shift) {
>> +		regmap_update_bits(div->map, div->offset, BIT(0), BIT(0));
>> +		regmap_update_bits(div->map, div->offset, BIT(0), 0);
>> +	}
>
>Looks like you could simplify this A LOT by just have a regular gate on
>this bit and splitting it out on its own clk_hw rather than mixing it
>with the rest
>
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct clk_ops amb_div_ops = {
>> +	.recalc_rate = amb_div_recalc_rate,
>> +	.determine_rate = amb_div_determine_rate,
>> +	.set_rate = amb_div_set_rate,
>> +};
>> +
>> +struct clk_hw *amb_div_register(struct device *dev, struct regmap *map,
>> +				const char *name, const struct clk_hw *parent,
>> +				u32 div_reg, u32 div_shift, u32 div_width,
>> +				u32 div_flags, u32 fix_divider)
>> +{
>> +	struct amb_div *div;
>> +	struct clk_init_data init = {};
>> +	int ret;
>> +
>> +	div = devm_kzalloc(dev, sizeof(*div), GFP_KERNEL);
>> +	if (!div)
>> +		return ERR_PTR(-ENOMEM);
>> +
>> +	div->map = map;
>> +	div->offset = div_reg;
>> +	div->shift = div_shift;
>> +	div->width = div_width;
>> +	div->flags = div_flags;
>> +	div->fix_divider = fix_divider;
>> +
>> +	init.name = name;
>> +	init.ops = &amb_div_ops;
>> +	init.parent_hws = &parent;
>> +	init.num_parents = 1;
>> +
>> +	div->hw.init = &init;
>> +
>> +	ret = devm_clk_hw_register(dev, &div->hw);
>> +	if (ret)
>> +		return ERR_PTR(ret);
>> +
>> +	return &div->hw;
>> +}
>> +
>> +struct clk_hw *amb_mux_div_register(struct device *dev, struct regmap *map,
>> +				    const struct amb_mux_div_desc *desc,
>> +				    const struct clk_parent_data *parent_data)
>> +{
>> +	struct amb_mux *mux;
>> +	struct amb_div *div;
>> +
>> +	mux = devm_kzalloc(dev, sizeof(*mux), GFP_KERNEL);
>> +	div = devm_kzalloc(dev, sizeof(*div), GFP_KERNEL);
>> +	if (!mux || !div)
>> +		return ERR_PTR(-ENOMEM);
>> +
>> +	mux->map = map;
>> +	mux->offset = desc->mux_reg;
>> +	mux->shift = desc->mux_shift;
>> +	mux->mask = desc->mux_mask;
>> +
>> +	div->map = map;
>> +	div->offset = desc->div_reg;
>> +	div->shift = desc->div_shift;
>> +	div->width = desc->div_width;
>> +	div->flags = desc->div_flags;
>> +	div->fix_divider = desc->fix_divider;
>> +
>> +	return devm_clk_hw_register_composite_pdata(dev, desc->name,
>> +						    parent_data,
>> +						    desc->num_parents,
>> +						    &mux->hw, &amb_mux_ops,
>> +						    &div->hw, &amb_div_ops,
>> +						    NULL, NULL,
>> +						    CLK_SET_RATE_NO_REPARENT);
>
>So none of your mux will ever reparent ? really ? Looks like a rather
>specific mux driver. That is likely a flag your controller should pass
>on when appropriate
>
>> +}
>> diff --git a/drivers/clk/ambarella/ccu_mux_div.h b/drivers/clk/ambarella/ccu_mux_div.h
>> new file mode 100644
>> index 000000000000..abaefff1201a
>> --- /dev/null
>> +++ b/drivers/clk/ambarella/ccu_mux_div.h
>> @@ -0,0 +1,35 @@
>> +/* SPDX-License-Identifier: GPL-2.0-only */
>> +/*
>> + * Copyright (C) 2026 Ambarella, Inc.
>> + */
>> +
>> +#ifndef __CCU_MUX_DIV_H
>> +#define __CCU_MUX_DIV_H
>> +
>> +#include <linux/clk-provider.h>
>> +#include <linux/regmap.h>
>> +
>> +struct amb_mux_div_desc {
>> +	const char *name;
>> +	const int *parents;
>> +	u8 num_parents;
>> +	u32 mux_reg;
>> +	u32 mux_shift;
>> +	u32 mux_mask;
>> +	u32 div_reg;
>> +	u32 div_shift;
>> +	u32 div_width;
>> +	u32 div_flags;
>> +	u32 fix_divider;
>> +};
>> +
>> +struct clk_hw *amb_mux_div_register(struct device *dev, struct regmap *map,
>> +				    const struct amb_mux_div_desc *desc,
>> +				    const struct clk_parent_data *parent_data);
>> +
>> +struct clk_hw *amb_div_register(struct device *dev, struct regmap *map,
>> +				const char *name, const struct clk_hw *parent,
>> +				u32 div_reg, u32 div_shift, u32 div_width,
>> +				u32 div_flags, u32 fix_divider);
>> +
>> +#endif
>> diff --git a/drivers/clk/ambarella/ccu_pll.c b/drivers/clk/ambarella/ccu_pll.c
>> new file mode 100644
>> index 000000000000..76452003bb0d
>> --- /dev/null
>> +++ b/drivers/clk/ambarella/ccu_pll.c
>> @@ -0,0 +1,421 @@
>> +// SPDX-License-Identifier: GPL-2.0-only
>> +/*
>> + * Copyright (C) 2026 Ambarella, Inc.
>> + */
>> +
>> +#include <linux/clk-provider.h>
>> +#include <linux/delay.h>
>> +#include <linux/device.h>
>> +#include <linux/math64.h>
>> +#include <linux/rational.h>
>> +#include <linux/regmap.h>
>> +#include <linux/slab.h>
>> +
>> +#include "ccu_pll.h"
>> +
>> +#define AMB_PLL_MAX_SOUT	16UL
>> +#define AMB_PLL_MAX_SDIV	16UL
>> +
>> +struct amb_pll {
>> +	struct clk_hw hw;
>> +	struct regmap *map;
>> +	u32 reg_offset[REG_NUM];
>> +	const struct amb_pll_soc_data *soc_data;
>> +	u32 fix_divider;
>> +	bool frac_mode;
>> +};
>> +
>> +#define to_amb_pll(_hw) container_of(_hw, struct amb_pll, hw)
>> +
>> +static void amb_pll_write_enable(struct regmap *map, u32 offset, u32 val)
>> +{
>> +	regmap_write(map, offset, val);
>> +	regmap_write(map, offset, val | CTRL_WRITE_ENABLE);
>> +	regmap_write(map, offset, val);
>> +}
>> +
>> +static unsigned long amb_pll_calc_vco(struct amb_pll *pll,
>> +				      unsigned long parent_rate)
>> +{
>> +	const struct amb_pll_soc_data *soc_data = pll->soc_data;
>> +	u32 *reg = pll->reg_offset;
>> +	u32 pre_scaler = 1;
>> +	u32 ctrl_val, ctrl2_val = 0, frac_val;
>> +	u32 intp, sdiv, vcodiv, fsdiv;
>> +	u64 frac = 0, vco;
>> +
>> +	if (reg[PRES_OFFSET]) {
>> +		regmap_read(pll->map, reg[PRES_OFFSET], &pre_scaler);
>> +		pre_scaler = (pre_scaler >> 4) + 1;
>> +	}
>> +
>> +	regmap_read(pll->map, reg[CTRL_OFFSET], &ctrl_val);
>> +	intp = ((ctrl_val >> 24) & 0x7f) + 1;
>> +	sdiv = ((ctrl_val >> 12) & 0xf) + 1;
>
>Magic numbers ?
>
>> +
>> +	if (soc_data->pll_version >= 2) {
>> +		vcodiv = (ctrl_val & soc_data->vcodiv_mask) ==
>> +			 soc_data->vcodiv_val ? 2 : 1;
>> +		fsdiv = (ctrl_val & soc_data->fsdiv_mask) ==
>> +			soc_data->fsdiv_val ? 2 : 1;
>> +	} else {
>> +		regmap_read(pll->map, reg[CTRL2_OFFSET], &ctrl2_val);
>> +		vcodiv = (ctrl2_val & soc_data->vcodiv_mask) ==
>> +			 soc_data->vcodiv_val ? 2 : 1;
>> +		fsdiv = (ctrl2_val & soc_data->fsdiv_mask) ==
>> +			soc_data->fsdiv_val ? 2 : 1;
>> +	}
>
>Looks like a copy of a vendor driver. Please clean it up. You do not
>even have this version 2 ATM. This whole table thing seems rather
>overcomplicated for what it does. Just define the field you need along
>with their offsets. You won't have to have if clause like that then
>
>> +
>> +	vco = (u64)parent_rate * vcodiv * fsdiv * intp * sdiv;
>> +	vco = div_u64(vco, pre_scaler);
>> +
>> +	if (ctrl_val & CTRL_FRAC_MODE) {
>> +		regmap_read(pll->map, reg[FRAC_OFFSET], &frac_val);
>> +		frac = (u64)parent_rate * vcodiv * fsdiv * sdiv * frac_val;
>> +		frac = div_u64(frac, pre_scaler) >> 32;
>> +	}
>> +
>> +	return vco + frac;
>> +}
>> +
>> +static unsigned long amb_pll_recalc_rate(struct clk_hw *hw,
>> +					 unsigned long parent_rate)
>> +{
>> +	struct amb_pll *pll = to_amb_pll(hw);
>> +	const struct amb_pll_soc_data *soc_data = pll->soc_data;
>> +	u32 *reg = pll->reg_offset;
>> +	u32 pre_scaler = 1, post_scaler = 1;
>> +	u32 ctrl_val, ctrl2_val = 0;
>> +	u32 vcodiv, fsout, sout;
>> +	u64 rate;
>> +
>> +	regmap_read(pll->map, reg[CTRL_OFFSET], &ctrl_val);
>> +	if (ctrl_val & (CTRL_POWER_DOWN | CTRL_HALT_VCO | CTRL_FORCE_RESET))
>> +		return 0;
>> +
>> +	if (reg[PRES_OFFSET]) {
>> +		regmap_read(pll->map, reg[PRES_OFFSET], &pre_scaler);
>> +		pre_scaler = (pre_scaler >> 4) + 1;
>> +	}
>> +
>> +	if (reg[POST_OFFSET]) {
>> +		regmap_read(pll->map, reg[POST_OFFSET], &post_scaler);
>> +		post_scaler = (post_scaler >> 4) + 1;
>> +	}
>> +
>> +	if (ctrl_val & CTRL_BYPASS)
>> +		return parent_rate / pre_scaler / post_scaler;
>> +
>> +	if (soc_data->pll_version >= 2) {
>> +		vcodiv = (ctrl_val & soc_data->vcodiv_mask) ==
>> +			 soc_data->vcodiv_val ? 2 : 1;
>> +		fsout = (ctrl_val & soc_data->fsout_mask) ==
>> +			soc_data->fsout_val ? 2 : 1;
>> +	} else {
>> +		regmap_read(pll->map, reg[CTRL2_OFFSET], &ctrl2_val);
>> +		vcodiv = (ctrl2_val & soc_data->vcodiv_mask) ==
>> +			 soc_data->vcodiv_val ? 2 : 1;
>> +		fsout = (ctrl2_val & soc_data->fsout_mask) ==
>> +			soc_data->fsout_val ? 2 : 1;
>> +	}
>> +
>> +	sout = ((ctrl_val >> 16) & 0xf) + 1;
>> +	rate = amb_pll_calc_vco(pll, parent_rate);
>> +
>> +	if (soc_data->pll_version >= 2) {
>> +		if (!(ctrl_val & CTRL_BYPASS_HSDIV))
>> +			rate = div_u64(rate, vcodiv * fsout * sout);
>> +	} else {
>> +		if (!(ctrl2_val & CTRL2_BYPASS_HSDIV))
>> +			rate = div_u64(rate, vcodiv * fsout * sout);
>> +	}
>> +
>> +	rate = div_u64(rate, pll->fix_divider * post_scaler);
>> +
>> +	return rate;
>> +}
>> +
>> +static int amb_pll_calc_params(struct amb_pll *pll, unsigned long rate,
>> +			       unsigned long parent_rate, u32 ctrl2_val,
>> +			       u32 *intp, u32 *sdiv, u32 *sout,
>> +			       u32 *vcodiv, u32 *fsdiv, u32 *fsout)
>> +{
>> +	const struct amb_pll_soc_data *soc_data = pll->soc_data;
>> +	unsigned long max_numerator, max_denominator;
>> +	unsigned long intp_ul, sout_ul, rate_tmp, parent_scaled;
>> +	u32 ctrl_val;
>> +
>> +	if (!parent_rate)
>> +		return -EINVAL;
>> +
>> +	*sdiv = 1;
>> +
>> +	if (soc_data->pll_version >= 2) {
>> +		ctrl_val = 0;
>> +		*vcodiv = (ctrl_val & soc_data->vcodiv_mask) ==
>> +			  soc_data->vcodiv_val ? 2 : 1;
>> +		*fsdiv = (ctrl_val & soc_data->fsdiv_mask) ==
>> +			 soc_data->fsdiv_val ? 2 : 1;
>> +		*fsout = (ctrl_val & soc_data->fsout_mask) ==
>> +			 soc_data->fsout_val ? 2 : 1;
>> +	} else {
>> +		*vcodiv = (ctrl2_val & soc_data->vcodiv_mask) ==
>> +			  soc_data->vcodiv_val ? 2 : 1;
>> +		*fsdiv = (ctrl2_val & soc_data->fsdiv_mask) ==
>> +			 soc_data->fsdiv_val ? 2 : 1;
>> +		*fsout = (ctrl2_val & soc_data->fsout_mask) ==
>> +			 soc_data->fsout_val ? 2 : 1;
>> +	}
>> +
>> +	if (rate < parent_rate)
>> +		return -EINVAL;
>> +
>> +	max_numerator = soc_data->vco_max_mhz;
>> +	max_numerator = div_u64(max_numerator * 1000000ULL, parent_rate);
>> +	max_numerator = div_u64(max_numerator, *vcodiv * *fsdiv);
>> +	max_numerator = min(128UL, max_numerator);
>> +	if (!max_numerator)
>> +		return -EINVAL;
>> +
>> +	/*
>> +	 * Approximate intp/sout against the fsdiv/fsout-scaled parent so the
>> +	 * initial guess matches the hardware frequency equation.
>> +	 */
>> +	max_denominator = AMB_PLL_MAX_SOUT;
>> +	parent_scaled = parent_rate * *fsdiv / *fsout;
>> +	if (!parent_scaled)
>> +		return -EINVAL;
>> +	rate_tmp = rate;
>> +	rational_best_approximation(rate_tmp, parent_scaled, max_numerator,
>> +				    max_denominator, &intp_ul, &sout_ul);
>> +
>> +	while (parent_rate * *fsdiv * intp_ul * *sdiv / *fsout / sout_ul >
>> +	       rate) {
>> +		unsigned long resolution = parent_scaled / AMB_PLL_MAX_SOUT;
>> +
>> +		/* Avoid an infinite loop when parent_rate < AMB_PLL_MAX_SOUT. */
>> +		if (!resolution)
>> +			resolution = 1;
>> +
>> +		if (rate_tmp <= resolution)
>> +			return -EINVAL;
>> +
>> +		rate_tmp -= resolution;
>> +		rational_best_approximation(rate_tmp, parent_scaled,
>> +					    max_numerator, max_denominator,
>> +					    &intp_ul, &sout_ul);
>> +	}
>> +
>> +	while (parent_rate / 1000000 * *vcodiv * *fsdiv * intp_ul * *sdiv <
>> +	       soc_data->vco_min_mhz) {
>> +		if (sout_ul > 8 || intp_ul > 64)
>> +			break;
>> +
>> +		intp_ul *= 2;
>> +		sout_ul *= 2;
>> +	}
>> +
>> +	if (intp_ul > max_numerator || sout_ul > max_denominator ||
>> +	    *sdiv > AMB_PLL_MAX_SDIV)
>> +		return -EINVAL;
>> +
>> +	*intp = intp_ul;
>> +	*sout = sout_ul;
>> +
>> +	return 0;
>> +}
>> +
>> +static int amb_pll_determine_rate(struct clk_hw *hw,
>> +				  struct clk_rate_request *req)
>> +{
>> +	struct amb_pll *pll = to_amb_pll(hw);
>> +	const struct amb_pll_soc_data *soc_data = pll->soc_data;
>> +	u32 ctrl2_val = 0;
>> +	u32 intp, sdiv, sout, vcodiv, fsdiv, fsout;
>> +	unsigned long rate;
>> +	u64 rounded;
>> +	int ret;
>> +
>> +	if (!req->best_parent_rate)
>> +		return -EINVAL;
>> +
>> +	if (!req->rate || pll->frac_mode)
>> +		return 0;
>> +
>> +	if (req->rate > ULONG_MAX / pll->fix_divider)
>> +		return -ERANGE;
>> +
>> +	rate = req->rate * pll->fix_divider;
>> +	if (soc_data->ctrl2_val)
>> +		ctrl2_val = soc_data->ctrl2_val;
>> +	else
>> +		regmap_read(pll->map, pll->reg_offset[CTRL2_OFFSET],
>> +			    &ctrl2_val);
>> +
>> +	ret = amb_pll_calc_params(pll, rate, req->best_parent_rate,
>> +				  ctrl2_val, &intp, &sdiv, &sout, &vcodiv,
>> +				  &fsdiv, &fsout);
>> +	if (ret)
>> +		return ret;
>> +
>> +	rounded = (u64)req->best_parent_rate * fsdiv * intp * sdiv;
>> +	rounded = div64_u64(rounded, (u64)fsout * sout);
>> +	rounded = div64_u64(rounded, pll->fix_divider);
>> +	if (rounded < req->min_rate || rounded > req->max_rate)
>> +		return -EINVAL;
>> +
>> +	req->rate = rounded;
>> +
>> +	return 0;
>> +}
>> +
>> +static int amb_pll_set_rate(struct clk_hw *hw, unsigned long rate,
>> +			    unsigned long parent_rate)
>> +{
>> +	struct amb_pll *pll = to_amb_pll(hw);
>> +	const struct amb_pll_soc_data *soc_data = pll->soc_data;
>> +	u32 *reg = pll->reg_offset;
>> +	u32 ctrl_val, ctrl2_val = 0, ctrl3_val, frac_val = 0;
>> +	u32 intp, sdiv, sout, vcodiv, fsdiv, fsout;
>> +	unsigned long old_rate, new_rate, rate_tmp;
>> +	int ret;
>> +
>> +	if (!rate) {
>> +		regmap_read(pll->map, reg[CTRL_OFFSET], &ctrl_val);
>> +		ctrl_val |= CTRL_POWER_DOWN | CTRL_HALT_VCO;
>> +		amb_pll_write_enable(pll->map, reg[CTRL_OFFSET], ctrl_val);
>> +		return 0;
>> +	}
>> +
>> +	if (!parent_rate)
>> +		return -EINVAL;
>> +
>> +	rate *= pll->fix_divider;
>> +
>> +	if (soc_data->ctrl2_val)
>> +		ctrl2_val = soc_data->ctrl2_val;
>> +	else
>> +		regmap_read(pll->map, reg[CTRL2_OFFSET], &ctrl2_val);
>> +
>> +	ret = amb_pll_calc_params(pll, rate, parent_rate, ctrl2_val,
>> +				  &intp, &sdiv, &sout, &vcodiv, &fsdiv,
>> +				  &fsout);
>> +	if (ret)
>> +		return ret;
>> +
>> +	if (soc_data->ctrl2_val)
>> +		regmap_write(pll->map, reg[CTRL2_OFFSET], soc_data->ctrl2_val);
>> +
>> +	ctrl_val = ((intp - 1) & 0x7f) << 24;
>> +	ctrl_val |= ((sdiv - 1) & 0xf) << 12;
>> +	ctrl_val |= ((sout - 1) & 0xf) << 16;
>> +	if (soc_data->pll_version >= 2) {
>> +		ctrl_val |= vcodiv == 2 ? soc_data->vcodiv_val : 0;
>> +		ctrl_val |= fsdiv == 2 ? soc_data->fsdiv_val : 0;
>> +		ctrl_val |= fsout == 2 ? soc_data->fsout_val : 0;
>> +	}
>> +
>> +	regmap_write(pll->map, reg[CTRL_OFFSET], ctrl_val);
>> +	regmap_write(pll->map, reg[FRAC_OFFSET], 0);
>> +
>> +	old_rate = amb_pll_recalc_rate(hw, parent_rate) * pll->fix_divider;
>> +	rate_tmp = old_rate > rate ? 0 : rate - old_rate;
>> +	if (rate_tmp && pll->frac_mode) {
>> +		u64 dividend, divider;
>> +
>> +		dividend = (u64)rate_tmp * sout * fsout;
>> +		dividend <<= 32;
>> +		divider = (u64)sdiv * fsdiv * parent_rate;
>> +		frac_val = DIV64_U64_ROUND_CLOSEST(dividend, divider);
>> +		regmap_write(pll->map, reg[FRAC_OFFSET], frac_val);
>> +		ctrl_val |= CTRL_FRAC_MODE;
>> +	}
>> +
>> +	if (soc_data->pll_version >= 2) {
>> +		ctrl3_val = soc_data->ctrl3_val;
>> +		regmap_write(pll->map, reg[CTRL3_OFFSET],
>> +			     ctrl3_val | CTRL3_VCO_CLAMP);
>> +		regmap_write(pll->map, reg[CTRL_OFFSET],
>> +			     ctrl_val | CTRL_FORCE_RESET);
>> +		ndelay(100);
>> +		regmap_write(pll->map, reg[CTRL_OFFSET],
>> +			     ctrl_val & ~CTRL_FORCE_RESET);
>> +		ndelay(100);
>> +		regmap_write(pll->map, reg[CTRL3_OFFSET],
>> +			     ctrl3_val & ~CTRL3_VCO_CLAMP);
>> +	} else {
>> +		u32 fvco_mhz, range;
>> +
>> +		fvco_mhz = amb_pll_calc_vco(pll, parent_rate) / 1000000UL;
>> +		for (range = 0; range < ARRAY_SIZE(soc_data->vco_range);
>> +		     range++) {
>> +			if (fvco_mhz > soc_data->vco_range[range])
>> +				break;
>> +		}
>> +		if (range == ARRAY_SIZE(soc_data->vco_range))
>> +			range = 0;
>> +		else
>> +			range = ARRAY_SIZE(soc_data->vco_range) - range - 1;
>> +
>> +		regmap_read(pll->map, reg[CTRL3_OFFSET], &ctrl3_val);
>> +		ctrl3_val &= ~CTRL3_VCO_RANGE_MASK;
>> +		ctrl3_val |= range << 1;
>> +		regmap_write(pll->map, reg[CTRL3_OFFSET], ctrl3_val);
>> +
>> +		if (frac_val) {
>> +			ctrl_val |= CTRL_FORCE_RESET;
>> +			amb_pll_write_enable(pll->map, reg[CTRL_OFFSET],
>> +					     ctrl_val);
>> +		}
>> +
>> +		ctrl_val &= ~CTRL_FORCE_RESET;
>> +		amb_pll_write_enable(pll->map, reg[CTRL_OFFSET], ctrl_val);
>> +	}
>> +
>> +	new_rate = amb_pll_recalc_rate(hw, parent_rate);
>> +	rate_tmp = rate / pll->fix_divider;
>> +	if (max(new_rate, rate_tmp) - min(new_rate, rate_tmp) > 10)
>> +		pr_warn("%s: requested %lu, got %lu\n", clk_hw_get_name(hw),
>> +			rate_tmp, new_rate);
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct clk_ops amb_pll_ops = {
>> +	.recalc_rate = amb_pll_recalc_rate,
>> +	.determine_rate = amb_pll_determine_rate,
>> +	.set_rate = amb_pll_set_rate,
>> +};
>> +
>> +struct clk_hw *amb_pll_register(struct device *dev, struct regmap *map,
>> +				const struct amb_pll_desc *desc)
>> +{
>> +	struct amb_pll *pll;
>> +	struct clk_init_data init = {};
>> +	const struct clk_hw *parent = desc->parent;
>> +	int ret;
>> +
>> +	pll = devm_kzalloc(dev, sizeof(*pll), GFP_KERNEL);
>> +	if (!pll)
>> +		return ERR_PTR(-ENOMEM);
>> +
>> +	pll->map = map;
>> +	memcpy(pll->reg_offset, desc->reg_offset, sizeof(pll->reg_offset));
>> +	pll->soc_data = desc->soc_data;
>> +	pll->fix_divider = 1;
>> +	pll->frac_mode = desc->frac_mode;
>> +
>> +	init.name = desc->name;
>> +	init.ops = &amb_pll_ops;
>> +	init.flags = CLK_GET_RATE_NOCACHE | CLK_IS_CRITICAL;
>> +	init.parent_hws = &parent;
>> +	init.num_parents = 1;
>> +
>> +	pll->hw.init = &init;
>> +
>> +	ret = devm_clk_hw_register(dev, &pll->hw);
>> +	if (ret)
>> +		return ERR_PTR(ret);
>> +
>> +	return &pll->hw;
>> +}
>> diff --git a/drivers/clk/ambarella/ccu_pll.h b/drivers/clk/ambarella/ccu_pll.h
>> new file mode 100644
>> index 000000000000..6d901e88f410
>> --- /dev/null
>> +++ b/drivers/clk/ambarella/ccu_pll.h
>> @@ -0,0 +1,70 @@
>> +/* SPDX-License-Identifier: GPL-2.0-only */
>> +/*
>> + * Copyright (C) 2026 Ambarella, Inc.
>> + */
>> +
>> +#ifndef __CCU_PLL_H
>> +#define __CCU_PLL_H
>> +
>> +#include <linux/bits.h>
>> +#include <linux/clk-provider.h>
>> +#include <linux/device.h>
>> +#include <linux/regmap.h>
>> +#include <linux/types.h>
>> +
>> +enum {
>> +	CTRL_OFFSET = 0,
>> +	FRAC_OFFSET,
>> +	CTRL2_OFFSET,
>> +	CTRL3_OFFSET,
>> +	PRES_OFFSET,
>> +	POST_OFFSET,
>> +	REG_NUM,
>> +};
>> +
>> +#define CTRL_BYPASS		BIT(2)
>> +#define CTRL_WRITE_ENABLE	BIT(0)
>> +#define CTRL_FRAC_MODE		BIT(3)
>> +#define CTRL_FORCE_RESET	BIT(4)
>> +#define CTRL_POWER_DOWN		BIT(5)
>> +#define CTRL_HALT_VCO		BIT(6)
>> +#define CTRL_VCODIV_DIV2	BIT(8)
>> +#define CTRL_FSDIV_DIV2		BIT(9)
>> +#define CTRL_FSOUT_DIV2		BIT(10)
>> +#define CTRL_BYPASS_HSDIV	BIT(11)
>> +
>> +#define CTRL2_VCODIV_DIV2	BIT(8)
>> +#define CTRL2_FSDIV_DIV2	BIT(9)
>> +#define CTRL2_FSOUT_DIV2	BIT(11)
>> +#define CTRL2_BYPASS_HSDIV	BIT(12)
>> +
>> +#define CTRL3_VCO_RANGE_MASK	0x6
>> +#define CTRL3_VCO_CLAMP		0x8
>> +
>> +struct amb_pll_soc_data {
>> +	u32 pll_version;
>> +	u32 fsout_mask;
>> +	u32 fsout_val;
>> +	u32 fsdiv_mask;
>> +	u32 fsdiv_val;
>> +	u32 vcodiv_mask;
>> +	u32 vcodiv_val;
>> +	u32 vco_max_mhz;
>> +	u32 vco_min_mhz;
>> +	u32 vco_range[4];
>> +	u32 ctrl2_val;
>> +	u32 ctrl3_val;
>> +};
>> +
>> +struct amb_pll_desc {
>> +	const char *name;
>> +	const struct clk_hw *parent;
>> +	u32 reg_offset[REG_NUM];
>> +	const struct amb_pll_soc_data *soc_data;
>> +	bool frac_mode;
>> +};
>> +
>> +struct clk_hw *amb_pll_register(struct device *dev, struct regmap *map,
>> +				const struct amb_pll_desc *desc);
>> +
>> +#endif
>>
>> -- 
>> 2.34.1
>>
>>
>
>-- 
>Jerome

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

* Re: [PATCH v6 08/13] gpio: regmap: support write_data_after_dir and girq
  2026-09-04  6:38 ` [PATCH v6 08/13] gpio: regmap: support write_data_after_dir and girq Long Zhao via B4 Relay
@ 2026-09-04 11:55   ` Andy Shevchenko
  0 siblings, 0 replies; 28+ messages in thread
From: Andy Shevchenko @ 2026-09-04 11:55 UTC (permalink / raw)
  To: longzhao
  Cc: Arnd Bergmann, Krzysztof Kozlowski, Alexandre Belloni, soc,
	linux-arm-kernel, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Michael Turquette, Stephen Boyd, Linus Walleij,
	Bartosz Golaszewski, Greg Kroah-Hartman, Jiri Slaby,
	Ilpo Järvinen, Catalin Marinas, Will Deacon, Long Zhao,
	Lee Jones, mfd, devicetree, linux-clk, linux-gpio, linux-serial,
	linux-kernel

On Fri, Sep 04, 2026 at 02:38:15PM +0800, Long Zhao via B4 Relay wrote:

Thanks for this change, my comments below.

> Add an optional write-after-direction-output quirk for controllers
> that ignore data-register writes while a line is still configured as
> input,

The chips that require this are buggy. Are you going to fix this HW in the next
version of the SoC?

> and allow drivers to pass an existing gpio_irq_chip through
> gpio_regmap_register() so IRQ setup can stay with the caller.

This needs to be in a separate update. Also we need to understand why
it is required. The caller should be able to create the respective IRQ
domain which is important.

...

> struct gpio_regmap {
>  	unsigned int reg_dir_out_base;
>  	unsigned long *fixed_direction_mask;
>  	unsigned long *fixed_direction_output;
> +	bool write_data_after_dir;

Is `pahole` happy with the layout?

>  #ifdef CONFIG_REGMAP_IRQ
>  	int regmap_irq_line;

...

> static int gpio_regmap_direction_output(struct gpio_chip *chip,

>  	gpio_regmap_set(chip, offset, value);

^^^ (see below)

>  
> -	return gpio_regmap_set_direction(chip, offset, true);
> +	ret = gpio_regmap_set_direction(chip, offset, true);
> +	if (ret)
> +		return ret;
> +
> +	/* Some controllers ignore data writes while the line is still an input. */
> +	if (gpio->write_data_after_dir)
> +		gpio_regmap_set(chip, offset, value);

This can lead to two writes to the same register, the above needs to be
conditional as well.

-- 
With Best Regards,
Andy Shevchenko




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

* Re: [PATCH v6 09/13] gpio: pl061: use gpio-regmap and add Ambarella layout
  2026-09-04  6:38 ` [PATCH v6 09/13] gpio: pl061: use gpio-regmap and add Ambarella layout Long Zhao via B4 Relay
@ 2026-09-04 13:14   ` Andy Shevchenko
  0 siblings, 0 replies; 28+ messages in thread
From: Andy Shevchenko @ 2026-09-04 13:14 UTC (permalink / raw)
  To: longzhao
  Cc: Arnd Bergmann, Krzysztof Kozlowski, Alexandre Belloni, soc,
	linux-arm-kernel, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Michael Turquette, Stephen Boyd, Linus Walleij,
	Bartosz Golaszewski, Greg Kroah-Hartman, Jiri Slaby,
	Ilpo Järvinen, Catalin Marinas, Will Deacon, Long Zhao,
	Lee Jones, mfd, devicetree, linux-clk, linux-gpio, linux-serial,
	linux-kernel

On Fri, Sep 04, 2026 at 02:38:16PM +0800, Long Zhao via B4 Relay wrote:

> Convert PL061 data and direction handling to gpio-regmap while
> keeping the existing PL061 irqchip path.

> Add an Ambarella CV75 register layout variant on the AMBA bus, without
> claiming unverified GPIO suspend/resume support for Ambarella.

This part should be in a separate patch.

So far I see at least 2 patches against gpio-regmap and 2 patches against
gpio-pl061.

...

> +struct pl061_variant_data {
> +	unsigned int data;
> +	unsigned int dir;
> +	unsigned int is;
> +	unsigned int ibe;
> +	unsigned int iev;
> +	unsigned int ie;
> +	unsigned int ris;
> +	unsigned int mis;
> +	unsigned int ic;
> +	unsigned int mask;
> +	unsigned int enable;
> +	unsigned int ngpio;
> +	bool access_32bit;
> +	bool masked_data_address;
> +	bool write_data_after_dir;
> +	bool clear_irq_on_type;
> +	bool pm_save_restore;
> +	const struct regmap_config *regmap_config;
> +};

Should not be like this. Part of it is in the respective regmap config (with
all volatile, precious, et cetera registers, and part of it comes from driver
data (in other words based on the compatible string or other ID).

...

>  struct pl061 {
>  	raw_spinlock_t		lock;
> -

Stray change.

>  	void __iomem		*base;
> -	struct gpio_chip	gc;
> +	const struct pl061_variant_data *variant;
> +	struct gpio_irq_chip	girq;
>  	int			parent_irq;

Here (in this structure) we should get a struct regmap instead of base and
gpio_chip.

>  	struct pl061_context_save_regs csave_regs;

This is not needed, see how gpio-pca953x does that with enabled regmap cache.

>  };

...

> +static u32 pl061_read(struct pl061 *pl061, unsigned int reg)
>  {
> -	struct pl061 *pl061 = gpiochip_get_data(gc);
> -	unsigned long flags;
> -	unsigned char gpiodir;
> +	if (pl061->variant->access_32bit)
> +		return readl(pl061->base + reg);
>  
> -	raw_spin_lock_irqsave(&pl061->lock, flags);
> -	writeb(!!value << offset, pl061->base + (BIT(offset + 2)));
> -	gpiodir = readb(pl061->base + GPIODIR);
> -	gpiodir |= BIT(offset);
> -	writeb(gpiodir, pl061->base + GPIODIR);
> -
> -	/*
> -	 * gpio value is set again, because pl061 doesn't allow to set value of
> -	 * a gpio pin before configuring it in OUT mode.
> -	 */
> -	writeb(!!value << offset, pl061->base + (BIT(offset + 2)));
> -	raw_spin_unlock_irqrestore(&pl061->lock, flags);
> -
> -	return 0;
> +	return readb(pl061->base + reg);
>  }

> -static int pl061_get_value(struct gpio_chip *gc, unsigned offset)
> +static void pl061_write(struct pl061 *pl061, u32 value, unsigned int reg)
>  {
> -	struct pl061 *pl061 = gpiochip_get_data(gc);
> -
> -	return !!readb(pl061->base + (BIT(offset + 2)));
> +	if (pl061->variant->access_32bit)
> +		writel(value, pl061->base + reg);
> +	else
> +		writeb(value, pl061->base + reg);

This is achieved by different regmap config — one for 32-bit, one for 8-bit
access.

>  }

...

I guess it's enough for now. this needs one more round of designing this.

-- 
With Best Regards,
Andy Shevchenko




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

* Re: [PATCH v6 10/13] pinctrl: ambarella: add CV75 pin controller
  2026-09-04  6:38 ` [PATCH v6 10/13] pinctrl: ambarella: add CV75 pin controller Long Zhao via B4 Relay
@ 2026-09-04 13:29   ` Andy Shevchenko
  0 siblings, 0 replies; 28+ messages in thread
From: Andy Shevchenko @ 2026-09-04 13:29 UTC (permalink / raw)
  To: longzhao
  Cc: Arnd Bergmann, Krzysztof Kozlowski, Alexandre Belloni, soc,
	linux-arm-kernel, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Michael Turquette, Stephen Boyd, Linus Walleij,
	Bartosz Golaszewski, Greg Kroah-Hartman, Jiri Slaby,
	Ilpo Järvinen, Catalin Marinas, Will Deacon, Long Zhao,
	Lee Jones, mfd, devicetree, linux-clk, linux-gpio, linux-serial,
	linux-kernel

On Fri, Sep 04, 2026 at 02:38:17PM +0800, Long Zhao via B4 Relay wrote:

> Add an Ambarella pinmux-only pinctrl driver with CV75 function/group
> tables. GPIO is handled by the PL061 driver.

...

> +#include <linux/kernel.h>

Absolutely no. Follow IWYU principle in all your patches.

...

> +#define CV75_GROUP(_name)						\
> +	{								\
> +		.name = #_name,						\
> +		.pinmux = cv75_##_name##_pinmux,				\
> +		.num_pins = ARRAY_SIZE(cv75_##_name##_pinmux),		\
> +	}
> +
> +#define CV75_FUNCTION(_name)						\
> +	{								\
> +		.name = #_name,						\
> +		.groups = cv75_##_name##_groups,				\
> +		.num_groups = ARRAY_SIZE(cv75_##_name##_groups),		\
> +	}

Don't we have respective macros in linux/pinctrl/pinctrl.h

...

> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/bitmap.h>
> +#include <linux/io.h>

> +#include <linux/of.h>
> +#include <linux/of_device.h>



> +#include <linux/slab.h>
> +#include <linux/regmap.h>
> +#include <linux/mfd/syscon.h>
> +#include <linux/pinctrl/pinctrl.h>
> +#include <linux/pinctrl/pinmux.h>
> +#include <linux/pinctrl/pinconf.h>
> +#include <linux/pinctrl/pinconf-generic.h>
> +#include <linux/seq_file.h>

Make it ordered, group linux/pinctrl/ separately after generic linux/*.h

> +#include "pinconf.h"
> +#include "pinctrl-ambarella.h"

...

> +struct amb_pinctrl_soc_data {
> +	struct device			*dev;
> +	const struct amb_pinctrl_data	*data;
> +	void __iomem			*iomux_base;
> +	struct regmap			*ds_regmap;
> +	struct regmap			*pull_regmap;
> +	unsigned int			npins;
> +	unsigned long			used[BITS_TO_LONGS(AMBA_MAX_PINS)];
> +	raw_spinlock_t lock;
> +
> +	struct pinctrl_dev		*pctl;

> +	const struct ambpin_function	*functions;
> +	unsigned int			nr_functions;
> +	struct ambpin_group		*groups;
> +	unsigned int			nr_groups;

Check what linux/pinctrl/pinctrl.h and drivers/pinctrl/core.h provide.

> +};

...

> +#if IS_ENABLED(CONFIG_DEBUG_FS)
> +static void amb_pin_dbg_show(struct pinctrl_dev *pctldev,
> +			     struct seq_file *s, unsigned int pin)
> +{
> +	seq_printf(s, " %s", pinctrl_dev_get_devname(pctldev));
> +}
> +#endif

Does this bring any new information?

...

> +/* check if the selector is a valid pin function selector */
> +static int amb_pinmux_request(struct pinctrl_dev *pctldev, unsigned int pin)
> +{
> +	struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
> +
> +	if (test_and_set_bit(pin, soc->used))
> +		return -EBUSY;
> +
> +	return 0;
> +}
> +
> +/* check if the selector is a valid pin function selector */
> +static int amb_pinmux_free(struct pinctrl_dev *pctldev, unsigned int pin)
> +{
> +	struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
> +
> +	clear_bit(pin, soc->used);
> +
> +	return 0;
> +}

Why? Isn't internal pinctrl tracking is not enough?

...

> +static void amb_pinmux_set_altfunc(struct amb_pinctrl_soc_data *soc,
> +				   u32 bank, u32 offset, u32 altfunc)
> +{
> +	u32 i, data;
> +
> +	if (!amb_iomux_accessible(soc))
> +		return;
> +
> +	for (i = 0; i < 3; i++) {

	for (unsigned int i...

> +		data = readl_relaxed(soc->iomux_base + IOMUX_OFFSET(bank, i));
> +		data &= ~BIT(offset);
> +		data |= ((altfunc >> i) & 1U) << offset;
> +		writel_relaxed(data, soc->iomux_base + IOMUX_OFFSET(bank, i));
> +	}
> +}

...

> +static int amb_drive_strength_to_reg(struct amb_pinctrl_soc_data *soc,
> +				     u32 strength)
> +{
> +	if (soc->data->have_ds2) {
> +		switch (strength) {
> +		case 3:
> +			return 0;
> +		case 4:
> +		case 5:
> +			return 1;
> +		case 6:
> +			return 2;
> +		case 7:
> +		case 8:
> +			return 3;
> +		case 9:
> +			return 4;
> +		case 12:
> +			return 5;
> +		default:
> +			return -EINVAL;
> +		}
> +	}
> +
> +	switch (strength) {
> +	case 2:
> +		return 0;
> +	case 4:
> +		return 1;
> +	case 8:
> +		return 2;
> +	case 12:
> +		return 3;
> +	default:
> +		return -EINVAL;
> +	}
> +}

I would look better in three functions

static int amb_drive_strength_to_reg(struct amb_pinctrl_soc_data *soc,
				     u32 strength)
{
	if (soc->data->have_ds2)
		return amb_drive_strength_to_reg_ds2(soc, strength);

	return amb_drive_strength_to_reg_ds1(soc, strength);

}

> +static int amb_reg_to_drive_strength(struct amb_pinctrl_soc_data *soc, u32 ds)
> +{
> +	static const int ds2_ma[] = { 3, 4, 6, 8, 9, 12 };
> +	static const int ds_ma[] = { 2, 4, 8, 12 };

You have switch cases that use the same arrays, use them for both.

> +	if (soc->data->have_ds2) {
> +		if (ds >= ARRAY_SIZE(ds2_ma))
> +			return -EINVAL;
> +
> +		return ds2_ma[ds];
> +	}
> +
> +	if (ds >= ARRAY_SIZE(ds_ma))
> +		return -EINVAL;
> +
> +	return ds_ma[ds];
> +}

...

I stopped here, this driver is quite far from the modern pin control standards,
please look around, you find a lot of new drivers there, that are written in
more-or-less compact ways, using better APIs and approaches.

When creating a driver, use also helper tools, like `pahole`,
scripts/bloat-o-meter, et cetera. Compile with `make W=1 C=1 ...`
using both compilers (GCC and clang).

...

> +static const struct of_device_id amb_pinctrl_dt_match[] = {
> +	{
> +		.compatible = "ambarella,cv75-pinctrl",
> +		.data = &ambarella_cv75_pinctrl_data,
> +	},
> +	{},

No trailing comma for the terminator entry.

> +};

...

> +static struct platform_driver amb_pinctrl_driver = {
> +	.probe	= amb_pinctrl_probe,
> +	.driver	= {
> +		.name	= "ambarella-pinctrl",
> +		.of_match_table = of_match_ptr(amb_pinctrl_dt_match),

Why of_match_ptr()?!

> +	},
> +};

-- 
With Best Regards,
Andy Shevchenko




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

* Re: [PATCH v6 05/13] dt-bindings: gpio: pl061: add Ambarella CV75 variant
  2026-09-04  6:38 ` [PATCH v6 05/13] dt-bindings: gpio: pl061: add Ambarella CV75 variant Long Zhao via B4 Relay
@ 2026-09-04 14:45   ` Rob Herring
  0 siblings, 0 replies; 28+ messages in thread
From: Rob Herring @ 2026-09-04 14:45 UTC (permalink / raw)
  To: Long Zhao
  Cc: Arnd Bergmann, Krzysztof Kozlowski, Alexandre Belloni, soc,
	linux-arm-kernel, Krzysztof Kozlowski, Conor Dooley,
	Michael Turquette, Stephen Boyd, Linus Walleij,
	Bartosz Golaszewski, Greg Kroah-Hartman, Jiri Slaby,
	Andy Shevchenko, Ilpo Järvinen, Catalin Marinas, Will Deacon,
	Long Zhao, Lee Jones, mfd, devicetree, linux-clk, linux-gpio,
	linux-serial, linux-kernel

On Fri, Sep 04, 2026 at 02:38:12PM +0800, Long Zhao wrote:
> Document the Ambarella CV75 GPIO bank compatible and the synthetic
> arm,primecell-periphid used with the PL061 binding.
> 
> Signed-off-by: Long Zhao <longzhao@ambarella.com>
> ---
>  .../devicetree/bindings/gpio/pl061-gpio.yaml       | 32 +++++++++++++++++++---
>  1 file changed, 28 insertions(+), 4 deletions(-)
> 
> diff --git a/Documentation/devicetree/bindings/gpio/pl061-gpio.yaml b/Documentation/devicetree/bindings/gpio/pl061-gpio.yaml
> index 4d970e55104b..5a6d95e84df2 100644
> --- a/Documentation/devicetree/bindings/gpio/pl061-gpio.yaml
> +++ b/Documentation/devicetree/bindings/gpio/pl061-gpio.yaml
> @@ -15,7 +15,9 @@ select:
>    properties:
>      compatible:
>        contains:
> -        const: arm,pl061
> +        enum:
> +          - arm,pl061
> +          - ambarella,cv75-gpio
>    required:
>      - compatible
>  
> @@ -24,9 +26,13 @@ properties:
>      pattern: "^gpio@[0-9a-f]+$"
>  
>    compatible:
> -    items:
> -      - const: arm,pl061
> -      - const: arm,primecell
> +    oneOf:
> +      - items:
> +          - const: arm,pl061
> +          - const: arm,primecell
> +      - items:
> +          - const: ambarella,cv75-gpio
> +          - const: arm,primecell

items:
  - enum:
      - ambarella,cv75-gpio
      - arm,pl061
  - const: arm,primecell


>  
>    reg:
>      maxItems: 1
> @@ -46,6 +52,10 @@ properties:
>  
>    clock-names: true
>  
> +  arm,primecell-periphid:
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    description: Override for the PrimeCell peripheral ID.

Add a $ref to primecell.yaml at the top level and drop this.

> +
>    "#gpio-cells":
>      const: 2
>  
> @@ -64,6 +74,20 @@ required:
>    - "#gpio-cells"
>    - gpio-controller
>  
> +allOf:
> +  - if:
> +      properties:
> +        compatible:
> +          contains:
> +            const: ambarella,cv75-gpio
> +    then:
> +      properties:
> +        arm,primecell-periphid:
> +          const: 0x000e8061
> +      required:
> +        - arm,primecell-periphid
> +        - gpio-ranges
> +
>  additionalProperties: false

And this will need to be unevaluatedProperties.

Rob


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

* Re: [PATCH v6 01/13] dt-bindings: arm: add Ambarella CV75 platforms
  2026-09-04  6:38 ` [PATCH v6 01/13] dt-bindings: arm: add Ambarella CV75 platforms Long Zhao via B4 Relay
@ 2026-09-04 22:39   ` Linus Walleij
  0 siblings, 0 replies; 28+ messages in thread
From: Linus Walleij @ 2026-09-04 22:39 UTC (permalink / raw)
  To: longzhao
  Cc: Arnd Bergmann, Krzysztof Kozlowski, Alexandre Belloni, soc,
	linux-arm-kernel, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Michael Turquette, Stephen Boyd, Bartosz Golaszewski,
	Greg Kroah-Hartman, Jiri Slaby, Andy Shevchenko,
	Ilpo Järvinen, Catalin Marinas, Will Deacon, Long Zhao,
	Lee Jones, mfd, devicetree, linux-clk, linux-gpio, linux-serial,
	linux-kernel, Krzysztof Kozlowski

On Fri, Sep 4, 2026 at 8:39 AM Long Zhao via B4 Relay
<devnull+longzhao.ambarella.com@kernel.org> wrote:

> From: Long Zhao <longzhao@ambarella.com>
>
> Add the Ambarella vendor prefix and CV75 EVK board compatible for the
> initial SoC bring-up.
>
> Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
> Signed-off-by: Long Zhao <longzhao@ambarella.com>

Reviewed-by: Linus Walleij <linusw@kernel.org>

Yours,
Linus Walleij


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

* Re: [PATCH v6 04/13] dt-bindings: pinctrl: add Ambarella CV75 pinctrl
  2026-09-04  6:38 ` [PATCH v6 04/13] dt-bindings: pinctrl: add Ambarella CV75 pinctrl Long Zhao via B4 Relay
@ 2026-09-04 22:40   ` Linus Walleij
  0 siblings, 0 replies; 28+ messages in thread
From: Linus Walleij @ 2026-09-04 22:40 UTC (permalink / raw)
  To: longzhao
  Cc: Arnd Bergmann, Krzysztof Kozlowski, Alexandre Belloni, soc,
	linux-arm-kernel, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Michael Turquette, Stephen Boyd, Bartosz Golaszewski,
	Greg Kroah-Hartman, Jiri Slaby, Andy Shevchenko,
	Ilpo Järvinen, Catalin Marinas, Will Deacon, Long Zhao,
	Lee Jones, mfd, devicetree, linux-clk, linux-gpio, linux-serial,
	linux-kernel

On Fri, Sep 4, 2026 at 8:39 AM Long Zhao via B4 Relay
<devnull+longzhao.ambarella.com@kernel.org> wrote:

> From: Long Zhao <longzhao@ambarella.com>
>
> Document the CV75 pin controller binding using generic function and
> groups properties for pinmux configuration.
>
> Signed-off-by: Long Zhao <longzhao@ambarella.com>

I think this looks great!
Reviewed-by: Linus Walleij <linusw@kernel.org>

Yours,
Linus Walleij


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

* Re: [PATCH v6 06/13] dt-bindings: serial: snps-dw-apb-uart: add ambarella,cv75-uart
  2026-09-04  6:38 ` [PATCH v6 06/13] dt-bindings: serial: snps-dw-apb-uart: add ambarella,cv75-uart Long Zhao via B4 Relay
@ 2026-09-04 22:41   ` Linus Walleij
  0 siblings, 0 replies; 28+ messages in thread
From: Linus Walleij @ 2026-09-04 22:41 UTC (permalink / raw)
  To: longzhao
  Cc: Arnd Bergmann, Krzysztof Kozlowski, Alexandre Belloni, soc,
	linux-arm-kernel, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Michael Turquette, Stephen Boyd, Bartosz Golaszewski,
	Greg Kroah-Hartman, Jiri Slaby, Andy Shevchenko,
	Ilpo Järvinen, Catalin Marinas, Will Deacon, Long Zhao,
	Lee Jones, mfd, devicetree, linux-clk, linux-gpio, linux-serial,
	linux-kernel

On Fri, Sep 4, 2026 at 8:39 AM Long Zhao via B4 Relay
<devnull+longzhao.ambarella.com@kernel.org> wrote:

> From: Long Zhao <longzhao@ambarella.com>
>
> Add the Ambarella CV75 compatible string to the Synopsys DW APB UART
> binding. The block is Ambarella IP with a DW-like register map, so it
> is described as a DW APB UART variant.
>
> Signed-off-by: Long Zhao <longzhao@ambarella.com>

Reviewed-by: Linus Walleij <linusw@kernel.org>

Yours,
Linus Walleij


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

* Re: [PATCH v6 11/13] serial: 8250_dw: add Ambarella CV75 quirks
  2026-09-04  6:38 ` [PATCH v6 11/13] serial: 8250_dw: add Ambarella CV75 quirks Long Zhao via B4 Relay
@ 2026-09-04 22:43   ` Linus Walleij
  0 siblings, 0 replies; 28+ messages in thread
From: Linus Walleij @ 2026-09-04 22:43 UTC (permalink / raw)
  To: longzhao
  Cc: Arnd Bergmann, Krzysztof Kozlowski, Alexandre Belloni, soc,
	linux-arm-kernel, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Michael Turquette, Stephen Boyd, Bartosz Golaszewski,
	Greg Kroah-Hartman, Jiri Slaby, Andy Shevchenko,
	Ilpo Järvinen, Catalin Marinas, Will Deacon, Long Zhao,
	Lee Jones, mfd, devicetree, linux-clk, linux-gpio, linux-serial,
	linux-kernel

On Fri, Sep 4, 2026 at 8:39 AM Long Zhao via B4 Relay
<devnull+longzhao.ambarella.com@kernel.org> wrote:

> From: Long Zhao <longzhao@ambarella.com>
>
> The Ambarella CV75 UART is Ambarella IP, not a Synopsys DesignWare
> licensed block. At the register level it is close enough to DW APB UART
> that a full custom UART driver is unnecessary: reuse 8250_dw with a
> small platform quirk.
>
> Quirks:
> - keep IER ETOI (bit 5) set for RX timeout behaviour
> - skip set_rate; baud clock is managed outside the UART block
>
> Signed-off-by: Long Zhao <longzhao@ambarella.com>

This is really nice. Compare the size of this to what was initially
submitted!

Reviewed-by: Linus Walleij <linusw@kernel.org>

Yours,
Linus Walleij


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

* Re: [PATCH v6 12/13] arm64: ambarella: add ARCH_AMBARELLA and CV75 EVK DT
  2026-09-04  6:38 ` [PATCH v6 12/13] arm64: ambarella: add ARCH_AMBARELLA and CV75 EVK DT Long Zhao via B4 Relay
@ 2026-09-04 22:45   ` Linus Walleij
  0 siblings, 0 replies; 28+ messages in thread
From: Linus Walleij @ 2026-09-04 22:45 UTC (permalink / raw)
  To: longzhao
  Cc: Arnd Bergmann, Krzysztof Kozlowski, Alexandre Belloni, soc,
	linux-arm-kernel, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Michael Turquette, Stephen Boyd, Bartosz Golaszewski,
	Greg Kroah-Hartman, Jiri Slaby, Andy Shevchenko,
	Ilpo Järvinen, Catalin Marinas, Will Deacon, Long Zhao,
	Lee Jones, mfd, devicetree, linux-clk, linux-gpio, linux-serial,
	linux-kernel

On Fri, Sep 4, 2026 at 8:39 AM Long Zhao via B4 Relay
<devnull+longzhao.ambarella.com@kernel.org> wrote:

> From: Long Zhao <longzhao@ambarella.com>
>
> Enable ARCH_AMBARELLA and add a minimal CV75 SoC / EVK device tree for
> console bring-up with the RCT clocks, UART, pinctrl and GPIO banks.
>
> Signed-off-by: Long Zhao <longzhao@ambarella.com>

Reviewed-by: Linus Walleij <linusw@kernel.org>

Yours,
Linus Walleij


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

* Re: [PATCH v6 13/13] MAINTAINERS: add ARM/AMBARELLA SoC support
  2026-09-04  6:38 ` [PATCH v6 13/13] MAINTAINERS: add ARM/AMBARELLA SoC support Long Zhao via B4 Relay
@ 2026-09-04 22:45   ` Linus Walleij
  0 siblings, 0 replies; 28+ messages in thread
From: Linus Walleij @ 2026-09-04 22:45 UTC (permalink / raw)
  To: longzhao
  Cc: Arnd Bergmann, Krzysztof Kozlowski, Alexandre Belloni, soc,
	linux-arm-kernel, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Michael Turquette, Stephen Boyd, Bartosz Golaszewski,
	Greg Kroah-Hartman, Jiri Slaby, Andy Shevchenko,
	Ilpo Järvinen, Catalin Marinas, Will Deacon, Long Zhao,
	Lee Jones, mfd, devicetree, linux-clk, linux-gpio, linux-serial,
	linux-kernel

On Fri, Sep 4, 2026 at 8:39 AM Long Zhao via B4 Relay
<devnull+longzhao.ambarella.com@kernel.org> wrote:

> From: Long Zhao <longzhao@ambarella.com>
>
> Add a maintainer entry covering Ambarella CV75 bindings, DTS and
> related drivers.
>
> Signed-off-by: Long Zhao <longzhao@ambarella.com>

Reviewed-by: Linus Walleij <linusw@kernel.org>

Yours,
Linus Walleij


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

end of thread, other threads:[~2026-09-04 22:46 UTC | newest]

Thread overview: 28+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-04  6:38 [PATCH v6 00/13] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
2026-09-04  6:38 ` [PATCH v6 01/13] dt-bindings: arm: add Ambarella CV75 platforms Long Zhao via B4 Relay
2026-09-04 22:39   ` Linus Walleij
2026-09-04  6:38 ` [PATCH v6 02/13] dt-bindings: mfd: syscon: add Ambarella CV75 secure scratchpad Long Zhao via B4 Relay
2026-09-04  6:38 ` [PATCH v6 03/13] dt-bindings: clock: add Ambarella CV75 RCT Long Zhao via B4 Relay
2026-09-04  6:38 ` [PATCH v6 04/13] dt-bindings: pinctrl: add Ambarella CV75 pinctrl Long Zhao via B4 Relay
2026-09-04 22:40   ` Linus Walleij
2026-09-04  6:38 ` [PATCH v6 05/13] dt-bindings: gpio: pl061: add Ambarella CV75 variant Long Zhao via B4 Relay
2026-09-04 14:45   ` Rob Herring
2026-09-04  6:38 ` [PATCH v6 06/13] dt-bindings: serial: snps-dw-apb-uart: add ambarella,cv75-uart Long Zhao via B4 Relay
2026-09-04 22:41   ` Linus Walleij
2026-09-04  6:38 ` [PATCH v6 07/13] clk: ambarella: add CV75 CCU driver Long Zhao via B4 Relay
2026-09-04  7:44   ` Jerome Brunet
2026-09-04  9:21     ` zl020895
2026-09-04  6:38 ` [PATCH v6 08/13] gpio: regmap: support write_data_after_dir and girq Long Zhao via B4 Relay
2026-09-04 11:55   ` Andy Shevchenko
2026-09-04  6:38 ` [PATCH v6 09/13] gpio: pl061: use gpio-regmap and add Ambarella layout Long Zhao via B4 Relay
2026-09-04 13:14   ` Andy Shevchenko
2026-09-04  6:38 ` [PATCH v6 10/13] pinctrl: ambarella: add CV75 pin controller Long Zhao via B4 Relay
2026-09-04 13:29   ` Andy Shevchenko
2026-09-04  6:38 ` [PATCH v6 11/13] serial: 8250_dw: add Ambarella CV75 quirks Long Zhao via B4 Relay
2026-09-04 22:43   ` Linus Walleij
2026-09-04  6:38 ` [PATCH v6 12/13] arm64: ambarella: add ARCH_AMBARELLA and CV75 EVK DT Long Zhao via B4 Relay
2026-09-04 22:45   ` Linus Walleij
2026-09-04  6:38 ` [PATCH v6 13/13] MAINTAINERS: add ARM/AMBARELLA SoC support Long Zhao via B4 Relay
2026-09-04 22:45   ` Linus Walleij
2026-09-04  6:54 ` [PATCH v6 00/13] Ambarella CV75 SoC minimal bring-up Jerome Brunet
2026-09-04  6:56   ` Krzysztof Kozlowski

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox