Linux-ARM-Kernel Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v5 00/12] Ambarella CV75 SoC minimal bring-up
@ 2026-08-18 10:31 Long Zhao via B4 Relay
  2026-08-18 10:31 ` [PATCH v5 01/12] dt-bindings: arm: add Ambarella CV75 platforms Long Zhao via B4 Relay
                   ` (11 more replies)
  0 siblings, 12 replies; 15+ messages in thread
From: Long Zhao via B4 Relay @ 2026-08-18 10:31 UTC (permalink / raw)
  To: Arnd Bergmann, Krzysztof Kozlowski, Alexandre Belloni, soc,
	linux-arm-kernel
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
	Stephen Boyd, Linus Walleij, Bartosz Golaszewski,
	Greg Kroah-Hartman, Jiri Slaby, Andy Shevchenko, Catalin Marinas,
	Will Deacon, devicetree, linux-clk, linux-gpio, linux-serial,
	linux-kernel, Brian Masney, Lee Jones, mfd, Long Zhao, Long Zhao,
	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
variant, 8250-based UART, ARCH_AMBARELLA, CV75 EVK DT, and MAINTAINERS.

This is again a single unified series (as in v2). It supersedes the v3
A/B/C split and the later v4 B resend.

  v3-A: bindings + RCT/CCU + SoC/EVK DT + MAINTAINERS
  v3-B / v4-B: pinctrl + PL061 GPIO
  v3-C: 8250_dw UART quirks

Please apply via the SoC tree; subsystem maintainers are Cc'd for their
pieces.

Verified with checkpatch --strict, dt_binding_check (ambarella),
CHECK_DTBS for cv75-evk, and CV75 boot / UART / clk_summary.

Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
Changes in v5:
- Reunify into one series toward soc@ (v2-style topics); supersede
  v3 A/B/C and v4 B (Krzysztof)
- UART: drop 8250_dw quirks; add standalone 8250_ambarella + binding
  (Linus, Andy)
- Fold review/self-fix commits into the corresponding topic patches
- pinctrl binding: drop redundant groups $ref (Krzysztof)
- Sashiko: address applicable findings
  - clk: guard parent_rate == 0; include fsdiv/fsout in PLL approx;
    clamp VCO range; clear divider disable bit on non-zero set_rate;
    integer determine_rate uses achievable params; 32-bit
    COMPILE_TEST uses DIV64_U64_ROUND_CLOSEST
  - PINCTRL_AMB depends on HAS_IOMEM and defaults when ARCH_AMBARELLA
    (ARCH no longer selects PINCTRL_AMB)
  - IOMUX: one commit per group; use BIT() for altfunc (offset 31)
  - PL061: per-pin AFSEL claim instead of wiping the bank at probe
  - GIC: omit unused #size-cells, keep only #address-cells = <0>
  - RCT remains clock-only (no #reset-cells / reset provider)
- No change: ARM_PSCI / HAVE_ARM_ARCH_TIMER (same as ARCH_AIROHA);
  syscon.yaml has no select block; GPIO pinconf-only DT nodes (GPIO
  mux is via gpiochip request, IOMUX alt 0)

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

---
Long Zhao (12):
      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: add Ambarella CV75 UART
      clk: ambarella: add CV75 CCU driver
      gpio: pl061: add Ambarella CV75 register layout variant
      pinctrl: ambarella: add CV75 pin controller
      serial: 8250: add Ambarella UART driver
      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         |  58 ++
 .../devicetree/bindings/gpio/pl061-gpio.yaml       |  35 +-
 Documentation/devicetree/bindings/mfd/syscon.yaml  |   1 +
 .../bindings/pinctrl/ambarella,cv75-pinctrl.yaml   | 239 ++++++
 .../bindings/serial/ambarella,cv75-uart.yaml       |  56 ++
 .../devicetree/bindings/vendor-prefixes.yaml       |   2 +
 MAINTAINERS                                        |  16 +
 arch/arm64/Kconfig.platforms                       |   8 +
 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            | 176 +++++
 arch/arm64/configs/defconfig                       |   2 +
 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/gpio-pl061.c                          | 314 ++++++--
 drivers/pinctrl/Kconfig                            |  15 +
 drivers/pinctrl/Makefile                           |   2 +
 drivers/pinctrl/pinctrl-ambarella-cv75.c           | 561 ++++++++++++++
 drivers/pinctrl/pinctrl-ambarella.c                | 863 +++++++++++++++++++++
 drivers/pinctrl/pinctrl-ambarella.h                |  50 ++
 drivers/tty/serial/8250/8250_ambarella.c           | 312 ++++++++
 drivers/tty/serial/8250/Kconfig                    |   9 +
 drivers/tty/serial/8250/Makefile                   |   1 +
 include/dt-bindings/clock/ambarella,cv75-rct.h     |  15 +
 35 files changed, 3883 insertions(+), 72 deletions(-)
---
base-commit: 34cf6dafc47441dfb6b356a095b89c3585a93714
change-id: 20260818-cv75-v5-9e2039b349bc

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




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

* [PATCH v5 01/12] dt-bindings: arm: add Ambarella CV75 platforms
  2026-08-18 10:31 [PATCH v5 00/12] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
@ 2026-08-18 10:31 ` Long Zhao via B4 Relay
  2026-08-18 10:31 ` [PATCH v5 02/12] dt-bindings: mfd: syscon: add Ambarella CV75 secure scratchpad Long Zhao via B4 Relay
                   ` (10 subsequent siblings)
  11 siblings, 0 replies; 15+ messages in thread
From: Long Zhao via B4 Relay @ 2026-08-18 10:31 UTC (permalink / raw)
  To: Arnd Bergmann, Krzysztof Kozlowski, Alexandre Belloni, soc,
	linux-arm-kernel
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
	Stephen Boyd, Linus Walleij, Bartosz Golaszewski,
	Greg Kroah-Hartman, Jiri Slaby, Andy Shevchenko, Catalin Marinas,
	Will Deacon, devicetree, linux-clk, linux-gpio, linux-serial,
	linux-kernel, Brian Masney, Lee Jones, mfd, Long Zhao, Long Zhao,
	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] 15+ messages in thread

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

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] 15+ messages in thread

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

From: Long Zhao <longzhao@ambarella.com>

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

This initial binding describes only the clock-provider functionality.
Reset-controller support is outside scope of this series, so #reset-cells
property is intentionally omitted.

Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
 .../bindings/clock/ambarella,cv75-rct.yaml         | 58 ++++++++++++++++++++++
 include/dt-bindings/clock/ambarella,cv75-rct.h     | 15 ++++++
 2 files changed, 73 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..eb5956fc32bc
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/ambarella,cv75-rct.yaml
@@ -0,0 +1,58 @@
+# 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 and dividers 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
+
+required:
+  - compatible
+  - reg
+  - clocks
+  - clock-names
+  - "#clock-cells"
+
+additionalProperties: false
+
+examples:
+  - |
+    clock-controller@ed080000 {
+        compatible = "ambarella,cv75-rct", "syscon";
+        reg = <0xed080000 0x1000>;
+        clocks = <&osc>;
+        clock-names = "osc";
+        #clock-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] 15+ messages in thread

* [PATCH v5 04/12] dt-bindings: pinctrl: add Ambarella CV75 pinctrl
  2026-08-18 10:31 [PATCH v5 00/12] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
                   ` (2 preceding siblings ...)
  2026-08-18 10:31 ` [PATCH v5 03/12] dt-bindings: clock: add Ambarella CV75 RCT Long Zhao via B4 Relay
@ 2026-08-18 10:31 ` Long Zhao via B4 Relay
  2026-08-18 10:31 ` [PATCH v5 05/12] dt-bindings: gpio: pl061: add Ambarella CV75 variant Long Zhao via B4 Relay
                   ` (7 subsequent siblings)
  11 siblings, 0 replies; 15+ messages in thread
From: Long Zhao via B4 Relay @ 2026-08-18 10:31 UTC (permalink / raw)
  To: Arnd Bergmann, Krzysztof Kozlowski, Alexandre Belloni, soc,
	linux-arm-kernel
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
	Stephen Boyd, Linus Walleij, Bartosz Golaszewski,
	Greg Kroah-Hartman, Jiri Slaby, Andy Shevchenko, Catalin Marinas,
	Will Deacon, devicetree, linux-clk, linux-gpio, linux-serial,
	linux-kernel, Brian Masney, Lee Jones, mfd, Long Zhao, Long Zhao

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] 15+ messages in thread

* [PATCH v5 05/12] dt-bindings: gpio: pl061: add Ambarella CV75 variant
  2026-08-18 10:31 [PATCH v5 00/12] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
                   ` (3 preceding siblings ...)
  2026-08-18 10:31 ` [PATCH v5 04/12] dt-bindings: pinctrl: add Ambarella CV75 pinctrl Long Zhao via B4 Relay
@ 2026-08-18 10:31 ` Long Zhao via B4 Relay
  2026-08-18 10:31 ` [PATCH v5 06/12] dt-bindings: serial: add Ambarella CV75 UART Long Zhao via B4 Relay
                   ` (6 subsequent siblings)
  11 siblings, 0 replies; 15+ messages in thread
From: Long Zhao via B4 Relay @ 2026-08-18 10:31 UTC (permalink / raw)
  To: Arnd Bergmann, Krzysztof Kozlowski, Alexandre Belloni, soc,
	linux-arm-kernel
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
	Stephen Boyd, Linus Walleij, Bartosz Golaszewski,
	Greg Kroah-Hartman, Jiri Slaby, Andy Shevchenko, Catalin Marinas,
	Will Deacon, devicetree, linux-clk, linux-gpio, linux-serial,
	linux-kernel, Brian Masney, Lee Jones, mfd, Long Zhao, Long Zhao

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       | 35 +++++++++++++++++++---
 1 file changed, 31 insertions(+), 4 deletions(-)

diff --git a/Documentation/devicetree/bindings/gpio/pl061-gpio.yaml b/Documentation/devicetree/bindings/gpio/pl061-gpio.yaml
index 4d970e55104b..414f8d4a3976 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,20 @@ 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
+
+  arm,primecell-periphid:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    description:
+      Value to override the hardware PrimeCell peripheral ID. Ambarella
+      variants use a synthetic ID retaining the PL061 part number because
+      the hardware has no discoverable ID or assigned designer ID.
 
   reg:
     maxItems: 1
@@ -57,6 +70,20 @@ properties:
     minItems: 1
     maxItems: 8
 
+allOf:
+  - if:
+      properties:
+        compatible:
+          contains:
+            const: ambarella,cv75-gpio
+    then:
+      properties:
+        arm,primecell-periphid:
+          const: 0x00000061
+      required:
+        - arm,primecell-periphid
+        - gpio-ranges
+
 required:
   - compatible
   - reg

-- 
2.34.1




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

* [PATCH v5 06/12] dt-bindings: serial: add Ambarella CV75 UART
  2026-08-18 10:31 [PATCH v5 00/12] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
                   ` (4 preceding siblings ...)
  2026-08-18 10:31 ` [PATCH v5 05/12] dt-bindings: gpio: pl061: add Ambarella CV75 variant Long Zhao via B4 Relay
@ 2026-08-18 10:31 ` Long Zhao via B4 Relay
  2026-08-18 10:31 ` [PATCH v5 07/12] clk: ambarella: add CV75 CCU driver Long Zhao via B4 Relay
                   ` (5 subsequent siblings)
  11 siblings, 0 replies; 15+ messages in thread
From: Long Zhao via B4 Relay @ 2026-08-18 10:31 UTC (permalink / raw)
  To: Arnd Bergmann, Krzysztof Kozlowski, Alexandre Belloni, soc,
	linux-arm-kernel
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
	Stephen Boyd, Linus Walleij, Bartosz Golaszewski,
	Greg Kroah-Hartman, Jiri Slaby, Andy Shevchenko, Catalin Marinas,
	Will Deacon, devicetree, linux-clk, linux-gpio, linux-serial,
	linux-kernel, Brian Masney, Lee Jones, mfd, Long Zhao, Long Zhao

From: Long Zhao <longzhao@ambarella.com>

Document the Ambarella CV75 on-chip UART controller binding for early
console bring-up. The binding requires a single RCT clock input and
fixed 32-bit register access via reg-shift and reg-io-width.

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

diff --git a/Documentation/devicetree/bindings/serial/ambarella,cv75-uart.yaml b/Documentation/devicetree/bindings/serial/ambarella,cv75-uart.yaml
new file mode 100644
index 000000000000..223c84bf460d
--- /dev/null
+++ b/Documentation/devicetree/bindings/serial/ambarella,cv75-uart.yaml
@@ -0,0 +1,56 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/serial/ambarella,cv75-uart.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Ambarella CV75 UART
+
+maintainers:
+  - Long Zhao <longzhao@ambarella.com>
+
+allOf:
+  - $ref: serial.yaml#
+
+properties:
+  compatible:
+    const: ambarella,cv75-uart
+
+  reg:
+    maxItems: 1
+
+  interrupts:
+    maxItems: 1
+
+  clocks:
+    maxItems: 1
+
+  reg-shift:
+    const: 2
+
+  reg-io-width:
+    const: 4
+
+required:
+  - compatible
+  - reg
+  - interrupts
+  - clocks
+  - reg-shift
+  - reg-io-width
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/clock/ambarella,cv75-rct.h>
+    #include <dt-bindings/interrupt-controller/arm-gic.h>
+
+    serial@e4000000 {
+        compatible = "ambarella,cv75-uart";
+        reg = <0xe4000000 0x1000>;
+        clocks = <&rct CV75_GCLK_UART0>;
+        interrupts = <GIC_SPI 170 IRQ_TYPE_LEVEL_HIGH>;
+        reg-shift = <2>;
+        reg-io-width = <4>;
+    };

-- 
2.34.1




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

* [PATCH v5 07/12] clk: ambarella: add CV75 CCU driver
  2026-08-18 10:31 [PATCH v5 00/12] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
                   ` (5 preceding siblings ...)
  2026-08-18 10:31 ` [PATCH v5 06/12] dt-bindings: serial: add Ambarella CV75 UART Long Zhao via B4 Relay
@ 2026-08-18 10:31 ` Long Zhao via B4 Relay
  2026-08-18 10:31 ` [PATCH v5 08/12] gpio: pl061: add Ambarella CV75 register layout variant Long Zhao via B4 Relay
                   ` (4 subsequent siblings)
  11 siblings, 0 replies; 15+ messages in thread
From: Long Zhao via B4 Relay @ 2026-08-18 10:31 UTC (permalink / raw)
  To: Arnd Bergmann, Krzysztof Kozlowski, Alexandre Belloni, soc,
	linux-arm-kernel
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
	Stephen Boyd, Linus Walleij, Bartosz Golaszewski,
	Greg Kroah-Hartman, Jiri Slaby, Andy Shevchenko, Catalin Marinas,
	Will Deacon, devicetree, linux-clk, linux-gpio, linux-serial,
	linux-kernel, Brian Masney, Lee Jones, mfd, Long Zhao, Long Zhao

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..d682da3cf28d
--- /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((void *)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..b9e453e9debf
--- /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;
+	const 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] 15+ messages in thread

* [PATCH v5 08/12] gpio: pl061: add Ambarella CV75 register layout variant
  2026-08-18 10:31 [PATCH v5 00/12] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
                   ` (6 preceding siblings ...)
  2026-08-18 10:31 ` [PATCH v5 07/12] clk: ambarella: add CV75 CCU driver Long Zhao via B4 Relay
@ 2026-08-18 10:31 ` Long Zhao via B4 Relay
  2026-08-18 14:11   ` Andy Shevchenko
  2026-08-18 10:31 ` [PATCH v5 09/12] pinctrl: ambarella: add CV75 pin controller Long Zhao via B4 Relay
                   ` (3 subsequent siblings)
  11 siblings, 1 reply; 15+ messages in thread
From: Long Zhao via B4 Relay @ 2026-08-18 10:31 UTC (permalink / raw)
  To: Arnd Bergmann, Krzysztof Kozlowski, Alexandre Belloni, soc,
	linux-arm-kernel
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
	Stephen Boyd, Linus Walleij, Bartosz Golaszewski,
	Greg Kroah-Hartman, Jiri Slaby, Andy Shevchenko, Catalin Marinas,
	Will Deacon, devicetree, linux-clk, linux-gpio, linux-serial,
	linux-kernel, Brian Masney, Lee Jones, mfd, Long Zhao, Long Zhao

From: Long Zhao <longzhao@ambarella.com>

Extend gpio-pl061 with a per-variant register layout so the Ambarella
CV75 GPIO banks can reuse the PL061 driver instead of a duplicate.

Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
 drivers/gpio/gpio-pl061.c | 314 ++++++++++++++++++++++++++++++++++++----------
 1 file changed, 246 insertions(+), 68 deletions(-)

diff --git a/drivers/gpio/gpio-pl061.c b/drivers/gpio/gpio-pl061.c
index 919cf86fd590..7002ddbc9713 100644
--- a/drivers/gpio/gpio-pl061.c
+++ b/drivers/gpio/gpio-pl061.c
@@ -26,41 +26,107 @@
 #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	U32_MAX
+#define PL061_AMBARELLA_PERIPH_ID	0x00000061
+
+struct pl061_variant_data {
+	u32 data;
+	u32 dir;
+	u32 is;
+	u32 ibe;
+	u32 iev;
+	u32 ie;
+	u32 ris;
+	u32 mis;
+	u32 ic;
+	u32 afsel;
+	u32 mask;
+	u32 enable;
+	unsigned int ngpio;
+	bool access_32bit;
+	bool masked_data_address;
+	bool write_data_after_dir;
+	bool clear_irq_on_type;
+};
 
 struct pl061_context_save_regs {
-	u8 gpio_data;
-	u8 gpio_dir;
-	u8 gpio_is;
-	u8 gpio_ibe;
-	u8 gpio_iev;
-	u8 gpio_ie;
+	u32 gpio_data;
+	u32 gpio_dir;
+	u32 gpio_is;
+	u32 gpio_ibe;
+	u32 gpio_iev;
+	u32 gpio_ie;
+	u32 gpio_afsel;
+	u32 gpio_mask;
 };
 
 struct pl061 {
 	raw_spinlock_t		lock;
 
 	void __iomem		*base;
+	const struct pl061_variant_data *variant;
 	struct gpio_chip	gc;
 	int			parent_irq;
 
 	struct pl061_context_save_regs csave_regs;
 };
 
+static u32 pl061_read(struct pl061 *pl061, u32 offset)
+{
+	if (pl061->variant->access_32bit)
+		return readl_relaxed(pl061->base + offset);
+
+	return readb_relaxed(pl061->base + offset);
+}
+
+static void pl061_write(struct pl061 *pl061, u32 value, u32 offset)
+{
+	if (pl061->variant->access_32bit)
+		writel_relaxed(value, pl061->base + offset);
+	else
+		writeb_relaxed(value, pl061->base + offset);
+}
+
+static int pl061_get_data(struct pl061 *pl061, unsigned int offset)
+{
+	if (pl061->variant->masked_data_address)
+		return !!readb_relaxed(pl061->base + BIT(offset + 2));
+
+	pl061_write(pl061, BIT(offset), pl061->variant->mask);
+	return !!(pl061_read(pl061, pl061->variant->data) & BIT(offset));
+}
+
+static void pl061_set_data(struct pl061 *pl061, unsigned int offset, int value)
+{
+	if (pl061->variant->masked_data_address) {
+		writeb_relaxed(!!value << offset,
+			       pl061->base + BIT(offset + 2));
+		return;
+	}
+
+	pl061_write(pl061, BIT(offset), pl061->variant->mask);
+	pl061_write(pl061, value ? BIT(offset) : 0, pl061->variant->data);
+}
+
+static void pl061_claim_gpio(struct pl061 *pl061, unsigned int offset)
+{
+	u32 afsel;
+
+	if (pl061->variant->afsel == PL061_REG_NONE)
+		return;
+
+	afsel = pl061_read(pl061, pl061->variant->afsel);
+	if (afsel & BIT(offset))
+		pl061_write(pl061, afsel & ~BIT(offset),
+			    pl061->variant->afsel);
+}
+
 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))
+	if (pl061_read(pl061, pl061->variant->dir) & BIT(offset))
 		return GPIO_LINE_DIRECTION_OUT;
 
 	return GPIO_LINE_DIRECTION_IN;
@@ -70,12 +136,13 @@ static int pl061_direction_input(struct gpio_chip *gc, unsigned offset)
 {
 	struct pl061 *pl061 = gpiochip_get_data(gc);
 	unsigned long flags;
-	unsigned char gpiodir;
+	u32 gpiodir;
 
 	raw_spin_lock_irqsave(&pl061->lock, flags);
-	gpiodir = readb(pl061->base + GPIODIR);
+	gpiodir = pl061_read(pl061, pl061->variant->dir);
 	gpiodir &= ~(BIT(offset));
-	writeb(gpiodir, pl061->base + GPIODIR);
+	pl061_write(pl061, gpiodir, pl061->variant->dir);
+	pl061_claim_gpio(pl061, offset);
 	raw_spin_unlock_irqrestore(&pl061->lock, flags);
 
 	return 0;
@@ -86,19 +153,21 @@ static int pl061_direction_output(struct gpio_chip *gc, unsigned offset,
 {
 	struct pl061 *pl061 = gpiochip_get_data(gc);
 	unsigned long flags;
-	unsigned char gpiodir;
+	u32 gpiodir;
 
 	raw_spin_lock_irqsave(&pl061->lock, flags);
-	writeb(!!value << offset, pl061->base + (BIT(offset + 2)));
-	gpiodir = readb(pl061->base + GPIODIR);
+	pl061_set_data(pl061, offset, value);
+	gpiodir = pl061_read(pl061, pl061->variant->dir);
 	gpiodir |= BIT(offset);
-	writeb(gpiodir, pl061->base + GPIODIR);
+	pl061_write(pl061, gpiodir, pl061->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.
 	 */
-	writeb(!!value << offset, pl061->base + (BIT(offset + 2)));
+	if (pl061->variant->write_data_after_dir)
+		pl061_set_data(pl061, offset, value);
+	pl061_claim_gpio(pl061, offset);
 	raw_spin_unlock_irqrestore(&pl061->lock, flags);
 
 	return 0;
@@ -107,15 +176,24 @@ static int pl061_direction_output(struct gpio_chip *gc, unsigned offset,
 static int pl061_get_value(struct gpio_chip *gc, unsigned offset)
 {
 	struct pl061 *pl061 = gpiochip_get_data(gc);
+	unsigned long flags;
+	int value;
 
-	return !!readb(pl061->base + (BIT(offset + 2)));
+	raw_spin_lock_irqsave(&pl061->lock, flags);
+	value = pl061_get_data(pl061, offset);
+	raw_spin_unlock_irqrestore(&pl061->lock, flags);
+
+	return value;
 }
 
 static int pl061_set_value(struct gpio_chip *gc, unsigned int offset, int value)
 {
 	struct pl061 *pl061 = gpiochip_get_data(gc);
+	unsigned long flags;
 
-	writeb(!!value << offset, pl061->base + (BIT(offset + 2)));
+	raw_spin_lock_irqsave(&pl061->lock, flags);
+	pl061_set_data(pl061, offset, value);
+	raw_spin_unlock_irqrestore(&pl061->lock, flags);
 
 	return 0;
 }
@@ -126,15 +204,14 @@ static int pl061_irq_type(struct irq_data *d, unsigned trigger)
 	struct pl061 *pl061 = gpiochip_get_data(gc);
 	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 >= gc->ngpio)
 		return -EINVAL;
 
 	if ((trigger & (IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_LEVEL_LOW)) &&
-	    (trigger & (IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING)))
-	{
+	    (trigger & (IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING))) {
 		dev_err(gc->parent,
 			"trying to configure line %d for both level and edge "
 			"detection, choose one!\n",
@@ -142,12 +219,11 @@ 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, pl061->variant->iev);
+	gpiois = pl061_read(pl061, pl061->variant->is);
+	gpioibe = pl061_read(pl061, pl061->variant->ibe);
 
 	if (trigger & (IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_LEVEL_LOW)) {
 		bool polarity = trigger & IRQ_TYPE_LEVEL_HIGH;
@@ -199,9 +275,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, pl061->variant->is);
+	pl061_write(pl061, gpioibe, pl061->variant->ibe);
+	pl061_write(pl061, gpioiev, pl061->variant->iev);
+	if (pl061->variant->clear_irq_on_type)
+		pl061_write(pl061, bit, pl061->variant->ic);
 
 	raw_spin_unlock_irqrestore(&pl061->lock, flags);
 
@@ -218,9 +296,9 @@ static void pl061_irq_handler(struct irq_desc *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)
+		for_each_set_bit(offset, &pending, gc->ngpio)
 			generic_handle_domain_irq(gc->irq.domain,
 						  offset);
 	}
@@ -232,12 +310,12 @@ 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;
+	u32 mask = BIT(irqd_to_hwirq(d));
+	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);
@@ -247,14 +325,14 @@ 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;
+	u32 mask = BIT(irqd_to_hwirq(d));
+	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);
 }
 
@@ -270,10 +348,10 @@ 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);
+	u32 mask = BIT(irqd_to_hwirq(d));
 
 	raw_spin_lock(&pl061->lock);
-	writeb(mask, pl061->base + GPIOIC);
+	pl061_write(pl061, mask, pl061->variant->ic);
 	raw_spin_unlock(&pl061->lock);
 }
 
@@ -292,6 +370,24 @@ static void pl061_irq_print_chip(struct irq_data *data, struct seq_file *p)
 	seq_puts(p, dev_name(gc->parent));
 }
 
+static int pl061_irq_request_resources(struct irq_data *d)
+{
+	struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+	struct pl061 *pl061 = gpiochip_get_data(gc);
+	unsigned long flags;
+	int ret;
+
+	ret = gpiochip_irq_reqres(d);
+	if (ret)
+		return ret;
+
+	raw_spin_lock_irqsave(&pl061->lock, flags);
+	pl061_claim_gpio(pl061, irqd_to_hwirq(d));
+	raw_spin_unlock_irqrestore(&pl061->lock, flags);
+
+	return 0;
+}
+
 static const struct irq_chip pl061_irq_chip = {
 	.irq_ack		= pl061_irq_ack,
 	.irq_mask		= pl061_irq_mask,
@@ -299,8 +395,9 @@ static const struct irq_chip pl061_irq_chip = {
 	.irq_set_type		= pl061_irq_type,
 	.irq_set_wake		= pl061_irq_set_wake,
 	.irq_print_chip		= pl061_irq_print_chip,
+	.irq_request_resources	= pl061_irq_request_resources,
+	.irq_release_resources	= gpiochip_irq_relres,
 	.flags			= IRQCHIP_IMMUTABLE,
-	GPIOCHIP_IRQ_RESOURCE_HELPERS,
 };
 
 static int pl061_probe(struct amba_device *adev, const struct amba_id *id)
@@ -314,6 +411,10 @@ static int pl061_probe(struct amba_device *adev, const struct amba_id *id)
 	if (pl061 == NULL)
 		return -ENOMEM;
 
+	pl061->variant = id->data;
+	if (!pl061->variant)
+		return dev_err_probe(dev, -EINVAL, "missing variant data\n");
+
 	pl061->base = devm_ioremap_resource(dev, &adev->res);
 	if (IS_ERR(pl061->base))
 		return PTR_ERR(pl061->base);
@@ -327,7 +428,7 @@ static int pl061_probe(struct amba_device *adev, const struct amba_id *id)
 	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.ngpio = pl061->variant->ngpio;
 	pl061->gc.label = dev_name(dev);
 	pl061->gc.parent = dev;
 	pl061->gc.owner = THIS_MODULE;
@@ -335,7 +436,12 @@ static int pl061_probe(struct amba_device *adev, const struct amba_id *id)
 	/*
 	 * irq_chip support
 	 */
-	writeb(0, pl061->base + GPIOIE); /* disable irqs */
+	pl061_write(pl061, 0, pl061->variant->ie); /* disable irqs */
+	if (pl061->variant->enable != PL061_REG_NONE)
+		pl061_write(pl061, GENMASK(pl061->gc.ngpio - 1, 0),
+			    pl061->variant->enable);
+	if (pl061->variant->mask != PL061_REG_NONE)
+		pl061_write(pl061, 0, pl061->variant->mask);
 	irq = adev->irq[0];
 	if (!irq)
 		dev_warn(&adev->dev, "IRQ support disabled\n");
@@ -366,20 +472,37 @@ 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);
+	unsigned long flags;
 	int offset;
 
+	raw_spin_lock_irqsave(&pl061->lock, flags);
 	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_data |=
-				pl061_get_value(&pl061->gc, offset) << offset;
+	pl061->csave_regs.gpio_dir =
+		pl061_read(pl061, pl061->variant->dir);
+	pl061->csave_regs.gpio_is =
+		pl061_read(pl061, pl061->variant->is);
+	pl061->csave_regs.gpio_ibe =
+		pl061_read(pl061, pl061->variant->ibe);
+	pl061->csave_regs.gpio_iev =
+		pl061_read(pl061, pl061->variant->iev);
+	pl061->csave_regs.gpio_ie =
+		pl061_read(pl061, pl061->variant->ie);
+	if (pl061->variant->afsel != PL061_REG_NONE)
+		pl061->csave_regs.gpio_afsel =
+			pl061_read(pl061, pl061->variant->afsel);
+	if (pl061->variant->mask != PL061_REG_NONE)
+		pl061->csave_regs.gpio_mask =
+			pl061_read(pl061, pl061->variant->mask);
+
+	for (offset = 0; offset < pl061->gc.ngpio; offset++) {
+		if ((pl061->csave_regs.gpio_dir & BIT(offset)) &&
+		    pl061_get_data(pl061, offset))
+			pl061->csave_regs.gpio_data |= BIT(offset);
 	}
+	if (pl061->variant->mask != PL061_REG_NONE)
+		pl061_write(pl061, pl061->csave_regs.gpio_mask,
+			    pl061->variant->mask);
+	raw_spin_unlock_irqrestore(&pl061->lock, flags);
 
 	return 0;
 }
@@ -389,7 +512,7 @@ static int pl061_resume(struct device *dev)
 	struct pl061 *pl061 = dev_get_drvdata(dev);
 	int offset;
 
-	for (offset = 0; offset < PL061_GPIO_NR; offset++) {
+	for (offset = 0; offset < pl061->gc.ngpio; offset++) {
 		if (pl061->csave_regs.gpio_dir & (BIT(offset)))
 			pl061_direction_output(&pl061->gc, offset,
 					pl061->csave_regs.gpio_data &
@@ -398,20 +521,75 @@ static int pl061_resume(struct device *dev)
 			pl061_direction_input(&pl061->gc, offset);
 	}
 
-	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,
+		    pl061->variant->is);
+	pl061_write(pl061, pl061->csave_regs.gpio_ibe,
+		    pl061->variant->ibe);
+	pl061_write(pl061, pl061->csave_regs.gpio_iev,
+		    pl061->variant->iev);
+	pl061_write(pl061, pl061->csave_regs.gpio_ie,
+		    pl061->variant->ie);
+	if (pl061->variant->afsel != PL061_REG_NONE)
+		pl061_write(pl061, pl061->csave_regs.gpio_afsel,
+			    pl061->variant->afsel);
+	if (pl061->variant->mask != PL061_REG_NONE)
+		pl061_write(pl061, pl061->csave_regs.gpio_mask,
+			    pl061->variant->mask);
+	if (pl061->variant->enable != PL061_REG_NONE)
+		pl061_write(pl061, GENMASK(pl061->gc.ngpio - 1, 0),
+			    pl061->variant->enable);
 
 	return 0;
 }
 
 static DEFINE_SIMPLE_DEV_PM_OPS(pl061_dev_pm_ops, pl061_suspend, pl061_resume);
 
+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,
+	.afsel = PL061_REG_NONE,
+	.mask = PL061_REG_NONE,
+	.enable = PL061_REG_NONE,
+	.ngpio = PL061_GPIO_NR,
+	.masked_data_address = true,
+	.write_data_after_dir = true,
+};
+
+static struct pl061_variant_data pl061_ambarella = {
+	.data = 0x00,
+	.dir = 0x04,
+	.is = 0x08,
+	.ibe = 0x0c,
+	.iev = 0x10,
+	.ie = 0x14,
+	.afsel = 0x18,
+	.ris = 0x1c,
+	.mis = 0x20,
+	.ic = 0x24,
+	.mask = 0x28,
+	.enable = 0x2c,
+	.ngpio = 32,
+	.access_32bit = true,
+	.clear_irq_on_type = true,
+};
+
 static const struct amba_id pl061_ids[] = {
 	{
 		.id	= 0x00041061,
 		.mask	= 0x000fffff,
+		.data	= &pl061_arm,
+	},
+	{
+		.id	= PL061_AMBARELLA_PERIPH_ID,
+		.mask	= 0xffffffff,
+		.data	= &pl061_ambarella,
 	},
 	{ 0, 0 },
 };

-- 
2.34.1




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

* [PATCH v5 09/12] pinctrl: ambarella: add CV75 pin controller
  2026-08-18 10:31 [PATCH v5 00/12] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
                   ` (7 preceding siblings ...)
  2026-08-18 10:31 ` [PATCH v5 08/12] gpio: pl061: add Ambarella CV75 register layout variant Long Zhao via B4 Relay
@ 2026-08-18 10:31 ` Long Zhao via B4 Relay
  2026-08-18 10:31 ` [PATCH v5 10/12] serial: 8250: add Ambarella UART driver Long Zhao via B4 Relay
                   ` (2 subsequent siblings)
  11 siblings, 0 replies; 15+ messages in thread
From: Long Zhao via B4 Relay @ 2026-08-18 10:31 UTC (permalink / raw)
  To: Arnd Bergmann, Krzysztof Kozlowski, Alexandre Belloni, soc,
	linux-arm-kernel
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
	Stephen Boyd, Linus Walleij, Bartosz Golaszewski,
	Greg Kroah-Hartman, Jiri Slaby, Andy Shevchenko, Catalin Marinas,
	Will Deacon, devicetree, linux-clk, linux-gpio, linux-serial,
	linux-kernel, Brian Masney, Lee Jones, mfd, Long Zhao, Long Zhao

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      | 863 +++++++++++++++++++++++++++++++
 drivers/pinctrl/pinctrl-ambarella.h      |  50 ++
 5 files changed, 1491 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..f9146d365c3c
--- /dev/null
+++ b/drivers/pinctrl/pinctrl-ambarella.c
@@ -0,0 +1,863 @@
+// 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/pm.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 amb_pinctrl_pm_state {
+	u32 iomux[3];
+	u32 pull[2];
+	u32 ds[3];
+};
+
+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;
+
+	struct amb_pinctrl_pm_state	pm[AMBA_MAX_BANKS];
+};
+
+/* 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_iomux_save_bank(struct amb_pinctrl_soc_data *soc, u32 bank)
+{
+	if (!amb_iomux_accessible(soc) || bank >= AMBA_MAX_BANKS)
+		return;
+
+	soc->pm[bank].iomux[0] =
+		readl_relaxed(soc->iomux_base + IOMUX_OFFSET(bank, 0));
+	soc->pm[bank].iomux[1] =
+		readl_relaxed(soc->iomux_base + IOMUX_OFFSET(bank, 1));
+	soc->pm[bank].iomux[2] =
+		readl_relaxed(soc->iomux_base + IOMUX_OFFSET(bank, 2));
+}
+
+static void amb_iomux_restore_bank(struct amb_pinctrl_soc_data *soc, u32 bank)
+{
+	if (!amb_iomux_accessible(soc) || bank >= AMBA_MAX_BANKS)
+		return;
+
+	writel_relaxed(soc->pm[bank].iomux[0],
+		       soc->iomux_base + IOMUX_OFFSET(bank, 0));
+	writel_relaxed(soc->pm[bank].iomux[1],
+		       soc->iomux_base + IOMUX_OFFSET(bank, 1));
+	writel_relaxed(soc->pm[bank].iomux[2],
+		       soc->iomux_base + IOMUX_OFFSET(bank, 2));
+}
+
+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!");
+
+	platform_set_drvdata(pdev, soc);
+	dev_info(&pdev->dev, "Ambarella pinctrl driver registered");
+
+	return 0;
+}
+
+static int amb_pinctrl_suspend(struct device *dev)
+{
+	struct amb_pinctrl_soc_data *soc = dev_get_drvdata(dev);
+	u32 bank, dedicated = soc->data->clk_au_dedicated_pin;
+
+	for (bank = 0; bank < soc->data->nr_banks; bank++) {
+		regmap_read(soc->pull_regmap, soc->data->pull_en[bank],
+			    &soc->pm[bank].pull[0]);
+		regmap_read(soc->pull_regmap, soc->data->pull_dir[bank],
+			    &soc->pm[bank].pull[1]);
+
+		regmap_read(soc->ds_regmap, soc->data->ds0[bank],
+			    &soc->pm[bank].ds[0]);
+		regmap_read(soc->ds_regmap, soc->data->ds1[bank],
+			    &soc->pm[bank].ds[1]);
+		if (soc->data->have_ds2)
+			regmap_read(soc->ds_regmap, soc->data->ds2[bank],
+				    &soc->pm[bank].ds[2]);
+
+		amb_iomux_save_bank(soc, bank);
+	}
+
+	if (dedicated >= soc->data->nr_banks * 32)
+		amb_iomux_save_bank(soc, PINID_TO_BANK(dedicated));
+
+	return 0;
+}
+
+static int amb_pinctrl_resume(struct device *dev)
+{
+	struct amb_pinctrl_soc_data *soc = dev_get_drvdata(dev);
+	u32 bank, dedicated = soc->data->clk_au_dedicated_pin;
+
+	for (bank = 0; bank < soc->data->nr_banks; bank++)
+		amb_iomux_restore_bank(soc, bank);
+
+	if (dedicated >= soc->data->nr_banks * 32)
+		amb_iomux_restore_bank(soc, PINID_TO_BANK(dedicated));
+
+	/* Ensure restored IOMUX writes land before the commit strobe. */
+	wmb();
+	amb_iomux_commit(soc);
+
+	for (bank = 0; bank < soc->data->nr_banks; bank++) {
+		regmap_write(soc->ds_regmap, soc->data->ds0[bank],
+			     soc->pm[bank].ds[0]);
+		regmap_write(soc->ds_regmap, soc->data->ds1[bank],
+			     soc->pm[bank].ds[1]);
+		if (soc->data->have_ds2)
+			regmap_write(soc->ds_regmap, soc->data->ds2[bank],
+				     soc->pm[bank].ds[2]);
+
+		regmap_write(soc->pull_regmap, soc->data->pull_dir[bank],
+			     soc->pm[bank].pull[1]);
+		regmap_write(soc->pull_regmap, soc->data->pull_en[bank],
+			     soc->pm[bank].pull[0]);
+	}
+
+	return 0;
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(amb_pinctrl_pm_ops,
+				amb_pinctrl_suspend,
+				amb_pinctrl_resume);
+
+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),
+		.pm = pm_sleep_ptr(&amb_pinctrl_pm_ops),
+	},
+};
+
+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] 15+ messages in thread

* [PATCH v5 10/12] serial: 8250: add Ambarella UART driver
  2026-08-18 10:31 [PATCH v5 00/12] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
                   ` (8 preceding siblings ...)
  2026-08-18 10:31 ` [PATCH v5 09/12] pinctrl: ambarella: add CV75 pin controller Long Zhao via B4 Relay
@ 2026-08-18 10:31 ` Long Zhao via B4 Relay
  2026-08-18 14:17   ` Andy Shevchenko
  2026-08-18 10:31 ` [PATCH v5 11/12] arm64: ambarella: add ARCH_AMBARELLA and CV75 EVK DT Long Zhao via B4 Relay
  2026-08-18 10:31 ` [PATCH v5 12/12] MAINTAINERS: add ARM/AMBARELLA SoC support Long Zhao via B4 Relay
  11 siblings, 1 reply; 15+ messages in thread
From: Long Zhao via B4 Relay @ 2026-08-18 10:31 UTC (permalink / raw)
  To: Arnd Bergmann, Krzysztof Kozlowski, Alexandre Belloni, soc,
	linux-arm-kernel
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
	Stephen Boyd, Linus Walleij, Bartosz Golaszewski,
	Greg Kroah-Hartman, Jiri Slaby, Andy Shevchenko, Catalin Marinas,
	Will Deacon, devicetree, linux-clk, linux-gpio, linux-serial,
	linux-kernel, Brian Masney, Lee Jones, mfd, Long Zhao, Long Zhao

From: Long Zhao <longzhao@ambarella.com>

Add an 8250 platform driver for Ambarella CV75 instead of claiming
snps,dw-apb-uart compatibility.

Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
 drivers/tty/serial/8250/8250_ambarella.c | 312 +++++++++++++++++++++++++++++++
 drivers/tty/serial/8250/Kconfig          |   9 +
 drivers/tty/serial/8250/Makefile         |   1 +
 3 files changed, 322 insertions(+)

diff --git a/drivers/tty/serial/8250/8250_ambarella.c b/drivers/tty/serial/8250/8250_ambarella.c
new file mode 100644
index 000000000000..951355e19fa2
--- /dev/null
+++ b/drivers/tty/serial/8250/8250_ambarella.c
@@ -0,0 +1,312 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Serial port driver for Ambarella UART
+ *
+ * Copyright (C) 2026 Ambarella, Inc.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/serial_8250.h>
+#include <linux/serial_reg.h>
+
+#include "8250.h"
+
+#define AMBARELLA_UART_IER_ETOI		BIT(5)
+#define AMBARELLA_UART_USR		0x1f
+#define AMBARELLA_UART_USR_BUSY		BIT(0)
+#define AMBARELLA_UART_IIR_IID		GENMASK(3, 0)
+#define AMBARELLA_UART_IIR_STATUS	GENMASK(5, 0)
+
+struct ambarella_uart_soc_data {
+	unsigned int	fifo_size;
+};
+
+struct ambarella_uart {
+	int					line;
+	struct clk				*clk;
+	const struct ambarella_uart_soc_data	*soc_data;
+	bool					in_idle;
+};
+
+static void ambarella_idle_exit(struct uart_port *p)
+{
+	struct ambarella_uart *data = p->private_data;
+	struct uart_8250_port *up = up_to_u8250p(p);
+
+	if (up->capabilities & UART_CAP_FIFO)
+		writel(up->fcr, p->membase + (UART_FCR << p->regshift));
+	writel(up->mcr, p->membase + (UART_MCR << p->regshift));
+	writel(up->ier, p->membase + (UART_IER << p->regshift));
+
+	data->in_idle = false;
+}
+
+static int ambarella_idle_enter(struct uart_port *p)
+{
+	struct ambarella_uart *data = p->private_data;
+	struct uart_8250_port *up = up_to_u8250p(p);
+	unsigned int status;
+	int retries = 4;
+
+	lockdep_assert_held_once(&p->lock);
+
+	data->in_idle = true;
+	writel(0, p->membase + (UART_IER << p->regshift));
+
+	serial8250_fifo_wait_for_lsr_thre(up, p->fifosize);
+	ndelay(p->frame_time);
+	writel(up->mcr | UART_MCR_LOOP,
+	       p->membase + (UART_MCR << p->regshift));
+
+	do {
+		serial8250_clear_fifos(up);
+		if (!(readl(p->membase +
+			    (AMBARELLA_UART_USR << p->regshift)) &
+		      AMBARELLA_UART_USR_BUSY))
+			break;
+		ndelay(p->frame_time);
+	} while (--retries);
+
+	status = serial_lsr_in(up);
+	if (status & UART_LSR_DR) {
+		readl(p->membase + (UART_RX << p->regshift));
+		up->lsr_saved_flags = 0;
+	}
+
+	if (readl(p->membase + (AMBARELLA_UART_USR << p->regshift)) &
+	    AMBARELLA_UART_USR_BUSY) {
+		ambarella_idle_exit(p);
+		return -EBUSY;
+	}
+
+	return 0;
+}
+
+static void ambarella_serial_out(struct uart_port *p, unsigned int offset,
+				 u32 value)
+{
+	struct ambarella_uart *data = p->private_data;
+	u32 lcr;
+
+	if (offset != UART_LCR || !data || data->in_idle) {
+		writel(value, p->membase + (offset << p->regshift));
+		return;
+	}
+
+	lcr = readl(p->membase + (UART_LCR << p->regshift));
+	if (lcr == value)
+		return;
+
+	writel(value, p->membase + (UART_LCR << p->regshift));
+	lcr = readl(p->membase + (UART_LCR << p->regshift));
+	if ((lcr & ~UART_LCR_SPAR) == (value & ~UART_LCR_SPAR))
+		return;
+
+	if (ambarella_idle_enter(p))
+		return;
+
+	writel(value, p->membase + (UART_LCR << p->regshift));
+	ambarella_idle_exit(p);
+}
+
+static u32 ambarella_serial_in(struct uart_port *p, unsigned int offset)
+{
+	return readl(p->membase + (offset << p->regshift));
+}
+
+static int ambarella_handle_irq(struct uart_port *p)
+{
+	struct uart_8250_port *up = up_to_u8250p(p);
+	unsigned int iir = serial_port_in(p, UART_IIR);
+	bool rx_timeout = (iir & AMBARELLA_UART_IIR_STATUS) ==
+			  UART_IIR_RX_TIMEOUT;
+	unsigned int status;
+
+	guard(uart_port_lock_check_sysrq_irqsave)(p);
+
+	switch (FIELD_GET(AMBARELLA_UART_IIR_IID, iir)) {
+	case UART_IIR_NO_INT:
+		return 0;
+	case UART_IIR_BUSY:
+		serial_port_in(p, AMBARELLA_UART_USR);
+		return 1;
+	}
+
+	if (rx_timeout) {
+		status = serial_lsr_in(up);
+		if (!(status & (UART_LSR_DR | UART_LSR_BI)))
+			serial_port_in(p, UART_RX);
+	}
+
+	serial8250_handle_irq_locked(p, iir);
+	return 1;
+}
+
+static void ambarella_set_divisor(struct uart_port *p, unsigned int baud,
+				  unsigned int quot, unsigned int quot_frac)
+{
+	struct uart_8250_port *up = up_to_u8250p(p);
+
+	if (ambarella_idle_enter(p))
+		return;
+
+	serial_port_out(p, UART_LCR, up->lcr | UART_LCR_DLAB);
+	if (serial_port_in(p, UART_LCR) & UART_LCR_DLAB)
+		serial_dl_write(up, quot);
+	serial_port_out(p, UART_LCR, up->lcr);
+
+	ambarella_idle_exit(p);
+}
+
+static int ambarella_startup(struct uart_port *port)
+{
+	struct uart_8250_port *up = up_to_u8250p(port);
+	int ret;
+
+	ret = serial8250_do_startup(port);
+	if (ret)
+		return ret;
+
+	up->ier |= AMBARELLA_UART_IER_ETOI;
+	serial_port_out(port, UART_IER, up->ier);
+
+	return 0;
+}
+
+static int ambarella_uart_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct ambarella_uart *data;
+	struct uart_8250_port uart = {};
+	struct resource *regs;
+	u32 reg_io_width = 4;
+	u32 reg_shift = 2;
+	int irq, ret;
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0)
+		return irq;
+
+	regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!regs)
+		return dev_err_probe(dev, -EINVAL, "missing registers\n");
+
+	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->soc_data = device_get_match_data(dev);
+	if (!data->soc_data)
+		return -EINVAL;
+
+	data->clk = devm_clk_get_enabled(dev, NULL);
+	if (IS_ERR(data->clk))
+		return dev_err_probe(dev, PTR_ERR(data->clk),
+				     "unable to get clock\n");
+
+	uart.port.dev = dev;
+	uart.port.mapbase = regs->start;
+	uart.port.mapsize = resource_size(regs);
+	uart.port.membase = devm_ioremap(dev, regs->start,
+					 resource_size(regs));
+	if (!uart.port.membase)
+		return -ENOMEM;
+
+	uart.port.irq = irq;
+	uart.port.type = PORT_16550A;
+	uart.port.flags = UPF_FIXED_PORT | UPF_FIXED_TYPE | UPF_SKIP_TEST;
+	uart.port.startup = ambarella_startup;
+	uart.port.handle_irq = ambarella_handle_irq;
+	uart.port.set_divisor = ambarella_set_divisor;
+	uart.port.uartclk = clk_get_rate(data->clk);
+	if (!uart.port.uartclk)
+		return dev_err_probe(dev, -EINVAL, "clock rate not defined\n");
+
+	uart.port.fifosize = data->soc_data->fifo_size;
+	uart.port.private_data = data;
+	uart.capabilities = UART_CAP_FIFO | UART_CAP_NOTEMT | UART_CAP_AFE;
+
+	of_property_read_u32(dev->of_node, "reg-shift", &reg_shift);
+	of_property_read_u32(dev->of_node, "reg-io-width", &reg_io_width);
+	uart.port.regshift = reg_shift;
+
+	switch (reg_io_width) {
+	case 4:
+		uart.port.iotype = UPIO_MEM32;
+		uart.port.serial_in = ambarella_serial_in;
+		uart.port.serial_out = ambarella_serial_out;
+		break;
+	default:
+		return dev_err_probe(dev, -EINVAL,
+				     "unsupported reg-io-width %u\n",
+				     reg_io_width);
+	}
+
+	ret = serial8250_register_8250_port(&uart);
+	if (ret < 0)
+		return dev_err_probe(dev, ret, "unable to register 8250 port\n");
+
+	data->line = ret;
+	platform_set_drvdata(pdev, data);
+	return 0;
+}
+
+static void ambarella_uart_remove(struct platform_device *pdev)
+{
+	struct ambarella_uart *data = platform_get_drvdata(pdev);
+
+	serial8250_unregister_port(data->line);
+}
+
+static const struct ambarella_uart_soc_data cv75_uart_data = {
+	.fifo_size = 64,
+};
+
+static const struct of_device_id ambarella_uart_dt_ids[] = {
+	{
+		.compatible = "ambarella,cv75-uart",
+		.data = &cv75_uart_data,
+	},
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, ambarella_uart_dt_ids);
+
+static struct platform_driver ambarella_uart_platform_driver = {
+	.driver = {
+		.name		= "8250-ambarella",
+		.of_match_table	= ambarella_uart_dt_ids,
+	},
+	.probe	= ambarella_uart_probe,
+	.remove	= ambarella_uart_remove,
+};
+
+module_platform_driver(ambarella_uart_platform_driver);
+
+static int __init ambarella_early_setup(struct earlycon_device *device,
+					const char *options)
+{
+	struct uart_port *port = &device->port;
+
+	if (!port->membase)
+		return -ENODEV;
+
+	port->iotype = UPIO_MEM32;
+	port->regshift = 2;
+	port->serial_in = ambarella_serial_in;
+	port->serial_out = ambarella_serial_out;
+
+	return early_serial8250_setup(device, options);
+}
+
+OF_EARLYCON_DECLARE(ambarella, "ambarella,cv75-uart", ambarella_early_setup);
+
+MODULE_IMPORT_NS("SERIAL_8250");
+MODULE_AUTHOR("Long Zhao <longzhao@ambarella.com>");
+MODULE_DESCRIPTION("Ambarella UART driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/tty/serial/8250/Kconfig b/drivers/tty/serial/8250/Kconfig
index fc3e58d62233..523c55be3227 100644
--- a/drivers/tty/serial/8250/Kconfig
+++ b/drivers/tty/serial/8250/Kconfig
@@ -337,6 +337,15 @@ config SERIAL_8250_ACORN
 	  system, say Y to this option.  The driver can handle 1, 2, or 3 port
 	  cards.  If unsure, say N.
 
+config SERIAL_8250_AMBARELLA
+	tristate "Ambarella UART support"
+	depends on SERIAL_8250 && HAVE_CLK
+	depends on ARCH_AMBARELLA || COMPILE_TEST
+	default y if ARCH_AMBARELLA
+	help
+	  Support for the on-chip UART found on Ambarella SoCs.
+	  If unsure, say N.
+
 config SERIAL_8250_BCM2835AUX
 	tristate "BCM2835 auxiliar mini UART support"
 	depends on ARCH_BCM2835 || COMPILE_TEST
diff --git a/drivers/tty/serial/8250/Makefile b/drivers/tty/serial/8250/Makefile
index 6d21402b4435..601d9e2589fe 100644
--- a/drivers/tty/serial/8250/Makefile
+++ b/drivers/tty/serial/8250/Makefile
@@ -20,6 +20,7 @@ obj-$(CONFIG_SERIAL_8250_CONSOLE)	+= 8250_early.o
 
 obj-$(CONFIG_SERIAL_8250_ACCENT)	+= 8250_accent.o
 obj-$(CONFIG_SERIAL_8250_ACORN)		+= 8250_acorn.o
+obj-$(CONFIG_SERIAL_8250_AMBARELLA)	+= 8250_ambarella.o
 obj-$(CONFIG_SERIAL_8250_ASPEED_VUART)	+= 8250_aspeed_vuart.o
 obj-$(CONFIG_SERIAL_8250_BCM2835AUX)	+= 8250_bcm2835aux.o
 obj-$(CONFIG_SERIAL_8250_BCM7271)	+= 8250_bcm7271.o

-- 
2.34.1




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

* [PATCH v5 11/12] arm64: ambarella: add ARCH_AMBARELLA and CV75 EVK DT
  2026-08-18 10:31 [PATCH v5 00/12] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
                   ` (9 preceding siblings ...)
  2026-08-18 10:31 ` [PATCH v5 10/12] serial: 8250: add Ambarella UART driver Long Zhao via B4 Relay
@ 2026-08-18 10:31 ` Long Zhao via B4 Relay
  2026-08-18 10:31 ` [PATCH v5 12/12] MAINTAINERS: add ARM/AMBARELLA SoC support Long Zhao via B4 Relay
  11 siblings, 0 replies; 15+ messages in thread
From: Long Zhao via B4 Relay @ 2026-08-18 10:31 UTC (permalink / raw)
  To: Arnd Bergmann, Krzysztof Kozlowski, Alexandre Belloni, soc,
	linux-arm-kernel
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
	Stephen Boyd, Linus Walleij, Bartosz Golaszewski,
	Greg Kroah-Hartman, Jiri Slaby, Andy Shevchenko, Catalin Marinas,
	Will Deacon, devicetree, linux-clk, linux-gpio, linux-serial,
	linux-kernel, Brian Masney, Lee Jones, mfd, Long Zhao, Long Zhao

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               |   8 ++
 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    | 176 +++++++++++++++++++++++++++++
 arch/arm64/configs/defconfig               |   2 +
 6 files changed, 220 insertions(+)

diff --git a/arch/arm64/Kconfig.platforms b/arch/arm64/Kconfig.platforms
index d2acfac73003..cb05eff267a8 100644
--- a/arch/arm64/Kconfig.platforms
+++ b/arch/arm64/Kconfig.platforms
@@ -26,6 +26,14 @@ config ARCH_SUNXI
 	help
 	  This enables support for Allwinner sunxi based SoCs like the A64.
 
+config ARCH_AMBARELLA
+	bool "Ambarella SoC family"
+	select ARM_PSCI
+	select HAVE_ARM_ARCH_TIMER
+	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..3b7948954968
--- /dev/null
+++ b/arch/arm64/boot/dts/ambarella/cv75.dtsi
@@ -0,0 +1,176 @@
+// 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";
+				reg = <0xe4000000 0x1000>;
+				clocks = <&rct CV75_GCLK_UART0>;
+				interrupts = <GIC_SPI 170 IRQ_TYPE_LEVEL_HIGH>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&uart0_pins>;
+				reg-io-width = <4>;
+				reg-shift = <2>;
+
+				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 = <0x00000061>;
+			};
+
+			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 = <0x00000061>;
+			};
+
+			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 = <0x00000061>;
+			};
+		};
+
+		rct: clock-controller@ffed080000 {
+			compatible = "ambarella,cv75-rct", "syscon";
+			reg = <0xff 0xed080000 0x0 0x1000>;
+			clocks = <&osc>;
+			clock-names = "osc";
+			#clock-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..ae5bcc6c9fc7 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
@@ -560,6 +561,7 @@ CONFIG_SERIAL_8250_CONSOLE=y
 CONFIG_SERIAL_8250_NR_UARTS=8
 CONFIG_SERIAL_8250_EXTENDED=y
 CONFIG_SERIAL_8250_SHARE_IRQ=y
+CONFIG_SERIAL_8250_AMBARELLA=y
 CONFIG_SERIAL_8250_BCM2835AUX=y
 CONFIG_SERIAL_8250_DW=y
 CONFIG_SERIAL_8250_EM=y

-- 
2.34.1




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

* [PATCH v5 12/12] MAINTAINERS: add ARM/AMBARELLA SoC support
  2026-08-18 10:31 [PATCH v5 00/12] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
                   ` (10 preceding siblings ...)
  2026-08-18 10:31 ` [PATCH v5 11/12] arm64: ambarella: add ARCH_AMBARELLA and CV75 EVK DT Long Zhao via B4 Relay
@ 2026-08-18 10:31 ` Long Zhao via B4 Relay
  11 siblings, 0 replies; 15+ messages in thread
From: Long Zhao via B4 Relay @ 2026-08-18 10:31 UTC (permalink / raw)
  To: Arnd Bergmann, Krzysztof Kozlowski, Alexandre Belloni, soc,
	linux-arm-kernel
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
	Stephen Boyd, Linus Walleij, Bartosz Golaszewski,
	Greg Kroah-Hartman, Jiri Slaby, Andy Shevchenko, Catalin Marinas,
	Will Deacon, devicetree, linux-clk, linux-gpio, linux-serial,
	linux-kernel, Brian Masney, Lee Jones, mfd, Long Zhao, Long Zhao

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 | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 803b60ad628f..4352a2dc1e3d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2502,6 +2502,22 @@ 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:	Documentation/devicetree/bindings/serial/ambarella,cv75-uart.yaml
+F:	arch/arm64/boot/dts/ambarella/
+F:	drivers/clk/ambarella/
+F:	drivers/pinctrl/pinctrl-ambarella*
+F:	drivers/tty/serial/8250/8250_ambarella.c
+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] 15+ messages in thread

* Re: [PATCH v5 08/12] gpio: pl061: add Ambarella CV75 register layout variant
  2026-08-18 10:31 ` [PATCH v5 08/12] gpio: pl061: add Ambarella CV75 register layout variant Long Zhao via B4 Relay
@ 2026-08-18 14:11   ` Andy Shevchenko
  0 siblings, 0 replies; 15+ messages in thread
From: Andy Shevchenko @ 2026-08-18 14:11 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,
	Catalin Marinas, Will Deacon, devicetree, linux-clk, linux-gpio,
	linux-serial, linux-kernel, Brian Masney, Lee Jones, mfd,
	Long Zhao

On Tue, Aug 18, 2026 at 06:31:22PM +0800, Long Zhao via B4 Relay wrote:

> Extend gpio-pl061 with a per-variant register layout so the Ambarella
> CV75 GPIO banks can reuse the PL061 driver instead of a duplicate.

This is reinvention of gpio-mmio.c (as a library). It has necessary facilities.
Also gpio-regmap may be considered. So, switch this driver to use one of the
above and the patch for the new device will be much simpler and elegant.

-- 
With Best Regards,
Andy Shevchenko




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

* Re: [PATCH v5 10/12] serial: 8250: add Ambarella UART driver
  2026-08-18 10:31 ` [PATCH v5 10/12] serial: 8250: add Ambarella UART driver Long Zhao via B4 Relay
@ 2026-08-18 14:17   ` Andy Shevchenko
  0 siblings, 0 replies; 15+ messages in thread
From: Andy Shevchenko @ 2026-08-18 14:17 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,
	Catalin Marinas, Will Deacon, devicetree, linux-clk, linux-gpio,
	linux-serial, linux-kernel, Brian Masney, Lee Jones, mfd,
	Long Zhao

On Tue, Aug 18, 2026 at 06:31:24PM +0800, Long Zhao via B4 Relay wrote:

> Add an 8250 platform driver for Ambarella CV75 instead of claiming
> snps,dw-apb-uart compatibility.

This is a record from a changelog. There is nothing in the commit message.
...

The code seems like an almost blind copy of 8250_dw.c.

> +#define AMBARELLA_UART_USR		0x1f
> +#define AMBARELLA_UART_USR_BUSY		BIT(0)

So, is it Synopsys IP-based design? How this gets into this platform? This
whole story completely made me lost.

> +#define AMBARELLA_UART_IIR_IID		GENMASK(3, 0)
> +#define AMBARELLA_UART_IIR_STATUS	GENMASK(5, 0)


...

> +	of_property_read_u32(dev->of_node, "reg-shift", &reg_shift);
> +	of_property_read_u32(dev->of_node, "reg-io-width", &reg_io_width);
> +	uart.port.regshift = reg_shift;

> +	switch (reg_io_width) {
> +	case 4:
> +		uart.port.iotype = UPIO_MEM32;
> +		uart.port.serial_in = ambarella_serial_in;
> +		uart.port.serial_out = ambarella_serial_out;
> +		break;
> +	default:
> +		return dev_err_probe(dev, -EINVAL,
> +				     "unsupported reg-io-width %u\n",
> +				     reg_io_width);
> +	}

This is handled by uart_read_port_properties().

-- 
With Best Regards,
Andy Shevchenko




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

end of thread, other threads:[~2026-08-18 14:17 UTC | newest]

Thread overview: 15+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-18 10:31 [PATCH v5 00/12] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
2026-08-18 10:31 ` [PATCH v5 01/12] dt-bindings: arm: add Ambarella CV75 platforms Long Zhao via B4 Relay
2026-08-18 10:31 ` [PATCH v5 02/12] dt-bindings: mfd: syscon: add Ambarella CV75 secure scratchpad Long Zhao via B4 Relay
2026-08-18 10:31 ` [PATCH v5 03/12] dt-bindings: clock: add Ambarella CV75 RCT Long Zhao via B4 Relay
2026-08-18 10:31 ` [PATCH v5 04/12] dt-bindings: pinctrl: add Ambarella CV75 pinctrl Long Zhao via B4 Relay
2026-08-18 10:31 ` [PATCH v5 05/12] dt-bindings: gpio: pl061: add Ambarella CV75 variant Long Zhao via B4 Relay
2026-08-18 10:31 ` [PATCH v5 06/12] dt-bindings: serial: add Ambarella CV75 UART Long Zhao via B4 Relay
2026-08-18 10:31 ` [PATCH v5 07/12] clk: ambarella: add CV75 CCU driver Long Zhao via B4 Relay
2026-08-18 10:31 ` [PATCH v5 08/12] gpio: pl061: add Ambarella CV75 register layout variant Long Zhao via B4 Relay
2026-08-18 14:11   ` Andy Shevchenko
2026-08-18 10:31 ` [PATCH v5 09/12] pinctrl: ambarella: add CV75 pin controller Long Zhao via B4 Relay
2026-08-18 10:31 ` [PATCH v5 10/12] serial: 8250: add Ambarella UART driver Long Zhao via B4 Relay
2026-08-18 14:17   ` Andy Shevchenko
2026-08-18 10:31 ` [PATCH v5 11/12] arm64: ambarella: add ARCH_AMBARELLA and CV75 EVK DT Long Zhao via B4 Relay
2026-08-18 10:31 ` [PATCH v5 12/12] MAINTAINERS: add ARM/AMBARELLA SoC support Long Zhao via B4 Relay

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