* [PATCH 00/10] Ambarella CV75 SoC minimal bring-up
@ 2026-07-30 10:43 Long Zhao
2026-07-30 10:43 ` [PATCH 01/10] dt-bindings: arm: add Ambarella CV75 platforms Long Zhao
` (10 more replies)
0 siblings, 11 replies; 21+ messages in thread
From: Long Zhao @ 2026-07-30 10:43 UTC (permalink / raw)
To: Arnd Bergmann, Alexandre Belloni, linux-arm-kernel, soc
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, devicetree,
Michael Turquette, Stephen Boyd, linux-clk, Linus Walleij,
linux-gpio, Greg Kroah-Hartman, Jiri Slaby, linux-serial,
Catalin Marinas, Will Deacon, linux-kernel, 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 of this first submission is a small, reviewable console-capable
baseline. Further SoC peripherals can follow in later series.
The series is based on linux-next. It has been booted on CV75
hardware with the serial console working.
Testing:
- Booted to console on CV75 EVK with initramfs
- scripts/checkpatch.pl --strict
- make dt_binding_check DT_SCHEMA_FILES=ambarella
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 | 94 +
.../pinctrl/ambarella,cv75-pinctrl.yaml | 167 ++
.../bindings/serial/ambarella,uart.yaml | 55 +
.../ambarella/ambarella,sec-scratchpad.yaml | 41 +
.../devicetree/bindings/vendor-prefixes.yaml | 2 +
MAINTAINERS | 18 +
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 | 151 ++
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 | 1 +
drivers/pinctrl/pinctrl-ambarella.c | 1561 +++++++++++++++++
drivers/tty/serial/Kconfig | 21 +
drivers/tty/serial/Makefile | 1 +
drivers/tty/serial/ambarella_uart.c | 937 ++++++++++
.../dt-bindings/clock/ambarella,cv75-clock.h | 17 +
.../dt-bindings/pinctrl/ambarella,pinctrl.h | 11 +
32 files changed, 4268 insertions(+)
create mode 100644 Documentation/devicetree/bindings/arm/ambarella.yaml
create mode 100644 Documentation/devicetree/bindings/clock/ambarella,cv75-rct.yaml
create mode 100644 Documentation/devicetree/bindings/pinctrl/ambarella,cv75-pinctrl.yaml
create mode 100644 Documentation/devicetree/bindings/serial/ambarella,uart.yaml
create mode 100644 Documentation/devicetree/bindings/soc/ambarella/ambarella,sec-scratchpad.yaml
create mode 100644 arch/arm64/boot/dts/ambarella/Makefile
create mode 100644 arch/arm64/boot/dts/ambarella/cv75-evk.dts
create mode 100644 arch/arm64/boot/dts/ambarella/cv75.dtsi
create mode 100644 drivers/clk/ambarella/Kconfig
create mode 100644 drivers/clk/ambarella/Makefile
create mode 100644 drivers/clk/ambarella/ccu-cv75.c
create mode 100644 drivers/clk/ambarella/ccu_common.c
create mode 100644 drivers/clk/ambarella/ccu_common.h
create mode 100644 drivers/clk/ambarella/ccu_mux_div.c
create mode 100644 drivers/clk/ambarella/ccu_mux_div.h
create mode 100644 drivers/clk/ambarella/ccu_pll.c
create mode 100644 drivers/clk/ambarella/ccu_pll.h
create mode 100644 drivers/pinctrl/pinctrl-ambarella.c
create mode 100644 drivers/tty/serial/ambarella_uart.c
create mode 100644 include/dt-bindings/clock/ambarella,cv75-clock.h
create mode 100644 include/dt-bindings/pinctrl/ambarella,pinctrl.h
--
2.34.1
NOTE: This email (including attachments) contain Ambarella Proprietary and/or Confidential Information and is intended solely for the use of the individual(s) to whom it is addressed. Any unauthorized review, use, disclosure, distribute, copy, or print is prohibited. If you are not an intended recipient, please contact the sender by reply email and destroy all copies of the original message. Thank you.
^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH 01/10] dt-bindings: arm: add Ambarella CV75 platforms
2026-07-30 10:43 [PATCH 00/10] Ambarella CV75 SoC minimal bring-up Long Zhao
@ 2026-07-30 10:43 ` Long Zhao
2026-07-30 10:43 ` [PATCH 02/10] dt-bindings: soc: add Ambarella secure scratchpad Long Zhao
` (9 subsequent siblings)
10 siblings, 0 replies; 21+ messages in thread
From: Long Zhao @ 2026-07-30 10:43 UTC (permalink / raw)
To: Arnd Bergmann, Alexandre Belloni, linux-arm-kernel, soc
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, devicetree,
Michael Turquette, Stephen Boyd, linux-clk, Linus Walleij,
linux-gpio, Greg Kroah-Hartman, Jiri Slaby, linux-serial,
Catalin Marinas, Will Deacon, linux-kernel, Long Zhao
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(+)
create mode 100644 Documentation/devicetree/bindings/arm/ambarella.yaml
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
NOTE: This email (including attachments) contain Ambarella Proprietary and/or Confidential Information and is intended solely for the use of the individual(s) to whom it is addressed. Any unauthorized review, use, disclosure, distribute, copy, or print is prohibited. If you are not an intended recipient, please contact the sender by reply email and destroy all copies of the original message. Thank you.
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH 02/10] dt-bindings: soc: add Ambarella secure scratchpad
2026-07-30 10:43 [PATCH 00/10] Ambarella CV75 SoC minimal bring-up Long Zhao
2026-07-30 10:43 ` [PATCH 01/10] dt-bindings: arm: add Ambarella CV75 platforms Long Zhao
@ 2026-07-30 10:43 ` Long Zhao
2026-07-30 10:43 ` [PATCH 03/10] dt-bindings: clock: add Ambarella CV75 RCT clock controller Long Zhao
` (8 subsequent siblings)
10 siblings, 0 replies; 21+ messages in thread
From: Long Zhao @ 2026-07-30 10:43 UTC (permalink / raw)
To: Arnd Bergmann, Alexandre Belloni, linux-arm-kernel, soc
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, devicetree,
Michael Turquette, Stephen Boyd, linux-clk, Linus Walleij,
linux-gpio, Greg Kroah-Hartman, Jiri Slaby, linux-serial,
Catalin Marinas, Will Deacon, linux-kernel, Long Zhao
Document the Ambarella secure scratchpad syscon used by CV75.
Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
.../ambarella/ambarella,sec-scratchpad.yaml | 41 +++++++++++++++++++
1 file changed, 41 insertions(+)
create mode 100644 Documentation/devicetree/bindings/soc/ambarella/ambarella,sec-scratchpad.yaml
diff --git a/Documentation/devicetree/bindings/soc/ambarella/ambarella,sec-scratchpad.yaml b/Documentation/devicetree/bindings/soc/ambarella/ambarella,sec-scratchpad.yaml
new file mode 100644
index 000000000000..2cca277674f7
--- /dev/null
+++ b/Documentation/devicetree/bindings/soc/ambarella/ambarella,sec-scratchpad.yaml
@@ -0,0 +1,41 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/soc/ambarella/ambarella,sec-scratchpad.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Ambarella 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: ambarella,sec-scratchpad
+ - const: syscon
+
+ reg:
+ maxItems: 1
+
+required:
+ - compatible
+ - reg
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ scratchpad@e002e000 {
+ compatible = "ambarella,cv75-sec-scratchpad",
+ "ambarella,sec-scratchpad", "syscon";
+ reg = <0xe002e000 0x1000>;
+ };
--
2.34.1
NOTE: This email (including attachments) contain Ambarella Proprietary and/or Confidential Information and is intended solely for the use of the individual(s) to whom it is addressed. Any unauthorized review, use, disclosure, distribute, copy, or print is prohibited. If you are not an intended recipient, please contact the sender by reply email and destroy all copies of the original message. Thank you.
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH 03/10] dt-bindings: clock: add Ambarella CV75 RCT clock controller
2026-07-30 10:43 [PATCH 00/10] Ambarella CV75 SoC minimal bring-up Long Zhao
2026-07-30 10:43 ` [PATCH 01/10] dt-bindings: arm: add Ambarella CV75 platforms Long Zhao
2026-07-30 10:43 ` [PATCH 02/10] dt-bindings: soc: add Ambarella secure scratchpad Long Zhao
@ 2026-07-30 10:43 ` Long Zhao
2026-07-30 12:42 ` Rob Herring (Arm)
2026-07-30 14:11 ` Rob Herring
2026-07-30 10:43 ` [PATCH 04/10] dt-bindings: pinctrl: add Ambarella CV75 pinctrl Long Zhao
` (7 subsequent siblings)
10 siblings, 2 replies; 21+ messages in thread
From: Long Zhao @ 2026-07-30 10:43 UTC (permalink / raw)
To: Arnd Bergmann, Alexandre Belloni, linux-arm-kernel, soc
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, devicetree,
Michael Turquette, Stephen Boyd, linux-clk, Linus Walleij,
linux-gpio, Greg Kroah-Hartman, Jiri Slaby, linux-serial,
Catalin Marinas, Will Deacon, linux-kernel, Long Zhao
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 | 94 +++++++++++++++++++
.../dt-bindings/clock/ambarella,cv75-clock.h | 17 ++++
2 files changed, 111 insertions(+)
create mode 100644 Documentation/devicetree/bindings/clock/ambarella,cv75-rct.yaml
create mode 100644 include/dt-bindings/clock/ambarella,cv75-clock.h
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..5ff5b6a4a0b9
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/ambarella,cv75-rct.yaml
@@ -0,0 +1,94 @@
+# 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 drivers
+ (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)
+
+allOf:
+ - $ref: /schemas/mfd/syscon-common.yaml#
+
+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
+
+ assigned-clocks: true
+
+ assigned-clock-parents: true
+
+ assigned-clock-rates: true
+
+required:
+ - compatible
+ - reg
+ - clocks
+ - clock-names
+ - "#clock-cells"
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/clock/ambarella,cv75-clock.h>
+
+ / {
+ compatible = "ambarella,cv75-evk", "ambarella,cv75";
+ #address-cells = <2>;
+ #size-cells = <2>;
+
+ osc: oscillator {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-output-names = "osc";
+ clock-frequency = <24000000>;
+ };
+
+ rct: clock-controller@ed080000 {
+ 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>;
+ };
+ };
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
NOTE: This email (including attachments) contain Ambarella Proprietary and/or Confidential Information and is intended solely for the use of the individual(s) to whom it is addressed. Any unauthorized review, use, disclosure, distribute, copy, or print is prohibited. If you are not an intended recipient, please contact the sender by reply email and destroy all copies of the original message. Thank you.
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH 04/10] dt-bindings: pinctrl: add Ambarella CV75 pinctrl
2026-07-30 10:43 [PATCH 00/10] Ambarella CV75 SoC minimal bring-up Long Zhao
` (2 preceding siblings ...)
2026-07-30 10:43 ` [PATCH 03/10] dt-bindings: clock: add Ambarella CV75 RCT clock controller Long Zhao
@ 2026-07-30 10:43 ` Long Zhao
2026-07-30 12:42 ` Rob Herring (Arm)
2026-07-30 14:21 ` Rob Herring
2026-07-30 10:43 ` [PATCH 05/10] dt-bindings: serial: add Ambarella UART Long Zhao
` (6 subsequent siblings)
10 siblings, 2 replies; 21+ messages in thread
From: Long Zhao @ 2026-07-30 10:43 UTC (permalink / raw)
To: Arnd Bergmann, Alexandre Belloni, linux-arm-kernel, soc
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, devicetree,
Michael Turquette, Stephen Boyd, linux-clk, Linus Walleij,
linux-gpio, Greg Kroah-Hartman, Jiri Slaby, linux-serial,
Catalin Marinas, Will Deacon, linux-kernel, Long Zhao
Add the CV75 pin controller binding and pinmux ID header.
Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
.../pinctrl/ambarella,cv75-pinctrl.yaml | 167 ++++++++++++++++++
.../dt-bindings/pinctrl/ambarella,pinctrl.h | 11 ++
2 files changed, 178 insertions(+)
create mode 100644 Documentation/devicetree/bindings/pinctrl/ambarella,cv75-pinctrl.yaml
create mode 100644 include/dt-bindings/pinctrl/ambarella,pinctrl.h
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..8a331c642fad
--- /dev/null
+++ b/Documentation/devicetree/bindings/pinctrl/ambarella,cv75-pinctrl.yaml
@@ -0,0 +1,167 @@
+# 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 multiplexing entries encode
+ the pin number and alternate function with the AMBA_PINMUX() macro from
+ dt-bindings/pinctrl/ambarella,pinctrl.h.
+
+allOf:
+ - $ref: pinctrl.yaml#
+
+properties:
+ compatible:
+ const: ambarella,cv75-pinctrl
+
+ reg:
+ items:
+ - description: GPIO bank 0 registers
+ - description: GPIO bank 1 registers
+ - description: GPIO bank 2 registers
+ - description: IOMUX registers
+
+ reg-names:
+ items:
+ - const: gpio0
+ - const: gpio1
+ - const: gpio2
+ - const: iomux
+
+ interrupts:
+ items:
+ - description: GPIO bank 0 interrupt
+ - description: GPIO bank 1 interrupt
+ - description: GPIO bank 2 interrupt
+
+ 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: 0
+
+patternProperties:
+ "^gpio@[0-9a-f]+$":
+ type: object
+ additionalProperties: false
+
+ properties:
+ reg:
+ maxItems: 1
+
+ gpio-controller: true
+
+ "#gpio-cells":
+ const: 2
+
+ gpio-ranges:
+ maxItems: 1
+
+ interrupt-controller: true
+
+ "#interrupt-cells":
+ const: 2
+
+ required:
+ - reg
+ - 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: true
+
+ pinmux:
+ $ref: /schemas/types.yaml#/definitions/uint32-array
+ minItems: 1
+ maxItems: 96
+
+ bias-disable: true
+ bias-pull-down: true
+ bias-pull-up: true
+
+ drive-strength:
+ enum: [3, 4, 5, 6, 7, 8, 9, 12]
+
+ required:
+ - function
+ - pinmux
+
+required:
+ - compatible
+ - reg
+ - reg-names
+ - interrupts
+ - ambarella,drive-strength-syscon
+ - ambarella,pull-syscon
+ - "#address-cells"
+ - "#size-cells"
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+ #include <dt-bindings/pinctrl/ambarella,pinctrl.h>
+
+ rct: rct {
+ };
+
+ scratchpad: scratchpad {
+ };
+
+ pinctrl: pinctrl@e4013000 {
+ compatible = "ambarella,cv75-pinctrl";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <0xe4013000 0x1000>, <0xe4014000 0x1000>,
+ <0xe4015000 0x1000>, <0xe4010000 0x1000>;
+ reg-names = "gpio0", "gpio1", "gpio2", "iomux";
+ interrupts = <GIC_SPI 163 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 164 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 165 IRQ_TYPE_LEVEL_HIGH>;
+ ambarella,drive-strength-syscon = <&rct>;
+ ambarella,pull-syscon = <&scratchpad>;
+
+ gpio: gpio@0 {
+ reg = <0>;
+ gpio-controller;
+ #gpio-cells = <2>;
+ gpio-ranges = <&pinctrl 0 0 95>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ };
+
+ uart0-pins {
+ function = "uart0";
+ pinmux = <AMBA_PINMUX(44, 1)>,
+ <AMBA_PINMUX(45, 1)>;
+ bias-disable;
+ drive-strength = <8>;
+ };
+ };
diff --git a/include/dt-bindings/pinctrl/ambarella,pinctrl.h b/include/dt-bindings/pinctrl/ambarella,pinctrl.h
new file mode 100644
index 000000000000..20463d5acc66
--- /dev/null
+++ b/include/dt-bindings/pinctrl/ambarella,pinctrl.h
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause */
+/*
+ * Copyright (C) 2026 Ambarella, Inc.
+ */
+
+#ifndef _DT_BINDINGS_PINCTRL_AMBARELLA_PINCTRL_H
+#define _DT_BINDINGS_PINCTRL_AMBARELLA_PINCTRL_H
+
+#define AMBA_PINMUX(pin, mux) (((mux) << 12) | (pin))
+
+#endif
--
2.34.1
NOTE: This email (including attachments) contain Ambarella Proprietary and/or Confidential Information and is intended solely for the use of the individual(s) to whom it is addressed. Any unauthorized review, use, disclosure, distribute, copy, or print is prohibited. If you are not an intended recipient, please contact the sender by reply email and destroy all copies of the original message. Thank you.
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH 05/10] dt-bindings: serial: add Ambarella UART
2026-07-30 10:43 [PATCH 00/10] Ambarella CV75 SoC minimal bring-up Long Zhao
` (3 preceding siblings ...)
2026-07-30 10:43 ` [PATCH 04/10] dt-bindings: pinctrl: add Ambarella CV75 pinctrl Long Zhao
@ 2026-07-30 10:43 ` Long Zhao
2026-07-30 10:49 ` Krzysztof Kozlowski
2026-07-30 10:43 ` [PATCH 06/10] clk: ambarella: add CV75 CCU driver Long Zhao
` (5 subsequent siblings)
10 siblings, 1 reply; 21+ messages in thread
From: Long Zhao @ 2026-07-30 10:43 UTC (permalink / raw)
To: Arnd Bergmann, Alexandre Belloni, linux-arm-kernel, soc
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, devicetree,
Michael Turquette, Stephen Boyd, linux-clk, Linus Walleij,
linux-gpio, Greg Kroah-Hartman, Jiri Slaby, linux-serial,
Catalin Marinas, Will Deacon, linux-kernel, Long Zhao
Document the Ambarella UART controller.
Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
.../bindings/serial/ambarella,uart.yaml | 55 +++++++++++++++++++
1 file changed, 55 insertions(+)
create mode 100644 Documentation/devicetree/bindings/serial/ambarella,uart.yaml
diff --git a/Documentation/devicetree/bindings/serial/ambarella,uart.yaml b/Documentation/devicetree/bindings/serial/ambarella,uart.yaml
new file mode 100644
index 000000000000..43c98499d99b
--- /dev/null
+++ b/Documentation/devicetree/bindings/serial/ambarella,uart.yaml
@@ -0,0 +1,55 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/serial/ambarella,uart.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Ambarella UART
+
+maintainers:
+ - Long Zhao <longzhao@ambarella.com>
+
+allOf:
+ - $ref: serial.yaml#
+
+properties:
+ compatible:
+ oneOf:
+ - items:
+ - const: ambarella,cv75-uart
+ - const: ambarella,uart
+ - const: ambarella,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>
+
+ uart_clk: clock {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <24000000>;
+ };
+
+ serial@e4000000 {
+ compatible = "ambarella,cv75-uart", "ambarella,uart";
+ reg = <0xe4000000 0x1000>;
+ interrupts = <GIC_SPI 170 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&uart_clk>;
+ };
--
2.34.1
NOTE: This email (including attachments) contain Ambarella Proprietary and/or Confidential Information and is intended solely for the use of the individual(s) to whom it is addressed. Any unauthorized review, use, disclosure, distribute, copy, or print is prohibited. If you are not an intended recipient, please contact the sender by reply email and destroy all copies of the original message. Thank you.
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH 06/10] clk: ambarella: add CV75 CCU driver
2026-07-30 10:43 [PATCH 00/10] Ambarella CV75 SoC minimal bring-up Long Zhao
` (4 preceding siblings ...)
2026-07-30 10:43 ` [PATCH 05/10] dt-bindings: serial: add Ambarella UART Long Zhao
@ 2026-07-30 10:43 ` Long Zhao
2026-07-30 10:43 ` [PATCH 07/10] pinctrl: ambarella: add Ambarella pin controller Long Zhao
` (4 subsequent siblings)
10 siblings, 0 replies; 21+ messages in thread
From: Long Zhao @ 2026-07-30 10:43 UTC (permalink / raw)
To: Arnd Bergmann, Alexandre Belloni, linux-arm-kernel, soc
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, devicetree,
Michael Turquette, Stephen Boyd, linux-clk, Linus Walleij,
linux-gpio, Greg Kroah-Hartman, Jiri Slaby, linux-serial,
Catalin Marinas, Will Deacon, linux-kernel, Long Zhao
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(+)
create mode 100644 drivers/clk/ambarella/Kconfig
create mode 100644 drivers/clk/ambarella/Makefile
create mode 100644 drivers/clk/ambarella/ccu-cv75.c
create mode 100644 drivers/clk/ambarella/ccu_common.c
create mode 100644 drivers/clk/ambarella/ccu_common.h
create mode 100644 drivers/clk/ambarella/ccu_mux_div.c
create mode 100644 drivers/clk/ambarella/ccu_mux_div.h
create mode 100644 drivers/clk/ambarella/ccu_pll.c
create mode 100644 drivers/clk/ambarella/ccu_pll.h
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
NOTE: This email (including attachments) contain Ambarella Proprietary and/or Confidential Information and is intended solely for the use of the individual(s) to whom it is addressed. Any unauthorized review, use, disclosure, distribute, copy, or print is prohibited. If you are not an intended recipient, please contact the sender by reply email and destroy all copies of the original message. Thank you.
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH 07/10] pinctrl: ambarella: add Ambarella pin controller
2026-07-30 10:43 [PATCH 00/10] Ambarella CV75 SoC minimal bring-up Long Zhao
` (5 preceding siblings ...)
2026-07-30 10:43 ` [PATCH 06/10] clk: ambarella: add CV75 CCU driver Long Zhao
@ 2026-07-30 10:43 ` Long Zhao
2026-07-30 10:43 ` [PATCH 08/10] serial: ambarella: add Ambarella UART driver Long Zhao
` (3 subsequent siblings)
10 siblings, 0 replies; 21+ messages in thread
From: Long Zhao @ 2026-07-30 10:43 UTC (permalink / raw)
To: Arnd Bergmann, Alexandre Belloni, linux-arm-kernel, soc
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, devicetree,
Michael Turquette, Stephen Boyd, linux-clk, Linus Walleij,
linux-gpio, Greg Kroah-Hartman, Jiri Slaby, linux-serial,
Catalin Marinas, Will Deacon, linux-kernel, Long Zhao
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 | 1 +
drivers/pinctrl/pinctrl-ambarella.c | 1561 +++++++++++++++++++++++++++
3 files changed, 1577 insertions(+)
create mode 100644 drivers/pinctrl/pinctrl-ambarella.c
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..7cd9820d3088 100644
--- a/drivers/pinctrl/Makefile
+++ b/drivers/pinctrl/Makefile
@@ -10,6 +10,7 @@ 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_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.c b/drivers/pinctrl/pinctrl-ambarella.c
new file mode 100644
index 000000000000..641b50a0624d
--- /dev/null
+++ b/drivers/pinctrl/pinctrl-ambarella.c
@@ -0,0 +1,1561 @@
+// 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/io.h>
+#include <linux/of.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 "core.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 MAX_BANK_NUM 8
+#define MAX_PIN_NUM (MAX_BANK_NUM * 32)
+
+#define PINID_TO_BANK(p) ((p) >> 5)
+#define PINID_TO_OFFSET(p) ((p) & 0x1f)
+
+#define MUXIDS_TO_PINID(m) ((m) & 0xfff)
+#define MUXIDS_TO_ALT(m) (((m) >> 12) & 0xf)
+
+struct ambpin_group {
+ const char *name;
+ const char *function;
+ unsigned int *pins;
+ unsigned int num_pins;
+ u8 *alt;
+ unsigned long *configs;
+ unsigned int num_configs;
+};
+
+struct ambpin_function {
+ const char *name;
+ const char **groups;
+ unsigned int num_groups;
+};
+
+struct amb_pinctrl_data {
+ unsigned int ds0[MAX_BANK_NUM];
+ unsigned int ds1[MAX_BANK_NUM];
+ unsigned int ds2[MAX_BANK_NUM];
+ unsigned int pull_en[MAX_BANK_NUM];
+ unsigned int pull_dir[MAX_BANK_NUM];
+ bool have_ds2;
+ u32 hsm_domain_id;
+ u32 clk_au_dedicated_pin;
+};
+
+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 amb_pinctrl_soc_data {
+ struct device *dev;
+ const struct amb_pinctrl_data *data;
+ void __iomem *gpio_base[MAX_BANK_NUM];
+ void __iomem *iomux_base;
+ struct regmap *ds_regmap;
+ struct regmap *pull_regmap;
+ unsigned int ds0[MAX_BANK_NUM];
+ unsigned int ds1[MAX_BANK_NUM];
+ unsigned int ds2[MAX_BANK_NUM];
+ unsigned int pull_en[MAX_BANK_NUM];
+ unsigned int pull_dir[MAX_BANK_NUM];
+ unsigned int bank_num;
+ unsigned int have_ds2;
+ int irq[MAX_BANK_NUM];
+ unsigned int irq_wake_mask[MAX_BANK_NUM];
+ unsigned long used[BITS_TO_LONGS(MAX_PIN_NUM)];
+ raw_spinlock_t lock;
+
+ struct pinctrl_dev *pctl;
+ struct gpio_chip *gc;
+ struct irq_domain *domain;
+
+ struct ambpin_function *functions;
+ unsigned int nr_functions;
+ struct ambpin_group *groups;
+ unsigned int nr_groups;
+
+ struct amb_pinctrl_pm_state pm[MAX_BANK_NUM];
+
+ /* Domain ID used in HSM boot. */
+ u32 hsm_domain_id;
+ /* Dedicated clk_au pin used on some chips. */
+ u32 clk_au_dedicated_pin;
+};
+
+static __iomem void *
+amb_get_gpio_base(const struct amb_pinctrl_soc_data *soc,
+ struct irq_data *data)
+{
+ return soc->gpio_base[PINID_TO_BANK(irqd_to_hwirq(data))];
+}
+
+/* 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)
+{
+ struct pin_desc *desc;
+
+ seq_printf(s, " %s", dev_name(pctldev->dev));
+
+ desc = pin_desc_get(pctldev, pin);
+ if (desc) {
+ seq_printf(s, " owner: %s%s%s%s",
+ desc->mux_owner ? desc->mux_owner : "",
+ desc->mux_owner && desc->gpio_owner ? " " : "",
+ desc->gpio_owner ? desc->gpio_owner : "",
+ !desc->mux_owner && !desc->gpio_owner ? "NULL" : "");
+ } else {
+ seq_puts(s, " not registered");
+ }
+}
+#endif
+
+#if IS_ENABLED(CONFIG_OF)
+static int amb_dt_node_to_map(struct pinctrl_dev *pctldev,
+ struct device_node *np,
+ struct pinctrl_map **map,
+ unsigned int *num_maps)
+{
+ struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+ struct ambpin_group *grp = NULL;
+ struct pinctrl_map *new_map;
+ u32 i, new_num;
+
+ /* find the group of this node by name */
+ for (i = 0; i < soc->nr_groups; i++) {
+ if (!strcmp(soc->groups[i].name, np->name)) {
+ grp = &soc->groups[i];
+ break;
+ }
+ }
+ if (!grp) {
+ dev_err(soc->dev, "unable to find group for node %s\n", np->name);
+ return -EINVAL;
+ }
+
+ new_num = !!grp->num_pins + !!grp->num_configs;
+ new_map = kcalloc(new_num, sizeof(*new_map), GFP_KERNEL);
+ if (!new_map)
+ return -ENOMEM;
+
+ *map = new_map;
+ *num_maps = new_num;
+
+ /* create mux map */
+ if (grp->num_pins) {
+ new_map[0].type = PIN_MAP_TYPE_MUX_GROUP;
+ new_map[0].data.mux.group = grp->name;
+ new_map[0].data.mux.function = grp->function;
+ new_map++;
+ }
+
+ if (grp->num_configs) {
+ new_map[0].type = PIN_MAP_TYPE_CONFIGS_GROUP;
+ new_map[0].data.configs.group_or_pin = grp->name;
+ new_map[0].data.configs.configs = grp->configs;
+ new_map[0].data.configs.num_configs = grp->num_configs;
+ }
+
+ return 0;
+}
+
+static void amb_dt_free_map(struct pinctrl_dev *pctldev,
+ struct pinctrl_map *map, unsigned int num_maps)
+{
+ kfree(map);
+}
+#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
+#if IS_ENABLED(CONFIG_OF)
+ .dt_node_to_map = amb_dt_node_to_map,
+ .dt_free_map = amb_dt_free_map,
+#endif
+};
+
+/* 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->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, bank, offset;
+ unsigned long flags;
+
+ grp = &soc->groups[group];
+
+ raw_spin_lock_irqsave(&soc->lock, flags);
+ for (i = 0; i < grp->num_pins; i++) {
+ bank = PINID_TO_BANK(grp->pins[i]);
+ offset = PINID_TO_OFFSET(grp->pins[i]);
+ amb_pinmux_set_altfunc(soc, bank, offset, grp->alt[i]);
+ }
+ 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->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;
+ }
+}
+
+/* set the pin config settings for a specified pin */
+static int amb_pinconf_set(struct pinctrl_dev *pctldev, unsigned int pin,
+ unsigned long *configs, unsigned int num_configs)
+{
+ struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+ u32 i, bank, offset;
+ unsigned long config, flags;
+ enum pin_config_param param;
+ u32 arg;
+ int ds;
+
+ bank = PINID_TO_BANK(pin);
+ offset = PINID_TO_OFFSET(pin);
+
+ 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->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->pull_dir[bank],
+ BIT(offset),
+ (param == PIN_CONFIG_BIAS_PULL_UP) ?
+ BIT(offset) : 0);
+ regmap_update_bits(soc->pull_regmap, soc->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->have_ds2) {
+ regmap_update_bits(soc->ds_regmap,
+ soc->ds0[bank], BIT(offset),
+ (ds & BIT(0)) ? BIT(offset) : 0);
+ regmap_update_bits(soc->ds_regmap,
+ soc->ds1[bank], BIT(offset),
+ (ds & BIT(1)) ? BIT(offset) : 0);
+ regmap_update_bits(soc->ds_regmap,
+ soc->ds2[bank], BIT(offset),
+ (ds & BIT(2)) ? BIT(offset) : 0);
+ } else {
+ regmap_update_bits(soc->ds_regmap,
+ soc->ds0[bank], BIT(offset),
+ (ds & BIT(1)) ? BIT(offset) : 0);
+ regmap_update_bits(soc->ds_regmap,
+ soc->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)
+{
+ dev_WARN_ONCE(pctldev->dev, true, "NOT Implemented.\n");
+ return -EOPNOTSUPP;
+}
+
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+static void amb_pinconf_dbg_show(struct pinctrl_dev *pctldev,
+ struct seq_file *s, unsigned int pin)
+{
+ struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+ u32 pull_en, pull_dir, ds0, ds1, ds2, drv_strength;
+ u32 bank, offset;
+
+ bank = PINID_TO_BANK(pin);
+ offset = PINID_TO_OFFSET(pin);
+
+ regmap_read(soc->pull_regmap, soc->pull_en[bank], &pull_en);
+ pull_en = (pull_en >> offset) & 0x1;
+ regmap_read(soc->pull_regmap, soc->pull_dir[bank], &pull_dir);
+ pull_dir = (pull_dir >> offset) & 0x1;
+ seq_printf(s, " pull: %s, ", pull_en ? pull_dir ? "up" : "down" : "disable");
+
+ regmap_read(soc->ds_regmap, soc->ds0[bank], &ds0);
+ ds0 = (ds0 >> offset) & 0x1;
+ regmap_read(soc->ds_regmap, soc->ds1[bank], &ds1);
+ ds1 = (ds1 >> offset) & 0x1;
+ if (soc->have_ds2) {
+ regmap_read(soc->ds_regmap, soc->ds2[bank], &ds2);
+ ds2 = (ds2 >> offset) & 0x1;
+ drv_strength = (ds2 << 2) | (ds1 << 1) | ds0;
+ seq_printf(s, "drive-strength: %s",
+ drv_strength == 5 ? "12mA(lvl5)" :
+ drv_strength == 4 ? "9mA(lvl4)" :
+ drv_strength == 3 ? "7.5mA(lvl3)" :
+ drv_strength == 2 ? "6mA(lvl2)" :
+ drv_strength == 1 ? "4.5mA(lvl1)" :
+ drv_strength == 0 ? "3mA(lvl0)" : "invalid");
+ } else {
+ drv_strength = (ds0 << 1) | ds1;
+ seq_printf(s, "drive-strength: %s",
+ drv_strength == 3 ? "12mA" :
+ drv_strength == 2 ? "8mA" :
+ drv_strength == 1 ? "4mA" : "2mA");
+ }
+}
+#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
+};
+
+static int amb_pinctrl_get_function_name(struct device_node *np,
+ const char **function)
+{
+ if (of_property_read_string(np, "function", function))
+ *function = np->name;
+
+ return 0;
+}
+
+static int amb_pinctrl_count_configs(struct device_node *np)
+{
+ unsigned int bias = 0, count = 0;
+
+ if (of_property_read_bool(np, "bias-disable"))
+ bias++;
+ if (of_property_read_bool(np, "bias-pull-down"))
+ bias++;
+ if (of_property_read_bool(np, "bias-pull-up"))
+ bias++;
+ if (bias > 1)
+ return -EINVAL;
+
+ count += bias;
+ if (of_find_property(np, "drive-strength", NULL))
+ count++;
+
+ return count;
+}
+
+static int amb_pinctrl_parse_configs(struct amb_pinctrl_soc_data *soc,
+ struct device_node *np,
+ struct ambpin_group *grp)
+{
+ u32 drive;
+ int num_configs, i = 0;
+
+ num_configs = amb_pinctrl_count_configs(np);
+ if (num_configs < 0)
+ return dev_err_probe(soc->dev, num_configs,
+ "%pOF has conflicting bias properties\n", np);
+ if (!num_configs)
+ return 0;
+
+ grp->configs = devm_kcalloc(soc->dev, num_configs,
+ sizeof(*grp->configs), GFP_KERNEL);
+ if (!grp->configs)
+ return -ENOMEM;
+
+ if (of_property_read_bool(np, "bias-disable"))
+ grp->configs[i++] =
+ pinconf_to_config_packed(PIN_CONFIG_BIAS_DISABLE, 0);
+ else if (of_property_read_bool(np, "bias-pull-down"))
+ grp->configs[i++] =
+ pinconf_to_config_packed(PIN_CONFIG_BIAS_PULL_DOWN, 1);
+ else if (of_property_read_bool(np, "bias-pull-up"))
+ grp->configs[i++] =
+ pinconf_to_config_packed(PIN_CONFIG_BIAS_PULL_UP, 1);
+
+ if (!of_property_read_u32(np, "drive-strength", &drive))
+ grp->configs[i++] =
+ pinconf_to_config_packed(PIN_CONFIG_DRIVE_STRENGTH,
+ drive);
+
+ grp->num_configs = i;
+
+ return 0;
+}
+
+static int amb_pinctrl_parse_group(struct amb_pinctrl_soc_data *soc,
+ struct device_node *np, int idx,
+ const char **out_name)
+{
+ struct ambpin_group *grp = &soc->groups[idx];
+ struct device *dev = soc->dev;
+ struct property *prop;
+ int length;
+ int ret;
+ u32 i;
+
+ grp->name = np->name;
+ amb_pinctrl_get_function_name(np, &grp->function);
+
+ prop = of_find_property(np, "pinmux", &length);
+ if (prop) {
+ grp->num_pins = length / sizeof(u32);
+
+ grp->pins = devm_kcalloc(dev, grp->num_pins, sizeof(*grp->pins), GFP_KERNEL);
+ if (!grp->pins)
+ return -ENOMEM;
+
+ grp->alt = devm_kcalloc(dev, grp->num_pins, sizeof(*grp->alt), GFP_KERNEL);
+ if (!grp->alt)
+ return -ENOMEM;
+
+ of_property_read_u32_array(np, "pinmux", grp->pins,
+ grp->num_pins);
+
+ for (i = 0; i < grp->num_pins; i++) {
+ grp->alt[i] = MUXIDS_TO_ALT(grp->pins[i]);
+ grp->pins[i] = MUXIDS_TO_PINID(grp->pins[i]);
+ }
+ }
+
+ if (!grp->num_pins)
+ return dev_err_probe(soc->dev, -EINVAL,
+ "%pOF: missing pinmux property\n", np);
+
+ ret = amb_pinctrl_parse_configs(soc, np, grp);
+ if (ret)
+ return ret;
+
+ if (out_name)
+ *out_name = grp->name;
+
+ return 0;
+}
+
+static int amb_pinctrl_find_function(struct amb_pinctrl_soc_data *soc,
+ const char *function)
+{
+ int i;
+
+ for (i = 0; i < soc->nr_functions; i++) {
+ if (soc->functions[i].name &&
+ !strcmp(soc->functions[i].name, function))
+ return i;
+ }
+
+ return -ENOENT;
+}
+
+static int amb_pinctrl_parse_dt(struct amb_pinctrl_soc_data *soc)
+{
+ struct device_node *np = soc->dev->of_node;
+ struct device_node *child;
+ int idxf = 0, idxg = 0;
+ int i, ret;
+
+ /* Count total functions and groups */
+ for_each_child_of_node(np, child) {
+ if (of_find_property(child, "gpio-controller", NULL))
+ continue;
+
+ soc->nr_groups++;
+ }
+
+ if (!soc->nr_groups) {
+ dev_err(soc->dev, "no group is defined\n");
+ return -ENOENT;
+ }
+
+ soc->functions = devm_kcalloc(soc->dev, soc->nr_groups,
+ sizeof(struct ambpin_function), GFP_KERNEL);
+ if (!soc->functions)
+ return -ENOMEM;
+
+ soc->groups = devm_kcalloc(soc->dev, soc->nr_groups,
+ sizeof(struct ambpin_group), GFP_KERNEL);
+ if (!soc->groups)
+ return -ENOMEM;
+
+ /* Parse groups and collect unique functions. */
+ idxg = 0;
+ for_each_child_of_node(np, child) {
+ if (of_find_property(child, "gpio-controller", NULL))
+ continue;
+
+ ret = amb_pinctrl_parse_group(soc, child, idxg, NULL);
+ if (ret) {
+ of_node_put(child);
+ return ret;
+ }
+
+ i = amb_pinctrl_find_function(soc, soc->groups[idxg].function);
+ if (i < 0) {
+ soc->functions[idxf].name = soc->groups[idxg].function;
+ i = idxf++;
+ }
+
+ soc->functions[i].num_groups++;
+ idxg++;
+ }
+
+ soc->nr_functions = idxf;
+
+ for (idxf = 0; idxf < soc->nr_functions; idxf++) {
+ soc->functions[idxf].groups =
+ devm_kcalloc(soc->dev, soc->functions[idxf].num_groups,
+ sizeof(*soc->functions[idxf].groups),
+ GFP_KERNEL);
+ if (!soc->functions[idxf].groups)
+ return -ENOMEM;
+
+ soc->functions[idxf].num_groups = 0;
+ }
+
+ for (idxg = 0; idxg < soc->nr_groups; idxg++) {
+ i = amb_pinctrl_find_function(soc, soc->groups[idxg].function);
+ if (i < 0)
+ return -EINVAL;
+
+ soc->functions[i].groups[soc->functions[i].num_groups++] =
+ soc->groups[idxg].name;
+ }
+
+ return 0;
+}
+
+/* register the pinctrl interface with the pinctrl subsystem */
+static int amb_pinctrl_register(struct amb_pinctrl_soc_data *soc)
+{
+ struct pinctrl_pin_desc *pindesc;
+ int pin, pin_num, rval;
+ struct pinctrl_desc *amb_pinctrl_desc;
+
+ /* Addition one for clk_au dedicated pin */
+ pin_num = soc->bank_num * 32 + 16;
+
+ /* dynamically populate the pin number and pin name for pindesc */
+ pindesc = devm_kcalloc(soc->dev, pin_num, sizeof(*pindesc), GFP_KERNEL);
+ if (!pindesc)
+ return -ENOMEM;
+
+ for (pin = 0; pin < pin_num; pin++) {
+ pindesc[pin].number = pin;
+ pindesc[pin].name = devm_kasprintf(soc->dev, GFP_KERNEL, "io%d", 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 = pin_num;
+ amb_pinctrl_desc->pctlops = &amb_pctrl_ops;
+ amb_pinctrl_desc->pmxops = &amb_pinmux_ops;
+ amb_pinctrl_desc->confops = &amb_pinconf_ops;
+
+ rval = amb_pinctrl_parse_dt(soc);
+ if (rval)
+ return rval;
+
+ 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_pinctrl_soc_data *soc = gpiochip_get_data(gc);
+ u32 bank, offset, data;
+ unsigned long flags;
+
+ bank = PINID_TO_BANK(pin);
+ offset = PINID_TO_OFFSET(pin);
+
+ raw_spin_lock_irqsave(&soc->lock, flags);
+ writel_relaxed(0x1 << offset, soc->gpio_base[bank] + GPIO_MASK_OFFSET);
+ data = (value == 0) ? 0 : 0x1 << offset;
+ writel_relaxed(data, soc->gpio_base[bank] + 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_pinctrl_soc_data *soc = gpiochip_get_data(gc);
+ u32 bank, offset, data;
+ unsigned long flags;
+
+ bank = PINID_TO_BANK(pin);
+ offset = PINID_TO_OFFSET(pin);
+
+ raw_spin_lock_irqsave(&soc->lock, flags);
+ writel_relaxed(0x1 << offset, soc->gpio_base[bank] + GPIO_MASK_OFFSET);
+ data = readl_relaxed(soc->gpio_base[bank] + GPIO_DATA_OFFSET);
+ raw_spin_unlock_irqrestore(&soc->lock, flags);
+
+ return (data >> offset) & 0x1;
+}
+
+static int amb_gpio_get_direction(struct gpio_chip *gc, unsigned int pin)
+{
+ struct amb_pinctrl_soc_data *soc = gpiochip_get_data(gc);
+ u32 bank, offset, data;
+ unsigned long flags;
+
+ bank = PINID_TO_BANK(pin);
+ offset = PINID_TO_OFFSET(pin);
+
+ raw_spin_lock_irqsave(&soc->lock, flags);
+ data = readl_relaxed(soc->gpio_base[bank] + GPIO_DIR_OFFSET);
+ data = (data >> offset) & 0x1;
+ raw_spin_unlock_irqrestore(&soc->lock, flags);
+
+ return data ? 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_pinctrl_soc_data *soc = gpiochip_get_data(gc);
+ u32 bank, offset, data;
+ unsigned long flags;
+
+ bank = PINID_TO_BANK(pin);
+ offset = PINID_TO_OFFSET(pin);
+
+ raw_spin_lock_irqsave(&soc->lock, flags);
+ data = readl_relaxed(soc->gpio_base[bank] + GPIO_DIR_OFFSET);
+ if (input)
+ writel_relaxed(data & ~(0x1 << offset), soc->gpio_base[bank] + GPIO_DIR_OFFSET);
+ else
+ writel_relaxed(data | (0x1 << offset), soc->gpio_base[bank] + 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)
+{
+ amb_gpio_set_direction(gc, pin, false);
+ amb_gpio_set(gc, pin, value);
+ return 0;
+}
+
+/* gpiolib gpio_to_irq callback function */
+static int amb_gpio_to_irq(struct gpio_chip *gc, unsigned int pin)
+{
+ struct amb_pinctrl_soc_data *soc = gpiochip_get_data(gc);
+
+ return irq_create_mapping(soc->domain, pin);
+}
+
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+static void amb_gpio_dbg_show(struct seq_file *s, struct gpio_chip *gc)
+{
+ struct amb_pinctrl_soc_data *soc = gpiochip_get_data(gc);
+ u32 afsel, data, dir, mask, iomux0, iomux1, iomux2, alt;
+ u32 i, bank, offset;
+
+ for (i = 0; i < gc->ngpio; i++) {
+ offset = PINID_TO_OFFSET(i);
+ if (!offset) {
+ bank = PINID_TO_BANK(i);
+
+ afsel = readl_relaxed(soc->gpio_base[bank] + GPIO_AFSEL_OFFSET);
+ dir = readl_relaxed(soc->gpio_base[bank] + GPIO_DIR_OFFSET);
+ mask = readl_relaxed(soc->gpio_base[bank] + GPIO_MASK_OFFSET);
+ writel_relaxed(0xffffffff, soc->gpio_base[bank] + GPIO_MASK_OFFSET);
+ data = readl_relaxed(soc->gpio_base[bank] + GPIO_DATA_OFFSET);
+ writel_relaxed(mask, soc->gpio_base[bank] + GPIO_MASK_OFFSET);
+
+ seq_printf(s, "\nGPIO[%d]:\t[%d - %d]\n", bank, i, i + 32 - 1);
+ 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);
+
+ iomux0 = readl_relaxed(soc->iomux_base + bank * 12);
+ iomux1 = readl_relaxed(soc->iomux_base + bank * 12 + 4);
+ iomux2 = readl_relaxed(soc->iomux_base + bank * 12 + 8);
+ seq_printf(s, "IOMUX_REG%d_0:\t0x%08X\n", bank, iomux0);
+ seq_printf(s, "IOMUX_REG%d_1:\t0x%08X\n", bank, iomux1);
+ seq_printf(s, "IOMUX_REG%d_2:\t0x%08X\n", bank, iomux2);
+ }
+
+ seq_printf(s, " gpio-%-3d", gc->base + i);
+
+ alt = ((iomux2 >> offset) & 1) << 2;
+ alt |= ((iomux1 >> offset) & 1) << 1;
+ alt |= ((iomux0 >> offset) & 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 & (1 << offset)) ? "out" : "in ",
+ (data & (1 << offset)) ? "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_pinctrl_soc_data *soc = gpiochip_get_data(gc);
+ void __iomem *gpio_base = amb_get_gpio_base(soc, data);
+ void __iomem *iomux_base = soc->iomux_base;
+ u32 i, val, bank, offset;
+ unsigned long flags;
+
+ bank = PINID_TO_BANK(irqd_to_hwirq(data));
+ offset = PINID_TO_OFFSET(irqd_to_hwirq(data));
+
+ 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, i));
+ val &= ~(0x1 << offset);
+ writel_relaxed(val, iomux_base + IOMUX_OFFSET(bank, 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_pinctrl_soc_data *soc = gpiochip_get_data(gc);
+ void __iomem *gpio_base = amb_get_gpio_base(soc, data);
+ u32 offset, ie;
+ unsigned long flags;
+
+ offset = PINID_TO_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_ack(struct irq_data *data)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
+ struct amb_pinctrl_soc_data *soc = gpiochip_get_data(gc);
+ void __iomem *gpio_base = amb_get_gpio_base(soc, data);
+ u32 offset = PINID_TO_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 gpio_chip *gc = irq_data_get_irq_chip_data(data);
+ struct amb_pinctrl_soc_data *soc = gpiochip_get_data(gc);
+ void __iomem *gpio_base = amb_get_gpio_base(soc, data);
+ u32 offset, ie;
+ unsigned long flags;
+
+ offset = PINID_TO_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 gpio_chip *gc = irq_data_get_irq_chip_data(data);
+ struct amb_pinctrl_soc_data *soc = gpiochip_get_data(gc);
+ void __iomem *gpio_base = amb_get_gpio_base(soc, data);
+ u32 offset, ie;
+ unsigned long flags;
+
+ offset = PINID_TO_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 gpio_chip *gc = irq_data_get_irq_chip_data(data);
+ struct amb_pinctrl_soc_data *soc = gpiochip_get_data(gc);
+ void __iomem *gpio_base = amb_get_gpio_base(soc, data);
+ u32 offset, ie;
+ unsigned long flags;
+
+ offset = PINID_TO_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 gpio_chip *gc = irq_data_get_irq_chip_data(data);
+ struct amb_pinctrl_soc_data *soc = gpiochip_get_data(gc);
+ void __iomem *gpio_base = amb_get_gpio_base(soc, data);
+ u32 offset = PINID_TO_OFFSET(irqd_to_hwirq(data));
+ u32 mask, bit, sense, bothedges, event;
+ unsigned long flags;
+
+ mask = ~(0x1 << offset);
+ bit = (0x1 << offset);
+ 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:
+ pr_err("%s: irq[%d] type[%d] fail!\n",
+ __func__, data->irq, type);
+ return -EINVAL;
+ }
+
+ raw_spin_lock_irqsave(&soc->lock, flags);
+ 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(0x1 << 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 gpio_chip *gc = irq_data_get_irq_chip_data(data);
+ struct amb_pinctrl_soc_data *soc = gpiochip_get_data(gc);
+ u32 bank = PINID_TO_BANK(irqd_to_hwirq(data));
+ u32 offset = PINID_TO_OFFSET(irqd_to_hwirq(data));
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&soc->lock, flags);
+ if (on)
+ soc->irq_wake_mask[bank] |= (1 << offset);
+ else
+ soc->irq_wake_mask[bank] &= ~(1 << offset);
+ raw_spin_unlock_irqrestore(&soc->lock, flags);
+ }
+ 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,
+ .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_pinctrl_soc_data *soc = d->host_data;
+
+ irq_set_chip_data(irq, soc->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_pinctrl_soc_data *soc;
+ struct irq_chip *irqchip;
+ u32 i, bit, gpio_mis, gpio_hwirq;
+
+ irqchip = irq_desc_get_chip(desc);
+ chained_irq_enter(irqchip, desc);
+
+ soc = irq_desc_get_handler_data(desc);
+
+ /* find the GPIO bank generating this irq */
+ for (i = 0; i < soc->bank_num; i++) {
+ if (soc->irq[i] == irq_desc_get_irq(desc))
+ break;
+ }
+
+ if (i == soc->bank_num)
+ goto out;
+
+ gpio_mis = readl_relaxed(soc->gpio_base[i] + GPIO_MIS_OFFSET);
+ while (gpio_mis) {
+ bit = __ffs(gpio_mis);
+ gpio_hwirq = i * 32 + bit;
+ generic_handle_domain_irq(soc->domain, gpio_hwirq);
+ gpio_mis &= ~BIT(bit);
+ }
+
+out:
+ chained_irq_exit(irqchip, desc);
+}
+
+/* register the pinctrl interface with the pinctrl subsystem */
+static int amb_gpio_register(struct amb_pinctrl_soc_data *soc)
+{
+ struct device_node *np;
+ int i, rval = -ENODEV;
+
+ for_each_child_of_node(soc->dev->of_node, np) {
+ if (of_find_property(np, "gpio-controller", NULL)) {
+ rval = 0;
+ break;
+ }
+ }
+ if (rval < 0) {
+ dev_err(soc->dev, "no gpio-controller child node\n");
+ return rval;
+ }
+
+ soc->gc = devm_kzalloc(soc->dev, sizeof(struct gpio_chip), GFP_KERNEL);
+ if (!soc->gc)
+ return -ENOMEM;
+ soc->gc->label = dev_name(soc->dev);
+ soc->gc->base = -1;
+ soc->gc->request = gpiochip_generic_request;
+ soc->gc->free = gpiochip_generic_free;
+ soc->gc->direction_input = amb_gpio_direction_input;
+ soc->gc->direction_output = amb_gpio_direction_output;
+ soc->gc->get_direction = amb_gpio_get_direction;
+ soc->gc->get = amb_gpio_get;
+ soc->gc->set = amb_gpio_set;
+ soc->gc->to_irq = amb_gpio_to_irq;
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+ soc->gc->dbg_show = amb_gpio_dbg_show;
+#endif
+ soc->gc->parent = soc->dev;
+ soc->gc->fwnode = of_fwnode_handle(np);
+ soc->gc->ngpio = soc->bank_num * 32;
+
+ rval = devm_gpiochip_add_data(soc->dev, soc->gc, soc);
+ if (rval) {
+ dev_err(soc->dev, "gpiochip_add_data failed %d\n", rval);
+ return rval;
+ }
+
+ for (i = 0; i < soc->bank_num; i++) {
+ writel_relaxed(0xffffffff, soc->gpio_base[i] + GPIO_ENABLE_OFFSET);
+ writel_relaxed(0x00000000, soc->gpio_base[i] + GPIO_AFSEL_OFFSET);
+ writel_relaxed(0x00000000, soc->gpio_base[i] + GPIO_MASK_OFFSET);
+ }
+
+ /* Initialize GPIO irq */
+ soc->domain = irq_domain_add_linear(np, soc->gc->ngpio,
+ &amb_gpio_irq_domain_ops, soc);
+ if (!soc->domain) {
+ dev_err(soc->dev, "Failed to create irqdomain\n");
+ return -ENODEV;
+ }
+
+ for (i = 0; i < soc->bank_num; i++) {
+ irq_set_irq_type(soc->irq[i], IRQ_TYPE_LEVEL_HIGH);
+ irq_set_chained_handler_and_data(soc->irq[i], amb_gpio_handle_irq, soc);
+ }
+
+ return 0;
+}
+
+static int amb_pinctrl_probe(struct platform_device *pdev)
+{
+ struct amb_pinctrl_soc_data *soc;
+ struct device_node *np;
+ 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->bank_num = of_irq_count(np);
+ if (!soc->bank_num || soc->bank_num > MAX_BANK_NUM)
+ return dev_err_probe(&pdev->dev, -EINVAL, "Invalid gpio bank(irq)");
+
+ for (i = 0; i < soc->bank_num; i++) {
+ soc->gpio_base[i] = devm_platform_ioremap_resource(pdev, i);
+ if (IS_ERR(soc->gpio_base[i]))
+ return dev_err_probe(&pdev->dev, PTR_ERR(soc->gpio_base[i]),
+ "couldn't get gpio[%d] reg", i);
+
+ soc->irq[i] = platform_get_irq(pdev, i);
+ if (soc->irq[i] < 0)
+ return dev_err_probe(&pdev->dev, -ENODEV, "couldn't get gpio[%d] irq", i);
+ }
+
+ soc->iomux_base = devm_platform_ioremap_resource(pdev, i);
+ if (IS_ERR(soc->iomux_base))
+ return dev_err_probe(&pdev->dev, PTR_ERR(soc->iomux_base),
+ "couldn't get iomux reg");
+
+ soc->ds_regmap = syscon_regmap_lookup_by_phandle(np, "ambarella,drive-strength-syscon");
+ if (IS_ERR(soc->ds_regmap))
+ return dev_err_probe(&pdev->dev, PTR_ERR(soc->ds_regmap),
+ "couldn't get drive-strength regmap");
+
+ soc->pull_regmap = syscon_regmap_lookup_by_phandle(np, "ambarella,pull-syscon");
+ if (IS_ERR(soc->pull_regmap))
+ return dev_err_probe(&pdev->dev, PTR_ERR(soc->pull_regmap),
+ "couldn't get pull regmap");
+
+ for (i = 0; i < soc->bank_num; i++) {
+ soc->ds0[i] = soc->data->ds0[i];
+ soc->ds1[i] = soc->data->ds1[i];
+ soc->ds2[i] = soc->data->ds2[i];
+ soc->pull_en[i] = soc->data->pull_en[i];
+ soc->pull_dir[i] = soc->data->pull_dir[i];
+ }
+
+ soc->have_ds2 = soc->data->have_ds2;
+ soc->hsm_domain_id = soc->data->hsm_domain_id;
+ soc->clk_au_dedicated_pin = soc->data->clk_au_dedicated_pin;
+
+ raw_spin_lock_init(&soc->lock);
+
+ /* not allowed to use for non-existed pins */
+ for (i = soc->bank_num * 32; i < MAX_BANK_NUM * 32; i++)
+ set_bit(i, soc->used);
+
+ if (soc->clk_au_dedicated_pin)
+ clear_bit(soc->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 i;
+
+ for (i = 0; i < soc->bank_num; i++) {
+ regmap_read(soc->pull_regmap, soc->pull_en[i], &soc->pm[i].pull[0]);
+ regmap_read(soc->pull_regmap, soc->pull_dir[i], &soc->pm[i].pull[1]);
+
+ regmap_read(soc->ds_regmap, soc->ds0[i], &soc->pm[i].ds[0]);
+ regmap_read(soc->ds_regmap, soc->ds1[i], &soc->pm[i].ds[1]);
+ if (soc->have_ds2)
+ regmap_read(soc->ds_regmap, soc->ds2[i], &soc->pm[i].ds[2]);
+
+ soc->pm[i].iomux[0] = readl_relaxed(soc->iomux_base + IOMUX_OFFSET(i, 0));
+ soc->pm[i].iomux[1] = readl_relaxed(soc->iomux_base + IOMUX_OFFSET(i, 1));
+ soc->pm[i].iomux[2] = readl_relaxed(soc->iomux_base + IOMUX_OFFSET(i, 2));
+
+ soc->pm[i].afsel = readl_relaxed(soc->gpio_base[i] + GPIO_AFSEL_OFFSET);
+ soc->pm[i].dir = readl_relaxed(soc->gpio_base[i] + GPIO_DIR_OFFSET);
+ soc->pm[i].is = readl_relaxed(soc->gpio_base[i] + GPIO_IS_OFFSET);
+ soc->pm[i].ibe = readl_relaxed(soc->gpio_base[i] + GPIO_IBE_OFFSET);
+ soc->pm[i].iev = readl_relaxed(soc->gpio_base[i] + GPIO_IEV_OFFSET);
+ soc->pm[i].ie = readl_relaxed(soc->gpio_base[i] + GPIO_IE_OFFSET);
+ soc->pm[i].mask = readl_relaxed(soc->gpio_base[i] + GPIO_MASK_OFFSET);
+ writel_relaxed(0xffffffff, soc->gpio_base[i] + GPIO_MASK_OFFSET);
+ soc->pm[i].data = readl_relaxed(soc->gpio_base[i] + GPIO_DATA_OFFSET);
+
+ if (soc->irq_wake_mask[i])
+ writel_relaxed(soc->irq_wake_mask[i], soc->gpio_base[i] + GPIO_IE_OFFSET);
+ }
+
+ if (soc->clk_au_dedicated_pin) {
+ i = PINID_TO_BANK(soc->clk_au_dedicated_pin);
+ soc->pm[i].iomux[0] = readl_relaxed(soc->iomux_base + IOMUX_OFFSET(i, 0));
+ soc->pm[i].iomux[1] = readl_relaxed(soc->iomux_base + IOMUX_OFFSET(i, 1));
+ soc->pm[i].iomux[2] = readl_relaxed(soc->iomux_base + IOMUX_OFFSET(i, 2));
+ }
+
+ return 0;
+}
+
+static int amb_pinctrl_resume_noirq(struct device *dev)
+{
+ struct amb_pinctrl_soc_data *soc = dev_get_drvdata(dev);
+ u32 i;
+
+ for (i = 0; i < soc->bank_num; i++) {
+ regmap_write(soc->pull_regmap, soc->pull_en[i], soc->pm[i].pull[0]);
+ regmap_write(soc->pull_regmap, soc->pull_dir[i], soc->pm[i].pull[1]);
+
+ regmap_write(soc->ds_regmap, soc->ds0[i], soc->pm[i].ds[0]);
+ regmap_write(soc->ds_regmap, soc->ds1[i], soc->pm[i].ds[1]);
+ if (soc->have_ds2)
+ regmap_write(soc->ds_regmap, soc->ds2[i], soc->pm[i].ds[2]);
+
+ writel_relaxed(soc->pm[i].iomux[0], soc->iomux_base + IOMUX_OFFSET(i, 0));
+ writel_relaxed(soc->pm[i].iomux[1], soc->iomux_base + IOMUX_OFFSET(i, 1));
+ writel_relaxed(soc->pm[i].iomux[2], soc->iomux_base + IOMUX_OFFSET(i, 2));
+
+ writel_relaxed(soc->pm[i].afsel, soc->gpio_base[i] + GPIO_AFSEL_OFFSET);
+ writel_relaxed(soc->pm[i].dir, soc->gpio_base[i] + GPIO_DIR_OFFSET);
+ /* Expose DATA writes while restoring the saved GPIO state. */
+ writel_relaxed(0xffffffff,
+ soc->gpio_base[i] + GPIO_MASK_OFFSET);
+ writel_relaxed(soc->pm[i].data, soc->gpio_base[i] + GPIO_DATA_OFFSET);
+ /* Ensure DATA restore reaches hardware before restoring mask. */
+ wmb();
+ writel_relaxed(soc->pm[i].mask, soc->gpio_base[i] + GPIO_MASK_OFFSET);
+ writel_relaxed(soc->pm[i].is, soc->gpio_base[i] + GPIO_IS_OFFSET);
+ writel_relaxed(soc->pm[i].ibe, soc->gpio_base[i] + GPIO_IBE_OFFSET);
+ writel_relaxed(soc->pm[i].iev, soc->gpio_base[i] + GPIO_IEV_OFFSET);
+ writel_relaxed(soc->pm[i].ie, soc->gpio_base[i] + GPIO_IE_OFFSET);
+ writel_relaxed(0xffffffff, soc->gpio_base[i] + GPIO_ENABLE_OFFSET);
+ }
+
+ if (soc->clk_au_dedicated_pin) {
+ i = PINID_TO_BANK(soc->clk_au_dedicated_pin);
+ writel_relaxed(soc->pm[i].iomux[0], soc->iomux_base + IOMUX_OFFSET(i, 0));
+ writel_relaxed(soc->pm[i].iomux[1], soc->iomux_base + IOMUX_OFFSET(i, 1));
+ writel_relaxed(soc->pm[i].iomux[2], soc->iomux_base + IOMUX_OFFSET(i, 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 amb_pinctrl_data 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,
+};
+
+static const struct of_device_id amb_pinctrl_dt_match[] = {
+ {
+ .compatible = "ambarella,cv75-pinctrl",
+ .data = &cv75_pinctrl_data,
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, amb_pinctrl_dt_match);
+
+static void amb_pinctrl_remove(struct platform_device *pdev)
+{
+ struct amb_pinctrl_soc_data *soc = platform_get_drvdata(pdev);
+ unsigned int i;
+
+ for (i = 0; i < soc->bank_num; i++)
+ irq_set_chained_handler_and_data(soc->irq[i], NULL, NULL);
+
+ if (soc->domain)
+ irq_domain_remove(soc->domain);
+}
+
+static struct platform_driver amb_pinctrl_driver = {
+ .probe = amb_pinctrl_probe,
+ .remove = amb_pinctrl_remove,
+ .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");
--
2.34.1
NOTE: This email (including attachments) contain Ambarella Proprietary and/or Confidential Information and is intended solely for the use of the individual(s) to whom it is addressed. Any unauthorized review, use, disclosure, distribute, copy, or print is prohibited. If you are not an intended recipient, please contact the sender by reply email and destroy all copies of the original message. Thank you.
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH 08/10] serial: ambarella: add Ambarella UART driver
2026-07-30 10:43 [PATCH 00/10] Ambarella CV75 SoC minimal bring-up Long Zhao
` (6 preceding siblings ...)
2026-07-30 10:43 ` [PATCH 07/10] pinctrl: ambarella: add Ambarella pin controller Long Zhao
@ 2026-07-30 10:43 ` Long Zhao
2026-07-30 10:54 ` Greg Kroah-Hartman
2026-07-30 10:43 ` [PATCH 09/10] arm64: ambarella: add ARCH_AMBARELLA and CV75 EVK DT Long Zhao
` (2 subsequent siblings)
10 siblings, 1 reply; 21+ messages in thread
From: Long Zhao @ 2026-07-30 10:43 UTC (permalink / raw)
To: Arnd Bergmann, Alexandre Belloni, linux-arm-kernel, soc
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, devicetree,
Michael Turquette, Stephen Boyd, linux-clk, Linus Walleij,
linux-gpio, Greg Kroah-Hartman, Jiri Slaby, linux-serial,
Catalin Marinas, Will Deacon, linux-kernel, Long Zhao
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 | 937 ++++++++++++++++++++++++++++
3 files changed, 959 insertions(+)
create mode 100644 drivers/tty/serial/ambarella_uart.c
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..e79f27c71986
--- /dev/null
+++ b/drivers/tty/serial/ambarella_uart.c
@@ -0,0 +1,937 @@
+// 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/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 */
+
+#define AMBARELLA_UART_TIMEOUT (1000000)
+
+struct ambarella_uart_port {
+ struct uart_port port;
+ struct clk *uart_pll;
+ unsigned long flags;
+ u32 mcr;
+};
+
+static struct ambarella_uart_port ambarella_port[AMBA_UART_MAX_NUM];
+
+static void __serial_ambarella_stop_tx(struct uart_port *port)
+{
+ u32 ier;
+
+ ier = readl_relaxed(port->membase + UART_IE_OFFSET);
+ writel_relaxed(ier & ~UART_IE_ETBEI, port->membase + UART_IE_OFFSET);
+}
+
+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)
+{
+ u32 ier = readl_relaxed(port->membase + UART_IE_OFFSET);
+
+ writel_relaxed(ier | UART_IE_EDSSI, port->membase + UART_IE_OFFSET);
+}
+
+static void __serial_ambarella_disable_ms(struct uart_port *port)
+{
+ u32 ier = readl_relaxed(port->membase + UART_IE_OFFSET);
+
+ writel_relaxed(ier & ~UART_IE_EDSSI, port->membase + UART_IE_OFFSET);
+}
+
+static inline void wait_for_tx(struct uart_port *port)
+{
+ unsigned int timeout = AMBARELLA_UART_TIMEOUT;
+
+ while (timeout &&
+ !(readl(port->membase + UART_LS_OFFSET) & UART_LS_TEMT)) {
+ udelay(1);
+ timeout--;
+ }
+
+ if (unlikely(!timeout)) {
+ 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 void wait_for_rx(struct uart_port *port)
+{
+ u32 ls;
+
+ ls = readl_relaxed(port->membase + UART_LS_OFFSET);
+ while ((ls & UART_LS_DR) != UART_LS_DR) {
+ cpu_relax();
+ ls = readl_relaxed(port->membase + UART_LS_OFFSET);
+ }
+}
+
+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;
+
+ amb_port = (struct ambarella_uart_port *)(port->private_data);
+
+ 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. */
+ writel_relaxed(0, port->membase + UART_IE_OFFSET);
+}
+
+static inline void serial_ambarella_receive_chars(struct uart_port *port,
+ u32 tmo)
+{
+ u32 ch, flag, ls;
+ int max_count;
+
+ ls = readl_relaxed(port->membase + UART_LS_OFFSET);
+ max_count = port->fifosize;
+
+ do {
+ flag = TTY_NORMAL;
+ 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 (likely(ls & UART_LS_DR)) {
+ ch = readl_relaxed(port->membase + UART_RB_OFFSET);
+ port->icount.rx++;
+ tmo = 0;
+
+ if (uart_handle_sysrq_char(port, ch))
+ goto ignore_char;
+
+ uart_insert_char(port, ls, UART_LS_OE, ch, flag);
+ } else if (tmo) {
+ ch = readl_relaxed(port->membase + UART_RB_OFFSET);
+ }
+
+ignore_char:
+ ls = readl_relaxed(port->membase + UART_LS_OFFSET);
+ } while ((ls & (UART_LS_DR | UART_LSR_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;
+ struct ambarella_uart_port *amb_port;
+ unsigned long flags;
+ u32 ii, ier;
+
+ amb_port = (struct ambarella_uart_port *)(port->private_data);
+
+ spin_lock_irqsave(&port->lock, flags);
+
+ 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 */
+ ier = readl_relaxed(port->membase + UART_IE_OFFSET);
+ writel_relaxed(ier & ~UART_IE_ERETOI,
+ amb_port->port.membase + UART_IE_OFFSET);
+ writel_relaxed(ier | UART_IE_ERETOI,
+ amb_port->port.membase + UART_IE_OFFSET);
+ 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;
+ }
+
+ spin_unlock_irqrestore(&port->lock, flags);
+
+ 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)
+{
+ u32 ier;
+
+ /* if transmit buffer is not allocated, just return */
+ if (!port->state->port.xmit_buf)
+ return;
+
+ ier = readl_relaxed(port->membase + UART_IE_OFFSET);
+ writel_relaxed(ier | UART_IE_ETBEI, port->membase + UART_IE_OFFSET);
+
+ 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)
+{
+ u32 ier;
+
+ ier = readl_relaxed(port->membase + UART_IE_OFFSET);
+ writel_relaxed(ier & ~UART_IE_ERBFI, port->membase + UART_IE_OFFSET);
+}
+
+static unsigned int serial_ambarella_tx_empty(struct uart_port *port)
+{
+ unsigned int lsr;
+ unsigned long flags;
+
+ spin_lock_irqsave(&port->lock, flags);
+ lsr = readl_relaxed(port->membase + UART_LS_OFFSET);
+ spin_unlock_irqrestore(&port->lock, flags);
+
+ 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;
+ u32 mcr, mcr_new = 0;
+
+ amb_port = (struct ambarella_uart_port *)(port->private_data);
+
+ 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 = readl_relaxed(port->membase + UART_LC_OFFSET);
+ unsigned long flags;
+
+ spin_lock_irqsave(&port->lock, flags);
+ 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);
+ spin_unlock_irqrestore(&port->lock, flags);
+}
+
+static void serial_ambarella_hw_deinit(struct ambarella_uart_port *amb_port)
+{
+ struct uart_port *port = &amb_port->port;
+
+ /* Disable interrupts */
+ writel_relaxed(0, port->membase + UART_IE_OFFSET);
+
+ /* 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;
+
+ amb_port = (struct ambarella_uart_port *)(port->private_data);
+
+ 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;
+ }
+
+ writel_relaxed(DEFAULT_AMBARELLA_UART_IER,
+ port->membase + UART_IE_OFFSET);
+
+ return 0;
+}
+
+static void serial_ambarella_shutdown(struct uart_port *port)
+{
+ struct ambarella_uart_port *amb_port;
+ u32 lcr;
+ unsigned long flags;
+
+ amb_port = (struct ambarella_uart_port *)(port->private_data);
+
+ spin_lock_irqsave(&port->lock, flags);
+ 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);
+ spin_unlock_irqrestore(&port->lock, flags);
+
+ 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;
+ unsigned int baud, quot;
+ unsigned long flags;
+ u32 lc = 0x0;
+
+ amb_port = (struct ambarella_uart_port *)(port->private_data);
+
+ 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);
+
+ spin_lock_irqsave(&port->lock, flags);
+ 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);
+
+ spin_unlock_irqrestore(&port->lock, flags);
+}
+
+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) {
+ wait_for_rx(port);
+ return readl_relaxed(port->membase + UART_RB_OFFSET);
+ }
+ return 0;
+}
+#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_console_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_write(struct console *co,
+ const char *s, unsigned int count)
+{
+ struct uart_port *port;
+ int locked = 1;
+ unsigned long flags;
+
+ port = &ambarella_port[co->index].port;
+
+ if (!port->suspended) {
+ if (port->sysrq)
+ locked = 0;
+ else if (oops_in_progress)
+ locked = uart_port_trylock_irqsave(port, &flags);
+ else
+ uart_port_lock_irqsave(port, &flags);
+
+ uart_console_write(port, s, count,
+ serial_ambarella_console_putchar);
+ wait_for_tx(port);
+
+ if (locked)
+ uart_port_unlock_irqrestore(port, flags);
+ }
+}
+
+static int __init serial_ambarella_console_setup(struct console *co,
+ char *options)
+{
+ struct uart_port *port;
+ int baud = 115200, bits = 8, parity = 'n', flow = 'n';
+
+ if (co->index < 0 || co->index >= serial_ambarella_reg.nr)
+ co->index = 0;
+
+ port = &ambarella_port[co->index].port;
+ if (!port->membase) {
+ pr_err("No device available for serial console\n");
+ return -ENODEV;
+ }
+
+ port->ops = &serial_ambarella_pops;
+ port->private_data = &ambarella_port[co->index];
+ port->line = co->index;
+
+ 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",
+ .write = serial_ambarella_console_write,
+ .device = uart_console_device,
+ .setup = serial_ambarella_console_setup,
+ .flags = CON_PRINTBUFFER | CON_ANYTIME,
+ .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_console_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,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.private_data = amb_port;
+ 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" },
+ { .compatible = "ambarella,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
NOTE: This email (including attachments) contain Ambarella Proprietary and/or Confidential Information and is intended solely for the use of the individual(s) to whom it is addressed. Any unauthorized review, use, disclosure, distribute, copy, or print is prohibited. If you are not an intended recipient, please contact the sender by reply email and destroy all copies of the original message. Thank you.
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH 09/10] arm64: ambarella: add ARCH_AMBARELLA and CV75 EVK DT
2026-07-30 10:43 [PATCH 00/10] Ambarella CV75 SoC minimal bring-up Long Zhao
` (7 preceding siblings ...)
2026-07-30 10:43 ` [PATCH 08/10] serial: ambarella: add Ambarella UART driver Long Zhao
@ 2026-07-30 10:43 ` Long Zhao
2026-07-30 12:18 ` Marc Zyngier
2026-07-30 10:43 ` [PATCH 10/10] MAINTAINERS: add ARM/AMBARELLA SoC support Long Zhao
2026-07-30 10:48 ` [PATCH 00/10] Ambarella CV75 SoC minimal bring-up Krzysztof Kozlowski
10 siblings, 1 reply; 21+ messages in thread
From: Long Zhao @ 2026-07-30 10:43 UTC (permalink / raw)
To: Arnd Bergmann, Alexandre Belloni, linux-arm-kernel, soc
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, devicetree,
Michael Turquette, Stephen Boyd, linux-clk, Linus Walleij,
linux-gpio, Greg Kroah-Hartman, Jiri Slaby, linux-serial,
Catalin Marinas, Will Deacon, linux-kernel, Long Zhao
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 | 151 +++++++++++++++++++++
arch/arm64/configs/defconfig | 3 +
6 files changed, 197 insertions(+)
create mode 100644 arch/arm64/boot/dts/ambarella/Makefile
create mode 100644 arch/arm64/boot/dts/ambarella/cv75-evk.dts
create mode 100644 arch/arm64/boot/dts/ambarella/cv75.dtsi
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..7c6a61f473ef
--- /dev/null
+++ b/arch/arm64/boot/dts/ambarella/cv75.dtsi
@@ -0,0 +1,151 @@
+// 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>
+#include <dt-bindings/pinctrl/ambarella,pinctrl.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@fff0100000 {
+ 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)>;
+ };
+
+ 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", "ambarella,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@e4013000 {
+ compatible = "ambarella,cv75-pinctrl";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <0xe4013000 0x1000>, <0xe4014000 0x1000>,
+ <0xe4015000 0x1000>, <0xe4010000 0x1000>;
+ reg-names = "gpio0", "gpio1", "gpio2", "iomux";
+ interrupts = <GIC_SPI 163 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 164 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 165 IRQ_TYPE_LEVEL_HIGH>;
+ ambarella,drive-strength-syscon = <&rct>;
+ ambarella,pull-syscon = <&s_scratchpad_syscon>;
+
+ gpio: gpio@0 {
+ reg = <0>;
+ gpio-controller;
+ #gpio-cells = <2>;
+ gpio-ranges = <&pinctrl 0 0 95>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ };
+
+ uart0_pins: uart0-pins {
+ function = "uart0";
+ pinmux = <AMBA_PINMUX(44, 1)>,
+ <AMBA_PINMUX(45, 1)>;
+ };
+ };
+ };
+
+ 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",
+ "ambarella,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
NOTE: This email (including attachments) contain Ambarella Proprietary and/or Confidential Information and is intended solely for the use of the individual(s) to whom it is addressed. Any unauthorized review, use, disclosure, distribute, copy, or print is prohibited. If you are not an intended recipient, please contact the sender by reply email and destroy all copies of the original message. Thank you.
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH 10/10] MAINTAINERS: add ARM/AMBARELLA SoC support
2026-07-30 10:43 [PATCH 00/10] Ambarella CV75 SoC minimal bring-up Long Zhao
` (8 preceding siblings ...)
2026-07-30 10:43 ` [PATCH 09/10] arm64: ambarella: add ARCH_AMBARELLA and CV75 EVK DT Long Zhao
@ 2026-07-30 10:43 ` Long Zhao
2026-07-30 10:48 ` [PATCH 00/10] Ambarella CV75 SoC minimal bring-up Krzysztof Kozlowski
10 siblings, 0 replies; 21+ messages in thread
From: Long Zhao @ 2026-07-30 10:43 UTC (permalink / raw)
To: Arnd Bergmann, Alexandre Belloni, linux-arm-kernel, soc
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, devicetree,
Michael Turquette, Stephen Boyd, linux-clk, Linus Walleij,
linux-gpio, Greg Kroah-Hartman, Jiri Slaby, linux-serial,
Catalin Marinas, Will Deacon, linux-kernel, Long Zhao
Add a maintainer entry covering Ambarella CV75 bindings, drivers
and device trees.
Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
MAINTAINERS | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 803b60ad628f..34c0ac2e00b9 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2502,6 +2502,24 @@ 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,uart.yaml
+F: Documentation/devicetree/bindings/soc/ambarella/
+F: arch/arm64/boot/dts/ambarella/
+F: drivers/clk/ambarella/
+F: drivers/pinctrl/pinctrl-ambarella.c
+F: drivers/tty/serial/ambarella_uart.c
+F: include/dt-bindings/clock/ambarella,cv75-clock.h
+F: include/dt-bindings/pinctrl/ambarella,pinctrl.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
NOTE: This email (including attachments) contain Ambarella Proprietary and/or Confidential Information and is intended solely for the use of the individual(s) to whom it is addressed. Any unauthorized review, use, disclosure, distribute, copy, or print is prohibited. If you are not an intended recipient, please contact the sender by reply email and destroy all copies of the original message. Thank you.
^ permalink raw reply related [flat|nested] 21+ messages in thread
* Re: [PATCH 00/10] Ambarella CV75 SoC minimal bring-up
2026-07-30 10:43 [PATCH 00/10] Ambarella CV75 SoC minimal bring-up Long Zhao
` (9 preceding siblings ...)
2026-07-30 10:43 ` [PATCH 10/10] MAINTAINERS: add ARM/AMBARELLA SoC support Long Zhao
@ 2026-07-30 10:48 ` Krzysztof Kozlowski
2026-07-30 11:05 ` [EXT] " Long Zhao
10 siblings, 1 reply; 21+ messages in thread
From: Krzysztof Kozlowski @ 2026-07-30 10:48 UTC (permalink / raw)
To: Long Zhao, Arnd Bergmann, Alexandre Belloni, linux-arm-kernel,
soc
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, devicetree,
Michael Turquette, Stephen Boyd, linux-clk, Linus Walleij,
linux-gpio, Greg Kroah-Hartman, Jiri Slaby, linux-serial,
Catalin Marinas, Will Deacon, linux-kernel
On 30/07/2026 12:43, Long Zhao wrote:
>
> NOTE: This email (including attachments) contain Ambarella Proprietary and/or Confidential Information and is intended solely for the use of the individual(s) to whom it is addressed. Any unauthorized review, use, disclosure, distribute, copy, or print is prohibited. If you are not an intended recipient, please contact the sender by reply email and destroy all copies of the original message. Thank you.
Maybe I am the intended recipient of your message, maybe not. I don't
want to have any legal questions regarding upstream, public
collaboration, thus probably I should just remove your messages.
Please talk with your IT that such disclaimers in open-source are not
desired (and maybe even harmful).
If you do not understand why, please also see:
https://www.youtube.com/live/fMeH7wqOwXA?si=GY7igfbda6vnjXlJ&t=835
If you need to go around company SMTP server, then consider using b4
web-relay: https://b4.docs.kernel.org/en/latest/contributor/send.html
Please be informed that by responding to this email you agree that all
communications from you and/or your company is made public. In other
words, all messages originating from you and/or your company will be
made public.
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH 05/10] dt-bindings: serial: add Ambarella UART
2026-07-30 10:43 ` [PATCH 05/10] dt-bindings: serial: add Ambarella UART Long Zhao
@ 2026-07-30 10:49 ` Krzysztof Kozlowski
0 siblings, 0 replies; 21+ messages in thread
From: Krzysztof Kozlowski @ 2026-07-30 10:49 UTC (permalink / raw)
To: Long Zhao, Arnd Bergmann, Alexandre Belloni, linux-arm-kernel,
soc
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, devicetree,
Michael Turquette, Stephen Boyd, linux-clk, Linus Walleij,
linux-gpio, Greg Kroah-Hartman, Jiri Slaby, linux-serial,
Catalin Marinas, Will Deacon, linux-kernel
On 30/07/2026 12:43, Long Zhao wrote:
> Document the Ambarella UART controller.
I will drop all your patches from DT patchwork as you requested in the
footer.
I cannot destroy the copies, but I will comply as much as I can.
Just a note - generic compatibles are not the way to go. Please
carefully read writing bindings document and DTS101 slides
...
> NOTE: This email (including attachments) contain Ambarella Proprietary and/or Confidential Information and is intended solely for the use of the individual(s) to whom it is addressed. Any unauthorized review, use, disclosure, distribute, copy, or print is prohibited. If you are not an intended recipient, please contact the sender by reply email and destroy all copies of the original message. Thank you.
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH 08/10] serial: ambarella: add Ambarella UART driver
2026-07-30 10:43 ` [PATCH 08/10] serial: ambarella: add Ambarella UART driver Long Zhao
@ 2026-07-30 10:54 ` Greg Kroah-Hartman
0 siblings, 0 replies; 21+ messages in thread
From: Greg Kroah-Hartman @ 2026-07-30 10:54 UTC (permalink / raw)
To: Long Zhao
Cc: Arnd Bergmann, Alexandre Belloni, linux-arm-kernel, soc,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, devicetree,
Michael Turquette, Stephen Boyd, linux-clk, Linus Walleij,
linux-gpio, Jiri Slaby, linux-serial, Catalin Marinas,
Will Deacon, linux-kernel
On Thu, Jul 30, 2026 at 06:43:28PM +0800, Long Zhao wrote:
> NOTE: This email (including attachments) contain Ambarella Proprietary and/or Confidential Information and is intended solely for the use of the individual(s) to whom it is addressed. Any unauthorized review, use, disclosure, distribute, copy, or print is prohibited. If you are not an intended recipient, please contact the sender by reply email and destroy all copies of the original message. Thank you.
Now deleted.
^ permalink raw reply [flat|nested] 21+ messages in thread
* RE: [EXT] Re: [PATCH 00/10] Ambarella CV75 SoC minimal bring-up
2026-07-30 10:48 ` [PATCH 00/10] Ambarella CV75 SoC minimal bring-up Krzysztof Kozlowski
@ 2026-07-30 11:05 ` Long Zhao
2026-07-30 11:56 ` Alexandre Belloni
0 siblings, 1 reply; 21+ messages in thread
From: Long Zhao @ 2026-07-30 11:05 UTC (permalink / raw)
To: Krzysztof Kozlowski, Arnd Bergmann, Alexandre Belloni,
linux-arm-kernel@lists.infradead.org, soc@lists.linux.dev
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley,
devicetree@vger.kernel.org, Michael Turquette, Stephen Boyd,
linux-clk@vger.kernel.org, Linus Walleij,
linux-gpio@vger.kernel.org, Greg Kroah-Hartman, Jiri Slaby,
linux-serial@vger.kernel.org, Catalin Marinas, Will Deacon,
linux-kernel@vger.kernel.org
Hi Krzysztof,
Thank you for the feedback.
Regarding the footer: I did not see that disclaimer when I sent a
test copy to myself. It looks like our mail system automatically
appends it only on messages going outside the company. That was
unintentional on my side, and I will check with our IT and find a way
to send v2 without that corporate disclaimer.
I will also switch to SoC-specific compatibles and re-read the writing
bindings documentation and DTS coding guidelines before posting v2.
I will wait a bit for further comments on the rest of the series and
address them together in v2.
Thanks,
Long Zhao
-----Original Message-----
From: Krzysztof Kozlowski <krzk@kernel.org>
Sent: Thursday, July 30, 2026 18:48
To: Long Zhao <longzhao@ambarella.com>; Arnd Bergmann <arnd@arndb.de>; Alexandre Belloni <alexandre.belloni@bootlin.com>; linux-arm-kernel@lists.infradead.org; soc@lists.linux.dev
Cc: Rob Herring <robh@kernel.org>; Krzysztof Kozlowski <krzk+dt@kernel.org>; Conor Dooley <conor+dt@kernel.org>; devicetree@vger.kernel.org; Michael Turquette <mturquette@baylibre.com>; Stephen Boyd <sboyd@kernel.org>; linux-clk@vger.kernel.org; Linus Walleij <linusw@kernel.org>; linux-gpio@vger.kernel.org; Greg Kroah-Hartman <gregkh@linuxfoundation.org>; Jiri Slaby <jirislaby@kernel.org>; linux-serial@vger.kernel.org; Catalin Marinas <catalin.marinas@arm.com>; Will Deacon <will@kernel.org>; linux-kernel@vger.kernel.org
Subject: [EXT] Re: [PATCH 00/10] Ambarella CV75 SoC minimal bring-up
On 30/07/2026 12:43, Long Zhao wrote:
>
> NOTE: This email (including attachments) contain Ambarella Proprietary and/or Confidential Information and is intended solely for the use of the individual(s) to whom it is addressed. Any unauthorized review, use, disclosure, distribute, copy, or print is prohibited. If you are not an intended recipient, please contact the sender by reply email and destroy all copies of the original message. Thank you.
Maybe I am the intended recipient of your message, maybe not. I don't want to have any legal questions regarding upstream, public collaboration, thus probably I should just remove your messages.
Please talk with your IT that such disclaimers in open-source are not desired (and maybe even harmful).
If you do not understand why, please also see:
https://urldefense.com/v3/__https://www.youtube.com/live/fMeH7wqOwXA?si=GY7igfbda6vnjXlJ&t=835__;!!PeEy7nZLVv0!mZI0csEB1zKe7UGEUdAjbQT7R3g7c3LEYitq4DVi3F_PXjl08eazP5ctMgouCBoz1x_bdQ3kJdmIWGo$
If you need to go around company SMTP server, then consider using b4
web-relay: https://urldefense.com/v3/__https://b4.docs.kernel.org/en/latest/contributor/send.html__;!!PeEy7nZLVv0!mZI0csEB1zKe7UGEUdAjbQT7R3g7c3LEYitq4DVi3F_PXjl08eazP5ctMgouCBoz1x_bdQ3kYN0VDlM$
Please be informed that by responding to this email you agree that all communications from you and/or your company is made public. In other words, all messages originating from you and/or your company will be made public.
Best regards,
Krzysztof
######################################################################
This EXTERNAL email has been scanned by Proofpoint Email Protect service.
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [EXT] Re: [PATCH 00/10] Ambarella CV75 SoC minimal bring-up
2026-07-30 11:05 ` [EXT] " Long Zhao
@ 2026-07-30 11:56 ` Alexandre Belloni
0 siblings, 0 replies; 21+ messages in thread
From: Alexandre Belloni @ 2026-07-30 11:56 UTC (permalink / raw)
To: Long Zhao
Cc: Krzysztof Kozlowski, Arnd Bergmann,
linux-arm-kernel@lists.infradead.org, soc@lists.linux.dev,
Rob Herring, Krzysztof Kozlowski, Conor Dooley,
devicetree@vger.kernel.org, Michael Turquette, Stephen Boyd,
linux-clk@vger.kernel.org, Linus Walleij,
linux-gpio@vger.kernel.org, Greg Kroah-Hartman, Jiri Slaby,
linux-serial@vger.kernel.org, Catalin Marinas, Will Deacon,
linux-kernel@vger.kernel.org
On 30/07/2026 11:05:01+0000, Long Zhao wrote:
> Hi Krzysztof,
>
> Thank you for the feedback.
>
> Regarding the footer: I did not see that disclaimer when I sent a
> test copy to myself. It looks like our mail system automatically
> appends it only on messages going outside the company. That was
> unintentional on my side, and I will check with our IT and find a way
> to send v2 without that corporate disclaimer.
>
> I will also switch to SoC-specific compatibles and re-read the writing
> bindings documentation and DTS coding guidelines before posting v2.
>
> I will wait a bit for further comments on the rest of the series and
> address them together in v2.
>
Note that tabs were converted to spaces in your submissions.
> Thanks,
> Long Zhao
>
> -----Original Message-----
> From: Krzysztof Kozlowski <krzk@kernel.org>
> Sent: Thursday, July 30, 2026 18:48
> To: Long Zhao <longzhao@ambarella.com>; Arnd Bergmann <arnd@arndb.de>; Alexandre Belloni <alexandre.belloni@bootlin.com>; linux-arm-kernel@lists.infradead.org; soc@lists.linux.dev
> Cc: Rob Herring <robh@kernel.org>; Krzysztof Kozlowski <krzk+dt@kernel.org>; Conor Dooley <conor+dt@kernel.org>; devicetree@vger.kernel.org; Michael Turquette <mturquette@baylibre.com>; Stephen Boyd <sboyd@kernel.org>; linux-clk@vger.kernel.org; Linus Walleij <linusw@kernel.org>; linux-gpio@vger.kernel.org; Greg Kroah-Hartman <gregkh@linuxfoundation.org>; Jiri Slaby <jirislaby@kernel.org>; linux-serial@vger.kernel.org; Catalin Marinas <catalin.marinas@arm.com>; Will Deacon <will@kernel.org>; linux-kernel@vger.kernel.org
> Subject: [EXT] Re: [PATCH 00/10] Ambarella CV75 SoC minimal bring-up
>
> On 30/07/2026 12:43, Long Zhao wrote:
> >
> > NOTE: This email (including attachments) contain Ambarella Proprietary and/or Confidential Information and is intended solely for the use of the individual(s) to whom it is addressed. Any unauthorized review, use, disclosure, distribute, copy, or print is prohibited. If you are not an intended recipient, please contact the sender by reply email and destroy all copies of the original message. Thank you.
>
> Maybe I am the intended recipient of your message, maybe not. I don't want to have any legal questions regarding upstream, public collaboration, thus probably I should just remove your messages.
>
> Please talk with your IT that such disclaimers in open-source are not desired (and maybe even harmful).
> If you do not understand why, please also see:
> https://urldefense.com/v3/__https://www.youtube.com/live/fMeH7wqOwXA?si=GY7igfbda6vnjXlJ&t=835__;!!PeEy7nZLVv0!mZI0csEB1zKe7UGEUdAjbQT7R3g7c3LEYitq4DVi3F_PXjl08eazP5ctMgouCBoz1x_bdQ3kJdmIWGo$
>
> If you need to go around company SMTP server, then consider using b4
> web-relay: https://urldefense.com/v3/__https://b4.docs.kernel.org/en/latest/contributor/send.html__;!!PeEy7nZLVv0!mZI0csEB1zKe7UGEUdAjbQT7R3g7c3LEYitq4DVi3F_PXjl08eazP5ctMgouCBoz1x_bdQ3kYN0VDlM$
>
> Please be informed that by responding to this email you agree that all communications from you and/or your company is made public. In other words, all messages originating from you and/or your company will be made public.
>
> Best regards,
> Krzysztof
>
> ######################################################################
> This EXTERNAL email has been scanned by Proofpoint Email Protect service.
--
Alexandre Belloni, co-owner and COO, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH 09/10] arm64: ambarella: add ARCH_AMBARELLA and CV75 EVK DT
2026-07-30 10:43 ` [PATCH 09/10] arm64: ambarella: add ARCH_AMBARELLA and CV75 EVK DT Long Zhao
@ 2026-07-30 12:18 ` Marc Zyngier
0 siblings, 0 replies; 21+ messages in thread
From: Marc Zyngier @ 2026-07-30 12:18 UTC (permalink / raw)
To: Long Zhao
Cc: Arnd Bergmann, Alexandre Belloni, linux-arm-kernel, soc,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, devicetree,
Michael Turquette, Stephen Boyd, linux-clk, Linus Walleij,
linux-gpio, Greg Kroah-Hartman, Jiri Slaby, linux-serial,
Catalin Marinas, Will Deacon, linux-kernel
On Thu, 30 Jul 2026 11:43:29 +0100,
Long Zhao <longzhao@ambarella.com> wrote:
[...]
> diff --git a/arch/arm64/boot/dts/ambarella/cv75.dtsi b/arch/arm64/boot/dts/ambarella/cv75.dtsi
> new file mode 100644
> index 000000000000..7c6a61f473ef
> --- /dev/null
> +++ b/arch/arm64/boot/dts/ambarella/cv75.dtsi
> @@ -0,0 +1,151 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Ambarella CV75 SoC
> + *
> + * Boot peripheral: UART0 console.
> + * Clocks: a single ambarella,cv75-rct provider.
> + */
> +
[...]
> + 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)>;
> + };
You are missing the EL2 virtual timer interrupt, which is provided by
Cortex-A76. This isn't optional. A recent Linux kernel will shout at
you at boot time when booted at EL2 if this interrupt isn't present.
M.
--
Without deviation from the norm, progress is not possible.
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH 03/10] dt-bindings: clock: add Ambarella CV75 RCT clock controller
2026-07-30 10:43 ` [PATCH 03/10] dt-bindings: clock: add Ambarella CV75 RCT clock controller Long Zhao
@ 2026-07-30 12:42 ` Rob Herring (Arm)
2026-07-30 14:11 ` Rob Herring
1 sibling, 0 replies; 21+ messages in thread
From: Rob Herring (Arm) @ 2026-07-30 12:42 UTC (permalink / raw)
To: Long Zhao
Cc: Jiri Slaby, linux-gpio, Krzysztof Kozlowski, Conor Dooley,
linux-kernel, Linus Walleij, Will Deacon, Alexandre Belloni,
linux-arm-kernel, Michael Turquette, Arnd Bergmann, devicetree,
soc, Greg Kroah-Hartman, linux-clk, Catalin Marinas, Stephen Boyd,
linux-serial
On Thu, 30 Jul 2026 18:43:23 +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 | 94 +++++++++++++++++++
> .../dt-bindings/clock/ambarella,cv75-clock.h | 17 ++++
> 2 files changed, 111 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/clock/ambarella,cv75-rct.yaml
> create mode 100644 include/dt-bindings/clock/ambarella,cv75-clock.h
>
My bot found errors running 'make dt_binding_check' on your patch:
yamllint warnings/errors:
dtschema/dtc warnings/errors:
Documentation/devicetree/bindings/clock/ambarella,cv75-rct.example.dtb: / (ambarella,cv75-evk): 'model' is a required property
from schema $id: http://devicetree.org/schemas/root-node.yaml
doc reference errors (make refcheckdocs):
See https://patchwork.kernel.org/project/devicetree/patch/20260730104330.81367-4-longzhao@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] 21+ messages in thread
* Re: [PATCH 04/10] dt-bindings: pinctrl: add Ambarella CV75 pinctrl
2026-07-30 10:43 ` [PATCH 04/10] dt-bindings: pinctrl: add Ambarella CV75 pinctrl Long Zhao
@ 2026-07-30 12:42 ` Rob Herring (Arm)
2026-07-30 14:21 ` Rob Herring
1 sibling, 0 replies; 21+ messages in thread
From: Rob Herring (Arm) @ 2026-07-30 12:42 UTC (permalink / raw)
To: Long Zhao
Cc: soc, Krzysztof Kozlowski, Michael Turquette, linux-clk,
linux-gpio, linux-serial, Stephen Boyd, Catalin Marinas,
linux-arm-kernel, Jiri Slaby, Arnd Bergmann, devicetree,
Alexandre Belloni, Linus Walleij, Conor Dooley, linux-kernel,
Greg Kroah-Hartman, Will Deacon
On Thu, 30 Jul 2026 18:43:24 +0800, Long Zhao wrote:
> Add the CV75 pin controller binding and pinmux ID header.
>
> Signed-off-by: Long Zhao <longzhao@ambarella.com>
> ---
> .../pinctrl/ambarella,cv75-pinctrl.yaml | 167 ++++++++++++++++++
> .../dt-bindings/pinctrl/ambarella,pinctrl.h | 11 ++
> 2 files changed, 178 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/pinctrl/ambarella,cv75-pinctrl.yaml
> create mode 100644 include/dt-bindings/pinctrl/ambarella,pinctrl.h
>
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
Documentation/devicetree/bindings/clock/ambarella,cv75-rct.example.dtb: / (ambarella,cv75-evk): 'model' is a required property
from schema $id: http://devicetree.org/schemas/root-node.yaml
doc reference errors (make refcheckdocs):
See https://patchwork.kernel.org/project/devicetree/patch/20260730104330.81367-5-longzhao@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] 21+ messages in thread
* Re: [PATCH 03/10] dt-bindings: clock: add Ambarella CV75 RCT clock controller
2026-07-30 10:43 ` [PATCH 03/10] dt-bindings: clock: add Ambarella CV75 RCT clock controller Long Zhao
2026-07-30 12:42 ` Rob Herring (Arm)
@ 2026-07-30 14:11 ` Rob Herring
1 sibling, 0 replies; 21+ messages in thread
From: Rob Herring @ 2026-07-30 14:11 UTC (permalink / raw)
To: Long Zhao
Cc: Arnd Bergmann, Alexandre Belloni, linux-arm-kernel, soc,
Krzysztof Kozlowski, Conor Dooley, devicetree, Michael Turquette,
Stephen Boyd, linux-clk, Linus Walleij, linux-gpio,
Greg Kroah-Hartman, Jiri Slaby, linux-serial, Catalin Marinas,
Will Deacon, linux-kernel
On Thu, Jul 30, 2026 at 06:43:23PM +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 | 94 +++++++++++++++++++
> .../dt-bindings/clock/ambarella,cv75-clock.h | 17 ++++
> 2 files changed, 111 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/clock/ambarella,cv75-rct.yaml
> create mode 100644 include/dt-bindings/clock/ambarella,cv75-clock.h
>
> 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..5ff5b6a4a0b9
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/clock/ambarella,cv75-rct.yaml
> @@ -0,0 +1,94 @@
> +# 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 drivers
> + (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)
> +
> +allOf:
> + - $ref: /schemas/mfd/syscon-common.yaml#
Drop this.
> +
> +properties:
> + $nodename:
> + pattern: "^clock-controller(@.*)?$"
"^clock-controller@"
unit-address is not optional.
> +
> + 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
> +
> + assigned-clocks: true
> +
> + assigned-clock-parents: true
> +
> + assigned-clock-rates: true
Drop. These are implicitly allowed for any node with 'clocks'.
> +
> +required:
> + - compatible
> + - reg
> + - clocks
> + - clock-names
> + - "#clock-cells"
> +
> +unevaluatedProperties: false
> +
> +examples:
> + - |
> + #include <dt-bindings/clock/ambarella,cv75-clock.h>
> +
> + / {
> + compatible = "ambarella,cv75-evk", "ambarella,cv75";
> + #address-cells = <2>;
> + #size-cells = <2>;
Drop all this, not relevant to this binding.
> +
> + osc: oscillator {
> + compatible = "fixed-clock";
> + #clock-cells = <0>;
> + clock-output-names = "osc";
> + clock-frequency = <24000000>;
> + };
Drop. We don't put providers in examples.
> +
> + rct: clock-controller@ed080000 {
> + 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>;
> + };
> + };
> 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
>
> NOTE: This email (including attachments) contain Ambarella Proprietary and/or Confidential Information and is intended solely for the use of the individual(s) to whom it is addressed. Any unauthorized review, use, disclosure, distribute, copy, or print is prohibited. If you are not an intended recipient, please contact the sender by reply email and destroy all copies of the original message. Thank you.
You must remove this or we can't do anything with your patches.
Rob
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH 04/10] dt-bindings: pinctrl: add Ambarella CV75 pinctrl
2026-07-30 10:43 ` [PATCH 04/10] dt-bindings: pinctrl: add Ambarella CV75 pinctrl Long Zhao
2026-07-30 12:42 ` Rob Herring (Arm)
@ 2026-07-30 14:21 ` Rob Herring
1 sibling, 0 replies; 21+ messages in thread
From: Rob Herring @ 2026-07-30 14:21 UTC (permalink / raw)
To: Long Zhao
Cc: Arnd Bergmann, Alexandre Belloni, linux-arm-kernel, soc,
Krzysztof Kozlowski, Conor Dooley, devicetree, Michael Turquette,
Stephen Boyd, linux-clk, Linus Walleij, linux-gpio,
Greg Kroah-Hartman, Jiri Slaby, linux-serial, Catalin Marinas,
Will Deacon, linux-kernel
On Thu, Jul 30, 2026 at 06:43:24PM +0800, Long Zhao wrote:
> Add the CV75 pin controller binding and pinmux ID header.
>
> Signed-off-by: Long Zhao <longzhao@ambarella.com>
> ---
> .../pinctrl/ambarella,cv75-pinctrl.yaml | 167 ++++++++++++++++++
> .../dt-bindings/pinctrl/ambarella,pinctrl.h | 11 ++
> 2 files changed, 178 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/pinctrl/ambarella,cv75-pinctrl.yaml
> create mode 100644 include/dt-bindings/pinctrl/ambarella,pinctrl.h
>
> 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..8a331c642fad
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/pinctrl/ambarella,cv75-pinctrl.yaml
> @@ -0,0 +1,167 @@
> +# 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 multiplexing entries encode
> + the pin number and alternate function with the AMBA_PINMUX() macro from
> + dt-bindings/pinctrl/ambarella,pinctrl.h.
> +
> +allOf:
> + - $ref: pinctrl.yaml#
> +
> +properties:
> + compatible:
> + const: ambarella,cv75-pinctrl
> +
> + reg:
> + items:
> + - description: GPIO bank 0 registers
> + - description: GPIO bank 1 registers
> + - description: GPIO bank 2 registers
> + - description: IOMUX registers
I'm not really convinced this is 1 h/w block. Looks more like 4 to me.
> +
> + reg-names:
> + items:
> + - const: gpio0
> + - const: gpio1
> + - const: gpio2
> + - const: iomux
> +
> + interrupts:
> + items:
> + - description: GPIO bank 0 interrupt
> + - description: GPIO bank 1 interrupt
> + - description: GPIO bank 2 interrupt
These go in the child nodes or your really should have 3 instances of
your GPIO h/w.
> +
> + 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: 0
> +
> +patternProperties:
> + "^gpio@[0-9a-f]+$":
> + type: object
> + additionalProperties: false
> +
> + properties:
> + reg:
> + maxItems: 1
If the child nodes stay, this should be the MMIO GPIO bank address, not
a made up bank number.
> +
> + gpio-controller: true
> +
> + "#gpio-cells":
> + const: 2
> +
> + gpio-ranges:
> + maxItems: 1
> +
> + interrupt-controller: true
> +
> + "#interrupt-cells":
> + const: 2
> +
> + required:
> + - reg
> + - 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: true
> +
> + pinmux:
> + $ref: /schemas/types.yaml#/definitions/uint32-array
> + minItems: 1
> + maxItems: 96
> +
> + bias-disable: true
> + bias-pull-down: true
> + bias-pull-up: true
> +
> + drive-strength:
> + enum: [3, 4, 5, 6, 7, 8, 9, 12]
> +
> + required:
> + - function
> + - pinmux
> +
> +required:
> + - compatible
> + - reg
> + - reg-names
> + - interrupts
> + - ambarella,drive-strength-syscon
> + - ambarella,pull-syscon
> + - "#address-cells"
> + - "#size-cells"
> +
> +unevaluatedProperties: false
> +
> +examples:
> + - |
> + #include <dt-bindings/interrupt-controller/arm-gic.h>
> + #include <dt-bindings/pinctrl/ambarella,pinctrl.h>
> +
> + rct: rct {
> + };
> +
> + scratchpad: scratchpad {
> + };
> +
> + pinctrl: pinctrl@e4013000 {
> + compatible = "ambarella,cv75-pinctrl";
> + #address-cells = <1>;
> + #size-cells = <0>;
> + reg = <0xe4013000 0x1000>, <0xe4014000 0x1000>,
> + <0xe4015000 0x1000>, <0xe4010000 0x1000>;
The alignment and size of these blocks is what tells me these are
separate blocks, not 1 block.
Rob
^ permalink raw reply [flat|nested] 21+ messages in thread
end of thread, other threads:[~2026-07-30 14:21 UTC | newest]
Thread overview: 21+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-30 10:43 [PATCH 00/10] Ambarella CV75 SoC minimal bring-up Long Zhao
2026-07-30 10:43 ` [PATCH 01/10] dt-bindings: arm: add Ambarella CV75 platforms Long Zhao
2026-07-30 10:43 ` [PATCH 02/10] dt-bindings: soc: add Ambarella secure scratchpad Long Zhao
2026-07-30 10:43 ` [PATCH 03/10] dt-bindings: clock: add Ambarella CV75 RCT clock controller Long Zhao
2026-07-30 12:42 ` Rob Herring (Arm)
2026-07-30 14:11 ` Rob Herring
2026-07-30 10:43 ` [PATCH 04/10] dt-bindings: pinctrl: add Ambarella CV75 pinctrl Long Zhao
2026-07-30 12:42 ` Rob Herring (Arm)
2026-07-30 14:21 ` Rob Herring
2026-07-30 10:43 ` [PATCH 05/10] dt-bindings: serial: add Ambarella UART Long Zhao
2026-07-30 10:49 ` Krzysztof Kozlowski
2026-07-30 10:43 ` [PATCH 06/10] clk: ambarella: add CV75 CCU driver Long Zhao
2026-07-30 10:43 ` [PATCH 07/10] pinctrl: ambarella: add Ambarella pin controller Long Zhao
2026-07-30 10:43 ` [PATCH 08/10] serial: ambarella: add Ambarella UART driver Long Zhao
2026-07-30 10:54 ` Greg Kroah-Hartman
2026-07-30 10:43 ` [PATCH 09/10] arm64: ambarella: add ARCH_AMBARELLA and CV75 EVK DT Long Zhao
2026-07-30 12:18 ` Marc Zyngier
2026-07-30 10:43 ` [PATCH 10/10] MAINTAINERS: add ARM/AMBARELLA SoC support Long Zhao
2026-07-30 10:48 ` [PATCH 00/10] Ambarella CV75 SoC minimal bring-up Krzysztof Kozlowski
2026-07-30 11:05 ` [EXT] " Long Zhao
2026-07-30 11:56 ` Alexandre Belloni
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox