Linux-ARM-Kernel Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 00/10] Ambarella CV75 SoC minimal bring-up
@ 2026-08-06  9:34 Long Zhao via B4 Relay
  2026-08-06  9:34 ` [PATCH v2 01/10] dt-bindings: arm: add Ambarella CV75 platforms Long Zhao via B4 Relay
                   ` (9 more replies)
  0 siblings, 10 replies; 12+ messages in thread
From: Long Zhao via B4 Relay @ 2026-08-06  9:34 UTC (permalink / raw)
  To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
	Stephen Boyd, Linus Walleij, Greg Kroah-Hartman, Jiri Slaby,
	Catalin Marinas, Will Deacon, Arnd Bergmann, Alexandre Belloni
  Cc: devicetree, linux-arm-kernel, linux-kernel, linux-clk, linux-gpio,
	linux-serial, soc, Long Zhao

This series adds minimal Ambarella CV75 support sufficient for early
bring-up with a serial console.

It introduces DT bindings and drivers for the RCT clock controller,
pinctrl, and UART, plus ARCH_AMBARELLA, a minimal CV75 EVK device tree,
arm64 defconfig entries, and a MAINTAINERS entry.

The goal remains a small, reviewable console-capable baseline. Further
SoC peripherals can follow in later series.

This is v2. Link to v1:
https://lore.kernel.org/r/20260730104330.81367-1-longzhao@ambarella.com

Changes since v1:
- pinctrl: parent node only describes IOMUX; GPIO bank MMIO and IRQs
  live in gpio child nodes (Rob)
- pinctrl: switch DT/bindings to function + groups; keep pinmux data in
  the driver; drop include/dt-bindings/pinctrl/ambarella,pinctrl.h
  (Linus)
- pinctrl: split CV75 static data into pinctrl-ambarella-cv75.c
- clock binding: drop syscon-common and redundant assigned-clock*
  properties from the schema (Rob)
- serial: keep only SoC-specific compatible ambarella,cv75-uart
  (Krzysztof)
- scratchpad: drop generic ambarella,sec-scratchpad compatible
  (Krzysztof)
- bindings: rename ambarella,uart.yaml and ambarella,sec-scratchpad.yaml
  to SoC-specific ambarella,cv75-*.yaml names
- dts: fix GIC unit-address; add EL2 virtual timer PPI (Marc)
- uart: use container_of and guarded locks; keep break_ctl under the
  port lock; split earlycon putchar from the console path (Jiri)
- uart: convert console to nbcon; stop toggling IER around console
  writes; drain RBR before uart_handle_break()
- misc binding/DTS cleanups from review

Testing:
- Booted to console on CV75 EVK with initramfs
- scripts/checkpatch.pl --strict
- make dt_binding_check DT_SCHEMA_FILES=ambarella
- W=1 arm64 Image build for the new drivers/DTS

Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
Long Zhao (10):
      dt-bindings: arm: add Ambarella CV75 platforms
      dt-bindings: soc: add Ambarella secure scratchpad
      dt-bindings: clock: add Ambarella CV75 RCT clock controller
      dt-bindings: pinctrl: add Ambarella CV75 pinctrl
      dt-bindings: serial: add Ambarella UART
      clk: ambarella: add CV75 CCU driver
      pinctrl: ambarella: add Ambarella pin controller
      serial: ambarella: add Ambarella UART driver
      arm64: ambarella: add ARCH_AMBARELLA and CV75 EVK DT
      MAINTAINERS: add ARM/AMBARELLA SoC support

 .../devicetree/bindings/arm/ambarella.yaml         |   23 +
 .../bindings/clock/ambarella,cv75-rct.yaml         |   66 +
 .../bindings/pinctrl/ambarella,cv75-pinctrl.yaml   |  175 +++
 .../bindings/serial/ambarella,cv75-uart.yaml       |   45 +
 .../ambarella/ambarella,cv75-sec-scratchpad.yaml   |   39 +
 .../devicetree/bindings/vendor-prefixes.yaml       |    2 +
 MAINTAINERS                                        |   17 +
 arch/arm64/Kconfig.platforms                       |    9 +
 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            |  166 +++
 arch/arm64/configs/defconfig                       |    3 +
 drivers/clk/Kconfig                                |    1 +
 drivers/clk/Makefile                               |    1 +
 drivers/clk/ambarella/Kconfig                      |   16 +
 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                |  223 +++
 drivers/clk/ambarella/ccu_mux_div.h                |   35 +
 drivers/clk/ambarella/ccu_pll.c                    |  374 +++++
 drivers/clk/ambarella/ccu_pll.h                    |   70 +
 drivers/pinctrl/Kconfig                            |   15 +
 drivers/pinctrl/Makefile                           |    2 +
 drivers/pinctrl/pinctrl-ambarella-cv75.c           |   68 +
 drivers/pinctrl/pinctrl-ambarella.c                | 1510 ++++++++++++++++++++
 drivers/pinctrl/pinctrl-ambarella.h                |   48 +
 drivers/tty/serial/Kconfig                         |   21 +
 drivers/tty/serial/Makefile                        |    1 +
 drivers/tty/serial/ambarella_uart.c                | 1001 +++++++++++++
 include/dt-bindings/clock/ambarella,cv75-clock.h   |   17 +
 33 files changed, 4369 insertions(+)
---
base-commit: 34cf6dafc47441dfb6b356a095b89c3585a93714
change-id: 20260804-longzhao-upstream-cv75-v2-c2597cb996ce

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




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

* [PATCH v2 01/10] dt-bindings: arm: add Ambarella CV75 platforms
  2026-08-06  9:34 [PATCH v2 00/10] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
@ 2026-08-06  9:34 ` Long Zhao via B4 Relay
  2026-08-06  9:34 ` [PATCH v2 02/10] dt-bindings: soc: add Ambarella secure scratchpad Long Zhao via B4 Relay
                   ` (8 subsequent siblings)
  9 siblings, 0 replies; 12+ messages in thread
From: Long Zhao via B4 Relay @ 2026-08-06  9:34 UTC (permalink / raw)
  To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
	Stephen Boyd, Linus Walleij, Greg Kroah-Hartman, Jiri Slaby,
	Catalin Marinas, Will Deacon, Arnd Bergmann, Alexandre Belloni
  Cc: devicetree, linux-arm-kernel, linux-kernel, linux-clk, linux-gpio,
	linux-serial, soc, Long Zhao

From: Long Zhao <longzhao@ambarella.com>

Add the Ambarella SoC board binding and vendor prefix covering
the CV75 EVK.

Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
 .../devicetree/bindings/arm/ambarella.yaml         | 23 ++++++++++++++++++++++
 .../devicetree/bindings/vendor-prefixes.yaml       |  2 ++
 2 files changed, 25 insertions(+)

diff --git a/Documentation/devicetree/bindings/arm/ambarella.yaml b/Documentation/devicetree/bindings/arm/ambarella.yaml
new file mode 100644
index 000000000000..347148530f80
--- /dev/null
+++ b/Documentation/devicetree/bindings/arm/ambarella.yaml
@@ -0,0 +1,23 @@
+# 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 EVK board
+        items:
+          - const: 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] 12+ messages in thread

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

From: Long Zhao <longzhao@ambarella.com>

Document the Ambarella secure scratchpad syscon used by CV75.

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

diff --git a/Documentation/devicetree/bindings/soc/ambarella/ambarella,cv75-sec-scratchpad.yaml b/Documentation/devicetree/bindings/soc/ambarella/ambarella,cv75-sec-scratchpad.yaml
new file mode 100644
index 000000000000..4bfeca0ac3da
--- /dev/null
+++ b/Documentation/devicetree/bindings/soc/ambarella/ambarella,cv75-sec-scratchpad.yaml
@@ -0,0 +1,39 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/soc/ambarella/ambarella,cv75-sec-scratchpad.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Ambarella CV75 Secure Scratchpad Registers
+
+maintainers:
+  - Long Zhao <longzhao@ambarella.com>
+
+description:
+  The secure scratchpad block contains shared system registers used by
+  other Ambarella SoC blocks through syscon regmap lookups.
+
+allOf:
+  - $ref: /schemas/mfd/syscon-common.yaml#
+
+properties:
+  compatible:
+    items:
+      - const: ambarella,cv75-sec-scratchpad
+      - const: syscon
+
+  reg:
+    maxItems: 1
+
+required:
+  - compatible
+  - reg
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    scratchpad@e002e000 {
+        compatible = "ambarella,cv75-sec-scratchpad", "syscon";
+        reg = <0xe002e000 0x1000>;
+    };

-- 
2.34.1




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

* [PATCH v2 03/10] dt-bindings: clock: add Ambarella CV75 RCT clock controller
  2026-08-06  9:34 [PATCH v2 00/10] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
  2026-08-06  9:34 ` [PATCH v2 01/10] dt-bindings: arm: add Ambarella CV75 platforms Long Zhao via B4 Relay
  2026-08-06  9:34 ` [PATCH v2 02/10] dt-bindings: soc: add Ambarella secure scratchpad Long Zhao via B4 Relay
@ 2026-08-06  9:34 ` Long Zhao via B4 Relay
  2026-08-06 10:56   ` Rob Herring (Arm)
  2026-08-06  9:34 ` [PATCH v2 04/10] dt-bindings: pinctrl: add Ambarella CV75 pinctrl Long Zhao via B4 Relay
                   ` (6 subsequent siblings)
  9 siblings, 1 reply; 12+ messages in thread
From: Long Zhao via B4 Relay @ 2026-08-06  9:34 UTC (permalink / raw)
  To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
	Stephen Boyd, Linus Walleij, Greg Kroah-Hartman, Jiri Slaby,
	Catalin Marinas, Will Deacon, Arnd Bergmann, Alexandre Belloni
  Cc: devicetree, linux-arm-kernel, linux-kernel, linux-clk, linux-gpio,
	linux-serial, soc, Long Zhao

From: Long Zhao <longzhao@ambarella.com>

Add the CV75 RCT clock controller binding and clock ID header.

Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
 .../bindings/clock/ambarella,cv75-rct.yaml         | 66 ++++++++++++++++++++++
 include/dt-bindings/clock/ambarella,cv75-clock.h   | 17 ++++++
 2 files changed, 83 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..aa96533bab9b
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/ambarella,cv75-rct.yaml
@@ -0,0 +1,66 @@
+# 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-clock.h:
+
+  - CV75_GCLK_UART0: UART0 mux/div clock
+  - CV75_GCLK_CORE: Core PLL (gclk_core)
+  - CV75_GCLK_AHB: AHB bus clock (core / 2)
+  - CV75_GCLK_APB: APB bus clock (core / 4)
+
+properties:
+  $nodename:
+    pattern: "^clock-controller@"
+
+  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-clock.h b/include/dt-bindings/clock/ambarella,cv75-clock.h
new file mode 100644
index 000000000000..3d3314446e75
--- /dev/null
+++ b/include/dt-bindings/clock/ambarella,cv75-clock.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause */
+/*
+ * Copyright (C) 2026 Ambarella, Inc.
+ */
+
+#ifndef _DT_BINDINGS_CLOCK_AMBARELLA_CV75_CLOCK_H
+#define _DT_BINDINGS_CLOCK_AMBARELLA_CV75_CLOCK_H
+
+/* Boot / bus clocks */
+#define CV75_GCLK_UART0		0
+#define CV75_GCLK_CORE		1
+#define CV75_GCLK_AHB		2
+#define CV75_GCLK_APB		3
+
+#define CV75_CLK_NUM		4
+
+#endif

-- 
2.34.1




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

* [PATCH v2 04/10] dt-bindings: pinctrl: add Ambarella CV75 pinctrl
  2026-08-06  9:34 [PATCH v2 00/10] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
                   ` (2 preceding siblings ...)
  2026-08-06  9:34 ` [PATCH v2 03/10] dt-bindings: clock: add Ambarella CV75 RCT clock controller Long Zhao via B4 Relay
@ 2026-08-06  9:34 ` Long Zhao via B4 Relay
  2026-08-06  9:34 ` [PATCH v2 05/10] dt-bindings: serial: add Ambarella UART Long Zhao via B4 Relay
                   ` (5 subsequent siblings)
  9 siblings, 0 replies; 12+ messages in thread
From: Long Zhao via B4 Relay @ 2026-08-06  9:34 UTC (permalink / raw)
  To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
	Stephen Boyd, Linus Walleij, Greg Kroah-Hartman, Jiri Slaby,
	Catalin Marinas, Will Deacon, Arnd Bergmann, Alexandre Belloni
  Cc: devicetree, linux-arm-kernel, linux-kernel, linux-clk, linux-gpio,
	linux-serial, soc, Long Zhao

From: Long Zhao <longzhao@ambarella.com>

Add the CV75 pin controller binding.

Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
 .../bindings/pinctrl/ambarella,cv75-pinctrl.yaml   | 175 +++++++++++++++++++++
 1 file changed, 175 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..3fa30b275e51
--- /dev/null
+++ b/Documentation/devicetree/bindings/pinctrl/ambarella,cv75-pinctrl.yaml
@@ -0,0 +1,175 @@
+# 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.
+
+  "#address-cells":
+    const: 1
+
+  "#size-cells":
+    const: 1
+
+  ranges: true
+
+patternProperties:
+  "^gpio@[0-9a-f]+$":
+    type: object
+    additionalProperties: false
+
+    properties:
+      reg:
+        maxItems: 1
+
+      interrupts:
+        maxItems: 1
+
+      gpio-controller: true
+
+      "#gpio-cells":
+        const: 2
+
+      gpio-ranges:
+        maxItems: 1
+
+      interrupt-controller: true
+
+      "#interrupt-cells":
+        const: 2
+
+    required:
+      - reg
+      - interrupts
+      - gpio-controller
+      - "#gpio-cells"
+      - gpio-ranges
+      - interrupt-controller
+      - "#interrupt-cells"
+
+  "^[a-z0-9-]+-pins$":
+    type: object
+    allOf:
+      - $ref: pinmux-node.yaml#
+      - $ref: pincfg-node.yaml#
+    additionalProperties: false
+
+    properties:
+      function:
+        const: uart0
+
+      groups:
+        $ref: /schemas/types.yaml#/definitions/string-array
+        items:
+          enum: [uart0tx, uart0rx]
+        minItems: 2
+        maxItems: 2
+        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
+  - "#address-cells"
+  - "#size-cells"
+  - ranges
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/arm-gic.h>
+
+    rct: rct {
+    };
+
+    scratchpad: scratchpad {
+    };
+
+    pinctrl: pinctrl@e4010000 {
+        compatible = "ambarella,cv75-pinctrl";
+        #address-cells = <1>;
+        #size-cells = <1>;
+        reg = <0xe4010000 0x1000>;
+        ranges;
+        ambarella,drive-strength-syscon = <&rct>;
+        ambarella,pull-syscon = <&scratchpad>;
+
+        gpio0: gpio@e4013000 {
+            reg = <0xe4013000 0x1000>;
+            interrupts = <GIC_SPI 163 IRQ_TYPE_LEVEL_HIGH>;
+            gpio-controller;
+            #gpio-cells = <2>;
+            gpio-ranges = <&pinctrl 0 0 32>;
+            interrupt-controller;
+            #interrupt-cells = <2>;
+        };
+
+        gpio1: gpio@e4014000 {
+            reg = <0xe4014000 0x1000>;
+            interrupts = <GIC_SPI 164 IRQ_TYPE_LEVEL_HIGH>;
+            gpio-controller;
+            #gpio-cells = <2>;
+            gpio-ranges = <&pinctrl 0 32 32>;
+            interrupt-controller;
+            #interrupt-cells = <2>;
+        };
+
+        gpio2: gpio@e4015000 {
+            reg = <0xe4015000 0x1000>;
+            interrupts = <GIC_SPI 165 IRQ_TYPE_LEVEL_HIGH>;
+            gpio-controller;
+            #gpio-cells = <2>;
+            gpio-ranges = <&pinctrl 0 64 32>;
+            interrupt-controller;
+            #interrupt-cells = <2>;
+        };
+
+        uart0-pins {
+            function = "uart0";
+            groups = "uart0tx", "uart0rx";
+            bias-disable;
+            drive-strength = <8>;
+        };
+    };

-- 
2.34.1




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

* [PATCH v2 05/10] dt-bindings: serial: add Ambarella UART
  2026-08-06  9:34 [PATCH v2 00/10] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
                   ` (3 preceding siblings ...)
  2026-08-06  9:34 ` [PATCH v2 04/10] dt-bindings: pinctrl: add Ambarella CV75 pinctrl Long Zhao via B4 Relay
@ 2026-08-06  9:34 ` Long Zhao via B4 Relay
  2026-08-06  9:34 ` [PATCH v2 06/10] clk: ambarella: add CV75 CCU driver Long Zhao via B4 Relay
                   ` (4 subsequent siblings)
  9 siblings, 0 replies; 12+ messages in thread
From: Long Zhao via B4 Relay @ 2026-08-06  9:34 UTC (permalink / raw)
  To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
	Stephen Boyd, Linus Walleij, Greg Kroah-Hartman, Jiri Slaby,
	Catalin Marinas, Will Deacon, Arnd Bergmann, Alexandre Belloni
  Cc: devicetree, linux-arm-kernel, linux-kernel, linux-clk, linux-gpio,
	linux-serial, soc, Long Zhao

From: Long Zhao <longzhao@ambarella.com>

Document the Ambarella UART controller.

Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
 .../bindings/serial/ambarella,cv75-uart.yaml       | 45 ++++++++++++++++++++++
 1 file changed, 45 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..86d6d8a61ace
--- /dev/null
+++ b/Documentation/devicetree/bindings/serial/ambarella,cv75-uart.yaml
@@ -0,0 +1,45 @@
+# 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
+
+required:
+  - compatible
+  - reg
+  - interrupts
+  - clocks
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/arm-gic.h>
+
+    serial@e4000000 {
+        compatible = "ambarella,cv75-uart";
+        reg = <0xe4000000 0x1000>;
+        interrupts = <GIC_SPI 170 IRQ_TYPE_LEVEL_HIGH>;
+        clocks = <&uart_clk>;
+    };

-- 
2.34.1




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

* [PATCH v2 06/10] clk: ambarella: add CV75 CCU driver
  2026-08-06  9:34 [PATCH v2 00/10] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
                   ` (4 preceding siblings ...)
  2026-08-06  9:34 ` [PATCH v2 05/10] dt-bindings: serial: add Ambarella UART Long Zhao via B4 Relay
@ 2026-08-06  9:34 ` Long Zhao via B4 Relay
  2026-08-06  9:34 ` [PATCH v2 07/10] pinctrl: ambarella: add Ambarella pin controller Long Zhao via B4 Relay
                   ` (3 subsequent siblings)
  9 siblings, 0 replies; 12+ messages in thread
From: Long Zhao via B4 Relay @ 2026-08-06  9:34 UTC (permalink / raw)
  To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
	Stephen Boyd, Linus Walleij, Greg Kroah-Hartman, Jiri Slaby,
	Catalin Marinas, Will Deacon, Arnd Bergmann, Alexandre Belloni
  Cc: devicetree, linux-arm-kernel, linux-kernel, linux-clk, linux-gpio,
	linux-serial, soc, Long Zhao

From: Long Zhao <longzhao@ambarella.com>

Add a minimal Ambarella clock controller for CV75 covering the
UART0 and core/ahb/apb clocks required 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       |  16 ++
 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 | 223 +++++++++++++++++++++
 drivers/clk/ambarella/ccu_mux_div.h |  35 ++++
 drivers/clk/ambarella/ccu_pll.c     | 374 ++++++++++++++++++++++++++++++++++++
 drivers/clk/ambarella/ccu_pll.h     |  70 +++++++
 11 files changed, 1107 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..639993033f96
--- /dev/null
+++ b/drivers/clk/ambarella/Kconfig
@@ -0,0 +1,16 @@
+# 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 support for the Reset and Clock Tree controller
+	  found on Ambarella CV75 SoCs. The controller provides the core PLL,
+	  bus clocks and UART clock required during early platform boot.
+	  It is needed to use the serial console on CV75-based systems.
+
+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..55132dfa7431
--- /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-clock.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, CV75_CLK_NUM);
+	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..33430506f8ed
--- /dev/null
+++ b/drivers/clk/ambarella/ccu_mux_div.c
@@ -0,0 +1,223 @@
+// 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/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);
+
+	if (val & BIT(div->width))
+		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)
+		do_div(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)
+		do_div(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) {
+		val = BIT(div->width);
+	} else {
+		val = divider_get_val(rate, parent_rate, NULL,
+				      div->width, div->flags);
+		if (val < 0)
+			return val;
+	}
+
+	mask = (div->flags & CLK_DIVIDER_ONE_BASED) ?
+		clk_div_mask(div->width + 1) : clk_div_mask(div->width);
+
+	regmap_update_bits(div->map, div->offset, mask << div->shift,
+			   val << div->shift);
+
+	if (!(div->flags & CLK_DIVIDER_ONE_BASED)) {
+		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..1293836e222e
--- /dev/null
+++ b/drivers/clk/ambarella/ccu_pll.c
@@ -0,0 +1,374 @@
+// 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_determine_rate(struct clk_hw *hw,
+				  struct clk_rate_request *req)
+{
+	struct amb_pll *pll = to_amb_pll(hw);
+	unsigned long half_refclk = req->best_parent_rate / 2;
+
+	if (pll->frac_mode)
+		return 0;
+
+	if (!half_refclk)
+		return 0;
+
+	req->rate = roundup(req->rate, half_refclk);
+
+	return 0;
+}
+
+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;
+	u32 ctrl_val;
+
+	*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;
+
+	max_denominator = AMB_PLL_MAX_SOUT;
+	rate_tmp = rate;
+	rational_best_approximation(rate_tmp, parent_rate, max_numerator,
+				    max_denominator, &intp_ul, &sout_ul);
+
+	while (parent_rate * *fsdiv * intp_ul * *sdiv / *fsout / sout_ul >
+	       rate) {
+		unsigned long resolution = parent_rate / AMB_PLL_MAX_SOUT;
+
+		if (rate_tmp <= resolution)
+			return -EINVAL;
+
+		rate_tmp -= resolution;
+		rational_best_approximation(rate_tmp, parent_rate,
+					    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_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;
+	}
+
+	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 = DIV_ROUND_CLOSEST_ULL(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;
+		}
+		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] 12+ messages in thread

* [PATCH v2 07/10] pinctrl: ambarella: add Ambarella pin controller
  2026-08-06  9:34 [PATCH v2 00/10] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
                   ` (5 preceding siblings ...)
  2026-08-06  9:34 ` [PATCH v2 06/10] clk: ambarella: add CV75 CCU driver Long Zhao via B4 Relay
@ 2026-08-06  9:34 ` Long Zhao via B4 Relay
  2026-08-06  9:34 ` [PATCH v2 08/10] serial: ambarella: add Ambarella UART driver Long Zhao via B4 Relay
                   ` (2 subsequent siblings)
  9 siblings, 0 replies; 12+ messages in thread
From: Long Zhao via B4 Relay @ 2026-08-06  9:34 UTC (permalink / raw)
  To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
	Stephen Boyd, Linus Walleij, Greg Kroah-Hartman, Jiri Slaby,
	Catalin Marinas, Will Deacon, Arnd Bergmann, Alexandre Belloni
  Cc: devicetree, linux-arm-kernel, linux-kernel, linux-clk, linux-gpio,
	linux-serial, soc, Long Zhao

From: Long Zhao <longzhao@ambarella.com>

Add the Ambarella pinmux/GPIO controller driver used by CV75.

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

diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig
index c2cdd7b2c49b..572a4a898a99 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
+	select GENERIC_PINCONF
+	select GPIOLIB
+	select GPIOLIB_IRQCHIP
+	select MFD_SYSCON
+	select PINCONF
+	select PINMUX
+	help
+	  Say Y here to enable the pin controller found on Ambarella SoCs.
+	  The driver provides pin multiplexing and pin configuration as well
+	  as GPIO and GPIO interrupt support. It is required to configure
+	  peripheral pins on systems using an Ambarella SoC.
+
 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..af4641bafebb
--- /dev/null
+++ b/drivers/pinctrl/pinctrl-ambarella-cv75.c
@@ -0,0 +1,68 @@
+// 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"
+
+static const u32 cv75_uart0tx_pinmux[] = {
+	AMBA_PINMUX(44, 1),
+};
+
+static const u32 cv75_uart0rx_pinmux[] = {
+	AMBA_PINMUX(45, 1),
+};
+
+static const struct ambpin_group_desc cv75_pin_groups[] = {
+	{
+		.name = "uart0tx",
+		.pinmux = cv75_uart0tx_pinmux,
+		.num_pins = ARRAY_SIZE(cv75_uart0tx_pinmux),
+	},
+	{
+		.name = "uart0rx",
+		.pinmux = cv75_uart0rx_pinmux,
+		.num_pins = ARRAY_SIZE(cv75_uart0rx_pinmux),
+	},
+};
+
+static const char * const cv75_uart0_groups[] = {
+	"uart0tx",
+	"uart0rx",
+};
+
+static const struct ambpin_function cv75_pin_functions[] = {
+	{
+		.name = "uart0",
+		.groups = cv75_uart0_groups,
+		.num_groups = ARRAY_SIZE(cv75_uart0_groups),
+	},
+};
+
+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,
+	.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..bb79e06c4160
--- /dev/null
+++ b/drivers/pinctrl/pinctrl-ambarella.c
@@ -0,0 +1,1510 @@
+// 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/interrupt.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/of_irq.h>
+#include <linux/pm.h>
+#include <linux/slab.h>
+#include <linux/regmap.h>
+#include <linux/mfd/syscon.h>
+#include <linux/irqdomain.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/gpio/driver.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 GPIO_DATA_OFFSET		0x00
+#define GPIO_DIR_OFFSET			0x04
+#define GPIO_IS_OFFSET			0x08
+#define GPIO_IBE_OFFSET			0x0c
+#define GPIO_IEV_OFFSET			0x10
+#define GPIO_IE_OFFSET			0x14
+#define GPIO_AFSEL_OFFSET		0x18
+#define GPIO_RIS_OFFSET			0x1c
+#define GPIO_MIS_OFFSET			0x20
+#define GPIO_IC_OFFSET			0x24
+#define GPIO_MASK_OFFSET		0x28
+#define GPIO_ENABLE_OFFSET		0x2c
+
+#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];
+	u32 data;
+	u32 dir;
+	u32 is;
+	u32 ibe;
+	u32 iev;
+	u32 ie;
+	u32 afsel;
+	u32 mask;
+};
+
+struct ambpin_group {
+	const char		*name;
+	const u32		*pinmux;
+	unsigned int		*pins;
+	unsigned int		num_pins;
+};
+
+struct amb_pinctrl_soc_data;
+
+struct amb_gpio_bank {
+	struct amb_pinctrl_soc_data	*soc;
+	void __iomem			*base;
+	struct gpio_chip		gc;
+	struct irq_domain		*domain;
+	unsigned int			pin_base;
+	unsigned int			hw_id;
+	unsigned int			irq_wake_mask;
+	int				irq;
+};
+
+struct amb_pinctrl_soc_data {
+	struct device			*dev;
+	const struct amb_pinctrl_data	*data;
+	struct amb_gpio_bank		banks[AMBA_MAX_BANKS];
+	void __iomem			*iomux_base;
+	struct regmap			*ds_regmap;
+	struct regmap			*pull_regmap;
+	unsigned int			bank_num;
+	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];
+};
+
+static struct amb_gpio_bank *amb_irq_data_to_bank(struct irq_data *data)
+{
+	struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
+
+	return gpiochip_get_data(gc);
+}
+
+static struct amb_gpio_bank *
+amb_pin_to_bank(struct amb_pinctrl_soc_data *soc, unsigned int pin)
+{
+	unsigned int i;
+
+	for (i = 0; i < soc->bank_num; i++) {
+		struct amb_gpio_bank *bank = &soc->banks[i];
+
+		if (pin >= bank->pin_base &&
+		    pin < bank->pin_base + bank->gc.ngpio)
+			return bank;
+	}
+
+	return NULL;
+}
+
+/* Exclusive end of the GPIO pin number space (max pin_base + ngpio). */
+static unsigned int amb_gpio_pins_end(const struct amb_pinctrl_soc_data *soc)
+{
+	unsigned int end = 0, i;
+
+	for (i = 0; i < soc->bank_num; i++)
+		end = max(end, soc->banks[i].pin_base + soc->banks[i].gc.ngpio);
+
+	return end;
+}
+
+/* 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 void amb_pinmux_set_altfunc(struct amb_pinctrl_soc_data *soc,
+				   u32 bank, u32 offset, u32 altfunc)
+{
+	u32 i, data;
+
+	/* On CV3 platform, only ARM cluster0 (safety domain) can access pinctrl registers */
+	if (soc->data->hsm_domain_id != 0)
+		return;
+
+	for (i = 0; i < 3; i++) {
+		data = readl_relaxed(soc->iomux_base + IOMUX_OFFSET(bank, i));
+		data &= (~(0x1 << offset));
+		data |= (((altfunc >> i) & 0x1) << offset);
+		writel_relaxed(data, soc->iomux_base + IOMUX_OFFSET(bank, i));
+	}
+
+	writel_relaxed(0x1, soc->iomux_base + IOMUX_CTRL_SET_OFFSET);
+	writel_relaxed(0x0, soc->iomux_base + IOMUX_CTRL_SET_OFFSET);
+}
+
+/* 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);
+	}
+	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);
+	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);
+	struct amb_gpio_bank *gpio_bank;
+	u32 i, bank, offset;
+	unsigned long config, flags;
+	enum pin_config_param param;
+	u32 arg;
+	int ds;
+
+	gpio_bank = amb_pin_to_bank(soc, pin);
+	if (!gpio_bank)
+		return -EINVAL;
+
+	bank = gpio_bank->hw_id;
+	offset = pin - gpio_bank->pin_base;
+
+	raw_spin_lock_irqsave(&soc->lock, flags);
+	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) {
+				raw_spin_unlock_irqrestore(&soc->lock, flags);
+				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:
+			raw_spin_unlock_irqrestore(&soc->lock, flags);
+			return -EOPNOTSUPP;
+		}
+	}
+	raw_spin_unlock_irqrestore(&soc->lock, flags);
+
+	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);
+	struct amb_gpio_bank *gpio_bank;
+	enum pin_config_param param = pinconf_to_config_param(*config);
+	u32 bank, offset, pull_en, pull_dir, ds0, ds1, ds2, ds;
+	int ret, strength;
+
+	gpio_bank = amb_pin_to_bank(soc, pin);
+	if (!gpio_bank)
+		return -EINVAL;
+
+	bank = gpio_bank->hw_id;
+	offset = pin - gpio_bank->pin_base;
+
+	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);
+	struct amb_gpio_bank *gpio_bank;
+	u32 pull_en, pull_dir, ds0, ds1, ds2, ds;
+	u32 bank, offset;
+	int strength;
+
+	gpio_bank = amb_pin_to_bank(soc, pin);
+	if (!gpio_bank) {
+		seq_puts(s, " (no pinconf)");
+		return;
+	}
+
+	bank = gpio_bank->hw_id;
+	offset = pin - gpio_bank->pin_base;
+
+	regmap_read(soc->pull_regmap, soc->data->pull_en[bank], &pull_en);
+	pull_en = (pull_en >> offset) & 1;
+	regmap_read(soc->pull_regmap, soc->data->pull_dir[bank], &pull_dir);
+	pull_dir = (pull_dir >> offset) & 1;
+	seq_printf(s, " pull: %s,",
+		   pull_en ? (pull_dir ? "up" : "down") : "disable");
+
+	regmap_read(soc->ds_regmap, soc->data->ds0[bank], &ds0);
+	ds0 = (ds0 >> offset) & 1;
+	regmap_read(soc->ds_regmap, soc->data->ds1[bank], &ds1);
+	ds1 = (ds1 >> offset) & 1;
+	if (soc->data->have_ds2) {
+		regmap_read(soc->ds_regmap, soc->data->ds2[bank], &ds2);
+		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;
+}
+
+/* gpiolib gpio_set callback function */
+static int amb_gpio_set(struct gpio_chip *gc, unsigned int pin, int value)
+{
+	struct amb_gpio_bank *bank = gpiochip_get_data(gc);
+	struct amb_pinctrl_soc_data *soc = bank->soc;
+	u32 data;
+	unsigned long flags;
+
+	raw_spin_lock_irqsave(&soc->lock, flags);
+	writel_relaxed(BIT(pin), bank->base + GPIO_MASK_OFFSET);
+	data = value ? BIT(pin) : 0;
+	writel_relaxed(data, bank->base + GPIO_DATA_OFFSET);
+	raw_spin_unlock_irqrestore(&soc->lock, flags);
+
+	return 0;
+}
+
+/* gpiolib gpio_get callback function */
+static int amb_gpio_get(struct gpio_chip *gc, unsigned int pin)
+{
+	struct amb_gpio_bank *bank = gpiochip_get_data(gc);
+	struct amb_pinctrl_soc_data *soc = bank->soc;
+	u32 data;
+	unsigned long flags;
+
+	raw_spin_lock_irqsave(&soc->lock, flags);
+	writel_relaxed(BIT(pin), bank->base + GPIO_MASK_OFFSET);
+	data = readl_relaxed(bank->base + GPIO_DATA_OFFSET);
+	raw_spin_unlock_irqrestore(&soc->lock, flags);
+
+	return !!(data & BIT(pin));
+}
+
+static int amb_gpio_get_direction(struct gpio_chip *gc, unsigned int pin)
+{
+	struct amb_gpio_bank *bank = gpiochip_get_data(gc);
+	struct amb_pinctrl_soc_data *soc = bank->soc;
+	u32 data;
+	unsigned long flags;
+
+	raw_spin_lock_irqsave(&soc->lock, flags);
+	data = readl_relaxed(bank->base + GPIO_DIR_OFFSET);
+	raw_spin_unlock_irqrestore(&soc->lock, flags);
+
+	return data & BIT(pin) ? GPIO_LINE_DIRECTION_OUT : GPIO_LINE_DIRECTION_IN;
+}
+
+static int amb_gpio_set_direction(struct gpio_chip *gc, unsigned int pin, bool input)
+{
+	struct amb_gpio_bank *bank = gpiochip_get_data(gc);
+	struct amb_pinctrl_soc_data *soc = bank->soc;
+	u32 data;
+	unsigned long flags;
+
+	raw_spin_lock_irqsave(&soc->lock, flags);
+	data = readl_relaxed(bank->base + GPIO_DIR_OFFSET);
+	if (input)
+		data &= ~BIT(pin);
+	else
+		data |= BIT(pin);
+	writel_relaxed(data, bank->base + GPIO_DIR_OFFSET);
+	raw_spin_unlock_irqrestore(&soc->lock, flags);
+
+	return 0;
+}
+
+/* gpiolib gpio_direction_input callback function */
+static int amb_gpio_direction_input(struct gpio_chip *gc, unsigned int pin)
+{
+	return amb_gpio_set_direction(gc, pin, true);
+}
+
+/* gpiolib gpio_direction_output callback function */
+static int amb_gpio_direction_output(struct gpio_chip *gc, unsigned int pin, int value)
+{
+	struct amb_gpio_bank *bank = gpiochip_get_data(gc);
+	struct amb_pinctrl_soc_data *soc = bank->soc;
+	unsigned long flags;
+	u32 data;
+
+	raw_spin_lock_irqsave(&soc->lock, flags);
+
+	writel_relaxed(BIT(pin), bank->base + GPIO_MASK_OFFSET);
+	writel_relaxed(value ? BIT(pin) : 0,
+		       bank->base + GPIO_DATA_OFFSET);
+
+	data = readl_relaxed(bank->base + GPIO_DIR_OFFSET);
+	writel_relaxed(data | BIT(pin), bank->base + GPIO_DIR_OFFSET);
+
+	raw_spin_unlock_irqrestore(&soc->lock, flags);
+
+	return 0;
+}
+
+/* gpiolib gpio_to_irq callback function */
+static int amb_gpio_to_irq(struct gpio_chip *gc, unsigned int pin)
+{
+	struct amb_gpio_bank *bank = gpiochip_get_data(gc);
+
+	return irq_create_mapping(bank->domain, pin);
+}
+
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+static void amb_gpio_dbg_show(struct seq_file *s, struct gpio_chip *gc)
+{
+	struct amb_gpio_bank *bank = gpiochip_get_data(gc);
+	struct amb_pinctrl_soc_data *soc = bank->soc;
+	u32 afsel, data, dir, mask, iomux0, iomux1, iomux2, alt;
+	unsigned long flags;
+	u32 i;
+
+	raw_spin_lock_irqsave(&soc->lock, flags);
+	afsel = readl_relaxed(bank->base + GPIO_AFSEL_OFFSET);
+	dir = readl_relaxed(bank->base + GPIO_DIR_OFFSET);
+	mask = readl_relaxed(bank->base + GPIO_MASK_OFFSET);
+	writel_relaxed(0xffffffff, bank->base + GPIO_MASK_OFFSET);
+	data = readl_relaxed(bank->base + GPIO_DATA_OFFSET);
+	writel_relaxed(mask, bank->base + GPIO_MASK_OFFSET);
+
+	iomux0 = readl_relaxed(soc->iomux_base + IOMUX_OFFSET(bank->hw_id, 0));
+	iomux1 = readl_relaxed(soc->iomux_base + IOMUX_OFFSET(bank->hw_id, 1));
+	iomux2 = readl_relaxed(soc->iomux_base + IOMUX_OFFSET(bank->hw_id, 2));
+	raw_spin_unlock_irqrestore(&soc->lock, flags);
+
+	seq_printf(s, "\nGPIO[%d]:\n", bank->hw_id);
+	seq_printf(s, "GPIO_AFSEL:\t0x%08X\n", afsel);
+	seq_printf(s, "GPIO_DIR:\t0x%08X\n", dir);
+	seq_printf(s, "GPIO_MASK:\t0x%08X\n", mask);
+	seq_printf(s, "GPIO_DATA:\t0x%08X\n", data);
+	seq_printf(s, "IOMUX_REG%d_0:\t0x%08X\n", bank->hw_id, iomux0);
+	seq_printf(s, "IOMUX_REG%d_1:\t0x%08X\n", bank->hw_id, iomux1);
+	seq_printf(s, "IOMUX_REG%d_2:\t0x%08X\n", bank->hw_id, iomux2);
+
+	for (i = 0; i < gc->ngpio; i++) {
+
+		seq_printf(s, " gpio-%-3d", gc->base + i);
+
+		alt = ((iomux2 >> i) & 1) << 2;
+		alt |= ((iomux1 >> i) & 1) << 1;
+		alt |= ((iomux0 >> i) & 1) << 0;
+		if (alt) {
+			seq_printf(s, " [HW  ] (alt%d)\n", alt);
+		} else {
+			char *label __free(kfree) = gpiochip_dup_line_label(gc, i);
+			if (IS_ERR(label)) {
+				pr_debug("Failed to duplicate label\n");
+				continue;
+			}
+
+			seq_printf(s, " [GPIO] (%-20.20s) %s %s\n",
+				   label ? label : "",
+				   (dir & BIT(i)) ? "out" : "in ",
+				   (data & BIT(i)) ? "hi" : "lo");
+		}
+	}
+}
+#endif
+
+static void amb_gpio_irq_enable(struct irq_data *data)
+{
+	struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
+	struct amb_gpio_bank *bank = amb_irq_data_to_bank(data);
+	struct amb_pinctrl_soc_data *soc = bank->soc;
+	void __iomem *gpio_base = bank->base;
+	void __iomem *iomux_base = soc->iomux_base;
+	u32 i, val, offset;
+	unsigned long flags;
+
+	offset = irqd_to_hwirq(data);
+
+	gpiochip_enable_irq(gc, offset);
+
+	raw_spin_lock_irqsave(&soc->lock, flags);
+
+	val = readl_relaxed(gpio_base + GPIO_DIR_OFFSET);
+	val &= ~(0x1 << offset);
+	writel_relaxed(val, gpio_base + GPIO_DIR_OFFSET);
+
+	for (i = 0; i < 3; i++) {
+		val = readl_relaxed(iomux_base + IOMUX_OFFSET(bank->hw_id, i));
+		val &= ~(0x1 << offset);
+		writel_relaxed(val, iomux_base + IOMUX_OFFSET(bank->hw_id, i));
+	}
+	writel_relaxed(0x1, iomux_base + IOMUX_CTRL_SET_OFFSET);
+	writel_relaxed(0x0, iomux_base + IOMUX_CTRL_SET_OFFSET);
+
+	writel_relaxed(0x1 << offset, gpio_base + GPIO_IC_OFFSET);
+
+	val = readl_relaxed(gpio_base + GPIO_IE_OFFSET);
+	val |= 0x1 << offset;
+	writel_relaxed(val, gpio_base + GPIO_IE_OFFSET);
+
+	raw_spin_unlock_irqrestore(&soc->lock, flags);
+}
+
+static void amb_gpio_irq_disable(struct irq_data *data)
+{
+	struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
+	struct amb_gpio_bank *bank = amb_irq_data_to_bank(data);
+	struct amb_pinctrl_soc_data *soc = bank->soc;
+	void __iomem *gpio_base = bank->base;
+	u32 offset, ie;
+	unsigned long flags;
+
+	offset = irqd_to_hwirq(data);
+
+	raw_spin_lock_irqsave(&soc->lock, flags);
+	ie = readl_relaxed(gpio_base + GPIO_IE_OFFSET);
+	writel_relaxed(ie & ~(0x1 << offset), gpio_base + GPIO_IE_OFFSET);
+	writel_relaxed(0x1 << offset, gpio_base + GPIO_IC_OFFSET);
+	raw_spin_unlock_irqrestore(&soc->lock, flags);
+
+	gpiochip_disable_irq(gc, offset);
+}
+
+static void amb_gpio_irq_ack(struct irq_data *data)
+{
+	struct amb_gpio_bank *bank = amb_irq_data_to_bank(data);
+	struct amb_pinctrl_soc_data *soc = bank->soc;
+	void __iomem *gpio_base = bank->base;
+	u32 offset = irqd_to_hwirq(data);
+	unsigned long flags;
+
+	raw_spin_lock_irqsave(&soc->lock, flags);
+	writel_relaxed(0x1 << offset, gpio_base + GPIO_IC_OFFSET);
+	raw_spin_unlock_irqrestore(&soc->lock, flags);
+}
+
+static void amb_gpio_irq_mask(struct irq_data *data)
+{
+	struct amb_gpio_bank *bank = amb_irq_data_to_bank(data);
+	struct amb_pinctrl_soc_data *soc = bank->soc;
+	void __iomem *gpio_base = bank->base;
+	u32 offset, ie;
+	unsigned long flags;
+
+	offset = irqd_to_hwirq(data);
+
+	raw_spin_lock_irqsave(&soc->lock, flags);
+	ie = readl_relaxed(gpio_base + GPIO_IE_OFFSET);
+	writel_relaxed(ie & ~(0x1 << offset), gpio_base + GPIO_IE_OFFSET);
+	raw_spin_unlock_irqrestore(&soc->lock, flags);
+}
+
+static void amb_gpio_irq_mask_ack(struct irq_data *data)
+{
+	struct amb_gpio_bank *bank = amb_irq_data_to_bank(data);
+	struct amb_pinctrl_soc_data *soc = bank->soc;
+	void __iomem *gpio_base = bank->base;
+	u32 offset, ie;
+	unsigned long flags;
+
+	offset = irqd_to_hwirq(data);
+
+	raw_spin_lock_irqsave(&soc->lock, flags);
+	ie = readl_relaxed(gpio_base + GPIO_IE_OFFSET);
+	writel_relaxed(ie & ~(0x1 << offset), gpio_base + GPIO_IE_OFFSET);
+	writel_relaxed(0x1 << offset, gpio_base + GPIO_IC_OFFSET);
+	raw_spin_unlock_irqrestore(&soc->lock, flags);
+}
+
+static void amb_gpio_irq_unmask(struct irq_data *data)
+{
+	struct amb_gpio_bank *bank = amb_irq_data_to_bank(data);
+	struct amb_pinctrl_soc_data *soc = bank->soc;
+	void __iomem *gpio_base = bank->base;
+	u32 offset, ie;
+	unsigned long flags;
+
+	offset = irqd_to_hwirq(data);
+
+	raw_spin_lock_irqsave(&soc->lock, flags);
+	ie = readl_relaxed(gpio_base + GPIO_IE_OFFSET);
+	writel_relaxed(ie | (0x1 << offset), gpio_base + GPIO_IE_OFFSET);
+	raw_spin_unlock_irqrestore(&soc->lock, flags);
+}
+
+static int amb_gpio_irq_set_type(struct irq_data *data, unsigned int type)
+{
+	struct amb_gpio_bank *bank = amb_irq_data_to_bank(data);
+	struct amb_pinctrl_soc_data *soc = bank->soc;
+	void __iomem *gpio_base = bank->base;
+	u32 offset = irqd_to_hwirq(data);
+	u32 mask, bit, sense, bothedges, event;
+	unsigned long flags;
+
+	mask = ~BIT(offset);
+	bit = BIT(offset);
+
+	raw_spin_lock_irqsave(&soc->lock, flags);
+	sense = readl_relaxed(gpio_base + GPIO_IS_OFFSET);
+	bothedges = readl_relaxed(gpio_base + GPIO_IBE_OFFSET);
+	event = readl_relaxed(gpio_base + GPIO_IEV_OFFSET);
+
+	switch (type) {
+	case IRQ_TYPE_EDGE_RISING:
+		sense &= mask;
+		bothedges &= mask;
+		event |= bit;
+		irq_set_handler_locked(data, handle_edge_irq);
+		break;
+	case IRQ_TYPE_EDGE_FALLING:
+		sense &= mask;
+		bothedges &= mask;
+		event &= mask;
+		irq_set_handler_locked(data, handle_edge_irq);
+		break;
+	case IRQ_TYPE_EDGE_BOTH:
+		sense &= mask;
+		bothedges |= bit;
+		event &= mask;
+		irq_set_handler_locked(data, handle_edge_irq);
+		break;
+	case IRQ_TYPE_LEVEL_HIGH:
+		sense |= bit;
+		bothedges &= mask;
+		event |= bit;
+		irq_set_handler_locked(data, handle_level_irq);
+		break;
+	case IRQ_TYPE_LEVEL_LOW:
+		sense |= bit;
+		bothedges &= mask;
+		event &= mask;
+		irq_set_handler_locked(data, handle_level_irq);
+		break;
+	default:
+		raw_spin_unlock_irqrestore(&soc->lock, flags);
+		return -EINVAL;
+	}
+
+	writel_relaxed(sense, gpio_base + GPIO_IS_OFFSET);
+	writel_relaxed(bothedges, gpio_base + GPIO_IBE_OFFSET);
+	writel_relaxed(event, gpio_base + GPIO_IEV_OFFSET);
+	/* clear obsolete irq */
+	writel_relaxed(BIT(offset), gpio_base + GPIO_IC_OFFSET);
+	raw_spin_unlock_irqrestore(&soc->lock, flags);
+
+	return 0;
+}
+
+static int amb_gpio_irq_set_wake(struct irq_data *data, unsigned int on)
+{
+	if (IS_ENABLED(CONFIG_PM)) {
+		struct amb_gpio_bank *bank = amb_irq_data_to_bank(data);
+		struct amb_pinctrl_soc_data *soc = bank->soc;
+		u32 offset = irqd_to_hwirq(data);
+		unsigned long flags;
+		int ret;
+
+		ret = irq_set_irq_wake(bank->irq, on);
+		if (ret)
+			return ret;
+
+		raw_spin_lock_irqsave(&soc->lock, flags);
+		if (on)
+			bank->irq_wake_mask |= BIT(offset);
+		else
+			bank->irq_wake_mask &= ~BIT(offset);
+		raw_spin_unlock_irqrestore(&soc->lock, flags);
+
+		return 0;
+	}
+	return 0;
+}
+
+static struct irq_chip amb_gpio_irqchip = {
+	.name		= "GPIO",
+	.irq_enable	= amb_gpio_irq_enable,
+	.irq_disable	= amb_gpio_irq_disable,
+	.irq_ack	= amb_gpio_irq_ack,
+	.irq_mask	= amb_gpio_irq_mask,
+	.irq_mask_ack	= amb_gpio_irq_mask_ack,
+	.irq_unmask	= amb_gpio_irq_unmask,
+	.irq_set_type	= amb_gpio_irq_set_type,
+	.irq_set_wake	= amb_gpio_irq_set_wake,
+	.irq_request_resources = gpiochip_irq_reqres,
+	.irq_release_resources = gpiochip_irq_relres,
+	.flags		= IRQCHIP_SET_TYPE_MASKED | IRQCHIP_MASK_ON_SUSPEND,
+};
+
+static int amb_gpio_irqdomain_map(struct irq_domain *d, unsigned int irq,
+				  irq_hw_number_t hwirq)
+{
+	struct amb_gpio_bank *bank = d->host_data;
+
+	irq_set_chip_data(irq, &bank->gc);
+	irq_set_chip_and_handler(irq, &amb_gpio_irqchip, handle_level_irq);
+	irq_set_noprobe(irq);
+
+	return 0;
+}
+
+static const struct irq_domain_ops amb_gpio_irq_domain_ops = {
+	.map = amb_gpio_irqdomain_map,
+	.xlate = irq_domain_xlate_twocell,
+};
+
+static void amb_gpio_handle_irq(struct irq_desc *desc)
+{
+	struct amb_gpio_bank *bank;
+	struct irq_chip *irqchip;
+	u32 bit, gpio_mis;
+
+	irqchip = irq_desc_get_chip(desc);
+	chained_irq_enter(irqchip, desc);
+
+	bank = irq_desc_get_handler_data(desc);
+	gpio_mis = readl_relaxed(bank->base + GPIO_MIS_OFFSET);
+	while (gpio_mis) {
+		bit = __ffs(gpio_mis);
+		generic_handle_domain_irq(bank->domain, bit);
+		gpio_mis &= ~BIT(bit);
+	}
+
+	chained_irq_exit(irqchip, desc);
+}
+
+static int amb_gpio_parse_dt(struct amb_pinctrl_soc_data *soc)
+{
+	struct of_phandle_args args;
+	struct resource res;
+	unsigned int hw_id, i;
+	int rval;
+
+	for_each_available_child_of_node_scoped(soc->dev->of_node, np) {
+		struct amb_gpio_bank *bank;
+
+		if (!of_property_present(np, "gpio-controller"))
+			continue;
+
+		if (soc->bank_num >= AMBA_MAX_BANKS)
+			return dev_err_probe(soc->dev, -EINVAL,
+					     "too many GPIO banks\n");
+
+		rval = of_parse_phandle_with_fixed_args(np, "gpio-ranges", 3, 0,
+						       &args);
+		if (rval)
+			return dev_err_probe(soc->dev, rval,
+					     "%pOF: invalid gpio-ranges\n", np);
+
+		if (args.np != soc->dev->of_node || args.args[0] ||
+		    args.args[1] % 32 || !args.args[2] || args.args[2] > 32) {
+			of_node_put(args.np);
+			return dev_err_probe(soc->dev, -EINVAL,
+					     "%pOF: invalid GPIO pin range\n", np);
+		}
+
+		hw_id = args.args[1] / 32;
+		of_node_put(args.np);
+		if (hw_id >= AMBA_MAX_BANKS)
+			return dev_err_probe(soc->dev, -EINVAL,
+					     "%pOF: invalid GPIO bank\n", np);
+
+		for (i = 0; i < soc->bank_num; i++) {
+			if (soc->banks[i].hw_id == hw_id)
+				return dev_err_probe(soc->dev, -EINVAL,
+						     "%pOF: duplicate GPIO bank\n",
+						     np);
+		}
+
+		bank = &soc->banks[soc->bank_num];
+		bank->soc = soc;
+		bank->pin_base = args.args[1];
+		bank->hw_id = hw_id;
+		bank->gc.ngpio = args.args[2];
+		bank->gc.fwnode = of_fwnode_handle(np);
+
+		rval = of_address_to_resource(np, 0, &res);
+		if (rval)
+			return dev_err_probe(soc->dev, rval,
+					     "%pOF: couldn't get registers\n", np);
+
+		bank->base = devm_ioremap_resource(soc->dev, &res);
+		if (IS_ERR(bank->base))
+			return dev_err_probe(soc->dev, PTR_ERR(bank->base),
+					     "%pOF: couldn't map registers\n", np);
+
+		bank->irq = of_irq_get(np, 0);
+		if (bank->irq < 0)
+			return dev_err_probe(soc->dev, bank->irq,
+					     "%pOF: couldn't get interrupt\n", np);
+
+		soc->bank_num++;
+	}
+
+	if (!soc->bank_num)
+		return dev_err_probe(soc->dev, -ENODEV,
+				     "no gpio-controller child nodes\n");
+
+	return 0;
+}
+
+static void amb_gpio_irq_cleanup(void *data)
+{
+	struct amb_gpio_bank *bank = data;
+
+	irq_set_chained_handler_and_data(bank->irq, NULL, NULL);
+	irq_domain_remove(bank->domain);
+	bank->domain = NULL;
+}
+
+static int amb_gpio_register(struct amb_pinctrl_soc_data *soc)
+{
+	struct amb_gpio_bank *bank;
+	unsigned int i;
+	int rval;
+
+	for (i = 0; i < soc->bank_num; i++) {
+		bank = &soc->banks[i];
+		bank->gc.label = devm_kasprintf(soc->dev, GFP_KERNEL,
+					       "%s-gpio%u", dev_name(soc->dev),
+					       bank->hw_id);
+		if (!bank->gc.label)
+			return -ENOMEM;
+
+		bank->gc.base = -1;
+		bank->gc.request = gpiochip_generic_request;
+		bank->gc.free = gpiochip_generic_free;
+		bank->gc.direction_input = amb_gpio_direction_input;
+		bank->gc.direction_output = amb_gpio_direction_output;
+		bank->gc.get_direction = amb_gpio_get_direction;
+		bank->gc.get = amb_gpio_get;
+		bank->gc.set = amb_gpio_set;
+		bank->gc.to_irq = amb_gpio_to_irq;
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+		bank->gc.dbg_show = amb_gpio_dbg_show;
+#endif
+		bank->gc.parent = soc->dev;
+
+		bank->domain = irq_domain_add_linear(to_of_node(bank->gc.fwnode),
+						    bank->gc.ngpio,
+						    &amb_gpio_irq_domain_ops,
+						    bank);
+		if (!bank->domain)
+			return dev_err_probe(soc->dev, -ENODEV,
+					     "GPIO%u: failed to create irqdomain\n",
+					     i);
+
+		rval = devm_gpiochip_add_data(soc->dev, &bank->gc, bank);
+		if (rval) {
+			irq_domain_remove(bank->domain);
+			bank->domain = NULL;
+			return dev_err_probe(soc->dev, rval,
+					     "GPIO%u: gpiochip registration failed\n",
+					     i);
+		}
+
+		writel_relaxed(0xffffffff, bank->base + GPIO_ENABLE_OFFSET);
+		writel_relaxed(0x00000000, bank->base + GPIO_AFSEL_OFFSET);
+		writel_relaxed(0x00000000, bank->base + GPIO_MASK_OFFSET);
+
+		irq_set_irq_type(bank->irq, IRQ_TYPE_LEVEL_HIGH);
+		irq_set_chained_handler_and_data(bank->irq,
+						 amb_gpio_handle_irq, bank);
+
+		rval = devm_add_action_or_reset(soc->dev,
+					       amb_gpio_irq_cleanup, bank);
+		if (rval)
+			return rval;
+	}
+
+	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 gpio_pins, group, group_pin;
+	size_t nr_group_pins = 0;
+	int i, 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");
+
+	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");
+
+	rval = amb_gpio_parse_dt(soc);
+	if (rval)
+		return rval;
+
+	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");
+
+	gpio_pins = amb_gpio_pins_end(soc);
+	soc->npins = gpio_pins;
+	if (soc->data->clk_au_dedicated_pin >= gpio_pins)
+		soc->npins = soc->data->clk_au_dedicated_pin + 1;
+
+	if (soc->npins > AMBA_MAX_PINS)
+		return dev_err_probe(&pdev->dev, -EINVAL, "too many pins\n");
+
+	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);
+	for (i = 0; i < soc->bank_num; i++) {
+		unsigned int pin;
+
+		for (pin = soc->banks[i].pin_base;
+		     pin < soc->banks[i].pin_base + soc->banks[i].gc.ngpio;
+		     pin++)
+			clear_bit(pin, soc->used);
+	}
+
+	if (soc->data->clk_au_dedicated_pin >= gpio_pins &&
+	    soc->data->clk_au_dedicated_pin < AMBA_MAX_PINS)
+		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!");
+
+	rval = amb_gpio_register(soc);
+	if (rval)
+		return dev_err_probe(&pdev->dev, rval, "gpio register failed!");
+
+	platform_set_drvdata(pdev, soc);
+	dev_info(&pdev->dev, "Ambarella pinctrl driver registered");
+
+	return 0;
+}
+
+static int amb_pinctrl_suspend_noirq(struct device *dev)
+{
+	struct amb_pinctrl_soc_data *soc = dev_get_drvdata(dev);
+	u32 bank, hw, i;
+
+	for (i = 0; i < soc->bank_num; i++) {
+		hw = soc->banks[i].hw_id;
+
+		regmap_read(soc->pull_regmap, soc->data->pull_en[hw], &soc->pm[i].pull[0]);
+		regmap_read(soc->pull_regmap, soc->data->pull_dir[hw], &soc->pm[i].pull[1]);
+
+		regmap_read(soc->ds_regmap, soc->data->ds0[hw], &soc->pm[i].ds[0]);
+		regmap_read(soc->ds_regmap, soc->data->ds1[hw], &soc->pm[i].ds[1]);
+		if (soc->data->have_ds2)
+			regmap_read(soc->ds_regmap, soc->data->ds2[hw], &soc->pm[i].ds[2]);
+
+		soc->pm[i].iomux[0] = readl_relaxed(soc->iomux_base + IOMUX_OFFSET(hw, 0));
+		soc->pm[i].iomux[1] = readl_relaxed(soc->iomux_base + IOMUX_OFFSET(hw, 1));
+		soc->pm[i].iomux[2] = readl_relaxed(soc->iomux_base + IOMUX_OFFSET(hw, 2));
+
+		soc->pm[i].afsel = readl_relaxed(soc->banks[i].base + GPIO_AFSEL_OFFSET);
+		soc->pm[i].dir = readl_relaxed(soc->banks[i].base + GPIO_DIR_OFFSET);
+		soc->pm[i].is = readl_relaxed(soc->banks[i].base + GPIO_IS_OFFSET);
+		soc->pm[i].ibe = readl_relaxed(soc->banks[i].base + GPIO_IBE_OFFSET);
+		soc->pm[i].iev = readl_relaxed(soc->banks[i].base + GPIO_IEV_OFFSET);
+		soc->pm[i].ie = readl_relaxed(soc->banks[i].base + GPIO_IE_OFFSET);
+		soc->pm[i].mask = readl_relaxed(soc->banks[i].base + GPIO_MASK_OFFSET);
+		writel_relaxed(0xffffffff, soc->banks[i].base + GPIO_MASK_OFFSET);
+		soc->pm[i].data = readl_relaxed(soc->banks[i].base + GPIO_DATA_OFFSET);
+
+		if (soc->banks[i].irq_wake_mask)
+			writel_relaxed(soc->banks[i].irq_wake_mask,
+				       soc->banks[i].base + GPIO_IE_OFFSET);
+	}
+
+	if (soc->data->clk_au_dedicated_pin >= amb_gpio_pins_end(soc)) {
+		bank = PINID_TO_BANK(soc->data->clk_au_dedicated_pin);
+		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));
+	}
+
+	return 0;
+}
+
+static int amb_pinctrl_resume_noirq(struct device *dev)
+{
+	struct amb_pinctrl_soc_data *soc = dev_get_drvdata(dev);
+	u32 bank, hw, i;
+
+	for (i = 0; i < soc->bank_num; i++) {
+		hw = soc->banks[i].hw_id;
+
+		regmap_write(soc->pull_regmap, soc->data->pull_en[hw], soc->pm[i].pull[0]);
+		regmap_write(soc->pull_regmap, soc->data->pull_dir[hw], soc->pm[i].pull[1]);
+
+		regmap_write(soc->ds_regmap, soc->data->ds0[hw], soc->pm[i].ds[0]);
+		regmap_write(soc->ds_regmap, soc->data->ds1[hw], soc->pm[i].ds[1]);
+		if (soc->data->have_ds2)
+			regmap_write(soc->ds_regmap, soc->data->ds2[hw], soc->pm[i].ds[2]);
+
+		writel_relaxed(soc->pm[i].iomux[0], soc->iomux_base + IOMUX_OFFSET(hw, 0));
+		writel_relaxed(soc->pm[i].iomux[1], soc->iomux_base + IOMUX_OFFSET(hw, 1));
+		writel_relaxed(soc->pm[i].iomux[2], soc->iomux_base + IOMUX_OFFSET(hw, 2));
+
+		writel_relaxed(soc->pm[i].afsel, soc->banks[i].base + GPIO_AFSEL_OFFSET);
+		writel_relaxed(soc->pm[i].dir, soc->banks[i].base + GPIO_DIR_OFFSET);
+		/* Expose DATA writes while restoring the saved GPIO state. */
+		writel_relaxed(0xffffffff,
+			       soc->banks[i].base + GPIO_MASK_OFFSET);
+		writel_relaxed(soc->pm[i].data,
+			       soc->banks[i].base + GPIO_DATA_OFFSET);
+		/* Ensure DATA restore reaches hardware before restoring mask. */
+		wmb();
+		writel_relaxed(soc->pm[i].mask, soc->banks[i].base + GPIO_MASK_OFFSET);
+		writel_relaxed(soc->pm[i].is, soc->banks[i].base + GPIO_IS_OFFSET);
+		writel_relaxed(soc->pm[i].ibe, soc->banks[i].base + GPIO_IBE_OFFSET);
+		writel_relaxed(soc->pm[i].iev, soc->banks[i].base + GPIO_IEV_OFFSET);
+		writel_relaxed(soc->pm[i].ie, soc->banks[i].base + GPIO_IE_OFFSET);
+		writel_relaxed(0xffffffff, soc->banks[i].base + GPIO_ENABLE_OFFSET);
+	}
+
+	if (soc->data->clk_au_dedicated_pin >= amb_gpio_pins_end(soc)) {
+		bank = PINID_TO_BANK(soc->data->clk_au_dedicated_pin);
+		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));
+		/* Ensure dedicated-pin iomux writes reach hardware before commit. */
+		wmb();
+	}
+
+	writel_relaxed(0x1, soc->iomux_base + IOMUX_CTRL_SET_OFFSET);
+	writel_relaxed(0x0, soc->iomux_base + IOMUX_CTRL_SET_OFFSET);
+
+	return 0;
+}
+
+static DEFINE_NOIRQ_DEV_PM_OPS(amb_pinctrl_pm_ops,
+			       amb_pinctrl_suspend_noirq,
+			       amb_pinctrl_resume_noirq);
+
+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..5fe776393ee3
--- /dev/null
+++ b/drivers/pinctrl/pinctrl-ambarella.h
@@ -0,0 +1,48 @@
+/* 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;
+	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] 12+ messages in thread

* [PATCH v2 08/10] serial: ambarella: add Ambarella UART driver
  2026-08-06  9:34 [PATCH v2 00/10] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
                   ` (6 preceding siblings ...)
  2026-08-06  9:34 ` [PATCH v2 07/10] pinctrl: ambarella: add Ambarella pin controller Long Zhao via B4 Relay
@ 2026-08-06  9:34 ` Long Zhao via B4 Relay
  2026-08-06  9:34 ` [PATCH v2 09/10] arm64: ambarella: add ARCH_AMBARELLA and CV75 EVK DT Long Zhao via B4 Relay
  2026-08-06  9:34 ` [PATCH v2 10/10] MAINTAINERS: add ARM/AMBARELLA SoC support Long Zhao via B4 Relay
  9 siblings, 0 replies; 12+ messages in thread
From: Long Zhao via B4 Relay @ 2026-08-06  9:34 UTC (permalink / raw)
  To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
	Stephen Boyd, Linus Walleij, Greg Kroah-Hartman, Jiri Slaby,
	Catalin Marinas, Will Deacon, Arnd Bergmann, Alexandre Belloni
  Cc: devicetree, linux-arm-kernel, linux-kernel, linux-clk, linux-gpio,
	linux-serial, soc, Long Zhao

From: Long Zhao <longzhao@ambarella.com>

Add an Ambarella UART driver with console support for early boot
bring-up on CV75. Keep udelay() in wait_for_tx(); it runs under
console/poll paths that may hold the port lock with IRQs disabled.

Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
 drivers/tty/serial/Kconfig          |   21 +
 drivers/tty/serial/Makefile         |    1 +
 drivers/tty/serial/ambarella_uart.c | 1001 +++++++++++++++++++++++++++++++++++
 3 files changed, 1023 insertions(+)

diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig
index cf7dba473b20..e3f5ec794c72 100644
--- a/drivers/tty/serial/Kconfig
+++ b/drivers/tty/serial/Kconfig
@@ -18,6 +18,27 @@ source "drivers/tty/serial/8250/Kconfig"
 
 comment "Non-8250 serial port support"
 
+config SERIAL_AMBARELLA
+	bool "Ambarella UART support"
+	depends on ARCH_AMBARELLA || COMPILE_TEST
+	select SERIAL_CORE
+	help
+	  Say Y here to enable the on-chip UART controller found on Ambarella
+	  SoCs. The driver supports interrupt-driven transmit and receive
+	  operation through the serial core. It can also provide a system
+	  console when SERIAL_AMBARELLA_CONSOLE is enabled.
+
+config SERIAL_AMBARELLA_CONSOLE
+	bool "Console on Ambarella UART"
+	depends on SERIAL_AMBARELLA
+	select SERIAL_CORE_CONSOLE
+	select SERIAL_EARLYCON
+	help
+	  Say Y here to use an Ambarella UART as the system console. This
+	  enables both the regular serial console and earlycon support for
+	  messages emitted before the full UART driver is initialized.
+	  Select this option for early boot diagnostics on Ambarella systems.
+
 config SERIAL_AMBA_PL010
 	tristate "ARM AMBA PL010 serial port support"
 	depends on ARM_AMBA || COMPILE_TEST
diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile
index bba7b21a4a1d..5c951719528b 100644
--- a/drivers/tty/serial/Makefile
+++ b/drivers/tty/serial/Makefile
@@ -26,6 +26,7 @@ obj-y += 8250/
 
 obj-$(CONFIG_SERIAL_ALTERA_JTAGUART)	+= altera_jtaguart.o
 obj-$(CONFIG_SERIAL_ALTERA_UART)	+= altera_uart.o
+obj-$(CONFIG_SERIAL_AMBARELLA)		+= ambarella_uart.o
 obj-$(CONFIG_SERIAL_AMBA_PL010)		+= amba-pl010.o
 obj-$(CONFIG_SERIAL_AMBA_PL011)		+= amba-pl011.o
 obj-$(CONFIG_SERIAL_GRLIB_GAISLER_APBUART) += apbuart.o
