* [PATCH v2 00/10] Ambarella CV75 SoC minimal bring-up
@ 2026-08-06 9:34 Long Zhao via B4 Relay
2026-08-06 9:34 ` [PATCH v2 01/10] dt-bindings: arm: add Ambarella CV75 platforms Long Zhao via B4 Relay
` (9 more replies)
0 siblings, 10 replies; 27+ messages in thread
From: Long Zhao via B4 Relay @ 2026-08-06 9:34 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
Stephen Boyd, Linus Walleij, Greg Kroah-Hartman, Jiri Slaby,
Catalin Marinas, Will Deacon, Arnd Bergmann, Alexandre Belloni
Cc: devicetree, linux-arm-kernel, linux-kernel, linux-clk, linux-gpio,
linux-serial, soc, Long Zhao
This series adds minimal Ambarella CV75 support sufficient for early
bring-up with a serial console.
It introduces DT bindings and drivers for the RCT clock controller,
pinctrl, and UART, plus ARCH_AMBARELLA, a minimal CV75 EVK device tree,
arm64 defconfig entries, and a MAINTAINERS entry.
The goal remains a small, reviewable console-capable baseline. Further
SoC peripherals can follow in later series.
This is v2. Link to v1:
https://lore.kernel.org/r/20260730104330.81367-1-longzhao@ambarella.com
Changes since v1:
- pinctrl: parent node only describes IOMUX; GPIO bank MMIO and IRQs
live in gpio child nodes (Rob)
- pinctrl: switch DT/bindings to function + groups; keep pinmux data in
the driver; drop include/dt-bindings/pinctrl/ambarella,pinctrl.h
(Linus)
- pinctrl: split CV75 static data into pinctrl-ambarella-cv75.c
- clock binding: drop syscon-common and redundant assigned-clock*
properties from the schema (Rob)
- serial: keep only SoC-specific compatible ambarella,cv75-uart
(Krzysztof)
- scratchpad: drop generic ambarella,sec-scratchpad compatible
(Krzysztof)
- bindings: rename ambarella,uart.yaml and ambarella,sec-scratchpad.yaml
to SoC-specific ambarella,cv75-*.yaml names
- dts: fix GIC unit-address; add EL2 virtual timer PPI (Marc)
- uart: use container_of and guarded locks; keep break_ctl under the
port lock; split earlycon putchar from the console path (Jiri)
- uart: convert console to nbcon; stop toggling IER around console
writes; drain RBR before uart_handle_break()
- misc binding/DTS cleanups from review
Testing:
- Booted to console on CV75 EVK with initramfs
- scripts/checkpatch.pl --strict
- make dt_binding_check DT_SCHEMA_FILES=ambarella
- W=1 arm64 Image build for the new drivers/DTS
Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
Long Zhao (10):
dt-bindings: arm: add Ambarella CV75 platforms
dt-bindings: soc: add Ambarella secure scratchpad
dt-bindings: clock: add Ambarella CV75 RCT clock controller
dt-bindings: pinctrl: add Ambarella CV75 pinctrl
dt-bindings: serial: add Ambarella UART
clk: ambarella: add CV75 CCU driver
pinctrl: ambarella: add Ambarella pin controller
serial: ambarella: add Ambarella UART driver
arm64: ambarella: add ARCH_AMBARELLA and CV75 EVK DT
MAINTAINERS: add ARM/AMBARELLA SoC support
.../devicetree/bindings/arm/ambarella.yaml | 23 +
.../bindings/clock/ambarella,cv75-rct.yaml | 66 +
.../bindings/pinctrl/ambarella,cv75-pinctrl.yaml | 175 +++
.../bindings/serial/ambarella,cv75-uart.yaml | 45 +
.../ambarella/ambarella,cv75-sec-scratchpad.yaml | 39 +
.../devicetree/bindings/vendor-prefixes.yaml | 2 +
MAINTAINERS | 17 +
arch/arm64/Kconfig.platforms | 9 +
arch/arm64/boot/dts/Makefile | 1 +
arch/arm64/boot/dts/ambarella/Makefile | 3 +
arch/arm64/boot/dts/ambarella/cv75-evk.dts | 30 +
arch/arm64/boot/dts/ambarella/cv75.dtsi | 166 +++
arch/arm64/configs/defconfig | 3 +
drivers/clk/Kconfig | 1 +
drivers/clk/Makefile | 1 +
drivers/clk/ambarella/Kconfig | 16 +
drivers/clk/ambarella/Makefile | 6 +
drivers/clk/ambarella/ccu-cv75.c | 296 ++++
drivers/clk/ambarella/ccu_common.c | 60 +
drivers/clk/ambarella/ccu_common.h | 25 +
drivers/clk/ambarella/ccu_mux_div.c | 223 +++
drivers/clk/ambarella/ccu_mux_div.h | 35 +
drivers/clk/ambarella/ccu_pll.c | 374 +++++
drivers/clk/ambarella/ccu_pll.h | 70 +
drivers/pinctrl/Kconfig | 15 +
drivers/pinctrl/Makefile | 2 +
drivers/pinctrl/pinctrl-ambarella-cv75.c | 68 +
drivers/pinctrl/pinctrl-ambarella.c | 1510 ++++++++++++++++++++
drivers/pinctrl/pinctrl-ambarella.h | 48 +
drivers/tty/serial/Kconfig | 21 +
drivers/tty/serial/Makefile | 1 +
drivers/tty/serial/ambarella_uart.c | 1001 +++++++++++++
include/dt-bindings/clock/ambarella,cv75-clock.h | 17 +
33 files changed, 4369 insertions(+)
---
base-commit: 34cf6dafc47441dfb6b356a095b89c3585a93714
change-id: 20260804-longzhao-upstream-cv75-v2-c2597cb996ce
Best regards,
--
Long Zhao <longzhao@ambarella.com>
^ permalink raw reply [flat|nested] 27+ messages in thread
* [PATCH v2 01/10] dt-bindings: arm: add Ambarella CV75 platforms
2026-08-06 9:34 [PATCH v2 00/10] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
@ 2026-08-06 9:34 ` Long Zhao via B4 Relay
2026-08-07 6:00 ` Krzysztof Kozlowski
2026-08-06 9:34 ` [PATCH v2 02/10] dt-bindings: soc: add Ambarella secure scratchpad Long Zhao via B4 Relay
` (8 subsequent siblings)
9 siblings, 1 reply; 27+ messages in thread
From: Long Zhao via B4 Relay @ 2026-08-06 9:34 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
Stephen Boyd, Linus Walleij, Greg Kroah-Hartman, Jiri Slaby,
Catalin Marinas, Will Deacon, Arnd Bergmann, Alexandre Belloni
Cc: devicetree, linux-arm-kernel, linux-kernel, linux-clk, linux-gpio,
linux-serial, soc, Long Zhao
From: Long Zhao <longzhao@ambarella.com>
Add the Ambarella SoC board binding and vendor prefix covering
the CV75 EVK.
Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
.../devicetree/bindings/arm/ambarella.yaml | 23 ++++++++++++++++++++++
.../devicetree/bindings/vendor-prefixes.yaml | 2 ++
2 files changed, 25 insertions(+)
diff --git a/Documentation/devicetree/bindings/arm/ambarella.yaml b/Documentation/devicetree/bindings/arm/ambarella.yaml
new file mode 100644
index 000000000000..347148530f80
--- /dev/null
+++ b/Documentation/devicetree/bindings/arm/ambarella.yaml
@@ -0,0 +1,23 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/arm/ambarella.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Ambarella SoC platforms
+
+maintainers:
+ - Long Zhao <longzhao@ambarella.com>
+
+properties:
+ $nodename:
+ const: "/"
+
+ compatible:
+ oneOf:
+ - description: Ambarella CV75 EVK board
+ items:
+ - const: ambarella,cv75-evk
+ - const: ambarella,cv75
+
+additionalProperties: true
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml
index 85a27937832e..f48aa6dabbe9 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
+++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
@@ -112,6 +112,8 @@ patternProperties:
description: Amarula Solutions
"^amazon,.*":
description: Amazon.com, Inc.
+ "^ambarella,.*":
+ description: Ambarella, Inc.
"^amcc,.*":
description: Applied Micro Circuits Corporation (APM, formally AMCC)
"^amd,.*":
--
2.34.1
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v2 02/10] dt-bindings: soc: add Ambarella secure scratchpad
2026-08-06 9:34 [PATCH v2 00/10] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
2026-08-06 9:34 ` [PATCH v2 01/10] dt-bindings: arm: add Ambarella CV75 platforms Long Zhao via B4 Relay
@ 2026-08-06 9:34 ` Long Zhao via B4 Relay
2026-08-07 6:01 ` Krzysztof Kozlowski
2026-08-06 9:34 ` [PATCH v2 03/10] dt-bindings: clock: add Ambarella CV75 RCT clock controller Long Zhao via B4 Relay
` (7 subsequent siblings)
9 siblings, 1 reply; 27+ messages in thread
From: Long Zhao via B4 Relay @ 2026-08-06 9:34 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
Stephen Boyd, Linus Walleij, Greg Kroah-Hartman, Jiri Slaby,
Catalin Marinas, Will Deacon, Arnd Bergmann, Alexandre Belloni
Cc: devicetree, linux-arm-kernel, linux-kernel, linux-clk, linux-gpio,
linux-serial, soc, Long Zhao
From: Long Zhao <longzhao@ambarella.com>
Document the Ambarella secure scratchpad syscon used by CV75.
Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
.../ambarella/ambarella,cv75-sec-scratchpad.yaml | 39 ++++++++++++++++++++++
1 file changed, 39 insertions(+)
diff --git a/Documentation/devicetree/bindings/soc/ambarella/ambarella,cv75-sec-scratchpad.yaml b/Documentation/devicetree/bindings/soc/ambarella/ambarella,cv75-sec-scratchpad.yaml
new file mode 100644
index 000000000000..4bfeca0ac3da
--- /dev/null
+++ b/Documentation/devicetree/bindings/soc/ambarella/ambarella,cv75-sec-scratchpad.yaml
@@ -0,0 +1,39 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/soc/ambarella/ambarella,cv75-sec-scratchpad.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Ambarella CV75 Secure Scratchpad Registers
+
+maintainers:
+ - Long Zhao <longzhao@ambarella.com>
+
+description:
+ The secure scratchpad block contains shared system registers used by
+ other Ambarella SoC blocks through syscon regmap lookups.
+
+allOf:
+ - $ref: /schemas/mfd/syscon-common.yaml#
+
+properties:
+ compatible:
+ items:
+ - const: ambarella,cv75-sec-scratchpad
+ - const: syscon
+
+ reg:
+ maxItems: 1
+
+required:
+ - compatible
+ - reg
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ scratchpad@e002e000 {
+ compatible = "ambarella,cv75-sec-scratchpad", "syscon";
+ reg = <0xe002e000 0x1000>;
+ };
--
2.34.1
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v2 03/10] dt-bindings: clock: add Ambarella CV75 RCT clock controller
2026-08-06 9:34 [PATCH v2 00/10] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
2026-08-06 9:34 ` [PATCH v2 01/10] dt-bindings: arm: add Ambarella CV75 platforms Long Zhao via B4 Relay
2026-08-06 9:34 ` [PATCH v2 02/10] dt-bindings: soc: add Ambarella secure scratchpad Long Zhao via B4 Relay
@ 2026-08-06 9:34 ` Long Zhao via B4 Relay
2026-08-06 9:42 ` sashiko-bot
` (2 more replies)
2026-08-06 9:34 ` [PATCH v2 04/10] dt-bindings: pinctrl: add Ambarella CV75 pinctrl Long Zhao via B4 Relay
` (6 subsequent siblings)
9 siblings, 3 replies; 27+ messages in thread
From: Long Zhao via B4 Relay @ 2026-08-06 9:34 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
Stephen Boyd, Linus Walleij, Greg Kroah-Hartman, Jiri Slaby,
Catalin Marinas, Will Deacon, Arnd Bergmann, Alexandre Belloni
Cc: devicetree, linux-arm-kernel, linux-kernel, linux-clk, linux-gpio,
linux-serial, soc, Long Zhao
From: Long Zhao <longzhao@ambarella.com>
Add the CV75 RCT clock controller binding and clock ID header.
Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
.../bindings/clock/ambarella,cv75-rct.yaml | 66 ++++++++++++++++++++++
include/dt-bindings/clock/ambarella,cv75-clock.h | 17 ++++++
2 files changed, 83 insertions(+)
diff --git a/Documentation/devicetree/bindings/clock/ambarella,cv75-rct.yaml b/Documentation/devicetree/bindings/clock/ambarella,cv75-rct.yaml
new file mode 100644
index 000000000000..aa96533bab9b
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/ambarella,cv75-rct.yaml
@@ -0,0 +1,66 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/clock/ambarella,cv75-rct.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Ambarella CV75 RCT Clock Controller
+
+maintainers:
+ - Long Zhao <longzhao@ambarella.com>
+
+description: |
+ The RCT (Reset and Clock Tree) block on the Ambarella CV75 SoC provides
+ PLLs, clock muxes and dividers for on-chip peripherals.
+
+ The same register region is also exposed as a syscon for other SoC
+ blocks (e.g. pin drive-strength), hence the "syscon" compatible.
+
+ Clock indices are defined in
+ include/dt-bindings/clock/ambarella,cv75-clock.h:
+
+ - CV75_GCLK_UART0: UART0 mux/div clock
+ - CV75_GCLK_CORE: Core PLL (gclk_core)
+ - CV75_GCLK_AHB: AHB bus clock (core / 2)
+ - CV75_GCLK_APB: APB bus clock (core / 4)
+
+properties:
+ $nodename:
+ pattern: "^clock-controller@"
+
+ compatible:
+ items:
+ - const: ambarella,cv75-rct
+ - const: syscon
+
+ reg:
+ maxItems: 1
+
+ clocks:
+ description: Reference crystal oscillator (typically 24 MHz)
+ maxItems: 1
+
+ clock-names:
+ const: osc
+
+ "#clock-cells":
+ const: 1
+
+required:
+ - compatible
+ - reg
+ - clocks
+ - clock-names
+ - "#clock-cells"
+
+additionalProperties: false
+
+examples:
+ - |
+ clock-controller@ed080000 {
+ compatible = "ambarella,cv75-rct", "syscon";
+ reg = <0xed080000 0x1000>;
+ clocks = <&osc>;
+ clock-names = "osc";
+ #clock-cells = <1>;
+ };
diff --git a/include/dt-bindings/clock/ambarella,cv75-clock.h b/include/dt-bindings/clock/ambarella,cv75-clock.h
new file mode 100644
index 000000000000..3d3314446e75
--- /dev/null
+++ b/include/dt-bindings/clock/ambarella,cv75-clock.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause */
+/*
+ * Copyright (C) 2026 Ambarella, Inc.
+ */
+
+#ifndef _DT_BINDINGS_CLOCK_AMBARELLA_CV75_CLOCK_H
+#define _DT_BINDINGS_CLOCK_AMBARELLA_CV75_CLOCK_H
+
+/* Boot / bus clocks */
+#define CV75_GCLK_UART0 0
+#define CV75_GCLK_CORE 1
+#define CV75_GCLK_AHB 2
+#define CV75_GCLK_APB 3
+
+#define CV75_CLK_NUM 4
+
+#endif
--
2.34.1
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v2 04/10] dt-bindings: pinctrl: add Ambarella CV75 pinctrl
2026-08-06 9:34 [PATCH v2 00/10] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
` (2 preceding siblings ...)
2026-08-06 9:34 ` [PATCH v2 03/10] dt-bindings: clock: add Ambarella CV75 RCT clock controller Long Zhao via B4 Relay
@ 2026-08-06 9:34 ` Long Zhao via B4 Relay
2026-08-06 9:41 ` sashiko-bot
2026-08-07 6:06 ` Krzysztof Kozlowski
2026-08-06 9:34 ` [PATCH v2 05/10] dt-bindings: serial: add Ambarella UART Long Zhao via B4 Relay
` (5 subsequent siblings)
9 siblings, 2 replies; 27+ messages in thread
From: Long Zhao via B4 Relay @ 2026-08-06 9:34 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
Stephen Boyd, Linus Walleij, Greg Kroah-Hartman, Jiri Slaby,
Catalin Marinas, Will Deacon, Arnd Bergmann, Alexandre Belloni
Cc: devicetree, linux-arm-kernel, linux-kernel, linux-clk, linux-gpio,
linux-serial, soc, Long Zhao
From: Long Zhao <longzhao@ambarella.com>
Add the CV75 pin controller binding.
Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
.../bindings/pinctrl/ambarella,cv75-pinctrl.yaml | 175 +++++++++++++++++++++
1 file changed, 175 insertions(+)
diff --git a/Documentation/devicetree/bindings/pinctrl/ambarella,cv75-pinctrl.yaml b/Documentation/devicetree/bindings/pinctrl/ambarella,cv75-pinctrl.yaml
new file mode 100644
index 000000000000..3fa30b275e51
--- /dev/null
+++ b/Documentation/devicetree/bindings/pinctrl/ambarella,cv75-pinctrl.yaml
@@ -0,0 +1,175 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/pinctrl/ambarella,cv75-pinctrl.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Ambarella CV75 Pin Controller
+
+maintainers:
+ - Long Zhao <longzhao@ambarella.com>
+
+description:
+ The Ambarella CV75 pin controller configures pin multiplexing, bias and
+ drive strength for the main GPIO banks. Pin groups and their mux settings
+ are defined by the pin controller driver and selected by function and group
+ names.
+
+allOf:
+ - $ref: pinctrl.yaml#
+
+properties:
+ compatible:
+ const: ambarella,cv75-pinctrl
+
+ reg:
+ maxItems: 1
+ description: IOMUX registers
+
+ ambarella,drive-strength-syscon:
+ $ref: /schemas/types.yaml#/definitions/phandle
+ description: RCT syscon containing the drive-strength registers.
+
+ ambarella,pull-syscon:
+ $ref: /schemas/types.yaml#/definitions/phandle
+ description: Secure scratchpad syscon containing the pull registers.
+
+ "#address-cells":
+ const: 1
+
+ "#size-cells":
+ const: 1
+
+ ranges: true
+
+patternProperties:
+ "^gpio@[0-9a-f]+$":
+ type: object
+ additionalProperties: false
+
+ properties:
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ gpio-controller: true
+
+ "#gpio-cells":
+ const: 2
+
+ gpio-ranges:
+ maxItems: 1
+
+ interrupt-controller: true
+
+ "#interrupt-cells":
+ const: 2
+
+ required:
+ - reg
+ - interrupts
+ - gpio-controller
+ - "#gpio-cells"
+ - gpio-ranges
+ - interrupt-controller
+ - "#interrupt-cells"
+
+ "^[a-z0-9-]+-pins$":
+ type: object
+ allOf:
+ - $ref: pinmux-node.yaml#
+ - $ref: pincfg-node.yaml#
+ additionalProperties: false
+
+ properties:
+ function:
+ const: uart0
+
+ groups:
+ $ref: /schemas/types.yaml#/definitions/string-array
+ items:
+ enum: [uart0tx, uart0rx]
+ minItems: 2
+ maxItems: 2
+ uniqueItems: true
+
+ bias-disable: true
+ bias-pull-down: true
+ bias-pull-up: true
+
+ drive-strength:
+ enum: [3, 4, 5, 6, 7, 8, 9, 12]
+
+ required:
+ - function
+ - groups
+
+required:
+ - compatible
+ - reg
+ - ambarella,drive-strength-syscon
+ - ambarella,pull-syscon
+ - "#address-cells"
+ - "#size-cells"
+ - ranges
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+
+ rct: rct {
+ };
+
+ scratchpad: scratchpad {
+ };
+
+ pinctrl: pinctrl@e4010000 {
+ compatible = "ambarella,cv75-pinctrl";
+ #address-cells = <1>;
+ #size-cells = <1>;
+ reg = <0xe4010000 0x1000>;
+ ranges;
+ ambarella,drive-strength-syscon = <&rct>;
+ ambarella,pull-syscon = <&scratchpad>;
+
+ gpio0: gpio@e4013000 {
+ reg = <0xe4013000 0x1000>;
+ interrupts = <GIC_SPI 163 IRQ_TYPE_LEVEL_HIGH>;
+ gpio-controller;
+ #gpio-cells = <2>;
+ gpio-ranges = <&pinctrl 0 0 32>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ };
+
+ gpio1: gpio@e4014000 {
+ reg = <0xe4014000 0x1000>;
+ interrupts = <GIC_SPI 164 IRQ_TYPE_LEVEL_HIGH>;
+ gpio-controller;
+ #gpio-cells = <2>;
+ gpio-ranges = <&pinctrl 0 32 32>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ };
+
+ gpio2: gpio@e4015000 {
+ reg = <0xe4015000 0x1000>;
+ interrupts = <GIC_SPI 165 IRQ_TYPE_LEVEL_HIGH>;
+ gpio-controller;
+ #gpio-cells = <2>;
+ gpio-ranges = <&pinctrl 0 64 32>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ };
+
+ uart0-pins {
+ function = "uart0";
+ groups = "uart0tx", "uart0rx";
+ bias-disable;
+ drive-strength = <8>;
+ };
+ };
--
2.34.1
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v2 05/10] dt-bindings: serial: add Ambarella UART
2026-08-06 9:34 [PATCH v2 00/10] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
` (3 preceding siblings ...)
2026-08-06 9:34 ` [PATCH v2 04/10] dt-bindings: pinctrl: add Ambarella CV75 pinctrl Long Zhao via B4 Relay
@ 2026-08-06 9:34 ` Long Zhao via B4 Relay
2026-08-06 9:34 ` [PATCH v2 06/10] clk: ambarella: add CV75 CCU driver Long Zhao via B4 Relay
` (4 subsequent siblings)
9 siblings, 0 replies; 27+ messages in thread
From: Long Zhao via B4 Relay @ 2026-08-06 9:34 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
Stephen Boyd, Linus Walleij, Greg Kroah-Hartman, Jiri Slaby,
Catalin Marinas, Will Deacon, Arnd Bergmann, Alexandre Belloni
Cc: devicetree, linux-arm-kernel, linux-kernel, linux-clk, linux-gpio,
linux-serial, soc, Long Zhao
From: Long Zhao <longzhao@ambarella.com>
Document the Ambarella UART controller.
Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
.../bindings/serial/ambarella,cv75-uart.yaml | 45 ++++++++++++++++++++++
1 file changed, 45 insertions(+)
diff --git a/Documentation/devicetree/bindings/serial/ambarella,cv75-uart.yaml b/Documentation/devicetree/bindings/serial/ambarella,cv75-uart.yaml
new file mode 100644
index 000000000000..86d6d8a61ace
--- /dev/null
+++ b/Documentation/devicetree/bindings/serial/ambarella,cv75-uart.yaml
@@ -0,0 +1,45 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/serial/ambarella,cv75-uart.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Ambarella CV75 UART
+
+maintainers:
+ - Long Zhao <longzhao@ambarella.com>
+
+allOf:
+ - $ref: serial.yaml#
+
+properties:
+ compatible:
+ const: ambarella,cv75-uart
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ clocks:
+ maxItems: 1
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - clocks
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+
+ serial@e4000000 {
+ compatible = "ambarella,cv75-uart";
+ reg = <0xe4000000 0x1000>;
+ interrupts = <GIC_SPI 170 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&uart_clk>;
+ };
--
2.34.1
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v2 06/10] clk: ambarella: add CV75 CCU driver
2026-08-06 9:34 [PATCH v2 00/10] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
` (4 preceding siblings ...)
2026-08-06 9:34 ` [PATCH v2 05/10] dt-bindings: serial: add Ambarella UART Long Zhao via B4 Relay
@ 2026-08-06 9:34 ` Long Zhao via B4 Relay
2026-08-06 9:45 ` sashiko-bot
2026-08-06 9:34 ` [PATCH v2 07/10] pinctrl: ambarella: add Ambarella pin controller Long Zhao via B4 Relay
` (3 subsequent siblings)
9 siblings, 1 reply; 27+ messages in thread
From: Long Zhao via B4 Relay @ 2026-08-06 9:34 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
Stephen Boyd, Linus Walleij, Greg Kroah-Hartman, Jiri Slaby,
Catalin Marinas, Will Deacon, Arnd Bergmann, Alexandre Belloni
Cc: devicetree, linux-arm-kernel, linux-kernel, linux-clk, linux-gpio,
linux-serial, soc, Long Zhao
From: Long Zhao <longzhao@ambarella.com>
Add a minimal Ambarella clock controller for CV75 covering the
UART0 and core/ahb/apb clocks required for early console bring-up.
Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
drivers/clk/Kconfig | 1 +
drivers/clk/Makefile | 1 +
drivers/clk/ambarella/Kconfig | 16 ++
drivers/clk/ambarella/Makefile | 6 +
drivers/clk/ambarella/ccu-cv75.c | 296 ++++++++++++++++++++++++++++
drivers/clk/ambarella/ccu_common.c | 60 ++++++
drivers/clk/ambarella/ccu_common.h | 25 +++
drivers/clk/ambarella/ccu_mux_div.c | 223 +++++++++++++++++++++
drivers/clk/ambarella/ccu_mux_div.h | 35 ++++
drivers/clk/ambarella/ccu_pll.c | 374 ++++++++++++++++++++++++++++++++++++
drivers/clk/ambarella/ccu_pll.h | 70 +++++++
11 files changed, 1107 insertions(+)
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index 1717ce75a907..fbbf4963716c 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -506,6 +506,7 @@ config COMMON_CLK_RPMI
the RISC-V platform management interface (RPMI) specification.
source "drivers/clk/actions/Kconfig"
+source "drivers/clk/ambarella/Kconfig"
source "drivers/clk/analogbits/Kconfig"
source "drivers/clk/aspeed/Kconfig"
source "drivers/clk/bcm/Kconfig"
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index cc108a75a900..30d823ee606f 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -112,6 +112,7 @@ obj-$(CONFIG_COMMON_CLK_XGENE) += clk-xgene.o
# please keep this section sorted lexicographically by directory path name
obj-y += actions/
+obj-y += ambarella/
obj-y += analogbits/
obj-y += aspeed/
obj-$(CONFIG_COMMON_CLK_AT91) += at91/
diff --git a/drivers/clk/ambarella/Kconfig b/drivers/clk/ambarella/Kconfig
new file mode 100644
index 000000000000..639993033f96
--- /dev/null
+++ b/drivers/clk/ambarella/Kconfig
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+config CLK_AMBARELLA_CV75
+ bool "Ambarella CV75 RCT clock controller"
+ depends on ARCH_AMBARELLA || COMPILE_TEST
+ select CLK_AMBARELLA_CCU
+ default ARCH_AMBARELLA
+ help
+ Say Y to enable support for the Reset and Clock Tree controller
+ found on Ambarella CV75 SoCs. The controller provides the core PLL,
+ bus clocks and UART clock required during early platform boot.
+ It is needed to use the serial console on CV75-based systems.
+
+config CLK_AMBARELLA_CCU
+ bool
+ select REGMAP_MMIO
diff --git a/drivers/clk/ambarella/Makefile b/drivers/clk/ambarella/Makefile
new file mode 100644
index 000000000000..36e96326ea97
--- /dev/null
+++ b/drivers/clk/ambarella/Makefile
@@ -0,0 +1,6 @@
+#
+# Makefile for ambarella specific clk
+#
+
+obj-$(CONFIG_CLK_AMBARELLA_CCU) += ccu_common.o ccu_mux_div.o ccu_pll.o
+obj-$(CONFIG_CLK_AMBARELLA_CV75) += ccu-cv75.o
diff --git a/drivers/clk/ambarella/ccu-cv75.c b/drivers/clk/ambarella/ccu-cv75.c
new file mode 100644
index 000000000000..55132dfa7431
--- /dev/null
+++ b/drivers/clk/ambarella/ccu-cv75.c
@@ -0,0 +1,296 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2026 Ambarella, Inc.
+ *
+ * CV75 RCT clock controller for boot clocks.
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <dt-bindings/clock/ambarella,cv75-clock.h>
+
+#include "ccu_common.h"
+#include "ccu_mux_div.h"
+#include "ccu_pll.h"
+
+enum amb_cv75_clk_type {
+ AMB_CV75_CLK_FIXED_RATE,
+ AMB_CV75_CLK_FIXED_FACTOR,
+ AMB_CV75_CLK_PLL,
+ AMB_CV75_CLK_DIV,
+ AMB_CV75_CLK_MUX_DIV,
+};
+
+enum amb_cv75_clk_ref {
+ AMB_CV75_CLK_REF_OSC = -1,
+ AMB_CV75_CLK_REF_DUMMY = -2,
+};
+
+/* PLL version for CV75 (ambarella,clkpll-v1 in vendor DTS) */
+static const struct amb_pll_soc_data cv75_pll_soc_data = {
+ .pll_version = 1,
+ .fsout_mask = CTRL2_FSOUT_DIV2,
+ .fsout_val = CTRL2_FSOUT_DIV2,
+ .fsdiv_mask = CTRL2_FSDIV_DIV2,
+ .fsdiv_val = CTRL2_FSDIV_DIV2,
+ .vcodiv_mask = CTRL2_VCODIV_DIV2,
+ .vcodiv_val = CTRL2_VCODIV_DIV2,
+ .vco_max_mhz = 2600UL,
+ .vco_min_mhz = 850UL,
+ .vco_range = { 1800UL, 1400UL, 1100UL, 0UL },
+};
+
+struct amb_cv75_clk_desc {
+ int id;
+ enum amb_cv75_clk_type type;
+ const char *name;
+ int parent;
+
+ union {
+ struct {
+ unsigned long rate;
+ } fixed_rate;
+ struct {
+ unsigned long flags;
+ unsigned int mult;
+ unsigned int div;
+ } fixed_factor;
+ struct {
+ u32 reg_offset[REG_NUM];
+ const struct amb_pll_soc_data *soc_data;
+ } pll;
+ struct {
+ u32 reg;
+ u32 shift;
+ u32 width;
+ u32 flags;
+ u32 fix_divider;
+ } div;
+ struct amb_mux_div_desc mux_div;
+ };
+};
+
+static const struct amb_cv75_clk_desc cv75_clks[] = {
+ {
+ .id = AMB_CV75_CLK_REF_DUMMY,
+ .type = AMB_CV75_CLK_FIXED_RATE,
+ .name = "dummy",
+ .fixed_rate.rate = 0,
+ },
+ {
+ .id = CV75_GCLK_CORE,
+ .type = AMB_CV75_CLK_PLL,
+ .name = "core",
+ .parent = AMB_CV75_CLK_REF_OSC,
+ .pll = {
+ .reg_offset = {
+ 0x000, 0x004, 0x100,
+ 0x104, 0x000, 0x000,
+ },
+ .soc_data = &cv75_pll_soc_data,
+ },
+ },
+ {
+ .id = CV75_GCLK_AHB,
+ .type = AMB_CV75_CLK_FIXED_FACTOR,
+ .name = "ahb",
+ .parent = CV75_GCLK_CORE,
+ .fixed_factor = {
+ .flags = 0,
+ .mult = 1,
+ .div = 2,
+ },
+ },
+ {
+ .id = CV75_GCLK_APB,
+ .type = AMB_CV75_CLK_FIXED_FACTOR,
+ .name = "apb",
+ .parent = CV75_GCLK_CORE,
+ .fixed_factor = {
+ .flags = 0,
+ .mult = 1,
+ .div = 4,
+ },
+ },
+ {
+ .id = CV75_GCLK_UART0,
+ .type = AMB_CV75_CLK_MUX_DIV,
+ .name = "uart0",
+ .mux_div = {
+ .name = "uart0",
+ .parents = (const int[]) {
+ AMB_CV75_CLK_REF_OSC, CV75_GCLK_CORE,
+ AMB_CV75_CLK_REF_DUMMY, AMB_CV75_CLK_REF_DUMMY,
+ },
+ .num_parents = 4,
+ .mux_reg = 0x1c8,
+ .mux_shift = 0,
+ .mux_mask = 0x3,
+ .div_reg = 0x038,
+ .div_shift = 0,
+ .div_width = 24,
+ .div_flags = CLK_DIVIDER_ONE_BASED,
+ .fix_divider = 1,
+ },
+ },
+};
+
+static struct clk_hw *amb_cv75_get_parent(struct amb_ccu *ccu,
+ struct clk_hw *osc,
+ struct clk_hw *dummy,
+ int parent)
+{
+ if (parent == AMB_CV75_CLK_REF_OSC)
+ return osc;
+ if (parent == AMB_CV75_CLK_REF_DUMMY)
+ return dummy;
+
+ if (parent < 0 || parent >= ccu->data->num)
+ return ERR_PTR(-EINVAL);
+
+ if (!ccu->data->hws[parent])
+ return ERR_PTR(-EPROBE_DEFER);
+
+ return ccu->data->hws[parent];
+}
+
+static struct clk_hw *amb_cv75_register_mux_div(struct device *dev,
+ struct amb_ccu *ccu,
+ const struct amb_cv75_clk_desc *desc,
+ struct clk_hw *osc,
+ struct clk_hw *dummy)
+{
+ const struct amb_mux_div_desc *md = &desc->mux_div;
+ struct clk_parent_data *pdata;
+ struct clk_hw *parent;
+ u8 i;
+
+ pdata = devm_kcalloc(dev, md->num_parents, sizeof(*pdata), GFP_KERNEL);
+ if (!pdata)
+ return ERR_PTR(-ENOMEM);
+
+ for (i = 0; i < md->num_parents; i++) {
+ if (md->parents[i] == AMB_CV75_CLK_REF_OSC) {
+ pdata[i].fw_name = "osc";
+ continue;
+ }
+
+ parent = amb_cv75_get_parent(ccu, osc, dummy, md->parents[i]);
+ if (IS_ERR(parent))
+ return parent;
+
+ pdata[i].hw = parent;
+ }
+
+ return amb_mux_div_register(dev, ccu->map, md, pdata);
+}
+
+static struct clk_hw *amb_cv75_register_clk(struct device *dev,
+ struct amb_ccu *ccu,
+ const struct amb_cv75_clk_desc *desc,
+ struct clk_hw *osc,
+ struct clk_hw *dummy)
+{
+ struct amb_pll_desc pll_desc;
+ struct clk_hw *parent;
+
+ switch (desc->type) {
+ case AMB_CV75_CLK_FIXED_RATE:
+ return devm_clk_hw_register_fixed_rate(dev, desc->name, NULL, 0,
+ desc->fixed_rate.rate);
+ case AMB_CV75_CLK_FIXED_FACTOR:
+ parent = amb_cv75_get_parent(ccu, osc, dummy, desc->parent);
+ if (IS_ERR(parent))
+ return parent;
+
+ return devm_clk_hw_register_fixed_factor_parent_hw(dev,
+ desc->name, parent,
+ desc->fixed_factor.flags,
+ desc->fixed_factor.mult,
+ desc->fixed_factor.div);
+ case AMB_CV75_CLK_PLL:
+ parent = amb_cv75_get_parent(ccu, osc, dummy, desc->parent);
+ if (IS_ERR(parent))
+ return parent;
+
+ pll_desc.name = desc->name;
+ pll_desc.parent = parent;
+ memcpy((void *)pll_desc.reg_offset, desc->pll.reg_offset,
+ sizeof(pll_desc.reg_offset));
+ pll_desc.soc_data = desc->pll.soc_data;
+ pll_desc.frac_mode = false;
+
+ return amb_pll_register(dev, ccu->map, &pll_desc);
+ case AMB_CV75_CLK_DIV:
+ parent = amb_cv75_get_parent(ccu, osc, dummy, desc->parent);
+ if (IS_ERR(parent))
+ return parent;
+
+ return amb_div_register(dev, ccu->map, desc->name, parent,
+ desc->div.reg, desc->div.shift,
+ desc->div.width, desc->div.flags,
+ desc->div.fix_divider);
+ case AMB_CV75_CLK_MUX_DIV:
+ return amb_cv75_register_mux_div(dev, ccu, desc, osc, dummy);
+ default:
+ return ERR_PTR(-EINVAL);
+ }
+}
+
+static int amb_cv75_rct_probe(struct platform_device *pdev)
+{
+ struct amb_ccu *ccu;
+ struct clk *osc_clk;
+ struct clk_hw *osc, *dummy = NULL, *hw;
+ int i;
+
+ ccu = amb_ccu_init(pdev, CV75_CLK_NUM);
+ if (IS_ERR(ccu))
+ return PTR_ERR(ccu);
+
+ osc_clk = devm_clk_get(&pdev->dev, "osc");
+ if (IS_ERR(osc_clk))
+ return dev_err_probe(&pdev->dev, PTR_ERR(osc_clk),
+ "missing osc clock\n");
+ osc = __clk_get_hw(osc_clk);
+
+ for (i = 0; i < ARRAY_SIZE(cv75_clks); i++) {
+ hw = amb_cv75_register_clk(&pdev->dev, ccu, &cv75_clks[i],
+ osc, dummy);
+ if (IS_ERR(hw))
+ return dev_err_probe(&pdev->dev, PTR_ERR(hw),
+ "failed to register %s\n",
+ cv75_clks[i].name);
+
+ if (cv75_clks[i].id == AMB_CV75_CLK_REF_DUMMY)
+ dummy = hw;
+ else
+ ccu->data->hws[cv75_clks[i].id] = hw;
+ }
+
+ return amb_ccu_register(ccu);
+}
+
+static const struct of_device_id amb_cv75_rct_match[] = {
+ { .compatible = "ambarella,cv75-rct" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, amb_cv75_rct_match);
+
+static struct platform_driver amb_cv75_rct_driver = {
+ .probe = amb_cv75_rct_probe,
+ .driver = {
+ .name = "ambarella-cv75-rct",
+ .of_match_table = amb_cv75_rct_match,
+ },
+};
+module_platform_driver(amb_cv75_rct_driver);
+
+MODULE_AUTHOR("Ambarella Inc.");
+MODULE_DESCRIPTION("Ambarella CV75 RCT clock controller");
+MODULE_LICENSE("GPL");
diff --git a/drivers/clk/ambarella/ccu_common.c b/drivers/clk/ambarella/ccu_common.c
new file mode 100644
index 000000000000..78587c5f2ce1
--- /dev/null
+++ b/drivers/clk/ambarella/ccu_common.c
@@ -0,0 +1,60 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2026 Ambarella, Inc.
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#include "ccu_common.h"
+
+static const struct regmap_config amb_rct_regmap_config = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .max_register = AMB_RCT_REG_SIZE - 4,
+};
+
+struct amb_ccu *amb_ccu_init(struct platform_device *pdev,
+ unsigned int num_clks)
+{
+ struct amb_ccu *ccu;
+ void __iomem *base;
+
+ if (!num_clks)
+ return ERR_PTR(-EINVAL);
+
+ ccu = devm_kzalloc(&pdev->dev, sizeof(*ccu), GFP_KERNEL);
+ if (!ccu)
+ return ERR_PTR(-ENOMEM);
+
+ ccu->dev = &pdev->dev;
+
+ ccu->data = devm_kzalloc(&pdev->dev,
+ struct_size(ccu->data, hws, num_clks),
+ GFP_KERNEL);
+ if (!ccu->data)
+ return ERR_PTR(-ENOMEM);
+
+ ccu->data->num = num_clks;
+
+ base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(base))
+ return ERR_CAST(base);
+
+ ccu->map = devm_regmap_init_mmio(&pdev->dev, base,
+ &amb_rct_regmap_config);
+ if (IS_ERR(ccu->map))
+ return ERR_CAST(ccu->map);
+
+ return ccu;
+}
+
+int amb_ccu_register(struct amb_ccu *ccu)
+{
+ return devm_of_clk_add_hw_provider(ccu->dev, of_clk_hw_onecell_get,
+ ccu->data);
+}
diff --git a/drivers/clk/ambarella/ccu_common.h b/drivers/clk/ambarella/ccu_common.h
new file mode 100644
index 000000000000..f2bf9d1f06d7
--- /dev/null
+++ b/drivers/clk/ambarella/ccu_common.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2026 Ambarella, Inc.
+ */
+
+#ifndef __CCU_COMMON_H
+#define __CCU_COMMON_H
+
+#include <linux/clk-provider.h>
+#include <linux/device.h>
+#include <linux/regmap.h>
+
+#define AMB_RCT_REG_SIZE 0x1000
+
+struct amb_ccu {
+ struct device *dev;
+ struct regmap *map;
+ struct clk_hw_onecell_data *data;
+};
+
+struct amb_ccu *amb_ccu_init(struct platform_device *pdev,
+ unsigned int num_clks);
+int amb_ccu_register(struct amb_ccu *ccu);
+
+#endif
diff --git a/drivers/clk/ambarella/ccu_mux_div.c b/drivers/clk/ambarella/ccu_mux_div.c
new file mode 100644
index 000000000000..33430506f8ed
--- /dev/null
+++ b/drivers/clk/ambarella/ccu_mux_div.c
@@ -0,0 +1,223 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2026 Ambarella, Inc.
+ *
+ * Regmap-backed mux + divider, derived from the vendor composite-clock driver.
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/device.h>
+#include <linux/regmap.h>
+
+#include "ccu_mux_div.h"
+
+struct amb_mux {
+ struct clk_hw hw;
+ struct regmap *map;
+ u32 offset;
+ u32 mask;
+ u32 shift;
+};
+
+struct amb_div {
+ struct clk_hw hw;
+ struct regmap *map;
+ u32 offset;
+ u32 shift;
+ u32 width;
+ u32 flags;
+ u32 fix_divider;
+};
+
+#define to_amb_mux(_hw) container_of(_hw, struct amb_mux, hw)
+#define to_amb_div(_hw) container_of(_hw, struct amb_div, hw)
+
+static u8 amb_mux_get_parent(struct clk_hw *hw)
+{
+ struct amb_mux *mux = to_amb_mux(hw);
+ u32 val;
+
+ regmap_read(mux->map, mux->offset, &val);
+ val >>= mux->shift;
+ val &= mux->mask;
+
+ return val;
+}
+
+static int amb_mux_set_parent(struct clk_hw *hw, u8 index)
+{
+ struct amb_mux *mux = to_amb_mux(hw);
+
+ return regmap_update_bits(mux->map, mux->offset,
+ mux->mask << mux->shift,
+ index << mux->shift);
+}
+
+static const struct clk_ops amb_mux_ops = {
+ .get_parent = amb_mux_get_parent,
+ .set_parent = amb_mux_set_parent,
+};
+
+static unsigned long amb_div_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct amb_div *div = to_amb_div(hw);
+ unsigned long rate;
+ u32 val;
+
+ regmap_read(div->map, div->offset, &val);
+
+ if (val & BIT(div->width))
+ return 0;
+
+ val >>= div->shift;
+ val &= clk_div_mask(div->width);
+
+ rate = divider_recalc_rate(hw, parent_rate, val, NULL,
+ div->flags, div->width);
+ if (div->fix_divider)
+ do_div(rate, div->fix_divider);
+
+ return rate;
+}
+
+static int amb_div_determine_rate(struct clk_hw *hw,
+ struct clk_rate_request *req)
+{
+ struct amb_div *div = to_amb_div(hw);
+ struct clk_rate_request scaled = *req;
+ int ret;
+
+ if (!req->rate && (div->flags & CLK_DIVIDER_ONE_BASED))
+ return 0;
+
+ if (div->fix_divider) {
+ scaled.rate = min(req->rate,
+ ULONG_MAX / div->fix_divider) *
+ div->fix_divider;
+ scaled.min_rate = min(req->min_rate,
+ ULONG_MAX / div->fix_divider) *
+ div->fix_divider;
+ scaled.max_rate = min(req->max_rate,
+ ULONG_MAX / div->fix_divider) *
+ div->fix_divider;
+ }
+
+ ret = divider_determine_rate(hw, &scaled, NULL, div->width,
+ div->flags);
+ if (ret)
+ return ret;
+
+ req->rate = scaled.rate;
+ req->best_parent_rate = scaled.best_parent_rate;
+ req->best_parent_hw = scaled.best_parent_hw;
+ if (div->fix_divider)
+ do_div(req->rate, div->fix_divider);
+
+ return 0;
+}
+
+static int amb_div_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct amb_div *div = to_amb_div(hw);
+ int val, mask;
+
+ if (div->fix_divider)
+ rate *= div->fix_divider;
+
+ if (!rate) {
+ val = BIT(div->width);
+ } else {
+ val = divider_get_val(rate, parent_rate, NULL,
+ div->width, div->flags);
+ if (val < 0)
+ return val;
+ }
+
+ mask = (div->flags & CLK_DIVIDER_ONE_BASED) ?
+ clk_div_mask(div->width + 1) : clk_div_mask(div->width);
+
+ regmap_update_bits(div->map, div->offset, mask << div->shift,
+ val << div->shift);
+
+ if (!(div->flags & CLK_DIVIDER_ONE_BASED)) {
+ regmap_update_bits(div->map, div->offset, BIT(0), BIT(0));
+ regmap_update_bits(div->map, div->offset, BIT(0), 0);
+ }
+
+ return 0;
+}
+
+static const struct clk_ops amb_div_ops = {
+ .recalc_rate = amb_div_recalc_rate,
+ .determine_rate = amb_div_determine_rate,
+ .set_rate = amb_div_set_rate,
+};
+
+struct clk_hw *amb_div_register(struct device *dev, struct regmap *map,
+ const char *name, const struct clk_hw *parent,
+ u32 div_reg, u32 div_shift, u32 div_width,
+ u32 div_flags, u32 fix_divider)
+{
+ struct amb_div *div;
+ struct clk_init_data init = {};
+ int ret;
+
+ div = devm_kzalloc(dev, sizeof(*div), GFP_KERNEL);
+ if (!div)
+ return ERR_PTR(-ENOMEM);
+
+ div->map = map;
+ div->offset = div_reg;
+ div->shift = div_shift;
+ div->width = div_width;
+ div->flags = div_flags;
+ div->fix_divider = fix_divider;
+
+ init.name = name;
+ init.ops = &amb_div_ops;
+ init.parent_hws = &parent;
+ init.num_parents = 1;
+
+ div->hw.init = &init;
+
+ ret = devm_clk_hw_register(dev, &div->hw);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return &div->hw;
+}
+
+struct clk_hw *amb_mux_div_register(struct device *dev, struct regmap *map,
+ const struct amb_mux_div_desc *desc,
+ const struct clk_parent_data *parent_data)
+{
+ struct amb_mux *mux;
+ struct amb_div *div;
+
+ mux = devm_kzalloc(dev, sizeof(*mux), GFP_KERNEL);
+ div = devm_kzalloc(dev, sizeof(*div), GFP_KERNEL);
+ if (!mux || !div)
+ return ERR_PTR(-ENOMEM);
+
+ mux->map = map;
+ mux->offset = desc->mux_reg;
+ mux->shift = desc->mux_shift;
+ mux->mask = desc->mux_mask;
+
+ div->map = map;
+ div->offset = desc->div_reg;
+ div->shift = desc->div_shift;
+ div->width = desc->div_width;
+ div->flags = desc->div_flags;
+ div->fix_divider = desc->fix_divider;
+
+ return devm_clk_hw_register_composite_pdata(dev, desc->name,
+ parent_data,
+ desc->num_parents,
+ &mux->hw, &amb_mux_ops,
+ &div->hw, &amb_div_ops,
+ NULL, NULL,
+ CLK_SET_RATE_NO_REPARENT);
+}
diff --git a/drivers/clk/ambarella/ccu_mux_div.h b/drivers/clk/ambarella/ccu_mux_div.h
new file mode 100644
index 000000000000..abaefff1201a
--- /dev/null
+++ b/drivers/clk/ambarella/ccu_mux_div.h
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2026 Ambarella, Inc.
+ */
+
+#ifndef __CCU_MUX_DIV_H
+#define __CCU_MUX_DIV_H
+
+#include <linux/clk-provider.h>
+#include <linux/regmap.h>
+
+struct amb_mux_div_desc {
+ const char *name;
+ const int *parents;
+ u8 num_parents;
+ u32 mux_reg;
+ u32 mux_shift;
+ u32 mux_mask;
+ u32 div_reg;
+ u32 div_shift;
+ u32 div_width;
+ u32 div_flags;
+ u32 fix_divider;
+};
+
+struct clk_hw *amb_mux_div_register(struct device *dev, struct regmap *map,
+ const struct amb_mux_div_desc *desc,
+ const struct clk_parent_data *parent_data);
+
+struct clk_hw *amb_div_register(struct device *dev, struct regmap *map,
+ const char *name, const struct clk_hw *parent,
+ u32 div_reg, u32 div_shift, u32 div_width,
+ u32 div_flags, u32 fix_divider);
+
+#endif
diff --git a/drivers/clk/ambarella/ccu_pll.c b/drivers/clk/ambarella/ccu_pll.c
new file mode 100644
index 000000000000..1293836e222e
--- /dev/null
+++ b/drivers/clk/ambarella/ccu_pll.c
@@ -0,0 +1,374 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2026 Ambarella, Inc.
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/math64.h>
+#include <linux/rational.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+#include "ccu_pll.h"
+
+#define AMB_PLL_MAX_SOUT 16UL
+#define AMB_PLL_MAX_SDIV 16UL
+
+struct amb_pll {
+ struct clk_hw hw;
+ struct regmap *map;
+ u32 reg_offset[REG_NUM];
+ const struct amb_pll_soc_data *soc_data;
+ u32 fix_divider;
+ bool frac_mode;
+};
+
+#define to_amb_pll(_hw) container_of(_hw, struct amb_pll, hw)
+
+static void amb_pll_write_enable(struct regmap *map, u32 offset, u32 val)
+{
+ regmap_write(map, offset, val);
+ regmap_write(map, offset, val | CTRL_WRITE_ENABLE);
+ regmap_write(map, offset, val);
+}
+
+static unsigned long amb_pll_calc_vco(struct amb_pll *pll,
+ unsigned long parent_rate)
+{
+ const struct amb_pll_soc_data *soc_data = pll->soc_data;
+ u32 *reg = pll->reg_offset;
+ u32 pre_scaler = 1;
+ u32 ctrl_val, ctrl2_val = 0, frac_val;
+ u32 intp, sdiv, vcodiv, fsdiv;
+ u64 frac = 0, vco;
+
+ if (reg[PRES_OFFSET]) {
+ regmap_read(pll->map, reg[PRES_OFFSET], &pre_scaler);
+ pre_scaler = (pre_scaler >> 4) + 1;
+ }
+
+ regmap_read(pll->map, reg[CTRL_OFFSET], &ctrl_val);
+ intp = ((ctrl_val >> 24) & 0x7f) + 1;
+ sdiv = ((ctrl_val >> 12) & 0xf) + 1;
+
+ if (soc_data->pll_version >= 2) {
+ vcodiv = (ctrl_val & soc_data->vcodiv_mask) ==
+ soc_data->vcodiv_val ? 2 : 1;
+ fsdiv = (ctrl_val & soc_data->fsdiv_mask) ==
+ soc_data->fsdiv_val ? 2 : 1;
+ } else {
+ regmap_read(pll->map, reg[CTRL2_OFFSET], &ctrl2_val);
+ vcodiv = (ctrl2_val & soc_data->vcodiv_mask) ==
+ soc_data->vcodiv_val ? 2 : 1;
+ fsdiv = (ctrl2_val & soc_data->fsdiv_mask) ==
+ soc_data->fsdiv_val ? 2 : 1;
+ }
+
+ vco = (u64)parent_rate * vcodiv * fsdiv * intp * sdiv;
+ vco = div_u64(vco, pre_scaler);
+
+ if (ctrl_val & CTRL_FRAC_MODE) {
+ regmap_read(pll->map, reg[FRAC_OFFSET], &frac_val);
+ frac = (u64)parent_rate * vcodiv * fsdiv * sdiv * frac_val;
+ frac = div_u64(frac, pre_scaler) >> 32;
+ }
+
+ return vco + frac;
+}
+
+static unsigned long amb_pll_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct amb_pll *pll = to_amb_pll(hw);
+ const struct amb_pll_soc_data *soc_data = pll->soc_data;
+ u32 *reg = pll->reg_offset;
+ u32 pre_scaler = 1, post_scaler = 1;
+ u32 ctrl_val, ctrl2_val = 0;
+ u32 vcodiv, fsout, sout;
+ u64 rate;
+
+ regmap_read(pll->map, reg[CTRL_OFFSET], &ctrl_val);
+ if (ctrl_val & (CTRL_POWER_DOWN | CTRL_HALT_VCO | CTRL_FORCE_RESET))
+ return 0;
+
+ if (reg[PRES_OFFSET]) {
+ regmap_read(pll->map, reg[PRES_OFFSET], &pre_scaler);
+ pre_scaler = (pre_scaler >> 4) + 1;
+ }
+
+ if (reg[POST_OFFSET]) {
+ regmap_read(pll->map, reg[POST_OFFSET], &post_scaler);
+ post_scaler = (post_scaler >> 4) + 1;
+ }
+
+ if (ctrl_val & CTRL_BYPASS)
+ return parent_rate / pre_scaler / post_scaler;
+
+ if (soc_data->pll_version >= 2) {
+ vcodiv = (ctrl_val & soc_data->vcodiv_mask) ==
+ soc_data->vcodiv_val ? 2 : 1;
+ fsout = (ctrl_val & soc_data->fsout_mask) ==
+ soc_data->fsout_val ? 2 : 1;
+ } else {
+ regmap_read(pll->map, reg[CTRL2_OFFSET], &ctrl2_val);
+ vcodiv = (ctrl2_val & soc_data->vcodiv_mask) ==
+ soc_data->vcodiv_val ? 2 : 1;
+ fsout = (ctrl2_val & soc_data->fsout_mask) ==
+ soc_data->fsout_val ? 2 : 1;
+ }
+
+ sout = ((ctrl_val >> 16) & 0xf) + 1;
+ rate = amb_pll_calc_vco(pll, parent_rate);
+
+ if (soc_data->pll_version >= 2) {
+ if (!(ctrl_val & CTRL_BYPASS_HSDIV))
+ rate = div_u64(rate, vcodiv * fsout * sout);
+ } else {
+ if (!(ctrl2_val & CTRL2_BYPASS_HSDIV))
+ rate = div_u64(rate, vcodiv * fsout * sout);
+ }
+
+ rate = div_u64(rate, pll->fix_divider * post_scaler);
+
+ return rate;
+}
+
+static int amb_pll_determine_rate(struct clk_hw *hw,
+ struct clk_rate_request *req)
+{
+ struct amb_pll *pll = to_amb_pll(hw);
+ unsigned long half_refclk = req->best_parent_rate / 2;
+
+ if (pll->frac_mode)
+ return 0;
+
+ if (!half_refclk)
+ return 0;
+
+ req->rate = roundup(req->rate, half_refclk);
+
+ return 0;
+}
+
+static int amb_pll_calc_params(struct amb_pll *pll, unsigned long rate,
+ unsigned long parent_rate, u32 ctrl2_val,
+ u32 *intp, u32 *sdiv, u32 *sout,
+ u32 *vcodiv, u32 *fsdiv, u32 *fsout)
+{
+ const struct amb_pll_soc_data *soc_data = pll->soc_data;
+ unsigned long max_numerator, max_denominator;
+ unsigned long intp_ul, sout_ul, rate_tmp;
+ u32 ctrl_val;
+
+ *sdiv = 1;
+
+ if (soc_data->pll_version >= 2) {
+ ctrl_val = 0;
+ *vcodiv = (ctrl_val & soc_data->vcodiv_mask) ==
+ soc_data->vcodiv_val ? 2 : 1;
+ *fsdiv = (ctrl_val & soc_data->fsdiv_mask) ==
+ soc_data->fsdiv_val ? 2 : 1;
+ *fsout = (ctrl_val & soc_data->fsout_mask) ==
+ soc_data->fsout_val ? 2 : 1;
+ } else {
+ *vcodiv = (ctrl2_val & soc_data->vcodiv_mask) ==
+ soc_data->vcodiv_val ? 2 : 1;
+ *fsdiv = (ctrl2_val & soc_data->fsdiv_mask) ==
+ soc_data->fsdiv_val ? 2 : 1;
+ *fsout = (ctrl2_val & soc_data->fsout_mask) ==
+ soc_data->fsout_val ? 2 : 1;
+ }
+
+ if (rate < parent_rate)
+ return -EINVAL;
+
+ max_numerator = soc_data->vco_max_mhz;
+ max_numerator = div_u64(max_numerator * 1000000ULL, parent_rate);
+ max_numerator = div_u64(max_numerator, *vcodiv * *fsdiv);
+ max_numerator = min(128UL, max_numerator);
+ if (!max_numerator)
+ return -EINVAL;
+
+ max_denominator = AMB_PLL_MAX_SOUT;
+ rate_tmp = rate;
+ rational_best_approximation(rate_tmp, parent_rate, max_numerator,
+ max_denominator, &intp_ul, &sout_ul);
+
+ while (parent_rate * *fsdiv * intp_ul * *sdiv / *fsout / sout_ul >
+ rate) {
+ unsigned long resolution = parent_rate / AMB_PLL_MAX_SOUT;
+
+ if (rate_tmp <= resolution)
+ return -EINVAL;
+
+ rate_tmp -= resolution;
+ rational_best_approximation(rate_tmp, parent_rate,
+ max_numerator, max_denominator,
+ &intp_ul, &sout_ul);
+ }
+
+ while (parent_rate / 1000000 * *vcodiv * *fsdiv * intp_ul * *sdiv <
+ soc_data->vco_min_mhz) {
+ if (sout_ul > 8 || intp_ul > 64)
+ break;
+
+ intp_ul *= 2;
+ sout_ul *= 2;
+ }
+
+ if (intp_ul > max_numerator || sout_ul > max_denominator ||
+ *sdiv > AMB_PLL_MAX_SDIV)
+ return -EINVAL;
+
+ *intp = intp_ul;
+ *sout = sout_ul;
+
+ return 0;
+}
+
+static int amb_pll_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct amb_pll *pll = to_amb_pll(hw);
+ const struct amb_pll_soc_data *soc_data = pll->soc_data;
+ u32 *reg = pll->reg_offset;
+ u32 ctrl_val, ctrl2_val = 0, ctrl3_val, frac_val = 0;
+ u32 intp, sdiv, sout, vcodiv, fsdiv, fsout;
+ unsigned long old_rate, new_rate, rate_tmp;
+ int ret;
+
+ if (!rate) {
+ regmap_read(pll->map, reg[CTRL_OFFSET], &ctrl_val);
+ ctrl_val |= CTRL_POWER_DOWN | CTRL_HALT_VCO;
+ amb_pll_write_enable(pll->map, reg[CTRL_OFFSET], ctrl_val);
+ return 0;
+ }
+
+ rate *= pll->fix_divider;
+
+ if (soc_data->ctrl2_val)
+ ctrl2_val = soc_data->ctrl2_val;
+ else
+ regmap_read(pll->map, reg[CTRL2_OFFSET], &ctrl2_val);
+
+ ret = amb_pll_calc_params(pll, rate, parent_rate, ctrl2_val,
+ &intp, &sdiv, &sout, &vcodiv, &fsdiv,
+ &fsout);
+ if (ret)
+ return ret;
+
+ if (soc_data->ctrl2_val)
+ regmap_write(pll->map, reg[CTRL2_OFFSET], soc_data->ctrl2_val);
+
+ ctrl_val = ((intp - 1) & 0x7f) << 24;
+ ctrl_val |= ((sdiv - 1) & 0xf) << 12;
+ ctrl_val |= ((sout - 1) & 0xf) << 16;
+ if (soc_data->pll_version >= 2) {
+ ctrl_val |= vcodiv == 2 ? soc_data->vcodiv_val : 0;
+ ctrl_val |= fsdiv == 2 ? soc_data->fsdiv_val : 0;
+ ctrl_val |= fsout == 2 ? soc_data->fsout_val : 0;
+ }
+
+ regmap_write(pll->map, reg[CTRL_OFFSET], ctrl_val);
+ regmap_write(pll->map, reg[FRAC_OFFSET], 0);
+
+ old_rate = amb_pll_recalc_rate(hw, parent_rate) * pll->fix_divider;
+ rate_tmp = old_rate > rate ? 0 : rate - old_rate;
+ if (rate_tmp && pll->frac_mode) {
+ u64 dividend, divider;
+
+ dividend = (u64)rate_tmp * sout * fsout;
+ dividend <<= 32;
+ divider = (u64)sdiv * fsdiv * parent_rate;
+ frac_val = DIV_ROUND_CLOSEST_ULL(dividend, divider);
+ regmap_write(pll->map, reg[FRAC_OFFSET], frac_val);
+ ctrl_val |= CTRL_FRAC_MODE;
+ }
+
+ if (soc_data->pll_version >= 2) {
+ ctrl3_val = soc_data->ctrl3_val;
+ regmap_write(pll->map, reg[CTRL3_OFFSET],
+ ctrl3_val | CTRL3_VCO_CLAMP);
+ regmap_write(pll->map, reg[CTRL_OFFSET],
+ ctrl_val | CTRL_FORCE_RESET);
+ ndelay(100);
+ regmap_write(pll->map, reg[CTRL_OFFSET],
+ ctrl_val & ~CTRL_FORCE_RESET);
+ ndelay(100);
+ regmap_write(pll->map, reg[CTRL3_OFFSET],
+ ctrl3_val & ~CTRL3_VCO_CLAMP);
+ } else {
+ u32 fvco_mhz, range;
+
+ fvco_mhz = amb_pll_calc_vco(pll, parent_rate) / 1000000UL;
+ for (range = 0; range < ARRAY_SIZE(soc_data->vco_range);
+ range++) {
+ if (fvco_mhz > soc_data->vco_range[range])
+ break;
+ }
+ range = ARRAY_SIZE(soc_data->vco_range) - range - 1;
+
+ regmap_read(pll->map, reg[CTRL3_OFFSET], &ctrl3_val);
+ ctrl3_val &= ~CTRL3_VCO_RANGE_MASK;
+ ctrl3_val |= range << 1;
+ regmap_write(pll->map, reg[CTRL3_OFFSET], ctrl3_val);
+
+ if (frac_val) {
+ ctrl_val |= CTRL_FORCE_RESET;
+ amb_pll_write_enable(pll->map, reg[CTRL_OFFSET],
+ ctrl_val);
+ }
+
+ ctrl_val &= ~CTRL_FORCE_RESET;
+ amb_pll_write_enable(pll->map, reg[CTRL_OFFSET], ctrl_val);
+ }
+
+ new_rate = amb_pll_recalc_rate(hw, parent_rate);
+ rate_tmp = rate / pll->fix_divider;
+ if (max(new_rate, rate_tmp) - min(new_rate, rate_tmp) > 10)
+ pr_warn("%s: requested %lu, got %lu\n", clk_hw_get_name(hw),
+ rate_tmp, new_rate);
+
+ return 0;
+}
+
+static const struct clk_ops amb_pll_ops = {
+ .recalc_rate = amb_pll_recalc_rate,
+ .determine_rate = amb_pll_determine_rate,
+ .set_rate = amb_pll_set_rate,
+};
+
+struct clk_hw *amb_pll_register(struct device *dev, struct regmap *map,
+ const struct amb_pll_desc *desc)
+{
+ struct amb_pll *pll;
+ struct clk_init_data init = {};
+ const struct clk_hw *parent = desc->parent;
+ int ret;
+
+ pll = devm_kzalloc(dev, sizeof(*pll), GFP_KERNEL);
+ if (!pll)
+ return ERR_PTR(-ENOMEM);
+
+ pll->map = map;
+ memcpy(pll->reg_offset, desc->reg_offset, sizeof(pll->reg_offset));
+ pll->soc_data = desc->soc_data;
+ pll->fix_divider = 1;
+ pll->frac_mode = desc->frac_mode;
+
+ init.name = desc->name;
+ init.ops = &amb_pll_ops;
+ init.flags = CLK_GET_RATE_NOCACHE | CLK_IS_CRITICAL;
+ init.parent_hws = &parent;
+ init.num_parents = 1;
+
+ pll->hw.init = &init;
+
+ ret = devm_clk_hw_register(dev, &pll->hw);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return &pll->hw;
+}
diff --git a/drivers/clk/ambarella/ccu_pll.h b/drivers/clk/ambarella/ccu_pll.h
new file mode 100644
index 000000000000..b9e453e9debf
--- /dev/null
+++ b/drivers/clk/ambarella/ccu_pll.h
@@ -0,0 +1,70 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2026 Ambarella, Inc.
+ */
+
+#ifndef __CCU_PLL_H
+#define __CCU_PLL_H
+
+#include <linux/bits.h>
+#include <linux/clk-provider.h>
+#include <linux/device.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+
+enum {
+ CTRL_OFFSET = 0,
+ FRAC_OFFSET,
+ CTRL2_OFFSET,
+ CTRL3_OFFSET,
+ PRES_OFFSET,
+ POST_OFFSET,
+ REG_NUM,
+};
+
+#define CTRL_BYPASS BIT(2)
+#define CTRL_WRITE_ENABLE BIT(0)
+#define CTRL_FRAC_MODE BIT(3)
+#define CTRL_FORCE_RESET BIT(4)
+#define CTRL_POWER_DOWN BIT(5)
+#define CTRL_HALT_VCO BIT(6)
+#define CTRL_VCODIV_DIV2 BIT(8)
+#define CTRL_FSDIV_DIV2 BIT(9)
+#define CTRL_FSOUT_DIV2 BIT(10)
+#define CTRL_BYPASS_HSDIV BIT(11)
+
+#define CTRL2_VCODIV_DIV2 BIT(8)
+#define CTRL2_FSDIV_DIV2 BIT(9)
+#define CTRL2_FSOUT_DIV2 BIT(11)
+#define CTRL2_BYPASS_HSDIV BIT(12)
+
+#define CTRL3_VCO_RANGE_MASK 0x6
+#define CTRL3_VCO_CLAMP 0x8
+
+struct amb_pll_soc_data {
+ u32 pll_version;
+ u32 fsout_mask;
+ u32 fsout_val;
+ u32 fsdiv_mask;
+ u32 fsdiv_val;
+ u32 vcodiv_mask;
+ u32 vcodiv_val;
+ u32 vco_max_mhz;
+ u32 vco_min_mhz;
+ u32 vco_range[4];
+ u32 ctrl2_val;
+ u32 ctrl3_val;
+};
+
+struct amb_pll_desc {
+ const char *name;
+ const struct clk_hw *parent;
+ const u32 reg_offset[REG_NUM];
+ const struct amb_pll_soc_data *soc_data;
+ bool frac_mode;
+};
+
+struct clk_hw *amb_pll_register(struct device *dev, struct regmap *map,
+ const struct amb_pll_desc *desc);
+
+#endif
--
2.34.1
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v2 07/10] pinctrl: ambarella: add Ambarella pin controller
2026-08-06 9:34 [PATCH v2 00/10] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
` (5 preceding siblings ...)
2026-08-06 9:34 ` [PATCH v2 06/10] clk: ambarella: add CV75 CCU driver Long Zhao via B4 Relay
@ 2026-08-06 9:34 ` Long Zhao via B4 Relay
2026-08-06 9:46 ` sashiko-bot
2026-08-07 17:45 ` Linus Walleij
2026-08-06 9:34 ` [PATCH v2 08/10] serial: ambarella: add Ambarella UART driver Long Zhao via B4 Relay
` (2 subsequent siblings)
9 siblings, 2 replies; 27+ messages in thread
From: Long Zhao via B4 Relay @ 2026-08-06 9:34 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
Stephen Boyd, Linus Walleij, Greg Kroah-Hartman, Jiri Slaby,
Catalin Marinas, Will Deacon, Arnd Bergmann, Alexandre Belloni
Cc: devicetree, linux-arm-kernel, linux-kernel, linux-clk, linux-gpio,
linux-serial, soc, Long Zhao
From: Long Zhao <longzhao@ambarella.com>
Add the Ambarella pinmux/GPIO controller driver used by CV75.
Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
drivers/pinctrl/Kconfig | 15 +
drivers/pinctrl/Makefile | 2 +
drivers/pinctrl/pinctrl-ambarella-cv75.c | 68 ++
drivers/pinctrl/pinctrl-ambarella.c | 1510 ++++++++++++++++++++++++++++++
drivers/pinctrl/pinctrl-ambarella.h | 48 +
5 files changed, 1643 insertions(+)
diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig
index c2cdd7b2c49b..572a4a898a99 100644
--- a/drivers/pinctrl/Kconfig
+++ b/drivers/pinctrl/Kconfig
@@ -37,6 +37,21 @@ config DEBUG_PINCTRL
help
Say Y here to add some extra checks and diagnostics to PINCTRL calls.
+config PINCTRL_AMB
+ bool "Ambarella pin controller support"
+ depends on ARCH_AMBARELLA || COMPILE_TEST
+ select GENERIC_PINCONF
+ select GPIOLIB
+ select GPIOLIB_IRQCHIP
+ select MFD_SYSCON
+ select PINCONF
+ select PINMUX
+ help
+ Say Y here to enable the pin controller found on Ambarella SoCs.
+ The driver provides pin multiplexing and pin configuration as well
+ as GPIO and GPIO interrupt support. It is required to configure
+ peripheral pins on systems using an Ambarella SoC.
+
config PINCTRL_AMD
bool "AMD GPIO pin control"
depends on HAS_IOMEM
diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile
index a35d71135abf..ce0825f4fc1b 100644
--- a/drivers/pinctrl/Makefile
+++ b/drivers/pinctrl/Makefile
@@ -10,6 +10,8 @@ obj-$(CONFIG_GENERIC_PINCONF) += pinconf-generic.o
obj-$(CONFIG_GENERIC_PINCTRL) += pinctrl-generic.o
obj-$(CONFIG_OF) += devicetree.o
+obj-$(CONFIG_PINCTRL_AMB) += pinctrl-ambarella.o
+obj-$(CONFIG_PINCTRL_AMB) += pinctrl-ambarella-cv75.o
obj-$(CONFIG_PINCTRL_AMD) += pinctrl-amd.o
obj-$(CONFIG_PINCTRL_AMDISP) += pinctrl-amdisp.o
obj-$(CONFIG_PINCTRL_APPLE_GPIO) += pinctrl-apple-gpio.o
diff --git a/drivers/pinctrl/pinctrl-ambarella-cv75.c b/drivers/pinctrl/pinctrl-ambarella-cv75.c
new file mode 100644
index 000000000000..af4641bafebb
--- /dev/null
+++ b/drivers/pinctrl/pinctrl-ambarella-cv75.c
@@ -0,0 +1,68 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Ambarella CV75 pinctrl data
+ *
+ * Copyright (C) 2026, Ambarella, Inc.
+ */
+
+#include <linux/kernel.h>
+
+#include "pinctrl-ambarella.h"
+
+static const u32 cv75_uart0tx_pinmux[] = {
+ AMBA_PINMUX(44, 1),
+};
+
+static const u32 cv75_uart0rx_pinmux[] = {
+ AMBA_PINMUX(45, 1),
+};
+
+static const struct ambpin_group_desc cv75_pin_groups[] = {
+ {
+ .name = "uart0tx",
+ .pinmux = cv75_uart0tx_pinmux,
+ .num_pins = ARRAY_SIZE(cv75_uart0tx_pinmux),
+ },
+ {
+ .name = "uart0rx",
+ .pinmux = cv75_uart0rx_pinmux,
+ .num_pins = ARRAY_SIZE(cv75_uart0rx_pinmux),
+ },
+};
+
+static const char * const cv75_uart0_groups[] = {
+ "uart0tx",
+ "uart0rx",
+};
+
+static const struct ambpin_function cv75_pin_functions[] = {
+ {
+ .name = "uart0",
+ .groups = cv75_uart0_groups,
+ .num_groups = ARRAY_SIZE(cv75_uart0_groups),
+ },
+};
+
+const struct amb_pinctrl_data ambarella_cv75_pinctrl_data = {
+ .ds0 = {
+ 0x314, 0x320, 0x32c,
+ },
+ .ds1 = {
+ 0x318, 0x324, 0x330,
+ },
+ .ds2 = {
+ 0x31c, 0x328, 0x334,
+ },
+ .pull_en = {
+ 0x60, 0x64, 0x68,
+ },
+ .pull_dir = {
+ 0x7c, 0x80, 0x84,
+ },
+ .have_ds2 = true,
+ .clk_au_dedicated_pin = 96,
+ .groups = cv75_pin_groups,
+ .nr_groups = ARRAY_SIZE(cv75_pin_groups),
+ .functions = cv75_pin_functions,
+ .nr_functions = ARRAY_SIZE(cv75_pin_functions),
+};
diff --git a/drivers/pinctrl/pinctrl-ambarella.c b/drivers/pinctrl/pinctrl-ambarella.c
new file mode 100644
index 000000000000..bb79e06c4160
--- /dev/null
+++ b/drivers/pinctrl/pinctrl-ambarella.c
@@ -0,0 +1,1510 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Pinctrl driver for Ambarella SoCs
+ *
+ * History:
+ * 2013/12/18 - [Cao Rongrong] created file
+ *
+ * Copyright (C) 2012-2026, Ambarella, Inc.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/bitmap.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/of_irq.h>
+#include <linux/pm.h>
+#include <linux/slab.h>
+#include <linux/regmap.h>
+#include <linux/mfd/syscon.h>
+#include <linux/irqdomain.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/gpio/driver.h>
+#include <linux/pinctrl/pinctrl.h>
+#include <linux/pinctrl/pinmux.h>
+#include <linux/pinctrl/pinconf.h>
+#include <linux/pinctrl/pinconf-generic.h>
+#include <linux/seq_file.h>
+
+#include "pinconf.h"
+#include "pinctrl-ambarella.h"
+
+/* ==========================================================================*/
+
+#define GPIO_DATA_OFFSET 0x00
+#define GPIO_DIR_OFFSET 0x04
+#define GPIO_IS_OFFSET 0x08
+#define GPIO_IBE_OFFSET 0x0c
+#define GPIO_IEV_OFFSET 0x10
+#define GPIO_IE_OFFSET 0x14
+#define GPIO_AFSEL_OFFSET 0x18
+#define GPIO_RIS_OFFSET 0x1c
+#define GPIO_MIS_OFFSET 0x20
+#define GPIO_IC_OFFSET 0x24
+#define GPIO_MASK_OFFSET 0x28
+#define GPIO_ENABLE_OFFSET 0x2c
+
+#define IOMUX_OFFSET(bank, n) (((bank) * 0xc) + ((n) * 4))
+#define IOMUX_CTRL_SET_OFFSET 0xf0
+
+/* ==========================================================================*/
+
+#define AMBA_MAX_PINS (AMBA_MAX_BANKS * 32)
+
+#define PINID_TO_BANK(p) ((p) >> 5)
+#define PINID_TO_OFFSET(p) ((p) & 0x1f)
+
+struct amb_pinctrl_pm_state {
+ u32 iomux[3];
+ u32 pull[2];
+ u32 ds[3];
+ u32 data;
+ u32 dir;
+ u32 is;
+ u32 ibe;
+ u32 iev;
+ u32 ie;
+ u32 afsel;
+ u32 mask;
+};
+
+struct ambpin_group {
+ const char *name;
+ const u32 *pinmux;
+ unsigned int *pins;
+ unsigned int num_pins;
+};
+
+struct amb_pinctrl_soc_data;
+
+struct amb_gpio_bank {
+ struct amb_pinctrl_soc_data *soc;
+ void __iomem *base;
+ struct gpio_chip gc;
+ struct irq_domain *domain;
+ unsigned int pin_base;
+ unsigned int hw_id;
+ unsigned int irq_wake_mask;
+ int irq;
+};
+
+struct amb_pinctrl_soc_data {
+ struct device *dev;
+ const struct amb_pinctrl_data *data;
+ struct amb_gpio_bank banks[AMBA_MAX_BANKS];
+ void __iomem *iomux_base;
+ struct regmap *ds_regmap;
+ struct regmap *pull_regmap;
+ unsigned int bank_num;
+ unsigned int npins;
+ unsigned long used[BITS_TO_LONGS(AMBA_MAX_PINS)];
+ raw_spinlock_t lock;
+
+ struct pinctrl_dev *pctl;
+
+ const struct ambpin_function *functions;
+ unsigned int nr_functions;
+ struct ambpin_group *groups;
+ unsigned int nr_groups;
+
+ struct amb_pinctrl_pm_state pm[AMBA_MAX_BANKS];
+};
+
+static struct amb_gpio_bank *amb_irq_data_to_bank(struct irq_data *data)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
+
+ return gpiochip_get_data(gc);
+}
+
+static struct amb_gpio_bank *
+amb_pin_to_bank(struct amb_pinctrl_soc_data *soc, unsigned int pin)
+{
+ unsigned int i;
+
+ for (i = 0; i < soc->bank_num; i++) {
+ struct amb_gpio_bank *bank = &soc->banks[i];
+
+ if (pin >= bank->pin_base &&
+ pin < bank->pin_base + bank->gc.ngpio)
+ return bank;
+ }
+
+ return NULL;
+}
+
+/* Exclusive end of the GPIO pin number space (max pin_base + ngpio). */
+static unsigned int amb_gpio_pins_end(const struct amb_pinctrl_soc_data *soc)
+{
+ unsigned int end = 0, i;
+
+ for (i = 0; i < soc->bank_num; i++)
+ end = max(end, soc->banks[i].pin_base + soc->banks[i].gc.ngpio);
+
+ return end;
+}
+
+/* check if the selector is a valid pin group selector */
+static int amb_get_group_count(struct pinctrl_dev *pctldev)
+{
+ struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+
+ return soc->nr_groups;
+}
+
+/* return the name of the group selected by the group selector */
+static const char *amb_get_group_name(struct pinctrl_dev *pctldev,
+ unsigned int selector)
+{
+ struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+
+ return soc->groups[selector].name;
+}
+
+/* return the pin numbers associated with the specified group */
+static int amb_get_group_pins(struct pinctrl_dev *pctldev,
+ unsigned int selector, const unsigned int **pins,
+ unsigned int *num_pins)
+{
+ struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+
+ *pins = soc->groups[selector].pins;
+ *num_pins = soc->groups[selector].num_pins;
+
+ return 0;
+}
+
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+static void amb_pin_dbg_show(struct pinctrl_dev *pctldev,
+ struct seq_file *s, unsigned int pin)
+{
+ seq_printf(s, " %s", pinctrl_dev_get_devname(pctldev));
+}
+#endif
+
+/* list of pinctrl callbacks for the pinctrl core */
+static const struct pinctrl_ops amb_pctrl_ops = {
+ .get_groups_count = amb_get_group_count,
+ .get_group_name = amb_get_group_name,
+ .get_group_pins = amb_get_group_pins,
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+ .pin_dbg_show = amb_pin_dbg_show,
+#endif
+ .dt_node_to_map = pinconf_generic_dt_node_to_map_all,
+ .dt_free_map = pinconf_generic_dt_free_map,
+};
+
+/* check if the selector is a valid pin function selector */
+static int amb_pinmux_request(struct pinctrl_dev *pctldev, unsigned int pin)
+{
+ struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+
+ if (test_and_set_bit(pin, soc->used))
+ return -EBUSY;
+
+ return 0;
+}
+
+/* check if the selector is a valid pin function selector */
+static int amb_pinmux_free(struct pinctrl_dev *pctldev, unsigned int pin)
+{
+ struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+
+ clear_bit(pin, soc->used);
+
+ return 0;
+}
+
+/* check if the selector is a valid pin function selector */
+static int amb_pinmux_get_fcount(struct pinctrl_dev *pctldev)
+{
+ struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+
+ return soc->nr_functions;
+}
+
+/* return the name of the pin function specified */
+static const char *amb_pinmux_get_fname(struct pinctrl_dev *pctldev,
+ unsigned int selector)
+{
+ struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+
+ return soc->functions[selector].name;
+}
+
+/* return the groups associated for the specified function selector */
+static int amb_pinmux_get_groups(struct pinctrl_dev *pctldev,
+ unsigned int selector,
+ const char * const **groups,
+ unsigned int * const num_groups)
+{
+ struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+
+ *groups = soc->functions[selector].groups;
+ *num_groups = soc->functions[selector].num_groups;
+
+ return 0;
+}
+
+static void amb_pinmux_set_altfunc(struct amb_pinctrl_soc_data *soc,
+ u32 bank, u32 offset, u32 altfunc)
+{
+ u32 i, data;
+
+ /* On CV3 platform, only ARM cluster0 (safety domain) can access pinctrl registers */
+ if (soc->data->hsm_domain_id != 0)
+ return;
+
+ for (i = 0; i < 3; i++) {
+ data = readl_relaxed(soc->iomux_base + IOMUX_OFFSET(bank, i));
+ data &= (~(0x1 << offset));
+ data |= (((altfunc >> i) & 0x1) << offset);
+ writel_relaxed(data, soc->iomux_base + IOMUX_OFFSET(bank, i));
+ }
+
+ writel_relaxed(0x1, soc->iomux_base + IOMUX_CTRL_SET_OFFSET);
+ writel_relaxed(0x0, soc->iomux_base + IOMUX_CTRL_SET_OFFSET);
+}
+
+/* enable a specified pinmux by writing to registers */
+static int amb_pinmux_set_mux(struct pinctrl_dev *pctldev,
+ unsigned int selector, unsigned int group)
+{
+ struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+ const struct ambpin_group *grp;
+ u32 i, pin, alt, bank, offset;
+ unsigned long flags;
+
+ grp = &soc->groups[group];
+
+ raw_spin_lock_irqsave(&soc->lock, flags);
+ for (i = 0; i < grp->num_pins; i++) {
+ pin = AMBA_PINMUX_TO_PIN(grp->pinmux[i]);
+ alt = AMBA_PINMUX_TO_ALT(grp->pinmux[i]);
+ bank = PINID_TO_BANK(pin);
+ offset = PINID_TO_OFFSET(pin);
+ amb_pinmux_set_altfunc(soc, bank, offset, alt);
+ }
+ raw_spin_unlock_irqrestore(&soc->lock, flags);
+
+ return 0;
+}
+
+static int amb_pinmux_gpio_request_enable(struct pinctrl_dev *pctldev,
+ struct pinctrl_gpio_range *range,
+ unsigned int pin)
+{
+ struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+ u32 bank, offset;
+ unsigned long flags;
+
+ if (!range || !range->gc) {
+ dev_err(soc->dev, "invalid range: %p\n", range);
+ return -EINVAL;
+ }
+
+ if (test_and_set_bit(pin, soc->used))
+ return -EBUSY;
+
+ bank = PINID_TO_BANK(pin);
+ offset = PINID_TO_OFFSET(pin);
+
+ raw_spin_lock_irqsave(&soc->lock, flags);
+ amb_pinmux_set_altfunc(soc, bank, offset, 0);
+ raw_spin_unlock_irqrestore(&soc->lock, flags);
+
+ return 0;
+}
+
+static void amb_pinmux_gpio_disable_free(struct pinctrl_dev *pctldev,
+ struct pinctrl_gpio_range *range,
+ unsigned int pin)
+{
+ struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+
+ dev_dbg(soc->dev, "disable pin %u as GPIO\n", pin);
+ /* Set the pin to some default state, GPIO is usually default */
+
+ clear_bit(pin, soc->used);
+}
+
+/* list of pinmux callbacks for the pinmux vertical in pinctrl core */
+static const struct pinmux_ops amb_pinmux_ops = {
+ .request = amb_pinmux_request,
+ .free = amb_pinmux_free,
+ .get_functions_count = amb_pinmux_get_fcount,
+ .get_function_name = amb_pinmux_get_fname,
+ .get_function_groups = amb_pinmux_get_groups,
+ .set_mux = amb_pinmux_set_mux,
+ .gpio_request_enable = amb_pinmux_gpio_request_enable,
+ .gpio_disable_free = amb_pinmux_gpio_disable_free,
+};
+
+static int amb_drive_strength_to_reg(struct amb_pinctrl_soc_data *soc,
+ u32 strength)
+{
+ if (soc->data->have_ds2) {
+ switch (strength) {
+ case 3:
+ return 0;
+ case 4:
+ case 5:
+ return 1;
+ case 6:
+ return 2;
+ case 7:
+ case 8:
+ return 3;
+ case 9:
+ return 4;
+ case 12:
+ return 5;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ switch (strength) {
+ case 2:
+ return 0;
+ case 4:
+ return 1;
+ case 8:
+ return 2;
+ case 12:
+ return 3;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int amb_reg_to_drive_strength(struct amb_pinctrl_soc_data *soc, u32 ds)
+{
+ static const int ds2_ma[] = { 3, 4, 6, 8, 9, 12 };
+ static const int ds_ma[] = { 2, 4, 8, 12 };
+
+ if (soc->data->have_ds2) {
+ if (ds >= ARRAY_SIZE(ds2_ma))
+ return -EINVAL;
+
+ return ds2_ma[ds];
+ }
+
+ if (ds >= ARRAY_SIZE(ds_ma))
+ return -EINVAL;
+
+ return ds_ma[ds];
+}
+
+/* set the pin config settings for a specified pin */
+static int amb_pinconf_set(struct pinctrl_dev *pctldev, unsigned int pin,
+ unsigned long *configs, unsigned int num_configs)
+{
+ struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+ struct amb_gpio_bank *gpio_bank;
+ u32 i, bank, offset;
+ unsigned long config, flags;
+ enum pin_config_param param;
+ u32 arg;
+ int ds;
+
+ gpio_bank = amb_pin_to_bank(soc, pin);
+ if (!gpio_bank)
+ return -EINVAL;
+
+ bank = gpio_bank->hw_id;
+ offset = pin - gpio_bank->pin_base;
+
+ raw_spin_lock_irqsave(&soc->lock, flags);
+ for (i = 0; i < num_configs; i++) {
+ config = configs[i];
+ param = pinconf_to_config_param(config);
+ arg = pinconf_to_config_argument(config);
+
+ switch (param) {
+ case PIN_CONFIG_BIAS_DISABLE:
+ regmap_update_bits(soc->pull_regmap,
+ soc->data->pull_en[bank], BIT(offset), 0);
+ break;
+ case PIN_CONFIG_BIAS_PULL_DOWN:
+ case PIN_CONFIG_BIAS_PULL_UP:
+ regmap_update_bits(soc->pull_regmap, soc->data->pull_dir[bank],
+ BIT(offset),
+ (param == PIN_CONFIG_BIAS_PULL_UP) ?
+ BIT(offset) : 0);
+ regmap_update_bits(soc->pull_regmap, soc->data->pull_en[bank],
+ BIT(offset), BIT(offset));
+ break;
+ case PIN_CONFIG_DRIVE_STRENGTH:
+ ds = amb_drive_strength_to_reg(soc, arg);
+ if (ds < 0) {
+ raw_spin_unlock_irqrestore(&soc->lock, flags);
+ return ds;
+ }
+ if (soc->data->have_ds2) {
+ regmap_update_bits(soc->ds_regmap,
+ soc->data->ds0[bank], BIT(offset),
+ (ds & BIT(0)) ? BIT(offset) : 0);
+ regmap_update_bits(soc->ds_regmap,
+ soc->data->ds1[bank], BIT(offset),
+ (ds & BIT(1)) ? BIT(offset) : 0);
+ regmap_update_bits(soc->ds_regmap,
+ soc->data->ds2[bank], BIT(offset),
+ (ds & BIT(2)) ? BIT(offset) : 0);
+ } else {
+ regmap_update_bits(soc->ds_regmap,
+ soc->data->ds0[bank], BIT(offset),
+ (ds & BIT(1)) ? BIT(offset) : 0);
+ regmap_update_bits(soc->ds_regmap,
+ soc->data->ds1[bank], BIT(offset),
+ (ds & BIT(0)) ? BIT(offset) : 0);
+ }
+ break;
+ default:
+ raw_spin_unlock_irqrestore(&soc->lock, flags);
+ return -EOPNOTSUPP;
+ }
+ }
+ raw_spin_unlock_irqrestore(&soc->lock, flags);
+
+ return 0;
+}
+
+static int amb_pinconf_group_set(struct pinctrl_dev *pctldev,
+ unsigned int selector,
+ unsigned long *configs,
+ unsigned int num_configs)
+{
+ struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+ const struct ambpin_group *grp = &soc->groups[selector];
+ int ret;
+ u32 i;
+
+ for (i = 0; i < grp->num_pins; i++) {
+ ret = amb_pinconf_set(pctldev, grp->pins[i], configs,
+ num_configs);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+/* get the pin config settings for a specified pin */
+static int amb_pinconf_get(struct pinctrl_dev *pctldev,
+ unsigned int pin, unsigned long *config)
+{
+ struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+ struct amb_gpio_bank *gpio_bank;
+ enum pin_config_param param = pinconf_to_config_param(*config);
+ u32 bank, offset, pull_en, pull_dir, ds0, ds1, ds2, ds;
+ int ret, strength;
+
+ gpio_bank = amb_pin_to_bank(soc, pin);
+ if (!gpio_bank)
+ return -EINVAL;
+
+ bank = gpio_bank->hw_id;
+ offset = pin - gpio_bank->pin_base;
+
+ switch (param) {
+ case PIN_CONFIG_BIAS_DISABLE:
+ case PIN_CONFIG_BIAS_PULL_DOWN:
+ case PIN_CONFIG_BIAS_PULL_UP:
+ ret = regmap_read(soc->pull_regmap, soc->data->pull_en[bank],
+ &pull_en);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(soc->pull_regmap, soc->data->pull_dir[bank],
+ &pull_dir);
+ if (ret)
+ return ret;
+
+ pull_en = (pull_en >> offset) & 1;
+ pull_dir = (pull_dir >> offset) & 1;
+
+ if (param == PIN_CONFIG_BIAS_DISABLE) {
+ if (pull_en)
+ return -EINVAL;
+ *config = pinconf_to_config_packed(param, 0);
+ return 0;
+ }
+
+ if (!pull_en)
+ return -EINVAL;
+ if (param == PIN_CONFIG_BIAS_PULL_UP && !pull_dir)
+ return -EINVAL;
+ if (param == PIN_CONFIG_BIAS_PULL_DOWN && pull_dir)
+ return -EINVAL;
+
+ *config = pinconf_to_config_packed(param, 1);
+ return 0;
+
+ case PIN_CONFIG_DRIVE_STRENGTH:
+ ret = regmap_read(soc->ds_regmap, soc->data->ds0[bank], &ds0);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(soc->ds_regmap, soc->data->ds1[bank], &ds1);
+ if (ret)
+ return ret;
+
+ ds0 = (ds0 >> offset) & 1;
+ ds1 = (ds1 >> offset) & 1;
+ if (soc->data->have_ds2) {
+ ret = regmap_read(soc->ds_regmap, soc->data->ds2[bank], &ds2);
+ if (ret)
+ return ret;
+
+ ds2 = (ds2 >> offset) & 1;
+ ds = (ds2 << 2) | (ds1 << 1) | ds0;
+ } else {
+ ds = (ds0 << 1) | ds1;
+ }
+
+ strength = amb_reg_to_drive_strength(soc, ds);
+ if (strength < 0)
+ return strength;
+
+ *config = pinconf_to_config_packed(param, strength);
+ return 0;
+
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+static void amb_pinconf_dbg_show(struct pinctrl_dev *pctldev,
+ struct seq_file *s, unsigned int pin)
+{
+ struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+ struct amb_gpio_bank *gpio_bank;
+ u32 pull_en, pull_dir, ds0, ds1, ds2, ds;
+ u32 bank, offset;
+ int strength;
+
+ gpio_bank = amb_pin_to_bank(soc, pin);
+ if (!gpio_bank) {
+ seq_puts(s, " (no pinconf)");
+ return;
+ }
+
+ bank = gpio_bank->hw_id;
+ offset = pin - gpio_bank->pin_base;
+
+ regmap_read(soc->pull_regmap, soc->data->pull_en[bank], &pull_en);
+ pull_en = (pull_en >> offset) & 1;
+ regmap_read(soc->pull_regmap, soc->data->pull_dir[bank], &pull_dir);
+ pull_dir = (pull_dir >> offset) & 1;
+ seq_printf(s, " pull: %s,",
+ pull_en ? (pull_dir ? "up" : "down") : "disable");
+
+ regmap_read(soc->ds_regmap, soc->data->ds0[bank], &ds0);
+ ds0 = (ds0 >> offset) & 1;
+ regmap_read(soc->ds_regmap, soc->data->ds1[bank], &ds1);
+ ds1 = (ds1 >> offset) & 1;
+ if (soc->data->have_ds2) {
+ regmap_read(soc->ds_regmap, soc->data->ds2[bank], &ds2);
+ ds2 = (ds2 >> offset) & 1;
+ ds = (ds2 << 2) | (ds1 << 1) | ds0;
+ } else {
+ ds = (ds0 << 1) | ds1;
+ }
+
+ strength = amb_reg_to_drive_strength(soc, ds);
+ if (strength < 0)
+ seq_puts(s, " drive-strength: invalid");
+ else
+ seq_printf(s, " drive-strength: %dmA", strength);
+}
+#endif
+
+/* list of pinconfig callbacks for pinconfig vertical in the pinctrl code */
+static const struct pinconf_ops amb_pinconf_ops = {
+ .is_generic = true,
+ .pin_config_get = amb_pinconf_get,
+ .pin_config_set = amb_pinconf_set,
+ .pin_config_group_set = amb_pinconf_group_set,
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+ .pin_config_dbg_show = amb_pinconf_dbg_show,
+#endif
+};
+
+/* register the pinctrl interface with the pinctrl subsystem */
+static int amb_pinctrl_register(struct amb_pinctrl_soc_data *soc)
+{
+ struct pinctrl_pin_desc *pindesc;
+ struct pinctrl_desc *amb_pinctrl_desc;
+ unsigned int pin;
+
+ /* dynamically populate the pin number and pin name for pindesc */
+ pindesc = devm_kcalloc(soc->dev, soc->npins, sizeof(*pindesc),
+ GFP_KERNEL);
+ if (!pindesc)
+ return -ENOMEM;
+
+ for (pin = 0; pin < soc->npins; pin++) {
+ pindesc[pin].number = pin;
+ pindesc[pin].name = devm_kasprintf(soc->dev, GFP_KERNEL,
+ "io%u", pin);
+ if (!pindesc[pin].name)
+ return -ENOMEM;
+ }
+
+ amb_pinctrl_desc = devm_kzalloc(soc->dev, sizeof(*amb_pinctrl_desc), GFP_KERNEL);
+ if (!amb_pinctrl_desc)
+ return -ENOMEM;
+
+ amb_pinctrl_desc->name = dev_name(soc->dev);
+ amb_pinctrl_desc->pins = pindesc;
+ amb_pinctrl_desc->npins = soc->npins;
+ amb_pinctrl_desc->pctlops = &amb_pctrl_ops;
+ amb_pinctrl_desc->pmxops = &amb_pinmux_ops;
+ amb_pinctrl_desc->confops = &amb_pinconf_ops;
+ amb_pinctrl_desc->owner = THIS_MODULE;
+
+ soc->pctl = devm_pinctrl_register(soc->dev, amb_pinctrl_desc, soc);
+ if (IS_ERR(soc->pctl)) {
+ dev_err(soc->dev, "could not register pinctrl driver\n");
+ return PTR_ERR(soc->pctl);
+ }
+
+ return 0;
+}
+
+/* gpiolib gpio_set callback function */
+static int amb_gpio_set(struct gpio_chip *gc, unsigned int pin, int value)
+{
+ struct amb_gpio_bank *bank = gpiochip_get_data(gc);
+ struct amb_pinctrl_soc_data *soc = bank->soc;
+ u32 data;
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&soc->lock, flags);
+ writel_relaxed(BIT(pin), bank->base + GPIO_MASK_OFFSET);
+ data = value ? BIT(pin) : 0;
+ writel_relaxed(data, bank->base + GPIO_DATA_OFFSET);
+ raw_spin_unlock_irqrestore(&soc->lock, flags);
+
+ return 0;
+}
+
+/* gpiolib gpio_get callback function */
+static int amb_gpio_get(struct gpio_chip *gc, unsigned int pin)
+{
+ struct amb_gpio_bank *bank = gpiochip_get_data(gc);
+ struct amb_pinctrl_soc_data *soc = bank->soc;
+ u32 data;
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&soc->lock, flags);
+ writel_relaxed(BIT(pin), bank->base + GPIO_MASK_OFFSET);
+ data = readl_relaxed(bank->base + GPIO_DATA_OFFSET);
+ raw_spin_unlock_irqrestore(&soc->lock, flags);
+
+ return !!(data & BIT(pin));
+}
+
+static int amb_gpio_get_direction(struct gpio_chip *gc, unsigned int pin)
+{
+ struct amb_gpio_bank *bank = gpiochip_get_data(gc);
+ struct amb_pinctrl_soc_data *soc = bank->soc;
+ u32 data;
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&soc->lock, flags);
+ data = readl_relaxed(bank->base + GPIO_DIR_OFFSET);
+ raw_spin_unlock_irqrestore(&soc->lock, flags);
+
+ return data & BIT(pin) ? GPIO_LINE_DIRECTION_OUT : GPIO_LINE_DIRECTION_IN;
+}
+
+static int amb_gpio_set_direction(struct gpio_chip *gc, unsigned int pin, bool input)
+{
+ struct amb_gpio_bank *bank = gpiochip_get_data(gc);
+ struct amb_pinctrl_soc_data *soc = bank->soc;
+ u32 data;
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&soc->lock, flags);
+ data = readl_relaxed(bank->base + GPIO_DIR_OFFSET);
+ if (input)
+ data &= ~BIT(pin);
+ else
+ data |= BIT(pin);
+ writel_relaxed(data, bank->base + GPIO_DIR_OFFSET);
+ raw_spin_unlock_irqrestore(&soc->lock, flags);
+
+ return 0;
+}
+
+/* gpiolib gpio_direction_input callback function */
+static int amb_gpio_direction_input(struct gpio_chip *gc, unsigned int pin)
+{
+ return amb_gpio_set_direction(gc, pin, true);
+}
+
+/* gpiolib gpio_direction_output callback function */
+static int amb_gpio_direction_output(struct gpio_chip *gc, unsigned int pin, int value)
+{
+ struct amb_gpio_bank *bank = gpiochip_get_data(gc);
+ struct amb_pinctrl_soc_data *soc = bank->soc;
+ unsigned long flags;
+ u32 data;
+
+ raw_spin_lock_irqsave(&soc->lock, flags);
+
+ writel_relaxed(BIT(pin), bank->base + GPIO_MASK_OFFSET);
+ writel_relaxed(value ? BIT(pin) : 0,
+ bank->base + GPIO_DATA_OFFSET);
+
+ data = readl_relaxed(bank->base + GPIO_DIR_OFFSET);
+ writel_relaxed(data | BIT(pin), bank->base + GPIO_DIR_OFFSET);
+
+ raw_spin_unlock_irqrestore(&soc->lock, flags);
+
+ return 0;
+}
+
+/* gpiolib gpio_to_irq callback function */
+static int amb_gpio_to_irq(struct gpio_chip *gc, unsigned int pin)
+{
+ struct amb_gpio_bank *bank = gpiochip_get_data(gc);
+
+ return irq_create_mapping(bank->domain, pin);
+}
+
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+static void amb_gpio_dbg_show(struct seq_file *s, struct gpio_chip *gc)
+{
+ struct amb_gpio_bank *bank = gpiochip_get_data(gc);
+ struct amb_pinctrl_soc_data *soc = bank->soc;
+ u32 afsel, data, dir, mask, iomux0, iomux1, iomux2, alt;
+ unsigned long flags;
+ u32 i;
+
+ raw_spin_lock_irqsave(&soc->lock, flags);
+ afsel = readl_relaxed(bank->base + GPIO_AFSEL_OFFSET);
+ dir = readl_relaxed(bank->base + GPIO_DIR_OFFSET);
+ mask = readl_relaxed(bank->base + GPIO_MASK_OFFSET);
+ writel_relaxed(0xffffffff, bank->base + GPIO_MASK_OFFSET);
+ data = readl_relaxed(bank->base + GPIO_DATA_OFFSET);
+ writel_relaxed(mask, bank->base + GPIO_MASK_OFFSET);
+
+ iomux0 = readl_relaxed(soc->iomux_base + IOMUX_OFFSET(bank->hw_id, 0));
+ iomux1 = readl_relaxed(soc->iomux_base + IOMUX_OFFSET(bank->hw_id, 1));
+ iomux2 = readl_relaxed(soc->iomux_base + IOMUX_OFFSET(bank->hw_id, 2));
+ raw_spin_unlock_irqrestore(&soc->lock, flags);
+
+ seq_printf(s, "\nGPIO[%d]:\n", bank->hw_id);
+ seq_printf(s, "GPIO_AFSEL:\t0x%08X\n", afsel);
+ seq_printf(s, "GPIO_DIR:\t0x%08X\n", dir);
+ seq_printf(s, "GPIO_MASK:\t0x%08X\n", mask);
+ seq_printf(s, "GPIO_DATA:\t0x%08X\n", data);
+ seq_printf(s, "IOMUX_REG%d_0:\t0x%08X\n", bank->hw_id, iomux0);
+ seq_printf(s, "IOMUX_REG%d_1:\t0x%08X\n", bank->hw_id, iomux1);
+ seq_printf(s, "IOMUX_REG%d_2:\t0x%08X\n", bank->hw_id, iomux2);
+
+ for (i = 0; i < gc->ngpio; i++) {
+
+ seq_printf(s, " gpio-%-3d", gc->base + i);
+
+ alt = ((iomux2 >> i) & 1) << 2;
+ alt |= ((iomux1 >> i) & 1) << 1;
+ alt |= ((iomux0 >> i) & 1) << 0;
+ if (alt) {
+ seq_printf(s, " [HW ] (alt%d)\n", alt);
+ } else {
+ char *label __free(kfree) = gpiochip_dup_line_label(gc, i);
+ if (IS_ERR(label)) {
+ pr_debug("Failed to duplicate label\n");
+ continue;
+ }
+
+ seq_printf(s, " [GPIO] (%-20.20s) %s %s\n",
+ label ? label : "",
+ (dir & BIT(i)) ? "out" : "in ",
+ (data & BIT(i)) ? "hi" : "lo");
+ }
+ }
+}
+#endif
+
+static void amb_gpio_irq_enable(struct irq_data *data)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
+ struct amb_gpio_bank *bank = amb_irq_data_to_bank(data);
+ struct amb_pinctrl_soc_data *soc = bank->soc;
+ void __iomem *gpio_base = bank->base;
+ void __iomem *iomux_base = soc->iomux_base;
+ u32 i, val, offset;
+ unsigned long flags;
+
+ offset = irqd_to_hwirq(data);
+
+ gpiochip_enable_irq(gc, offset);
+
+ raw_spin_lock_irqsave(&soc->lock, flags);
+
+ val = readl_relaxed(gpio_base + GPIO_DIR_OFFSET);
+ val &= ~(0x1 << offset);
+ writel_relaxed(val, gpio_base + GPIO_DIR_OFFSET);
+
+ for (i = 0; i < 3; i++) {
+ val = readl_relaxed(iomux_base + IOMUX_OFFSET(bank->hw_id, i));
+ val &= ~(0x1 << offset);
+ writel_relaxed(val, iomux_base + IOMUX_OFFSET(bank->hw_id, i));
+ }
+ writel_relaxed(0x1, iomux_base + IOMUX_CTRL_SET_OFFSET);
+ writel_relaxed(0x0, iomux_base + IOMUX_CTRL_SET_OFFSET);
+
+ writel_relaxed(0x1 << offset, gpio_base + GPIO_IC_OFFSET);
+
+ val = readl_relaxed(gpio_base + GPIO_IE_OFFSET);
+ val |= 0x1 << offset;
+ writel_relaxed(val, gpio_base + GPIO_IE_OFFSET);
+
+ raw_spin_unlock_irqrestore(&soc->lock, flags);
+}
+
+static void amb_gpio_irq_disable(struct irq_data *data)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
+ struct amb_gpio_bank *bank = amb_irq_data_to_bank(data);
+ struct amb_pinctrl_soc_data *soc = bank->soc;
+ void __iomem *gpio_base = bank->base;
+ u32 offset, ie;
+ unsigned long flags;
+
+ offset = irqd_to_hwirq(data);
+
+ raw_spin_lock_irqsave(&soc->lock, flags);
+ ie = readl_relaxed(gpio_base + GPIO_IE_OFFSET);
+ writel_relaxed(ie & ~(0x1 << offset), gpio_base + GPIO_IE_OFFSET);
+ writel_relaxed(0x1 << offset, gpio_base + GPIO_IC_OFFSET);
+ raw_spin_unlock_irqrestore(&soc->lock, flags);
+
+ gpiochip_disable_irq(gc, offset);
+}
+
+static void amb_gpio_irq_ack(struct irq_data *data)
+{
+ struct amb_gpio_bank *bank = amb_irq_data_to_bank(data);
+ struct amb_pinctrl_soc_data *soc = bank->soc;
+ void __iomem *gpio_base = bank->base;
+ u32 offset = irqd_to_hwirq(data);
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&soc->lock, flags);
+ writel_relaxed(0x1 << offset, gpio_base + GPIO_IC_OFFSET);
+ raw_spin_unlock_irqrestore(&soc->lock, flags);
+}
+
+static void amb_gpio_irq_mask(struct irq_data *data)
+{
+ struct amb_gpio_bank *bank = amb_irq_data_to_bank(data);
+ struct amb_pinctrl_soc_data *soc = bank->soc;
+ void __iomem *gpio_base = bank->base;
+ u32 offset, ie;
+ unsigned long flags;
+
+ offset = irqd_to_hwirq(data);
+
+ raw_spin_lock_irqsave(&soc->lock, flags);
+ ie = readl_relaxed(gpio_base + GPIO_IE_OFFSET);
+ writel_relaxed(ie & ~(0x1 << offset), gpio_base + GPIO_IE_OFFSET);
+ raw_spin_unlock_irqrestore(&soc->lock, flags);
+}
+
+static void amb_gpio_irq_mask_ack(struct irq_data *data)
+{
+ struct amb_gpio_bank *bank = amb_irq_data_to_bank(data);
+ struct amb_pinctrl_soc_data *soc = bank->soc;
+ void __iomem *gpio_base = bank->base;
+ u32 offset, ie;
+ unsigned long flags;
+
+ offset = irqd_to_hwirq(data);
+
+ raw_spin_lock_irqsave(&soc->lock, flags);
+ ie = readl_relaxed(gpio_base + GPIO_IE_OFFSET);
+ writel_relaxed(ie & ~(0x1 << offset), gpio_base + GPIO_IE_OFFSET);
+ writel_relaxed(0x1 << offset, gpio_base + GPIO_IC_OFFSET);
+ raw_spin_unlock_irqrestore(&soc->lock, flags);
+}
+
+static void amb_gpio_irq_unmask(struct irq_data *data)
+{
+ struct amb_gpio_bank *bank = amb_irq_data_to_bank(data);
+ struct amb_pinctrl_soc_data *soc = bank->soc;
+ void __iomem *gpio_base = bank->base;
+ u32 offset, ie;
+ unsigned long flags;
+
+ offset = irqd_to_hwirq(data);
+
+ raw_spin_lock_irqsave(&soc->lock, flags);
+ ie = readl_relaxed(gpio_base + GPIO_IE_OFFSET);
+ writel_relaxed(ie | (0x1 << offset), gpio_base + GPIO_IE_OFFSET);
+ raw_spin_unlock_irqrestore(&soc->lock, flags);
+}
+
+static int amb_gpio_irq_set_type(struct irq_data *data, unsigned int type)
+{
+ struct amb_gpio_bank *bank = amb_irq_data_to_bank(data);
+ struct amb_pinctrl_soc_data *soc = bank->soc;
+ void __iomem *gpio_base = bank->base;
+ u32 offset = irqd_to_hwirq(data);
+ u32 mask, bit, sense, bothedges, event;
+ unsigned long flags;
+
+ mask = ~BIT(offset);
+ bit = BIT(offset);
+
+ raw_spin_lock_irqsave(&soc->lock, flags);
+ sense = readl_relaxed(gpio_base + GPIO_IS_OFFSET);
+ bothedges = readl_relaxed(gpio_base + GPIO_IBE_OFFSET);
+ event = readl_relaxed(gpio_base + GPIO_IEV_OFFSET);
+
+ switch (type) {
+ case IRQ_TYPE_EDGE_RISING:
+ sense &= mask;
+ bothedges &= mask;
+ event |= bit;
+ irq_set_handler_locked(data, handle_edge_irq);
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ sense &= mask;
+ bothedges &= mask;
+ event &= mask;
+ irq_set_handler_locked(data, handle_edge_irq);
+ break;
+ case IRQ_TYPE_EDGE_BOTH:
+ sense &= mask;
+ bothedges |= bit;
+ event &= mask;
+ irq_set_handler_locked(data, handle_edge_irq);
+ break;
+ case IRQ_TYPE_LEVEL_HIGH:
+ sense |= bit;
+ bothedges &= mask;
+ event |= bit;
+ irq_set_handler_locked(data, handle_level_irq);
+ break;
+ case IRQ_TYPE_LEVEL_LOW:
+ sense |= bit;
+ bothedges &= mask;
+ event &= mask;
+ irq_set_handler_locked(data, handle_level_irq);
+ break;
+ default:
+ raw_spin_unlock_irqrestore(&soc->lock, flags);
+ return -EINVAL;
+ }
+
+ writel_relaxed(sense, gpio_base + GPIO_IS_OFFSET);
+ writel_relaxed(bothedges, gpio_base + GPIO_IBE_OFFSET);
+ writel_relaxed(event, gpio_base + GPIO_IEV_OFFSET);
+ /* clear obsolete irq */
+ writel_relaxed(BIT(offset), gpio_base + GPIO_IC_OFFSET);
+ raw_spin_unlock_irqrestore(&soc->lock, flags);
+
+ return 0;
+}
+
+static int amb_gpio_irq_set_wake(struct irq_data *data, unsigned int on)
+{
+ if (IS_ENABLED(CONFIG_PM)) {
+ struct amb_gpio_bank *bank = amb_irq_data_to_bank(data);
+ struct amb_pinctrl_soc_data *soc = bank->soc;
+ u32 offset = irqd_to_hwirq(data);
+ unsigned long flags;
+ int ret;
+
+ ret = irq_set_irq_wake(bank->irq, on);
+ if (ret)
+ return ret;
+
+ raw_spin_lock_irqsave(&soc->lock, flags);
+ if (on)
+ bank->irq_wake_mask |= BIT(offset);
+ else
+ bank->irq_wake_mask &= ~BIT(offset);
+ raw_spin_unlock_irqrestore(&soc->lock, flags);
+
+ return 0;
+ }
+ return 0;
+}
+
+static struct irq_chip amb_gpio_irqchip = {
+ .name = "GPIO",
+ .irq_enable = amb_gpio_irq_enable,
+ .irq_disable = amb_gpio_irq_disable,
+ .irq_ack = amb_gpio_irq_ack,
+ .irq_mask = amb_gpio_irq_mask,
+ .irq_mask_ack = amb_gpio_irq_mask_ack,
+ .irq_unmask = amb_gpio_irq_unmask,
+ .irq_set_type = amb_gpio_irq_set_type,
+ .irq_set_wake = amb_gpio_irq_set_wake,
+ .irq_request_resources = gpiochip_irq_reqres,
+ .irq_release_resources = gpiochip_irq_relres,
+ .flags = IRQCHIP_SET_TYPE_MASKED | IRQCHIP_MASK_ON_SUSPEND,
+};
+
+static int amb_gpio_irqdomain_map(struct irq_domain *d, unsigned int irq,
+ irq_hw_number_t hwirq)
+{
+ struct amb_gpio_bank *bank = d->host_data;
+
+ irq_set_chip_data(irq, &bank->gc);
+ irq_set_chip_and_handler(irq, &amb_gpio_irqchip, handle_level_irq);
+ irq_set_noprobe(irq);
+
+ return 0;
+}
+
+static const struct irq_domain_ops amb_gpio_irq_domain_ops = {
+ .map = amb_gpio_irqdomain_map,
+ .xlate = irq_domain_xlate_twocell,
+};
+
+static void amb_gpio_handle_irq(struct irq_desc *desc)
+{
+ struct amb_gpio_bank *bank;
+ struct irq_chip *irqchip;
+ u32 bit, gpio_mis;
+
+ irqchip = irq_desc_get_chip(desc);
+ chained_irq_enter(irqchip, desc);
+
+ bank = irq_desc_get_handler_data(desc);
+ gpio_mis = readl_relaxed(bank->base + GPIO_MIS_OFFSET);
+ while (gpio_mis) {
+ bit = __ffs(gpio_mis);
+ generic_handle_domain_irq(bank->domain, bit);
+ gpio_mis &= ~BIT(bit);
+ }
+
+ chained_irq_exit(irqchip, desc);
+}
+
+static int amb_gpio_parse_dt(struct amb_pinctrl_soc_data *soc)
+{
+ struct of_phandle_args args;
+ struct resource res;
+ unsigned int hw_id, i;
+ int rval;
+
+ for_each_available_child_of_node_scoped(soc->dev->of_node, np) {
+ struct amb_gpio_bank *bank;
+
+ if (!of_property_present(np, "gpio-controller"))
+ continue;
+
+ if (soc->bank_num >= AMBA_MAX_BANKS)
+ return dev_err_probe(soc->dev, -EINVAL,
+ "too many GPIO banks\n");
+
+ rval = of_parse_phandle_with_fixed_args(np, "gpio-ranges", 3, 0,
+ &args);
+ if (rval)
+ return dev_err_probe(soc->dev, rval,
+ "%pOF: invalid gpio-ranges\n", np);
+
+ if (args.np != soc->dev->of_node || args.args[0] ||
+ args.args[1] % 32 || !args.args[2] || args.args[2] > 32) {
+ of_node_put(args.np);
+ return dev_err_probe(soc->dev, -EINVAL,
+ "%pOF: invalid GPIO pin range\n", np);
+ }
+
+ hw_id = args.args[1] / 32;
+ of_node_put(args.np);
+ if (hw_id >= AMBA_MAX_BANKS)
+ return dev_err_probe(soc->dev, -EINVAL,
+ "%pOF: invalid GPIO bank\n", np);
+
+ for (i = 0; i < soc->bank_num; i++) {
+ if (soc->banks[i].hw_id == hw_id)
+ return dev_err_probe(soc->dev, -EINVAL,
+ "%pOF: duplicate GPIO bank\n",
+ np);
+ }
+
+ bank = &soc->banks[soc->bank_num];
+ bank->soc = soc;
+ bank->pin_base = args.args[1];
+ bank->hw_id = hw_id;
+ bank->gc.ngpio = args.args[2];
+ bank->gc.fwnode = of_fwnode_handle(np);
+
+ rval = of_address_to_resource(np, 0, &res);
+ if (rval)
+ return dev_err_probe(soc->dev, rval,
+ "%pOF: couldn't get registers\n", np);
+
+ bank->base = devm_ioremap_resource(soc->dev, &res);
+ if (IS_ERR(bank->base))
+ return dev_err_probe(soc->dev, PTR_ERR(bank->base),
+ "%pOF: couldn't map registers\n", np);
+
+ bank->irq = of_irq_get(np, 0);
+ if (bank->irq < 0)
+ return dev_err_probe(soc->dev, bank->irq,
+ "%pOF: couldn't get interrupt\n", np);
+
+ soc->bank_num++;
+ }
+
+ if (!soc->bank_num)
+ return dev_err_probe(soc->dev, -ENODEV,
+ "no gpio-controller child nodes\n");
+
+ return 0;
+}
+
+static void amb_gpio_irq_cleanup(void *data)
+{
+ struct amb_gpio_bank *bank = data;
+
+ irq_set_chained_handler_and_data(bank->irq, NULL, NULL);
+ irq_domain_remove(bank->domain);
+ bank->domain = NULL;
+}
+
+static int amb_gpio_register(struct amb_pinctrl_soc_data *soc)
+{
+ struct amb_gpio_bank *bank;
+ unsigned int i;
+ int rval;
+
+ for (i = 0; i < soc->bank_num; i++) {
+ bank = &soc->banks[i];
+ bank->gc.label = devm_kasprintf(soc->dev, GFP_KERNEL,
+ "%s-gpio%u", dev_name(soc->dev),
+ bank->hw_id);
+ if (!bank->gc.label)
+ return -ENOMEM;
+
+ bank->gc.base = -1;
+ bank->gc.request = gpiochip_generic_request;
+ bank->gc.free = gpiochip_generic_free;
+ bank->gc.direction_input = amb_gpio_direction_input;
+ bank->gc.direction_output = amb_gpio_direction_output;
+ bank->gc.get_direction = amb_gpio_get_direction;
+ bank->gc.get = amb_gpio_get;
+ bank->gc.set = amb_gpio_set;
+ bank->gc.to_irq = amb_gpio_to_irq;
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+ bank->gc.dbg_show = amb_gpio_dbg_show;
+#endif
+ bank->gc.parent = soc->dev;
+
+ bank->domain = irq_domain_add_linear(to_of_node(bank->gc.fwnode),
+ bank->gc.ngpio,
+ &amb_gpio_irq_domain_ops,
+ bank);
+ if (!bank->domain)
+ return dev_err_probe(soc->dev, -ENODEV,
+ "GPIO%u: failed to create irqdomain\n",
+ i);
+
+ rval = devm_gpiochip_add_data(soc->dev, &bank->gc, bank);
+ if (rval) {
+ irq_domain_remove(bank->domain);
+ bank->domain = NULL;
+ return dev_err_probe(soc->dev, rval,
+ "GPIO%u: gpiochip registration failed\n",
+ i);
+ }
+
+ writel_relaxed(0xffffffff, bank->base + GPIO_ENABLE_OFFSET);
+ writel_relaxed(0x00000000, bank->base + GPIO_AFSEL_OFFSET);
+ writel_relaxed(0x00000000, bank->base + GPIO_MASK_OFFSET);
+
+ irq_set_irq_type(bank->irq, IRQ_TYPE_LEVEL_HIGH);
+ irq_set_chained_handler_and_data(bank->irq,
+ amb_gpio_handle_irq, bank);
+
+ rval = devm_add_action_or_reset(soc->dev,
+ amb_gpio_irq_cleanup, bank);
+ if (rval)
+ return rval;
+ }
+
+ return 0;
+}
+
+static int amb_pinctrl_probe(struct platform_device *pdev)
+{
+ struct amb_pinctrl_soc_data *soc;
+ struct device_node *np;
+ unsigned int *group_pins;
+ unsigned int gpio_pins, group, group_pin;
+ size_t nr_group_pins = 0;
+ int i, rval;
+
+ soc = devm_kzalloc(&pdev->dev, sizeof(*soc), GFP_KERNEL);
+ if (!soc)
+ return -ENOMEM;
+
+ soc->dev = &pdev->dev;
+ soc->data = of_device_get_match_data(&pdev->dev);
+ if (!soc->data)
+ return dev_err_probe(&pdev->dev, -EINVAL, "missing soc data");
+
+ np = pdev->dev.of_node;
+ soc->iomux_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(soc->iomux_base))
+ return dev_err_probe(&pdev->dev, PTR_ERR(soc->iomux_base),
+ "couldn't get iomux reg");
+
+ rval = amb_gpio_parse_dt(soc);
+ if (rval)
+ return rval;
+
+ soc->ds_regmap = syscon_regmap_lookup_by_phandle(np, "ambarella,drive-strength-syscon");
+ if (IS_ERR(soc->ds_regmap))
+ return dev_err_probe(&pdev->dev, PTR_ERR(soc->ds_regmap),
+ "couldn't get drive-strength regmap");
+
+ soc->pull_regmap = syscon_regmap_lookup_by_phandle(np, "ambarella,pull-syscon");
+ if (IS_ERR(soc->pull_regmap))
+ return dev_err_probe(&pdev->dev, PTR_ERR(soc->pull_regmap),
+ "couldn't get pull regmap");
+
+ gpio_pins = amb_gpio_pins_end(soc);
+ soc->npins = gpio_pins;
+ if (soc->data->clk_au_dedicated_pin >= gpio_pins)
+ soc->npins = soc->data->clk_au_dedicated_pin + 1;
+
+ if (soc->npins > AMBA_MAX_PINS)
+ return dev_err_probe(&pdev->dev, -EINVAL, "too many pins\n");
+
+ soc->nr_groups = soc->data->nr_groups;
+ soc->functions = soc->data->functions;
+ soc->nr_functions = soc->data->nr_functions;
+ if (!soc->data->groups || !soc->nr_groups ||
+ !soc->functions || !soc->nr_functions)
+ return dev_err_probe(&pdev->dev, -EINVAL,
+ "missing pin groups or functions\n");
+
+ soc->groups = devm_kcalloc(&pdev->dev, soc->nr_groups,
+ sizeof(*soc->groups), GFP_KERNEL);
+ if (!soc->groups)
+ return -ENOMEM;
+
+ for (group = 0; group < soc->nr_groups; group++) {
+ const struct ambpin_group_desc *desc =
+ &soc->data->groups[group];
+
+ if (!desc->name || !desc->pinmux || !desc->num_pins)
+ return dev_err_probe(&pdev->dev, -EINVAL,
+ "invalid pin group %u\n", group);
+
+ nr_group_pins += desc->num_pins;
+ }
+
+ group_pins = devm_kcalloc(&pdev->dev, nr_group_pins,
+ sizeof(*group_pins), GFP_KERNEL);
+ if (!group_pins)
+ return -ENOMEM;
+
+ for (group = 0; group < soc->nr_groups; group++) {
+ const struct ambpin_group_desc *desc =
+ &soc->data->groups[group];
+ struct ambpin_group *grp = &soc->groups[group];
+
+ grp->name = desc->name;
+ grp->pinmux = desc->pinmux;
+ grp->pins = group_pins;
+ grp->num_pins = desc->num_pins;
+ for (group_pin = 0;
+ group_pin < grp->num_pins;
+ group_pin++) {
+ u32 pinmux = grp->pinmux[group_pin];
+ unsigned int pin = AMBA_PINMUX_TO_PIN(pinmux);
+ unsigned int alt = AMBA_PINMUX_TO_ALT(pinmux);
+
+ if (pin >= soc->npins || alt > 7)
+ return dev_err_probe(&pdev->dev, -EINVAL,
+ "group %s has invalid pinmux %#x\n",
+ grp->name, pinmux);
+
+ grp->pins[group_pin] = pin;
+ }
+ group_pins += grp->num_pins;
+ }
+
+ raw_spin_lock_init(&soc->lock);
+
+ /* Mark all pins unavailable, then clear pins that exist. */
+ bitmap_fill(soc->used, AMBA_MAX_PINS);
+ for (i = 0; i < soc->bank_num; i++) {
+ unsigned int pin;
+
+ for (pin = soc->banks[i].pin_base;
+ pin < soc->banks[i].pin_base + soc->banks[i].gc.ngpio;
+ pin++)
+ clear_bit(pin, soc->used);
+ }
+
+ if (soc->data->clk_au_dedicated_pin >= gpio_pins &&
+ soc->data->clk_au_dedicated_pin < AMBA_MAX_PINS)
+ clear_bit(soc->data->clk_au_dedicated_pin, soc->used);
+
+ rval = amb_pinctrl_register(soc);
+ if (rval)
+ return dev_err_probe(&pdev->dev, rval, "pinctrl register failed!");
+
+ rval = amb_gpio_register(soc);
+ if (rval)
+ return dev_err_probe(&pdev->dev, rval, "gpio register failed!");
+
+ platform_set_drvdata(pdev, soc);
+ dev_info(&pdev->dev, "Ambarella pinctrl driver registered");
+
+ return 0;
+}
+
+static int amb_pinctrl_suspend_noirq(struct device *dev)
+{
+ struct amb_pinctrl_soc_data *soc = dev_get_drvdata(dev);
+ u32 bank, hw, i;
+
+ for (i = 0; i < soc->bank_num; i++) {
+ hw = soc->banks[i].hw_id;
+
+ regmap_read(soc->pull_regmap, soc->data->pull_en[hw], &soc->pm[i].pull[0]);
+ regmap_read(soc->pull_regmap, soc->data->pull_dir[hw], &soc->pm[i].pull[1]);
+
+ regmap_read(soc->ds_regmap, soc->data->ds0[hw], &soc->pm[i].ds[0]);
+ regmap_read(soc->ds_regmap, soc->data->ds1[hw], &soc->pm[i].ds[1]);
+ if (soc->data->have_ds2)
+ regmap_read(soc->ds_regmap, soc->data->ds2[hw], &soc->pm[i].ds[2]);
+
+ soc->pm[i].iomux[0] = readl_relaxed(soc->iomux_base + IOMUX_OFFSET(hw, 0));
+ soc->pm[i].iomux[1] = readl_relaxed(soc->iomux_base + IOMUX_OFFSET(hw, 1));
+ soc->pm[i].iomux[2] = readl_relaxed(soc->iomux_base + IOMUX_OFFSET(hw, 2));
+
+ soc->pm[i].afsel = readl_relaxed(soc->banks[i].base + GPIO_AFSEL_OFFSET);
+ soc->pm[i].dir = readl_relaxed(soc->banks[i].base + GPIO_DIR_OFFSET);
+ soc->pm[i].is = readl_relaxed(soc->banks[i].base + GPIO_IS_OFFSET);
+ soc->pm[i].ibe = readl_relaxed(soc->banks[i].base + GPIO_IBE_OFFSET);
+ soc->pm[i].iev = readl_relaxed(soc->banks[i].base + GPIO_IEV_OFFSET);
+ soc->pm[i].ie = readl_relaxed(soc->banks[i].base + GPIO_IE_OFFSET);
+ soc->pm[i].mask = readl_relaxed(soc->banks[i].base + GPIO_MASK_OFFSET);
+ writel_relaxed(0xffffffff, soc->banks[i].base + GPIO_MASK_OFFSET);
+ soc->pm[i].data = readl_relaxed(soc->banks[i].base + GPIO_DATA_OFFSET);
+
+ if (soc->banks[i].irq_wake_mask)
+ writel_relaxed(soc->banks[i].irq_wake_mask,
+ soc->banks[i].base + GPIO_IE_OFFSET);
+ }
+
+ if (soc->data->clk_au_dedicated_pin >= amb_gpio_pins_end(soc)) {
+ bank = PINID_TO_BANK(soc->data->clk_au_dedicated_pin);
+ soc->pm[bank].iomux[0] =
+ readl_relaxed(soc->iomux_base + IOMUX_OFFSET(bank, 0));
+ soc->pm[bank].iomux[1] =
+ readl_relaxed(soc->iomux_base + IOMUX_OFFSET(bank, 1));
+ soc->pm[bank].iomux[2] =
+ readl_relaxed(soc->iomux_base + IOMUX_OFFSET(bank, 2));
+ }
+
+ return 0;
+}
+
+static int amb_pinctrl_resume_noirq(struct device *dev)
+{
+ struct amb_pinctrl_soc_data *soc = dev_get_drvdata(dev);
+ u32 bank, hw, i;
+
+ for (i = 0; i < soc->bank_num; i++) {
+ hw = soc->banks[i].hw_id;
+
+ regmap_write(soc->pull_regmap, soc->data->pull_en[hw], soc->pm[i].pull[0]);
+ regmap_write(soc->pull_regmap, soc->data->pull_dir[hw], soc->pm[i].pull[1]);
+
+ regmap_write(soc->ds_regmap, soc->data->ds0[hw], soc->pm[i].ds[0]);
+ regmap_write(soc->ds_regmap, soc->data->ds1[hw], soc->pm[i].ds[1]);
+ if (soc->data->have_ds2)
+ regmap_write(soc->ds_regmap, soc->data->ds2[hw], soc->pm[i].ds[2]);
+
+ writel_relaxed(soc->pm[i].iomux[0], soc->iomux_base + IOMUX_OFFSET(hw, 0));
+ writel_relaxed(soc->pm[i].iomux[1], soc->iomux_base + IOMUX_OFFSET(hw, 1));
+ writel_relaxed(soc->pm[i].iomux[2], soc->iomux_base + IOMUX_OFFSET(hw, 2));
+
+ writel_relaxed(soc->pm[i].afsel, soc->banks[i].base + GPIO_AFSEL_OFFSET);
+ writel_relaxed(soc->pm[i].dir, soc->banks[i].base + GPIO_DIR_OFFSET);
+ /* Expose DATA writes while restoring the saved GPIO state. */
+ writel_relaxed(0xffffffff,
+ soc->banks[i].base + GPIO_MASK_OFFSET);
+ writel_relaxed(soc->pm[i].data,
+ soc->banks[i].base + GPIO_DATA_OFFSET);
+ /* Ensure DATA restore reaches hardware before restoring mask. */
+ wmb();
+ writel_relaxed(soc->pm[i].mask, soc->banks[i].base + GPIO_MASK_OFFSET);
+ writel_relaxed(soc->pm[i].is, soc->banks[i].base + GPIO_IS_OFFSET);
+ writel_relaxed(soc->pm[i].ibe, soc->banks[i].base + GPIO_IBE_OFFSET);
+ writel_relaxed(soc->pm[i].iev, soc->banks[i].base + GPIO_IEV_OFFSET);
+ writel_relaxed(soc->pm[i].ie, soc->banks[i].base + GPIO_IE_OFFSET);
+ writel_relaxed(0xffffffff, soc->banks[i].base + GPIO_ENABLE_OFFSET);
+ }
+
+ if (soc->data->clk_au_dedicated_pin >= amb_gpio_pins_end(soc)) {
+ bank = PINID_TO_BANK(soc->data->clk_au_dedicated_pin);
+ writel_relaxed(soc->pm[bank].iomux[0],
+ soc->iomux_base + IOMUX_OFFSET(bank, 0));
+ writel_relaxed(soc->pm[bank].iomux[1],
+ soc->iomux_base + IOMUX_OFFSET(bank, 1));
+ writel_relaxed(soc->pm[bank].iomux[2],
+ soc->iomux_base + IOMUX_OFFSET(bank, 2));
+ /* Ensure dedicated-pin iomux writes reach hardware before commit. */
+ wmb();
+ }
+
+ writel_relaxed(0x1, soc->iomux_base + IOMUX_CTRL_SET_OFFSET);
+ writel_relaxed(0x0, soc->iomux_base + IOMUX_CTRL_SET_OFFSET);
+
+ return 0;
+}
+
+static DEFINE_NOIRQ_DEV_PM_OPS(amb_pinctrl_pm_ops,
+ amb_pinctrl_suspend_noirq,
+ amb_pinctrl_resume_noirq);
+
+static const struct of_device_id amb_pinctrl_dt_match[] = {
+ {
+ .compatible = "ambarella,cv75-pinctrl",
+ .data = &ambarella_cv75_pinctrl_data,
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, amb_pinctrl_dt_match);
+
+static struct platform_driver amb_pinctrl_driver = {
+ .probe = amb_pinctrl_probe,
+ .driver = {
+ .name = "ambarella-pinctrl",
+ .of_match_table = of_match_ptr(amb_pinctrl_dt_match),
+ .pm = pm_sleep_ptr(&amb_pinctrl_pm_ops),
+ },
+};
+
+static int __init amb_pinctrl_drv_register(void)
+{
+ return platform_driver_register(&amb_pinctrl_driver);
+}
+arch_initcall(amb_pinctrl_drv_register);
+
+MODULE_AUTHOR("Cao Rongrong <rrcao@ambarella.com>");
+MODULE_DESCRIPTION("Ambarella SoC pinctrl driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/pinctrl/pinctrl-ambarella.h b/drivers/pinctrl/pinctrl-ambarella.h
new file mode 100644
index 000000000000..5fe776393ee3
--- /dev/null
+++ b/drivers/pinctrl/pinctrl-ambarella.h
@@ -0,0 +1,48 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Ambarella pinctrl common data definitions
+ *
+ * Copyright (C) 2012-2026, Ambarella, Inc.
+ */
+
+#ifndef _PINCTRL_AMBARELLA_H
+#define _PINCTRL_AMBARELLA_H
+
+#include <linux/types.h>
+
+#define AMBA_MAX_BANKS 8
+
+#define AMBA_PINMUX(pin, alt) (((alt) << 12) | (pin))
+#define AMBA_PINMUX_TO_PIN(mux) ((mux) & 0xfff)
+#define AMBA_PINMUX_TO_ALT(mux) (((mux) >> 12) & 0xf)
+
+struct ambpin_group_desc {
+ const char *name;
+ const u32 *pinmux;
+ unsigned int num_pins;
+};
+
+struct ambpin_function {
+ const char *name;
+ const char * const *groups;
+ unsigned int num_groups;
+};
+
+struct amb_pinctrl_data {
+ unsigned int ds0[AMBA_MAX_BANKS];
+ unsigned int ds1[AMBA_MAX_BANKS];
+ unsigned int ds2[AMBA_MAX_BANKS];
+ unsigned int pull_en[AMBA_MAX_BANKS];
+ unsigned int pull_dir[AMBA_MAX_BANKS];
+ bool have_ds2;
+ u32 hsm_domain_id;
+ u32 clk_au_dedicated_pin;
+ const struct ambpin_group_desc *groups;
+ unsigned int nr_groups;
+ const struct ambpin_function *functions;
+ unsigned int nr_functions;
+};
+
+extern const struct amb_pinctrl_data ambarella_cv75_pinctrl_data;
+
+#endif /* _PINCTRL_AMBARELLA_H */
--
2.34.1
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v2 08/10] serial: ambarella: add Ambarella UART driver
2026-08-06 9:34 [PATCH v2 00/10] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
` (6 preceding siblings ...)
2026-08-06 9:34 ` [PATCH v2 07/10] pinctrl: ambarella: add Ambarella pin controller Long Zhao via B4 Relay
@ 2026-08-06 9:34 ` Long Zhao via B4 Relay
2026-08-06 9:49 ` sashiko-bot
` (2 more replies)
2026-08-06 9:34 ` [PATCH v2 09/10] arm64: ambarella: add ARCH_AMBARELLA and CV75 EVK DT Long Zhao via B4 Relay
2026-08-06 9:34 ` [PATCH v2 10/10] MAINTAINERS: add ARM/AMBARELLA SoC support Long Zhao via B4 Relay
9 siblings, 3 replies; 27+ messages in thread
From: Long Zhao via B4 Relay @ 2026-08-06 9:34 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
Stephen Boyd, Linus Walleij, Greg Kroah-Hartman, Jiri Slaby,
Catalin Marinas, Will Deacon, Arnd Bergmann, Alexandre Belloni
Cc: devicetree, linux-arm-kernel, linux-kernel, linux-clk, linux-gpio,
linux-serial, soc, Long Zhao
From: Long Zhao <longzhao@ambarella.com>
Add an Ambarella UART driver with console support for early boot
bring-up on CV75. Keep udelay() in wait_for_tx(); it runs under
console/poll paths that may hold the port lock with IRQs disabled.
Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
drivers/tty/serial/Kconfig | 21 +
drivers/tty/serial/Makefile | 1 +
drivers/tty/serial/ambarella_uart.c | 1001 +++++++++++++++++++++++++++++++++++
3 files changed, 1023 insertions(+)
diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig
index cf7dba473b20..e3f5ec794c72 100644
--- a/drivers/tty/serial/Kconfig
+++ b/drivers/tty/serial/Kconfig
@@ -18,6 +18,27 @@ source "drivers/tty/serial/8250/Kconfig"
comment "Non-8250 serial port support"
+config SERIAL_AMBARELLA
+ bool "Ambarella UART support"
+ depends on ARCH_AMBARELLA || COMPILE_TEST
+ select SERIAL_CORE
+ help
+ Say Y here to enable the on-chip UART controller found on Ambarella
+ SoCs. The driver supports interrupt-driven transmit and receive
+ operation through the serial core. It can also provide a system
+ console when SERIAL_AMBARELLA_CONSOLE is enabled.
+
+config SERIAL_AMBARELLA_CONSOLE
+ bool "Console on Ambarella UART"
+ depends on SERIAL_AMBARELLA
+ select SERIAL_CORE_CONSOLE
+ select SERIAL_EARLYCON
+ help
+ Say Y here to use an Ambarella UART as the system console. This
+ enables both the regular serial console and earlycon support for
+ messages emitted before the full UART driver is initialized.
+ Select this option for early boot diagnostics on Ambarella systems.
+
config SERIAL_AMBA_PL010
tristate "ARM AMBA PL010 serial port support"
depends on ARM_AMBA || COMPILE_TEST
diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile
index bba7b21a4a1d..5c951719528b 100644
--- a/drivers/tty/serial/Makefile
+++ b/drivers/tty/serial/Makefile
@@ -26,6 +26,7 @@ obj-y += 8250/
obj-$(CONFIG_SERIAL_ALTERA_JTAGUART) += altera_jtaguart.o
obj-$(CONFIG_SERIAL_ALTERA_UART) += altera_uart.o
+obj-$(CONFIG_SERIAL_AMBARELLA) += ambarella_uart.o
obj-$(CONFIG_SERIAL_AMBA_PL010) += amba-pl010.o
obj-$(CONFIG_SERIAL_AMBA_PL011) += amba-pl011.o
obj-$(CONFIG_SERIAL_GRLIB_GAISLER_APBUART) += apbuart.o
diff --git a/drivers/tty/serial/ambarella_uart.c b/drivers/tty/serial/ambarella_uart.c
new file mode 100644
index 000000000000..7356b242f0ef
--- /dev/null
+++ b/drivers/tty/serial/ambarella_uart.c
@@ -0,0 +1,1001 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <linux/clk.h>
+#include <linux/console.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/serial_reg.h>
+#include <linux/serial_core.h>
+#include <linux/sysrq.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+
+#define UART_RB_OFFSET 0x00
+#define UART_TH_OFFSET 0x00
+#define UART_DLL_OFFSET 0x00
+#define UART_IE_OFFSET 0x04
+#define UART_DLH_OFFSET 0x04
+#define UART_II_OFFSET 0x08
+#define UART_FC_OFFSET 0x08
+#define UART_LC_OFFSET 0x0c
+#define UART_MC_OFFSET 0x10
+#define UART_LS_OFFSET 0x14
+#define UART_MS_OFFSET 0x18
+#define UART_US_OFFSET 0x7c
+#define UART_SRR_OFFSET 0x88
+
+#define UART_IE_ERETOI 0x40
+#define UART_IE_ETOI 0x20
+#define UART_IE_EDSSI 0x08
+#define UART_IE_ELSI 0x04
+#define UART_IE_ETBEI 0x02
+#define UART_IE_ERBFI 0x01
+
+#define UART_II_MODEM_STATUS_CHANGED 0x00
+#define UART_II_NO_INT_PENDING 0x01
+#define UART_II_THR_EMPTY 0x02
+#define UART_II_RCV_DATA_AVAIL 0x04
+#define UART_II_RCV_STATUS 0x06
+#define UART_II_CHAR_TIMEOUT 0x0c
+#define UART_II_CHAR_TIMEOUT_FIFO_EMPTY 0x0d
+
+#define UART_FC_RX_2_TO_FULL 0xc0
+#define UART_FC_TX_EMPTY 0x00
+#define UART_FC_XMITR 0x04
+#define UART_FC_RCVRR 0x02
+#define UART_FC_FIFOE 0x01
+
+#define UART_LC_DLAB 0x80
+#define UART_LC_BRK 0x40
+#define UART_LC_EVEN_PARITY 0x10
+#define UART_LC_ODD_PARITY 0x00
+#define UART_LC_PEN 0x08
+#define UART_LC_STOP_2BIT 0x04
+#define UART_LC_STOP_1BIT 0x00
+#define UART_LC_CLS_8_BITS 0x03
+#define UART_LC_CLS_7_BITS 0x02
+#define UART_LC_CLS_6_BITS 0x01
+#define UART_LC_CLS_5_BITS 0x00
+
+#define UART_MC_AFCE 0x20
+#define UART_MC_LB 0x10
+#define UART_MC_OUT2 0x08
+#define UART_MC_OUT1 0x04
+#define UART_MC_RTS 0x02
+#define UART_MC_DTR 0x01
+
+#define UART_LS_TEMT 0x40
+#define UART_LS_THRE 0x20
+#define UART_LS_BI 0x10
+#define UART_LS_FE 0x08
+#define UART_LS_PE 0x04
+#define UART_LS_OE 0x02
+#define UART_LS_DR 0x01
+
+#define UART_MS_DCD 0x80
+#define UART_MS_RI 0x40
+#define UART_MS_DSR 0x20
+#define UART_MS_CTS 0x10
+#define UART_MS_DDCD 0x08
+#define UART_MS_DCTS 0x01
+
+#define UART_US_TFNF 0x02
+
+#define UART_FIFO_SIZE 64
+
+#define DEFAULT_AMBARELLA_UART_MCR 0
+#define DEFAULT_AMBARELLA_UART_IER (UART_IE_ELSI | UART_IE_ERBFI | \
+ UART_IE_ETOI)
+
+#define AMBA_UART_MAX_NUM 8
+
+#define AMBA_UART_RESET_FLAG 0 /* bit 0 */
+
+/* Poll timeout in microseconds (atomic helpers use udelay). */
+#define AMBARELLA_UART_TIMEOUT_US 1000000
+
+struct ambarella_uart_port {
+ struct uart_port port;
+ struct clk *uart_pll;
+ unsigned long flags;
+ u32 mcr;
+ /* Software copy of UART_IE, updated with the port lock held. */
+ u32 ier;
+ bool console_line_ended;
+};
+
+static struct ambarella_uart_port ambarella_port[AMBA_UART_MAX_NUM];
+
+static struct ambarella_uart_port *
+to_ambarella_uart_port(struct uart_port *port)
+{
+ return container_of(port, struct ambarella_uart_port, port);
+}
+
+static void serial_ambarella_ier_write(struct uart_port *port, u32 ier)
+{
+ struct ambarella_uart_port *amb_port = to_ambarella_uart_port(port);
+
+ amb_port->ier = ier;
+ writel_relaxed(ier, port->membase + UART_IE_OFFSET);
+}
+
+static void serial_ambarella_ier_set(struct uart_port *port, u32 set)
+{
+ struct ambarella_uart_port *amb_port = to_ambarella_uart_port(port);
+
+ amb_port->ier |= set;
+ writel_relaxed(amb_port->ier, port->membase + UART_IE_OFFSET);
+}
+
+static void serial_ambarella_ier_clear(struct uart_port *port, u32 clear)
+{
+ struct ambarella_uart_port *amb_port = to_ambarella_uart_port(port);
+
+ amb_port->ier &= ~clear;
+ writel_relaxed(amb_port->ier, port->membase + UART_IE_OFFSET);
+}
+
+static void serial_ambarella_ier_toggle(struct uart_port *port, u32 mask)
+{
+ struct ambarella_uart_port *amb_port = to_ambarella_uart_port(port);
+
+ amb_port->ier &= ~mask;
+ writel_relaxed(amb_port->ier, port->membase + UART_IE_OFFSET);
+ amb_port->ier |= mask;
+ writel_relaxed(amb_port->ier, port->membase + UART_IE_OFFSET);
+}
+
+static void __serial_ambarella_stop_tx(struct uart_port *port)
+{
+ serial_ambarella_ier_clear(port, UART_IE_ETBEI);
+}
+
+static u32 __serial_ambarella_read_ms(struct uart_port *port)
+{
+ return readl_relaxed(port->membase + UART_MS_OFFSET);
+}
+
+static void __serial_ambarella_enable_ms(struct uart_port *port)
+{
+ serial_ambarella_ier_set(port, UART_IE_EDSSI);
+}
+
+static void __serial_ambarella_disable_ms(struct uart_port *port)
+{
+ serial_ambarella_ier_clear(port, UART_IE_EDSSI);
+}
+
+static inline void wait_for_tx(struct uart_port *port)
+{
+ u32 ls;
+ int ret;
+
+ ret = readl_poll_timeout_atomic(port->membase + UART_LS_OFFSET, ls,
+ ls & UART_LS_TEMT, 1,
+ AMBARELLA_UART_TIMEOUT_US);
+ if (likely(!ret))
+ return;
+
+ /* Recover a stuck TX path so console/poll can continue. */
+ writel_relaxed(UART_FC_RX_2_TO_FULL | UART_FC_TX_EMPTY |
+ UART_FC_XMITR | UART_FC_RCVRR,
+ port->membase + UART_FC_OFFSET);
+ udelay(100);
+ writel_relaxed(UART_FC_FIFOE | UART_FC_RX_2_TO_FULL |
+ UART_FC_TX_EMPTY | UART_FC_XMITR |
+ UART_FC_RCVRR,
+ port->membase + UART_FC_OFFSET);
+}
+
+static inline int wait_for_rx(struct uart_port *port)
+{
+ u32 ls;
+
+ return readl_relaxed_poll_timeout_atomic(port->membase + UART_LS_OFFSET,
+ ls, ls & UART_LS_DR, 1,
+ AMBARELLA_UART_TIMEOUT_US);
+}
+
+static inline int tx_fifo_is_full(struct uart_port *port)
+{
+ return !(readl_relaxed(port->membase + UART_US_OFFSET) & UART_US_TFNF);
+}
+
+static void serial_ambarella_hw_setup(struct uart_port *port)
+{
+ struct ambarella_uart_port *amb_port = to_ambarella_uart_port(port);
+
+ if (!test_and_set_bit(AMBA_UART_RESET_FLAG, &amb_port->flags)) {
+ if (amb_port->uart_pll)
+ port->uartclk = clk_get_rate(amb_port->uart_pll);
+ /* reset the whole UART only once */
+ writel_relaxed(0x01, port->membase + UART_SRR_OFFSET);
+ mdelay(1);
+ writel_relaxed(0x00, port->membase + UART_SRR_OFFSET);
+ }
+
+ writel_relaxed(UART_FC_FIFOE | UART_FC_RX_2_TO_FULL | UART_FC_TX_EMPTY |
+ UART_FC_XMITR | UART_FC_RCVRR, port->membase + UART_FC_OFFSET);
+ /* Keep interrupts disabled until the IRQ handler is registered. */
+ serial_ambarella_ier_write(port, 0);
+}
+
+static inline void serial_ambarella_receive_chars(struct uart_port *port,
+ u32 tmo)
+{
+ u32 ch, flag, ls;
+ bool have_char;
+ int max_count;
+
+ ls = readl_relaxed(port->membase + UART_LS_OFFSET);
+ max_count = port->fifosize;
+
+ do {
+ flag = TTY_NORMAL;
+ have_char = ls & UART_LS_DR;
+ if (have_char || (ls & UART_LS_BI) || tmo)
+ ch = readl_relaxed(port->membase + UART_RB_OFFSET);
+ if (have_char) {
+ port->icount.rx++;
+ tmo = 0;
+ }
+
+ if (unlikely(ls & (UART_LS_BI | UART_LS_PE |
+ UART_LS_FE | UART_LS_OE))) {
+ if (ls & UART_LS_BI) {
+ ls &= ~(UART_LS_FE | UART_LS_PE);
+ port->icount.brk++;
+
+ if (uart_handle_break(port))
+ goto ignore_char;
+ }
+ if (ls & UART_LS_FE)
+ port->icount.frame++;
+ if (ls & UART_LS_PE)
+ port->icount.parity++;
+ if (ls & UART_LS_OE)
+ port->icount.overrun++;
+
+ ls &= port->read_status_mask;
+
+ if (ls & UART_LS_BI)
+ flag = TTY_BREAK;
+ else if (ls & UART_LS_FE)
+ flag = TTY_FRAME;
+ else if (ls & UART_LS_PE)
+ flag = TTY_PARITY;
+ else if (ls & UART_LS_OE)
+ flag = TTY_OVERRUN;
+
+ if (ls & UART_LS_OE)
+ pr_debug("%s: OVERFLOW\n", __func__);
+ }
+
+ if (have_char) {
+ if (uart_handle_sysrq_char(port, ch))
+ goto ignore_char;
+
+ uart_insert_char(port, ls, UART_LS_OE, ch, flag);
+ }
+
+ignore_char:
+ ls = readl_relaxed(port->membase + UART_LS_OFFSET);
+ } while ((ls & (UART_LS_DR | UART_LS_BI)) && (max_count-- > 0));
+
+ tty_flip_buffer_push(&port->state->port);
+}
+
+static void serial_ambarella_transmit_chars(struct uart_port *port)
+{
+ struct tty_port *tport = &port->state->port;
+ int count;
+
+ if (port->x_char) {
+ writel_relaxed(port->x_char, port->membase + UART_TH_OFFSET);
+ port->icount.tx++;
+ port->x_char = 0;
+ return;
+ }
+
+ if (uart_tx_stopped(port) || kfifo_is_empty(&tport->xmit_fifo)) {
+ __serial_ambarella_stop_tx(port);
+ return;
+ }
+
+ count = port->fifosize;
+ while (count-- > 0) {
+ unsigned char c;
+
+ if (tx_fifo_is_full(port))
+ break;
+
+ if (!kfifo_peek(&tport->xmit_fifo, &c))
+ break;
+
+ writel_relaxed(c, port->membase + UART_TH_OFFSET);
+ kfifo_skip(&tport->xmit_fifo);
+ port->icount.tx++;
+ if (kfifo_is_empty(&tport->xmit_fifo))
+ break;
+ }
+
+ if (kfifo_len(&tport->xmit_fifo) < WAKEUP_CHARS)
+ uart_write_wakeup(port);
+ if (kfifo_is_empty(&tport->xmit_fifo))
+ __serial_ambarella_stop_tx(port);
+}
+
+static inline void serial_ambarella_check_modem_status(struct uart_port *port)
+{
+ u32 ms;
+
+ ms = __serial_ambarella_read_ms(port);
+
+ if (ms & UART_MS_RI)
+ port->icount.rng++;
+ if (ms & UART_MS_DSR)
+ port->icount.dsr++;
+ if (ms & UART_MS_DCTS)
+ uart_handle_cts_change(port, (ms & UART_MS_CTS));
+ if (ms & UART_MS_DDCD)
+ uart_handle_dcd_change(port, (ms & UART_MS_DCD));
+
+ wake_up_interruptible(&port->state->port.delta_msr_wait);
+}
+
+static irqreturn_t serial_ambarella_irq(int irq, void *dev_id)
+{
+ struct uart_port *port = dev_id;
+ u32 ii;
+
+ scoped_guard(uart_port_lock_irqsave, port) {
+ ii = readl_relaxed(port->membase + UART_II_OFFSET);
+ switch (ii & 0x0F) {
+ case UART_II_MODEM_STATUS_CHANGED:
+ serial_ambarella_check_modem_status(port);
+ break;
+ case UART_II_THR_EMPTY:
+ serial_ambarella_transmit_chars(port);
+ break;
+ case UART_II_RCV_STATUS:
+ case UART_II_RCV_DATA_AVAIL:
+ serial_ambarella_receive_chars(port, 0);
+ break;
+ case UART_II_CHAR_TIMEOUT_FIFO_EMPTY:
+ /* Clear ERETOI to dismiss timeout-with-empty-FIFO IRQ */
+ serial_ambarella_ier_toggle(port, UART_IE_ERETOI);
+ fallthrough;
+ case UART_II_CHAR_TIMEOUT:
+ serial_ambarella_receive_chars(port, 1);
+ break;
+ case UART_II_NO_INT_PENDING:
+ break;
+ default:
+ pr_debug("%s: 0x%x\n", __func__, ii);
+ break;
+ }
+ }
+
+ return IRQ_HANDLED;
+}
+
+static void serial_ambarella_enable_ms(struct uart_port *port)
+{
+ __serial_ambarella_enable_ms(port);
+}
+
+static void serial_ambarella_start_tx(struct uart_port *port)
+{
+ /* if transmit buffer is not allocated, just return */
+ if (!port->state->port.xmit_buf)
+ return;
+
+ serial_ambarella_ier_set(port, UART_IE_ETBEI);
+ serial_ambarella_transmit_chars(port);
+}
+
+static void serial_ambarella_stop_tx(struct uart_port *port)
+{
+ __serial_ambarella_stop_tx(port);
+}
+
+static void serial_ambarella_stop_rx(struct uart_port *port)
+{
+ serial_ambarella_ier_clear(port, UART_IE_ERBFI);
+}
+
+static unsigned int serial_ambarella_tx_empty(struct uart_port *port)
+{
+ unsigned int lsr;
+
+ guard(uart_port_lock_irqsave)(port);
+ lsr = readl_relaxed(port->membase + UART_LS_OFFSET);
+
+ return ((lsr & (UART_LS_TEMT | UART_LS_THRE)) ==
+ (UART_LS_TEMT | UART_LS_THRE)) ? TIOCSER_TEMT : 0;
+}
+
+static unsigned int serial_ambarella_get_mctrl(struct uart_port *port)
+{
+ u32 ms, mctrl = 0;
+
+ ms = __serial_ambarella_read_ms(port);
+
+ if (ms & UART_MS_CTS)
+ mctrl |= TIOCM_CTS;
+ if (ms & UART_MS_DSR)
+ mctrl |= TIOCM_DSR;
+ if (ms & UART_MS_RI)
+ mctrl |= TIOCM_RI;
+ if (ms & UART_MS_DCD)
+ mctrl |= TIOCM_CD;
+
+ return mctrl;
+}
+
+static void serial_ambarella_set_mctrl(struct uart_port *port,
+ unsigned int mctrl)
+{
+ struct ambarella_uart_port *amb_port = to_ambarella_uart_port(port);
+ u32 mcr, mcr_new = 0;
+
+ mcr = readl_relaxed(port->membase + UART_MC_OFFSET);
+
+ if (mctrl & TIOCM_DTR)
+ mcr_new |= UART_MC_DTR;
+ if (mctrl & TIOCM_RTS)
+ mcr_new |= UART_MC_RTS;
+ if (mctrl & TIOCM_OUT1)
+ mcr_new |= UART_MC_OUT1;
+ if (mctrl & TIOCM_OUT2)
+ mcr_new |= UART_MC_OUT2;
+ if (mctrl & TIOCM_LOOP)
+ mcr_new |= UART_MC_LB;
+
+ mcr_new |= amb_port->mcr;
+ if (mcr_new != mcr) {
+ if ((mcr & UART_MC_AFCE) == UART_MC_AFCE) {
+ mcr &= ~UART_MC_AFCE;
+ writel_relaxed(mcr, port->membase + UART_MC_OFFSET);
+ }
+ writel_relaxed(mcr_new, port->membase + UART_MC_OFFSET);
+ }
+}
+
+static void serial_ambarella_break_ctl(struct uart_port *port, int break_state)
+{
+ u32 lcr;
+
+ guard(uart_port_lock_irqsave)(port);
+ lcr = readl_relaxed(port->membase + UART_LC_OFFSET);
+ if (break_state != 0)
+ writel_relaxed(lcr | UART_LC_BRK, port->membase + UART_LC_OFFSET);
+ else
+ writel_relaxed(lcr & ~UART_LC_BRK, port->membase + UART_LC_OFFSET);
+}
+
+static void serial_ambarella_hw_deinit(struct ambarella_uart_port *amb_port)
+{
+ struct uart_port *port = &amb_port->port;
+
+ /* Disable interrupts */
+ serial_ambarella_ier_write(port, 0);
+
+ /* Reset the Rx and Tx FIFOs */
+ writel_relaxed(UART_FCR_CLEAR_XMIT | UART_FCR_CLEAR_RCVR,
+ port->membase + UART_SRR_OFFSET);
+}
+
+static int serial_ambarella_startup(struct uart_port *port)
+{
+ int rval;
+ struct ambarella_uart_port *amb_port = to_ambarella_uart_port(port);
+
+ serial_ambarella_hw_setup(port);
+
+ rval = request_irq(port->irq, serial_ambarella_irq, IRQF_TRIGGER_HIGH,
+ dev_name(amb_port->port.dev), &amb_port->port);
+ if (rval < 0) {
+ dev_err(amb_port->port.dev,
+ "Failed to register ISR for IRQ %d\n", port->irq);
+ serial_ambarella_hw_deinit(amb_port);
+ return rval;
+ }
+
+ serial_ambarella_ier_write(port, DEFAULT_AMBARELLA_UART_IER);
+
+ return 0;
+}
+
+static void serial_ambarella_shutdown(struct uart_port *port)
+{
+ struct ambarella_uart_port *amb_port = to_ambarella_uart_port(port);
+ u32 lcr;
+
+ scoped_guard(uart_port_lock_irqsave, port) {
+ serial_ambarella_hw_deinit(amb_port);
+ lcr = readl_relaxed(port->membase + UART_LC_OFFSET);
+ writel_relaxed(lcr & ~UART_LC_BRK, port->membase + UART_LC_OFFSET);
+ }
+
+ free_irq(amb_port->port.irq, &amb_port->port);
+}
+
+static void serial_ambarella_set_termios(struct uart_port *port,
+ struct ktermios *termios,
+ const struct ktermios *old)
+{
+ struct ambarella_uart_port *amb_port = to_ambarella_uart_port(port);
+ unsigned int baud, quot;
+ u32 lc = 0x0;
+
+ port->uartclk = clk_get_rate(amb_port->uart_pll);
+ switch (termios->c_cflag & CSIZE) {
+ case CS5:
+ lc |= UART_LC_CLS_5_BITS;
+ break;
+ case CS6:
+ lc |= UART_LC_CLS_6_BITS;
+ break;
+ case CS7:
+ lc |= UART_LC_CLS_7_BITS;
+ break;
+ case CS8:
+ default:
+ lc |= UART_LC_CLS_8_BITS;
+ break;
+ }
+
+ if (termios->c_cflag & CSTOPB)
+ lc |= UART_LC_STOP_2BIT;
+ else
+ lc |= UART_LC_STOP_1BIT;
+
+ if (termios->c_cflag & PARENB) {
+ if (termios->c_cflag & PARODD)
+ lc |= (UART_LC_PEN | UART_LC_ODD_PARITY);
+ else
+ lc |= (UART_LC_PEN | UART_LC_EVEN_PARITY);
+ }
+
+ baud = uart_get_baud_rate(port, termios, old, 0, port->uartclk / 16);
+ quot = uart_get_divisor(port, baud);
+
+ scoped_guard(uart_port_lock_irqsave, port) {
+ uart_update_timeout(port, termios->c_cflag, baud);
+
+ port->read_status_mask = UART_LSR_OE | UART_LSR_THRE | UART_LSR_DR;
+ if (termios->c_iflag & INPCK)
+ port->read_status_mask |= UART_LSR_FE | UART_LSR_PE;
+ if (termios->c_iflag & (BRKINT | PARMRK))
+ port->read_status_mask |= UART_LSR_BI;
+
+ port->ignore_status_mask = 0;
+ if (termios->c_iflag & IGNPAR)
+ port->ignore_status_mask |= UART_LSR_PE | UART_LSR_FE;
+ if (termios->c_iflag & IGNBRK) {
+ port->ignore_status_mask |= UART_LSR_BI;
+ if (termios->c_iflag & IGNPAR)
+ port->ignore_status_mask |= UART_LSR_OE;
+ }
+ if ((termios->c_cflag & CREAD) == 0)
+ port->ignore_status_mask |= UART_LSR_DR;
+
+ if ((termios->c_cflag & CRTSCTS) == 0) {
+ amb_port->mcr &= ~UART_MC_AFCE;
+ port->status &= ~UPSTAT_AUTOCTS;
+ } else {
+ amb_port->mcr |= UART_MC_AFCE;
+ port->status |= UPSTAT_AUTOCTS;
+ }
+
+ writel_relaxed(UART_LC_DLAB, port->membase + UART_LC_OFFSET);
+ writel_relaxed(quot & 0xff, port->membase + UART_DLL_OFFSET);
+ writel_relaxed((quot >> 8) & 0xff, port->membase + UART_DLH_OFFSET);
+ writel_relaxed(lc, port->membase + UART_LC_OFFSET);
+ if (UART_ENABLE_MS(port, termios->c_cflag))
+ __serial_ambarella_enable_ms(port);
+ else
+ __serial_ambarella_disable_ms(port);
+ serial_ambarella_set_mctrl(port, port->mctrl);
+ }
+}
+
+static void serial_ambarella_pm(struct uart_port *port,
+ unsigned int state, unsigned int oldstate)
+{
+}
+
+static void serial_ambarella_release_port(struct uart_port *port)
+{
+}
+
+static int serial_ambarella_request_port(struct uart_port *port)
+{
+ return 0;
+}
+
+static void serial_ambarella_config_port(struct uart_port *port, int flags)
+{
+}
+
+static int serial_ambarella_verify_port(struct uart_port *port,
+ struct serial_struct *ser)
+{
+ int rval = 0;
+
+ if (ser->type != PORT_UNKNOWN && ser->type != PORT_UART00)
+ rval = -EINVAL;
+ if (port->irq != ser->irq)
+ rval = -EINVAL;
+ if (ser->io_type != SERIAL_IO_MEM)
+ rval = -EINVAL;
+
+ return rval;
+}
+
+static const char *serial_ambarella_type(struct uart_port *port)
+{
+ return "ambuart";
+}
+
+#ifdef CONFIG_CONSOLE_POLL
+static void serial_ambarella_poll_put_char(struct uart_port *port,
+ unsigned char chr)
+{
+ if (!port->suspended) {
+ wait_for_tx(port);
+ writel_relaxed(chr, port->membase + UART_TH_OFFSET);
+ }
+}
+
+static int serial_ambarella_poll_get_char(struct uart_port *port)
+{
+ if (port->suspended)
+ return NO_POLL_CHAR;
+
+ if (wait_for_rx(port))
+ return NO_POLL_CHAR;
+
+ return readl_relaxed(port->membase + UART_RB_OFFSET);
+}
+#endif
+
+static const struct uart_ops serial_ambarella_pops = {
+ .tx_empty = serial_ambarella_tx_empty,
+ .set_mctrl = serial_ambarella_set_mctrl,
+ .get_mctrl = serial_ambarella_get_mctrl,
+ .stop_tx = serial_ambarella_stop_tx,
+ .start_tx = serial_ambarella_start_tx,
+ .stop_rx = serial_ambarella_stop_rx,
+ .enable_ms = serial_ambarella_enable_ms,
+ .break_ctl = serial_ambarella_break_ctl,
+ .startup = serial_ambarella_startup,
+ .shutdown = serial_ambarella_shutdown,
+ .set_termios = serial_ambarella_set_termios,
+ .pm = serial_ambarella_pm,
+ .type = serial_ambarella_type,
+ .release_port = serial_ambarella_release_port,
+ .request_port = serial_ambarella_request_port,
+ .config_port = serial_ambarella_config_port,
+ .verify_port = serial_ambarella_verify_port,
+#ifdef CONFIG_CONSOLE_POLL
+ .poll_put_char = serial_ambarella_poll_put_char,
+ .poll_get_char = serial_ambarella_poll_get_char,
+#endif
+};
+
+#if defined(CONFIG_SERIAL_AMBARELLA_CONSOLE)
+
+static struct uart_driver serial_ambarella_reg;
+
+static void serial_ambarella_putchar(struct uart_port *port, unsigned char ch)
+{
+ wait_for_tx(port);
+ writel_relaxed(ch, port->membase + UART_TH_OFFSET);
+}
+
+static void serial_ambarella_console_putchar(struct uart_port *port,
+ unsigned char ch)
+{
+ struct ambarella_uart_port *amb_port = to_ambarella_uart_port(port);
+
+ serial_ambarella_putchar(port, ch);
+ amb_port->console_line_ended = (ch == '\n');
+}
+
+static void serial_ambarella_console_device_lock(struct console *co,
+ unsigned long *flags)
+{
+ __uart_port_lock_irqsave(&ambarella_port[co->index].port, flags);
+}
+
+static void serial_ambarella_console_device_unlock(struct console *co,
+ unsigned long flags)
+{
+ __uart_port_unlock_irqrestore(&ambarella_port[co->index].port, flags);
+}
+
+static void serial_ambarella_console_write_atomic(struct console *co,
+ struct nbcon_write_context *wctxt)
+{
+ struct ambarella_uart_port *amb_port = &ambarella_port[co->index];
+ struct uart_port *port = &amb_port->port;
+
+ if (port->suspended)
+ return;
+
+ if (!nbcon_enter_unsafe(wctxt))
+ return;
+
+ if (!amb_port->console_line_ended)
+ uart_console_write(port, "\n", 1, serial_ambarella_console_putchar);
+ uart_console_write(port, wctxt->outbuf, wctxt->len,
+ serial_ambarella_console_putchar);
+ wait_for_tx(port);
+
+ nbcon_exit_unsafe(wctxt);
+}
+
+static void serial_ambarella_console_write_thread(struct console *co,
+ struct nbcon_write_context *wctxt)
+{
+ struct ambarella_uart_port *amb_port = &ambarella_port[co->index];
+ struct uart_port *port = &amb_port->port;
+
+ if (port->suspended)
+ return;
+
+ if (!nbcon_enter_unsafe(wctxt))
+ return;
+
+ if (nbcon_exit_unsafe(wctxt)) {
+ unsigned int len = READ_ONCE(wctxt->len);
+ unsigned int i;
+
+ /*
+ * Toggle unsafe per byte so a higher-priority context can
+ * take over. After a failed enter/exit, outbuf/len are no
+ * longer trusted and printing must stop.
+ */
+ for (i = 0; i < len; i++) {
+ if (!nbcon_enter_unsafe(wctxt))
+ break;
+ uart_console_write(port, wctxt->outbuf + i, 1,
+ serial_ambarella_console_putchar);
+ if (!nbcon_exit_unsafe(wctxt))
+ break;
+ }
+ }
+
+ while (!nbcon_enter_unsafe(wctxt))
+ nbcon_reacquire_nobuf(wctxt);
+
+ wait_for_tx(port);
+ nbcon_exit_unsafe(wctxt);
+}
+
+static int __init serial_ambarella_console_setup(struct console *co,
+ char *options)
+{
+ struct uart_port *port;
+ struct ambarella_uart_port *amb_port;
+ int baud = 115200, bits = 8, parity = 'n', flow = 'n';
+
+ if (co->index < 0 || co->index >= serial_ambarella_reg.nr)
+ co->index = 0;
+
+ amb_port = &ambarella_port[co->index];
+ port = &amb_port->port;
+ if (!port->membase) {
+ pr_err("No device available for serial console\n");
+ return -ENODEV;
+ }
+
+ port->ops = &serial_ambarella_pops;
+ port->line = co->index;
+ amb_port->console_line_ended = true;
+
+ serial_ambarella_hw_setup(port);
+
+ if (options)
+ uart_parse_options(options, &baud, &parity, &bits, &flow);
+
+ return uart_set_options(port, co, baud, parity, bits, flow);
+}
+
+static struct console serial_ambarella_console = {
+ .name = "ttyAB",
+ .device = uart_console_device,
+ .setup = serial_ambarella_console_setup,
+ .write_atomic = serial_ambarella_console_write_atomic,
+ .write_thread = serial_ambarella_console_write_thread,
+ .device_lock = serial_ambarella_console_device_lock,
+ .device_unlock = serial_ambarella_console_device_unlock,
+ .flags = CON_PRINTBUFFER | CON_ANYTIME | CON_NBCON,
+ .index = -1,
+ .data = &serial_ambarella_reg,
+};
+
+static void serial_ambarella_console_early_write(struct console *con,
+ const char *s,
+ unsigned int count)
+{
+ struct earlycon_device *dev = con->data;
+
+ uart_console_write(&dev->port, s, count, serial_ambarella_putchar);
+}
+
+static int __init serial_ambarella_console_early_setup(struct earlycon_device *dev,
+ const char *opt)
+{
+ if (!dev->port.membase)
+ return -ENODEV;
+
+ dev->con->write = serial_ambarella_console_early_write;
+
+ return 0;
+}
+
+OF_EARLYCON_DECLARE(ambarella_uart, "ambarella,cv75-uart",
+ serial_ambarella_console_early_setup);
+
+#define AMBARELLA_CONSOLE (&serial_ambarella_console)
+#else
+#define AMBARELLA_CONSOLE NULL
+#endif
+
+static struct uart_driver serial_ambarella_reg = {
+ .owner = THIS_MODULE,
+ .driver_name = "ambarella-uart",
+ .dev_name = "ttyAB",
+ .major = 0,
+ .minor = 0,
+ .nr = AMBA_UART_MAX_NUM,
+ .cons = AMBARELLA_CONSOLE,
+};
+
+static int serial_ambarella_probe(struct platform_device *pdev)
+{
+ struct ambarella_uart_port *amb_port;
+ struct resource *mem;
+ struct pinctrl *pinctrl;
+ int irq, id, rval;
+
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!mem) {
+ dev_err(&pdev->dev, "no mem resource!\n");
+ return -ENODEV;
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0) {
+ dev_err(&pdev->dev, "no irq resource!\n");
+ return -ENODEV;
+ }
+
+ id = of_alias_get_id(pdev->dev.of_node, "serial");
+ if (id < 0 || id >= serial_ambarella_reg.nr) {
+ dev_err(&pdev->dev, "Invalid uart ID %d!\n", id);
+ return -ENXIO;
+ }
+
+ pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
+ if (IS_ERR(pinctrl)) {
+ dev_err(&pdev->dev, "Failed to request pinctrl\n");
+ return PTR_ERR(pinctrl);
+ }
+
+ amb_port = &ambarella_port[id];
+
+ amb_port->uart_pll = devm_clk_get_enabled(&pdev->dev, NULL);
+ if (IS_ERR(amb_port->uart_pll)) {
+ dev_err(&pdev->dev, "Get uart clk failed!\n");
+ return PTR_ERR(amb_port->uart_pll);
+ }
+
+ amb_port->mcr = DEFAULT_AMBARELLA_UART_MCR;
+
+ amb_port->port.dev = &pdev->dev;
+ amb_port->port.type = PORT_UART00;
+ amb_port->port.iotype = UPIO_MEM;
+ amb_port->port.fifosize = UART_FIFO_SIZE;
+ amb_port->port.uartclk = clk_get_rate(amb_port->uart_pll);
+ amb_port->port.ops = &serial_ambarella_pops;
+ amb_port->port.irq = irq;
+ amb_port->port.line = id;
+ amb_port->port.mapbase = mem->start;
+ amb_port->port.membase = devm_ioremap_resource(&pdev->dev, mem);
+ if (IS_ERR(amb_port->port.membase))
+ return PTR_ERR(amb_port->port.membase);
+
+ rval = uart_add_one_port(&serial_ambarella_reg, &amb_port->port);
+ if (rval < 0)
+ dev_err(&pdev->dev, "failed to add port: %d, %d!\n", id, rval);
+
+ platform_set_drvdata(pdev, amb_port);
+
+ return rval;
+}
+
+static void serial_ambarella_remove(struct platform_device *pdev)
+{
+ struct ambarella_uart_port *amb_port;
+
+ amb_port = platform_get_drvdata(pdev);
+ uart_remove_one_port(&serial_ambarella_reg, &amb_port->port);
+}
+
+static int serial_ambarella_suspend(struct device *dev)
+{
+ struct ambarella_uart_port *amb_port = dev_get_drvdata(dev);
+
+ return uart_suspend_port(&serial_ambarella_reg, &amb_port->port);
+}
+
+static int serial_ambarella_resume(struct device *dev)
+{
+ struct ambarella_uart_port *amb_port = dev_get_drvdata(dev);
+
+ clear_bit(AMBA_UART_RESET_FLAG, &amb_port->flags);
+ serial_ambarella_hw_setup(&amb_port->port);
+
+ return uart_resume_port(&serial_ambarella_reg, &amb_port->port);
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(serial_ambarella_pm_ops,
+ serial_ambarella_suspend,
+ serial_ambarella_resume);
+
+static const struct of_device_id ambarella_serial_of_match[] = {
+ { .compatible = "ambarella,cv75-uart" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, ambarella_serial_of_match);
+
+static struct platform_driver serial_ambarella_driver = {
+ .probe = serial_ambarella_probe,
+ .remove = serial_ambarella_remove,
+ .driver = {
+ .name = "ambarella-uart",
+ .of_match_table = ambarella_serial_of_match,
+ .pm = pm_sleep_ptr(&serial_ambarella_pm_ops),
+ },
+};
+
+static int __init serial_ambarella_init(void)
+{
+ int rval;
+
+ rval = uart_register_driver(&serial_ambarella_reg);
+ if (rval < 0)
+ return rval;
+
+ rval = platform_driver_register(&serial_ambarella_driver);
+ if (rval < 0) {
+ uart_unregister_driver(&serial_ambarella_reg);
+ return rval;
+ }
+
+ return 0;
+}
+
+static void __exit serial_ambarella_exit(void)
+{
+ platform_driver_unregister(&serial_ambarella_driver);
+ uart_unregister_driver(&serial_ambarella_reg);
+}
+
+module_init(serial_ambarella_init);
+module_exit(serial_ambarella_exit);
+
+MODULE_DESCRIPTION("Ambarella UART driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:ambarella-uart");
--
2.34.1
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v2 09/10] arm64: ambarella: add ARCH_AMBARELLA and CV75 EVK DT
2026-08-06 9:34 [PATCH v2 00/10] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
` (7 preceding siblings ...)
2026-08-06 9:34 ` [PATCH v2 08/10] serial: ambarella: add Ambarella UART driver Long Zhao via B4 Relay
@ 2026-08-06 9:34 ` Long Zhao via B4 Relay
2026-08-06 9:51 ` sashiko-bot
2026-08-07 6:11 ` Krzysztof Kozlowski
2026-08-06 9:34 ` [PATCH v2 10/10] MAINTAINERS: add ARM/AMBARELLA SoC support Long Zhao via B4 Relay
9 siblings, 2 replies; 27+ messages in thread
From: Long Zhao via B4 Relay @ 2026-08-06 9:34 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
Stephen Boyd, Linus Walleij, Greg Kroah-Hartman, Jiri Slaby,
Catalin Marinas, Will Deacon, Arnd Bergmann, Alexandre Belloni
Cc: devicetree, linux-arm-kernel, linux-kernel, linux-clk, linux-gpio,
linux-serial, soc, Long Zhao
From: Long Zhao <longzhao@ambarella.com>
Enable the Ambarella ARMv8 platform Kconfig, add the CV75 SoC and
EVK device trees, and select the Ambarella UART in the arm64
defconfig for console bring-up.
Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
arch/arm64/Kconfig.platforms | 9 ++
arch/arm64/boot/dts/Makefile | 1 +
arch/arm64/boot/dts/ambarella/Makefile | 3 +
arch/arm64/boot/dts/ambarella/cv75-evk.dts | 30 ++++++
arch/arm64/boot/dts/ambarella/cv75.dtsi | 166 +++++++++++++++++++++++++++++
arch/arm64/configs/defconfig | 3 +
6 files changed, 212 insertions(+)
diff --git a/arch/arm64/Kconfig.platforms b/arch/arm64/Kconfig.platforms
index d2acfac73003..166430ddb3c9 100644
--- a/arch/arm64/Kconfig.platforms
+++ b/arch/arm64/Kconfig.platforms
@@ -26,6 +26,15 @@ config ARCH_SUNXI
help
This enables support for Allwinner sunxi based SoCs like the A64.
+config ARCH_AMBARELLA
+ bool "Ambarella SoC family"
+ select ARM_PSCI
+ select HAVE_ARM_ARCH_TIMER
+ select PINCTRL
+ select PINCTRL_AMB
+ help
+ This enables support for the ARM64 based Ambarella SoCs like CV75.
+
config ARCH_ALPINE
bool "Annapurna Labs Alpine platform"
select ALPINE_MSI if PCI
diff --git a/arch/arm64/boot/dts/Makefile b/arch/arm64/boot/dts/Makefile
index fc726b215f12..f1709eb5d915 100644
--- a/arch/arm64/boot/dts/Makefile
+++ b/arch/arm64/boot/dts/Makefile
@@ -4,6 +4,7 @@ subdir-y += airoha
subdir-y += allwinner
subdir-y += altera
subdir-y += amazon
+subdir-y += ambarella
subdir-y += amd
subdir-y += amlogic
subdir-y += apm
diff --git a/arch/arm64/boot/dts/ambarella/Makefile b/arch/arm64/boot/dts/ambarella/Makefile
new file mode 100644
index 000000000000..ddca3d7b18cc
--- /dev/null
+++ b/arch/arm64/boot/dts/ambarella/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+
+dtb-$(CONFIG_ARCH_AMBARELLA) += cv75-evk.dtb
diff --git a/arch/arm64/boot/dts/ambarella/cv75-evk.dts b/arch/arm64/boot/dts/ambarella/cv75-evk.dts
new file mode 100644
index 000000000000..42a0fe24b83b
--- /dev/null
+++ b/arch/arm64/boot/dts/ambarella/cv75-evk.dts
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Ambarella CV75 EVK board
+ */
+
+/dts-v1/;
+
+#include "cv75.dtsi"
+
+/ {
+ model = "Ambarella CV75 EVK Board";
+ compatible = "ambarella,cv75-evk", "ambarella,cv75";
+
+ aliases {
+ serial0 = &uart0;
+ };
+
+ chosen {
+ stdout-path = &uart0;
+ };
+
+ memory@200000 {
+ device_type = "memory";
+ reg = <0x0 0x00200000 0x0 0x07e00000>;
+ };
+};
+
+&uart0 {
+ status = "okay";
+};
diff --git a/arch/arm64/boot/dts/ambarella/cv75.dtsi b/arch/arm64/boot/dts/ambarella/cv75.dtsi
new file mode 100644
index 000000000000..825b773f052f
--- /dev/null
+++ b/arch/arm64/boot/dts/ambarella/cv75.dtsi
@@ -0,0 +1,166 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Ambarella CV75 SoC
+ *
+ * Boot peripheral: UART0 console.
+ * Clocks: a single ambarella,cv75-rct provider.
+ */
+
+#include <dt-bindings/clock/ambarella,cv75-clock.h>
+#include <dt-bindings/interrupt-controller/arm-gic.h>
+
+/ {
+ compatible = "ambarella,cv75";
+ interrupt-parent = <&gic>;
+ #address-cells = <2>;
+ #size-cells = <2>;
+
+ osc: oscillator {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-output-names = "osc";
+ clock-frequency = <24000000>;
+ };
+
+ psci {
+ compatible = "arm,psci-0.2";
+ method = "smc";
+ };
+
+ cpus {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ cpu@0 {
+ compatible = "arm,cortex-a76";
+ device_type = "cpu";
+ reg = <0x000>;
+ enable-method = "psci";
+ };
+
+ cpu@1 {
+ compatible = "arm,cortex-a76";
+ device_type = "cpu";
+ reg = <0x100>;
+ enable-method = "psci";
+ };
+ };
+
+ gic: interrupt-controller@fff0101000 {
+ compatible = "arm,gic-400";
+ #interrupt-cells = <3>;
+ #address-cells = <0>;
+ #size-cells = <1>;
+ interrupt-controller;
+ reg = <0xff 0xf0101000 0x0 0x1000>,
+ <0xff 0xf0102000 0x0 0x2000>,
+ <0xff 0xf0104000 0x0 0x2000>,
+ <0xff 0xf0106000 0x0 0x2000>;
+ interrupts = <GIC_PPI 9 (GIC_CPU_MASK_SIMPLE(2) | IRQ_TYPE_LEVEL_HIGH)>;
+ };
+
+ timer {
+ compatible = "arm,armv8-timer";
+ interrupts = <GIC_PPI 13 (GIC_CPU_MASK_SIMPLE(2) | IRQ_TYPE_LEVEL_LOW)>,
+ <GIC_PPI 14 (GIC_CPU_MASK_SIMPLE(2) | IRQ_TYPE_LEVEL_LOW)>,
+ <GIC_PPI 11 (GIC_CPU_MASK_SIMPLE(2) | IRQ_TYPE_LEVEL_LOW)>,
+ <GIC_PPI 10 (GIC_CPU_MASK_SIMPLE(2) | IRQ_TYPE_LEVEL_LOW)>,
+ <GIC_PPI 12 (GIC_CPU_MASK_SIMPLE(2) | IRQ_TYPE_LEVEL_LOW)>;
+ };
+
+ soc {
+ compatible = "simple-bus";
+ #address-cells = <2>;
+ #size-cells = <2>;
+ ranges;
+
+ /* Clock provider and shared system registers. */
+ rct: clock-controller@ffed080000 {
+ compatible = "ambarella,cv75-rct", "syscon";
+ reg = <0xff 0xed080000 0x0 0x1000>;
+ clocks = <&osc>;
+ clock-names = "osc";
+ #clock-cells = <1>;
+
+ assigned-clocks = <&rct CV75_GCLK_UART0>;
+ assigned-clock-parents = <&osc>;
+ assigned-clock-rates = <24000000>;
+ };
+
+ apb@ffe4000000 {
+ compatible = "simple-bus";
+ #address-cells = <1>;
+ #size-cells = <1>;
+ reg = <0xff 0xe4000000 0x0 0x01000000>;
+ ranges = <0xe4000000 0xff 0xe4000000 0x01000000>;
+
+ uart0: serial@e4000000 {
+ compatible = "ambarella,cv75-uart";
+ reg = <0xe4000000 0x1000>;
+ interrupts = <GIC_SPI 170 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&rct CV75_GCLK_UART0>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&uart0_pins>;
+ status = "disabled";
+ };
+
+ pinctrl: pinctrl@e4010000 {
+ compatible = "ambarella,cv75-pinctrl";
+ #address-cells = <1>;
+ #size-cells = <1>;
+ reg = <0xe4010000 0x1000>;
+ ranges;
+ ambarella,drive-strength-syscon = <&rct>;
+ ambarella,pull-syscon = <&s_scratchpad_syscon>;
+
+ gpio0: gpio@e4013000 {
+ reg = <0xe4013000 0x1000>;
+ interrupts = <GIC_SPI 163 IRQ_TYPE_LEVEL_HIGH>;
+ gpio-controller;
+ #gpio-cells = <2>;
+ gpio-ranges = <&pinctrl 0 0 32>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ };
+
+ gpio1: gpio@e4014000 {
+ reg = <0xe4014000 0x1000>;
+ interrupts = <GIC_SPI 164 IRQ_TYPE_LEVEL_HIGH>;
+ gpio-controller;
+ #gpio-cells = <2>;
+ gpio-ranges = <&pinctrl 0 32 32>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ };
+
+ gpio2: gpio@e4015000 {
+ reg = <0xe4015000 0x1000>;
+ interrupts = <GIC_SPI 165 IRQ_TYPE_LEVEL_HIGH>;
+ gpio-controller;
+ #gpio-cells = <2>;
+ gpio-ranges = <&pinctrl 0 64 32>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ };
+
+ uart0_pins: uart0-pins {
+ function = "uart0";
+ groups = "uart0tx", "uart0rx";
+ };
+ };
+ };
+
+ ahb@ffe0000000 {
+ compatible = "simple-bus";
+ #address-cells = <1>;
+ #size-cells = <1>;
+ reg = <0xff 0xe0000000 0x0 0x01000000>;
+ ranges = <0xe0000000 0xff 0xe0000000 0x01000000>;
+
+ s_scratchpad_syscon: scratchpad@e002e000 {
+ compatible = "ambarella,cv75-sec-scratchpad", "syscon";
+ reg = <0xe002e000 0x1000>;
+ };
+ };
+ };
+};
diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig
index ad3bd3d9507b..64e8fbe43d27 100644
--- a/arch/arm64/configs/defconfig
+++ b/arch/arm64/configs/defconfig
@@ -36,6 +36,7 @@ CONFIG_CRASH_DUMP=y
CONFIG_ARCH_ACTIONS=y
CONFIG_ARCH_AIROHA=y
CONFIG_ARCH_SUNXI=y
+CONFIG_ARCH_AMBARELLA=y
CONFIG_ARCH_ALPINE=y
CONFIG_ARCH_APPLE=y
CONFIG_ARCH_ARTPEC=y
@@ -567,6 +568,8 @@ CONFIG_SERIAL_8250_OMAP=y
CONFIG_SERIAL_8250_MT6577=y
CONFIG_SERIAL_8250_UNIPHIER=y
CONFIG_SERIAL_OF_PLATFORM=y
+CONFIG_SERIAL_AMBARELLA=y
+CONFIG_SERIAL_AMBARELLA_CONSOLE=y
CONFIG_SERIAL_AMBA_PL011=y
CONFIG_SERIAL_AMBA_PL011_CONSOLE=y
CONFIG_SERIAL_MESON=y
--
2.34.1
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v2 10/10] MAINTAINERS: add ARM/AMBARELLA SoC support
2026-08-06 9:34 [PATCH v2 00/10] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
` (8 preceding siblings ...)
2026-08-06 9:34 ` [PATCH v2 09/10] arm64: ambarella: add ARCH_AMBARELLA and CV75 EVK DT Long Zhao via B4 Relay
@ 2026-08-06 9:34 ` Long Zhao via B4 Relay
9 siblings, 0 replies; 27+ messages in thread
From: Long Zhao via B4 Relay @ 2026-08-06 9:34 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
Stephen Boyd, Linus Walleij, Greg Kroah-Hartman, Jiri Slaby,
Catalin Marinas, Will Deacon, Arnd Bergmann, Alexandre Belloni
Cc: devicetree, linux-arm-kernel, linux-kernel, linux-clk, linux-gpio,
linux-serial, soc, Long Zhao
From: Long Zhao <longzhao@ambarella.com>
Add a maintainer entry covering Ambarella CV75 bindings, drivers
and device trees.
Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
MAINTAINERS | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 803b60ad628f..c06c84fab57a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2502,6 +2502,23 @@ F: drivers/clocksource/asm9260_timer.c
F: drivers/rtc/rtc-asm9260.c
F: drivers/watchdog/asm9260_wdt.c
+ARM/AMBARELLA SOC SUPPORT
+M: Long Zhao <longzhao@ambarella.com>
+L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
+L: devicetree@vger.kernel.org
+S: Maintained
+F: Documentation/devicetree/bindings/arm/ambarella.yaml
+F: Documentation/devicetree/bindings/clock/ambarella,cv75-rct.yaml
+F: Documentation/devicetree/bindings/pinctrl/ambarella,cv75-pinctrl.yaml
+F: Documentation/devicetree/bindings/serial/ambarella,cv75-uart.yaml
+F: Documentation/devicetree/bindings/soc/ambarella/
+F: arch/arm64/boot/dts/ambarella/
+F: drivers/clk/ambarella/
+F: drivers/pinctrl/pinctrl-ambarella*
+F: drivers/tty/serial/ambarella_uart.c
+F: include/dt-bindings/clock/ambarella,cv75-clock.h
+N: ambarella
+
ARM/AMD PENSANDO ARM64 ARCHITECTURE
M: Brad Larson <blarson@amd.com>
L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
--
2.34.1
^ permalink raw reply related [flat|nested] 27+ messages in thread
* Re: [PATCH v2 04/10] dt-bindings: pinctrl: add Ambarella CV75 pinctrl
2026-08-06 9:34 ` [PATCH v2 04/10] dt-bindings: pinctrl: add Ambarella CV75 pinctrl Long Zhao via B4 Relay
@ 2026-08-06 9:41 ` sashiko-bot
2026-08-07 6:06 ` Krzysztof Kozlowski
1 sibling, 0 replies; 27+ messages in thread
From: sashiko-bot @ 2026-08-06 9:41 UTC (permalink / raw)
To: Long Zhao; +Cc: robh, conor+dt, devicetree
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] The pin configuration node schema (`^[a-z0-9-]+-pins$`) incorrectly uses `additionalProperties: false` alongside `$ref` schemas, and restrictively hardcodes `function` and `groups` exclusively to `uart0`.
- [Medium] The GPIO child node pattern (`^gpio@[0-9a-f]+$`) incorrectly uses `additionalProperties: false` while omitting a reference to standard GPIO schemas, blocking common GPIO properties.
--
--- Patch [4]: [PATCH v2 04/10] dt-bindings: pinctrl: add Ambarella CV75 pinctrl ---
commit 004945278e95f95e4ac87cc90d5d3bc4136ce194
Author: Long Zhao <longzhao@ambarella.com>
dt-bindings: pinctrl: add Ambarella CV75 pinctrl
This commit adds the Ambarella CV75 pin controller device tree binding,
which configures pin multiplexing, bias, and drive strength.
> 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 0000000000000..3fa30b275e514
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/pinctrl/ambarella,cv75-pinctrl.yaml
[ ... ]
> +patternProperties:
> + "^gpio@[0-9a-f]+$":
> + type: object
> + additionalProperties: false
[Severity: Medium]
Does this strict property boundary prevent the use of standard GPIO properties
like gpio-line-names or ngpios?
Setting additionalProperties to false without referencing
/schemas/gpio/gpio.yaml# typically causes validation to fail when these common
optional properties are used. Would unevaluatedProperties: false along with a
reference to the standard GPIO schema be more appropriate here?
[ ... ]
> + "^[a-z0-9-]+-pins$":
> + type: object
> + allOf:
> + - $ref: pinmux-node.yaml#
> + - $ref: pincfg-node.yaml#
> + additionalProperties: false
[Severity: Medium]
Does using additionalProperties: false here incorrectly restrict standard
properties inherited from the $ref schemas?
This usually requires workarounds like redeclaring properties such as
bias-disable. Would unevaluatedProperties: false be the correct approach when
using $ref schemas?
> +
> + properties:
> + function:
> + const: uart0
> +
> + groups:
> + $ref: /schemas/types.yaml#/definitions/string-array
> + items:
> + enum: [uart0tx, uart0rx]
[Severity: Medium]
Does this lock the schema strictly to uart0?
Hardcoding the function and groups like this appears to invalidate
configurations for any other hardware functions such as I2C, SPI, or GPIO,
causing device tree validation to fail for them.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260806-longzhao-upstream-cv75-v2-v2-0-6b09707c5fe9@ambarella.com?part=4
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v2 03/10] dt-bindings: clock: add Ambarella CV75 RCT clock controller
2026-08-06 9:34 ` [PATCH v2 03/10] dt-bindings: clock: add Ambarella CV75 RCT clock controller Long Zhao via B4 Relay
@ 2026-08-06 9:42 ` sashiko-bot
2026-08-06 10:56 ` Rob Herring (Arm)
2026-08-07 6:02 ` Krzysztof Kozlowski
2 siblings, 0 replies; 27+ messages in thread
From: sashiko-bot @ 2026-08-06 9:42 UTC (permalink / raw)
To: Long Zhao; +Cc: devicetree, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Medium] The binding omits the `#reset-cells` property for the RCT (Reset and Clock Tree) block, failing to document its reset provider capabilities.
- [Low] The dt-bindings header includes a `CV75_CLK_NUM` macro, which is an anti-pattern for device tree bindings.
- [Low] The `$nodename` pattern `^clock-controller@` is incomplete and allows trailing garbage in the unit address.
--
--- Patch [3]: [PATCH v2 03/10] dt-bindings: clock: add Ambarella CV75 RCT clock controller ---
commit 06893bbacfebbbcc609e16261fa2b05c7e05554d
Author: Long Zhao <longzhao@ambarella.com>
dt-bindings: clock: add Ambarella CV75 RCT clock controller
Add the CV75 RCT clock controller binding and clock ID header.
> 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 0000000000000..aa96533bab9b5
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/clock/ambarella,cv75-rct.yaml
> @@ -0,0 +1,66 @@
[ ... ]
> +properties:
> + $nodename:
> + pattern: "^clock-controller@"
[Severity: Low]
Is this regex pattern a bit too permissive? Without the end-of-string anchor
and unit address character classes, this will match invalid node names with
non-hexadecimal characters or trailing garbage, such as
"clock-controller@1000xyz".
Should this be updated to strictly enforce the expected node name format,
such as "^clock-controller@[0-9a-f]+$"?
[ ... ]
> +description: |
> + The RCT (Reset and Clock Tree) block on the Ambarella CV75 SoC provides
> + PLLs, clock muxes and dividers for on-chip peripherals.
[ ... ]
> + "#clock-cells":
> + const: 1
> +
> +required:
> + - compatible
[Severity: Medium]
Since the description indicates this hardware block is the Reset and Clock
Tree, shouldn't #reset-cells be included in the properties and required
lists?
Because additionalProperties: false is set, any device tree attempting to
describe the reset functionality of this hardware will fail schema validation.
Device tree bindings typically need to comprehensively describe the full
hardware block's capabilities, not just the features currently implemented by
the OS clock driver.
[ ... ]
> 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 0000000000000..3d3314446e75f
> --- /dev/null
> +++ b/include/dt-bindings/clock/ambarella,cv75-clock.h
> @@ -0,0 +1,17 @@
[ ... ]
> +/* 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
[Severity: Low]
Does this code need to expose CV75_CLK_NUM in the bindings header?
Defining total count macros in DT binding headers ties the ABI to a software
implementation detail. If new clocks are discovered or added later, updating
this macro modifies a published ABI, which violates DT stability rules and
can cause mismatches between updated headers and older drivers.
Could the total number of clocks be tracked internally within the clock
driver itself instead?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260806-longzhao-upstream-cv75-v2-v2-0-6b09707c5fe9@ambarella.com?part=3
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v2 06/10] clk: ambarella: add CV75 CCU driver
2026-08-06 9:34 ` [PATCH v2 06/10] clk: ambarella: add CV75 CCU driver Long Zhao via B4 Relay
@ 2026-08-06 9:45 ` sashiko-bot
0 siblings, 0 replies; 27+ messages in thread
From: sashiko-bot @ 2026-08-06 9:45 UTC (permalink / raw)
To: Long Zhao; +Cc: robh, devicetree, conor+dt
Thank you for your contribution! Sashiko AI review found 8 potential issue(s) to consider:
- [High] `do_div()` is incorrectly used on `unsigned long` variables, causing stack memory corruption on 32-bit architectures.
- [High] The generic divider implementation contains logic bugs that cause it to check the wrong bit for status, fail to disable clocks, and corrupt divider values.
- [High] A zero or invalid parent rate causes a fatal divide-by-zero panic during PLL parameter calculation.
- [High] Infinite loop in `amb_pll_calc_params` when `parent_rate` is smaller than the maximum post-divider.
- [Medium] `amb_pll_calc_params` omits static `fsdiv` and `fsout` dividers from its rational target approximation, resulting in incorrect rates.
- [Medium] `amb_pll_set_rate` fails to apply `pre_scaler` and `post_scaler` to target calculations, breaking rate configurations.
- [Medium] Stale hardware register read causes the incorrect VCO range to be selected for fractional PLL modes.
- [Medium] Integer PLL rates are unnecessarily restricted to coarse multiples of `parent_rate / 2`.
--
--- Patch [6]: [PATCH v2 06/10] clk: ambarella: add CV75 CCU driver ---
commit 3c34aa8f15f802b4bcbd9606eec16b27a37dd8d9
Author: Long Zhao <longzhao@ambarella.com>
clk: ambarella: add CV75 CCU driver
This commit adds a minimal Ambarella clock controller driver for the CV75 SoC,
providing core PLL, bus, and UART clocks required for early console bring-up.
> diff --git a/drivers/clk/ambarella/ccu_mux_div.c b/drivers/clk/ambarella/ccu_mux_div.c
> new file mode 100644
> index 0000000000000..33430506f8ed1
> --- /dev/null
> +++ b/drivers/clk/ambarella/ccu_mux_div.c
[ ... ]
> +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;
[Severity: High]
Does this check the correct bit? The code checks BIT(div->width) directly
against the raw register value before shifting it by div->shift. If
div->shift is non-zero, this evaluates the wrong hardware bit.
> +
> + 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);
[Severity: High]
Can this cause a stack buffer overflow on 32-bit architectures? The do_div
macro expects a 64-bit u64 pointer. Because this driver can be built for
32-bit targets via COMPILE_TEST, unsigned long is 32 bits on those systems.
Passing a 32-bit variable causes the macro to write 64 bits, overwriting
adjacent stack memory.
> +
> + return rate;
> +}
> +
> +static int amb_div_determine_rate(struct clk_hw *hw,
> + struct clk_rate_request *req)
> +{
[ ... ]
> + 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);
[Severity: High]
Could this corrupt the adjacent fields in the clk_rate_request struct? The
do_div macro writes 64 bits to req->rate, which is an unsigned long and
thus only 32 bits on 32-bit builds.
> +
> + 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);
[Severity: High]
Can this prevent the clock from being disabled? The update mask for
zero-based dividers uses clk_div_mask(div->width), which excludes
BIT(div->width). When setting a rate of 0, val evaluates to BIT(div->width),
but it gets masked out here, silently leaving the clock enabled with a zero
divider.
> +
> + 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);
> + }
[Severity: High]
Will toggling BIT(0) directly on the register corrupt the actual divider
field if div->shift is 0?
> diff --git a/drivers/clk/ambarella/ccu_pll.c b/drivers/clk/ambarella/ccu_pll.c
> new file mode 100644
> index 0000000000000..1293836e222e5
> --- /dev/null
> +++ b/drivers/clk/ambarella/ccu_pll.c
[ ... ]
> +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;
[Severity: High]
If the best parent rate is 0 or 1, half_refclk evaluates to 0, returning 0
(success). Doesn't this allow a 0 parent rate to reach amb_pll_set_rate,
which then calls amb_pll_calc_params and triggers a divide-by-zero panic?
> +
> + req->rate = roundup(req->rate, half_refclk);
> +
> + return 0;
> +}
[Severity: Medium]
Does this artificially restrict the integer PLL solver? By forcibly rounding
up the requested frequency to multiples of parent_rate / 2, this prevents
the framework from utilizing the fine-grained post-divider (sout) inside
amb_pll_calc_params to generate precise integer ratios.
> +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)
> +{
[ ... ]
> + 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);
[Severity: Medium]
Should the rate approximation factor in the static fsdiv and fsout dividers?
The rational_best_approximation is called assuming the output rate
relationship is solely parent_rate * intp / sout. Because the true hardware
equation includes fsdiv and fsout, omitting them from the target ratio
approximation results in incorrectly calculated parameters.
> +
> + 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;
[Severity: High]
Is there a risk of an infinite loop here if parent_rate is very low? If
parent_rate is less than AMB_PLL_MAX_SOUT (16), resolution truncates to 0.
Subtracting 0 leaves rate_tmp unchanged, causing the loop condition to
evaluate as permanently true.
> + rational_best_approximation(rate_tmp, parent_rate,
> + max_numerator, max_denominator,
> + &intp_ul, &sout_ul);
> + }
[ ... ]
> +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);
[ ... ]
> + ret = amb_pll_calc_params(pll, rate, parent_rate, ctrl2_val,
> + &intp, &sdiv, &sout, &vcodiv, &fsdiv,
> + &fsout);
[Severity: Medium]
Should this adjust the requested rate and parent_rate using the hardware
pre_scaler and post_scaler? Unlike amb_pll_recalc_rate which reads these
scalers from hardware registers, amb_pll_set_rate passes the raw rate and
parent_rate directly into amb_pll_calc_params, which will configure the PLL
clocks to incorrect frequencies.
> + 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) {
[ ... ]
> + } else {
> + u32 fvco_mhz, range;
> +
> + fvco_mhz = amb_pll_calc_vco(pll, parent_rate) / 1000000UL;
[Severity: Medium]
Does amb_pll_calc_vco() use a stale hardware register read to determine the
VCO frequency? The CTRL_FRAC_MODE bit is enabled in the local ctrl_val
variable above, but not yet written to the hardware register. When
amb_pll_calc_vco() reads the register directly, it misses the uncommitted
fractional change, which can lead to the driver selecting the wrong
VCO_RANGE boundary.
> + 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;
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260806-longzhao-upstream-cv75-v2-v2-0-6b09707c5fe9@ambarella.com?part=6
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v2 07/10] pinctrl: ambarella: add Ambarella pin controller
2026-08-06 9:34 ` [PATCH v2 07/10] pinctrl: ambarella: add Ambarella pin controller Long Zhao via B4 Relay
@ 2026-08-06 9:46 ` sashiko-bot
2026-08-07 17:45 ` Linus Walleij
1 sibling, 0 replies; 27+ messages in thread
From: sashiko-bot @ 2026-08-06 9:46 UTC (permalink / raw)
To: Long Zhao; +Cc: devicetree, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 9 potential issue(s) to consider:
- [High] Improper devres cleanup ordering leaves the GPIO chip with a destroyed IRQ domain, leading to Use-After-Free.
- [High] Inconsistent enforcement of hsm_domain_id access restrictions for IOMUX registers causes bus faults.
- [High] The device tree node pointer is assigned to bank->gc.fwnode without taking a reference, leading to a Use-After-Free.
- [High] Calling regmap_update_bits() while holding a raw_spinlock_t causes a lock nesting bug on PREEMPT_RT.
- [High] Performing regmap accesses in _noirq power management callbacks causes lockdep BUGs on PREEMPT_RT.
- [Medium] Improper register restoration order during resume causes glitches on GPIO output pins and pull resistors.
- [Medium] amb_pinctrl_suspend_noirq overwrites GPIO_IE_OFFSET with irq_wake_mask, disabling IRQF_NO_SUSPEND interrupts.
- [Medium] The driver fails to properly handle clk_au_dedicated_pin on device trees with non-contiguous GPIO banks.
- [Medium] amb_gpio_irq_enable unconditionally forces the pin direction to INPUT, breaking open-drain output IRQs.
--
--- Patch [7]: [PATCH v2 07/10] pinctrl: ambarella: add Ambarella pin controller ---
commit e1626036cbd93218655297fbd8b45ae82567717d
Author: Long Zhao <longzhao@ambarella.com>
pinctrl: ambarella: add Ambarella pin controller
Add the Ambarella pinmux/GPIO controller driver used by CV75.
> diff --git a/drivers/pinctrl/pinctrl-ambarella.c b/drivers/pinctrl/pinctrl-ambarella.c
> index 0000000000000..bb79e06c4160f 100644
> --- /dev/null
> +++ b/drivers/pinctrl/pinctrl-ambarella.c
[ ... ]
> @@ -462,13 +462,13 @@ static int amb_pinconf_set(struct pinctrl_dev *pctldev, unsigned int pin,
> bank = gpio_bank->hw_id;
> offset = pin - gpio_bank->pin_base;
>
> raw_spin_lock_irqsave(&soc->lock, flags);
> for (i = 0; i < num_configs; i++) {
> config = configs[i];
> param = pinconf_to_config_param(config);
> arg = pinconf_to_config_argument(config);
>
> switch (param) {
> case PIN_CONFIG_BIAS_DISABLE:
> regmap_update_bits(soc->pull_regmap,
> soc->data->pull_en[bank], BIT(offset), 0);
[Severity: High]
The syscon regmaps use a standard spinlock_t internally, which maps to a
sleepable rt_mutex on PREEMPT_RT.
Since raw_spin_lock_irqsave(&soc->lock, flags) is held, does calling
regmap_update_bits() here trigger a "sleeping in atomic context" BUG
on PREEMPT_RT?
[ ... ]
> @@ -746,13 +746,13 @@ static int amb_gpio_direction_output(struct gpio_chip *gc, unsigned int pin, int
> writel_relaxed(data | BIT(pin), bank->base + GPIO_DIR_OFFSET);
>
> raw_spin_unlock_irqrestore(&soc->lock, flags);
>
> return 0;
> }
>
> /* gpiolib gpio_to_irq callback function */
> static int amb_gpio_to_irq(struct gpio_chip *gc, unsigned int pin)
> {
> struct amb_gpio_bank *bank = gpiochip_get_data(gc);
>
> return irq_create_mapping(bank->domain, pin);
> }
[ ... ]
> @@ -1181,24 +1181,24 @@ static int amb_gpio_register(struct amb_pinctrl_soc_data *soc)
>
> bank->domain = irq_domain_add_linear(to_of_node(bank->gc.fwnode),
> bank->gc.ngpio,
> &amb_gpio_irq_domain_ops,
> bank);
> if (!bank->domain)
> return dev_err_probe(soc->dev, -ENODEV,
> "GPIO%u: failed to create irqdomain\n",
> i);
>
> rval = devm_gpiochip_add_data(soc->dev, &bank->gc, bank);
> if (rval) {
> irq_domain_remove(bank->domain);
> bank->domain = NULL;
> return dev_err_probe(soc->dev, rval,
> "GPIO%u: gpiochip registration failed\n",
> i);
> }
>
> writel_relaxed(0xffffffff, bank->base + GPIO_ENABLE_OFFSET);
> writel_relaxed(0x00000000, bank->base + GPIO_AFSEL_OFFSET);
> writel_relaxed(0x00000000, bank->base + GPIO_MASK_OFFSET);
>
> irq_set_irq_type(bank->irq, IRQ_TYPE_LEVEL_HIGH);
> irq_set_chained_handler_and_data(bank->irq,
> amb_gpio_handle_irq, bank);
>
> rval = devm_add_action_or_reset(soc->dev,
> amb_gpio_irq_cleanup, bank);
[Severity: High]
The devres cleanup action amb_gpio_irq_cleanup() is registered after the
gpiochip is added. Since devres actions execute in reverse order, the IRQ
domain will be destroyed before the gpiochip is removed.
Could this leave the gpiochip with a NULL bank->domain during unbind,
causing a use-after-free if amb_gpio_to_irq() is called?
[ ... ]
> @@ -841,13 +841,13 @@ static void amb_gpio_irq_enable(struct irq_data *data)
>
> offset = irqd_to_hwirq(data);
>
> gpiochip_enable_irq(gc, offset);
>
> raw_spin_lock_irqsave(&soc->lock, flags);
>
> val = readl_relaxed(gpio_base + GPIO_DIR_OFFSET);
> val &= ~(0x1 << offset);
> writel_relaxed(val, gpio_base + GPIO_DIR_OFFSET);
[Severity: Medium]
The gpiolib core supports open-drain outputs as interrupt sources. Will
unconditionally clearing the direction bit here force the pin to be an
input, breaking any open-drain output IRQs?
>
> for (i = 0; i < 3; i++) {
> val = readl_relaxed(iomux_base + IOMUX_OFFSET(bank->hw_id, i));
> val &= ~(0x1 << offset);
> writel_relaxed(val, iomux_base + IOMUX_OFFSET(bank->hw_id, i));
> }
[Severity: High]
The driver restricts IOMUX register access in amb_pinmux_set_altfunc()
to only the safety domain (hsm_domain_id == 0).
Does this loop bypass that check? Can unprotected writes here trigger a bus
fault on platforms where hsm_domain_id != 0?
[ ... ]
> @@ -996,9 +996,9 @@ static int amb_gpio_parse_dt(struct amb_pinctrl_soc_data *soc)
> if (soc->banks[i].hw_id == hw_id)
> return dev_err_probe(soc->dev, -EINVAL,
> "%pOF: duplicate GPIO bank\n",
> np);
> }
>
> bank = &soc->banks[soc->bank_num];
> bank->soc = soc;
> bank->pin_base = args.args[1];
> bank->hw_id = hw_id;
> bank->gc.ngpio = args.args[2];
> bank->gc.fwnode = of_fwnode_handle(np);
[Severity: High]
The for_each_available_child_of_node_scoped() loop automatically drops the
reference to the np node when it goes out of scope.
Will assigning it to bank->gc.fwnode without taking an additional reference
via of_node_get() lead to a use-after-free later?
[ ... ]
> @@ -1300,9 +1300,9 @@ static int amb_pinctrl_probe(struct platform_device *pdev)
> for (i = 0; i < soc->bank_num; i++) {
> unsigned int pin;
>
> for (pin = soc->banks[i].pin_base;
> pin < soc->banks[i].pin_base + soc->banks[i].gc.ngpio;
> pin++)
> clear_bit(pin, soc->used);
> }
>
> if (soc->data->clk_au_dedicated_pin >= gpio_pins &&
> soc->data->clk_au_dedicated_pin < AMBA_MAX_PINS)
> clear_bit(soc->data->clk_au_dedicated_pin, soc->used);
[Severity: Medium]
If the device tree contains sparse, non-contiguous GPIO banks, the dedicated
pin might fall into a "hole" (a missing bank) that has an ID less than
the highest pin ID returned by amb_gpio_pins_end(soc).
If this happens, will gpio_pins incorrectly skip this dedicated pin, leaving
it marked busy and preventing it from being suspended?
[ ... ]
> @@ -1390,15 +1390,15 @@ static int amb_pinctrl_suspend_noirq(struct device *dev)
> struct amb_pinctrl_soc_data *soc = dev_get_drvdata(dev);
> u32 bank, hw, i;
>
> for (i = 0; i < soc->bank_num; i++) {
> hw = soc->banks[i].hw_id;
>
> regmap_read(soc->pull_regmap, soc->data->pull_en[hw], &soc->pm[i].pull[0]);
> regmap_read(soc->pull_regmap, soc->data->pull_dir[hw], &soc->pm[i].pull[1]);
>
> regmap_read(soc->ds_regmap, soc->data->ds0[hw], &soc->pm[i].ds[0]);
[Severity: High]
The amb_pinctrl_suspend_noirq() callback executes with hardirqs disabled.
Will reading the syscon regmaps here attempt to acquire a sleepable rt_mutex
on PREEMPT_RT, triggering a "sleeping in atomic context" BUG?
> regmap_read(soc->ds_regmap, soc->data->ds1[hw], &soc->pm[i].ds[1]);
> if (soc->data->have_ds2)
> regmap_read(soc->ds_regmap, soc->data->ds2[hw], &soc->pm[i].ds[2]);
>
> soc->pm[i].iomux[0] = readl_relaxed(soc->iomux_base + IOMUX_OFFSET(hw, 0));
> soc->pm[i].iomux[1] = readl_relaxed(soc->iomux_base + IOMUX_OFFSET(hw, 1));
> soc->pm[i].iomux[2] = readl_relaxed(soc->iomux_base + IOMUX_OFFSET(hw, 2));
[Severity: High]
Similar to amb_gpio_irq_enable(), is this missing a check to ensure
hsm_domain_id == 0? Could these IOMUX reads trigger a bus fault in
non-safety domains?
[ ... ]
> @@ -1404,9 +1404,9 @@ static int amb_pinctrl_suspend_noirq(struct device *dev)
> soc->pm[i].is = readl_relaxed(soc->banks[i].base + GPIO_IS_OFFSET);
> soc->pm[i].ibe = readl_relaxed(soc->banks[i].base + GPIO_IBE_OFFSET);
> soc->pm[i].iev = readl_relaxed(soc->banks[i].base + GPIO_IEV_OFFSET);
> soc->pm[i].ie = readl_relaxed(soc->banks[i].base + GPIO_IE_OFFSET);
> soc->pm[i].mask = readl_relaxed(soc->banks[i].base + GPIO_MASK_OFFSET);
> writel_relaxed(0xffffffff, soc->banks[i].base + GPIO_MASK_OFFSET);
> soc->pm[i].data = readl_relaxed(soc->banks[i].base + GPIO_DATA_OFFSET);
>
> if (soc->banks[i].irq_wake_mask)
> writel_relaxed(soc->banks[i].irq_wake_mask,
> soc->banks[i].base + GPIO_IE_OFFSET);
[Severity: Medium]
The generic IRQ core already masks non-wake interrupts during suspend,
leaving wake and IRQF_NO_SUSPEND interrupts enabled.
Does unconditionally overwriting GPIO_IE_OFFSET with the irq_wake_mask here
unintentionally disable any configured IRQF_NO_SUSPEND interrupts?
[ ... ]
> @@ -1452,9 +1452,9 @@ static int amb_pinctrl_resume_noirq(struct device *dev)
> if (soc->data->have_ds2)
> regmap_write(soc->ds_regmap, soc->data->ds2[hw], soc->pm[i].ds[2]);
>
> writel_relaxed(soc->pm[i].iomux[0], soc->iomux_base + IOMUX_OFFSET(hw, 0));
> writel_relaxed(soc->pm[i].iomux[1], soc->iomux_base + IOMUX_OFFSET(hw, 1));
> writel_relaxed(soc->pm[i].iomux[2], soc->iomux_base + IOMUX_OFFSET(hw, 2));
>
> writel_relaxed(soc->pm[i].afsel, soc->banks[i].base + GPIO_AFSEL_OFFSET);
> writel_relaxed(soc->pm[i].dir, soc->banks[i].base + GPIO_DIR_OFFSET);
> /* Expose DATA writes while restoring the saved GPIO state. */
> writel_relaxed(0xffffffff,
> soc->banks[i].base + GPIO_MASK_OFFSET);
> writel_relaxed(soc->pm[i].data,
> soc->banks[i].base + GPIO_DATA_OFFSET);
[Severity: Medium]
The direction is restored before the data is written back. If a pin is
configured as an output driving HIGH, will setting its direction to output
while the data register still holds a default 0 momentarily drive the pin
LOW, causing a hardware glitch during resume?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260806-longzhao-upstream-cv75-v2-v2-0-6b09707c5fe9@ambarella.com?part=7
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v2 08/10] serial: ambarella: add Ambarella UART driver
2026-08-06 9:34 ` [PATCH v2 08/10] serial: ambarella: add Ambarella UART driver Long Zhao via B4 Relay
@ 2026-08-06 9:49 ` sashiko-bot
2026-08-07 6:18 ` Jiri Slaby
2026-08-07 18:37 ` Linus Walleij
2 siblings, 0 replies; 27+ messages in thread
From: sashiko-bot @ 2026-08-06 9:49 UTC (permalink / raw)
To: Long Zhao; +Cc: robh, devicetree, conor+dt
Thank you for your contribution! Sashiko AI review found 10 potential issue(s) to consider:
- [High] Incorrect assignment of TTY_OVERRUN to the character flag causes valid data to be marked as an error and duplicates overrun reporting.
- [High] The wait_for_tx() function resets both the TX and RX FIFOs when recovering from a stuck TX path, destroying any pending incoming data.
- [High] The IRQ handler unconditionally returns IRQ_HANDLED even when no interrupt is pending, breaking the kernel's spurious interrupt detection.
- [High] The Interrupt Enable Register (IER) is modified without holding the port lock after the IRQ handler is registered, creating a race condition.
- [High] Hardware state is modified in `serial_ambarella_resume` without acquiring the port lock, causing races with concurrent console writes.
- [High] Missing in-use check on the global `ambarella_port` array during probe allows multiple devices to overwrite the same port structure, leading to a Use-After-Free.
- [Medium] The AMBA_UART_RESET_FLAG is not cleared upon driver removal, preventing hardware reset on subsequent driver binds.
- [Medium] The driver unconditionally writes flow control characters (`port->x_char`) to the TX FIFO without verifying if there is space.
- [Medium] The `write_atomic` console callback does not manage the DLAB bit, which corrupts the baud rate divisor and drops panic messages if it preempts `set_termios`.
- [Low] `serial_ambarella_hw_deinit()` writes FIFO reset commands to the Software Reset Register (SRR) instead of the FIFO Control Register (FCR).
--
--- Patch [8]: [PATCH v2 08/10] serial: ambarella: add Ambarella UART driver ---
commit 10ca0d13a0637a7d9bf6d4a3c052576253ff7922
Author: Long Zhao <longzhao@ambarella.com>
serial: ambarella: add Ambarella UART driver
This commit adds a new UART driver for Ambarella SoCs with console
support for early boot bring-up on CV75.
> diff --git a/drivers/tty/serial/ambarella_uart.c b/drivers/tty/serial/ambarella_uart.c
> new file mode 100644
> index 0000000000000..7356b242f0ef7
> --- /dev/null
> +++ b/drivers/tty/serial/ambarella_uart.c
[ ... ]
> +static inline void wait_for_tx(struct uart_port *port)
> +{
> + u32 ls;
> + int ret;
> +
> + ret = readl_poll_timeout_atomic(port->membase + UART_LS_OFFSET, ls,
> + ls & UART_LS_TEMT, 1,
> + AMBARELLA_UART_TIMEOUT_US);
> + if (likely(!ret))
> + return;
> +
> + /* Recover a stuck TX path so console/poll can continue. */
> + writel_relaxed(UART_FC_RX_2_TO_FULL | UART_FC_TX_EMPTY |
> + UART_FC_XMITR | UART_FC_RCVRR,
> + port->membase + UART_FC_OFFSET);
> + udelay(100);
> + writel_relaxed(UART_FC_FIFOE | UART_FC_RX_2_TO_FULL |
> + UART_FC_TX_EMPTY | UART_FC_XMITR |
> + UART_FC_RCVRR,
> + port->membase + UART_FC_OFFSET);
> +}
[Severity: High]
Does this unconditional write of UART_FC_RCVRR accidentally flush the RX FIFO
when recovering from a TX timeout?
If the polling loop times out due to hardware flow control blocking the
transmitter, this reset logic writes the receive reset bit alongside the
transmit reset. This forcefully flushes the RX FIFO and can drop incoming
serial data that is unrelated to the TX path timeout.
[ ... ]
> +static inline void serial_ambarella_receive_chars(struct uart_port *port,
> + u32 tmo)
> +{
[ ... ]
> + if (unlikely(ls & (UART_LS_BI | UART_LS_PE |
> + UART_LS_FE | UART_LS_OE))) {
[ ... ]
> + if (ls & UART_LS_BI)
> + flag = TTY_BREAK;
> + else if (ls & UART_LS_FE)
> + flag = TTY_FRAME;
> + else if (ls & UART_LS_PE)
> + flag = TTY_PARITY;
> + else if (ls & UART_LS_OE)
> + flag = TTY_OVERRUN;
> +
> + if (ls & UART_LS_OE)
> + pr_debug("%s: OVERFLOW\n", __func__);
> + }
> +
> + if (have_char) {
> + if (uart_handle_sysrq_char(port, ch))
> + goto ignore_char;
> +
> + uart_insert_char(port, ls, UART_LS_OE, ch, flag);
> + }
[Severity: High]
Could this overwrite the valid character flag with an error flag?
When the UART_LS_OE bit is set, the code sets flag to TTY_OVERRUN for the
current character. When this flag is passed to uart_insert_char(), the TTY
layer will treat this valid character as an overrun error and drop its data.
Since uart_insert_char() inherently handles inserting a separate overrun
marker, replacing the current character's flag appears to drop valid data.
[ ... ]
> +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;
> + }
[Severity: Medium]
Should this verify if the TX FIFO is full before writing the flow control
character?
When the TTY layer requests sending a flow control character like XON or
XOFF, the driver unconditionally writes x_char to UART_TH_OFFSET. If the
transmit FIFO happens to be full, this write could overflow the FIFO and
cause the flow control character to be lost or corrupt queued data.
[ ... ]
> +static irqreturn_t serial_ambarella_irq(int irq, void *dev_id)
> +{
> + struct uart_port *port = dev_id;
> + u32 ii;
> +
> + scoped_guard(uart_port_lock_irqsave, port) {
> + ii = readl_relaxed(port->membase + UART_II_OFFSET);
> + switch (ii & 0x0F) {
[ ... ]
> + case UART_II_NO_INT_PENDING:
> + break;
> + default:
> + pr_debug("%s: 0x%x\n", __func__, ii);
> + break;
> + }
> + }
> +
> + return IRQ_HANDLED;
> +}
[Severity: High]
Does returning IRQ_HANDLED unconditionally for UART_II_NO_INT_PENDING break
the spurious interrupt detection in the kernel?
If the Interrupt Identification Register evaluates to no pending interrupt
or an unknown value, the handler breaks out of the switch and falls
through to unconditionally return IRQ_HANDLED.
Returning that an interrupt was handled when none was actually serviced
prevents the IRQ core from detecting an interrupt storm and disabling the
faulty IRQ line, which can lead to system lockups.
[ ... ]
> +static void serial_ambarella_hw_deinit(struct ambarella_uart_port *amb_port)
> +{
> + struct uart_port *port = &amb_port->port;
> +
> + /* Disable interrupts */
> + serial_ambarella_ier_write(port, 0);
> +
> + /* Reset the Rx and Tx FIFOs */
> + writel_relaxed(UART_FCR_CLEAR_XMIT | UART_FCR_CLEAR_RCVR,
> + port->membase + UART_SRR_OFFSET);
> +}
[Severity: Low]
Should these FIFO reset commands be written to the FIFO Control Register
rather than the Software Reset Register?
The code writes UART_FCR_CLEAR_XMIT and UART_FCR_CLEAR_RCVR to
UART_SRR_OFFSET. While the bit values might overlap, mixing FIFO control
macros with the software reset register seems semantically incorrect and
might be a typo for UART_FC_OFFSET.
[ ... ]
> +static int serial_ambarella_startup(struct uart_port *port)
> +{
> + int rval;
> + struct ambarella_uart_port *amb_port = to_ambarella_uart_port(port);
> +
> + serial_ambarella_hw_setup(port);
> +
> + rval = request_irq(port->irq, serial_ambarella_irq, IRQF_TRIGGER_HIGH,
> + dev_name(amb_port->port.dev), &amb_port->port);
> + if (rval < 0) {
> + dev_err(amb_port->port.dev,
> + "Failed to register ISR for IRQ %d\n", port->irq);
> + serial_ambarella_hw_deinit(amb_port);
> + return rval;
> + }
> +
> + serial_ambarella_ier_write(port, DEFAULT_AMBARELLA_UART_IER);
> +
> + return 0;
> +}
[Severity: High]
Could writing to the IER register here create a race condition with the
newly registered interrupt handler?
The request_irq() call unmasks the interrupt line. An interrupt can fire
immediately, allowing serial_ambarella_irq() to execute concurrently on
another CPU and modify the IER state under the port lock.
Because serial_ambarella_ier_write() is then called without acquiring the
port lock, this unprotected read-modify-write can clobber changes made by
the interrupt handler.
[ ... ]
> +static void serial_ambarella_console_write_atomic(struct console *co,
> + struct nbcon_write_context *wctxt)
> +{
> + struct ambarella_uart_port *amb_port = &ambarella_port[co->index];
> + struct uart_port *port = &amb_port->port;
> +
> + if (port->suspended)
> + return;
> +
> + if (!nbcon_enter_unsafe(wctxt))
> + return;
> +
> + if (!amb_port->console_line_ended)
> + uart_console_write(port, "\n", 1, serial_ambarella_console_putchar);
> + uart_console_write(port, wctxt->outbuf, wctxt->len,
> + serial_ambarella_console_putchar);
> + wait_for_tx(port);
> +
> + nbcon_exit_unsafe(wctxt);
> +}
[Severity: Medium]
Does this atomic write sequence need to check and clear the DLAB bit?
The write_atomic console callback is designed to preempt any context,
including NMIs, and writes characters directly to UART_TH_OFFSET. In
8250-compatible UARTs, this offset only acts as the Transmit Holding
register if the Divisor Latch Access Bit (DLAB) is cleared.
If a kernel panic occurs while serial_ambarella_set_termios() is executing
with DLAB set, this atomic write could preempt it and write the panic
message characters into the Divisor Latch register instead, corrupting the
baud rate and dropping the panic logs.
[ ... ]
> +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;
[ ... ]
> + 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);
[Severity: High]
Is there a risk of multiple devices overwriting the same global port
structure?
The driver gets the serial alias id and assigns it directly to amb_port
without verifying if that slot in ambarella_port is already in use.
If a malformed Device Tree assigns the same alias to multiple UART nodes,
the second probe will overwrite the first device's devm-managed resources.
When the second probe eventually fails in uart_add_one_port(), devres will
free those pointers, leaving the active first device with a freed membase
pointer and causing a use-after-free on the next interrupt.
[ ... ]
> +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);
> +}
[Severity: Medium]
Does this removal path need to clear the AMBA_UART_RESET_FLAG?
During driver bind, serial_ambarella_hw_setup() sets AMBA_UART_RESET_FLAG
in the static ambarella_port array to ensure the hardware resets only once.
However, this flag is not cleared when the driver is unbound.
If the driver is unbound, devres disables the clock. On a subsequent
re-bind, the hardware clock is re-enabled from a gated state and needs a
reset, but the setup function will skip it because the old flag is still
set.
[ ... ]
> +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);
> +}
[Severity: High]
Can this hardware setup race with concurrent console writes during resume?
Calling serial_ambarella_hw_setup() resets the UART and clears its FIFOs.
This is performed without acquiring the port lock or synchronizing with
the console.
When no_console_suspend is used, the console remains active during resume.
A concurrent printk from another CPU could invoke atomic console routines
and attempt to write characters to the UART while it is being held in
reset or its FIFOs are being cleared.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260806-longzhao-upstream-cv75-v2-v2-0-6b09707c5fe9@ambarella.com?part=8
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v2 09/10] arm64: ambarella: add ARCH_AMBARELLA and CV75 EVK DT
2026-08-06 9:34 ` [PATCH v2 09/10] arm64: ambarella: add ARCH_AMBARELLA and CV75 EVK DT Long Zhao via B4 Relay
@ 2026-08-06 9:51 ` sashiko-bot
2026-08-07 6:11 ` Krzysztof Kozlowski
1 sibling, 0 replies; 27+ messages in thread
From: sashiko-bot @ 2026-08-06 9:51 UTC (permalink / raw)
To: Long Zhao; +Cc: robh, devicetree, conor+dt
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] ARCH_AMBARELLA selects non-existent 32-bit ARM Kconfig symbols (ARM_PSCI and HAVE_ARM_ARCH_TIMER) on an arm64 build.
--
--- Patch [9]: [PATCH v2 09/10] arm64: ambarella: add ARCH_AMBARELLA and CV75 EVK DT ---
commit 30d61a45a9c8eee14cc764b1f10b92bd0992454f
Author: Long Zhao <longzhao@ambarella.com>
arm64: ambarella: add ARCH_AMBARELLA and CV75 EVK DT
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.
> diff --git a/arch/arm64/Kconfig.platforms b/arch/arm64/Kconfig.platforms
> index d2acfac730037..166430ddb3c93 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
[Severity: Low]
Are these Kconfig selections intended?
The ARM_PSCI and HAVE_ARM_ARCH_TIMER symbols appear to be defined only in the
32-bit arch/arm/Kconfig tree and do not exist in the arm64 Kconfig. Because
they do not exist here, selecting them has no effect.
The 64-bit equivalents (ARM_PSCI_FW and ARM_ARCH_TIMER) are already
unconditionally selected by the top-level config ARM64.
> + select PINCTRL
> + select PINCTRL_AMB
> + help
> + This enables support for the ARM64 based Ambarella SoCs like CV75.
> +
> config ARCH_ALPINE
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260806-longzhao-upstream-cv75-v2-v2-0-6b09707c5fe9@ambarella.com?part=9
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v2 03/10] dt-bindings: clock: add Ambarella CV75 RCT clock controller
2026-08-06 9:34 ` [PATCH v2 03/10] dt-bindings: clock: add Ambarella CV75 RCT clock controller Long Zhao via B4 Relay
2026-08-06 9:42 ` sashiko-bot
@ 2026-08-06 10:56 ` Rob Herring (Arm)
2026-08-07 6:02 ` Krzysztof Kozlowski
2 siblings, 0 replies; 27+ messages in thread
From: Rob Herring (Arm) @ 2026-08-06 10:56 UTC (permalink / raw)
To: Long Zhao
Cc: linux-gpio, Alexandre Belloni, Jiri Slaby, Greg Kroah-Hartman,
Conor Dooley, linux-arm-kernel, linux-serial, Linus Walleij,
linux-kernel, soc, Catalin Marinas, linux-clk, Arnd Bergmann,
Stephen Boyd, Michael Turquette, Will Deacon, devicetree,
Krzysztof Kozlowski
On Thu, 06 Aug 2026 17:34:11 +0800, Long Zhao wrote:
> Add the CV75 RCT clock controller binding and clock ID header.
>
> Signed-off-by: Long Zhao <longzhao@ambarella.com>
> ---
> .../bindings/clock/ambarella,cv75-rct.yaml | 66 ++++++++++++++++++++++
> include/dt-bindings/clock/ambarella,cv75-clock.h | 17 ++++++
> 2 files changed, 83 insertions(+)
>
My bot found errors running 'make dt_binding_check' on your patch:
yamllint warnings/errors:
dtschema/dtc warnings/errors:
Documentation/devicetree/bindings/display/mediatek/mediatek,mt8196-tdshp.example.dtb: tdshp@321e0000 (mediatek,mt8196-disp-tdshp): 'clock-names' is a required property
from schema $id: http://devicetree.org/schemas/display/mediatek/mediatek,mt8196-tdshp.yaml
Documentation/devicetree/bindings/display/imx/fsl,imx-lcdc.example.dtb: display0: fsl,pcr: b'\xf0\xc8\x80\x80' is not of type 'object', 'integer', 'array', 'boolean', 'null'
from schema $id: http://devicetree.org/schemas/dt-core.yaml
doc reference errors (make refcheckdocs):
See https://patchwork.kernel.org/project/devicetree/patch/20260806-longzhao-upstream-cv75-v2-v2-3-6b09707c5fe9@ambarella.com
The base for the series is generally the latest rc1. A different dependency
should be noted in *this* patch.
If you already ran 'make dt_binding_check' and didn't see the above
error(s), then make sure 'yamllint' is installed and dt-schema is up to
date:
pip3 install dtschema --upgrade
Please check and re-submit after running the above command yourself. Note
that DT_SCHEMA_FILES can be set to your schema file to speed up checking
your schema. However, it must be unset to test all examples with your schema.
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v2 01/10] dt-bindings: arm: add Ambarella CV75 platforms
2026-08-06 9:34 ` [PATCH v2 01/10] dt-bindings: arm: add Ambarella CV75 platforms Long Zhao via B4 Relay
@ 2026-08-07 6:00 ` Krzysztof Kozlowski
0 siblings, 0 replies; 27+ messages in thread
From: Krzysztof Kozlowski @ 2026-08-07 6:00 UTC (permalink / raw)
To: Long Zhao
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
Stephen Boyd, Linus Walleij, Greg Kroah-Hartman, Jiri Slaby,
Catalin Marinas, Will Deacon, Arnd Bergmann, Alexandre Belloni,
devicetree, linux-arm-kernel, linux-kernel, linux-clk, linux-gpio,
linux-serial, soc
On Thu, Aug 06, 2026 at 05:34:09PM +0800, Long Zhao wrote:
> Add the Ambarella SoC board binding and vendor prefix covering
> the CV75 EVK.
>
> Signed-off-by: Long Zhao <longzhao@ambarella.com>
> ---
> .../devicetree/bindings/arm/ambarella.yaml | 23 ++++++++++++++++++++++
> .../devicetree/bindings/vendor-prefixes.yaml | 2 ++
> 2 files changed, 25 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/arm/ambarella.yaml b/Documentation/devicetree/bindings/arm/ambarella.yaml
> new file mode 100644
> index 000000000000..347148530f80
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/arm/ambarella.yaml
> @@ -0,0 +1,23 @@
> +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/arm/ambarella.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Ambarella SoC platforms
> +
> +maintainers:
> + - Long Zhao <longzhao@ambarella.com>
> +
> +properties:
> + $nodename:
> + const: "/"
> +
> + compatible:
> + oneOf:
> + - description: Ambarella CV75 EVK board
> + items:
> + - const: ambarella,cv75-evk
This should be enum, it will grow with other boards, no?
Anyway,
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v2 02/10] dt-bindings: soc: add Ambarella secure scratchpad
2026-08-06 9:34 ` [PATCH v2 02/10] dt-bindings: soc: add Ambarella secure scratchpad Long Zhao via B4 Relay
@ 2026-08-07 6:01 ` Krzysztof Kozlowski
2026-08-07 8:29 ` Long Zhao
0 siblings, 1 reply; 27+ messages in thread
From: Krzysztof Kozlowski @ 2026-08-07 6:01 UTC (permalink / raw)
To: Long Zhao
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
Stephen Boyd, Linus Walleij, Greg Kroah-Hartman, Jiri Slaby,
Catalin Marinas, Will Deacon, Arnd Bergmann, Alexandre Belloni,
devicetree, linux-arm-kernel, linux-kernel, linux-clk, linux-gpio,
linux-serial, soc
On Thu, Aug 06, 2026 at 05:34:10PM +0800, Long Zhao wrote:
> Document the Ambarella secure scratchpad syscon used by CV75.
>
> Signed-off-by: Long Zhao <longzhao@ambarella.com>
> ---
> .../ambarella/ambarella,cv75-sec-scratchpad.yaml | 39 ++++++++++++++++++++++
> 1 file changed, 39 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/soc/ambarella/ambarella,cv75-sec-scratchpad.yaml b/Documentation/devicetree/bindings/soc/ambarella/ambarella,cv75-sec-scratchpad.yaml
> new file mode 100644
> index 000000000000..4bfeca0ac3da
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/soc/ambarella/ambarella,cv75-sec-scratchpad.yaml
Why do you need separate binding? What is incomplete here that it cannot
go to syscon.yaml?
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v2 03/10] dt-bindings: clock: add Ambarella CV75 RCT clock controller
2026-08-06 9:34 ` [PATCH v2 03/10] dt-bindings: clock: add Ambarella CV75 RCT clock controller Long Zhao via B4 Relay
2026-08-06 9:42 ` sashiko-bot
2026-08-06 10:56 ` Rob Herring (Arm)
@ 2026-08-07 6:02 ` Krzysztof Kozlowski
2 siblings, 0 replies; 27+ messages in thread
From: Krzysztof Kozlowski @ 2026-08-07 6:02 UTC (permalink / raw)
To: Long Zhao
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
Stephen Boyd, Linus Walleij, Greg Kroah-Hartman, Jiri Slaby,
Catalin Marinas, Will Deacon, Arnd Bergmann, Alexandre Belloni,
devicetree, linux-arm-kernel, linux-kernel, linux-clk, linux-gpio,
linux-serial, soc
On Thu, Aug 06, 2026 at 05:34:11PM +0800, Long Zhao wrote:
> Add the CV75 RCT clock controller binding and clock ID header.
>
> Signed-off-by: Long Zhao <longzhao@ambarella.com>
> ---
> .../bindings/clock/ambarella,cv75-rct.yaml | 66 ++++++++++++++++++++++
> include/dt-bindings/clock/ambarella,cv75-clock.h | 17 ++++++
> 2 files changed, 83 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/clock/ambarella,cv75-rct.yaml b/Documentation/devicetree/bindings/clock/ambarella,cv75-rct.yaml
> new file mode 100644
> index 000000000000..aa96533bab9b
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/clock/ambarella,cv75-rct.yaml
> @@ -0,0 +1,66 @@
> +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/clock/ambarella,cv75-rct.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Ambarella CV75 RCT Clock Controller
> +
> +maintainers:
> + - Long Zhao <longzhao@ambarella.com>
> +
> +description: |
> + The RCT (Reset and Clock Tree) block on the Ambarella CV75 SoC provides
> + PLLs, clock muxes and dividers for on-chip peripherals.
> +
> + The same register region is also exposed as a syscon for other SoC
> + blocks (e.g. pin drive-strength), hence the "syscon" compatible.
> +
> + Clock indices are defined in
> + include/dt-bindings/clock/ambarella,cv75-clock.h:
> +
> + - CV75_GCLK_UART0: UART0 mux/div clock
> + - CV75_GCLK_CORE: Core PLL (gclk_core)
> + - CV75_GCLK_AHB: AHB bus clock (core / 2)
> + - CV75_GCLK_APB: APB bus clock (core / 4)
> +
> +properties:
> + $nodename:
> + pattern: "^clock-controller@"
Drop nodename.
> +
> + compatible:
> + items:
> + - const: ambarella,cv75-rct
> + - const: syscon
> +
> + reg:
> + maxItems: 1
> +
> + clocks:
> + description: Reference crystal oscillator (typically 24 MHz)
> + maxItems: 1
> +
> + clock-names:
> + const: osc
> +
> + "#clock-cells":
> + const: 1
> +
> +required:
> + - compatible
> + - reg
> + - clocks
> + - clock-names
> + - "#clock-cells"
> +
> +additionalProperties: false
> +
> +examples:
> + - |
> + clock-controller@ed080000 {
> + compatible = "ambarella,cv75-rct", "syscon";
> + reg = <0xed080000 0x1000>;
> + clocks = <&osc>;
> + clock-names = "osc";
> + #clock-cells = <1>;
> + };
> diff --git a/include/dt-bindings/clock/ambarella,cv75-clock.h b/include/dt-bindings/clock/ambarella,cv75-clock.h
> new file mode 100644
> index 000000000000..3d3314446e75
> --- /dev/null
> +++ b/include/dt-bindings/clock/ambarella,cv75-clock.h
Filenames must be the same.
> @@ -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
Drop, not a binding.
> +
> +#endif
>
> --
> 2.34.1
>
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v2 04/10] dt-bindings: pinctrl: add Ambarella CV75 pinctrl
2026-08-06 9:34 ` [PATCH v2 04/10] dt-bindings: pinctrl: add Ambarella CV75 pinctrl Long Zhao via B4 Relay
2026-08-06 9:41 ` sashiko-bot
@ 2026-08-07 6:06 ` Krzysztof Kozlowski
1 sibling, 0 replies; 27+ messages in thread
From: Krzysztof Kozlowski @ 2026-08-07 6:06 UTC (permalink / raw)
To: Long Zhao
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
Stephen Boyd, Linus Walleij, Greg Kroah-Hartman, Jiri Slaby,
Catalin Marinas, Will Deacon, Arnd Bergmann, Alexandre Belloni,
devicetree, linux-arm-kernel, linux-kernel, linux-clk, linux-gpio,
linux-serial, soc
On Thu, Aug 06, 2026 at 05:34:12PM +0800, Long Zhao wrote:
> Add the CV75 pin controller binding.
>
> Signed-off-by: Long Zhao <longzhao@ambarella.com>
> ---
> .../bindings/pinctrl/ambarella,cv75-pinctrl.yaml | 175 +++++++++++++++++++++
> 1 file changed, 175 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/pinctrl/ambarella,cv75-pinctrl.yaml b/Documentation/devicetree/bindings/pinctrl/ambarella,cv75-pinctrl.yaml
> new file mode 100644
> index 000000000000..3fa30b275e51
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/pinctrl/ambarella,cv75-pinctrl.yaml
> @@ -0,0 +1,175 @@
> +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/pinctrl/ambarella,cv75-pinctrl.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Ambarella CV75 Pin Controller
> +
> +maintainers:
> + - Long Zhao <longzhao@ambarella.com>
> +
> +description:
> + The Ambarella CV75 pin controller configures pin multiplexing, bias and
> + drive strength for the main GPIO banks. Pin groups and their mux settings
> + are defined by the pin controller driver and selected by function and group
> + names.
> +
> +allOf:
> + - $ref: pinctrl.yaml#
> +
> +properties:
> + compatible:
> + const: ambarella,cv75-pinctrl
> +
> + reg:
> + maxItems: 1
> + description: IOMUX registers
> +
> + ambarella,drive-strength-syscon:
> + $ref: /schemas/types.yaml#/definitions/phandle
> + description: RCT syscon containing the drive-strength registers.
> +
> + ambarella,pull-syscon:
> + $ref: /schemas/types.yaml#/definitions/phandle
> + description: Secure scratchpad syscon containing the pull registers.
> +
> + "#address-cells":
> + const: 1
> +
> + "#size-cells":
> + const: 1
> +
> + ranges: true
> +
> +patternProperties:
> + "^gpio@[0-9a-f]+$":
> + type: object
> + additionalProperties: false
> +
> + properties:
> + reg:
> + maxItems: 1
> +
> + interrupts:
> + maxItems: 1
> +
> + gpio-controller: true
> +
> + "#gpio-cells":
> + const: 2
> +
> + gpio-ranges:
> + maxItems: 1
> +
> + interrupt-controller: true
> +
> + "#interrupt-cells":
> + const: 2
> +
> + required:
> + - reg
> + - interrupts
> + - gpio-controller
> + - "#gpio-cells"
> + - gpio-ranges
> + - interrupt-controller
> + - "#interrupt-cells"
> +
> + "^[a-z0-9-]+-pins$":
Mixing MMIO and non-MMIO nodes in a bus is discouraged. Sloppy code.
Why the pins aren't per gpio-controller?
> + type: object
> + allOf:
> + - $ref: pinmux-node.yaml#
> + - $ref: pincfg-node.yaml#
> + additionalProperties: false
> +
> + properties:
> + function:
> + const: uart0
If the function is fixed, then you do not need any of this.
You must post COMPLETE bindings, see writing bindings doc.
> +
> + groups:
> + $ref: /schemas/types.yaml#/definitions/string-array
> + items:
> + enum: [uart0tx, uart0rx]
Same here, pointless/redunadnt.
> + minItems: 2
> + maxItems: 2
> + uniqueItems: true
> +
> + bias-disable: true
> + bias-pull-down: true
> + bias-pull-up: true
> +
> + drive-strength:
> + enum: [3, 4, 5, 6, 7, 8, 9, 12]
> +
> + required:
> + - function
> + - groups
> +
> +required:
> + - compatible
> + - reg
> + - ambarella,drive-strength-syscon
> + - ambarella,pull-syscon
> + - "#address-cells"
> + - "#size-cells"
> + - ranges
> +
> +unevaluatedProperties: false
> +
> +examples:
> + - |
> + #include <dt-bindings/interrupt-controller/arm-gic.h>
> +
> + rct: rct {
> + };
> +
> + scratchpad: scratchpad {
> + };
Drop above, irrelevant. Do you see any other bindings doing this?
> +
> + pinctrl: pinctrl@e4010000 {
> + compatible = "ambarella,cv75-pinctrl";
> + #address-cells = <1>;
> + #size-cells = <1>;
You need to follow DTS coding style.
> + reg = <0xe4010000 0x1000>;
> + ranges;
> + ambarella,drive-strength-syscon = <&rct>;
> + ambarella,pull-syscon = <&scratchpad>;
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v2 09/10] arm64: ambarella: add ARCH_AMBARELLA and CV75 EVK DT
2026-08-06 9:34 ` [PATCH v2 09/10] arm64: ambarella: add ARCH_AMBARELLA and CV75 EVK DT Long Zhao via B4 Relay
2026-08-06 9:51 ` sashiko-bot
@ 2026-08-07 6:11 ` Krzysztof Kozlowski
1 sibling, 0 replies; 27+ messages in thread
From: Krzysztof Kozlowski @ 2026-08-07 6:11 UTC (permalink / raw)
To: Long Zhao
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
Stephen Boyd, Linus Walleij, Greg Kroah-Hartman, Jiri Slaby,
Catalin Marinas, Will Deacon, Arnd Bergmann, Alexandre Belloni,
devicetree, linux-arm-kernel, linux-kernel, linux-clk, linux-gpio,
linux-serial, soc
On Thu, Aug 06, 2026 at 05:34:17PM +0800, Long Zhao wrote:
> 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.
Subject - missing dts prefix. Please do not come with your own style.
It's for example: arm64: dts: ambarella:
>
> Signed-off-by: Long Zhao <longzhao@ambarella.com>
> ---
> arch/arm64/Kconfig.platforms | 9 ++
> arch/arm64/boot/dts/Makefile | 1 +
> arch/arm64/boot/dts/ambarella/Makefile | 3 +
> arch/arm64/boot/dts/ambarella/cv75-evk.dts | 30 ++++++
> arch/arm64/boot/dts/ambarella/cv75.dtsi | 166 +++++++++++++++++++++++++++++
> arch/arm64/configs/defconfig | 3 +
> 6 files changed, 212 insertions(+)
>
> diff --git a/arch/arm64/Kconfig.platforms b/arch/arm64/Kconfig.platforms
> index d2acfac73003..166430ddb3c9 100644
> --- a/arch/arm64/Kconfig.platforms
> +++ b/arch/arm64/Kconfig.platforms
> @@ -26,6 +26,15 @@ config ARCH_SUNXI
> help
> This enables support for Allwinner sunxi based SoCs like the A64.
>
> +config ARCH_AMBARELLA
> + bool "Ambarella SoC family"
> + select ARM_PSCI
> + select HAVE_ARM_ARCH_TIMER
> + select PINCTRL
> + select PINCTRL_AMB
> + help
> + This enables support for the ARM64 based Ambarella SoCs like CV75.
> +
> config ARCH_ALPINE
> bool "Annapurna Labs Alpine platform"
> select ALPINE_MSI if PCI
> diff --git a/arch/arm64/boot/dts/Makefile b/arch/arm64/boot/dts/Makefile
> index fc726b215f12..f1709eb5d915 100644
> --- a/arch/arm64/boot/dts/Makefile
> +++ b/arch/arm64/boot/dts/Makefile
> @@ -4,6 +4,7 @@ subdir-y += airoha
> subdir-y += allwinner
> subdir-y += altera
> subdir-y += amazon
> +subdir-y += ambarella
> subdir-y += amd
> subdir-y += amlogic
> subdir-y += apm
> diff --git a/arch/arm64/boot/dts/ambarella/Makefile b/arch/arm64/boot/dts/ambarella/Makefile
> new file mode 100644
> index 000000000000..ddca3d7b18cc
> --- /dev/null
> +++ b/arch/arm64/boot/dts/ambarella/Makefile
> @@ -0,0 +1,3 @@
> +# SPDX-License-Identifier: GPL-2.0
> +
> +dtb-$(CONFIG_ARCH_AMBARELLA) += cv75-evk.dtb
> diff --git a/arch/arm64/boot/dts/ambarella/cv75-evk.dts b/arch/arm64/boot/dts/ambarella/cv75-evk.dts
> new file mode 100644
> index 000000000000..42a0fe24b83b
> --- /dev/null
> +++ b/arch/arm64/boot/dts/ambarella/cv75-evk.dts
> @@ -0,0 +1,30 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Ambarella CV75 EVK board
> + */
> +
> +/dts-v1/;
> +
> +#include "cv75.dtsi"
> +
> +/ {
> + model = "Ambarella CV75 EVK Board";
> + compatible = "ambarella,cv75-evk", "ambarella,cv75";
> +
> + aliases {
> + serial0 = &uart0;
> + };
> +
> + chosen {
> + stdout-path = &uart0;
> + };
> +
> + memory@200000 {
> + device_type = "memory";
> + reg = <0x0 0x00200000 0x0 0x07e00000>;
> + };
> +};
> +
> +&uart0 {
> + status = "okay";
> +};
> diff --git a/arch/arm64/boot/dts/ambarella/cv75.dtsi b/arch/arm64/boot/dts/ambarella/cv75.dtsi
> new file mode 100644
> index 000000000000..825b773f052f
> --- /dev/null
> +++ b/arch/arm64/boot/dts/ambarella/cv75.dtsi
> @@ -0,0 +1,166 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Ambarella CV75 SoC
> + *
> + * Boot peripheral: UART0 console.
> + * Clocks: a single ambarella,cv75-rct provider.
> + */
> +
> +#include <dt-bindings/clock/ambarella,cv75-clock.h>
> +#include <dt-bindings/interrupt-controller/arm-gic.h>
> +
> +/ {
> + compatible = "ambarella,cv75";
> + interrupt-parent = <&gic>;
> + #address-cells = <2>;
> + #size-cells = <2>;
> +
> + osc: oscillator {
> + compatible = "fixed-clock";
> + #clock-cells = <0>;
> + clock-output-names = "osc";
> + clock-frequency = <24000000>;
> + };
> +
> + psci {
This looks randomly ordered.
Please follow DTS coding style.
> + compatible = "arm,psci-0.2";
> + method = "smc";
> + };
> +
> + cpus {
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + cpu@0 {
> + compatible = "arm,cortex-a76";
> + device_type = "cpu";
> + reg = <0x000>;
> + enable-method = "psci";
> + };
> +
> + cpu@1 {
> + compatible = "arm,cortex-a76";
> + device_type = "cpu";
> + reg = <0x100>;
> + enable-method = "psci";
> + };
> + };
> +
> + gic: interrupt-controller@fff0101000 {
This cannot be here, it's MMIO node, you have dtbs check warnings
likely. Just look at any other file how it is done.
Please confirm that you have run dtbs_check W=1 and you fixed all the
warnings.
> + compatible = "arm,gic-400";
> + #interrupt-cells = <3>;
> + #address-cells = <0>;
> + #size-cells = <1>;
> + interrupt-controller;
> + reg = <0xff 0xf0101000 0x0 0x1000>,
> + <0xff 0xf0102000 0x0 0x2000>,
> + <0xff 0xf0104000 0x0 0x2000>,
> + <0xff 0xf0106000 0x0 0x2000>;
> + interrupts = <GIC_PPI 9 (GIC_CPU_MASK_SIMPLE(2) | IRQ_TYPE_LEVEL_HIGH)>;
> + };
> +
> + timer {
> + compatible = "arm,armv8-timer";
> + interrupts = <GIC_PPI 13 (GIC_CPU_MASK_SIMPLE(2) | IRQ_TYPE_LEVEL_LOW)>,
> + <GIC_PPI 14 (GIC_CPU_MASK_SIMPLE(2) | IRQ_TYPE_LEVEL_LOW)>,
> + <GIC_PPI 11 (GIC_CPU_MASK_SIMPLE(2) | IRQ_TYPE_LEVEL_LOW)>,
> + <GIC_PPI 10 (GIC_CPU_MASK_SIMPLE(2) | IRQ_TYPE_LEVEL_LOW)>,
> + <GIC_PPI 12 (GIC_CPU_MASK_SIMPLE(2) | IRQ_TYPE_LEVEL_LOW)>;
> + };
> +
> + soc {
> + compatible = "simple-bus";
> + #address-cells = <2>;
> + #size-cells = <2>;
> + ranges;
> +
> + /* Clock provider and shared system registers. */
> + rct: clock-controller@ffed080000 {
> + compatible = "ambarella,cv75-rct", "syscon";
> + reg = <0xff 0xed080000 0x0 0x1000>;
> + clocks = <&osc>;
> + clock-names = "osc";
> + #clock-cells = <1>;
> +
> + assigned-clocks = <&rct CV75_GCLK_UART0>;
> + assigned-clock-parents = <&osc>;
> + assigned-clock-rates = <24000000>;
> + };
> +
> + apb@ffe4000000 {
> + compatible = "simple-bus";
> + #address-cells = <1>;
> + #size-cells = <1>;
> + reg = <0xff 0xe4000000 0x0 0x01000000>;
> + ranges = <0xe4000000 0xff 0xe4000000 0x01000000>;
You need to read DTS coding style.
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v2 08/10] serial: ambarella: add Ambarella UART driver
2026-08-06 9:34 ` [PATCH v2 08/10] serial: ambarella: add Ambarella UART driver Long Zhao via B4 Relay
2026-08-06 9:49 ` sashiko-bot
@ 2026-08-07 6:18 ` Jiri Slaby
2026-08-07 18:37 ` Linus Walleij
2 siblings, 0 replies; 27+ messages in thread
From: Jiri Slaby @ 2026-08-07 6:18 UTC (permalink / raw)
To: longzhao, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Michael Turquette, Stephen Boyd, Linus Walleij,
Greg Kroah-Hartman, Catalin Marinas, Will Deacon, Arnd Bergmann,
Alexandre Belloni
Cc: devicetree, linux-arm-kernel, linux-kernel, linux-clk, linux-gpio,
linux-serial, soc
Hi,
On 06. 08. 26, 11:34, Long Zhao via B4 Relay wrote:
> 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.
...
> 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
I would put it after AMBA_*. That should be also the ascii order, right?
> obj-$(CONFIG_SERIAL_AMBA_PL010) += amba-pl010.o
> obj-$(CONFIG_SERIAL_AMBA_PL011) += amba-pl011.o
> obj-$(CONFIG_SERIAL_GRLIB_GAISLER_APBUART) += apbuart.o
> diff --git a/drivers/tty/serial/ambarella_uart.c b/drivers/tty/serial/ambarella_uart.c
> new file mode 100644
> index 000000000000..7356b242f0ef
> --- /dev/null
> +++ b/drivers/tty/serial/ambarella_uart.c
> @@ -0,0 +1,1001 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +
> +#include <linux/clk.h>
> +#include <linux/console.h>
> +#include <linux/delay.h>
> +#include <linux/io.h>
> +#include <linux/iopoll.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/pinctrl/consumer.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm.h>
> +#include <linux/serial_reg.h>
> +#include <linux/serial_core.h>
> +#include <linux/sysrq.h>
> +#include <linux/tty.h>
> +#include <linux/tty_flip.h>
> +
> +#define UART_RB_OFFSET 0x00
> +#define UART_TH_OFFSET 0x00
> +#define UART_DLL_OFFSET 0x00
> +#define UART_IE_OFFSET 0x04
> +#define UART_DLH_OFFSET 0x04
> +#define UART_II_OFFSET 0x08
> +#define UART_FC_OFFSET 0x08
> +#define UART_LC_OFFSET 0x0c
> +#define UART_MC_OFFSET 0x10
> +#define UART_LS_OFFSET 0x14
> +#define UART_MS_OFFSET 0x18
> +#define UART_US_OFFSET 0x7c
> +#define UART_SRR_OFFSET 0x88
For me, this would be more understadable:
#define UART_IE 0x4
# define UART_IE_ERBFI BIT(0)
...
# define UART_IE_ETOI BIT(6)
# define UART_IE_ERETOI BIT(7)
and so on.
That is:
* define the bits near the offset
* no _OFFSET suffix
* use BIT()
...
> +#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 */
Perhaps convert this one to an enum?
> +/* Poll timeout in microseconds (atomic helpers use udelay). */
> +#define AMBARELLA_UART_TIMEOUT_US 1000000
USEC_PER_SEC
> +static inline void wait_for_tx(struct uart_port *port)
> +{
> + u32 ls;
> + int ret;
> +
> + ret = readl_poll_timeout_atomic(port->membase + UART_LS_OFFSET, ls,
> + ls & UART_LS_TEMT, 1,
> + AMBARELLA_UART_TIMEOUT_US);
> + if (likely(!ret))
How did you measure this "likely" matters?
> + return;
> +
> + /* Recover a stuck TX path so console/poll can continue. */
> + writel_relaxed(UART_FC_RX_2_TO_FULL | UART_FC_TX_EMPTY |
> + UART_FC_XMITR | UART_FC_RCVRR,
> + port->membase + UART_FC_OFFSET);
> + udelay(100);
> + writel_relaxed(UART_FC_FIFOE | UART_FC_RX_2_TO_FULL |
> + UART_FC_TX_EMPTY | UART_FC_XMITR |
> + UART_FC_RCVRR,
> + port->membase + UART_FC_OFFSET);
> +}
...
> +static void serial_ambarella_hw_setup(struct uart_port *port)
> +{
> + struct ambarella_uart_port *amb_port = to_ambarella_uart_port(port);
> +
> + if (!test_and_set_bit(AMBA_UART_RESET_FLAG, &amb_port->flags)) {
> + if (amb_port->uart_pll)
> + port->uartclk = clk_get_rate(amb_port->uart_pll);
> + /* reset the whole UART only once */
> + writel_relaxed(0x01, port->membase + UART_SRR_OFFSET);
Could you document also this bit in SRR (by a macro)?
> + mdelay(1);
1 ms of spinning? That's very bad. Why this cannot be a sleep instead?
> + writel_relaxed(0x00, port->membase + UART_SRR_OFFSET);
> + }
> +
> + writel_relaxed(UART_FC_FIFOE | UART_FC_RX_2_TO_FULL | UART_FC_TX_EMPTY |
> + UART_FC_XMITR | UART_FC_RCVRR, port->membase + UART_FC_OFFSET);
> + /* Keep interrupts disabled until the IRQ handler is registered. */
> + serial_ambarella_ier_write(port, 0);
> +}
...> +static void serial_ambarella_transmit_chars(struct uart_port *port)
Any reason not to use uart_port_tx_limited()?
> +{
> + struct tty_port *tport = &port->state->port;
> + int count;
> +
> + if (port->x_char) {
> + writel_relaxed(port->x_char, port->membase + UART_TH_OFFSET);
> + port->icount.tx++;
> + port->x_char = 0;
> + return;
> + }
> +
> + if (uart_tx_stopped(port) || kfifo_is_empty(&tport->xmit_fifo)) {
> + __serial_ambarella_stop_tx(port);
> + return;
> + }
> +
> + count = port->fifosize;
> + while (count-- > 0) {
> + unsigned char c;
> +
> + if (tx_fifo_is_full(port))
> + break;
> +
> + if (!kfifo_peek(&tport->xmit_fifo, &c))
> + break;
> +
> + writel_relaxed(c, port->membase + UART_TH_OFFSET);
> + kfifo_skip(&tport->xmit_fifo);
> + port->icount.tx++;
> + if (kfifo_is_empty(&tport->xmit_fifo))
> + break;
> + }
> +
> + if (kfifo_len(&tport->xmit_fifo) < WAKEUP_CHARS)
> + uart_write_wakeup(port);
> + if (kfifo_is_empty(&tport->xmit_fifo))
> + __serial_ambarella_stop_tx(port);
> +}
> +
> +static inline void serial_ambarella_check_modem_status(struct uart_port *port)
> +{
> + u32 ms;
> +
> + ms = __serial_ambarella_read_ms(port);
> +
> + if (ms & UART_MS_RI)
> + port->icount.rng++;
> + if (ms & UART_MS_DSR)
> + port->icount.dsr++;
> + if (ms & UART_MS_DCTS)
> + uart_handle_cts_change(port, (ms & UART_MS_CTS));
> + if (ms & UART_MS_DDCD)
> + uart_handle_dcd_change(port, (ms & UART_MS_DCD));
> +
> + wake_up_interruptible(&port->state->port.delta_msr_wait);
> +}
> +
> +static irqreturn_t serial_ambarella_irq(int irq, void *dev_id)
> +{
> + struct uart_port *port = dev_id;
> + u32 ii;
> +
> + scoped_guard(uart_port_lock_irqsave, port) {
This needs not to be scoped. Just guard().
> + ii = readl_relaxed(port->membase + UART_II_OFFSET);
> + switch (ii & 0x0F) {
> + case UART_II_MODEM_STATUS_CHANGED:
> + serial_ambarella_check_modem_status(port);
> + break;
> + case UART_II_THR_EMPTY:
> + serial_ambarella_transmit_chars(port);
> + break;
> + case UART_II_RCV_STATUS:
> + case UART_II_RCV_DATA_AVAIL:
> + serial_ambarella_receive_chars(port, 0);
> + break;
> + case UART_II_CHAR_TIMEOUT_FIFO_EMPTY:
> + /* Clear ERETOI to dismiss timeout-with-empty-FIFO IRQ */
> + serial_ambarella_ier_toggle(port, UART_IE_ERETOI);
> + fallthrough;
> + case UART_II_CHAR_TIMEOUT:
> + serial_ambarella_receive_chars(port, 1);
Hmm, the last param looks like a bool. Why do you have tmo declared as u32?
> + break;
> + case UART_II_NO_INT_PENDING:
> + break;
> + default:
> + pr_debug("%s: 0x%x\n", __func__, ii);
Are you sure you want to dump the unknown irq for every interrupt (on
DEBUG)?
> + break;
> + }
> + }
> +
> + return IRQ_HANDLED;
> +}
...> +static unsigned int serial_ambarella_tx_empty(struct uart_port *port)
> +{
> + unsigned int lsr;
> +
> + guard(uart_port_lock_irqsave)(port);
> + lsr = readl_relaxed(port->membase + UART_LS_OFFSET);
u32 lsr = ...
No need for the previous declaration.
> +
> + return ((lsr & (UART_LS_TEMT | UART_LS_THRE)) ==
> + (UART_LS_TEMT | UART_LS_THRE)) ? TIOCSER_TEMT : 0;
> +}
...
> +static void serial_ambarella_set_termios(struct uart_port *port,
> + struct ktermios *termios,
> + const struct ktermios *old)
> +{
> + struct ambarella_uart_port *amb_port = to_ambarella_uart_port(port);
> + unsigned int baud, quot;
> + u32 lc = 0x0;
> +
> + port->uartclk = clk_get_rate(amb_port->uart_pll);
> + switch (termios->c_cflag & CSIZE) {
> + case CS5:
> + lc |= UART_LC_CLS_5_BITS;
> + break;
> + case CS6:
> + lc |= UART_LC_CLS_6_BITS;
> + break;
> + case CS7:
> + lc |= UART_LC_CLS_7_BITS;
> + break;
> + case CS8:
> + default:
> + lc |= UART_LC_CLS_8_BITS;
> + break;
> + }
> +
> + if (termios->c_cflag & CSTOPB)
> + lc |= UART_LC_STOP_2BIT;
> + else
> + lc |= UART_LC_STOP_1BIT;
> +
> + if (termios->c_cflag & PARENB) {
> + if (termios->c_cflag & PARODD)
> + lc |= (UART_LC_PEN | UART_LC_ODD_PARITY);
> + else
> + lc |= (UART_LC_PEN | UART_LC_EVEN_PARITY);
> + }
> +
> + baud = uart_get_baud_rate(port, termios, old, 0, port->uartclk / 16);
> + quot = uart_get_divisor(port, baud);
> +
> + scoped_guard(uart_port_lock_irqsave, port) {
No need for scoped.
> + uart_update_timeout(port, termios->c_cflag, baud);
> +
> + port->read_status_mask = UART_LSR_OE | UART_LSR_THRE | UART_LSR_DR;
> + if (termios->c_iflag & INPCK)
> + port->read_status_mask |= UART_LSR_FE | UART_LSR_PE;
> + if (termios->c_iflag & (BRKINT | PARMRK))
> + port->read_status_mask |= UART_LSR_BI;
> +
> + port->ignore_status_mask = 0;
> + if (termios->c_iflag & IGNPAR)
> + port->ignore_status_mask |= UART_LSR_PE | UART_LSR_FE;
> + if (termios->c_iflag & IGNBRK) {
> + port->ignore_status_mask |= UART_LSR_BI;
> + if (termios->c_iflag & IGNPAR)
> + port->ignore_status_mask |= UART_LSR_OE;
> + }
> + if ((termios->c_cflag & CREAD) == 0)
> + port->ignore_status_mask |= UART_LSR_DR;
> +
> + if ((termios->c_cflag & CRTSCTS) == 0) {
> + amb_port->mcr &= ~UART_MC_AFCE;
> + port->status &= ~UPSTAT_AUTOCTS;
> + } else {
> + amb_port->mcr |= UART_MC_AFCE;
> + port->status |= UPSTAT_AUTOCTS;
> + }
> +
> + writel_relaxed(UART_LC_DLAB, port->membase + UART_LC_OFFSET);
> + writel_relaxed(quot & 0xff, port->membase + UART_DLL_OFFSET);
> + writel_relaxed((quot >> 8) & 0xff, port->membase + UART_DLH_OFFSET);
> + writel_relaxed(lc, port->membase + UART_LC_OFFSET);
> + if (UART_ENABLE_MS(port, termios->c_cflag))
> + __serial_ambarella_enable_ms(port);
> + else
> + __serial_ambarella_disable_ms(port);
> + serial_ambarella_set_mctrl(port, port->mctrl);
> + }
> +}
> +
> +static void serial_ambarella_pm(struct uart_port *port,
> + unsigned int state, unsigned int oldstate)
> +{
> +}
> +
> +static void serial_ambarella_release_port(struct uart_port *port)
> +{
> +}
> +
> +static int serial_ambarella_request_port(struct uart_port *port)
> +{
> + return 0;
> +}
> +
> +static void serial_ambarella_config_port(struct uart_port *port, int flags)
> +{
> +}
No need for empty definitions. The hooks are optional. We should
document this, likely.
> +static int serial_ambarella_verify_port(struct uart_port *port,
> + struct serial_struct *ser)
> +{
> + int rval = 0;
You can return immediately, without the need for this variable, right?
> +
> + 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 int serial_ambarella_probe(struct platform_device *pdev)
> +{
> + struct ambarella_uart_port *amb_port;
> + struct resource *mem;
> + struct pinctrl *pinctrl;
> + int irq, id, rval;
> +
> + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + if (!mem) {
> + dev_err(&pdev->dev, "no mem resource!\n");
> + return -ENODEV;
> + }
> +
> + irq = platform_get_irq(pdev, 0);
> + if (irq < 0) {
> + dev_err(&pdev->dev, "no irq resource!\n");
> + return -ENODEV;
> + }
> +
> + id = of_alias_get_id(pdev->dev.of_node, "serial");
> + if (id < 0 || id >= serial_ambarella_reg.nr) {
> + dev_err(&pdev->dev, "Invalid uart ID %d!\n", id);
> + return -ENXIO;
> + }
> +
> + pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
> + if (IS_ERR(pinctrl)) {
> + dev_err(&pdev->dev, "Failed to request pinctrl\n");
> + return PTR_ERR(pinctrl);
> + }
> +
> + amb_port = &ambarella_port[id];
> +
> + amb_port->uart_pll = devm_clk_get_enabled(&pdev->dev, NULL);
> + if (IS_ERR(amb_port->uart_pll)) {
> + dev_err(&pdev->dev, "Get uart clk failed!\n");
> + return PTR_ERR(amb_port->uart_pll);
> + }
> +
> + amb_port->mcr = DEFAULT_AMBARELLA_UART_MCR;
> +
> + amb_port->port.dev = &pdev->dev;
> + amb_port->port.type = PORT_UART00;
> + amb_port->port.iotype = UPIO_MEM;
> + amb_port->port.fifosize = UART_FIFO_SIZE;
> + amb_port->port.uartclk = clk_get_rate(amb_port->uart_pll);
> + amb_port->port.ops = &serial_ambarella_pops;
> + amb_port->port.irq = irq;
> + amb_port->port.line = id;
> + amb_port->port.mapbase = mem->start;
> + amb_port->port.membase = devm_ioremap_resource(&pdev->dev, mem);
> + if (IS_ERR(amb_port->port.membase))
> + return PTR_ERR(amb_port->port.membase);
> +
> + rval = uart_add_one_port(&serial_ambarella_reg, &amb_port->port);
> + if (rval < 0)
> + dev_err(&pdev->dev, "failed to add port: %d, %d!\n", id, rval);
> +
> + platform_set_drvdata(pdev, amb_port);
You set this even on failure. Does it matter? I don't know.
> +
> + return rval;
> +}
> +
> +static void serial_ambarella_remove(struct platform_device *pdev)
> +{
> + struct ambarella_uart_port *amb_port;
> +
> + amb_port = platform_get_drvdata(pdev);
This can be amended to the declaration.
> + uart_remove_one_port(&serial_ambarella_reg, &amb_port->port);
> +}
> +
thanks,
--
js
suse labs
^ permalink raw reply [flat|nested] 27+ messages in thread
* RE: Re: [PATCH v2 02/10] dt-bindings: soc: add Ambarella secure scratchpad
2026-08-07 6:01 ` Krzysztof Kozlowski
@ 2026-08-07 8:29 ` Long Zhao
0 siblings, 0 replies; 27+ messages in thread
From: Long Zhao @ 2026-08-07 8:29 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
Stephen Boyd, Linus Walleij, Greg Kroah-Hartman, Jiri Slaby,
Catalin Marinas, Will Deacon, Arnd Bergmann, Alexandre Belloni,
devicetree@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org, linux-clk@vger.kernel.org,
linux-gpio@vger.kernel.org, linux-serial@vger.kernel.org,
soc@lists.linux.dev
Hi Krzysztof,
Thanks for the review.
You are right — the secure scratchpad is only a shared register
region used via syscon (e.g. pinctrl pull control). There is no
dedicated driver and no extra properties, so I'll move the
compatible into syscon.yaml and drop the separate binding in v3.
Best regards!
Long Zhao
Ambarella Shanghai Co., Ltd
-----Original Message-----
From: Krzysztof Kozlowski <krzk@kernel.org>
Sent: Friday, August 7, 2026 14:02
To: Long Zhao <longzhao@ambarella.com>
Cc: Rob Herring <robh@kernel.org>; Krzysztof Kozlowski <krzk+dt@kernel.org>; Conor Dooley <conor+dt@kernel.org>; Michael Turquette <mturquette@baylibre.com>; Stephen Boyd <sboyd@kernel.org>; Linus Walleij <linusw@kernel.org>; Greg Kroah-Hartman <gregkh@linuxfoundation.org>; Jiri Slaby <jirislaby@kernel.org>; Catalin Marinas <catalin.marinas@arm.com>; Will Deacon <will@kernel.org>; Arnd Bergmann <arnd@arndb.de>; Alexandre Belloni <alexandre.belloni@bootlin.com>; devicetree@vger.kernel.org; linux-arm-kernel@lists.infradead.org; linux-kernel@vger.kernel.org; linux-clk@vger.kernel.org; linux-gpio@vger.kernel.org; linux-serial@vger.kernel.org; soc@lists.linux.dev
Subject: [EXT] Re: [PATCH v2 02/10] dt-bindings: soc: add Ambarella secure scratchpad
On Thu, Aug 06, 2026 at 05:34:10PM +0800, Long Zhao wrote:
> Document the Ambarella secure scratchpad syscon used by CV75.
>
> Signed-off-by: Long Zhao <longzhao@ambarella.com>
> ---
> .../ambarella/ambarella,cv75-sec-scratchpad.yaml | 39 ++++++++++++++++++++++
> 1 file changed, 39 insertions(+)
>
> diff --git
> a/Documentation/devicetree/bindings/soc/ambarella/ambarella,cv75-sec-s
> cratchpad.yaml
> b/Documentation/devicetree/bindings/soc/ambarella/ambarella,cv75-sec-s
> cratchpad.yaml
> new file mode 100644
> index 000000000000..4bfeca0ac3da
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/soc/ambarella/ambarella,cv75-s
> +++ ec-scratchpad.yaml
Why do you need separate binding? What is incomplete here that it cannot go to syscon.yaml?
Best regards,
Krzysztof
######################################################################
This EXTERNAL email has been scanned by Proofpoint Email Protect service.
**********************************************************************
This email and 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] 27+ messages in thread
* Re: [PATCH v2 07/10] pinctrl: ambarella: add Ambarella pin controller
2026-08-06 9:34 ` [PATCH v2 07/10] pinctrl: ambarella: add Ambarella pin controller Long Zhao via B4 Relay
2026-08-06 9:46 ` sashiko-bot
@ 2026-08-07 17:45 ` Linus Walleij
1 sibling, 0 replies; 27+ messages in thread
From: Linus Walleij @ 2026-08-07 17:45 UTC (permalink / raw)
To: longzhao, Bartosz Golaszewski
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
Stephen Boyd, Greg Kroah-Hartman, Jiri Slaby, Catalin Marinas,
Will Deacon, Arnd Bergmann, Alexandre Belloni, devicetree,
linux-arm-kernel, linux-kernel, linux-clk, linux-gpio,
linux-serial, soc
Hi Long,
thanks for your patch!
On Thu, Aug 6, 2026 at 11:34 AM Long Zhao via B4 Relay
<devnull+longzhao.ambarella.com@kernel.org> wrote:
> +#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
As everyone can see from a mile away this is a modified ARM PL061
primecell, see drivers/gpio/gpio-pl061.c.
I'm not very interested in having two drivers for pretty much the same
hardware in the kernel. Odds are that there will be new quirks and fixed
to one of the drivers that the other one doesn't get :/
Also the PL061 driver is pretty complex and specialized as GPIO
drivers go already. I certainly don't want to have a second copy
of that.
We need to find a way to re-use the PL061 driver with this pin
controller.
1. Modify the existing PL061 driver to use a dynamic register
layout, i.e. make the different register locations part of some
per-variant data:
struct vendor_data {
u32 data_offset;
u32 dir_offset;
...
};
Add:
struct pl061 {
struct vendor_data *variant;
...
};
Add vendor data to the PL061 variant:
static struct vendor_data vendor_arm = {...};
static struct vendor_data vendor_ambarella = {...};
static const struct amba_id pl061_ids[] = {
{
.id = 0x00041061,
.mask = 0x000fffff,
. data = &vendor_arm;
},
{
.id = /* make something up, see include/linux/amba/bus.h */
.mask = ....
.data = &vendor_ambarella;
},
Essentially follow the pattern from drivers/spi/spi-pl022.c.
2. Contemplate if the GPIO should be a separate entity in the
device tree or not. If not, Linux needs to spawn the new AMBA
device with this pin control driver using e.g.
amba_device_alloc() and amba_device_register() etc,
see drivers/of/platform.c
3. select GPIO_PL061 in Kconfig for this pin controller...
This is not a simple solution but it is the right one.
Yours,
Linus Walleij
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v2 08/10] serial: ambarella: add Ambarella UART driver
2026-08-06 9:34 ` [PATCH v2 08/10] serial: ambarella: add Ambarella UART driver Long Zhao via B4 Relay
2026-08-06 9:49 ` sashiko-bot
2026-08-07 6:18 ` Jiri Slaby
@ 2026-08-07 18:37 ` Linus Walleij
2 siblings, 0 replies; 27+ messages in thread
From: Linus Walleij @ 2026-08-07 18:37 UTC (permalink / raw)
To: longzhao
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
Stephen Boyd, Greg Kroah-Hartman, Jiri Slaby, Catalin Marinas,
Will Deacon, Arnd Bergmann, Alexandre Belloni, devicetree,
linux-arm-kernel, linux-kernel, linux-clk, linux-gpio,
linux-serial, soc
Hi Long,
thanks for your patch!
I know this driver has a long history and has been refined inside
Ambarella for many years. However this is clearly an 8250
UART derivative:
On Thu, Aug 6, 2026 at 11:34 AM Long Zhao via B4 Relay
<devnull+longzhao.ambarella.com@kernel.org> wrote:
> +#define UART_RB_OFFSET 0x00
> +#define UART_TH_OFFSET 0x00
> +#define UART_DLL_OFFSET 0x00
RB, TH, DLL sharing register 0x00.
> +#define UART_IE_OFFSET 0x04
> +#define UART_DLH_OFFSET 0x04
IE, DLH sharing register 0x04.
> +#define UART_II_OFFSET 0x08
> +#define UART_FC_OFFSET 0x08
II, FC sharing register 0x08.
Compare:
include/uapi/linux/serial_reg.h
#define UART_RX 0 /* In: Receive buffer */
#define UART_TX 0 /* Out: Transmit buffer */
#define UART_DLL 0 /* Out: Divisor Latch Low */
#define UART_IER 1 /* Out: Interrupt Enable Register */
#define UART_DLM 1 /* Out: Divisor Latch High */
#define UART_IIR 2 /* In: Interrupt ID Register */
#define UART_FCR 2 /* Out: FIFO Control Register */
This hardware is clearly using the original 8250/16450/16550
tricks to save address space.
The only difference is that your registers are spaced out
with a register shift of 2 so 1 becomes 0x04, 2 becomes 0x08
etc.
It's not even the first doing this, e.g. the DesignWare 8250
driver does the same thing.
This needs to be rewritten using the 8250 library in the kernel
under drivers/tty/serial/8250/8250_ambarella.c
or similar.
Probably you will get much less code to maintain and a few
features for free when you do this.
Yours,
Linus Walleij
^ permalink raw reply [flat|nested] 27+ messages in thread
end of thread, other threads:[~2026-08-07 18:37 UTC | newest]
Thread overview: 27+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-06 9:34 [PATCH v2 00/10] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
2026-08-06 9:34 ` [PATCH v2 01/10] dt-bindings: arm: add Ambarella CV75 platforms Long Zhao via B4 Relay
2026-08-07 6:00 ` Krzysztof Kozlowski
2026-08-06 9:34 ` [PATCH v2 02/10] dt-bindings: soc: add Ambarella secure scratchpad Long Zhao via B4 Relay
2026-08-07 6:01 ` Krzysztof Kozlowski
2026-08-07 8:29 ` Long Zhao
2026-08-06 9:34 ` [PATCH v2 03/10] dt-bindings: clock: add Ambarella CV75 RCT clock controller Long Zhao via B4 Relay
2026-08-06 9:42 ` sashiko-bot
2026-08-06 10:56 ` Rob Herring (Arm)
2026-08-07 6:02 ` Krzysztof Kozlowski
2026-08-06 9:34 ` [PATCH v2 04/10] dt-bindings: pinctrl: add Ambarella CV75 pinctrl Long Zhao via B4 Relay
2026-08-06 9:41 ` sashiko-bot
2026-08-07 6:06 ` Krzysztof Kozlowski
2026-08-06 9:34 ` [PATCH v2 05/10] dt-bindings: serial: add Ambarella UART Long Zhao via B4 Relay
2026-08-06 9:34 ` [PATCH v2 06/10] clk: ambarella: add CV75 CCU driver Long Zhao via B4 Relay
2026-08-06 9:45 ` sashiko-bot
2026-08-06 9:34 ` [PATCH v2 07/10] pinctrl: ambarella: add Ambarella pin controller Long Zhao via B4 Relay
2026-08-06 9:46 ` sashiko-bot
2026-08-07 17:45 ` Linus Walleij
2026-08-06 9:34 ` [PATCH v2 08/10] serial: ambarella: add Ambarella UART driver Long Zhao via B4 Relay
2026-08-06 9:49 ` sashiko-bot
2026-08-07 6:18 ` Jiri Slaby
2026-08-07 18:37 ` Linus Walleij
2026-08-06 9:34 ` [PATCH v2 09/10] arm64: ambarella: add ARCH_AMBARELLA and CV75 EVK DT Long Zhao via B4 Relay
2026-08-06 9:51 ` sashiko-bot
2026-08-07 6:11 ` Krzysztof Kozlowski
2026-08-06 9:34 ` [PATCH v2 10/10] MAINTAINERS: add ARM/AMBARELLA SoC support Long Zhao via B4 Relay
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox