* [PATCH v5 01/12] dt-bindings: arm: add Ambarella CV75 platforms
2026-08-18 10:31 [PATCH v5 00/12] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
@ 2026-08-18 10:31 ` Long Zhao via B4 Relay
2026-08-18 10:31 ` [PATCH v5 02/12] dt-bindings: mfd: syscon: add Ambarella CV75 secure scratchpad Long Zhao via B4 Relay
` (10 subsequent siblings)
11 siblings, 0 replies; 17+ messages in thread
From: Long Zhao via B4 Relay @ 2026-08-18 10:31 UTC (permalink / raw)
To: Arnd Bergmann, Krzysztof Kozlowski, Alexandre Belloni, soc,
linux-arm-kernel
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
Stephen Boyd, Linus Walleij, Bartosz Golaszewski,
Greg Kroah-Hartman, Jiri Slaby, Andy Shevchenko, Catalin Marinas,
Will Deacon, devicetree, linux-clk, linux-gpio, linux-serial,
linux-kernel, Brian Masney, Lee Jones, mfd, Long Zhao, Long Zhao,
Krzysztof Kozlowski
From: Long Zhao <longzhao@ambarella.com>
Add the Ambarella vendor prefix and CV75 EVK board compatible for the
initial SoC bring-up.
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
.../devicetree/bindings/arm/ambarella.yaml | 24 ++++++++++++++++++++++
.../devicetree/bindings/vendor-prefixes.yaml | 2 ++
2 files changed, 26 insertions(+)
diff --git a/Documentation/devicetree/bindings/arm/ambarella.yaml b/Documentation/devicetree/bindings/arm/ambarella.yaml
new file mode 100644
index 000000000000..72946e94fa76
--- /dev/null
+++ b/Documentation/devicetree/bindings/arm/ambarella.yaml
@@ -0,0 +1,24 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/arm/ambarella.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Ambarella SoC platforms
+
+maintainers:
+ - Long Zhao <longzhao@ambarella.com>
+
+properties:
+ $nodename:
+ const: "/"
+
+ compatible:
+ oneOf:
+ - description: Ambarella CV75 boards
+ items:
+ - enum:
+ - ambarella,cv75-evk
+ - const: ambarella,cv75
+
+additionalProperties: true
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml
index 85a27937832e..f48aa6dabbe9 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
+++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
@@ -112,6 +112,8 @@ patternProperties:
description: Amarula Solutions
"^amazon,.*":
description: Amazon.com, Inc.
+ "^ambarella,.*":
+ description: Ambarella, Inc.
"^amcc,.*":
description: Applied Micro Circuits Corporation (APM, formally AMCC)
"^amd,.*":
--
2.34.1
^ permalink raw reply related [flat|nested] 17+ messages in thread* [PATCH v5 02/12] dt-bindings: mfd: syscon: add Ambarella CV75 secure scratchpad
2026-08-18 10:31 [PATCH v5 00/12] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
2026-08-18 10:31 ` [PATCH v5 01/12] dt-bindings: arm: add Ambarella CV75 platforms Long Zhao via B4 Relay
@ 2026-08-18 10:31 ` Long Zhao via B4 Relay
2026-08-18 19:27 ` Krzysztof Kozlowski
2026-08-18 10:31 ` [PATCH v5 03/12] dt-bindings: clock: add Ambarella CV75 RCT Long Zhao via B4 Relay
` (9 subsequent siblings)
11 siblings, 1 reply; 17+ messages in thread
From: Long Zhao via B4 Relay @ 2026-08-18 10:31 UTC (permalink / raw)
To: Arnd Bergmann, Krzysztof Kozlowski, Alexandre Belloni, soc,
linux-arm-kernel
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
Stephen Boyd, Linus Walleij, Bartosz Golaszewski,
Greg Kroah-Hartman, Jiri Slaby, Andy Shevchenko, Catalin Marinas,
Will Deacon, devicetree, linux-clk, linux-gpio, linux-serial,
linux-kernel, Brian Masney, Lee Jones, mfd, Long Zhao, Long Zhao
From: Long Zhao <longzhao@ambarella.com>
Allow ambarella,cv75-sec-scratchpad as a syscon for the shared secure
scratchpad registers on CV75.
Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
Documentation/devicetree/bindings/mfd/syscon.yaml | 1 +
1 file changed, 1 insertion(+)
diff --git a/Documentation/devicetree/bindings/mfd/syscon.yaml b/Documentation/devicetree/bindings/mfd/syscon.yaml
index fe882d3f828b..9ebf702c06f1 100644
--- a/Documentation/devicetree/bindings/mfd/syscon.yaml
+++ b/Documentation/devicetree/bindings/mfd/syscon.yaml
@@ -32,6 +32,7 @@ properties:
- allwinner,sun50i-a64-system-controller
- altr,l3regs
- altr,sdr-ctl
+ - ambarella,cv75-sec-scratchpad
- amd,pensando-elba-syscon
- amlogic,meson-mx-assist
- amlogic,meson-mx-bootrom
--
2.34.1
^ permalink raw reply related [flat|nested] 17+ messages in thread* Re: [PATCH v5 02/12] dt-bindings: mfd: syscon: add Ambarella CV75 secure scratchpad
2026-08-18 10:31 ` [PATCH v5 02/12] dt-bindings: mfd: syscon: add Ambarella CV75 secure scratchpad Long Zhao via B4 Relay
@ 2026-08-18 19:27 ` Krzysztof Kozlowski
0 siblings, 0 replies; 17+ messages in thread
From: Krzysztof Kozlowski @ 2026-08-18 19:27 UTC (permalink / raw)
To: longzhao, Arnd Bergmann, Alexandre Belloni, soc, linux-arm-kernel
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
Stephen Boyd, Linus Walleij, Bartosz Golaszewski,
Greg Kroah-Hartman, Jiri Slaby, Andy Shevchenko, Catalin Marinas,
Will Deacon, devicetree, linux-clk, linux-gpio, linux-serial,
linux-kernel, Brian Masney, Lee Jones, mfd, Long Zhao
On 18/08/2026 12:31, Long Zhao via B4 Relay wrote:
> From: Long Zhao <longzhao@ambarella.com>
>
> Allow ambarella,cv75-sec-scratchpad as a syscon for the shared secure
> scratchpad registers on CV75.
>
> Signed-off-by: Long Zhao <longzhao@ambarella.com>
You already received review at v2 which you did not respond to and did
not implement.
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v5 03/12] dt-bindings: clock: add Ambarella CV75 RCT
2026-08-18 10:31 [PATCH v5 00/12] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
2026-08-18 10:31 ` [PATCH v5 01/12] dt-bindings: arm: add Ambarella CV75 platforms Long Zhao via B4 Relay
2026-08-18 10:31 ` [PATCH v5 02/12] dt-bindings: mfd: syscon: add Ambarella CV75 secure scratchpad Long Zhao via B4 Relay
@ 2026-08-18 10:31 ` Long Zhao via B4 Relay
2026-08-18 19:28 ` Krzysztof Kozlowski
2026-08-18 10:31 ` [PATCH v5 04/12] dt-bindings: pinctrl: add Ambarella CV75 pinctrl Long Zhao via B4 Relay
` (8 subsequent siblings)
11 siblings, 1 reply; 17+ messages in thread
From: Long Zhao via B4 Relay @ 2026-08-18 10:31 UTC (permalink / raw)
To: Arnd Bergmann, Krzysztof Kozlowski, Alexandre Belloni, soc,
linux-arm-kernel
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
Stephen Boyd, Linus Walleij, Bartosz Golaszewski,
Greg Kroah-Hartman, Jiri Slaby, Andy Shevchenko, Catalin Marinas,
Will Deacon, devicetree, linux-clk, linux-gpio, linux-serial,
linux-kernel, Brian Masney, Lee Jones, mfd, Long Zhao, Long Zhao
From: Long Zhao <longzhao@ambarella.com>
Document the CV75 RCT clock controller and the clock ID header used by
DT consumers.
This initial binding describes only the clock-provider functionality.
Reset-controller support is outside scope of this series, so #reset-cells
property is intentionally omitted.
Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
.../bindings/clock/ambarella,cv75-rct.yaml | 58 ++++++++++++++++++++++
include/dt-bindings/clock/ambarella,cv75-rct.h | 15 ++++++
2 files changed, 73 insertions(+)
diff --git a/Documentation/devicetree/bindings/clock/ambarella,cv75-rct.yaml b/Documentation/devicetree/bindings/clock/ambarella,cv75-rct.yaml
new file mode 100644
index 000000000000..eb5956fc32bc
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/ambarella,cv75-rct.yaml
@@ -0,0 +1,58 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/clock/ambarella,cv75-rct.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Ambarella CV75 RCT Clock Controller
+
+maintainers:
+ - Long Zhao <longzhao@ambarella.com>
+
+description: |
+ The RCT (Reset and Clock Tree) block on the Ambarella CV75 SoC provides
+ PLLs, clock muxes and dividers for on-chip peripherals.
+
+ The same register region is also exposed as a syscon for other SoC
+ blocks (e.g. pin drive-strength), hence the "syscon" compatible.
+
+ Clock indices are defined in include/dt-bindings/clock/ambarella,cv75-rct.h.
+
+properties:
+
+ compatible:
+ items:
+ - const: ambarella,cv75-rct
+ - const: syscon
+
+ reg:
+ maxItems: 1
+
+ clocks:
+ description: Reference crystal oscillator (typically 24 MHz)
+ maxItems: 1
+
+ clock-names:
+ const: osc
+
+ "#clock-cells":
+ const: 1
+
+required:
+ - compatible
+ - reg
+ - clocks
+ - clock-names
+ - "#clock-cells"
+
+additionalProperties: false
+
+examples:
+ - |
+ clock-controller@ed080000 {
+ compatible = "ambarella,cv75-rct", "syscon";
+ reg = <0xed080000 0x1000>;
+ clocks = <&osc>;
+ clock-names = "osc";
+ #clock-cells = <1>;
+ };
diff --git a/include/dt-bindings/clock/ambarella,cv75-rct.h b/include/dt-bindings/clock/ambarella,cv75-rct.h
new file mode 100644
index 000000000000..0a6e946c9108
--- /dev/null
+++ b/include/dt-bindings/clock/ambarella,cv75-rct.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause */
+/*
+ * Copyright (C) 2026 Ambarella, Inc.
+ */
+
+#ifndef _DT_BINDINGS_CLOCK_AMBARELLA_CV75_RCT_H
+#define _DT_BINDINGS_CLOCK_AMBARELLA_CV75_RCT_H
+
+/* Boot / bus clocks */
+#define CV75_GCLK_UART0 0
+#define CV75_GCLK_CORE 1
+#define CV75_GCLK_AHB 2
+#define CV75_GCLK_APB 3
+
+#endif
--
2.34.1
^ permalink raw reply related [flat|nested] 17+ messages in thread* Re: [PATCH v5 03/12] dt-bindings: clock: add Ambarella CV75 RCT
2026-08-18 10:31 ` [PATCH v5 03/12] dt-bindings: clock: add Ambarella CV75 RCT Long Zhao via B4 Relay
@ 2026-08-18 19:28 ` Krzysztof Kozlowski
0 siblings, 0 replies; 17+ messages in thread
From: Krzysztof Kozlowski @ 2026-08-18 19:28 UTC (permalink / raw)
To: longzhao, Arnd Bergmann, Alexandre Belloni, soc, linux-arm-kernel
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
Stephen Boyd, Linus Walleij, Bartosz Golaszewski,
Greg Kroah-Hartman, Jiri Slaby, Andy Shevchenko, Catalin Marinas,
Will Deacon, devicetree, linux-clk, linux-gpio, linux-serial,
linux-kernel, Brian Masney, Lee Jones, mfd, Long Zhao
On 18/08/2026 12:31, Long Zhao via B4 Relay wrote:
> From: Long Zhao <longzhao@ambarella.com>
>
> Document the CV75 RCT clock controller and the clock ID header used by
> DT consumers.
>
> This initial binding describes only the clock-provider functionality.
> Reset-controller support is outside scope of this series, so #reset-cells
> property is intentionally omitted.
>
You received feedback at v1 or v2. You ignored it. It was reminded you
at v3. You ignored it.
Now you send this again.
NAK, you need to start responding to feedback.
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v5 04/12] dt-bindings: pinctrl: add Ambarella CV75 pinctrl
2026-08-18 10:31 [PATCH v5 00/12] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
` (2 preceding siblings ...)
2026-08-18 10:31 ` [PATCH v5 03/12] dt-bindings: clock: add Ambarella CV75 RCT Long Zhao via B4 Relay
@ 2026-08-18 10:31 ` Long Zhao via B4 Relay
2026-08-18 10:31 ` [PATCH v5 05/12] dt-bindings: gpio: pl061: add Ambarella CV75 variant Long Zhao via B4 Relay
` (7 subsequent siblings)
11 siblings, 0 replies; 17+ messages in thread
From: Long Zhao via B4 Relay @ 2026-08-18 10:31 UTC (permalink / raw)
To: Arnd Bergmann, Krzysztof Kozlowski, Alexandre Belloni, soc,
linux-arm-kernel
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
Stephen Boyd, Linus Walleij, Bartosz Golaszewski,
Greg Kroah-Hartman, Jiri Slaby, Andy Shevchenko, Catalin Marinas,
Will Deacon, devicetree, linux-clk, linux-gpio, linux-serial,
linux-kernel, Brian Masney, Lee Jones, mfd, Long Zhao, Long Zhao
From: Long Zhao <longzhao@ambarella.com>
Document the CV75 pin controller binding using generic function and
groups properties for pinmux configuration.
Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
.../bindings/pinctrl/ambarella,cv75-pinctrl.yaml | 239 +++++++++++++++++++++
1 file changed, 239 insertions(+)
diff --git a/Documentation/devicetree/bindings/pinctrl/ambarella,cv75-pinctrl.yaml b/Documentation/devicetree/bindings/pinctrl/ambarella,cv75-pinctrl.yaml
new file mode 100644
index 000000000000..34054bc56c3b
--- /dev/null
+++ b/Documentation/devicetree/bindings/pinctrl/ambarella,cv75-pinctrl.yaml
@@ -0,0 +1,239 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/pinctrl/ambarella,cv75-pinctrl.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Ambarella CV75 Pin Controller
+
+maintainers:
+ - Long Zhao <longzhao@ambarella.com>
+
+description:
+ The Ambarella CV75 pin controller configures pin multiplexing, bias and
+ drive strength for the main GPIO banks. Pin groups and their mux settings
+ are defined by the pin controller driver and selected by function and group
+ names.
+
+allOf:
+ - $ref: pinctrl.yaml#
+
+properties:
+ compatible:
+ const: ambarella,cv75-pinctrl
+
+ reg:
+ maxItems: 1
+ description: IOMUX registers
+
+ ambarella,drive-strength-syscon:
+ $ref: /schemas/types.yaml#/definitions/phandle
+ description: RCT syscon containing the drive-strength registers.
+
+ ambarella,pull-syscon:
+ $ref: /schemas/types.yaml#/definitions/phandle
+ description: Secure scratchpad syscon containing the pull registers.
+
+patternProperties:
+ "^[a-z0-9-]+-pins$":
+ type: object
+ allOf:
+ - $ref: pinmux-node.yaml#
+ - $ref: pincfg-node.yaml#
+ additionalProperties: false
+
+ properties:
+ function:
+ enum:
+ - can0
+ - can1
+ - clk_au
+ - dmic0
+ - enet0
+ - hsync0
+ - hsync1
+ - i2c0
+ - i2c1
+ - i2c2
+ - i2c3
+ - i2cs
+ - i2s0
+ - i2s1
+ - ir
+ - pwm0
+ - pwm1
+ - pwm2
+ - pwm3
+ - pwm4
+ - pwm5
+ - pwm6
+ - pwm7
+ - pwm8
+ - pwm9
+ - pwm10
+ - pwm11
+ - sdmmc0
+ - sdmmc1
+ - snand
+ - spi0
+ - spi1
+ - spi2
+ - spi3
+ - spi_slave
+ - spinor
+ - uart0
+ - uart1
+ - uart2
+ - uart3
+ - uart4
+ - vin_master_sync
+ - vsync0
+ - vsync1
+ - vsync2
+ - vsync3
+ - wdt
+
+ groups:
+ items:
+ enum:
+ - can0
+ - can1
+ - clk_au
+ - dmic0
+ - enet_2nd_ref_clk_a
+ - enet_2nd_ref_clk_b
+ - enet_ext_osc_clk
+ - enet0_ptp_pps_o
+ - hsync0
+ - hsync1
+ - i2c0_a
+ - i2c0_b
+ - i2c1_a
+ - i2c1_b
+ - i2c2
+ - i2c3_a
+ - i2c3_b
+ - i2c3_c
+ - i2cs_a
+ - i2cs_b
+ - i2cs_c
+ - i2cs_d
+ - i2s0
+ - i2s1
+ - ir
+ - pwm0
+ - pwm1
+ - pwm2
+ - pwm3
+ - pwm4_a
+ - pwm4_b
+ - pwm5_a
+ - pwm5_b
+ - pwm6_a
+ - pwm6_b
+ - pwm7_a
+ - pwm7_b
+ - pwm8_a
+ - pwm8_b
+ - pwm9_a
+ - pwm9_b
+ - pwm10_a
+ - pwm10_b
+ - pwm11_a
+ - pwm11_b
+ - rgmii0
+ - rmii0
+ - sdmmc0_1bit
+ - sdmmc0_4bit
+ - sdmmc0_cd
+ - sdmmc0_hs_sel
+ - sdmmc0_reset
+ - sdmmc0_wp
+ - sdmmc1_1bit
+ - sdmmc1_4bit
+ - sdmmc1_cd
+ - sdmmc1_hs_sel
+ - sdmmc1_reset
+ - sdmmc1_wp
+ - snand
+ - spi0
+ - spi1
+ - spi2
+ - spi3_a
+ - spi3_b
+ - spi3_c
+ - spi_slave_a
+ - spi_slave_b
+ - spi_slave_c
+ - spi_slave_d
+ - spi_slave_e
+ - spinor
+ - uart0
+ - uart1
+ - uart1_flow
+ - uart2_a
+ - uart2_b
+ - uart2_c
+ - uart2_flow_a
+ - uart2_flow_b
+ - uart3_a
+ - uart3_b
+ - uart3_flow_a
+ - uart3_flow_b
+ - uart4_a
+ - uart4_b
+ - uart4_flow_a
+ - uart4_flow_b
+ - vin_master_sync_a
+ - vin_master_sync_b
+ - vin_master_sync_c
+ - vin_master_sync_d
+ - vin_master_sync_e
+ - vsync0
+ - vsync1
+ - vsync2
+ - vsync3
+ - wdt_a
+ - wdt_b
+ - wdt_c
+ - wdt_d
+ - wdt_e
+ - wdt_f
+ minItems: 1
+ maxItems: 6
+ uniqueItems: true
+
+ bias-disable: true
+ bias-pull-down: true
+ bias-pull-up: true
+
+ drive-strength:
+ enum: [3, 4, 5, 6, 7, 8, 9, 12]
+
+ required:
+ - function
+ - groups
+
+required:
+ - compatible
+ - reg
+ - ambarella,drive-strength-syscon
+ - ambarella,pull-syscon
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ pinctrl: pinctrl@e4010000 {
+ compatible = "ambarella,cv75-pinctrl";
+ reg = <0xe4010000 0x1000>;
+ ambarella,drive-strength-syscon = <&rct>;
+ ambarella,pull-syscon = <&scratchpad>;
+
+ uart0-pins {
+ function = "uart0";
+ groups = "uart0";
+ bias-disable;
+ drive-strength = <8>;
+ };
+ };
--
2.34.1
^ permalink raw reply related [flat|nested] 17+ messages in thread* [PATCH v5 05/12] dt-bindings: gpio: pl061: add Ambarella CV75 variant
2026-08-18 10:31 [PATCH v5 00/12] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
` (3 preceding siblings ...)
2026-08-18 10:31 ` [PATCH v5 04/12] dt-bindings: pinctrl: add Ambarella CV75 pinctrl Long Zhao via B4 Relay
@ 2026-08-18 10:31 ` Long Zhao via B4 Relay
2026-08-18 10:31 ` [PATCH v5 06/12] dt-bindings: serial: add Ambarella CV75 UART Long Zhao via B4 Relay
` (6 subsequent siblings)
11 siblings, 0 replies; 17+ messages in thread
From: Long Zhao via B4 Relay @ 2026-08-18 10:31 UTC (permalink / raw)
To: Arnd Bergmann, Krzysztof Kozlowski, Alexandre Belloni, soc,
linux-arm-kernel
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
Stephen Boyd, Linus Walleij, Bartosz Golaszewski,
Greg Kroah-Hartman, Jiri Slaby, Andy Shevchenko, Catalin Marinas,
Will Deacon, devicetree, linux-clk, linux-gpio, linux-serial,
linux-kernel, Brian Masney, Lee Jones, mfd, Long Zhao, Long Zhao
From: Long Zhao <longzhao@ambarella.com>
Document the Ambarella CV75 GPIO bank compatible and the synthetic
arm,primecell-periphid used with the PL061 binding.
Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
.../devicetree/bindings/gpio/pl061-gpio.yaml | 35 +++++++++++++++++++---
1 file changed, 31 insertions(+), 4 deletions(-)
diff --git a/Documentation/devicetree/bindings/gpio/pl061-gpio.yaml b/Documentation/devicetree/bindings/gpio/pl061-gpio.yaml
index 4d970e55104b..414f8d4a3976 100644
--- a/Documentation/devicetree/bindings/gpio/pl061-gpio.yaml
+++ b/Documentation/devicetree/bindings/gpio/pl061-gpio.yaml
@@ -15,7 +15,9 @@ select:
properties:
compatible:
contains:
- const: arm,pl061
+ enum:
+ - arm,pl061
+ - ambarella,cv75-gpio
required:
- compatible
@@ -24,9 +26,20 @@ properties:
pattern: "^gpio@[0-9a-f]+$"
compatible:
- items:
- - const: arm,pl061
- - const: arm,primecell
+ oneOf:
+ - items:
+ - const: arm,pl061
+ - const: arm,primecell
+ - items:
+ - const: ambarella,cv75-gpio
+ - const: arm,primecell
+
+ arm,primecell-periphid:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description:
+ Value to override the hardware PrimeCell peripheral ID. Ambarella
+ variants use a synthetic ID retaining the PL061 part number because
+ the hardware has no discoverable ID or assigned designer ID.
reg:
maxItems: 1
@@ -57,6 +70,20 @@ properties:
minItems: 1
maxItems: 8
+allOf:
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: ambarella,cv75-gpio
+ then:
+ properties:
+ arm,primecell-periphid:
+ const: 0x00000061
+ required:
+ - arm,primecell-periphid
+ - gpio-ranges
+
required:
- compatible
- reg
--
2.34.1
^ permalink raw reply related [flat|nested] 17+ messages in thread* [PATCH v5 06/12] dt-bindings: serial: add Ambarella CV75 UART
2026-08-18 10:31 [PATCH v5 00/12] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
` (4 preceding siblings ...)
2026-08-18 10:31 ` [PATCH v5 05/12] dt-bindings: gpio: pl061: add Ambarella CV75 variant Long Zhao via B4 Relay
@ 2026-08-18 10:31 ` Long Zhao via B4 Relay
2026-08-18 10:31 ` [PATCH v5 07/12] clk: ambarella: add CV75 CCU driver Long Zhao via B4 Relay
` (5 subsequent siblings)
11 siblings, 0 replies; 17+ messages in thread
From: Long Zhao via B4 Relay @ 2026-08-18 10:31 UTC (permalink / raw)
To: Arnd Bergmann, Krzysztof Kozlowski, Alexandre Belloni, soc,
linux-arm-kernel
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
Stephen Boyd, Linus Walleij, Bartosz Golaszewski,
Greg Kroah-Hartman, Jiri Slaby, Andy Shevchenko, Catalin Marinas,
Will Deacon, devicetree, linux-clk, linux-gpio, linux-serial,
linux-kernel, Brian Masney, Lee Jones, mfd, Long Zhao, Long Zhao
From: Long Zhao <longzhao@ambarella.com>
Document the Ambarella CV75 on-chip UART controller binding for early
console bring-up. The binding requires a single RCT clock input and
fixed 32-bit register access via reg-shift and reg-io-width.
Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
.../bindings/serial/ambarella,cv75-uart.yaml | 56 ++++++++++++++++++++++
1 file changed, 56 insertions(+)
diff --git a/Documentation/devicetree/bindings/serial/ambarella,cv75-uart.yaml b/Documentation/devicetree/bindings/serial/ambarella,cv75-uart.yaml
new file mode 100644
index 000000000000..223c84bf460d
--- /dev/null
+++ b/Documentation/devicetree/bindings/serial/ambarella,cv75-uart.yaml
@@ -0,0 +1,56 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/serial/ambarella,cv75-uart.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Ambarella CV75 UART
+
+maintainers:
+ - Long Zhao <longzhao@ambarella.com>
+
+allOf:
+ - $ref: serial.yaml#
+
+properties:
+ compatible:
+ const: ambarella,cv75-uart
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ clocks:
+ maxItems: 1
+
+ reg-shift:
+ const: 2
+
+ reg-io-width:
+ const: 4
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - clocks
+ - reg-shift
+ - reg-io-width
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/clock/ambarella,cv75-rct.h>
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+
+ serial@e4000000 {
+ compatible = "ambarella,cv75-uart";
+ reg = <0xe4000000 0x1000>;
+ clocks = <&rct CV75_GCLK_UART0>;
+ interrupts = <GIC_SPI 170 IRQ_TYPE_LEVEL_HIGH>;
+ reg-shift = <2>;
+ reg-io-width = <4>;
+ };
--
2.34.1
^ permalink raw reply related [flat|nested] 17+ messages in thread* [PATCH v5 07/12] clk: ambarella: add CV75 CCU driver
2026-08-18 10:31 [PATCH v5 00/12] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
` (5 preceding siblings ...)
2026-08-18 10:31 ` [PATCH v5 06/12] dt-bindings: serial: add Ambarella CV75 UART Long Zhao via B4 Relay
@ 2026-08-18 10:31 ` Long Zhao via B4 Relay
2026-08-18 10:31 ` [PATCH v5 08/12] gpio: pl061: add Ambarella CV75 register layout variant Long Zhao via B4 Relay
` (4 subsequent siblings)
11 siblings, 0 replies; 17+ messages in thread
From: Long Zhao via B4 Relay @ 2026-08-18 10:31 UTC (permalink / raw)
To: Arnd Bergmann, Krzysztof Kozlowski, Alexandre Belloni, soc,
linux-arm-kernel
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
Stephen Boyd, Linus Walleij, Bartosz Golaszewski,
Greg Kroah-Hartman, Jiri Slaby, Andy Shevchenko, Catalin Marinas,
Will Deacon, devicetree, linux-clk, linux-gpio, linux-serial,
linux-kernel, Brian Masney, Lee Jones, mfd, Long Zhao, Long Zhao
From: Long Zhao <longzhao@ambarella.com>
Add the Ambarella CV75 clock driver covering the core PLL and the
UART0 / AHB / APB clocks needed for early console bring-up.
Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
drivers/clk/Kconfig | 1 +
drivers/clk/Makefile | 1 +
drivers/clk/ambarella/Kconfig | 13 ++
drivers/clk/ambarella/Makefile | 6 +
drivers/clk/ambarella/ccu-cv75.c | 296 +++++++++++++++++++++++++
drivers/clk/ambarella/ccu_common.c | 60 +++++
drivers/clk/ambarella/ccu_common.h | 25 +++
drivers/clk/ambarella/ccu_mux_div.c | 234 ++++++++++++++++++++
drivers/clk/ambarella/ccu_mux_div.h | 35 +++
drivers/clk/ambarella/ccu_pll.c | 421 ++++++++++++++++++++++++++++++++++++
drivers/clk/ambarella/ccu_pll.h | 70 ++++++
11 files changed, 1162 insertions(+)
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index 1717ce75a907..fbbf4963716c 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -506,6 +506,7 @@ config COMMON_CLK_RPMI
the RISC-V platform management interface (RPMI) specification.
source "drivers/clk/actions/Kconfig"
+source "drivers/clk/ambarella/Kconfig"
source "drivers/clk/analogbits/Kconfig"
source "drivers/clk/aspeed/Kconfig"
source "drivers/clk/bcm/Kconfig"
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index cc108a75a900..30d823ee606f 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -112,6 +112,7 @@ obj-$(CONFIG_COMMON_CLK_XGENE) += clk-xgene.o
# please keep this section sorted lexicographically by directory path name
obj-y += actions/
+obj-y += ambarella/
obj-y += analogbits/
obj-y += aspeed/
obj-$(CONFIG_COMMON_CLK_AT91) += at91/
diff --git a/drivers/clk/ambarella/Kconfig b/drivers/clk/ambarella/Kconfig
new file mode 100644
index 000000000000..d79bb4bd6df5
--- /dev/null
+++ b/drivers/clk/ambarella/Kconfig
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+config CLK_AMBARELLA_CV75
+ bool "Ambarella CV75 RCT clock controller"
+ depends on ARCH_AMBARELLA || COMPILE_TEST
+ select CLK_AMBARELLA_CCU
+ default ARCH_AMBARELLA
+ help
+ Say Y to enable the Ambarella CV75 RCT clock controller.
+
+config CLK_AMBARELLA_CCU
+ bool
+ select REGMAP_MMIO
diff --git a/drivers/clk/ambarella/Makefile b/drivers/clk/ambarella/Makefile
new file mode 100644
index 000000000000..36e96326ea97
--- /dev/null
+++ b/drivers/clk/ambarella/Makefile
@@ -0,0 +1,6 @@
+#
+# Makefile for ambarella specific clk
+#
+
+obj-$(CONFIG_CLK_AMBARELLA_CCU) += ccu_common.o ccu_mux_div.o ccu_pll.o
+obj-$(CONFIG_CLK_AMBARELLA_CV75) += ccu-cv75.o
diff --git a/drivers/clk/ambarella/ccu-cv75.c b/drivers/clk/ambarella/ccu-cv75.c
new file mode 100644
index 000000000000..d682da3cf28d
--- /dev/null
+++ b/drivers/clk/ambarella/ccu-cv75.c
@@ -0,0 +1,296 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2026 Ambarella, Inc.
+ *
+ * CV75 RCT clock controller for boot clocks.
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <dt-bindings/clock/ambarella,cv75-rct.h>
+
+#include "ccu_common.h"
+#include "ccu_mux_div.h"
+#include "ccu_pll.h"
+
+enum amb_cv75_clk_type {
+ AMB_CV75_CLK_FIXED_RATE,
+ AMB_CV75_CLK_FIXED_FACTOR,
+ AMB_CV75_CLK_PLL,
+ AMB_CV75_CLK_DIV,
+ AMB_CV75_CLK_MUX_DIV,
+};
+
+enum amb_cv75_clk_ref {
+ AMB_CV75_CLK_REF_OSC = -1,
+ AMB_CV75_CLK_REF_DUMMY = -2,
+};
+
+/* PLL version for CV75 (ambarella,clkpll-v1 in vendor DTS) */
+static const struct amb_pll_soc_data cv75_pll_soc_data = {
+ .pll_version = 1,
+ .fsout_mask = CTRL2_FSOUT_DIV2,
+ .fsout_val = CTRL2_FSOUT_DIV2,
+ .fsdiv_mask = CTRL2_FSDIV_DIV2,
+ .fsdiv_val = CTRL2_FSDIV_DIV2,
+ .vcodiv_mask = CTRL2_VCODIV_DIV2,
+ .vcodiv_val = CTRL2_VCODIV_DIV2,
+ .vco_max_mhz = 2600UL,
+ .vco_min_mhz = 850UL,
+ .vco_range = { 1800UL, 1400UL, 1100UL, 0UL },
+};
+
+struct amb_cv75_clk_desc {
+ int id;
+ enum amb_cv75_clk_type type;
+ const char *name;
+ int parent;
+
+ union {
+ struct {
+ unsigned long rate;
+ } fixed_rate;
+ struct {
+ unsigned long flags;
+ unsigned int mult;
+ unsigned int div;
+ } fixed_factor;
+ struct {
+ u32 reg_offset[REG_NUM];
+ const struct amb_pll_soc_data *soc_data;
+ } pll;
+ struct {
+ u32 reg;
+ u32 shift;
+ u32 width;
+ u32 flags;
+ u32 fix_divider;
+ } div;
+ struct amb_mux_div_desc mux_div;
+ };
+};
+
+static const struct amb_cv75_clk_desc cv75_clks[] = {
+ {
+ .id = AMB_CV75_CLK_REF_DUMMY,
+ .type = AMB_CV75_CLK_FIXED_RATE,
+ .name = "dummy",
+ .fixed_rate.rate = 0,
+ },
+ {
+ .id = CV75_GCLK_CORE,
+ .type = AMB_CV75_CLK_PLL,
+ .name = "core",
+ .parent = AMB_CV75_CLK_REF_OSC,
+ .pll = {
+ .reg_offset = {
+ 0x000, 0x004, 0x100,
+ 0x104, 0x000, 0x000,
+ },
+ .soc_data = &cv75_pll_soc_data,
+ },
+ },
+ {
+ .id = CV75_GCLK_AHB,
+ .type = AMB_CV75_CLK_FIXED_FACTOR,
+ .name = "ahb",
+ .parent = CV75_GCLK_CORE,
+ .fixed_factor = {
+ .flags = 0,
+ .mult = 1,
+ .div = 2,
+ },
+ },
+ {
+ .id = CV75_GCLK_APB,
+ .type = AMB_CV75_CLK_FIXED_FACTOR,
+ .name = "apb",
+ .parent = CV75_GCLK_CORE,
+ .fixed_factor = {
+ .flags = 0,
+ .mult = 1,
+ .div = 4,
+ },
+ },
+ {
+ .id = CV75_GCLK_UART0,
+ .type = AMB_CV75_CLK_MUX_DIV,
+ .name = "uart0",
+ .mux_div = {
+ .name = "uart0",
+ .parents = (const int[]) {
+ AMB_CV75_CLK_REF_OSC, CV75_GCLK_CORE,
+ AMB_CV75_CLK_REF_DUMMY, AMB_CV75_CLK_REF_DUMMY,
+ },
+ .num_parents = 4,
+ .mux_reg = 0x1c8,
+ .mux_shift = 0,
+ .mux_mask = 0x3,
+ .div_reg = 0x038,
+ .div_shift = 0,
+ .div_width = 24,
+ .div_flags = CLK_DIVIDER_ONE_BASED,
+ .fix_divider = 1,
+ },
+ },
+};
+
+static struct clk_hw *amb_cv75_get_parent(struct amb_ccu *ccu,
+ struct clk_hw *osc,
+ struct clk_hw *dummy,
+ int parent)
+{
+ if (parent == AMB_CV75_CLK_REF_OSC)
+ return osc;
+ if (parent == AMB_CV75_CLK_REF_DUMMY)
+ return dummy;
+
+ if (parent < 0 || parent >= ccu->data->num)
+ return ERR_PTR(-EINVAL);
+
+ if (!ccu->data->hws[parent])
+ return ERR_PTR(-EPROBE_DEFER);
+
+ return ccu->data->hws[parent];
+}
+
+static struct clk_hw *amb_cv75_register_mux_div(struct device *dev,
+ struct amb_ccu *ccu,
+ const struct amb_cv75_clk_desc *desc,
+ struct clk_hw *osc,
+ struct clk_hw *dummy)
+{
+ const struct amb_mux_div_desc *md = &desc->mux_div;
+ struct clk_parent_data *pdata;
+ struct clk_hw *parent;
+ u8 i;
+
+ pdata = devm_kcalloc(dev, md->num_parents, sizeof(*pdata), GFP_KERNEL);
+ if (!pdata)
+ return ERR_PTR(-ENOMEM);
+
+ for (i = 0; i < md->num_parents; i++) {
+ if (md->parents[i] == AMB_CV75_CLK_REF_OSC) {
+ pdata[i].fw_name = "osc";
+ continue;
+ }
+
+ parent = amb_cv75_get_parent(ccu, osc, dummy, md->parents[i]);
+ if (IS_ERR(parent))
+ return parent;
+
+ pdata[i].hw = parent;
+ }
+
+ return amb_mux_div_register(dev, ccu->map, md, pdata);
+}
+
+static struct clk_hw *amb_cv75_register_clk(struct device *dev,
+ struct amb_ccu *ccu,
+ const struct amb_cv75_clk_desc *desc,
+ struct clk_hw *osc,
+ struct clk_hw *dummy)
+{
+ struct amb_pll_desc pll_desc;
+ struct clk_hw *parent;
+
+ switch (desc->type) {
+ case AMB_CV75_CLK_FIXED_RATE:
+ return devm_clk_hw_register_fixed_rate(dev, desc->name, NULL, 0,
+ desc->fixed_rate.rate);
+ case AMB_CV75_CLK_FIXED_FACTOR:
+ parent = amb_cv75_get_parent(ccu, osc, dummy, desc->parent);
+ if (IS_ERR(parent))
+ return parent;
+
+ return devm_clk_hw_register_fixed_factor_parent_hw(dev,
+ desc->name, parent,
+ desc->fixed_factor.flags,
+ desc->fixed_factor.mult,
+ desc->fixed_factor.div);
+ case AMB_CV75_CLK_PLL:
+ parent = amb_cv75_get_parent(ccu, osc, dummy, desc->parent);
+ if (IS_ERR(parent))
+ return parent;
+
+ pll_desc.name = desc->name;
+ pll_desc.parent = parent;
+ memcpy((void *)pll_desc.reg_offset, desc->pll.reg_offset,
+ sizeof(pll_desc.reg_offset));
+ pll_desc.soc_data = desc->pll.soc_data;
+ pll_desc.frac_mode = false;
+
+ return amb_pll_register(dev, ccu->map, &pll_desc);
+ case AMB_CV75_CLK_DIV:
+ parent = amb_cv75_get_parent(ccu, osc, dummy, desc->parent);
+ if (IS_ERR(parent))
+ return parent;
+
+ return amb_div_register(dev, ccu->map, desc->name, parent,
+ desc->div.reg, desc->div.shift,
+ desc->div.width, desc->div.flags,
+ desc->div.fix_divider);
+ case AMB_CV75_CLK_MUX_DIV:
+ return amb_cv75_register_mux_div(dev, ccu, desc, osc, dummy);
+ default:
+ return ERR_PTR(-EINVAL);
+ }
+}
+
+static int amb_cv75_rct_probe(struct platform_device *pdev)
+{
+ struct amb_ccu *ccu;
+ struct clk *osc_clk;
+ struct clk_hw *osc, *dummy = NULL, *hw;
+ int i;
+
+ ccu = amb_ccu_init(pdev, ARRAY_SIZE(cv75_clks));
+ if (IS_ERR(ccu))
+ return PTR_ERR(ccu);
+
+ osc_clk = devm_clk_get(&pdev->dev, "osc");
+ if (IS_ERR(osc_clk))
+ return dev_err_probe(&pdev->dev, PTR_ERR(osc_clk),
+ "missing osc clock\n");
+ osc = __clk_get_hw(osc_clk);
+
+ for (i = 0; i < ARRAY_SIZE(cv75_clks); i++) {
+ hw = amb_cv75_register_clk(&pdev->dev, ccu, &cv75_clks[i],
+ osc, dummy);
+ if (IS_ERR(hw))
+ return dev_err_probe(&pdev->dev, PTR_ERR(hw),
+ "failed to register %s\n",
+ cv75_clks[i].name);
+
+ if (cv75_clks[i].id == AMB_CV75_CLK_REF_DUMMY)
+ dummy = hw;
+ else
+ ccu->data->hws[cv75_clks[i].id] = hw;
+ }
+
+ return amb_ccu_register(ccu);
+}
+
+static const struct of_device_id amb_cv75_rct_match[] = {
+ { .compatible = "ambarella,cv75-rct" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, amb_cv75_rct_match);
+
+static struct platform_driver amb_cv75_rct_driver = {
+ .probe = amb_cv75_rct_probe,
+ .driver = {
+ .name = "ambarella-cv75-rct",
+ .of_match_table = amb_cv75_rct_match,
+ },
+};
+module_platform_driver(amb_cv75_rct_driver);
+
+MODULE_AUTHOR("Ambarella Inc.");
+MODULE_DESCRIPTION("Ambarella CV75 RCT clock controller");
+MODULE_LICENSE("GPL");
diff --git a/drivers/clk/ambarella/ccu_common.c b/drivers/clk/ambarella/ccu_common.c
new file mode 100644
index 000000000000..78587c5f2ce1
--- /dev/null
+++ b/drivers/clk/ambarella/ccu_common.c
@@ -0,0 +1,60 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2026 Ambarella, Inc.
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#include "ccu_common.h"
+
+static const struct regmap_config amb_rct_regmap_config = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .max_register = AMB_RCT_REG_SIZE - 4,
+};
+
+struct amb_ccu *amb_ccu_init(struct platform_device *pdev,
+ unsigned int num_clks)
+{
+ struct amb_ccu *ccu;
+ void __iomem *base;
+
+ if (!num_clks)
+ return ERR_PTR(-EINVAL);
+
+ ccu = devm_kzalloc(&pdev->dev, sizeof(*ccu), GFP_KERNEL);
+ if (!ccu)
+ return ERR_PTR(-ENOMEM);
+
+ ccu->dev = &pdev->dev;
+
+ ccu->data = devm_kzalloc(&pdev->dev,
+ struct_size(ccu->data, hws, num_clks),
+ GFP_KERNEL);
+ if (!ccu->data)
+ return ERR_PTR(-ENOMEM);
+
+ ccu->data->num = num_clks;
+
+ base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(base))
+ return ERR_CAST(base);
+
+ ccu->map = devm_regmap_init_mmio(&pdev->dev, base,
+ &amb_rct_regmap_config);
+ if (IS_ERR(ccu->map))
+ return ERR_CAST(ccu->map);
+
+ return ccu;
+}
+
+int amb_ccu_register(struct amb_ccu *ccu)
+{
+ return devm_of_clk_add_hw_provider(ccu->dev, of_clk_hw_onecell_get,
+ ccu->data);
+}
diff --git a/drivers/clk/ambarella/ccu_common.h b/drivers/clk/ambarella/ccu_common.h
new file mode 100644
index 000000000000..f2bf9d1f06d7
--- /dev/null
+++ b/drivers/clk/ambarella/ccu_common.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2026 Ambarella, Inc.
+ */
+
+#ifndef __CCU_COMMON_H
+#define __CCU_COMMON_H
+
+#include <linux/clk-provider.h>
+#include <linux/device.h>
+#include <linux/regmap.h>
+
+#define AMB_RCT_REG_SIZE 0x1000
+
+struct amb_ccu {
+ struct device *dev;
+ struct regmap *map;
+ struct clk_hw_onecell_data *data;
+};
+
+struct amb_ccu *amb_ccu_init(struct platform_device *pdev,
+ unsigned int num_clks);
+int amb_ccu_register(struct amb_ccu *ccu);
+
+#endif
diff --git a/drivers/clk/ambarella/ccu_mux_div.c b/drivers/clk/ambarella/ccu_mux_div.c
new file mode 100644
index 000000000000..d5259bc347e4
--- /dev/null
+++ b/drivers/clk/ambarella/ccu_mux_div.c
@@ -0,0 +1,234 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2026 Ambarella, Inc.
+ *
+ * Regmap-backed mux + divider, derived from the vendor composite-clock driver.
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/device.h>
+#include <linux/math64.h>
+#include <linux/regmap.h>
+
+#include "ccu_mux_div.h"
+
+struct amb_mux {
+ struct clk_hw hw;
+ struct regmap *map;
+ u32 offset;
+ u32 mask;
+ u32 shift;
+};
+
+struct amb_div {
+ struct clk_hw hw;
+ struct regmap *map;
+ u32 offset;
+ u32 shift;
+ u32 width;
+ u32 flags;
+ u32 fix_divider;
+};
+
+#define to_amb_mux(_hw) container_of(_hw, struct amb_mux, hw)
+#define to_amb_div(_hw) container_of(_hw, struct amb_div, hw)
+
+static u8 amb_mux_get_parent(struct clk_hw *hw)
+{
+ struct amb_mux *mux = to_amb_mux(hw);
+ u32 val;
+
+ regmap_read(mux->map, mux->offset, &val);
+ val >>= mux->shift;
+ val &= mux->mask;
+
+ return val;
+}
+
+static int amb_mux_set_parent(struct clk_hw *hw, u8 index)
+{
+ struct amb_mux *mux = to_amb_mux(hw);
+
+ return regmap_update_bits(mux->map, mux->offset,
+ mux->mask << mux->shift,
+ index << mux->shift);
+}
+
+static const struct clk_ops amb_mux_ops = {
+ .get_parent = amb_mux_get_parent,
+ .set_parent = amb_mux_set_parent,
+};
+
+static unsigned long amb_div_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct amb_div *div = to_amb_div(hw);
+ unsigned long rate;
+ u32 val;
+
+ regmap_read(div->map, div->offset, &val);
+
+ /* Divider reset/disable bit sits above the field at shift+width. */
+ if (val & (BIT(div->width) << div->shift))
+ return 0;
+
+ val >>= div->shift;
+ val &= clk_div_mask(div->width);
+
+ rate = divider_recalc_rate(hw, parent_rate, val, NULL,
+ div->flags, div->width);
+ if (div->fix_divider)
+ rate = div_u64(rate, div->fix_divider);
+
+ return rate;
+}
+
+static int amb_div_determine_rate(struct clk_hw *hw,
+ struct clk_rate_request *req)
+{
+ struct amb_div *div = to_amb_div(hw);
+ struct clk_rate_request scaled = *req;
+ int ret;
+
+ if (!req->rate && (div->flags & CLK_DIVIDER_ONE_BASED))
+ return 0;
+
+ if (div->fix_divider) {
+ scaled.rate = min(req->rate,
+ ULONG_MAX / div->fix_divider) *
+ div->fix_divider;
+ scaled.min_rate = min(req->min_rate,
+ ULONG_MAX / div->fix_divider) *
+ div->fix_divider;
+ scaled.max_rate = min(req->max_rate,
+ ULONG_MAX / div->fix_divider) *
+ div->fix_divider;
+ }
+
+ ret = divider_determine_rate(hw, &scaled, NULL, div->width,
+ div->flags);
+ if (ret)
+ return ret;
+
+ req->rate = scaled.rate;
+ req->best_parent_rate = scaled.best_parent_rate;
+ req->best_parent_hw = scaled.best_parent_hw;
+ if (div->fix_divider)
+ req->rate = div_u64(req->rate, div->fix_divider);
+
+ return 0;
+}
+
+static int amb_div_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct amb_div *div = to_amb_div(hw);
+ int val, mask;
+
+ if (div->fix_divider)
+ rate *= div->fix_divider;
+
+ if (!rate) {
+ /* Assert the reset/disable bit above the divider field. */
+ val = BIT(div->width);
+ mask = clk_div_mask(div->width + 1);
+ } else {
+ val = divider_get_val(rate, parent_rate, NULL,
+ div->width, div->flags);
+ if (val < 0)
+ return val;
+
+ /*
+ * Include the disable bit in the mask so a later non-zero
+ * set_rate clears a previous rate==0 disable assert.
+ */
+ mask = clk_div_mask(div->width + 1);
+ }
+
+ regmap_update_bits(div->map, div->offset, mask << div->shift,
+ val << div->shift);
+
+ /*
+ * Non-ONE_BASED dividers use bit 0 as a write-enable strobe. Skip the
+ * pulse when shift == 0 so we do not corrupt the divider field itself.
+ */
+ if (!(div->flags & CLK_DIVIDER_ONE_BASED) && div->shift) {
+ regmap_update_bits(div->map, div->offset, BIT(0), BIT(0));
+ regmap_update_bits(div->map, div->offset, BIT(0), 0);
+ }
+
+ return 0;
+}
+
+static const struct clk_ops amb_div_ops = {
+ .recalc_rate = amb_div_recalc_rate,
+ .determine_rate = amb_div_determine_rate,
+ .set_rate = amb_div_set_rate,
+};
+
+struct clk_hw *amb_div_register(struct device *dev, struct regmap *map,
+ const char *name, const struct clk_hw *parent,
+ u32 div_reg, u32 div_shift, u32 div_width,
+ u32 div_flags, u32 fix_divider)
+{
+ struct amb_div *div;
+ struct clk_init_data init = {};
+ int ret;
+
+ div = devm_kzalloc(dev, sizeof(*div), GFP_KERNEL);
+ if (!div)
+ return ERR_PTR(-ENOMEM);
+
+ div->map = map;
+ div->offset = div_reg;
+ div->shift = div_shift;
+ div->width = div_width;
+ div->flags = div_flags;
+ div->fix_divider = fix_divider;
+
+ init.name = name;
+ init.ops = &amb_div_ops;
+ init.parent_hws = &parent;
+ init.num_parents = 1;
+
+ div->hw.init = &init;
+
+ ret = devm_clk_hw_register(dev, &div->hw);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return &div->hw;
+}
+
+struct clk_hw *amb_mux_div_register(struct device *dev, struct regmap *map,
+ const struct amb_mux_div_desc *desc,
+ const struct clk_parent_data *parent_data)
+{
+ struct amb_mux *mux;
+ struct amb_div *div;
+
+ mux = devm_kzalloc(dev, sizeof(*mux), GFP_KERNEL);
+ div = devm_kzalloc(dev, sizeof(*div), GFP_KERNEL);
+ if (!mux || !div)
+ return ERR_PTR(-ENOMEM);
+
+ mux->map = map;
+ mux->offset = desc->mux_reg;
+ mux->shift = desc->mux_shift;
+ mux->mask = desc->mux_mask;
+
+ div->map = map;
+ div->offset = desc->div_reg;
+ div->shift = desc->div_shift;
+ div->width = desc->div_width;
+ div->flags = desc->div_flags;
+ div->fix_divider = desc->fix_divider;
+
+ return devm_clk_hw_register_composite_pdata(dev, desc->name,
+ parent_data,
+ desc->num_parents,
+ &mux->hw, &amb_mux_ops,
+ &div->hw, &amb_div_ops,
+ NULL, NULL,
+ CLK_SET_RATE_NO_REPARENT);
+}
diff --git a/drivers/clk/ambarella/ccu_mux_div.h b/drivers/clk/ambarella/ccu_mux_div.h
new file mode 100644
index 000000000000..abaefff1201a
--- /dev/null
+++ b/drivers/clk/ambarella/ccu_mux_div.h
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2026 Ambarella, Inc.
+ */
+
+#ifndef __CCU_MUX_DIV_H
+#define __CCU_MUX_DIV_H
+
+#include <linux/clk-provider.h>
+#include <linux/regmap.h>
+
+struct amb_mux_div_desc {
+ const char *name;
+ const int *parents;
+ u8 num_parents;
+ u32 mux_reg;
+ u32 mux_shift;
+ u32 mux_mask;
+ u32 div_reg;
+ u32 div_shift;
+ u32 div_width;
+ u32 div_flags;
+ u32 fix_divider;
+};
+
+struct clk_hw *amb_mux_div_register(struct device *dev, struct regmap *map,
+ const struct amb_mux_div_desc *desc,
+ const struct clk_parent_data *parent_data);
+
+struct clk_hw *amb_div_register(struct device *dev, struct regmap *map,
+ const char *name, const struct clk_hw *parent,
+ u32 div_reg, u32 div_shift, u32 div_width,
+ u32 div_flags, u32 fix_divider);
+
+#endif
diff --git a/drivers/clk/ambarella/ccu_pll.c b/drivers/clk/ambarella/ccu_pll.c
new file mode 100644
index 000000000000..76452003bb0d
--- /dev/null
+++ b/drivers/clk/ambarella/ccu_pll.c
@@ -0,0 +1,421 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2026 Ambarella, Inc.
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/math64.h>
+#include <linux/rational.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+#include "ccu_pll.h"
+
+#define AMB_PLL_MAX_SOUT 16UL
+#define AMB_PLL_MAX_SDIV 16UL
+
+struct amb_pll {
+ struct clk_hw hw;
+ struct regmap *map;
+ u32 reg_offset[REG_NUM];
+ const struct amb_pll_soc_data *soc_data;
+ u32 fix_divider;
+ bool frac_mode;
+};
+
+#define to_amb_pll(_hw) container_of(_hw, struct amb_pll, hw)
+
+static void amb_pll_write_enable(struct regmap *map, u32 offset, u32 val)
+{
+ regmap_write(map, offset, val);
+ regmap_write(map, offset, val | CTRL_WRITE_ENABLE);
+ regmap_write(map, offset, val);
+}
+
+static unsigned long amb_pll_calc_vco(struct amb_pll *pll,
+ unsigned long parent_rate)
+{
+ const struct amb_pll_soc_data *soc_data = pll->soc_data;
+ u32 *reg = pll->reg_offset;
+ u32 pre_scaler = 1;
+ u32 ctrl_val, ctrl2_val = 0, frac_val;
+ u32 intp, sdiv, vcodiv, fsdiv;
+ u64 frac = 0, vco;
+
+ if (reg[PRES_OFFSET]) {
+ regmap_read(pll->map, reg[PRES_OFFSET], &pre_scaler);
+ pre_scaler = (pre_scaler >> 4) + 1;
+ }
+
+ regmap_read(pll->map, reg[CTRL_OFFSET], &ctrl_val);
+ intp = ((ctrl_val >> 24) & 0x7f) + 1;
+ sdiv = ((ctrl_val >> 12) & 0xf) + 1;
+
+ if (soc_data->pll_version >= 2) {
+ vcodiv = (ctrl_val & soc_data->vcodiv_mask) ==
+ soc_data->vcodiv_val ? 2 : 1;
+ fsdiv = (ctrl_val & soc_data->fsdiv_mask) ==
+ soc_data->fsdiv_val ? 2 : 1;
+ } else {
+ regmap_read(pll->map, reg[CTRL2_OFFSET], &ctrl2_val);
+ vcodiv = (ctrl2_val & soc_data->vcodiv_mask) ==
+ soc_data->vcodiv_val ? 2 : 1;
+ fsdiv = (ctrl2_val & soc_data->fsdiv_mask) ==
+ soc_data->fsdiv_val ? 2 : 1;
+ }
+
+ vco = (u64)parent_rate * vcodiv * fsdiv * intp * sdiv;
+ vco = div_u64(vco, pre_scaler);
+
+ if (ctrl_val & CTRL_FRAC_MODE) {
+ regmap_read(pll->map, reg[FRAC_OFFSET], &frac_val);
+ frac = (u64)parent_rate * vcodiv * fsdiv * sdiv * frac_val;
+ frac = div_u64(frac, pre_scaler) >> 32;
+ }
+
+ return vco + frac;
+}
+
+static unsigned long amb_pll_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct amb_pll *pll = to_amb_pll(hw);
+ const struct amb_pll_soc_data *soc_data = pll->soc_data;
+ u32 *reg = pll->reg_offset;
+ u32 pre_scaler = 1, post_scaler = 1;
+ u32 ctrl_val, ctrl2_val = 0;
+ u32 vcodiv, fsout, sout;
+ u64 rate;
+
+ regmap_read(pll->map, reg[CTRL_OFFSET], &ctrl_val);
+ if (ctrl_val & (CTRL_POWER_DOWN | CTRL_HALT_VCO | CTRL_FORCE_RESET))
+ return 0;
+
+ if (reg[PRES_OFFSET]) {
+ regmap_read(pll->map, reg[PRES_OFFSET], &pre_scaler);
+ pre_scaler = (pre_scaler >> 4) + 1;
+ }
+
+ if (reg[POST_OFFSET]) {
+ regmap_read(pll->map, reg[POST_OFFSET], &post_scaler);
+ post_scaler = (post_scaler >> 4) + 1;
+ }
+
+ if (ctrl_val & CTRL_BYPASS)
+ return parent_rate / pre_scaler / post_scaler;
+
+ if (soc_data->pll_version >= 2) {
+ vcodiv = (ctrl_val & soc_data->vcodiv_mask) ==
+ soc_data->vcodiv_val ? 2 : 1;
+ fsout = (ctrl_val & soc_data->fsout_mask) ==
+ soc_data->fsout_val ? 2 : 1;
+ } else {
+ regmap_read(pll->map, reg[CTRL2_OFFSET], &ctrl2_val);
+ vcodiv = (ctrl2_val & soc_data->vcodiv_mask) ==
+ soc_data->vcodiv_val ? 2 : 1;
+ fsout = (ctrl2_val & soc_data->fsout_mask) ==
+ soc_data->fsout_val ? 2 : 1;
+ }
+
+ sout = ((ctrl_val >> 16) & 0xf) + 1;
+ rate = amb_pll_calc_vco(pll, parent_rate);
+
+ if (soc_data->pll_version >= 2) {
+ if (!(ctrl_val & CTRL_BYPASS_HSDIV))
+ rate = div_u64(rate, vcodiv * fsout * sout);
+ } else {
+ if (!(ctrl2_val & CTRL2_BYPASS_HSDIV))
+ rate = div_u64(rate, vcodiv * fsout * sout);
+ }
+
+ rate = div_u64(rate, pll->fix_divider * post_scaler);
+
+ return rate;
+}
+
+static int amb_pll_calc_params(struct amb_pll *pll, unsigned long rate,
+ unsigned long parent_rate, u32 ctrl2_val,
+ u32 *intp, u32 *sdiv, u32 *sout,
+ u32 *vcodiv, u32 *fsdiv, u32 *fsout)
+{
+ const struct amb_pll_soc_data *soc_data = pll->soc_data;
+ unsigned long max_numerator, max_denominator;
+ unsigned long intp_ul, sout_ul, rate_tmp, parent_scaled;
+ u32 ctrl_val;
+
+ if (!parent_rate)
+ return -EINVAL;
+
+ *sdiv = 1;
+
+ if (soc_data->pll_version >= 2) {
+ ctrl_val = 0;
+ *vcodiv = (ctrl_val & soc_data->vcodiv_mask) ==
+ soc_data->vcodiv_val ? 2 : 1;
+ *fsdiv = (ctrl_val & soc_data->fsdiv_mask) ==
+ soc_data->fsdiv_val ? 2 : 1;
+ *fsout = (ctrl_val & soc_data->fsout_mask) ==
+ soc_data->fsout_val ? 2 : 1;
+ } else {
+ *vcodiv = (ctrl2_val & soc_data->vcodiv_mask) ==
+ soc_data->vcodiv_val ? 2 : 1;
+ *fsdiv = (ctrl2_val & soc_data->fsdiv_mask) ==
+ soc_data->fsdiv_val ? 2 : 1;
+ *fsout = (ctrl2_val & soc_data->fsout_mask) ==
+ soc_data->fsout_val ? 2 : 1;
+ }
+
+ if (rate < parent_rate)
+ return -EINVAL;
+
+ max_numerator = soc_data->vco_max_mhz;
+ max_numerator = div_u64(max_numerator * 1000000ULL, parent_rate);
+ max_numerator = div_u64(max_numerator, *vcodiv * *fsdiv);
+ max_numerator = min(128UL, max_numerator);
+ if (!max_numerator)
+ return -EINVAL;
+
+ /*
+ * Approximate intp/sout against the fsdiv/fsout-scaled parent so the
+ * initial guess matches the hardware frequency equation.
+ */
+ max_denominator = AMB_PLL_MAX_SOUT;
+ parent_scaled = parent_rate * *fsdiv / *fsout;
+ if (!parent_scaled)
+ return -EINVAL;
+ rate_tmp = rate;
+ rational_best_approximation(rate_tmp, parent_scaled, max_numerator,
+ max_denominator, &intp_ul, &sout_ul);
+
+ while (parent_rate * *fsdiv * intp_ul * *sdiv / *fsout / sout_ul >
+ rate) {
+ unsigned long resolution = parent_scaled / AMB_PLL_MAX_SOUT;
+
+ /* Avoid an infinite loop when parent_rate < AMB_PLL_MAX_SOUT. */
+ if (!resolution)
+ resolution = 1;
+
+ if (rate_tmp <= resolution)
+ return -EINVAL;
+
+ rate_tmp -= resolution;
+ rational_best_approximation(rate_tmp, parent_scaled,
+ max_numerator, max_denominator,
+ &intp_ul, &sout_ul);
+ }
+
+ while (parent_rate / 1000000 * *vcodiv * *fsdiv * intp_ul * *sdiv <
+ soc_data->vco_min_mhz) {
+ if (sout_ul > 8 || intp_ul > 64)
+ break;
+
+ intp_ul *= 2;
+ sout_ul *= 2;
+ }
+
+ if (intp_ul > max_numerator || sout_ul > max_denominator ||
+ *sdiv > AMB_PLL_MAX_SDIV)
+ return -EINVAL;
+
+ *intp = intp_ul;
+ *sout = sout_ul;
+
+ return 0;
+}
+
+static int amb_pll_determine_rate(struct clk_hw *hw,
+ struct clk_rate_request *req)
+{
+ struct amb_pll *pll = to_amb_pll(hw);
+ const struct amb_pll_soc_data *soc_data = pll->soc_data;
+ u32 ctrl2_val = 0;
+ u32 intp, sdiv, sout, vcodiv, fsdiv, fsout;
+ unsigned long rate;
+ u64 rounded;
+ int ret;
+
+ if (!req->best_parent_rate)
+ return -EINVAL;
+
+ if (!req->rate || pll->frac_mode)
+ return 0;
+
+ if (req->rate > ULONG_MAX / pll->fix_divider)
+ return -ERANGE;
+
+ rate = req->rate * pll->fix_divider;
+ if (soc_data->ctrl2_val)
+ ctrl2_val = soc_data->ctrl2_val;
+ else
+ regmap_read(pll->map, pll->reg_offset[CTRL2_OFFSET],
+ &ctrl2_val);
+
+ ret = amb_pll_calc_params(pll, rate, req->best_parent_rate,
+ ctrl2_val, &intp, &sdiv, &sout, &vcodiv,
+ &fsdiv, &fsout);
+ if (ret)
+ return ret;
+
+ rounded = (u64)req->best_parent_rate * fsdiv * intp * sdiv;
+ rounded = div64_u64(rounded, (u64)fsout * sout);
+ rounded = div64_u64(rounded, pll->fix_divider);
+ if (rounded < req->min_rate || rounded > req->max_rate)
+ return -EINVAL;
+
+ req->rate = rounded;
+
+ return 0;
+}
+
+static int amb_pll_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct amb_pll *pll = to_amb_pll(hw);
+ const struct amb_pll_soc_data *soc_data = pll->soc_data;
+ u32 *reg = pll->reg_offset;
+ u32 ctrl_val, ctrl2_val = 0, ctrl3_val, frac_val = 0;
+ u32 intp, sdiv, sout, vcodiv, fsdiv, fsout;
+ unsigned long old_rate, new_rate, rate_tmp;
+ int ret;
+
+ if (!rate) {
+ regmap_read(pll->map, reg[CTRL_OFFSET], &ctrl_val);
+ ctrl_val |= CTRL_POWER_DOWN | CTRL_HALT_VCO;
+ amb_pll_write_enable(pll->map, reg[CTRL_OFFSET], ctrl_val);
+ return 0;
+ }
+
+ if (!parent_rate)
+ return -EINVAL;
+
+ rate *= pll->fix_divider;
+
+ if (soc_data->ctrl2_val)
+ ctrl2_val = soc_data->ctrl2_val;
+ else
+ regmap_read(pll->map, reg[CTRL2_OFFSET], &ctrl2_val);
+
+ ret = amb_pll_calc_params(pll, rate, parent_rate, ctrl2_val,
+ &intp, &sdiv, &sout, &vcodiv, &fsdiv,
+ &fsout);
+ if (ret)
+ return ret;
+
+ if (soc_data->ctrl2_val)
+ regmap_write(pll->map, reg[CTRL2_OFFSET], soc_data->ctrl2_val);
+
+ ctrl_val = ((intp - 1) & 0x7f) << 24;
+ ctrl_val |= ((sdiv - 1) & 0xf) << 12;
+ ctrl_val |= ((sout - 1) & 0xf) << 16;
+ if (soc_data->pll_version >= 2) {
+ ctrl_val |= vcodiv == 2 ? soc_data->vcodiv_val : 0;
+ ctrl_val |= fsdiv == 2 ? soc_data->fsdiv_val : 0;
+ ctrl_val |= fsout == 2 ? soc_data->fsout_val : 0;
+ }
+
+ regmap_write(pll->map, reg[CTRL_OFFSET], ctrl_val);
+ regmap_write(pll->map, reg[FRAC_OFFSET], 0);
+
+ old_rate = amb_pll_recalc_rate(hw, parent_rate) * pll->fix_divider;
+ rate_tmp = old_rate > rate ? 0 : rate - old_rate;
+ if (rate_tmp && pll->frac_mode) {
+ u64 dividend, divider;
+
+ dividend = (u64)rate_tmp * sout * fsout;
+ dividend <<= 32;
+ divider = (u64)sdiv * fsdiv * parent_rate;
+ frac_val = DIV64_U64_ROUND_CLOSEST(dividend, divider);
+ regmap_write(pll->map, reg[FRAC_OFFSET], frac_val);
+ ctrl_val |= CTRL_FRAC_MODE;
+ }
+
+ if (soc_data->pll_version >= 2) {
+ ctrl3_val = soc_data->ctrl3_val;
+ regmap_write(pll->map, reg[CTRL3_OFFSET],
+ ctrl3_val | CTRL3_VCO_CLAMP);
+ regmap_write(pll->map, reg[CTRL_OFFSET],
+ ctrl_val | CTRL_FORCE_RESET);
+ ndelay(100);
+ regmap_write(pll->map, reg[CTRL_OFFSET],
+ ctrl_val & ~CTRL_FORCE_RESET);
+ ndelay(100);
+ regmap_write(pll->map, reg[CTRL3_OFFSET],
+ ctrl3_val & ~CTRL3_VCO_CLAMP);
+ } else {
+ u32 fvco_mhz, range;
+
+ fvco_mhz = amb_pll_calc_vco(pll, parent_rate) / 1000000UL;
+ for (range = 0; range < ARRAY_SIZE(soc_data->vco_range);
+ range++) {
+ if (fvco_mhz > soc_data->vco_range[range])
+ break;
+ }
+ if (range == ARRAY_SIZE(soc_data->vco_range))
+ range = 0;
+ else
+ range = ARRAY_SIZE(soc_data->vco_range) - range - 1;
+
+ regmap_read(pll->map, reg[CTRL3_OFFSET], &ctrl3_val);
+ ctrl3_val &= ~CTRL3_VCO_RANGE_MASK;
+ ctrl3_val |= range << 1;
+ regmap_write(pll->map, reg[CTRL3_OFFSET], ctrl3_val);
+
+ if (frac_val) {
+ ctrl_val |= CTRL_FORCE_RESET;
+ amb_pll_write_enable(pll->map, reg[CTRL_OFFSET],
+ ctrl_val);
+ }
+
+ ctrl_val &= ~CTRL_FORCE_RESET;
+ amb_pll_write_enable(pll->map, reg[CTRL_OFFSET], ctrl_val);
+ }
+
+ new_rate = amb_pll_recalc_rate(hw, parent_rate);
+ rate_tmp = rate / pll->fix_divider;
+ if (max(new_rate, rate_tmp) - min(new_rate, rate_tmp) > 10)
+ pr_warn("%s: requested %lu, got %lu\n", clk_hw_get_name(hw),
+ rate_tmp, new_rate);
+
+ return 0;
+}
+
+static const struct clk_ops amb_pll_ops = {
+ .recalc_rate = amb_pll_recalc_rate,
+ .determine_rate = amb_pll_determine_rate,
+ .set_rate = amb_pll_set_rate,
+};
+
+struct clk_hw *amb_pll_register(struct device *dev, struct regmap *map,
+ const struct amb_pll_desc *desc)
+{
+ struct amb_pll *pll;
+ struct clk_init_data init = {};
+ const struct clk_hw *parent = desc->parent;
+ int ret;
+
+ pll = devm_kzalloc(dev, sizeof(*pll), GFP_KERNEL);
+ if (!pll)
+ return ERR_PTR(-ENOMEM);
+
+ pll->map = map;
+ memcpy(pll->reg_offset, desc->reg_offset, sizeof(pll->reg_offset));
+ pll->soc_data = desc->soc_data;
+ pll->fix_divider = 1;
+ pll->frac_mode = desc->frac_mode;
+
+ init.name = desc->name;
+ init.ops = &amb_pll_ops;
+ init.flags = CLK_GET_RATE_NOCACHE | CLK_IS_CRITICAL;
+ init.parent_hws = &parent;
+ init.num_parents = 1;
+
+ pll->hw.init = &init;
+
+ ret = devm_clk_hw_register(dev, &pll->hw);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return &pll->hw;
+}
diff --git a/drivers/clk/ambarella/ccu_pll.h b/drivers/clk/ambarella/ccu_pll.h
new file mode 100644
index 000000000000..b9e453e9debf
--- /dev/null
+++ b/drivers/clk/ambarella/ccu_pll.h
@@ -0,0 +1,70 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2026 Ambarella, Inc.
+ */
+
+#ifndef __CCU_PLL_H
+#define __CCU_PLL_H
+
+#include <linux/bits.h>
+#include <linux/clk-provider.h>
+#include <linux/device.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+
+enum {
+ CTRL_OFFSET = 0,
+ FRAC_OFFSET,
+ CTRL2_OFFSET,
+ CTRL3_OFFSET,
+ PRES_OFFSET,
+ POST_OFFSET,
+ REG_NUM,
+};
+
+#define CTRL_BYPASS BIT(2)
+#define CTRL_WRITE_ENABLE BIT(0)
+#define CTRL_FRAC_MODE BIT(3)
+#define CTRL_FORCE_RESET BIT(4)
+#define CTRL_POWER_DOWN BIT(5)
+#define CTRL_HALT_VCO BIT(6)
+#define CTRL_VCODIV_DIV2 BIT(8)
+#define CTRL_FSDIV_DIV2 BIT(9)
+#define CTRL_FSOUT_DIV2 BIT(10)
+#define CTRL_BYPASS_HSDIV BIT(11)
+
+#define CTRL2_VCODIV_DIV2 BIT(8)
+#define CTRL2_FSDIV_DIV2 BIT(9)
+#define CTRL2_FSOUT_DIV2 BIT(11)
+#define CTRL2_BYPASS_HSDIV BIT(12)
+
+#define CTRL3_VCO_RANGE_MASK 0x6
+#define CTRL3_VCO_CLAMP 0x8
+
+struct amb_pll_soc_data {
+ u32 pll_version;
+ u32 fsout_mask;
+ u32 fsout_val;
+ u32 fsdiv_mask;
+ u32 fsdiv_val;
+ u32 vcodiv_mask;
+ u32 vcodiv_val;
+ u32 vco_max_mhz;
+ u32 vco_min_mhz;
+ u32 vco_range[4];
+ u32 ctrl2_val;
+ u32 ctrl3_val;
+};
+
+struct amb_pll_desc {
+ const char *name;
+ const struct clk_hw *parent;
+ const u32 reg_offset[REG_NUM];
+ const struct amb_pll_soc_data *soc_data;
+ bool frac_mode;
+};
+
+struct clk_hw *amb_pll_register(struct device *dev, struct regmap *map,
+ const struct amb_pll_desc *desc);
+
+#endif
--
2.34.1
^ permalink raw reply related [flat|nested] 17+ messages in thread* [PATCH v5 08/12] gpio: pl061: add Ambarella CV75 register layout variant
2026-08-18 10:31 [PATCH v5 00/12] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
` (6 preceding siblings ...)
2026-08-18 10:31 ` [PATCH v5 07/12] clk: ambarella: add CV75 CCU driver Long Zhao via B4 Relay
@ 2026-08-18 10:31 ` Long Zhao via B4 Relay
2026-08-18 14:11 ` Andy Shevchenko
2026-08-18 10:31 ` [PATCH v5 09/12] pinctrl: ambarella: add CV75 pin controller Long Zhao via B4 Relay
` (3 subsequent siblings)
11 siblings, 1 reply; 17+ messages in thread
From: Long Zhao via B4 Relay @ 2026-08-18 10:31 UTC (permalink / raw)
To: Arnd Bergmann, Krzysztof Kozlowski, Alexandre Belloni, soc,
linux-arm-kernel
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
Stephen Boyd, Linus Walleij, Bartosz Golaszewski,
Greg Kroah-Hartman, Jiri Slaby, Andy Shevchenko, Catalin Marinas,
Will Deacon, devicetree, linux-clk, linux-gpio, linux-serial,
linux-kernel, Brian Masney, Lee Jones, mfd, Long Zhao, Long Zhao
From: Long Zhao <longzhao@ambarella.com>
Extend gpio-pl061 with a per-variant register layout so the Ambarella
CV75 GPIO banks can reuse the PL061 driver instead of a duplicate.
Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
drivers/gpio/gpio-pl061.c | 314 ++++++++++++++++++++++++++++++++++++----------
1 file changed, 246 insertions(+), 68 deletions(-)
diff --git a/drivers/gpio/gpio-pl061.c b/drivers/gpio/gpio-pl061.c
index 919cf86fd590..7002ddbc9713 100644
--- a/drivers/gpio/gpio-pl061.c
+++ b/drivers/gpio/gpio-pl061.c
@@ -26,41 +26,107 @@
#include <linux/slab.h>
#include <linux/spinlock.h>
-#define GPIODIR 0x400
-#define GPIOIS 0x404
-#define GPIOIBE 0x408
-#define GPIOIEV 0x40C
-#define GPIOIE 0x410
-#define GPIORIS 0x414
-#define GPIOMIS 0x418
-#define GPIOIC 0x41C
-
#define PL061_GPIO_NR 8
+#define PL061_REG_NONE U32_MAX
+#define PL061_AMBARELLA_PERIPH_ID 0x00000061
+
+struct pl061_variant_data {
+ u32 data;
+ u32 dir;
+ u32 is;
+ u32 ibe;
+ u32 iev;
+ u32 ie;
+ u32 ris;
+ u32 mis;
+ u32 ic;
+ u32 afsel;
+ u32 mask;
+ u32 enable;
+ unsigned int ngpio;
+ bool access_32bit;
+ bool masked_data_address;
+ bool write_data_after_dir;
+ bool clear_irq_on_type;
+};
struct pl061_context_save_regs {
- u8 gpio_data;
- u8 gpio_dir;
- u8 gpio_is;
- u8 gpio_ibe;
- u8 gpio_iev;
- u8 gpio_ie;
+ u32 gpio_data;
+ u32 gpio_dir;
+ u32 gpio_is;
+ u32 gpio_ibe;
+ u32 gpio_iev;
+ u32 gpio_ie;
+ u32 gpio_afsel;
+ u32 gpio_mask;
};
struct pl061 {
raw_spinlock_t lock;
void __iomem *base;
+ const struct pl061_variant_data *variant;
struct gpio_chip gc;
int parent_irq;
struct pl061_context_save_regs csave_regs;
};
+static u32 pl061_read(struct pl061 *pl061, u32 offset)
+{
+ if (pl061->variant->access_32bit)
+ return readl_relaxed(pl061->base + offset);
+
+ return readb_relaxed(pl061->base + offset);
+}
+
+static void pl061_write(struct pl061 *pl061, u32 value, u32 offset)
+{
+ if (pl061->variant->access_32bit)
+ writel_relaxed(value, pl061->base + offset);
+ else
+ writeb_relaxed(value, pl061->base + offset);
+}
+
+static int pl061_get_data(struct pl061 *pl061, unsigned int offset)
+{
+ if (pl061->variant->masked_data_address)
+ return !!readb_relaxed(pl061->base + BIT(offset + 2));
+
+ pl061_write(pl061, BIT(offset), pl061->variant->mask);
+ return !!(pl061_read(pl061, pl061->variant->data) & BIT(offset));
+}
+
+static void pl061_set_data(struct pl061 *pl061, unsigned int offset, int value)
+{
+ if (pl061->variant->masked_data_address) {
+ writeb_relaxed(!!value << offset,
+ pl061->base + BIT(offset + 2));
+ return;
+ }
+
+ pl061_write(pl061, BIT(offset), pl061->variant->mask);
+ pl061_write(pl061, value ? BIT(offset) : 0, pl061->variant->data);
+}
+
+static void pl061_claim_gpio(struct pl061 *pl061, unsigned int offset)
+{
+ u32 afsel;
+
+ if (pl061->variant->afsel == PL061_REG_NONE)
+ return;
+
+ afsel = pl061_read(pl061, pl061->variant->afsel);
+ if (afsel & BIT(offset))
+ pl061_write(pl061, afsel & ~BIT(offset),
+ pl061->variant->afsel);
+}
+
static int pl061_get_direction(struct gpio_chip *gc, unsigned offset)
{
struct pl061 *pl061 = gpiochip_get_data(gc);
- if (readb(pl061->base + GPIODIR) & BIT(offset))
+ if (pl061_read(pl061, pl061->variant->dir) & BIT(offset))
return GPIO_LINE_DIRECTION_OUT;
return GPIO_LINE_DIRECTION_IN;
@@ -70,12 +136,13 @@ static int pl061_direction_input(struct gpio_chip *gc, unsigned offset)
{
struct pl061 *pl061 = gpiochip_get_data(gc);
unsigned long flags;
- unsigned char gpiodir;
+ u32 gpiodir;
raw_spin_lock_irqsave(&pl061->lock, flags);
- gpiodir = readb(pl061->base + GPIODIR);
+ gpiodir = pl061_read(pl061, pl061->variant->dir);
gpiodir &= ~(BIT(offset));
- writeb(gpiodir, pl061->base + GPIODIR);
+ pl061_write(pl061, gpiodir, pl061->variant->dir);
+ pl061_claim_gpio(pl061, offset);
raw_spin_unlock_irqrestore(&pl061->lock, flags);
return 0;
@@ -86,19 +153,21 @@ static int pl061_direction_output(struct gpio_chip *gc, unsigned offset,
{
struct pl061 *pl061 = gpiochip_get_data(gc);
unsigned long flags;
- unsigned char gpiodir;
+ u32 gpiodir;
raw_spin_lock_irqsave(&pl061->lock, flags);
- writeb(!!value << offset, pl061->base + (BIT(offset + 2)));
- gpiodir = readb(pl061->base + GPIODIR);
+ pl061_set_data(pl061, offset, value);
+ gpiodir = pl061_read(pl061, pl061->variant->dir);
gpiodir |= BIT(offset);
- writeb(gpiodir, pl061->base + GPIODIR);
+ pl061_write(pl061, gpiodir, pl061->variant->dir);
/*
* gpio value is set again, because pl061 doesn't allow to set value of
* a gpio pin before configuring it in OUT mode.
*/
- writeb(!!value << offset, pl061->base + (BIT(offset + 2)));
+ if (pl061->variant->write_data_after_dir)
+ pl061_set_data(pl061, offset, value);
+ pl061_claim_gpio(pl061, offset);
raw_spin_unlock_irqrestore(&pl061->lock, flags);
return 0;
@@ -107,15 +176,24 @@ static int pl061_direction_output(struct gpio_chip *gc, unsigned offset,
static int pl061_get_value(struct gpio_chip *gc, unsigned offset)
{
struct pl061 *pl061 = gpiochip_get_data(gc);
+ unsigned long flags;
+ int value;
- return !!readb(pl061->base + (BIT(offset + 2)));
+ raw_spin_lock_irqsave(&pl061->lock, flags);
+ value = pl061_get_data(pl061, offset);
+ raw_spin_unlock_irqrestore(&pl061->lock, flags);
+
+ return value;
}
static int pl061_set_value(struct gpio_chip *gc, unsigned int offset, int value)
{
struct pl061 *pl061 = gpiochip_get_data(gc);
+ unsigned long flags;
- writeb(!!value << offset, pl061->base + (BIT(offset + 2)));
+ raw_spin_lock_irqsave(&pl061->lock, flags);
+ pl061_set_data(pl061, offset, value);
+ raw_spin_unlock_irqrestore(&pl061->lock, flags);
return 0;
}
@@ -126,15 +204,14 @@ static int pl061_irq_type(struct irq_data *d, unsigned trigger)
struct pl061 *pl061 = gpiochip_get_data(gc);
int offset = irqd_to_hwirq(d);
unsigned long flags;
- u8 gpiois, gpioibe, gpioiev;
- u8 bit = BIT(offset);
+ u32 gpiois, gpioibe, gpioiev;
+ u32 bit = BIT(offset);
- if (offset < 0 || offset >= PL061_GPIO_NR)
+ if (offset < 0 || offset >= gc->ngpio)
return -EINVAL;
if ((trigger & (IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_LEVEL_LOW)) &&
- (trigger & (IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING)))
- {
+ (trigger & (IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING))) {
dev_err(gc->parent,
"trying to configure line %d for both level and edge "
"detection, choose one!\n",
@@ -142,12 +219,11 @@ static int pl061_irq_type(struct irq_data *d, unsigned trigger)
return -EINVAL;
}
-
raw_spin_lock_irqsave(&pl061->lock, flags);
- gpioiev = readb(pl061->base + GPIOIEV);
- gpiois = readb(pl061->base + GPIOIS);
- gpioibe = readb(pl061->base + GPIOIBE);
+ gpioiev = pl061_read(pl061, pl061->variant->iev);
+ gpiois = pl061_read(pl061, pl061->variant->is);
+ gpioibe = pl061_read(pl061, pl061->variant->ibe);
if (trigger & (IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_LEVEL_LOW)) {
bool polarity = trigger & IRQ_TYPE_LEVEL_HIGH;
@@ -199,9 +275,11 @@ static int pl061_irq_type(struct irq_data *d, unsigned trigger)
offset);
}
- writeb(gpiois, pl061->base + GPIOIS);
- writeb(gpioibe, pl061->base + GPIOIBE);
- writeb(gpioiev, pl061->base + GPIOIEV);
+ pl061_write(pl061, gpiois, pl061->variant->is);
+ pl061_write(pl061, gpioibe, pl061->variant->ibe);
+ pl061_write(pl061, gpioiev, pl061->variant->iev);
+ if (pl061->variant->clear_irq_on_type)
+ pl061_write(pl061, bit, pl061->variant->ic);
raw_spin_unlock_irqrestore(&pl061->lock, flags);
@@ -218,9 +296,9 @@ static void pl061_irq_handler(struct irq_desc *desc)
chained_irq_enter(irqchip, desc);
- pending = readb(pl061->base + GPIOMIS);
+ pending = pl061_read(pl061, pl061->variant->mis);
if (pending) {
- for_each_set_bit(offset, &pending, PL061_GPIO_NR)
+ for_each_set_bit(offset, &pending, gc->ngpio)
generic_handle_domain_irq(gc->irq.domain,
offset);
}
@@ -232,12 +310,12 @@ static void pl061_irq_mask(struct irq_data *d)
{
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
struct pl061 *pl061 = gpiochip_get_data(gc);
- u8 mask = BIT(irqd_to_hwirq(d) % PL061_GPIO_NR);
- u8 gpioie;
+ u32 mask = BIT(irqd_to_hwirq(d));
+ u32 gpioie;
raw_spin_lock(&pl061->lock);
- gpioie = readb(pl061->base + GPIOIE) & ~mask;
- writeb(gpioie, pl061->base + GPIOIE);
+ gpioie = pl061_read(pl061, pl061->variant->ie) & ~mask;
+ pl061_write(pl061, gpioie, pl061->variant->ie);
raw_spin_unlock(&pl061->lock);
gpiochip_disable_irq(gc, d->hwirq);
@@ -247,14 +325,14 @@ static void pl061_irq_unmask(struct irq_data *d)
{
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
struct pl061 *pl061 = gpiochip_get_data(gc);
- u8 mask = BIT(irqd_to_hwirq(d) % PL061_GPIO_NR);
- u8 gpioie;
+ u32 mask = BIT(irqd_to_hwirq(d));
+ u32 gpioie;
gpiochip_enable_irq(gc, d->hwirq);
raw_spin_lock(&pl061->lock);
- gpioie = readb(pl061->base + GPIOIE) | mask;
- writeb(gpioie, pl061->base + GPIOIE);
+ gpioie = pl061_read(pl061, pl061->variant->ie) | mask;
+ pl061_write(pl061, gpioie, pl061->variant->ie);
raw_spin_unlock(&pl061->lock);
}
@@ -270,10 +348,10 @@ static void pl061_irq_ack(struct irq_data *d)
{
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
struct pl061 *pl061 = gpiochip_get_data(gc);
- u8 mask = BIT(irqd_to_hwirq(d) % PL061_GPIO_NR);
+ u32 mask = BIT(irqd_to_hwirq(d));
raw_spin_lock(&pl061->lock);
- writeb(mask, pl061->base + GPIOIC);
+ pl061_write(pl061, mask, pl061->variant->ic);
raw_spin_unlock(&pl061->lock);
}
@@ -292,6 +370,24 @@ static void pl061_irq_print_chip(struct irq_data *data, struct seq_file *p)
seq_puts(p, dev_name(gc->parent));
}
+static int pl061_irq_request_resources(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct pl061 *pl061 = gpiochip_get_data(gc);
+ unsigned long flags;
+ int ret;
+
+ ret = gpiochip_irq_reqres(d);
+ if (ret)
+ return ret;
+
+ raw_spin_lock_irqsave(&pl061->lock, flags);
+ pl061_claim_gpio(pl061, irqd_to_hwirq(d));
+ raw_spin_unlock_irqrestore(&pl061->lock, flags);
+
+ return 0;
+}
+
static const struct irq_chip pl061_irq_chip = {
.irq_ack = pl061_irq_ack,
.irq_mask = pl061_irq_mask,
@@ -299,8 +395,9 @@ static const struct irq_chip pl061_irq_chip = {
.irq_set_type = pl061_irq_type,
.irq_set_wake = pl061_irq_set_wake,
.irq_print_chip = pl061_irq_print_chip,
+ .irq_request_resources = pl061_irq_request_resources,
+ .irq_release_resources = gpiochip_irq_relres,
.flags = IRQCHIP_IMMUTABLE,
- GPIOCHIP_IRQ_RESOURCE_HELPERS,
};
static int pl061_probe(struct amba_device *adev, const struct amba_id *id)
@@ -314,6 +411,10 @@ static int pl061_probe(struct amba_device *adev, const struct amba_id *id)
if (pl061 == NULL)
return -ENOMEM;
+ pl061->variant = id->data;
+ if (!pl061->variant)
+ return dev_err_probe(dev, -EINVAL, "missing variant data\n");
+
pl061->base = devm_ioremap_resource(dev, &adev->res);
if (IS_ERR(pl061->base))
return PTR_ERR(pl061->base);
@@ -327,7 +428,7 @@ static int pl061_probe(struct amba_device *adev, const struct amba_id *id)
pl061->gc.direction_output = pl061_direction_output;
pl061->gc.get = pl061_get_value;
pl061->gc.set = pl061_set_value;
- pl061->gc.ngpio = PL061_GPIO_NR;
+ pl061->gc.ngpio = pl061->variant->ngpio;
pl061->gc.label = dev_name(dev);
pl061->gc.parent = dev;
pl061->gc.owner = THIS_MODULE;
@@ -335,7 +436,12 @@ static int pl061_probe(struct amba_device *adev, const struct amba_id *id)
/*
* irq_chip support
*/
- writeb(0, pl061->base + GPIOIE); /* disable irqs */
+ pl061_write(pl061, 0, pl061->variant->ie); /* disable irqs */
+ if (pl061->variant->enable != PL061_REG_NONE)
+ pl061_write(pl061, GENMASK(pl061->gc.ngpio - 1, 0),
+ pl061->variant->enable);
+ if (pl061->variant->mask != PL061_REG_NONE)
+ pl061_write(pl061, 0, pl061->variant->mask);
irq = adev->irq[0];
if (!irq)
dev_warn(&adev->dev, "IRQ support disabled\n");
@@ -366,20 +472,37 @@ static int pl061_probe(struct amba_device *adev, const struct amba_id *id)
static int pl061_suspend(struct device *dev)
{
struct pl061 *pl061 = dev_get_drvdata(dev);
+ unsigned long flags;
int offset;
+ raw_spin_lock_irqsave(&pl061->lock, flags);
pl061->csave_regs.gpio_data = 0;
- pl061->csave_regs.gpio_dir = readb(pl061->base + GPIODIR);
- pl061->csave_regs.gpio_is = readb(pl061->base + GPIOIS);
- pl061->csave_regs.gpio_ibe = readb(pl061->base + GPIOIBE);
- pl061->csave_regs.gpio_iev = readb(pl061->base + GPIOIEV);
- pl061->csave_regs.gpio_ie = readb(pl061->base + GPIOIE);
-
- for (offset = 0; offset < PL061_GPIO_NR; offset++) {
- if (pl061->csave_regs.gpio_dir & (BIT(offset)))
- pl061->csave_regs.gpio_data |=
- pl061_get_value(&pl061->gc, offset) << offset;
+ pl061->csave_regs.gpio_dir =
+ pl061_read(pl061, pl061->variant->dir);
+ pl061->csave_regs.gpio_is =
+ pl061_read(pl061, pl061->variant->is);
+ pl061->csave_regs.gpio_ibe =
+ pl061_read(pl061, pl061->variant->ibe);
+ pl061->csave_regs.gpio_iev =
+ pl061_read(pl061, pl061->variant->iev);
+ pl061->csave_regs.gpio_ie =
+ pl061_read(pl061, pl061->variant->ie);
+ if (pl061->variant->afsel != PL061_REG_NONE)
+ pl061->csave_regs.gpio_afsel =
+ pl061_read(pl061, pl061->variant->afsel);
+ if (pl061->variant->mask != PL061_REG_NONE)
+ pl061->csave_regs.gpio_mask =
+ pl061_read(pl061, pl061->variant->mask);
+
+ for (offset = 0; offset < pl061->gc.ngpio; offset++) {
+ if ((pl061->csave_regs.gpio_dir & BIT(offset)) &&
+ pl061_get_data(pl061, offset))
+ pl061->csave_regs.gpio_data |= BIT(offset);
}
+ if (pl061->variant->mask != PL061_REG_NONE)
+ pl061_write(pl061, pl061->csave_regs.gpio_mask,
+ pl061->variant->mask);
+ raw_spin_unlock_irqrestore(&pl061->lock, flags);
return 0;
}
@@ -389,7 +512,7 @@ static int pl061_resume(struct device *dev)
struct pl061 *pl061 = dev_get_drvdata(dev);
int offset;
- for (offset = 0; offset < PL061_GPIO_NR; offset++) {
+ for (offset = 0; offset < pl061->gc.ngpio; offset++) {
if (pl061->csave_regs.gpio_dir & (BIT(offset)))
pl061_direction_output(&pl061->gc, offset,
pl061->csave_regs.gpio_data &
@@ -398,20 +521,75 @@ static int pl061_resume(struct device *dev)
pl061_direction_input(&pl061->gc, offset);
}
- writeb(pl061->csave_regs.gpio_is, pl061->base + GPIOIS);
- writeb(pl061->csave_regs.gpio_ibe, pl061->base + GPIOIBE);
- writeb(pl061->csave_regs.gpio_iev, pl061->base + GPIOIEV);
- writeb(pl061->csave_regs.gpio_ie, pl061->base + GPIOIE);
+ pl061_write(pl061, pl061->csave_regs.gpio_is,
+ pl061->variant->is);
+ pl061_write(pl061, pl061->csave_regs.gpio_ibe,
+ pl061->variant->ibe);
+ pl061_write(pl061, pl061->csave_regs.gpio_iev,
+ pl061->variant->iev);
+ pl061_write(pl061, pl061->csave_regs.gpio_ie,
+ pl061->variant->ie);
+ if (pl061->variant->afsel != PL061_REG_NONE)
+ pl061_write(pl061, pl061->csave_regs.gpio_afsel,
+ pl061->variant->afsel);
+ if (pl061->variant->mask != PL061_REG_NONE)
+ pl061_write(pl061, pl061->csave_regs.gpio_mask,
+ pl061->variant->mask);
+ if (pl061->variant->enable != PL061_REG_NONE)
+ pl061_write(pl061, GENMASK(pl061->gc.ngpio - 1, 0),
+ pl061->variant->enable);
return 0;
}
static DEFINE_SIMPLE_DEV_PM_OPS(pl061_dev_pm_ops, pl061_suspend, pl061_resume);
+static struct pl061_variant_data pl061_arm = {
+ .data = 0x000,
+ .dir = 0x400,
+ .is = 0x404,
+ .ibe = 0x408,
+ .iev = 0x40c,
+ .ie = 0x410,
+ .ris = 0x414,
+ .mis = 0x418,
+ .ic = 0x41c,
+ .afsel = PL061_REG_NONE,
+ .mask = PL061_REG_NONE,
+ .enable = PL061_REG_NONE,
+ .ngpio = PL061_GPIO_NR,
+ .masked_data_address = true,
+ .write_data_after_dir = true,
+};
+
+static struct pl061_variant_data pl061_ambarella = {
+ .data = 0x00,
+ .dir = 0x04,
+ .is = 0x08,
+ .ibe = 0x0c,
+ .iev = 0x10,
+ .ie = 0x14,
+ .afsel = 0x18,
+ .ris = 0x1c,
+ .mis = 0x20,
+ .ic = 0x24,
+ .mask = 0x28,
+ .enable = 0x2c,
+ .ngpio = 32,
+ .access_32bit = true,
+ .clear_irq_on_type = true,
+};
+
static const struct amba_id pl061_ids[] = {
{
.id = 0x00041061,
.mask = 0x000fffff,
+ .data = &pl061_arm,
+ },
+ {
+ .id = PL061_AMBARELLA_PERIPH_ID,
+ .mask = 0xffffffff,
+ .data = &pl061_ambarella,
},
{ 0, 0 },
};
--
2.34.1
^ permalink raw reply related [flat|nested] 17+ messages in thread* Re: [PATCH v5 08/12] gpio: pl061: add Ambarella CV75 register layout variant
2026-08-18 10:31 ` [PATCH v5 08/12] gpio: pl061: add Ambarella CV75 register layout variant Long Zhao via B4 Relay
@ 2026-08-18 14:11 ` Andy Shevchenko
0 siblings, 0 replies; 17+ messages in thread
From: Andy Shevchenko @ 2026-08-18 14:11 UTC (permalink / raw)
To: longzhao
Cc: Arnd Bergmann, Krzysztof Kozlowski, Alexandre Belloni, soc,
linux-arm-kernel, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Michael Turquette, Stephen Boyd, Linus Walleij,
Bartosz Golaszewski, Greg Kroah-Hartman, Jiri Slaby,
Catalin Marinas, Will Deacon, devicetree, linux-clk, linux-gpio,
linux-serial, linux-kernel, Brian Masney, Lee Jones, mfd,
Long Zhao
On Tue, Aug 18, 2026 at 06:31:22PM +0800, Long Zhao via B4 Relay wrote:
> Extend gpio-pl061 with a per-variant register layout so the Ambarella
> CV75 GPIO banks can reuse the PL061 driver instead of a duplicate.
This is reinvention of gpio-mmio.c (as a library). It has necessary facilities.
Also gpio-regmap may be considered. So, switch this driver to use one of the
above and the patch for the new device will be much simpler and elegant.
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v5 09/12] pinctrl: ambarella: add CV75 pin controller
2026-08-18 10:31 [PATCH v5 00/12] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
` (7 preceding siblings ...)
2026-08-18 10:31 ` [PATCH v5 08/12] gpio: pl061: add Ambarella CV75 register layout variant Long Zhao via B4 Relay
@ 2026-08-18 10:31 ` Long Zhao via B4 Relay
2026-08-18 10:31 ` [PATCH v5 10/12] serial: 8250: add Ambarella UART driver Long Zhao via B4 Relay
` (2 subsequent siblings)
11 siblings, 0 replies; 17+ messages in thread
From: Long Zhao via B4 Relay @ 2026-08-18 10:31 UTC (permalink / raw)
To: Arnd Bergmann, Krzysztof Kozlowski, Alexandre Belloni, soc,
linux-arm-kernel
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
Stephen Boyd, Linus Walleij, Bartosz Golaszewski,
Greg Kroah-Hartman, Jiri Slaby, Andy Shevchenko, Catalin Marinas,
Will Deacon, devicetree, linux-clk, linux-gpio, linux-serial,
linux-kernel, Brian Masney, Lee Jones, mfd, Long Zhao, Long Zhao
From: Long Zhao <longzhao@ambarella.com>
Add an Ambarella pinmux-only pinctrl driver with CV75 function/group
tables. GPIO is handled by the PL061 driver.
Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
drivers/pinctrl/Kconfig | 15 +
drivers/pinctrl/Makefile | 2 +
drivers/pinctrl/pinctrl-ambarella-cv75.c | 561 ++++++++++++++++++++
drivers/pinctrl/pinctrl-ambarella.c | 863 +++++++++++++++++++++++++++++++
drivers/pinctrl/pinctrl-ambarella.h | 50 ++
5 files changed, 1491 insertions(+)
diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig
index c2cdd7b2c49b..da423792b5fa 100644
--- a/drivers/pinctrl/Kconfig
+++ b/drivers/pinctrl/Kconfig
@@ -37,6 +37,21 @@ config DEBUG_PINCTRL
help
Say Y here to add some extra checks and diagnostics to PINCTRL calls.
+config PINCTRL_AMB
+ bool "Ambarella pin controller support"
+ depends on ARCH_AMBARELLA || COMPILE_TEST
+ depends on HAS_IOMEM
+ default y if ARCH_AMBARELLA
+ select GENERIC_PINCONF
+ select GPIOLIB
+ select GPIOLIB_IRQCHIP
+ select GPIO_PL061
+ select MFD_SYSCON
+ select PINCONF
+ select PINMUX
+ help
+ Say Y here to enable the Ambarella pin controller driver.
+
config PINCTRL_AMD
bool "AMD GPIO pin control"
depends on HAS_IOMEM
diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile
index a35d71135abf..ce0825f4fc1b 100644
--- a/drivers/pinctrl/Makefile
+++ b/drivers/pinctrl/Makefile
@@ -10,6 +10,8 @@ obj-$(CONFIG_GENERIC_PINCONF) += pinconf-generic.o
obj-$(CONFIG_GENERIC_PINCTRL) += pinctrl-generic.o
obj-$(CONFIG_OF) += devicetree.o
+obj-$(CONFIG_PINCTRL_AMB) += pinctrl-ambarella.o
+obj-$(CONFIG_PINCTRL_AMB) += pinctrl-ambarella-cv75.o
obj-$(CONFIG_PINCTRL_AMD) += pinctrl-amd.o
obj-$(CONFIG_PINCTRL_AMDISP) += pinctrl-amdisp.o
obj-$(CONFIG_PINCTRL_APPLE_GPIO) += pinctrl-apple-gpio.o
diff --git a/drivers/pinctrl/pinctrl-ambarella-cv75.c b/drivers/pinctrl/pinctrl-ambarella-cv75.c
new file mode 100644
index 000000000000..e617f0717b31
--- /dev/null
+++ b/drivers/pinctrl/pinctrl-ambarella-cv75.c
@@ -0,0 +1,561 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Ambarella CV75 pinctrl data
+ *
+ * Copyright (C) 2026, Ambarella, Inc.
+ */
+
+#include <linux/kernel.h>
+
+#include "pinctrl-ambarella.h"
+
+#define CV75_PINMUX_GROUP(_name, ...) \
+ static const u32 cv75_##_name##_pinmux[] = { __VA_ARGS__ }
+
+#define CV75_GROUP(_name) \
+ { \
+ .name = #_name, \
+ .pinmux = cv75_##_name##_pinmux, \
+ .num_pins = ARRAY_SIZE(cv75_##_name##_pinmux), \
+ }
+
+#define CV75_FUNCTION(_name) \
+ { \
+ .name = #_name, \
+ .groups = cv75_##_name##_groups, \
+ .num_groups = ARRAY_SIZE(cv75_##_name##_groups), \
+ }
+
+/* UART */
+CV75_PINMUX_GROUP(uart0, AMBA_PINMUX(44, 1), AMBA_PINMUX(45, 1));
+CV75_PINMUX_GROUP(uart1, AMBA_PINMUX(46, 1), AMBA_PINMUX(47, 1));
+CV75_PINMUX_GROUP(uart1_flow, AMBA_PINMUX(48, 1), AMBA_PINMUX(49, 1));
+CV75_PINMUX_GROUP(uart2_a, AMBA_PINMUX(46, 2), AMBA_PINMUX(47, 2));
+CV75_PINMUX_GROUP(uart2_b, AMBA_PINMUX(50, 2), AMBA_PINMUX(51, 2));
+CV75_PINMUX_GROUP(uart2_c, AMBA_PINMUX(66, 2), AMBA_PINMUX(68, 2));
+CV75_PINMUX_GROUP(uart2_flow_a, AMBA_PINMUX(48, 2), AMBA_PINMUX(49, 2));
+CV75_PINMUX_GROUP(uart2_flow_b, AMBA_PINMUX(65, 3), AMBA_PINMUX(67, 3));
+CV75_PINMUX_GROUP(uart3_a, AMBA_PINMUX(70, 3), AMBA_PINMUX(72, 3));
+CV75_PINMUX_GROUP(uart3_b, AMBA_PINMUX(80, 3), AMBA_PINMUX(79, 3));
+CV75_PINMUX_GROUP(uart3_flow_a, AMBA_PINMUX(71, 3), AMBA_PINMUX(69, 3));
+CV75_PINMUX_GROUP(uart3_flow_b, AMBA_PINMUX(81, 3), AMBA_PINMUX(82, 3));
+CV75_PINMUX_GROUP(uart4_a, AMBA_PINMUX(29, 3), AMBA_PINMUX(30, 3));
+CV75_PINMUX_GROUP(uart4_b, AMBA_PINMUX(76, 3), AMBA_PINMUX(77, 3));
+CV75_PINMUX_GROUP(uart4_flow_a, AMBA_PINMUX(28, 3), AMBA_PINMUX(31, 3));
+CV75_PINMUX_GROUP(uart4_flow_b, AMBA_PINMUX(74, 3), AMBA_PINMUX(75, 3));
+
+/* Flash */
+CV75_PINMUX_GROUP(snand, AMBA_PINMUX(79, 1), AMBA_PINMUX(80, 1),
+ AMBA_PINMUX(81, 1), AMBA_PINMUX(82, 1),
+ AMBA_PINMUX(83, 1), AMBA_PINMUX(84, 1));
+CV75_PINMUX_GROUP(spinor, AMBA_PINMUX(79, 2), AMBA_PINMUX(80, 2),
+ AMBA_PINMUX(81, 2), AMBA_PINMUX(82, 2),
+ AMBA_PINMUX(83, 2), AMBA_PINMUX(84, 2),
+ AMBA_PINMUX(85, 2));
+
+/* SD/MMC */
+CV75_PINMUX_GROUP(sdmmc0_cd, AMBA_PINMUX(6, 1));
+CV75_PINMUX_GROUP(sdmmc0_wp, AMBA_PINMUX(7, 1));
+CV75_PINMUX_GROUP(sdmmc0_reset, AMBA_PINMUX(8, 1));
+CV75_PINMUX_GROUP(sdmmc0_hs_sel, AMBA_PINMUX(93, 1));
+CV75_PINMUX_GROUP(sdmmc0_1bit, AMBA_PINMUX(0, 1), AMBA_PINMUX(4, 1),
+ AMBA_PINMUX(5, 1));
+CV75_PINMUX_GROUP(sdmmc0_4bit, AMBA_PINMUX(0, 1), AMBA_PINMUX(1, 1),
+ AMBA_PINMUX(2, 1), AMBA_PINMUX(3, 1),
+ AMBA_PINMUX(4, 1), AMBA_PINMUX(5, 1));
+CV75_PINMUX_GROUP(sdmmc1_cd, AMBA_PINMUX(15, 1));
+CV75_PINMUX_GROUP(sdmmc1_wp, AMBA_PINMUX(16, 1));
+CV75_PINMUX_GROUP(sdmmc1_reset, AMBA_PINMUX(17, 1));
+CV75_PINMUX_GROUP(sdmmc1_hs_sel, AMBA_PINMUX(94, 1));
+CV75_PINMUX_GROUP(sdmmc1_1bit, AMBA_PINMUX(9, 1), AMBA_PINMUX(13, 1),
+ AMBA_PINMUX(14, 1));
+CV75_PINMUX_GROUP(sdmmc1_4bit, AMBA_PINMUX(9, 1), AMBA_PINMUX(10, 1),
+ AMBA_PINMUX(11, 1), AMBA_PINMUX(12, 1),
+ AMBA_PINMUX(13, 1), AMBA_PINMUX(14, 1));
+
+/* Ethernet */
+CV75_PINMUX_GROUP(enet_ext_osc_clk, AMBA_PINMUX(77, 1));
+CV75_PINMUX_GROUP(enet_2nd_ref_clk_a, AMBA_PINMUX(78, 1));
+CV75_PINMUX_GROUP(enet_2nd_ref_clk_b, AMBA_PINMUX(76, 2));
+CV75_PINMUX_GROUP(enet0_ptp_pps_o, AMBA_PINMUX(74, 1));
+CV75_PINMUX_GROUP(rgmii0, AMBA_PINMUX(62, 1), AMBA_PINMUX(63, 1),
+ AMBA_PINMUX(64, 1), AMBA_PINMUX(65, 1),
+ AMBA_PINMUX(66, 1), AMBA_PINMUX(67, 1),
+ AMBA_PINMUX(68, 1), AMBA_PINMUX(69, 1),
+ AMBA_PINMUX(70, 1), AMBA_PINMUX(71, 1),
+ AMBA_PINMUX(72, 1), AMBA_PINMUX(73, 1),
+ AMBA_PINMUX(75, 1), AMBA_PINMUX(76, 1));
+CV75_PINMUX_GROUP(rmii0, AMBA_PINMUX(62, 1), AMBA_PINMUX(63, 1),
+ AMBA_PINMUX(64, 1), AMBA_PINMUX(67, 1),
+ AMBA_PINMUX(68, 1), AMBA_PINMUX(71, 1),
+ AMBA_PINMUX(72, 1), AMBA_PINMUX(73, 1),
+ AMBA_PINMUX(75, 2));
+
+/* I2C */
+CV75_PINMUX_GROUP(i2c0_a, AMBA_PINMUX(67, 2), AMBA_PINMUX(68, 2));
+CV75_PINMUX_GROUP(i2c0_b, AMBA_PINMUX(86, 1), AMBA_PINMUX(87, 1));
+CV75_PINMUX_GROUP(i2c1_a, AMBA_PINMUX(22, 2), AMBA_PINMUX(23, 2));
+CV75_PINMUX_GROUP(i2c1_b, AMBA_PINMUX(69, 2), AMBA_PINMUX(70, 2));
+CV75_PINMUX_GROUP(i2c2, AMBA_PINMUX(88, 1), AMBA_PINMUX(89, 1));
+CV75_PINMUX_GROUP(i2c3_a, AMBA_PINMUX(24, 2), AMBA_PINMUX(25, 2));
+CV75_PINMUX_GROUP(i2c3_b, AMBA_PINMUX(48, 3), AMBA_PINMUX(49, 3));
+CV75_PINMUX_GROUP(i2c3_c, AMBA_PINMUX(58, 3), AMBA_PINMUX(59, 3));
+CV75_PINMUX_GROUP(i2cs_a, AMBA_PINMUX(19, 2), AMBA_PINMUX(21, 2));
+CV75_PINMUX_GROUP(i2cs_b, AMBA_PINMUX(60, 3), AMBA_PINMUX(61, 3));
+CV75_PINMUX_GROUP(i2cs_c, AMBA_PINMUX(71, 2), AMBA_PINMUX(72, 2));
+CV75_PINMUX_GROUP(i2cs_d, AMBA_PINMUX(86, 2), AMBA_PINMUX(87, 2));
+
+/* CAN, IR, WDT */
+CV75_PINMUX_GROUP(can0, AMBA_PINMUX(50, 1), AMBA_PINMUX(51, 1));
+CV75_PINMUX_GROUP(can1, AMBA_PINMUX(52, 1), AMBA_PINMUX(53, 1));
+CV75_PINMUX_GROUP(ir, AMBA_PINMUX(18, 1));
+CV75_PINMUX_GROUP(wdt_a, AMBA_PINMUX(20, 2));
+CV75_PINMUX_GROUP(wdt_b, AMBA_PINMUX(27, 3));
+CV75_PINMUX_GROUP(wdt_c, AMBA_PINMUX(39, 2));
+CV75_PINMUX_GROUP(wdt_d, AMBA_PINMUX(83, 3));
+CV75_PINMUX_GROUP(wdt_e, AMBA_PINMUX(85, 4));
+CV75_PINMUX_GROUP(wdt_f, AMBA_PINMUX(90, 1));
+
+/* I2S */
+CV75_PINMUX_GROUP(i2s0, AMBA_PINMUX(54, 1), AMBA_PINMUX(55, 1),
+ AMBA_PINMUX(56, 1), AMBA_PINMUX(57, 1));
+CV75_PINMUX_GROUP(i2s1, AMBA_PINMUX(58, 1), AMBA_PINMUX(59, 1),
+ AMBA_PINMUX(60, 1), AMBA_PINMUX(61, 1));
+CV75_PINMUX_GROUP(clk_au, AMBA_PINMUX(96, 1));
+CV75_PINMUX_GROUP(dmic0, AMBA_PINMUX(54, 2), AMBA_PINMUX(55, 2));
+
+/* PWM */
+CV75_PINMUX_GROUP(pwm0, AMBA_PINMUX(40, 1));
+CV75_PINMUX_GROUP(pwm1, AMBA_PINMUX(41, 1));
+CV75_PINMUX_GROUP(pwm2, AMBA_PINMUX(42, 1));
+CV75_PINMUX_GROUP(pwm3, AMBA_PINMUX(43, 1));
+CV75_PINMUX_GROUP(pwm4_a, AMBA_PINMUX(19, 3));
+CV75_PINMUX_GROUP(pwm4_b, AMBA_PINMUX(32, 4));
+CV75_PINMUX_GROUP(pwm5_a, AMBA_PINMUX(20, 3));
+CV75_PINMUX_GROUP(pwm5_b, AMBA_PINMUX(33, 4));
+CV75_PINMUX_GROUP(pwm6_a, AMBA_PINMUX(21, 3));
+CV75_PINMUX_GROUP(pwm6_b, AMBA_PINMUX(34, 4));
+CV75_PINMUX_GROUP(pwm7_a, AMBA_PINMUX(22, 3));
+CV75_PINMUX_GROUP(pwm7_b, AMBA_PINMUX(35, 4));
+CV75_PINMUX_GROUP(pwm8_a, AMBA_PINMUX(23, 3));
+CV75_PINMUX_GROUP(pwm8_b, AMBA_PINMUX(36, 4));
+CV75_PINMUX_GROUP(pwm9_a, AMBA_PINMUX(24, 3));
+CV75_PINMUX_GROUP(pwm9_b, AMBA_PINMUX(37, 4));
+CV75_PINMUX_GROUP(pwm10_a, AMBA_PINMUX(25, 3));
+CV75_PINMUX_GROUP(pwm10_b, AMBA_PINMUX(38, 4));
+CV75_PINMUX_GROUP(pwm11_a, AMBA_PINMUX(26, 3));
+CV75_PINMUX_GROUP(pwm11_b, AMBA_PINMUX(39, 4));
+
+/* SPI */
+CV75_PINMUX_GROUP(spi0, AMBA_PINMUX(19, 1), AMBA_PINMUX(20, 1),
+ AMBA_PINMUX(21, 1));
+CV75_PINMUX_GROUP(spi1, AMBA_PINMUX(24, 1), AMBA_PINMUX(25, 1),
+ AMBA_PINMUX(26, 1));
+CV75_PINMUX_GROUP(spi2, AMBA_PINMUX(28, 1), AMBA_PINMUX(29, 1),
+ AMBA_PINMUX(30, 1));
+CV75_PINMUX_GROUP(spi3_a, AMBA_PINMUX(32, 5), AMBA_PINMUX(33, 5),
+ AMBA_PINMUX(34, 5));
+CV75_PINMUX_GROUP(spi3_b, AMBA_PINMUX(40, 2), AMBA_PINMUX(41, 2),
+ AMBA_PINMUX(43, 2));
+CV75_PINMUX_GROUP(spi3_c, AMBA_PINMUX(58, 2), AMBA_PINMUX(59, 2),
+ AMBA_PINMUX(60, 2));
+CV75_PINMUX_GROUP(spi_slave_a, AMBA_PINMUX(24, 4), AMBA_PINMUX(25, 4),
+ AMBA_PINMUX(26, 4), AMBA_PINMUX(27, 4));
+CV75_PINMUX_GROUP(spi_slave_b, AMBA_PINMUX(28, 2), AMBA_PINMUX(29, 2),
+ AMBA_PINMUX(30, 2), AMBA_PINMUX(31, 2));
+CV75_PINMUX_GROUP(spi_slave_c, AMBA_PINMUX(36, 5), AMBA_PINMUX(37, 5),
+ AMBA_PINMUX(38, 5), AMBA_PINMUX(39, 5));
+CV75_PINMUX_GROUP(spi_slave_d, AMBA_PINMUX(50, 3), AMBA_PINMUX(51, 3),
+ AMBA_PINMUX(52, 3), AMBA_PINMUX(53, 3));
+CV75_PINMUX_GROUP(spi_slave_e, AMBA_PINMUX(81, 4), AMBA_PINMUX(82, 4),
+ AMBA_PINMUX(83, 4), AMBA_PINMUX(84, 4));
+
+/* VIN master sync */
+CV75_PINMUX_GROUP(vin_master_sync_a, AMBA_PINMUX(91, 1),
+ AMBA_PINMUX(92, 1));
+CV75_PINMUX_GROUP(vin_master_sync_b, AMBA_PINMUX(91, 2),
+ AMBA_PINMUX(92, 2));
+CV75_PINMUX_GROUP(vin_master_sync_c, AMBA_PINMUX(40, 3),
+ AMBA_PINMUX(41, 3));
+CV75_PINMUX_GROUP(vin_master_sync_d, AMBA_PINMUX(46, 3),
+ AMBA_PINMUX(47, 3));
+CV75_PINMUX_GROUP(vin_master_sync_e, AMBA_PINMUX(79, 4),
+ AMBA_PINMUX(80, 4));
+CV75_PINMUX_GROUP(vsync0, AMBA_PINMUX(32, 1));
+CV75_PINMUX_GROUP(vsync1, AMBA_PINMUX(33, 1));
+CV75_PINMUX_GROUP(vsync2, AMBA_PINMUX(34, 1));
+CV75_PINMUX_GROUP(vsync3, AMBA_PINMUX(35, 1));
+CV75_PINMUX_GROUP(hsync0, AMBA_PINMUX(36, 1));
+CV75_PINMUX_GROUP(hsync1, AMBA_PINMUX(37, 1));
+
+static const struct ambpin_group_desc cv75_pin_groups[] = {
+ CV75_GROUP(uart0),
+ CV75_GROUP(uart1), CV75_GROUP(uart1_flow),
+ CV75_GROUP(uart2_a), CV75_GROUP(uart2_b), CV75_GROUP(uart2_c),
+ CV75_GROUP(uart2_flow_a), CV75_GROUP(uart2_flow_b),
+ CV75_GROUP(uart3_a), CV75_GROUP(uart3_b),
+ CV75_GROUP(uart3_flow_a), CV75_GROUP(uart3_flow_b),
+ CV75_GROUP(uart4_a), CV75_GROUP(uart4_b),
+ CV75_GROUP(uart4_flow_a), CV75_GROUP(uart4_flow_b),
+ CV75_GROUP(snand), CV75_GROUP(spinor),
+ CV75_GROUP(sdmmc0_cd), CV75_GROUP(sdmmc0_wp),
+ CV75_GROUP(sdmmc0_reset), CV75_GROUP(sdmmc0_hs_sel),
+ CV75_GROUP(sdmmc0_1bit), CV75_GROUP(sdmmc0_4bit),
+ CV75_GROUP(sdmmc1_cd), CV75_GROUP(sdmmc1_wp),
+ CV75_GROUP(sdmmc1_reset), CV75_GROUP(sdmmc1_hs_sel),
+ CV75_GROUP(sdmmc1_1bit), CV75_GROUP(sdmmc1_4bit),
+ CV75_GROUP(enet_ext_osc_clk), CV75_GROUP(enet_2nd_ref_clk_a),
+ CV75_GROUP(enet_2nd_ref_clk_b), CV75_GROUP(enet0_ptp_pps_o),
+ CV75_GROUP(rgmii0), CV75_GROUP(rmii0),
+ CV75_GROUP(i2c0_a), CV75_GROUP(i2c0_b),
+ CV75_GROUP(i2c1_a), CV75_GROUP(i2c1_b), CV75_GROUP(i2c2),
+ CV75_GROUP(i2c3_a), CV75_GROUP(i2c3_b), CV75_GROUP(i2c3_c),
+ CV75_GROUP(i2cs_a), CV75_GROUP(i2cs_b),
+ CV75_GROUP(i2cs_c), CV75_GROUP(i2cs_d),
+ CV75_GROUP(can0), CV75_GROUP(can1), CV75_GROUP(ir),
+ CV75_GROUP(wdt_a), CV75_GROUP(wdt_b), CV75_GROUP(wdt_c),
+ CV75_GROUP(wdt_d), CV75_GROUP(wdt_e), CV75_GROUP(wdt_f),
+ CV75_GROUP(i2s0), CV75_GROUP(i2s1),
+ CV75_GROUP(clk_au), CV75_GROUP(dmic0),
+ CV75_GROUP(pwm0), CV75_GROUP(pwm1),
+ CV75_GROUP(pwm2), CV75_GROUP(pwm3),
+ CV75_GROUP(pwm4_a), CV75_GROUP(pwm4_b),
+ CV75_GROUP(pwm5_a), CV75_GROUP(pwm5_b),
+ CV75_GROUP(pwm6_a), CV75_GROUP(pwm6_b),
+ CV75_GROUP(pwm7_a), CV75_GROUP(pwm7_b),
+ CV75_GROUP(pwm8_a), CV75_GROUP(pwm8_b),
+ CV75_GROUP(pwm9_a), CV75_GROUP(pwm9_b),
+ CV75_GROUP(pwm10_a), CV75_GROUP(pwm10_b),
+ CV75_GROUP(pwm11_a), CV75_GROUP(pwm11_b),
+ CV75_GROUP(spi0), CV75_GROUP(spi1), CV75_GROUP(spi2),
+ CV75_GROUP(spi3_a), CV75_GROUP(spi3_b), CV75_GROUP(spi3_c),
+ CV75_GROUP(spi_slave_a), CV75_GROUP(spi_slave_b),
+ CV75_GROUP(spi_slave_c), CV75_GROUP(spi_slave_d),
+ CV75_GROUP(spi_slave_e),
+ CV75_GROUP(vin_master_sync_a), CV75_GROUP(vin_master_sync_b),
+ CV75_GROUP(vin_master_sync_c), CV75_GROUP(vin_master_sync_d),
+ CV75_GROUP(vin_master_sync_e),
+ CV75_GROUP(vsync0), CV75_GROUP(vsync1),
+ CV75_GROUP(vsync2), CV75_GROUP(vsync3),
+ CV75_GROUP(hsync0), CV75_GROUP(hsync1),
+};
+
+static const char * const cv75_uart0_groups[] = {
+ "uart0",
+};
+
+static const char * const cv75_uart1_groups[] = {
+ "uart1",
+ "uart1_flow",
+};
+
+static const char * const cv75_uart2_groups[] = {
+ "uart2_a",
+ "uart2_b",
+ "uart2_c",
+ "uart2_flow_a",
+ "uart2_flow_b",
+};
+
+static const char * const cv75_uart3_groups[] = {
+ "uart3_a",
+ "uart3_b",
+ "uart3_flow_a",
+ "uart3_flow_b",
+};
+
+static const char * const cv75_uart4_groups[] = {
+ "uart4_a",
+ "uart4_b",
+ "uart4_flow_a",
+ "uart4_flow_b",
+};
+
+static const char * const cv75_snand_groups[] = {
+ "snand",
+};
+
+static const char * const cv75_spinor_groups[] = {
+ "spinor",
+};
+
+static const char * const cv75_sdmmc0_groups[] = {
+ "sdmmc0_cd",
+ "sdmmc0_wp",
+ "sdmmc0_reset",
+ "sdmmc0_hs_sel",
+ "sdmmc0_1bit",
+ "sdmmc0_4bit",
+};
+
+static const char * const cv75_sdmmc1_groups[] = {
+ "sdmmc1_cd",
+ "sdmmc1_wp",
+ "sdmmc1_reset",
+ "sdmmc1_hs_sel",
+ "sdmmc1_1bit",
+ "sdmmc1_4bit",
+};
+
+static const char * const cv75_enet0_groups[] = {
+ "enet_ext_osc_clk",
+ "enet_2nd_ref_clk_a",
+ "enet_2nd_ref_clk_b",
+ "enet0_ptp_pps_o",
+ "rgmii0",
+ "rmii0",
+};
+
+static const char * const cv75_i2c0_groups[] = {
+ "i2c0_a",
+ "i2c0_b",
+};
+
+static const char * const cv75_i2c1_groups[] = {
+ "i2c1_a",
+ "i2c1_b",
+};
+
+static const char * const cv75_i2c2_groups[] = {
+ "i2c2",
+};
+
+static const char * const cv75_i2c3_groups[] = {
+ "i2c3_a",
+ "i2c3_b",
+ "i2c3_c",
+};
+
+static const char * const cv75_i2cs_groups[] = {
+ "i2cs_a",
+ "i2cs_b",
+ "i2cs_c",
+ "i2cs_d",
+};
+
+static const char * const cv75_can0_groups[] = {
+ "can0",
+};
+
+static const char * const cv75_can1_groups[] = {
+ "can1",
+};
+
+static const char * const cv75_ir_groups[] = {
+ "ir",
+};
+
+static const char * const cv75_wdt_groups[] = {
+ "wdt_a",
+ "wdt_b",
+ "wdt_c",
+ "wdt_d",
+ "wdt_e",
+ "wdt_f",
+};
+
+static const char * const cv75_i2s0_groups[] = {
+ "i2s0",
+};
+
+static const char * const cv75_i2s1_groups[] = {
+ "i2s1",
+};
+
+static const char * const cv75_clk_au_groups[] = {
+ "clk_au",
+};
+
+static const char * const cv75_dmic0_groups[] = {
+ "dmic0",
+};
+
+static const char * const cv75_pwm0_groups[] = {
+ "pwm0",
+};
+
+static const char * const cv75_pwm1_groups[] = {
+ "pwm1",
+};
+
+static const char * const cv75_pwm2_groups[] = {
+ "pwm2",
+};
+
+static const char * const cv75_pwm3_groups[] = {
+ "pwm3",
+};
+
+static const char * const cv75_pwm4_groups[] = {
+ "pwm4_a",
+ "pwm4_b",
+};
+
+static const char * const cv75_pwm5_groups[] = {
+ "pwm5_a",
+ "pwm5_b",
+};
+
+static const char * const cv75_pwm6_groups[] = {
+ "pwm6_a",
+ "pwm6_b",
+};
+
+static const char * const cv75_pwm7_groups[] = {
+ "pwm7_a",
+ "pwm7_b",
+};
+
+static const char * const cv75_pwm8_groups[] = {
+ "pwm8_a",
+ "pwm8_b",
+};
+
+static const char * const cv75_pwm9_groups[] = {
+ "pwm9_a",
+ "pwm9_b",
+};
+
+static const char * const cv75_pwm10_groups[] = {
+ "pwm10_a",
+ "pwm10_b",
+};
+
+static const char * const cv75_pwm11_groups[] = {
+ "pwm11_a",
+ "pwm11_b",
+};
+
+static const char * const cv75_spi0_groups[] = {
+ "spi0",
+};
+
+static const char * const cv75_spi1_groups[] = {
+ "spi1",
+};
+
+static const char * const cv75_spi2_groups[] = {
+ "spi2",
+};
+
+static const char * const cv75_spi3_groups[] = {
+ "spi3_a",
+ "spi3_b",
+ "spi3_c",
+};
+
+static const char * const cv75_spi_slave_groups[] = {
+ "spi_slave_a",
+ "spi_slave_b",
+ "spi_slave_c",
+ "spi_slave_d",
+ "spi_slave_e",
+};
+
+static const char * const cv75_vin_master_sync_groups[] = {
+ "vin_master_sync_a",
+ "vin_master_sync_b",
+ "vin_master_sync_c",
+ "vin_master_sync_d",
+ "vin_master_sync_e",
+};
+
+static const char * const cv75_vsync0_groups[] = {
+ "vsync0",
+};
+
+static const char * const cv75_vsync1_groups[] = {
+ "vsync1",
+};
+
+static const char * const cv75_vsync2_groups[] = {
+ "vsync2",
+};
+
+static const char * const cv75_vsync3_groups[] = {
+ "vsync3",
+};
+
+static const char * const cv75_hsync0_groups[] = {
+ "hsync0",
+};
+
+static const char * const cv75_hsync1_groups[] = {
+ "hsync1",
+};
+
+static const struct ambpin_function cv75_pin_functions[] = {
+ CV75_FUNCTION(uart0),
+ CV75_FUNCTION(uart1),
+ CV75_FUNCTION(uart2),
+ CV75_FUNCTION(uart3),
+ CV75_FUNCTION(uart4),
+ CV75_FUNCTION(snand),
+ CV75_FUNCTION(spinor),
+ CV75_FUNCTION(sdmmc0),
+ CV75_FUNCTION(sdmmc1),
+ CV75_FUNCTION(enet0),
+ CV75_FUNCTION(i2c0),
+ CV75_FUNCTION(i2c1),
+ CV75_FUNCTION(i2c2),
+ CV75_FUNCTION(i2c3),
+ CV75_FUNCTION(i2cs),
+ CV75_FUNCTION(can0),
+ CV75_FUNCTION(can1),
+ CV75_FUNCTION(ir),
+ CV75_FUNCTION(wdt),
+ CV75_FUNCTION(i2s0),
+ CV75_FUNCTION(i2s1),
+ CV75_FUNCTION(clk_au),
+ CV75_FUNCTION(dmic0),
+ CV75_FUNCTION(pwm0),
+ CV75_FUNCTION(pwm1),
+ CV75_FUNCTION(pwm2),
+ CV75_FUNCTION(pwm3),
+ CV75_FUNCTION(pwm4),
+ CV75_FUNCTION(pwm5),
+ CV75_FUNCTION(pwm6),
+ CV75_FUNCTION(pwm7),
+ CV75_FUNCTION(pwm8),
+ CV75_FUNCTION(pwm9),
+ CV75_FUNCTION(pwm10),
+ CV75_FUNCTION(pwm11),
+ CV75_FUNCTION(spi0),
+ CV75_FUNCTION(spi1),
+ CV75_FUNCTION(spi2),
+ CV75_FUNCTION(spi3),
+ CV75_FUNCTION(spi_slave),
+ CV75_FUNCTION(vin_master_sync),
+ CV75_FUNCTION(vsync0),
+ CV75_FUNCTION(vsync1),
+ CV75_FUNCTION(vsync2),
+ CV75_FUNCTION(vsync3),
+ CV75_FUNCTION(hsync0),
+ CV75_FUNCTION(hsync1),
+};
+
+const struct amb_pinctrl_data ambarella_cv75_pinctrl_data = {
+ .ds0 = {
+ 0x314, 0x320, 0x32c,
+ },
+ .ds1 = {
+ 0x318, 0x324, 0x330,
+ },
+ .ds2 = {
+ 0x31c, 0x328, 0x334,
+ },
+ .pull_en = {
+ 0x60, 0x64, 0x68,
+ },
+ .pull_dir = {
+ 0x7c, 0x80, 0x84,
+ },
+ .have_ds2 = true,
+ .clk_au_dedicated_pin = 96,
+ .nr_banks = 3,
+ .npins = 97,
+ .groups = cv75_pin_groups,
+ .nr_groups = ARRAY_SIZE(cv75_pin_groups),
+ .functions = cv75_pin_functions,
+ .nr_functions = ARRAY_SIZE(cv75_pin_functions),
+};
diff --git a/drivers/pinctrl/pinctrl-ambarella.c b/drivers/pinctrl/pinctrl-ambarella.c
new file mode 100644
index 000000000000..f9146d365c3c
--- /dev/null
+++ b/drivers/pinctrl/pinctrl-ambarella.c
@@ -0,0 +1,863 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Pinctrl driver for Ambarella SoCs
+ *
+ * History:
+ * 2013/12/18 - [Cao Rongrong] created file
+ *
+ * Copyright (C) 2012-2026, Ambarella, Inc.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/bitmap.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/pm.h>
+#include <linux/slab.h>
+#include <linux/regmap.h>
+#include <linux/mfd/syscon.h>
+#include <linux/pinctrl/pinctrl.h>
+#include <linux/pinctrl/pinmux.h>
+#include <linux/pinctrl/pinconf.h>
+#include <linux/pinctrl/pinconf-generic.h>
+#include <linux/seq_file.h>
+
+#include "pinconf.h"
+#include "pinctrl-ambarella.h"
+
+/* ==========================================================================*/
+
+#define IOMUX_OFFSET(bank, n) (((bank) * 0xc) + ((n) * 4))
+#define IOMUX_CTRL_SET_OFFSET 0xf0
+
+/* ==========================================================================*/
+
+#define AMBA_MAX_PINS (AMBA_MAX_BANKS * 32)
+
+#define PINID_TO_BANK(p) ((p) >> 5)
+#define PINID_TO_OFFSET(p) ((p) & 0x1f)
+
+struct amb_pinctrl_pm_state {
+ u32 iomux[3];
+ u32 pull[2];
+ u32 ds[3];
+};
+
+struct ambpin_group {
+ const char *name;
+ const u32 *pinmux;
+ unsigned int *pins;
+ unsigned int num_pins;
+};
+
+struct amb_pinctrl_soc_data {
+ struct device *dev;
+ const struct amb_pinctrl_data *data;
+ void __iomem *iomux_base;
+ struct regmap *ds_regmap;
+ struct regmap *pull_regmap;
+ unsigned int npins;
+ unsigned long used[BITS_TO_LONGS(AMBA_MAX_PINS)];
+ raw_spinlock_t lock;
+
+ struct pinctrl_dev *pctl;
+
+ const struct ambpin_function *functions;
+ unsigned int nr_functions;
+ struct ambpin_group *groups;
+ unsigned int nr_groups;
+
+ struct amb_pinctrl_pm_state pm[AMBA_MAX_BANKS];
+};
+
+/* check if the selector is a valid pin group selector */
+static int amb_get_group_count(struct pinctrl_dev *pctldev)
+{
+ struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+
+ return soc->nr_groups;
+}
+
+/* return the name of the group selected by the group selector */
+static const char *amb_get_group_name(struct pinctrl_dev *pctldev,
+ unsigned int selector)
+{
+ struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+
+ return soc->groups[selector].name;
+}
+
+/* return the pin numbers associated with the specified group */
+static int amb_get_group_pins(struct pinctrl_dev *pctldev,
+ unsigned int selector, const unsigned int **pins,
+ unsigned int *num_pins)
+{
+ struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+
+ *pins = soc->groups[selector].pins;
+ *num_pins = soc->groups[selector].num_pins;
+
+ return 0;
+}
+
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+static void amb_pin_dbg_show(struct pinctrl_dev *pctldev,
+ struct seq_file *s, unsigned int pin)
+{
+ seq_printf(s, " %s", pinctrl_dev_get_devname(pctldev));
+}
+#endif
+
+/* list of pinctrl callbacks for the pinctrl core */
+static const struct pinctrl_ops amb_pctrl_ops = {
+ .get_groups_count = amb_get_group_count,
+ .get_group_name = amb_get_group_name,
+ .get_group_pins = amb_get_group_pins,
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+ .pin_dbg_show = amb_pin_dbg_show,
+#endif
+ .dt_node_to_map = pinconf_generic_dt_node_to_map_all,
+ .dt_free_map = pinconf_generic_dt_free_map,
+};
+
+/* check if the selector is a valid pin function selector */
+static int amb_pinmux_request(struct pinctrl_dev *pctldev, unsigned int pin)
+{
+ struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+
+ if (test_and_set_bit(pin, soc->used))
+ return -EBUSY;
+
+ return 0;
+}
+
+/* check if the selector is a valid pin function selector */
+static int amb_pinmux_free(struct pinctrl_dev *pctldev, unsigned int pin)
+{
+ struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+
+ clear_bit(pin, soc->used);
+
+ return 0;
+}
+
+/* check if the selector is a valid pin function selector */
+static int amb_pinmux_get_fcount(struct pinctrl_dev *pctldev)
+{
+ struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+
+ return soc->nr_functions;
+}
+
+/* return the name of the pin function specified */
+static const char *amb_pinmux_get_fname(struct pinctrl_dev *pctldev,
+ unsigned int selector)
+{
+ struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+
+ return soc->functions[selector].name;
+}
+
+/* return the groups associated for the specified function selector */
+static int amb_pinmux_get_groups(struct pinctrl_dev *pctldev,
+ unsigned int selector,
+ const char * const **groups,
+ unsigned int * const num_groups)
+{
+ struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+
+ *groups = soc->functions[selector].groups;
+ *num_groups = soc->functions[selector].num_groups;
+
+ return 0;
+}
+
+static bool amb_iomux_accessible(const struct amb_pinctrl_soc_data *soc)
+{
+ return soc->data->hsm_domain_id == 0;
+}
+
+static void amb_iomux_commit(struct amb_pinctrl_soc_data *soc)
+{
+ if (!amb_iomux_accessible(soc))
+ return;
+
+ writel_relaxed(0x1, soc->iomux_base + IOMUX_CTRL_SET_OFFSET);
+ writel_relaxed(0x0, soc->iomux_base + IOMUX_CTRL_SET_OFFSET);
+}
+
+static void amb_iomux_save_bank(struct amb_pinctrl_soc_data *soc, u32 bank)
+{
+ if (!amb_iomux_accessible(soc) || bank >= AMBA_MAX_BANKS)
+ return;
+
+ soc->pm[bank].iomux[0] =
+ readl_relaxed(soc->iomux_base + IOMUX_OFFSET(bank, 0));
+ soc->pm[bank].iomux[1] =
+ readl_relaxed(soc->iomux_base + IOMUX_OFFSET(bank, 1));
+ soc->pm[bank].iomux[2] =
+ readl_relaxed(soc->iomux_base + IOMUX_OFFSET(bank, 2));
+}
+
+static void amb_iomux_restore_bank(struct amb_pinctrl_soc_data *soc, u32 bank)
+{
+ if (!amb_iomux_accessible(soc) || bank >= AMBA_MAX_BANKS)
+ return;
+
+ writel_relaxed(soc->pm[bank].iomux[0],
+ soc->iomux_base + IOMUX_OFFSET(bank, 0));
+ writel_relaxed(soc->pm[bank].iomux[1],
+ soc->iomux_base + IOMUX_OFFSET(bank, 1));
+ writel_relaxed(soc->pm[bank].iomux[2],
+ soc->iomux_base + IOMUX_OFFSET(bank, 2));
+}
+
+static void amb_pinmux_set_altfunc(struct amb_pinctrl_soc_data *soc,
+ u32 bank, u32 offset, u32 altfunc)
+{
+ u32 i, data;
+
+ if (!amb_iomux_accessible(soc))
+ return;
+
+ for (i = 0; i < 3; i++) {
+ data = readl_relaxed(soc->iomux_base + IOMUX_OFFSET(bank, i));
+ data &= ~BIT(offset);
+ data |= ((altfunc >> i) & 1U) << offset;
+ writel_relaxed(data, soc->iomux_base + IOMUX_OFFSET(bank, i));
+ }
+}
+
+/* enable a specified pinmux by writing to registers */
+static int amb_pinmux_set_mux(struct pinctrl_dev *pctldev,
+ unsigned int selector, unsigned int group)
+{
+ struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+ const struct ambpin_group *grp;
+ u32 i, pin, alt, bank, offset;
+ unsigned long flags;
+
+ grp = &soc->groups[group];
+
+ raw_spin_lock_irqsave(&soc->lock, flags);
+ for (i = 0; i < grp->num_pins; i++) {
+ pin = AMBA_PINMUX_TO_PIN(grp->pinmux[i]);
+ alt = AMBA_PINMUX_TO_ALT(grp->pinmux[i]);
+ bank = PINID_TO_BANK(pin);
+ offset = PINID_TO_OFFSET(pin);
+ amb_pinmux_set_altfunc(soc, bank, offset, alt);
+ }
+ /* One commit after the whole group is programmed. */
+ amb_iomux_commit(soc);
+ raw_spin_unlock_irqrestore(&soc->lock, flags);
+
+ return 0;
+}
+
+static int amb_pinmux_gpio_request_enable(struct pinctrl_dev *pctldev,
+ struct pinctrl_gpio_range *range,
+ unsigned int pin)
+{
+ struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+ u32 bank, offset;
+ unsigned long flags;
+
+ if (!range || !range->gc) {
+ dev_err(soc->dev, "invalid range: %p\n", range);
+ return -EINVAL;
+ }
+
+ if (test_and_set_bit(pin, soc->used))
+ return -EBUSY;
+
+ bank = PINID_TO_BANK(pin);
+ offset = PINID_TO_OFFSET(pin);
+
+ raw_spin_lock_irqsave(&soc->lock, flags);
+ amb_pinmux_set_altfunc(soc, bank, offset, 0);
+ amb_iomux_commit(soc);
+ raw_spin_unlock_irqrestore(&soc->lock, flags);
+
+ return 0;
+}
+
+static void amb_pinmux_gpio_disable_free(struct pinctrl_dev *pctldev,
+ struct pinctrl_gpio_range *range,
+ unsigned int pin)
+{
+ struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+
+ dev_dbg(soc->dev, "disable pin %u as GPIO\n", pin);
+ /* Set the pin to some default state, GPIO is usually default */
+
+ clear_bit(pin, soc->used);
+}
+
+/* list of pinmux callbacks for the pinmux vertical in pinctrl core */
+static const struct pinmux_ops amb_pinmux_ops = {
+ .request = amb_pinmux_request,
+ .free = amb_pinmux_free,
+ .get_functions_count = amb_pinmux_get_fcount,
+ .get_function_name = amb_pinmux_get_fname,
+ .get_function_groups = amb_pinmux_get_groups,
+ .set_mux = amb_pinmux_set_mux,
+ .gpio_request_enable = amb_pinmux_gpio_request_enable,
+ .gpio_disable_free = amb_pinmux_gpio_disable_free,
+};
+
+static int amb_drive_strength_to_reg(struct amb_pinctrl_soc_data *soc,
+ u32 strength)
+{
+ if (soc->data->have_ds2) {
+ switch (strength) {
+ case 3:
+ return 0;
+ case 4:
+ case 5:
+ return 1;
+ case 6:
+ return 2;
+ case 7:
+ case 8:
+ return 3;
+ case 9:
+ return 4;
+ case 12:
+ return 5;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ switch (strength) {
+ case 2:
+ return 0;
+ case 4:
+ return 1;
+ case 8:
+ return 2;
+ case 12:
+ return 3;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int amb_reg_to_drive_strength(struct amb_pinctrl_soc_data *soc, u32 ds)
+{
+ static const int ds2_ma[] = { 3, 4, 6, 8, 9, 12 };
+ static const int ds_ma[] = { 2, 4, 8, 12 };
+
+ if (soc->data->have_ds2) {
+ if (ds >= ARRAY_SIZE(ds2_ma))
+ return -EINVAL;
+
+ return ds2_ma[ds];
+ }
+
+ if (ds >= ARRAY_SIZE(ds_ma))
+ return -EINVAL;
+
+ return ds_ma[ds];
+}
+
+/* set the pin config settings for a specified pin */
+static int amb_pinconf_set(struct pinctrl_dev *pctldev, unsigned int pin,
+ unsigned long *configs, unsigned int num_configs)
+{
+ struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+ u32 i, bank, offset;
+ unsigned long config;
+ enum pin_config_param param;
+ u32 arg;
+ int ds;
+
+ bank = PINID_TO_BANK(pin);
+ if (bank >= soc->data->nr_banks)
+ return -EINVAL;
+
+ offset = PINID_TO_OFFSET(pin);
+
+ for (i = 0; i < num_configs; i++) {
+ config = configs[i];
+ param = pinconf_to_config_param(config);
+ arg = pinconf_to_config_argument(config);
+
+ switch (param) {
+ case PIN_CONFIG_BIAS_DISABLE:
+ regmap_update_bits(soc->pull_regmap,
+ soc->data->pull_en[bank], BIT(offset), 0);
+ break;
+ case PIN_CONFIG_BIAS_PULL_DOWN:
+ case PIN_CONFIG_BIAS_PULL_UP:
+ regmap_update_bits(soc->pull_regmap, soc->data->pull_dir[bank],
+ BIT(offset),
+ (param == PIN_CONFIG_BIAS_PULL_UP) ?
+ BIT(offset) : 0);
+ regmap_update_bits(soc->pull_regmap, soc->data->pull_en[bank],
+ BIT(offset), BIT(offset));
+ break;
+ case PIN_CONFIG_DRIVE_STRENGTH:
+ ds = amb_drive_strength_to_reg(soc, arg);
+ if (ds < 0)
+ return ds;
+ if (soc->data->have_ds2) {
+ regmap_update_bits(soc->ds_regmap,
+ soc->data->ds0[bank], BIT(offset),
+ (ds & BIT(0)) ? BIT(offset) : 0);
+ regmap_update_bits(soc->ds_regmap,
+ soc->data->ds1[bank], BIT(offset),
+ (ds & BIT(1)) ? BIT(offset) : 0);
+ regmap_update_bits(soc->ds_regmap,
+ soc->data->ds2[bank], BIT(offset),
+ (ds & BIT(2)) ? BIT(offset) : 0);
+ } else {
+ regmap_update_bits(soc->ds_regmap,
+ soc->data->ds0[bank], BIT(offset),
+ (ds & BIT(1)) ? BIT(offset) : 0);
+ regmap_update_bits(soc->ds_regmap,
+ soc->data->ds1[bank], BIT(offset),
+ (ds & BIT(0)) ? BIT(offset) : 0);
+ }
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+ }
+
+ return 0;
+}
+
+static int amb_pinconf_group_set(struct pinctrl_dev *pctldev,
+ unsigned int selector,
+ unsigned long *configs,
+ unsigned int num_configs)
+{
+ struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+ const struct ambpin_group *grp = &soc->groups[selector];
+ int ret;
+ u32 i;
+
+ for (i = 0; i < grp->num_pins; i++) {
+ ret = amb_pinconf_set(pctldev, grp->pins[i], configs,
+ num_configs);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+/* get the pin config settings for a specified pin */
+static int amb_pinconf_get(struct pinctrl_dev *pctldev,
+ unsigned int pin, unsigned long *config)
+{
+ struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+ enum pin_config_param param = pinconf_to_config_param(*config);
+ u32 bank, offset, pull_en, pull_dir, ds0, ds1, ds2, ds;
+ int ret, strength;
+
+ bank = PINID_TO_BANK(pin);
+ if (bank >= soc->data->nr_banks)
+ return -EINVAL;
+
+ offset = PINID_TO_OFFSET(pin);
+
+ switch (param) {
+ case PIN_CONFIG_BIAS_DISABLE:
+ case PIN_CONFIG_BIAS_PULL_DOWN:
+ case PIN_CONFIG_BIAS_PULL_UP:
+ ret = regmap_read(soc->pull_regmap, soc->data->pull_en[bank],
+ &pull_en);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(soc->pull_regmap, soc->data->pull_dir[bank],
+ &pull_dir);
+ if (ret)
+ return ret;
+
+ pull_en = (pull_en >> offset) & 1;
+ pull_dir = (pull_dir >> offset) & 1;
+
+ if (param == PIN_CONFIG_BIAS_DISABLE) {
+ if (pull_en)
+ return -EINVAL;
+ *config = pinconf_to_config_packed(param, 0);
+ return 0;
+ }
+
+ if (!pull_en)
+ return -EINVAL;
+ if (param == PIN_CONFIG_BIAS_PULL_UP && !pull_dir)
+ return -EINVAL;
+ if (param == PIN_CONFIG_BIAS_PULL_DOWN && pull_dir)
+ return -EINVAL;
+
+ *config = pinconf_to_config_packed(param, 1);
+ return 0;
+
+ case PIN_CONFIG_DRIVE_STRENGTH:
+ ret = regmap_read(soc->ds_regmap, soc->data->ds0[bank], &ds0);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(soc->ds_regmap, soc->data->ds1[bank], &ds1);
+ if (ret)
+ return ret;
+
+ ds0 = (ds0 >> offset) & 1;
+ ds1 = (ds1 >> offset) & 1;
+ if (soc->data->have_ds2) {
+ ret = regmap_read(soc->ds_regmap, soc->data->ds2[bank], &ds2);
+ if (ret)
+ return ret;
+
+ ds2 = (ds2 >> offset) & 1;
+ ds = (ds2 << 2) | (ds1 << 1) | ds0;
+ } else {
+ ds = (ds0 << 1) | ds1;
+ }
+
+ strength = amb_reg_to_drive_strength(soc, ds);
+ if (strength < 0)
+ return strength;
+
+ *config = pinconf_to_config_packed(param, strength);
+ return 0;
+
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+static void amb_pinconf_dbg_show(struct pinctrl_dev *pctldev,
+ struct seq_file *s, unsigned int pin)
+{
+ struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+ u32 pull_en, pull_dir, ds0, ds1, ds2 = 0, ds;
+ u32 bank, offset;
+ int strength, ret;
+
+ bank = PINID_TO_BANK(pin);
+ if (bank >= soc->data->nr_banks) {
+ seq_puts(s, " (no pinconf)");
+ return;
+ }
+
+ offset = PINID_TO_OFFSET(pin);
+
+ ret = regmap_read(soc->pull_regmap, soc->data->pull_en[bank],
+ &pull_en);
+ if (ret) {
+ seq_puts(s, " pull: <read error>");
+ return;
+ }
+ ret = regmap_read(soc->pull_regmap, soc->data->pull_dir[bank],
+ &pull_dir);
+ if (ret) {
+ seq_puts(s, " pull: <read error>");
+ return;
+ }
+ pull_en = (pull_en >> offset) & 1;
+ pull_dir = (pull_dir >> offset) & 1;
+ seq_printf(s, " pull: %s,",
+ pull_en ? (pull_dir ? "up" : "down") : "disable");
+
+ ret = regmap_read(soc->ds_regmap, soc->data->ds0[bank], &ds0);
+ if (ret) {
+ seq_puts(s, " drive-strength: <read error>");
+ return;
+ }
+ ret = regmap_read(soc->ds_regmap, soc->data->ds1[bank], &ds1);
+ if (ret) {
+ seq_puts(s, " drive-strength: <read error>");
+ return;
+ }
+ ds0 = (ds0 >> offset) & 1;
+ ds1 = (ds1 >> offset) & 1;
+ if (soc->data->have_ds2) {
+ ret = regmap_read(soc->ds_regmap, soc->data->ds2[bank], &ds2);
+ if (ret) {
+ seq_puts(s, " drive-strength: <read error>");
+ return;
+ }
+ ds2 = (ds2 >> offset) & 1;
+ ds = (ds2 << 2) | (ds1 << 1) | ds0;
+ } else {
+ ds = (ds0 << 1) | ds1;
+ }
+
+ strength = amb_reg_to_drive_strength(soc, ds);
+ if (strength < 0)
+ seq_puts(s, " drive-strength: invalid");
+ else
+ seq_printf(s, " drive-strength: %dmA", strength);
+}
+#endif
+
+/* list of pinconfig callbacks for pinconfig vertical in the pinctrl code */
+static const struct pinconf_ops amb_pinconf_ops = {
+ .is_generic = true,
+ .pin_config_get = amb_pinconf_get,
+ .pin_config_set = amb_pinconf_set,
+ .pin_config_group_set = amb_pinconf_group_set,
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+ .pin_config_dbg_show = amb_pinconf_dbg_show,
+#endif
+};
+
+/* register the pinctrl interface with the pinctrl subsystem */
+static int amb_pinctrl_register(struct amb_pinctrl_soc_data *soc)
+{
+ struct pinctrl_pin_desc *pindesc;
+ struct pinctrl_desc *amb_pinctrl_desc;
+ unsigned int pin;
+
+ /* dynamically populate the pin number and pin name for pindesc */
+ pindesc = devm_kcalloc(soc->dev, soc->npins, sizeof(*pindesc),
+ GFP_KERNEL);
+ if (!pindesc)
+ return -ENOMEM;
+
+ for (pin = 0; pin < soc->npins; pin++) {
+ pindesc[pin].number = pin;
+ pindesc[pin].name = devm_kasprintf(soc->dev, GFP_KERNEL,
+ "io%u", pin);
+ if (!pindesc[pin].name)
+ return -ENOMEM;
+ }
+
+ amb_pinctrl_desc = devm_kzalloc(soc->dev, sizeof(*amb_pinctrl_desc), GFP_KERNEL);
+ if (!amb_pinctrl_desc)
+ return -ENOMEM;
+
+ amb_pinctrl_desc->name = dev_name(soc->dev);
+ amb_pinctrl_desc->pins = pindesc;
+ amb_pinctrl_desc->npins = soc->npins;
+ amb_pinctrl_desc->pctlops = &amb_pctrl_ops;
+ amb_pinctrl_desc->pmxops = &amb_pinmux_ops;
+ amb_pinctrl_desc->confops = &amb_pinconf_ops;
+ amb_pinctrl_desc->owner = THIS_MODULE;
+
+ soc->pctl = devm_pinctrl_register(soc->dev, amb_pinctrl_desc, soc);
+ if (IS_ERR(soc->pctl)) {
+ dev_err(soc->dev, "could not register pinctrl driver\n");
+ return PTR_ERR(soc->pctl);
+ }
+
+ return 0;
+}
+
+static int amb_pinctrl_probe(struct platform_device *pdev)
+{
+ struct amb_pinctrl_soc_data *soc;
+ struct device_node *np;
+ unsigned int *group_pins;
+ unsigned int group, group_pin;
+ size_t nr_group_pins = 0;
+ int rval;
+
+ soc = devm_kzalloc(&pdev->dev, sizeof(*soc), GFP_KERNEL);
+ if (!soc)
+ return -ENOMEM;
+
+ soc->dev = &pdev->dev;
+ soc->data = of_device_get_match_data(&pdev->dev);
+ if (!soc->data)
+ return dev_err_probe(&pdev->dev, -EINVAL, "missing soc data");
+ if (!soc->data->nr_banks ||
+ soc->data->nr_banks > AMBA_MAX_BANKS ||
+ !soc->data->npins ||
+ soc->data->npins > AMBA_MAX_PINS ||
+ soc->data->nr_banks * 32 > soc->data->npins ||
+ soc->data->clk_au_dedicated_pin >= soc->data->npins ||
+ PINID_TO_BANK(soc->data->clk_au_dedicated_pin) >= AMBA_MAX_BANKS)
+ return dev_err_probe(&pdev->dev, -EINVAL,
+ "invalid bank or pin count\n");
+
+ soc->npins = soc->data->npins;
+
+ np = pdev->dev.of_node;
+ soc->iomux_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(soc->iomux_base))
+ return dev_err_probe(&pdev->dev, PTR_ERR(soc->iomux_base),
+ "couldn't get iomux reg");
+
+ soc->ds_regmap = syscon_regmap_lookup_by_phandle(np, "ambarella,drive-strength-syscon");
+ if (IS_ERR(soc->ds_regmap))
+ return dev_err_probe(&pdev->dev, PTR_ERR(soc->ds_regmap),
+ "couldn't get drive-strength regmap");
+
+ soc->pull_regmap = syscon_regmap_lookup_by_phandle(np, "ambarella,pull-syscon");
+ if (IS_ERR(soc->pull_regmap))
+ return dev_err_probe(&pdev->dev, PTR_ERR(soc->pull_regmap),
+ "couldn't get pull regmap");
+
+ soc->nr_groups = soc->data->nr_groups;
+ soc->functions = soc->data->functions;
+ soc->nr_functions = soc->data->nr_functions;
+ if (!soc->data->groups || !soc->nr_groups ||
+ !soc->functions || !soc->nr_functions)
+ return dev_err_probe(&pdev->dev, -EINVAL,
+ "missing pin groups or functions\n");
+
+ soc->groups = devm_kcalloc(&pdev->dev, soc->nr_groups,
+ sizeof(*soc->groups), GFP_KERNEL);
+ if (!soc->groups)
+ return -ENOMEM;
+
+ for (group = 0; group < soc->nr_groups; group++) {
+ const struct ambpin_group_desc *desc =
+ &soc->data->groups[group];
+
+ if (!desc->name || !desc->pinmux || !desc->num_pins)
+ return dev_err_probe(&pdev->dev, -EINVAL,
+ "invalid pin group %u\n", group);
+
+ nr_group_pins += desc->num_pins;
+ }
+
+ group_pins = devm_kcalloc(&pdev->dev, nr_group_pins,
+ sizeof(*group_pins), GFP_KERNEL);
+ if (!group_pins)
+ return -ENOMEM;
+
+ for (group = 0; group < soc->nr_groups; group++) {
+ const struct ambpin_group_desc *desc =
+ &soc->data->groups[group];
+ struct ambpin_group *grp = &soc->groups[group];
+
+ grp->name = desc->name;
+ grp->pinmux = desc->pinmux;
+ grp->pins = group_pins;
+ grp->num_pins = desc->num_pins;
+ for (group_pin = 0;
+ group_pin < grp->num_pins;
+ group_pin++) {
+ u32 pinmux = grp->pinmux[group_pin];
+ unsigned int pin = AMBA_PINMUX_TO_PIN(pinmux);
+ unsigned int alt = AMBA_PINMUX_TO_ALT(pinmux);
+
+ if (pin >= soc->npins || alt > 7)
+ return dev_err_probe(&pdev->dev, -EINVAL,
+ "group %s has invalid pinmux %#x\n",
+ grp->name, pinmux);
+
+ grp->pins[group_pin] = pin;
+ }
+ group_pins += grp->num_pins;
+ }
+
+ raw_spin_lock_init(&soc->lock);
+
+ /* Mark all pins unavailable, then clear pins that exist. */
+ bitmap_fill(soc->used, AMBA_MAX_PINS);
+ bitmap_clear(soc->used, 0, soc->data->nr_banks * 32);
+ clear_bit(soc->data->clk_au_dedicated_pin, soc->used);
+
+ rval = amb_pinctrl_register(soc);
+ if (rval)
+ return dev_err_probe(&pdev->dev, rval, "pinctrl register failed!");
+
+ platform_set_drvdata(pdev, soc);
+ dev_info(&pdev->dev, "Ambarella pinctrl driver registered");
+
+ return 0;
+}
+
+static int amb_pinctrl_suspend(struct device *dev)
+{
+ struct amb_pinctrl_soc_data *soc = dev_get_drvdata(dev);
+ u32 bank, dedicated = soc->data->clk_au_dedicated_pin;
+
+ for (bank = 0; bank < soc->data->nr_banks; bank++) {
+ regmap_read(soc->pull_regmap, soc->data->pull_en[bank],
+ &soc->pm[bank].pull[0]);
+ regmap_read(soc->pull_regmap, soc->data->pull_dir[bank],
+ &soc->pm[bank].pull[1]);
+
+ regmap_read(soc->ds_regmap, soc->data->ds0[bank],
+ &soc->pm[bank].ds[0]);
+ regmap_read(soc->ds_regmap, soc->data->ds1[bank],
+ &soc->pm[bank].ds[1]);
+ if (soc->data->have_ds2)
+ regmap_read(soc->ds_regmap, soc->data->ds2[bank],
+ &soc->pm[bank].ds[2]);
+
+ amb_iomux_save_bank(soc, bank);
+ }
+
+ if (dedicated >= soc->data->nr_banks * 32)
+ amb_iomux_save_bank(soc, PINID_TO_BANK(dedicated));
+
+ return 0;
+}
+
+static int amb_pinctrl_resume(struct device *dev)
+{
+ struct amb_pinctrl_soc_data *soc = dev_get_drvdata(dev);
+ u32 bank, dedicated = soc->data->clk_au_dedicated_pin;
+
+ for (bank = 0; bank < soc->data->nr_banks; bank++)
+ amb_iomux_restore_bank(soc, bank);
+
+ if (dedicated >= soc->data->nr_banks * 32)
+ amb_iomux_restore_bank(soc, PINID_TO_BANK(dedicated));
+
+ /* Ensure restored IOMUX writes land before the commit strobe. */
+ wmb();
+ amb_iomux_commit(soc);
+
+ for (bank = 0; bank < soc->data->nr_banks; bank++) {
+ regmap_write(soc->ds_regmap, soc->data->ds0[bank],
+ soc->pm[bank].ds[0]);
+ regmap_write(soc->ds_regmap, soc->data->ds1[bank],
+ soc->pm[bank].ds[1]);
+ if (soc->data->have_ds2)
+ regmap_write(soc->ds_regmap, soc->data->ds2[bank],
+ soc->pm[bank].ds[2]);
+
+ regmap_write(soc->pull_regmap, soc->data->pull_dir[bank],
+ soc->pm[bank].pull[1]);
+ regmap_write(soc->pull_regmap, soc->data->pull_en[bank],
+ soc->pm[bank].pull[0]);
+ }
+
+ return 0;
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(amb_pinctrl_pm_ops,
+ amb_pinctrl_suspend,
+ amb_pinctrl_resume);
+
+static const struct of_device_id amb_pinctrl_dt_match[] = {
+ {
+ .compatible = "ambarella,cv75-pinctrl",
+ .data = &ambarella_cv75_pinctrl_data,
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, amb_pinctrl_dt_match);
+
+static struct platform_driver amb_pinctrl_driver = {
+ .probe = amb_pinctrl_probe,
+ .driver = {
+ .name = "ambarella-pinctrl",
+ .of_match_table = of_match_ptr(amb_pinctrl_dt_match),
+ .pm = pm_sleep_ptr(&amb_pinctrl_pm_ops),
+ },
+};
+
+static int __init amb_pinctrl_drv_register(void)
+{
+ return platform_driver_register(&amb_pinctrl_driver);
+}
+arch_initcall(amb_pinctrl_drv_register);
+
+MODULE_AUTHOR("Cao Rongrong <rrcao@ambarella.com>");
+MODULE_DESCRIPTION("Ambarella SoC pinctrl driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/pinctrl/pinctrl-ambarella.h b/drivers/pinctrl/pinctrl-ambarella.h
new file mode 100644
index 000000000000..094639627131
--- /dev/null
+++ b/drivers/pinctrl/pinctrl-ambarella.h
@@ -0,0 +1,50 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Ambarella pinctrl common data definitions
+ *
+ * Copyright (C) 2012-2026, Ambarella, Inc.
+ */
+
+#ifndef _PINCTRL_AMBARELLA_H
+#define _PINCTRL_AMBARELLA_H
+
+#include <linux/types.h>
+
+#define AMBA_MAX_BANKS 8
+
+#define AMBA_PINMUX(pin, alt) (((alt) << 12) | (pin))
+#define AMBA_PINMUX_TO_PIN(mux) ((mux) & 0xfff)
+#define AMBA_PINMUX_TO_ALT(mux) (((mux) >> 12) & 0xf)
+
+struct ambpin_group_desc {
+ const char *name;
+ const u32 *pinmux;
+ unsigned int num_pins;
+};
+
+struct ambpin_function {
+ const char *name;
+ const char * const *groups;
+ unsigned int num_groups;
+};
+
+struct amb_pinctrl_data {
+ unsigned int ds0[AMBA_MAX_BANKS];
+ unsigned int ds1[AMBA_MAX_BANKS];
+ unsigned int ds2[AMBA_MAX_BANKS];
+ unsigned int pull_en[AMBA_MAX_BANKS];
+ unsigned int pull_dir[AMBA_MAX_BANKS];
+ bool have_ds2;
+ u32 hsm_domain_id;
+ u32 clk_au_dedicated_pin;
+ unsigned int nr_banks;
+ unsigned int npins;
+ const struct ambpin_group_desc *groups;
+ unsigned int nr_groups;
+ const struct ambpin_function *functions;
+ unsigned int nr_functions;
+};
+
+extern const struct amb_pinctrl_data ambarella_cv75_pinctrl_data;
+
+#endif /* _PINCTRL_AMBARELLA_H */
--
2.34.1
^ permalink raw reply related [flat|nested] 17+ messages in thread* [PATCH v5 10/12] serial: 8250: add Ambarella UART driver
2026-08-18 10:31 [PATCH v5 00/12] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
` (8 preceding siblings ...)
2026-08-18 10:31 ` [PATCH v5 09/12] pinctrl: ambarella: add CV75 pin controller Long Zhao via B4 Relay
@ 2026-08-18 10:31 ` Long Zhao via B4 Relay
2026-08-18 14:17 ` Andy Shevchenko
2026-08-18 10:31 ` [PATCH v5 11/12] arm64: ambarella: add ARCH_AMBARELLA and CV75 EVK DT Long Zhao via B4 Relay
2026-08-18 10:31 ` [PATCH v5 12/12] MAINTAINERS: add ARM/AMBARELLA SoC support Long Zhao via B4 Relay
11 siblings, 1 reply; 17+ messages in thread
From: Long Zhao via B4 Relay @ 2026-08-18 10:31 UTC (permalink / raw)
To: Arnd Bergmann, Krzysztof Kozlowski, Alexandre Belloni, soc,
linux-arm-kernel
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
Stephen Boyd, Linus Walleij, Bartosz Golaszewski,
Greg Kroah-Hartman, Jiri Slaby, Andy Shevchenko, Catalin Marinas,
Will Deacon, devicetree, linux-clk, linux-gpio, linux-serial,
linux-kernel, Brian Masney, Lee Jones, mfd, Long Zhao, Long Zhao
From: Long Zhao <longzhao@ambarella.com>
Add an 8250 platform driver for Ambarella CV75 instead of claiming
snps,dw-apb-uart compatibility.
Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
drivers/tty/serial/8250/8250_ambarella.c | 312 +++++++++++++++++++++++++++++++
drivers/tty/serial/8250/Kconfig | 9 +
drivers/tty/serial/8250/Makefile | 1 +
3 files changed, 322 insertions(+)
diff --git a/drivers/tty/serial/8250/8250_ambarella.c b/drivers/tty/serial/8250/8250_ambarella.c
new file mode 100644
index 000000000000..951355e19fa2
--- /dev/null
+++ b/drivers/tty/serial/8250/8250_ambarella.c
@@ -0,0 +1,312 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Serial port driver for Ambarella UART
+ *
+ * Copyright (C) 2026 Ambarella, Inc.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/serial_8250.h>
+#include <linux/serial_reg.h>
+
+#include "8250.h"
+
+#define AMBARELLA_UART_IER_ETOI BIT(5)
+#define AMBARELLA_UART_USR 0x1f
+#define AMBARELLA_UART_USR_BUSY BIT(0)
+#define AMBARELLA_UART_IIR_IID GENMASK(3, 0)
+#define AMBARELLA_UART_IIR_STATUS GENMASK(5, 0)
+
+struct ambarella_uart_soc_data {
+ unsigned int fifo_size;
+};
+
+struct ambarella_uart {
+ int line;
+ struct clk *clk;
+ const struct ambarella_uart_soc_data *soc_data;
+ bool in_idle;
+};
+
+static void ambarella_idle_exit(struct uart_port *p)
+{
+ struct ambarella_uart *data = p->private_data;
+ struct uart_8250_port *up = up_to_u8250p(p);
+
+ if (up->capabilities & UART_CAP_FIFO)
+ writel(up->fcr, p->membase + (UART_FCR << p->regshift));
+ writel(up->mcr, p->membase + (UART_MCR << p->regshift));
+ writel(up->ier, p->membase + (UART_IER << p->regshift));
+
+ data->in_idle = false;
+}
+
+static int ambarella_idle_enter(struct uart_port *p)
+{
+ struct ambarella_uart *data = p->private_data;
+ struct uart_8250_port *up = up_to_u8250p(p);
+ unsigned int status;
+ int retries = 4;
+
+ lockdep_assert_held_once(&p->lock);
+
+ data->in_idle = true;
+ writel(0, p->membase + (UART_IER << p->regshift));
+
+ serial8250_fifo_wait_for_lsr_thre(up, p->fifosize);
+ ndelay(p->frame_time);
+ writel(up->mcr | UART_MCR_LOOP,
+ p->membase + (UART_MCR << p->regshift));
+
+ do {
+ serial8250_clear_fifos(up);
+ if (!(readl(p->membase +
+ (AMBARELLA_UART_USR << p->regshift)) &
+ AMBARELLA_UART_USR_BUSY))
+ break;
+ ndelay(p->frame_time);
+ } while (--retries);
+
+ status = serial_lsr_in(up);
+ if (status & UART_LSR_DR) {
+ readl(p->membase + (UART_RX << p->regshift));
+ up->lsr_saved_flags = 0;
+ }
+
+ if (readl(p->membase + (AMBARELLA_UART_USR << p->regshift)) &
+ AMBARELLA_UART_USR_BUSY) {
+ ambarella_idle_exit(p);
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
+static void ambarella_serial_out(struct uart_port *p, unsigned int offset,
+ u32 value)
+{
+ struct ambarella_uart *data = p->private_data;
+ u32 lcr;
+
+ if (offset != UART_LCR || !data || data->in_idle) {
+ writel(value, p->membase + (offset << p->regshift));
+ return;
+ }
+
+ lcr = readl(p->membase + (UART_LCR << p->regshift));
+ if (lcr == value)
+ return;
+
+ writel(value, p->membase + (UART_LCR << p->regshift));
+ lcr = readl(p->membase + (UART_LCR << p->regshift));
+ if ((lcr & ~UART_LCR_SPAR) == (value & ~UART_LCR_SPAR))
+ return;
+
+ if (ambarella_idle_enter(p))
+ return;
+
+ writel(value, p->membase + (UART_LCR << p->regshift));
+ ambarella_idle_exit(p);
+}
+
+static u32 ambarella_serial_in(struct uart_port *p, unsigned int offset)
+{
+ return readl(p->membase + (offset << p->regshift));
+}
+
+static int ambarella_handle_irq(struct uart_port *p)
+{
+ struct uart_8250_port *up = up_to_u8250p(p);
+ unsigned int iir = serial_port_in(p, UART_IIR);
+ bool rx_timeout = (iir & AMBARELLA_UART_IIR_STATUS) ==
+ UART_IIR_RX_TIMEOUT;
+ unsigned int status;
+
+ guard(uart_port_lock_check_sysrq_irqsave)(p);
+
+ switch (FIELD_GET(AMBARELLA_UART_IIR_IID, iir)) {
+ case UART_IIR_NO_INT:
+ return 0;
+ case UART_IIR_BUSY:
+ serial_port_in(p, AMBARELLA_UART_USR);
+ return 1;
+ }
+
+ if (rx_timeout) {
+ status = serial_lsr_in(up);
+ if (!(status & (UART_LSR_DR | UART_LSR_BI)))
+ serial_port_in(p, UART_RX);
+ }
+
+ serial8250_handle_irq_locked(p, iir);
+ return 1;
+}
+
+static void ambarella_set_divisor(struct uart_port *p, unsigned int baud,
+ unsigned int quot, unsigned int quot_frac)
+{
+ struct uart_8250_port *up = up_to_u8250p(p);
+
+ if (ambarella_idle_enter(p))
+ return;
+
+ serial_port_out(p, UART_LCR, up->lcr | UART_LCR_DLAB);
+ if (serial_port_in(p, UART_LCR) & UART_LCR_DLAB)
+ serial_dl_write(up, quot);
+ serial_port_out(p, UART_LCR, up->lcr);
+
+ ambarella_idle_exit(p);
+}
+
+static int ambarella_startup(struct uart_port *port)
+{
+ struct uart_8250_port *up = up_to_u8250p(port);
+ int ret;
+
+ ret = serial8250_do_startup(port);
+ if (ret)
+ return ret;
+
+ up->ier |= AMBARELLA_UART_IER_ETOI;
+ serial_port_out(port, UART_IER, up->ier);
+
+ return 0;
+}
+
+static int ambarella_uart_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct ambarella_uart *data;
+ struct uart_8250_port uart = {};
+ struct resource *regs;
+ u32 reg_io_width = 4;
+ u32 reg_shift = 2;
+ int irq, ret;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!regs)
+ return dev_err_probe(dev, -EINVAL, "missing registers\n");
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->soc_data = device_get_match_data(dev);
+ if (!data->soc_data)
+ return -EINVAL;
+
+ data->clk = devm_clk_get_enabled(dev, NULL);
+ if (IS_ERR(data->clk))
+ return dev_err_probe(dev, PTR_ERR(data->clk),
+ "unable to get clock\n");
+
+ uart.port.dev = dev;
+ uart.port.mapbase = regs->start;
+ uart.port.mapsize = resource_size(regs);
+ uart.port.membase = devm_ioremap(dev, regs->start,
+ resource_size(regs));
+ if (!uart.port.membase)
+ return -ENOMEM;
+
+ uart.port.irq = irq;
+ uart.port.type = PORT_16550A;
+ uart.port.flags = UPF_FIXED_PORT | UPF_FIXED_TYPE | UPF_SKIP_TEST;
+ uart.port.startup = ambarella_startup;
+ uart.port.handle_irq = ambarella_handle_irq;
+ uart.port.set_divisor = ambarella_set_divisor;
+ uart.port.uartclk = clk_get_rate(data->clk);
+ if (!uart.port.uartclk)
+ return dev_err_probe(dev, -EINVAL, "clock rate not defined\n");
+
+ uart.port.fifosize = data->soc_data->fifo_size;
+ uart.port.private_data = data;
+ uart.capabilities = UART_CAP_FIFO | UART_CAP_NOTEMT | UART_CAP_AFE;
+
+ of_property_read_u32(dev->of_node, "reg-shift", ®_shift);
+ of_property_read_u32(dev->of_node, "reg-io-width", ®_io_width);
+ uart.port.regshift = reg_shift;
+
+ switch (reg_io_width) {
+ case 4:
+ uart.port.iotype = UPIO_MEM32;
+ uart.port.serial_in = ambarella_serial_in;
+ uart.port.serial_out = ambarella_serial_out;
+ break;
+ default:
+ return dev_err_probe(dev, -EINVAL,
+ "unsupported reg-io-width %u\n",
+ reg_io_width);
+ }
+
+ ret = serial8250_register_8250_port(&uart);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "unable to register 8250 port\n");
+
+ data->line = ret;
+ platform_set_drvdata(pdev, data);
+ return 0;
+}
+
+static void ambarella_uart_remove(struct platform_device *pdev)
+{
+ struct ambarella_uart *data = platform_get_drvdata(pdev);
+
+ serial8250_unregister_port(data->line);
+}
+
+static const struct ambarella_uart_soc_data cv75_uart_data = {
+ .fifo_size = 64,
+};
+
+static const struct of_device_id ambarella_uart_dt_ids[] = {
+ {
+ .compatible = "ambarella,cv75-uart",
+ .data = &cv75_uart_data,
+ },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, ambarella_uart_dt_ids);
+
+static struct platform_driver ambarella_uart_platform_driver = {
+ .driver = {
+ .name = "8250-ambarella",
+ .of_match_table = ambarella_uart_dt_ids,
+ },
+ .probe = ambarella_uart_probe,
+ .remove = ambarella_uart_remove,
+};
+
+module_platform_driver(ambarella_uart_platform_driver);
+
+static int __init ambarella_early_setup(struct earlycon_device *device,
+ const char *options)
+{
+ struct uart_port *port = &device->port;
+
+ if (!port->membase)
+ return -ENODEV;
+
+ port->iotype = UPIO_MEM32;
+ port->regshift = 2;
+ port->serial_in = ambarella_serial_in;
+ port->serial_out = ambarella_serial_out;
+
+ return early_serial8250_setup(device, options);
+}
+
+OF_EARLYCON_DECLARE(ambarella, "ambarella,cv75-uart", ambarella_early_setup);
+
+MODULE_IMPORT_NS("SERIAL_8250");
+MODULE_AUTHOR("Long Zhao <longzhao@ambarella.com>");
+MODULE_DESCRIPTION("Ambarella UART driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/tty/serial/8250/Kconfig b/drivers/tty/serial/8250/Kconfig
index fc3e58d62233..523c55be3227 100644
--- a/drivers/tty/serial/8250/Kconfig
+++ b/drivers/tty/serial/8250/Kconfig
@@ -337,6 +337,15 @@ config SERIAL_8250_ACORN
system, say Y to this option. The driver can handle 1, 2, or 3 port
cards. If unsure, say N.
+config SERIAL_8250_AMBARELLA
+ tristate "Ambarella UART support"
+ depends on SERIAL_8250 && HAVE_CLK
+ depends on ARCH_AMBARELLA || COMPILE_TEST
+ default y if ARCH_AMBARELLA
+ help
+ Support for the on-chip UART found on Ambarella SoCs.
+ If unsure, say N.
+
config SERIAL_8250_BCM2835AUX
tristate "BCM2835 auxiliar mini UART support"
depends on ARCH_BCM2835 || COMPILE_TEST
diff --git a/drivers/tty/serial/8250/Makefile b/drivers/tty/serial/8250/Makefile
index 6d21402b4435..601d9e2589fe 100644
--- a/drivers/tty/serial/8250/Makefile
+++ b/drivers/tty/serial/8250/Makefile
@@ -20,6 +20,7 @@ obj-$(CONFIG_SERIAL_8250_CONSOLE) += 8250_early.o
obj-$(CONFIG_SERIAL_8250_ACCENT) += 8250_accent.o
obj-$(CONFIG_SERIAL_8250_ACORN) += 8250_acorn.o
+obj-$(CONFIG_SERIAL_8250_AMBARELLA) += 8250_ambarella.o
obj-$(CONFIG_SERIAL_8250_ASPEED_VUART) += 8250_aspeed_vuart.o
obj-$(CONFIG_SERIAL_8250_BCM2835AUX) += 8250_bcm2835aux.o
obj-$(CONFIG_SERIAL_8250_BCM7271) += 8250_bcm7271.o
--
2.34.1
^ permalink raw reply related [flat|nested] 17+ messages in thread* Re: [PATCH v5 10/12] serial: 8250: add Ambarella UART driver
2026-08-18 10:31 ` [PATCH v5 10/12] serial: 8250: add Ambarella UART driver Long Zhao via B4 Relay
@ 2026-08-18 14:17 ` Andy Shevchenko
0 siblings, 0 replies; 17+ messages in thread
From: Andy Shevchenko @ 2026-08-18 14:17 UTC (permalink / raw)
To: longzhao
Cc: Arnd Bergmann, Krzysztof Kozlowski, Alexandre Belloni, soc,
linux-arm-kernel, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Michael Turquette, Stephen Boyd, Linus Walleij,
Bartosz Golaszewski, Greg Kroah-Hartman, Jiri Slaby,
Catalin Marinas, Will Deacon, devicetree, linux-clk, linux-gpio,
linux-serial, linux-kernel, Brian Masney, Lee Jones, mfd,
Long Zhao
On Tue, Aug 18, 2026 at 06:31:24PM +0800, Long Zhao via B4 Relay wrote:
> Add an 8250 platform driver for Ambarella CV75 instead of claiming
> snps,dw-apb-uart compatibility.
This is a record from a changelog. There is nothing in the commit message.
...
The code seems like an almost blind copy of 8250_dw.c.
> +#define AMBARELLA_UART_USR 0x1f
> +#define AMBARELLA_UART_USR_BUSY BIT(0)
So, is it Synopsys IP-based design? How this gets into this platform? This
whole story completely made me lost.
> +#define AMBARELLA_UART_IIR_IID GENMASK(3, 0)
> +#define AMBARELLA_UART_IIR_STATUS GENMASK(5, 0)
...
> + of_property_read_u32(dev->of_node, "reg-shift", ®_shift);
> + of_property_read_u32(dev->of_node, "reg-io-width", ®_io_width);
> + uart.port.regshift = reg_shift;
> + switch (reg_io_width) {
> + case 4:
> + uart.port.iotype = UPIO_MEM32;
> + uart.port.serial_in = ambarella_serial_in;
> + uart.port.serial_out = ambarella_serial_out;
> + break;
> + default:
> + return dev_err_probe(dev, -EINVAL,
> + "unsupported reg-io-width %u\n",
> + reg_io_width);
> + }
This is handled by uart_read_port_properties().
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v5 11/12] arm64: ambarella: add ARCH_AMBARELLA and CV75 EVK DT
2026-08-18 10:31 [PATCH v5 00/12] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
` (9 preceding siblings ...)
2026-08-18 10:31 ` [PATCH v5 10/12] serial: 8250: add Ambarella UART driver Long Zhao via B4 Relay
@ 2026-08-18 10:31 ` Long Zhao via B4 Relay
2026-08-18 10:31 ` [PATCH v5 12/12] MAINTAINERS: add ARM/AMBARELLA SoC support Long Zhao via B4 Relay
11 siblings, 0 replies; 17+ messages in thread
From: Long Zhao via B4 Relay @ 2026-08-18 10:31 UTC (permalink / raw)
To: Arnd Bergmann, Krzysztof Kozlowski, Alexandre Belloni, soc,
linux-arm-kernel
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
Stephen Boyd, Linus Walleij, Bartosz Golaszewski,
Greg Kroah-Hartman, Jiri Slaby, Andy Shevchenko, Catalin Marinas,
Will Deacon, devicetree, linux-clk, linux-gpio, linux-serial,
linux-kernel, Brian Masney, Lee Jones, mfd, Long Zhao, Long Zhao
From: Long Zhao <longzhao@ambarella.com>
Enable ARCH_AMBARELLA and add a minimal CV75 SoC / EVK device tree for
console bring-up with the RCT clocks, UART, pinctrl and GPIO banks.
Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
arch/arm64/Kconfig.platforms | 8 ++
arch/arm64/boot/dts/Makefile | 1 +
arch/arm64/boot/dts/ambarella/Makefile | 3 +
arch/arm64/boot/dts/ambarella/cv75-evk.dts | 30 +++++
arch/arm64/boot/dts/ambarella/cv75.dtsi | 176 +++++++++++++++++++++++++++++
arch/arm64/configs/defconfig | 2 +
6 files changed, 220 insertions(+)
diff --git a/arch/arm64/Kconfig.platforms b/arch/arm64/Kconfig.platforms
index d2acfac73003..cb05eff267a8 100644
--- a/arch/arm64/Kconfig.platforms
+++ b/arch/arm64/Kconfig.platforms
@@ -26,6 +26,14 @@ config ARCH_SUNXI
help
This enables support for Allwinner sunxi based SoCs like the A64.
+config ARCH_AMBARELLA
+ bool "Ambarella SoC family"
+ select ARM_PSCI
+ select HAVE_ARM_ARCH_TIMER
+ select PINCTRL
+ help
+ This enables support for the ARM64 based Ambarella SoCs like CV75.
+
config ARCH_ALPINE
bool "Annapurna Labs Alpine platform"
select ALPINE_MSI if PCI
diff --git a/arch/arm64/boot/dts/Makefile b/arch/arm64/boot/dts/Makefile
index fc726b215f12..f1709eb5d915 100644
--- a/arch/arm64/boot/dts/Makefile
+++ b/arch/arm64/boot/dts/Makefile
@@ -4,6 +4,7 @@ subdir-y += airoha
subdir-y += allwinner
subdir-y += altera
subdir-y += amazon
+subdir-y += ambarella
subdir-y += amd
subdir-y += amlogic
subdir-y += apm
diff --git a/arch/arm64/boot/dts/ambarella/Makefile b/arch/arm64/boot/dts/ambarella/Makefile
new file mode 100644
index 000000000000..ddca3d7b18cc
--- /dev/null
+++ b/arch/arm64/boot/dts/ambarella/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+
+dtb-$(CONFIG_ARCH_AMBARELLA) += cv75-evk.dtb
diff --git a/arch/arm64/boot/dts/ambarella/cv75-evk.dts b/arch/arm64/boot/dts/ambarella/cv75-evk.dts
new file mode 100644
index 000000000000..42a0fe24b83b
--- /dev/null
+++ b/arch/arm64/boot/dts/ambarella/cv75-evk.dts
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Ambarella CV75 EVK board
+ */
+
+/dts-v1/;
+
+#include "cv75.dtsi"
+
+/ {
+ model = "Ambarella CV75 EVK Board";
+ compatible = "ambarella,cv75-evk", "ambarella,cv75";
+
+ aliases {
+ serial0 = &uart0;
+ };
+
+ chosen {
+ stdout-path = &uart0;
+ };
+
+ memory@200000 {
+ device_type = "memory";
+ reg = <0x0 0x00200000 0x0 0x07e00000>;
+ };
+};
+
+&uart0 {
+ status = "okay";
+};
diff --git a/arch/arm64/boot/dts/ambarella/cv75.dtsi b/arch/arm64/boot/dts/ambarella/cv75.dtsi
new file mode 100644
index 000000000000..3b7948954968
--- /dev/null
+++ b/arch/arm64/boot/dts/ambarella/cv75.dtsi
@@ -0,0 +1,176 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Ambarella CV75 SoC
+ *
+ * Boot peripheral: UART0 console.
+ * Clocks: a single ambarella,cv75-rct provider.
+ */
+
+#include <dt-bindings/clock/ambarella,cv75-rct.h>
+#include <dt-bindings/interrupt-controller/arm-gic.h>
+
+/ {
+ compatible = "ambarella,cv75";
+ interrupt-parent = <&gic>;
+ #address-cells = <2>;
+ #size-cells = <2>;
+
+ cpus {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ cpu@0 {
+ compatible = "arm,cortex-a76";
+ device_type = "cpu";
+ reg = <0x000>;
+ enable-method = "psci";
+ };
+
+ cpu@1 {
+ compatible = "arm,cortex-a76";
+ device_type = "cpu";
+ reg = <0x100>;
+ enable-method = "psci";
+ };
+ };
+
+ osc: oscillator {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <24000000>;
+ clock-output-names = "osc";
+ };
+
+ psci {
+ compatible = "arm,psci-0.2";
+ method = "smc";
+ };
+
+ soc {
+ compatible = "simple-bus";
+ #address-cells = <2>;
+ #size-cells = <2>;
+ ranges;
+
+ ahb@ffe0000000 {
+ compatible = "simple-bus";
+ reg = <0xff 0xe0000000 0x0 0x01000000>;
+ ranges = <0xe0000000 0xff 0xe0000000 0x01000000>;
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ s_scratchpad_syscon: scratchpad@e002e000 {
+ compatible = "ambarella,cv75-sec-scratchpad", "syscon";
+ reg = <0xe002e000 0x1000>;
+ };
+ };
+
+ apb@ffe4000000 {
+ compatible = "simple-bus";
+ reg = <0xff 0xe4000000 0x0 0x01000000>;
+ ranges = <0xe4000000 0xff 0xe4000000 0x01000000>;
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ uart0: serial@e4000000 {
+ compatible = "ambarella,cv75-uart";
+ reg = <0xe4000000 0x1000>;
+ clocks = <&rct CV75_GCLK_UART0>;
+ interrupts = <GIC_SPI 170 IRQ_TYPE_LEVEL_HIGH>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&uart0_pins>;
+ reg-io-width = <4>;
+ reg-shift = <2>;
+
+ status = "disabled";
+ };
+
+ pinctrl: pinctrl@e4010000 {
+ compatible = "ambarella,cv75-pinctrl";
+ reg = <0xe4010000 0x1000>;
+ ambarella,drive-strength-syscon = <&rct>;
+ ambarella,pull-syscon = <&s_scratchpad_syscon>;
+
+ uart0_pins: uart0-pins {
+ function = "uart0";
+ groups = "uart0";
+ };
+ };
+
+ gpio0: gpio@e4013000 {
+ compatible = "ambarella,cv75-gpio", "arm,primecell";
+ reg = <0xe4013000 0x1000>;
+ clocks = <&rct CV75_GCLK_APB>;
+ clock-names = "apb_pclk";
+ interrupts = <GIC_SPI 163 IRQ_TYPE_LEVEL_HIGH>;
+ gpio-controller;
+ #gpio-cells = <2>;
+ gpio-ranges = <&pinctrl 0 0 32>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ arm,primecell-periphid = <0x00000061>;
+ };
+
+ gpio1: gpio@e4014000 {
+ compatible = "ambarella,cv75-gpio", "arm,primecell";
+ reg = <0xe4014000 0x1000>;
+ clocks = <&rct CV75_GCLK_APB>;
+ clock-names = "apb_pclk";
+ interrupts = <GIC_SPI 164 IRQ_TYPE_LEVEL_HIGH>;
+ gpio-controller;
+ #gpio-cells = <2>;
+ gpio-ranges = <&pinctrl 0 32 32>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ arm,primecell-periphid = <0x00000061>;
+ };
+
+ gpio2: gpio@e4015000 {
+ compatible = "ambarella,cv75-gpio", "arm,primecell";
+ reg = <0xe4015000 0x1000>;
+ clocks = <&rct CV75_GCLK_APB>;
+ clock-names = "apb_pclk";
+ interrupts = <GIC_SPI 165 IRQ_TYPE_LEVEL_HIGH>;
+ gpio-controller;
+ #gpio-cells = <2>;
+ gpio-ranges = <&pinctrl 0 64 32>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ arm,primecell-periphid = <0x00000061>;
+ };
+ };
+
+ rct: clock-controller@ffed080000 {
+ compatible = "ambarella,cv75-rct", "syscon";
+ reg = <0xff 0xed080000 0x0 0x1000>;
+ clocks = <&osc>;
+ clock-names = "osc";
+ #clock-cells = <1>;
+ assigned-clocks = <&rct CV75_GCLK_UART0>;
+ assigned-clock-parents = <&osc>;
+ assigned-clock-rates = <24000000>;
+ };
+
+ gic: interrupt-controller@fff0101000 {
+ compatible = "arm,gic-400";
+ reg = <0xff 0xf0101000 0x0 0x1000>,
+ <0xff 0xf0102000 0x0 0x2000>,
+ <0xff 0xf0104000 0x0 0x2000>,
+ <0xff 0xf0106000 0x0 0x2000>;
+ interrupt-controller;
+ #interrupt-cells = <3>;
+ #address-cells = <0>;
+ interrupts = <GIC_PPI 9
+ (GIC_CPU_MASK_SIMPLE(2) | IRQ_TYPE_LEVEL_HIGH)>;
+ };
+ };
+
+ timer {
+ compatible = "arm,armv8-timer";
+ interrupts = <GIC_PPI 13 (GIC_CPU_MASK_SIMPLE(2) | IRQ_TYPE_LEVEL_LOW)>,
+ <GIC_PPI 14 (GIC_CPU_MASK_SIMPLE(2) | IRQ_TYPE_LEVEL_LOW)>,
+ <GIC_PPI 11 (GIC_CPU_MASK_SIMPLE(2) | IRQ_TYPE_LEVEL_LOW)>,
+ <GIC_PPI 10 (GIC_CPU_MASK_SIMPLE(2) | IRQ_TYPE_LEVEL_LOW)>,
+ <GIC_PPI 12 (GIC_CPU_MASK_SIMPLE(2) | IRQ_TYPE_LEVEL_LOW)>;
+ };
+};
diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig
index ad3bd3d9507b..ae5bcc6c9fc7 100644
--- a/arch/arm64/configs/defconfig
+++ b/arch/arm64/configs/defconfig
@@ -36,6 +36,7 @@ CONFIG_CRASH_DUMP=y
CONFIG_ARCH_ACTIONS=y
CONFIG_ARCH_AIROHA=y
CONFIG_ARCH_SUNXI=y
+CONFIG_ARCH_AMBARELLA=y
CONFIG_ARCH_ALPINE=y
CONFIG_ARCH_APPLE=y
CONFIG_ARCH_ARTPEC=y
@@ -560,6 +561,7 @@ CONFIG_SERIAL_8250_CONSOLE=y
CONFIG_SERIAL_8250_NR_UARTS=8
CONFIG_SERIAL_8250_EXTENDED=y
CONFIG_SERIAL_8250_SHARE_IRQ=y
+CONFIG_SERIAL_8250_AMBARELLA=y
CONFIG_SERIAL_8250_BCM2835AUX=y
CONFIG_SERIAL_8250_DW=y
CONFIG_SERIAL_8250_EM=y
--
2.34.1
^ permalink raw reply related [flat|nested] 17+ messages in thread* [PATCH v5 12/12] MAINTAINERS: add ARM/AMBARELLA SoC support
2026-08-18 10:31 [PATCH v5 00/12] Ambarella CV75 SoC minimal bring-up Long Zhao via B4 Relay
` (10 preceding siblings ...)
2026-08-18 10:31 ` [PATCH v5 11/12] arm64: ambarella: add ARCH_AMBARELLA and CV75 EVK DT Long Zhao via B4 Relay
@ 2026-08-18 10:31 ` Long Zhao via B4 Relay
11 siblings, 0 replies; 17+ messages in thread
From: Long Zhao via B4 Relay @ 2026-08-18 10:31 UTC (permalink / raw)
To: Arnd Bergmann, Krzysztof Kozlowski, Alexandre Belloni, soc,
linux-arm-kernel
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Michael Turquette,
Stephen Boyd, Linus Walleij, Bartosz Golaszewski,
Greg Kroah-Hartman, Jiri Slaby, Andy Shevchenko, Catalin Marinas,
Will Deacon, devicetree, linux-clk, linux-gpio, linux-serial,
linux-kernel, Brian Masney, Lee Jones, mfd, Long Zhao, Long Zhao
From: Long Zhao <longzhao@ambarella.com>
Add a maintainer entry covering Ambarella CV75 bindings, DTS and
related drivers.
Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
MAINTAINERS | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 803b60ad628f..4352a2dc1e3d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2502,6 +2502,22 @@ F: drivers/clocksource/asm9260_timer.c
F: drivers/rtc/rtc-asm9260.c
F: drivers/watchdog/asm9260_wdt.c
+ARM/AMBARELLA SOC SUPPORT
+M: Long Zhao <longzhao@ambarella.com>
+L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
+L: devicetree@vger.kernel.org
+S: Maintained
+F: Documentation/devicetree/bindings/arm/ambarella.yaml
+F: Documentation/devicetree/bindings/clock/ambarella,cv75-rct.yaml
+F: Documentation/devicetree/bindings/pinctrl/ambarella,cv75-pinctrl.yaml
+F: Documentation/devicetree/bindings/serial/ambarella,cv75-uart.yaml
+F: arch/arm64/boot/dts/ambarella/
+F: drivers/clk/ambarella/
+F: drivers/pinctrl/pinctrl-ambarella*
+F: drivers/tty/serial/8250/8250_ambarella.c
+F: include/dt-bindings/clock/ambarella,cv75-rct.h
+N: ambarella
+
ARM/AMD PENSANDO ARM64 ARCHITECTURE
M: Brad Larson <blarson@amd.com>
L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
--
2.34.1
^ permalink raw reply related [flat|nested] 17+ messages in thread