diff --git a/drivers/tty/serial/ambarella_uart.c b/drivers/tty/serial/ambarella_uart.c
new file mode 100644
index 000000000000..7356b242f0ef
--- /dev/null
+++ b/drivers/tty/serial/ambarella_uart.c
@@ -0,0 +1,1001 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <linux/clk.h>
+#include <linux/console.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/serial_reg.h>
+#include <linux/serial_core.h>
+#include <linux/sysrq.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+
+#define UART_RB_OFFSET			0x00
+#define UART_TH_OFFSET			0x00
+#define UART_DLL_OFFSET			0x00
+#define UART_IE_OFFSET			0x04
+#define UART_DLH_OFFSET			0x04
+#define UART_II_OFFSET			0x08
+#define UART_FC_OFFSET			0x08
+#define UART_LC_OFFSET			0x0c
+#define UART_MC_OFFSET			0x10
+#define UART_LS_OFFSET			0x14
+#define UART_MS_OFFSET			0x18
+#define UART_US_OFFSET			0x7c
+#define UART_SRR_OFFSET			0x88
+
+#define UART_IE_ERETOI			0x40
+#define UART_IE_ETOI			0x20
+#define UART_IE_EDSSI			0x08
+#define UART_IE_ELSI			0x04
+#define UART_IE_ETBEI			0x02
+#define UART_IE_ERBFI			0x01
+
+#define UART_II_MODEM_STATUS_CHANGED	0x00
+#define UART_II_NO_INT_PENDING		0x01
+#define UART_II_THR_EMPTY		0x02
+#define UART_II_RCV_DATA_AVAIL		0x04
+#define UART_II_RCV_STATUS		0x06
+#define UART_II_CHAR_TIMEOUT		0x0c
+#define UART_II_CHAR_TIMEOUT_FIFO_EMPTY	0x0d
+
+#define UART_FC_RX_2_TO_FULL		0xc0
+#define UART_FC_TX_EMPTY		0x00
+#define UART_FC_XMITR			0x04
+#define UART_FC_RCVRR			0x02
+#define UART_FC_FIFOE			0x01
+
+#define UART_LC_DLAB			0x80
+#define UART_LC_BRK			0x40
+#define UART_LC_EVEN_PARITY		0x10
+#define UART_LC_ODD_PARITY		0x00
+#define UART_LC_PEN			0x08
+#define UART_LC_STOP_2BIT		0x04
+#define UART_LC_STOP_1BIT		0x00
+#define UART_LC_CLS_8_BITS		0x03
+#define UART_LC_CLS_7_BITS		0x02
+#define UART_LC_CLS_6_BITS		0x01
+#define UART_LC_CLS_5_BITS		0x00
+
+#define UART_MC_AFCE			0x20
+#define UART_MC_LB			0x10
+#define UART_MC_OUT2			0x08
+#define UART_MC_OUT1			0x04
+#define UART_MC_RTS			0x02
+#define UART_MC_DTR			0x01
+
+#define UART_LS_TEMT			0x40
+#define UART_LS_THRE			0x20
+#define UART_LS_BI			0x10
+#define UART_LS_FE			0x08
+#define UART_LS_PE			0x04
+#define UART_LS_OE			0x02
+#define UART_LS_DR			0x01
+
+#define UART_MS_DCD			0x80
+#define UART_MS_RI			0x40
+#define UART_MS_DSR			0x20
+#define UART_MS_CTS			0x10
+#define UART_MS_DDCD			0x08
+#define UART_MS_DCTS			0x01
+
+#define UART_US_TFNF			0x02
+
+#define UART_FIFO_SIZE			64
+
+#define DEFAULT_AMBARELLA_UART_MCR	0
+#define DEFAULT_AMBARELLA_UART_IER	(UART_IE_ELSI | UART_IE_ERBFI | \
+					 UART_IE_ETOI)
+
+#define AMBA_UART_MAX_NUM		8
+
+#define AMBA_UART_RESET_FLAG		0 /* bit 0 */
+
+/* Poll timeout in microseconds (atomic helpers use udelay). */
+#define AMBARELLA_UART_TIMEOUT_US	1000000
+
+struct ambarella_uart_port {
+	struct uart_port port;
+	struct clk *uart_pll;
+	unsigned long flags;
+	u32 mcr;
+	/* Software copy of UART_IE, updated with the port lock held. */
+	u32 ier;
+	bool console_line_ended;
+};
+
+static struct ambarella_uart_port ambarella_port[AMBA_UART_MAX_NUM];
+
+static struct ambarella_uart_port *
+to_ambarella_uart_port(struct uart_port *port)
+{
+	return container_of(port, struct ambarella_uart_port, port);
+}
+
+static void serial_ambarella_ier_write(struct uart_port *port, u32 ier)
+{
+	struct ambarella_uart_port *amb_port = to_ambarella_uart_port(port);
+
+	amb_port->ier = ier;
+	writel_relaxed(ier, port->membase + UART_IE_OFFSET);
+}
+
+static void serial_ambarella_ier_set(struct uart_port *port, u32 set)
+{
+	struct ambarella_uart_port *amb_port = to_ambarella_uart_port(port);
+
+	amb_port->ier |= set;
+	writel_relaxed(amb_port->ier, port->membase + UART_IE_OFFSET);
+}
+
+static void serial_ambarella_ier_clear(struct uart_port *port, u32 clear)
+{
+	struct ambarella_uart_port *amb_port = to_ambarella_uart_port(port);
+
+	amb_port->ier &= ~clear;
+	writel_relaxed(amb_port->ier, port->membase + UART_IE_OFFSET);
+}
+
+static void serial_ambarella_ier_toggle(struct uart_port *port, u32 mask)
+{
+	struct ambarella_uart_port *amb_port = to_ambarella_uart_port(port);
+
+	amb_port->ier &= ~mask;
+	writel_relaxed(amb_port->ier, port->membase + UART_IE_OFFSET);
+	amb_port->ier |= mask;
+	writel_relaxed(amb_port->ier, port->membase + UART_IE_OFFSET);
+}
+
+static void __serial_ambarella_stop_tx(struct uart_port *port)
+{
+	serial_ambarella_ier_clear(port, UART_IE_ETBEI);
+}
+
+static u32 __serial_ambarella_read_ms(struct uart_port *port)
+{
+	return readl_relaxed(port->membase + UART_MS_OFFSET);
+}
+
+static void __serial_ambarella_enable_ms(struct uart_port *port)
+{
+	serial_ambarella_ier_set(port, UART_IE_EDSSI);
+}
+
+static void __serial_ambarella_disable_ms(struct uart_port *port)
+{
+	serial_ambarella_ier_clear(port, UART_IE_EDSSI);
+}
+
+static inline void wait_for_tx(struct uart_port *port)
+{
+	u32 ls;
+	int ret;
+
+	ret = readl_poll_timeout_atomic(port->membase + UART_LS_OFFSET, ls,
+					ls & UART_LS_TEMT, 1,
+					AMBARELLA_UART_TIMEOUT_US);
+	if (likely(!ret))
+		return;
+
+	/* Recover a stuck TX path so console/poll can continue. */
+	writel_relaxed(UART_FC_RX_2_TO_FULL | UART_FC_TX_EMPTY |
+			UART_FC_XMITR | UART_FC_RCVRR,
+			port->membase + UART_FC_OFFSET);
+	udelay(100);
+	writel_relaxed(UART_FC_FIFOE | UART_FC_RX_2_TO_FULL |
+			UART_FC_TX_EMPTY | UART_FC_XMITR |
+			UART_FC_RCVRR,
+			port->membase + UART_FC_OFFSET);
+}
+
+static inline int wait_for_rx(struct uart_port *port)
+{
+	u32 ls;
+
+	return readl_relaxed_poll_timeout_atomic(port->membase + UART_LS_OFFSET,
+						 ls, ls & UART_LS_DR, 1,
+						 AMBARELLA_UART_TIMEOUT_US);
+}
+
+static inline int tx_fifo_is_full(struct uart_port *port)
+{
+	return !(readl_relaxed(port->membase + UART_US_OFFSET) & UART_US_TFNF);
+}
+
+static void serial_ambarella_hw_setup(struct uart_port *port)
+{
+	struct ambarella_uart_port *amb_port = to_ambarella_uart_port(port);
+
+	if (!test_and_set_bit(AMBA_UART_RESET_FLAG, &amb_port->flags)) {
+		if (amb_port->uart_pll)
+			port->uartclk = clk_get_rate(amb_port->uart_pll);
+		/* reset the whole UART only once */
+		writel_relaxed(0x01, port->membase + UART_SRR_OFFSET);
+		mdelay(1);
+		writel_relaxed(0x00, port->membase + UART_SRR_OFFSET);
+	}
+
+	writel_relaxed(UART_FC_FIFOE | UART_FC_RX_2_TO_FULL | UART_FC_TX_EMPTY |
+			UART_FC_XMITR | UART_FC_RCVRR, port->membase + UART_FC_OFFSET);
+	/* Keep interrupts disabled until the IRQ handler is registered. */
+	serial_ambarella_ier_write(port, 0);
+}
+
+static inline void serial_ambarella_receive_chars(struct uart_port *port,
+						  u32 tmo)
+{
+	u32 ch, flag, ls;
+	bool have_char;
+	int max_count;
+
+	ls = readl_relaxed(port->membase + UART_LS_OFFSET);
+	max_count = port->fifosize;
+
+	do {
+		flag = TTY_NORMAL;
+		have_char = ls & UART_LS_DR;
+		if (have_char || (ls & UART_LS_BI) || tmo)
+			ch = readl_relaxed(port->membase + UART_RB_OFFSET);
+		if (have_char) {
+			port->icount.rx++;
+			tmo = 0;
+		}
+
+		if (unlikely(ls & (UART_LS_BI | UART_LS_PE |
+					UART_LS_FE | UART_LS_OE))) {
+			if (ls & UART_LS_BI) {
+				ls &= ~(UART_LS_FE | UART_LS_PE);
+				port->icount.brk++;
+
+				if (uart_handle_break(port))
+					goto ignore_char;
+			}
+			if (ls & UART_LS_FE)
+				port->icount.frame++;
+			if (ls & UART_LS_PE)
+				port->icount.parity++;
+			if (ls & UART_LS_OE)
+				port->icount.overrun++;
+
+			ls &= port->read_status_mask;
+
+			if (ls & UART_LS_BI)
+				flag = TTY_BREAK;
+			else if (ls & UART_LS_FE)
+				flag = TTY_FRAME;
+			else if (ls & UART_LS_PE)
+				flag = TTY_PARITY;
+			else if (ls & UART_LS_OE)
+				flag = TTY_OVERRUN;
+
+			if (ls & UART_LS_OE)
+				pr_debug("%s: OVERFLOW\n", __func__);
+		}
+
+		if (have_char) {
+			if (uart_handle_sysrq_char(port, ch))
+				goto ignore_char;
+
+			uart_insert_char(port, ls, UART_LS_OE, ch, flag);
+		}
+
+ignore_char:
+		ls = readl_relaxed(port->membase + UART_LS_OFFSET);
+	} while ((ls & (UART_LS_DR | UART_LS_BI)) && (max_count-- > 0));
+
+	tty_flip_buffer_push(&port->state->port);
+}
+
+static void serial_ambarella_transmit_chars(struct uart_port *port)
+{
+	struct tty_port *tport = &port->state->port;
+	int count;
+
+	if (port->x_char) {
+		writel_relaxed(port->x_char, port->membase + UART_TH_OFFSET);
+		port->icount.tx++;
+		port->x_char = 0;
+		return;
+	}
+
+	if (uart_tx_stopped(port) || kfifo_is_empty(&tport->xmit_fifo)) {
+		__serial_ambarella_stop_tx(port);
+		return;
+	}
+
+	count = port->fifosize;
+	while (count-- > 0) {
+		unsigned char c;
+
+		if (tx_fifo_is_full(port))
+			break;
+
+		if (!kfifo_peek(&tport->xmit_fifo, &c))
+			break;
+
+		writel_relaxed(c, port->membase + UART_TH_OFFSET);
+		kfifo_skip(&tport->xmit_fifo);
+		port->icount.tx++;
+		if (kfifo_is_empty(&tport->xmit_fifo))
+			break;
+	}
+
+	if (kfifo_len(&tport->xmit_fifo) < WAKEUP_CHARS)
+		uart_write_wakeup(port);
+	if (kfifo_is_empty(&tport->xmit_fifo))
+		__serial_ambarella_stop_tx(port);
+}
+
+static inline void serial_ambarella_check_modem_status(struct uart_port *port)
+{
+	u32 ms;
+
+	ms = __serial_ambarella_read_ms(port);
+
+	if (ms & UART_MS_RI)
+		port->icount.rng++;
+	if (ms & UART_MS_DSR)
+		port->icount.dsr++;
+	if (ms & UART_MS_DCTS)
+		uart_handle_cts_change(port, (ms & UART_MS_CTS));
+	if (ms & UART_MS_DDCD)
+		uart_handle_dcd_change(port, (ms & UART_MS_DCD));
+
+	wake_up_interruptible(&port->state->port.delta_msr_wait);
+}
+
+static irqreturn_t serial_ambarella_irq(int irq, void *dev_id)
+{
+	struct uart_port *port = dev_id;
+	u32 ii;
+
+	scoped_guard(uart_port_lock_irqsave, port) {
+		ii = readl_relaxed(port->membase + UART_II_OFFSET);
+		switch (ii & 0x0F) {
+		case UART_II_MODEM_STATUS_CHANGED:
+			serial_ambarella_check_modem_status(port);
+			break;
+		case UART_II_THR_EMPTY:
+			serial_ambarella_transmit_chars(port);
+			break;
+		case UART_II_RCV_STATUS:
+		case UART_II_RCV_DATA_AVAIL:
+			serial_ambarella_receive_chars(port, 0);
+			break;
+		case UART_II_CHAR_TIMEOUT_FIFO_EMPTY:
+			/* Clear ERETOI to dismiss timeout-with-empty-FIFO IRQ */
+			serial_ambarella_ier_toggle(port, UART_IE_ERETOI);
+			fallthrough;
+		case UART_II_CHAR_TIMEOUT:
+			serial_ambarella_receive_chars(port, 1);
+			break;
+		case UART_II_NO_INT_PENDING:
+			break;
+		default:
+			pr_debug("%s: 0x%x\n", __func__, ii);
+			break;
+		}
+	}
+
+	return IRQ_HANDLED;
+}
+
+static void serial_ambarella_enable_ms(struct uart_port *port)
+{
+	__serial_ambarella_enable_ms(port);
+}
+
+static void serial_ambarella_start_tx(struct uart_port *port)
+{
+	/* if transmit buffer is not allocated, just return */
+	if (!port->state->port.xmit_buf)
+		return;
+
+	serial_ambarella_ier_set(port, UART_IE_ETBEI);
+	serial_ambarella_transmit_chars(port);
+}
+
+static void serial_ambarella_stop_tx(struct uart_port *port)
+{
+	__serial_ambarella_stop_tx(port);
+}
+
+static void serial_ambarella_stop_rx(struct uart_port *port)
+{
+	serial_ambarella_ier_clear(port, UART_IE_ERBFI);
+}
+
+static unsigned int serial_ambarella_tx_empty(struct uart_port *port)
+{
+	unsigned int lsr;
+
+	guard(uart_port_lock_irqsave)(port);
+	lsr = readl_relaxed(port->membase + UART_LS_OFFSET);
+
+	return ((lsr & (UART_LS_TEMT | UART_LS_THRE)) ==
+		(UART_LS_TEMT | UART_LS_THRE)) ? TIOCSER_TEMT : 0;
+}
+
+static unsigned int serial_ambarella_get_mctrl(struct uart_port *port)
+{
+	u32 ms, mctrl = 0;
+
+	ms = __serial_ambarella_read_ms(port);
+
+	if (ms & UART_MS_CTS)
+		mctrl |= TIOCM_CTS;
+	if (ms & UART_MS_DSR)
+		mctrl |= TIOCM_DSR;
+	if (ms & UART_MS_RI)
+		mctrl |= TIOCM_RI;
+	if (ms & UART_MS_DCD)
+		mctrl |= TIOCM_CD;
+
+	return mctrl;
+}
+
+static void serial_ambarella_set_mctrl(struct uart_port *port,
+				       unsigned int mctrl)
+{
+	struct ambarella_uart_port *amb_port = to_ambarella_uart_port(port);
+	u32 mcr, mcr_new = 0;
+
+	mcr = readl_relaxed(port->membase + UART_MC_OFFSET);
+
+	if (mctrl & TIOCM_DTR)
+		mcr_new |= UART_MC_DTR;
+	if (mctrl & TIOCM_RTS)
+		mcr_new |= UART_MC_RTS;
+	if (mctrl & TIOCM_OUT1)
+		mcr_new |= UART_MC_OUT1;
+	if (mctrl & TIOCM_OUT2)
+		mcr_new |= UART_MC_OUT2;
+	if (mctrl & TIOCM_LOOP)
+		mcr_new |= UART_MC_LB;
+
+	mcr_new |= amb_port->mcr;
+	if (mcr_new != mcr) {
+		if ((mcr & UART_MC_AFCE) == UART_MC_AFCE) {
+			mcr &= ~UART_MC_AFCE;
+			writel_relaxed(mcr, port->membase + UART_MC_OFFSET);
+		}
+		writel_relaxed(mcr_new, port->membase + UART_MC_OFFSET);
+	}
+}
+
+static void serial_ambarella_break_ctl(struct uart_port *port, int break_state)
+{
+	u32 lcr;
+
+	guard(uart_port_lock_irqsave)(port);
+	lcr = readl_relaxed(port->membase + UART_LC_OFFSET);
+	if (break_state != 0)
+		writel_relaxed(lcr | UART_LC_BRK, port->membase + UART_LC_OFFSET);
+	else
+		writel_relaxed(lcr & ~UART_LC_BRK, port->membase + UART_LC_OFFSET);
+}
+
+static void serial_ambarella_hw_deinit(struct ambarella_uart_port *amb_port)
+{
+	struct uart_port *port = &amb_port->port;
+
+	/* Disable interrupts */
+	serial_ambarella_ier_write(port, 0);
+
+	/* Reset the Rx and Tx FIFOs */
+	writel_relaxed(UART_FCR_CLEAR_XMIT | UART_FCR_CLEAR_RCVR,
+		       port->membase + UART_SRR_OFFSET);
+}
+
+static int serial_ambarella_startup(struct uart_port *port)
+{
+	int rval;
+	struct ambarella_uart_port *amb_port = to_ambarella_uart_port(port);
+
+	serial_ambarella_hw_setup(port);
+
+	rval = request_irq(port->irq, serial_ambarella_irq, IRQF_TRIGGER_HIGH,
+			   dev_name(amb_port->port.dev), &amb_port->port);
+	if (rval < 0) {
+		dev_err(amb_port->port.dev,
+			"Failed to register ISR for IRQ %d\n", port->irq);
+		serial_ambarella_hw_deinit(amb_port);
+		return rval;
+	}
+
+	serial_ambarella_ier_write(port, DEFAULT_AMBARELLA_UART_IER);
+
+	return 0;
+}
+
+static void serial_ambarella_shutdown(struct uart_port *port)
+{
+	struct ambarella_uart_port *amb_port = to_ambarella_uart_port(port);
+	u32 lcr;
+
+	scoped_guard(uart_port_lock_irqsave, port) {
+		serial_ambarella_hw_deinit(amb_port);
+		lcr = readl_relaxed(port->membase + UART_LC_OFFSET);
+		writel_relaxed(lcr & ~UART_LC_BRK, port->membase + UART_LC_OFFSET);
+	}
+
+	free_irq(amb_port->port.irq, &amb_port->port);
+}
+
+static void serial_ambarella_set_termios(struct uart_port *port,
+					 struct ktermios *termios,
+					 const struct ktermios *old)
+{
+	struct ambarella_uart_port *amb_port = to_ambarella_uart_port(port);
+	unsigned int baud, quot;
+	u32 lc = 0x0;
+
+	port->uartclk = clk_get_rate(amb_port->uart_pll);
+	switch (termios->c_cflag & CSIZE) {
+	case CS5:
+		lc |= UART_LC_CLS_5_BITS;
+		break;
+	case CS6:
+		lc |= UART_LC_CLS_6_BITS;
+		break;
+	case CS7:
+		lc |= UART_LC_CLS_7_BITS;
+		break;
+	case CS8:
+	default:
+		lc |= UART_LC_CLS_8_BITS;
+		break;
+	}
+
+	if (termios->c_cflag & CSTOPB)
+		lc |= UART_LC_STOP_2BIT;
+	else
+		lc |= UART_LC_STOP_1BIT;
+
+	if (termios->c_cflag & PARENB) {
+		if (termios->c_cflag & PARODD)
+			lc |= (UART_LC_PEN | UART_LC_ODD_PARITY);
+		else
+			lc |= (UART_LC_PEN | UART_LC_EVEN_PARITY);
+	}
+
+	baud = uart_get_baud_rate(port, termios, old, 0, port->uartclk / 16);
+	quot = uart_get_divisor(port, baud);
+
+	scoped_guard(uart_port_lock_irqsave, port) {
+		uart_update_timeout(port, termios->c_cflag, baud);
+
+		port->read_status_mask = UART_LSR_OE | UART_LSR_THRE | UART_LSR_DR;
+		if (termios->c_iflag & INPCK)
+			port->read_status_mask |= UART_LSR_FE | UART_LSR_PE;
+		if (termios->c_iflag & (BRKINT | PARMRK))
+			port->read_status_mask |= UART_LSR_BI;
+
+		port->ignore_status_mask = 0;
+		if (termios->c_iflag & IGNPAR)
+			port->ignore_status_mask |= UART_LSR_PE | UART_LSR_FE;
+		if (termios->c_iflag & IGNBRK) {
+			port->ignore_status_mask |= UART_LSR_BI;
+			if (termios->c_iflag & IGNPAR)
+				port->ignore_status_mask |= UART_LSR_OE;
+		}
+		if ((termios->c_cflag & CREAD) == 0)
+			port->ignore_status_mask |= UART_LSR_DR;
+
+		if ((termios->c_cflag & CRTSCTS) == 0) {
+			amb_port->mcr &= ~UART_MC_AFCE;
+			port->status &= ~UPSTAT_AUTOCTS;
+		} else {
+			amb_port->mcr |= UART_MC_AFCE;
+			port->status |= UPSTAT_AUTOCTS;
+		}
+
+		writel_relaxed(UART_LC_DLAB, port->membase + UART_LC_OFFSET);
+		writel_relaxed(quot & 0xff, port->membase + UART_DLL_OFFSET);
+		writel_relaxed((quot >> 8) & 0xff, port->membase + UART_DLH_OFFSET);
+		writel_relaxed(lc, port->membase + UART_LC_OFFSET);
+		if (UART_ENABLE_MS(port, termios->c_cflag))
+			__serial_ambarella_enable_ms(port);
+		else
+			__serial_ambarella_disable_ms(port);
+		serial_ambarella_set_mctrl(port, port->mctrl);
+	}
+}
+
+static void serial_ambarella_pm(struct uart_port *port,
+				unsigned int state, unsigned int oldstate)
+{
+}
+
+static void serial_ambarella_release_port(struct uart_port *port)
+{
+}
+
+static int serial_ambarella_request_port(struct uart_port *port)
+{
+	return 0;
+}
+
+static void serial_ambarella_config_port(struct uart_port *port, int flags)
+{
+}
+
+static int serial_ambarella_verify_port(struct uart_port *port,
+					struct serial_struct *ser)
+{
+	int rval = 0;
+
+	if (ser->type != PORT_UNKNOWN && ser->type != PORT_UART00)
+		rval = -EINVAL;
+	if (port->irq != ser->irq)
+		rval = -EINVAL;
+	if (ser->io_type != SERIAL_IO_MEM)
+		rval = -EINVAL;
+
+	return rval;
+}
+
+static const char *serial_ambarella_type(struct uart_port *port)
+{
+	return "ambuart";
+}
+
+#ifdef CONFIG_CONSOLE_POLL
+static void serial_ambarella_poll_put_char(struct uart_port *port,
+					   unsigned char chr)
+{
+	if (!port->suspended) {
+		wait_for_tx(port);
+		writel_relaxed(chr, port->membase + UART_TH_OFFSET);
+	}
+}
+
+static int serial_ambarella_poll_get_char(struct uart_port *port)
+{
+	if (port->suspended)
+		return NO_POLL_CHAR;
+
+	if (wait_for_rx(port))
+		return NO_POLL_CHAR;
+
+	return readl_relaxed(port->membase + UART_RB_OFFSET);
+}
+#endif
+
+static const struct uart_ops serial_ambarella_pops = {
+	.tx_empty	= serial_ambarella_tx_empty,
+	.set_mctrl	= serial_ambarella_set_mctrl,
+	.get_mctrl	= serial_ambarella_get_mctrl,
+	.stop_tx	= serial_ambarella_stop_tx,
+	.start_tx	= serial_ambarella_start_tx,
+	.stop_rx	= serial_ambarella_stop_rx,
+	.enable_ms	= serial_ambarella_enable_ms,
+	.break_ctl	= serial_ambarella_break_ctl,
+	.startup	= serial_ambarella_startup,
+	.shutdown	= serial_ambarella_shutdown,
+	.set_termios	= serial_ambarella_set_termios,
+	.pm		= serial_ambarella_pm,
+	.type		= serial_ambarella_type,
+	.release_port	= serial_ambarella_release_port,
+	.request_port	= serial_ambarella_request_port,
+	.config_port	= serial_ambarella_config_port,
+	.verify_port	= serial_ambarella_verify_port,
+#ifdef CONFIG_CONSOLE_POLL
+	.poll_put_char	= serial_ambarella_poll_put_char,
+	.poll_get_char	= serial_ambarella_poll_get_char,
+#endif
+};
+
+#if defined(CONFIG_SERIAL_AMBARELLA_CONSOLE)
+
+static struct uart_driver serial_ambarella_reg;
+
+static void serial_ambarella_putchar(struct uart_port *port, unsigned char ch)
+{
+	wait_for_tx(port);
+	writel_relaxed(ch, port->membase + UART_TH_OFFSET);
+}
+
+static void serial_ambarella_console_putchar(struct uart_port *port,
+					     unsigned char ch)
+{
+	struct ambarella_uart_port *amb_port = to_ambarella_uart_port(port);
+
+	serial_ambarella_putchar(port, ch);
+	amb_port->console_line_ended = (ch == '\n');
+}
+
+static void serial_ambarella_console_device_lock(struct console *co,
+						 unsigned long *flags)
+{
+	__uart_port_lock_irqsave(&ambarella_port[co->index].port, flags);
+}
+
+static void serial_ambarella_console_device_unlock(struct console *co,
+						   unsigned long flags)
+{
+	__uart_port_unlock_irqrestore(&ambarella_port[co->index].port, flags);
+}
+
+static void serial_ambarella_console_write_atomic(struct console *co,
+						  struct nbcon_write_context *wctxt)
+{
+	struct ambarella_uart_port *amb_port = &ambarella_port[co->index];
+	struct uart_port *port = &amb_port->port;
+
+	if (port->suspended)
+		return;
+
+	if (!nbcon_enter_unsafe(wctxt))
+		return;
+
+	if (!amb_port->console_line_ended)
+		uart_console_write(port, "\n", 1, serial_ambarella_console_putchar);
+	uart_console_write(port, wctxt->outbuf, wctxt->len,
+			   serial_ambarella_console_putchar);
+	wait_for_tx(port);
+
+	nbcon_exit_unsafe(wctxt);
+}
+
+static void serial_ambarella_console_write_thread(struct console *co,
+						  struct nbcon_write_context *wctxt)
+{
+	struct ambarella_uart_port *amb_port = &ambarella_port[co->index];
+	struct uart_port *port = &amb_port->port;
+
+	if (port->suspended)
+		return;
+
+	if (!nbcon_enter_unsafe(wctxt))
+		return;
+
+	if (nbcon_exit_unsafe(wctxt)) {
+		unsigned int len = READ_ONCE(wctxt->len);
+		unsigned int i;
+
+		/*
+		 * Toggle unsafe per byte so a higher-priority context can
+		 * take over. After a failed enter/exit, outbuf/len are no
+		 * longer trusted and printing must stop.
+		 */
+		for (i = 0; i < len; i++) {
+			if (!nbcon_enter_unsafe(wctxt))
+				break;
+			uart_console_write(port, wctxt->outbuf + i, 1,
+					   serial_ambarella_console_putchar);
+			if (!nbcon_exit_unsafe(wctxt))
+				break;
+		}
+	}
+
+	while (!nbcon_enter_unsafe(wctxt))
+		nbcon_reacquire_nobuf(wctxt);
+
+	wait_for_tx(port);
+	nbcon_exit_unsafe(wctxt);
+}
+
+static int __init serial_ambarella_console_setup(struct console *co,
+						 char *options)
+{
+	struct uart_port *port;
+	struct ambarella_uart_port *amb_port;
+	int baud = 115200, bits = 8, parity = 'n', flow = 'n';
+
+	if (co->index < 0 || co->index >= serial_ambarella_reg.nr)
+		co->index = 0;
+
+	amb_port = &ambarella_port[co->index];
+	port = &amb_port->port;
+	if (!port->membase) {
+		pr_err("No device available for serial console\n");
+		return -ENODEV;
+	}
+
+	port->ops = &serial_ambarella_pops;
+	port->line = co->index;
+	amb_port->console_line_ended = true;
+
+	serial_ambarella_hw_setup(port);
+
+	if (options)
+		uart_parse_options(options, &baud, &parity, &bits, &flow);
+
+	return uart_set_options(port, co, baud, parity, bits, flow);
+}
+
+static struct console serial_ambarella_console = {
+	.name		= "ttyAB",
+	.device		= uart_console_device,
+	.setup		= serial_ambarella_console_setup,
+	.write_atomic	= serial_ambarella_console_write_atomic,
+	.write_thread	= serial_ambarella_console_write_thread,
+	.device_lock	= serial_ambarella_console_device_lock,
+	.device_unlock	= serial_ambarella_console_device_unlock,
+	.flags		= CON_PRINTBUFFER | CON_ANYTIME | CON_NBCON,
+	.index		= -1,
+	.data		= &serial_ambarella_reg,
+};
+
+static void serial_ambarella_console_early_write(struct console *con,
+						 const char *s,
+						 unsigned int count)
+{
+	struct earlycon_device *dev = con->data;
+
+	uart_console_write(&dev->port, s, count, serial_ambarella_putchar);
+}
+
+static int __init serial_ambarella_console_early_setup(struct earlycon_device *dev,
+						       const char *opt)
+{
+	if (!dev->port.membase)
+		return -ENODEV;
+
+	dev->con->write = serial_ambarella_console_early_write;
+
+	return 0;
+}
+
+OF_EARLYCON_DECLARE(ambarella_uart, "ambarella,cv75-uart",
+		    serial_ambarella_console_early_setup);
+
+#define AMBARELLA_CONSOLE	(&serial_ambarella_console)
+#else
+#define AMBARELLA_CONSOLE	NULL
+#endif
+
+static struct uart_driver serial_ambarella_reg = {
+	.owner		= THIS_MODULE,
+	.driver_name	= "ambarella-uart",
+	.dev_name	= "ttyAB",
+	.major		= 0,
+	.minor		= 0,
+	.nr		= AMBA_UART_MAX_NUM,
+	.cons		= AMBARELLA_CONSOLE,
+};
+
+static int serial_ambarella_probe(struct platform_device *pdev)
+{
+	struct ambarella_uart_port *amb_port;
+	struct resource *mem;
+	struct pinctrl *pinctrl;
+	int irq, id, rval;
+
+	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!mem) {
+		dev_err(&pdev->dev, "no mem resource!\n");
+		return -ENODEV;
+	}
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0) {
+		dev_err(&pdev->dev, "no irq resource!\n");
+		return -ENODEV;
+	}
+
+	id = of_alias_get_id(pdev->dev.of_node, "serial");
+	if (id < 0 || id >= serial_ambarella_reg.nr) {
+		dev_err(&pdev->dev, "Invalid uart ID %d!\n", id);
+		return -ENXIO;
+	}
+
+	pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
+	if (IS_ERR(pinctrl)) {
+		dev_err(&pdev->dev, "Failed to request pinctrl\n");
+		return PTR_ERR(pinctrl);
+	}
+
+	amb_port = &ambarella_port[id];
+
+	amb_port->uart_pll = devm_clk_get_enabled(&pdev->dev, NULL);
+	if (IS_ERR(amb_port->uart_pll)) {
+		dev_err(&pdev->dev, "Get uart clk failed!\n");
+		return PTR_ERR(amb_port->uart_pll);
+	}
+
+	amb_port->mcr = DEFAULT_AMBARELLA_UART_MCR;
+
+	amb_port->port.dev = &pdev->dev;
+	amb_port->port.type = PORT_UART00;
+	amb_port->port.iotype = UPIO_MEM;
+	amb_port->port.fifosize = UART_FIFO_SIZE;
+	amb_port->port.uartclk = clk_get_rate(amb_port->uart_pll);
+	amb_port->port.ops = &serial_ambarella_pops;
+	amb_port->port.irq = irq;
+	amb_port->port.line = id;
+	amb_port->port.mapbase = mem->start;
+	amb_port->port.membase = devm_ioremap_resource(&pdev->dev, mem);
+	if (IS_ERR(amb_port->port.membase))
+		return PTR_ERR(amb_port->port.membase);
+
+	rval = uart_add_one_port(&serial_ambarella_reg, &amb_port->port);
+	if (rval < 0)
+		dev_err(&pdev->dev, "failed to add port: %d, %d!\n", id, rval);
+
+	platform_set_drvdata(pdev, amb_port);
+
+	return rval;
+}
+
+static void serial_ambarella_remove(struct platform_device *pdev)
+{
+	struct ambarella_uart_port *amb_port;
+
+	amb_port = platform_get_drvdata(pdev);
+	uart_remove_one_port(&serial_ambarella_reg, &amb_port->port);
+}
+
+static int serial_ambarella_suspend(struct device *dev)
+{
+	struct ambarella_uart_port *amb_port = dev_get_drvdata(dev);
+
+	return uart_suspend_port(&serial_ambarella_reg, &amb_port->port);
+}
+
+static int serial_ambarella_resume(struct device *dev)
+{
+	struct ambarella_uart_port *amb_port = dev_get_drvdata(dev);
+
+	clear_bit(AMBA_UART_RESET_FLAG, &amb_port->flags);
+	serial_ambarella_hw_setup(&amb_port->port);
+
+	return uart_resume_port(&serial_ambarella_reg, &amb_port->port);
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(serial_ambarella_pm_ops,
+				serial_ambarella_suspend,
+				serial_ambarella_resume);
+
+static const struct of_device_id ambarella_serial_of_match[] = {
+	{ .compatible = "ambarella,cv75-uart" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, ambarella_serial_of_match);
+
+static struct platform_driver serial_ambarella_driver = {
+	.probe		= serial_ambarella_probe,
+	.remove		= serial_ambarella_remove,
+	.driver		= {
+		.name	= "ambarella-uart",
+		.of_match_table = ambarella_serial_of_match,
+		.pm = pm_sleep_ptr(&serial_ambarella_pm_ops),
+	},
+};
+
+static int __init serial_ambarella_init(void)
+{
+	int rval;
+
+	rval = uart_register_driver(&serial_ambarella_reg);
+	if (rval < 0)
+		return rval;
+
+	rval = platform_driver_register(&serial_ambarella_driver);
+	if (rval < 0) {
+		uart_unregister_driver(&serial_ambarella_reg);
+		return rval;
+	}
+
+	return 0;
+}
+
+static void __exit serial_ambarella_exit(void)
+{
+	platform_driver_unregister(&serial_ambarella_driver);
+	uart_unregister_driver(&serial_ambarella_reg);
+}
+
+module_init(serial_ambarella_init);
+module_exit(serial_ambarella_exit);
+
+MODULE_DESCRIPTION("Ambarella UART driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:ambarella-uart");

-- 
2.34.1




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

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

From: Long Zhao <longzhao@ambarella.com>

Enable the Ambarella ARMv8 platform Kconfig, add the CV75 SoC and
EVK device trees, and select the Ambarella UART in the arm64
defconfig for console bring-up.

Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
 arch/arm64/Kconfig.platforms               |   9 ++
 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    | 166 +++++++++++++++++++++++++++++
 arch/arm64/configs/defconfig               |   3 +
 6 files changed, 212 insertions(+)

diff --git a/arch/arm64/Kconfig.platforms b/arch/arm64/Kconfig.platforms
index d2acfac73003..166430ddb3c9 100644
--- a/arch/arm64/Kconfig.platforms
+++ b/arch/arm64/Kconfig.platforms
@@ -26,6 +26,15 @@ 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
+	select PINCTRL_AMB
+	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..825b773f052f
--- /dev/null
+++ b/arch/arm64/boot/dts/ambarella/cv75.dtsi
@@ -0,0 +1,166 @@
+// 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-clock.h>
+#include <dt-bindings/interrupt-controller/arm-gic.h>
+
+/ {
+	compatible = "ambarella,cv75";
+	interrupt-parent = <&gic>;
+	#address-cells = <2>;
+	#size-cells = <2>;
+
+	osc: oscillator {
+		compatible = "fixed-clock";
+		#clock-cells = <0>;
+		clock-output-names = "osc";
+		clock-frequency = <24000000>;
+	};
+
+	psci {
+		compatible = "arm,psci-0.2";
+		method = "smc";
+	};
+
+	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";
+		};
+	};
+
+	gic: interrupt-controller@fff0101000 {
+		compatible = "arm,gic-400";
+		#interrupt-cells = <3>;
+		#address-cells = <0>;
+		#size-cells = <1>;
+		interrupt-controller;
+		reg = <0xff 0xf0101000 0x0 0x1000>,
+		      <0xff 0xf0102000 0x0 0x2000>,
+		      <0xff 0xf0104000 0x0 0x2000>,
+		      <0xff 0xf0106000 0x0 0x2000>;
+		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)>;
+	};
+
+	soc {
+		compatible = "simple-bus";
+		#address-cells = <2>;
+		#size-cells = <2>;
+		ranges;
+
+		/* Clock provider and shared system registers. */
+		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>;
+		};
+
+		apb@ffe4000000 {
+			compatible = "simple-bus";
+			#address-cells = <1>;
+			#size-cells = <1>;
+			reg = <0xff 0xe4000000 0x0 0x01000000>;
+			ranges = <0xe4000000 0xff 0xe4000000 0x01000000>;
+
+			uart0: serial@e4000000 {
+				compatible = "ambarella,cv75-uart";
+				reg = <0xe4000000 0x1000>;
+				interrupts = <GIC_SPI 170 IRQ_TYPE_LEVEL_HIGH>;
+				clocks = <&rct CV75_GCLK_UART0>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&uart0_pins>;
+				status = "disabled";
+			};
+
+			pinctrl: pinctrl@e4010000 {
+				compatible = "ambarella,cv75-pinctrl";
+				#address-cells = <1>;
+				#size-cells = <1>;
+				reg = <0xe4010000 0x1000>;
+				ranges;
+				ambarella,drive-strength-syscon = <&rct>;
+				ambarella,pull-syscon = <&s_scratchpad_syscon>;
+
+				gpio0: gpio@e4013000 {
+					reg = <0xe4013000 0x1000>;
+					interrupts = <GIC_SPI 163 IRQ_TYPE_LEVEL_HIGH>;
+					gpio-controller;
+					#gpio-cells = <2>;
+					gpio-ranges = <&pinctrl 0 0 32>;
+					interrupt-controller;
+					#interrupt-cells = <2>;
+				};
+
+				gpio1: gpio@e4014000 {
+					reg = <0xe4014000 0x1000>;
+					interrupts = <GIC_SPI 164 IRQ_TYPE_LEVEL_HIGH>;
+					gpio-controller;
+					#gpio-cells = <2>;
+					gpio-ranges = <&pinctrl 0 32 32>;
+					interrupt-controller;
+					#interrupt-cells = <2>;
+				};
+
+				gpio2: gpio@e4015000 {
+					reg = <0xe4015000 0x1000>;
+					interrupts = <GIC_SPI 165 IRQ_TYPE_LEVEL_HIGH>;
+					gpio-controller;
+					#gpio-cells = <2>;
+					gpio-ranges = <&pinctrl 0 64 32>;
+					interrupt-controller;
+					#interrupt-cells = <2>;
+				};
+
+				uart0_pins: uart0-pins {
+					function = "uart0";
+					groups = "uart0tx", "uart0rx";
+				};
+			};
+		};
+
+		ahb@ffe0000000 {
+			compatible = "simple-bus";
+			#address-cells = <1>;
+			#size-cells = <1>;
+			reg = <0xff 0xe0000000 0x0 0x01000000>;
+			ranges = <0xe0000000 0xff 0xe0000000 0x01000000>;
+
+			s_scratchpad_syscon: scratchpad@e002e000 {
+				compatible = "ambarella,cv75-sec-scratchpad", "syscon";
+				reg = <0xe002e000 0x1000>;
+			};
+		};
+	};
+};
diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig
index ad3bd3d9507b..64e8fbe43d27 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
@@ -567,6 +568,8 @@ CONFIG_SERIAL_8250_OMAP=y
 CONFIG_SERIAL_8250_MT6577=y
 CONFIG_SERIAL_8250_UNIPHIER=y
 CONFIG_SERIAL_OF_PLATFORM=y
+CONFIG_SERIAL_AMBARELLA=y
+CONFIG_SERIAL_AMBARELLA_CONSOLE=y
 CONFIG_SERIAL_AMBA_PL011=y
 CONFIG_SERIAL_AMBA_PL011_CONSOLE=y
 CONFIG_SERIAL_MESON=y

-- 
2.34.1




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

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

From: Long Zhao <longzhao@ambarella.com>

Add a maintainer entry covering Ambarella CV75 bindings, drivers
and device trees.

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

diff --git a/MAINTAINERS b/MAINTAINERS
index 803b60ad628f..c06c84fab57a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2502,6 +2502,23 @@ 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:	Documentation/devicetree/bindings/soc/ambarella/
+F:	arch/arm64/boot/dts/ambarella/
+F:	drivers/clk/ambarella/
+F:	drivers/pinctrl/pinctrl-ambarella*
+F:	drivers/tty/serial/ambarella_uart.c
+F:	include/dt-bindings/clock/ambarella,cv75-clock.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] 12+ messages in thread

* Re: [PATCH v2 03/10] dt-bindings: clock: add Ambarella CV75 RCT clock controller
  2026-08-06  9:34 ` [PATCH v2 03/10] dt-bindings: clock: add Ambarella CV75 RCT clock controller Long Zhao via B4 Relay
@ 2026-08-06 10:56   ` Rob Herring (Arm)
  0 siblings, 0 replies; 12+ messages in thread
From: Rob Herring (Arm) @ 2026-08-06 10:56 UTC (permalink / raw)
  To: Long Zhao
  Cc: linux-gpio, Alexandre Belloni, Jiri Slaby, Greg Kroah-Hartman,
	Conor Dooley, linux-arm-kernel, linux-serial, Linus Walleij,
	linux-kernel, soc, Catalin Marinas, linux-clk, Arnd Bergmann,
	Stephen Boyd, Michael Turquette, Will Deacon, devicetree,
	Krzysztof Kozlowski


On Thu, 06 Aug 2026 17:34:11 +0800, Long Zhao wrote:
> Add the CV75 RCT clock controller binding and clock ID header.
> 
> Signed-off-by: Long Zhao <longzhao@ambarella.com>
> ---
>  .../bindings/clock/ambarella,cv75-rct.yaml         | 66 ++++++++++++++++++++++
>  include/dt-bindings/clock/ambarella,cv75-clock.h   | 17 ++++++
>  2 files changed, 83 insertions(+)
> 

My bot found errors running 'make dt_binding_check' on your patch:

yamllint warnings/errors:

dtschema/dtc warnings/errors:
Documentation/devicetree/bindings/display/mediatek/mediatek,mt8196-tdshp.example.dtb: tdshp@321e0000 (mediatek,mt8196-disp-tdshp): 'clock-names' is a required property
	from schema $id: http://devicetree.org/schemas/display/mediatek/mediatek,mt8196-tdshp.yaml
Documentation/devicetree/bindings/display/imx/fsl,imx-lcdc.example.dtb: display0: fsl,pcr: b'\xf0\xc8\x80\x80' is not of type 'object', 'integer', 'array', 'boolean', 'null'
	from schema $id: http://devicetree.org/schemas/dt-core.yaml

doc reference errors (make refcheckdocs):

See https://patchwork.kernel.org/project/devicetree/patch/20260806-longzhao-upstream-cv75-v2-v2-3-6b09707c5fe9@ambarella.com

The base for the series is generally the latest rc1. A different dependency
should be noted in *this* patch.

If you already ran 'make dt_binding_check' and didn't see the above
error(s), then make sure 'yamllint' is installed and dt-schema is up to
date:

pip3 install dtschema --upgrade

Please check and re-submit after running the above command yourself. Note
that DT_SCHEMA_FILES can be set to your schema file to speed up checking
your schema. However, it must be unset to test all examples with your schema.



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

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

Thread overview: 12+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-06  9:34 [PATCH v2 00/10] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
2026-08-06  9:34 ` [PATCH v2 01/10] dt-bindings: arm: add Ambarella CV75 platforms Long Zhao via B4 Relay
2026-08-06  9:34 ` [PATCH v2 02/10] dt-bindings: soc: add Ambarella secure scratchpad Long Zhao via B4 Relay
2026-08-06  9:34 ` [PATCH v2 03/10] dt-bindings: clock: add Ambarella CV75 RCT clock controller Long Zhao via B4 Relay
2026-08-06 10:56   ` Rob Herring (Arm)
2026-08-06  9:34 ` [PATCH v2 04/10] dt-bindings: pinctrl: add Ambarella CV75 pinctrl Long Zhao via B4 Relay
2026-08-06  9:34 ` [PATCH v2 05/10] dt-bindings: serial: add Ambarella UART Long Zhao via B4 Relay
2026-08-06  9:34 ` [PATCH v2 06/10] clk: ambarella: add CV75 CCU driver Long Zhao via B4 Relay
2026-08-06  9:34 ` [PATCH v2 07/10] pinctrl: ambarella: add Ambarella pin controller Long Zhao via B4 Relay
2026-08-06  9:34 ` [PATCH v2 08/10] serial: ambarella: add Ambarella UART driver Long Zhao via B4 Relay
2026-08-06  9:34 ` [PATCH v2 09/10] arm64: ambarella: add ARCH_AMBARELLA and CV75 EVK DT Long Zhao via B4 Relay
2026-08-06  9:34 ` [PATCH v2 10/10] 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