* [PATCH v4 0/4] Ambarella CV75: pinctrl and PL061 GPIO
@ 2026-08-14 6:25 ` Long Zhao
0 siblings, 0 replies; 13+ messages in thread
From: Long Zhao via B4 Relay @ 2026-08-14 6:25 UTC (permalink / raw)
To: Linus Walleij, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Bartosz Golaszewski
Cc: linux-gpio, devicetree, linux-arm-kernel, linux-kernel, Long Zhao
This is part B of a 3-part CV75 split:
A: bindings + RCT/CCU clocks + SoC/EVK DT + MAINTAINERS
B: pinctrl + PL061 GPIO variant (this series)
C: 8250_dw UART quirks
Depends on series A:
https://lore.kernel.org/r/20260813-cv75-v3-a-v3-0-cfdd20b1b4dd@ambarella.com
Link to v3:
https://lore.kernel.org/r/20260813-cv75-v3-b-v3-0-1d82e18da09b@ambarella.com
Link to v2:
https://lore.kernel.org/r/20260806-longzhao-upstream-cv75-v2-v2-0-6b09707c5fe9@ambarella.com
Summary:
- CV75 pinctrl binding and driver
- Ambarella register-layout variant for gpio-pl061
(DT binding and driver in separate patches)
Changes since v3:
- Split pl061 DT binding and driver into separate patches (Bartosz)
Changes since v2 (B scope):
- reuse PL061 instead of a duplicated GPIO implementation (Linus)
- complete function/groups enums in the binding (Krzysztof)
Verified on CV75 with A+B+C.
Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
Long Zhao (4):
dt-bindings: pinctrl: add Ambarella CV75 pinctrl
dt-bindings: gpio: pl061: add Ambarella CV75 variant
gpio: pl061: add Ambarella CV75 register layout variant
pinctrl: ambarella: add CV75 pin controller
.../devicetree/bindings/gpio/pl061-gpio.yaml | 35 +-
.../bindings/pinctrl/ambarella,cv75-pinctrl.yaml | 240 ++++++
drivers/gpio/gpio-pl061.c | 280 +++++--
drivers/pinctrl/Kconfig | 13 +
drivers/pinctrl/Makefile | 2 +
drivers/pinctrl/pinctrl-ambarella-cv75.c | 561 ++++++++++++++
drivers/pinctrl/pinctrl-ambarella.c | 839 +++++++++++++++++++++
drivers/pinctrl/pinctrl-ambarella.h | 50 ++
8 files changed, 1949 insertions(+), 71 deletions(-)
---
base-commit: 18bf29fba8d0bdd0db5a0497ecf06f404bc740d1
change-id: 20260814-cv75-v4-b-fed40c58c86f
Best regards,
--
Long Zhao <longzhao@ambarella.com>
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v4 0/4] Ambarella CV75: pinctrl and PL061 GPIO
@ 2026-08-14 6:25 ` Long Zhao
0 siblings, 0 replies; 13+ messages in thread
From: Long Zhao @ 2026-08-14 6:25 UTC (permalink / raw)
To: Linus Walleij, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Bartosz Golaszewski
Cc: linux-gpio, devicetree, linux-arm-kernel, linux-kernel, Long Zhao
This is part B of a 3-part CV75 split:
A: bindings + RCT/CCU clocks + SoC/EVK DT + MAINTAINERS
B: pinctrl + PL061 GPIO variant (this series)
C: 8250_dw UART quirks
Depends on series A:
https://lore.kernel.org/r/20260813-cv75-v3-a-v3-0-cfdd20b1b4dd@ambarella.com
Link to v3:
https://lore.kernel.org/r/20260813-cv75-v3-b-v3-0-1d82e18da09b@ambarella.com
Link to v2:
https://lore.kernel.org/r/20260806-longzhao-upstream-cv75-v2-v2-0-6b09707c5fe9@ambarella.com
Summary:
- CV75 pinctrl binding and driver
- Ambarella register-layout variant for gpio-pl061
(DT binding and driver in separate patches)
Changes since v3:
- Split pl061 DT binding and driver into separate patches (Bartosz)
Changes since v2 (B scope):
- reuse PL061 instead of a duplicated GPIO implementation (Linus)
- complete function/groups enums in the binding (Krzysztof)
Verified on CV75 with A+B+C.
Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
Long Zhao (4):
dt-bindings: pinctrl: add Ambarella CV75 pinctrl
dt-bindings: gpio: pl061: add Ambarella CV75 variant
gpio: pl061: add Ambarella CV75 register layout variant
pinctrl: ambarella: add CV75 pin controller
.../devicetree/bindings/gpio/pl061-gpio.yaml | 35 +-
.../bindings/pinctrl/ambarella,cv75-pinctrl.yaml | 240 ++++++
drivers/gpio/gpio-pl061.c | 280 +++++--
drivers/pinctrl/Kconfig | 13 +
drivers/pinctrl/Makefile | 2 +
drivers/pinctrl/pinctrl-ambarella-cv75.c | 561 ++++++++++++++
drivers/pinctrl/pinctrl-ambarella.c | 839 +++++++++++++++++++++
drivers/pinctrl/pinctrl-ambarella.h | 50 ++
8 files changed, 1949 insertions(+), 71 deletions(-)
---
base-commit: 18bf29fba8d0bdd0db5a0497ecf06f404bc740d1
change-id: 20260814-cv75-v4-b-fed40c58c86f
Best regards,
--
Long Zhao <longzhao@ambarella.com>
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v4 1/4] dt-bindings: pinctrl: add Ambarella CV75 pinctrl
2026-08-14 6:25 ` Long Zhao
@ 2026-08-14 6:25 ` Long Zhao
-1 siblings, 0 replies; 13+ messages in thread
From: Long Zhao via B4 Relay @ 2026-08-14 6:25 UTC (permalink / raw)
To: Linus Walleij, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Bartosz Golaszewski
Cc: linux-gpio, devicetree, linux-arm-kernel, linux-kernel, 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 | 240 +++++++++++++++++++++
1 file changed, 240 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..9d65da32d3d2
--- /dev/null
+++ b/Documentation/devicetree/bindings/pinctrl/ambarella,cv75-pinctrl.yaml
@@ -0,0 +1,240 @@
+# 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:
+ $ref: /schemas/types.yaml#/definitions/string-array
+ 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] 13+ messages in thread
* [PATCH v4 1/4] dt-bindings: pinctrl: add Ambarella CV75 pinctrl
@ 2026-08-14 6:25 ` Long Zhao
0 siblings, 0 replies; 13+ messages in thread
From: Long Zhao @ 2026-08-14 6:25 UTC (permalink / raw)
To: Linus Walleij, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Bartosz Golaszewski
Cc: linux-gpio, devicetree, linux-arm-kernel, linux-kernel, Long Zhao
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 | 240 +++++++++++++++++++++
1 file changed, 240 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..9d65da32d3d2
--- /dev/null
+++ b/Documentation/devicetree/bindings/pinctrl/ambarella,cv75-pinctrl.yaml
@@ -0,0 +1,240 @@
+# 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:
+ $ref: /schemas/types.yaml#/definitions/string-array
+ 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] 13+ messages in thread
* [PATCH v4 2/4] dt-bindings: gpio: pl061: add Ambarella CV75 variant
2026-08-14 6:25 ` Long Zhao
@ 2026-08-14 6:25 ` Long Zhao
-1 siblings, 0 replies; 13+ messages in thread
From: Long Zhao via B4 Relay @ 2026-08-14 6:25 UTC (permalink / raw)
To: Linus Walleij, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Bartosz Golaszewski
Cc: linux-gpio, devicetree, linux-arm-kernel, linux-kernel, 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] 13+ messages in thread
* [PATCH v4 2/4] dt-bindings: gpio: pl061: add Ambarella CV75 variant
@ 2026-08-14 6:25 ` Long Zhao
0 siblings, 0 replies; 13+ messages in thread
From: Long Zhao @ 2026-08-14 6:25 UTC (permalink / raw)
To: Linus Walleij, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Bartosz Golaszewski
Cc: linux-gpio, devicetree, linux-arm-kernel, linux-kernel, Long Zhao
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] 13+ messages in thread
* [PATCH v4 3/4] gpio: pl061: add Ambarella CV75 register layout variant
2026-08-14 6:25 ` Long Zhao
@ 2026-08-14 6:25 ` Long Zhao
-1 siblings, 0 replies; 13+ messages in thread
From: Long Zhao via B4 Relay @ 2026-08-14 6:25 UTC (permalink / raw)
To: Linus Walleij, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Bartosz Golaszewski
Cc: linux-gpio, devicetree, linux-arm-kernel, linux-kernel, 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 | 280 +++++++++++++++++++++++++++++++++++-----------
1 file changed, 213 insertions(+), 67 deletions(-)
diff --git a/drivers/gpio/gpio-pl061.c b/drivers/gpio/gpio-pl061.c
index 919cf86fd590..d9e5a9f25681 100644
--- a/drivers/gpio/gpio-pl061.c
+++ b/drivers/gpio/gpio-pl061.c
@@ -26,41 +26,94 @@
#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 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 +123,12 @@ 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);
raw_spin_unlock_irqrestore(&pl061->lock, flags);
return 0;
@@ -86,19 +139,20 @@ 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);
raw_spin_unlock_irqrestore(&pl061->lock, flags);
return 0;
@@ -107,15 +161,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 +189,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 +204,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 +260,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 +281,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 +295,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 +310,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 +333,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);
}
@@ -314,6 +377,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 +394,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 +402,14 @@ 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->afsel != PL061_REG_NONE)
+ pl061_write(pl061, 0, pl061->variant->afsel);
+ 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 +440,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 +480,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 +489,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] 13+ messages in thread
* [PATCH v4 3/4] gpio: pl061: add Ambarella CV75 register layout variant
@ 2026-08-14 6:25 ` Long Zhao
0 siblings, 0 replies; 13+ messages in thread
From: Long Zhao @ 2026-08-14 6:25 UTC (permalink / raw)
To: Linus Walleij, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Bartosz Golaszewski
Cc: linux-gpio, devicetree, linux-arm-kernel, linux-kernel, Long Zhao
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 | 280 +++++++++++++++++++++++++++++++++++-----------
1 file changed, 213 insertions(+), 67 deletions(-)
diff --git a/drivers/gpio/gpio-pl061.c b/drivers/gpio/gpio-pl061.c
index 919cf86fd590..d9e5a9f25681 100644
--- a/drivers/gpio/gpio-pl061.c
+++ b/drivers/gpio/gpio-pl061.c
@@ -26,41 +26,94 @@
#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 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 +123,12 @@ 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);
raw_spin_unlock_irqrestore(&pl061->lock, flags);
return 0;
@@ -86,19 +139,20 @@ 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);
raw_spin_unlock_irqrestore(&pl061->lock, flags);
return 0;
@@ -107,15 +161,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 +189,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 +204,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 +260,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 +281,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 +295,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 +310,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 +333,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);
}
@@ -314,6 +377,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 +394,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 +402,14 @@ 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->afsel != PL061_REG_NONE)
+ pl061_write(pl061, 0, pl061->variant->afsel);
+ 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 +440,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 +480,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 +489,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] 13+ messages in thread
* [PATCH v4 4/4] pinctrl: ambarella: add CV75 pin controller
2026-08-14 6:25 ` Long Zhao
@ 2026-08-14 6:25 ` Long Zhao
-1 siblings, 0 replies; 13+ messages in thread
From: Long Zhao via B4 Relay @ 2026-08-14 6:25 UTC (permalink / raw)
To: Linus Walleij, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Bartosz Golaszewski
Cc: linux-gpio, devicetree, linux-arm-kernel, linux-kernel, 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 | 13 +
drivers/pinctrl/Makefile | 2 +
drivers/pinctrl/pinctrl-ambarella-cv75.c | 561 +++++++++++++++++++++
drivers/pinctrl/pinctrl-ambarella.c | 839 +++++++++++++++++++++++++++++++
drivers/pinctrl/pinctrl-ambarella.h | 50 ++
5 files changed, 1465 insertions(+)
diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig
index c2cdd7b2c49b..119acde60a93 100644
--- a/drivers/pinctrl/Kconfig
+++ b/drivers/pinctrl/Kconfig
@@ -37,6 +37,19 @@ config DEBUG_PINCTRL
help
Say Y here to add some extra checks and diagnostics to PINCTRL calls.
+config PINCTRL_AMB
+ bool "Ambarella pin controller support"
+ depends on ARCH_AMBARELLA || COMPILE_TEST
+ select GENERIC_PINCONF
+ select GPIOLIB
+ select GPIOLIB_IRQCHIP
+ select 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..fb79a8b02209
--- /dev/null
+++ b/drivers/pinctrl/pinctrl-ambarella.c
@@ -0,0 +1,839 @@
+// 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 &= (~(0x1 << offset));
+ data |= (((altfunc >> i) & 0x1) << offset);
+ writel_relaxed(data, soc->iomux_base + IOMUX_OFFSET(bank, i));
+ }
+
+ amb_iomux_commit(soc);
+}
+
+/* enable a specified pinmux by writing to registers */
+static int amb_pinmux_set_mux(struct pinctrl_dev *pctldev,
+ unsigned int selector, unsigned int group)
+{
+ struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+ const struct ambpin_group *grp;
+ u32 i, pin, alt, bank, offset;
+ unsigned long flags;
+
+ grp = &soc->groups[group];
+
+ raw_spin_lock_irqsave(&soc->lock, flags);
+ for (i = 0; i < grp->num_pins; i++) {
+ pin = AMBA_PINMUX_TO_PIN(grp->pinmux[i]);
+ alt = AMBA_PINMUX_TO_ALT(grp->pinmux[i]);
+ bank = PINID_TO_BANK(pin);
+ offset = PINID_TO_OFFSET(pin);
+ amb_pinmux_set_altfunc(soc, bank, offset, alt);
+ }
+ raw_spin_unlock_irqrestore(&soc->lock, flags);
+
+ return 0;
+}
+
+static int amb_pinmux_gpio_request_enable(struct pinctrl_dev *pctldev,
+ struct pinctrl_gpio_range *range,
+ unsigned int pin)
+{
+ struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+ u32 bank, offset;
+ unsigned long flags;
+
+ if (!range || !range->gc) {
+ dev_err(soc->dev, "invalid range: %p\n", range);
+ return -EINVAL;
+ }
+
+ if (test_and_set_bit(pin, soc->used))
+ return -EBUSY;
+
+ bank = PINID_TO_BANK(pin);
+ offset = PINID_TO_OFFSET(pin);
+
+ raw_spin_lock_irqsave(&soc->lock, flags);
+ amb_pinmux_set_altfunc(soc, bank, offset, 0);
+ raw_spin_unlock_irqrestore(&soc->lock, flags);
+
+ return 0;
+}
+
+static void amb_pinmux_gpio_disable_free(struct pinctrl_dev *pctldev,
+ struct pinctrl_gpio_range *range,
+ unsigned int pin)
+{
+ struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+
+ dev_dbg(soc->dev, "disable pin %u as GPIO\n", pin);
+ /* Set the pin to some default state, GPIO is usually default */
+
+ clear_bit(pin, soc->used);
+}
+
+/* list of pinmux callbacks for the pinmux vertical in pinctrl core */
+static const struct pinmux_ops amb_pinmux_ops = {
+ .request = amb_pinmux_request,
+ .free = amb_pinmux_free,
+ .get_functions_count = amb_pinmux_get_fcount,
+ .get_function_name = amb_pinmux_get_fname,
+ .get_function_groups = amb_pinmux_get_groups,
+ .set_mux = amb_pinmux_set_mux,
+ .gpio_request_enable = amb_pinmux_gpio_request_enable,
+ .gpio_disable_free = amb_pinmux_gpio_disable_free,
+};
+
+static int amb_drive_strength_to_reg(struct amb_pinctrl_soc_data *soc,
+ u32 strength)
+{
+ if (soc->data->have_ds2) {
+ switch (strength) {
+ case 3:
+ return 0;
+ case 4:
+ case 5:
+ return 1;
+ case 6:
+ return 2;
+ case 7:
+ case 8:
+ return 3;
+ case 9:
+ return 4;
+ case 12:
+ return 5;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ switch (strength) {
+ case 2:
+ return 0;
+ case 4:
+ return 1;
+ case 8:
+ return 2;
+ case 12:
+ return 3;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int amb_reg_to_drive_strength(struct amb_pinctrl_soc_data *soc, u32 ds)
+{
+ static const int ds2_ma[] = { 3, 4, 6, 8, 9, 12 };
+ static const int ds_ma[] = { 2, 4, 8, 12 };
+
+ if (soc->data->have_ds2) {
+ if (ds >= ARRAY_SIZE(ds2_ma))
+ return -EINVAL;
+
+ return ds2_ma[ds];
+ }
+
+ if (ds >= ARRAY_SIZE(ds_ma))
+ return -EINVAL;
+
+ return ds_ma[ds];
+}
+
+/* set the pin config settings for a specified pin */
+static int amb_pinconf_set(struct pinctrl_dev *pctldev, unsigned int pin,
+ unsigned long *configs, unsigned int num_configs)
+{
+ struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+ 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, ds;
+ u32 bank, offset;
+ int strength;
+
+ bank = PINID_TO_BANK(pin);
+ if (bank >= soc->data->nr_banks) {
+ seq_puts(s, " (no pinconf)");
+ return;
+ }
+
+ offset = PINID_TO_OFFSET(pin);
+
+ regmap_read(soc->pull_regmap, soc->data->pull_en[bank], &pull_en);
+ pull_en = (pull_en >> offset) & 1;
+ regmap_read(soc->pull_regmap, soc->data->pull_dir[bank], &pull_dir);
+ pull_dir = (pull_dir >> offset) & 1;
+ seq_printf(s, " pull: %s,",
+ pull_en ? (pull_dir ? "up" : "down") : "disable");
+
+ regmap_read(soc->ds_regmap, soc->data->ds0[bank], &ds0);
+ ds0 = (ds0 >> offset) & 1;
+ regmap_read(soc->ds_regmap, soc->data->ds1[bank], &ds1);
+ ds1 = (ds1 >> offset) & 1;
+ if (soc->data->have_ds2) {
+ regmap_read(soc->ds_regmap, soc->data->ds2[bank], &ds2);
+ ds2 = (ds2 >> offset) & 1;
+ ds = (ds2 << 2) | (ds1 << 1) | ds0;
+ } else {
+ ds = (ds0 << 1) | ds1;
+ }
+
+ strength = amb_reg_to_drive_strength(soc, ds);
+ if (strength < 0)
+ seq_puts(s, " drive-strength: invalid");
+ else
+ seq_printf(s, " drive-strength: %dmA", strength);
+}
+#endif
+
+/* list of pinconfig callbacks for pinconfig vertical in the pinctrl code */
+static const struct pinconf_ops amb_pinconf_ops = {
+ .is_generic = true,
+ .pin_config_get = amb_pinconf_get,
+ .pin_config_set = amb_pinconf_set,
+ .pin_config_group_set = amb_pinconf_group_set,
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+ .pin_config_dbg_show = amb_pinconf_dbg_show,
+#endif
+};
+
+/* register the pinctrl interface with the pinctrl subsystem */
+static int amb_pinctrl_register(struct amb_pinctrl_soc_data *soc)
+{
+ struct pinctrl_pin_desc *pindesc;
+ struct pinctrl_desc *amb_pinctrl_desc;
+ unsigned int pin;
+
+ /* dynamically populate the pin number and pin name for pindesc */
+ pindesc = devm_kcalloc(soc->dev, soc->npins, sizeof(*pindesc),
+ GFP_KERNEL);
+ if (!pindesc)
+ return -ENOMEM;
+
+ for (pin = 0; pin < soc->npins; pin++) {
+ pindesc[pin].number = pin;
+ pindesc[pin].name = devm_kasprintf(soc->dev, GFP_KERNEL,
+ "io%u", pin);
+ if (!pindesc[pin].name)
+ return -ENOMEM;
+ }
+
+ amb_pinctrl_desc = devm_kzalloc(soc->dev, sizeof(*amb_pinctrl_desc), GFP_KERNEL);
+ if (!amb_pinctrl_desc)
+ return -ENOMEM;
+
+ amb_pinctrl_desc->name = dev_name(soc->dev);
+ amb_pinctrl_desc->pins = pindesc;
+ amb_pinctrl_desc->npins = soc->npins;
+ amb_pinctrl_desc->pctlops = &amb_pctrl_ops;
+ amb_pinctrl_desc->pmxops = &amb_pinmux_ops;
+ amb_pinctrl_desc->confops = &amb_pinconf_ops;
+ amb_pinctrl_desc->owner = THIS_MODULE;
+
+ soc->pctl = devm_pinctrl_register(soc->dev, amb_pinctrl_desc, soc);
+ if (IS_ERR(soc->pctl)) {
+ dev_err(soc->dev, "could not register pinctrl driver\n");
+ return PTR_ERR(soc->pctl);
+ }
+
+ return 0;
+}
+
+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));
+
+ 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] 13+ messages in thread
* [PATCH v4 4/4] pinctrl: ambarella: add CV75 pin controller
@ 2026-08-14 6:25 ` Long Zhao
0 siblings, 0 replies; 13+ messages in thread
From: Long Zhao @ 2026-08-14 6:25 UTC (permalink / raw)
To: Linus Walleij, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Bartosz Golaszewski
Cc: linux-gpio, devicetree, linux-arm-kernel, linux-kernel, Long Zhao
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 | 13 +
drivers/pinctrl/Makefile | 2 +
drivers/pinctrl/pinctrl-ambarella-cv75.c | 561 +++++++++++++++++++++
drivers/pinctrl/pinctrl-ambarella.c | 839 +++++++++++++++++++++++++++++++
drivers/pinctrl/pinctrl-ambarella.h | 50 ++
5 files changed, 1465 insertions(+)
diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig
index c2cdd7b2c49b..119acde60a93 100644
--- a/drivers/pinctrl/Kconfig
+++ b/drivers/pinctrl/Kconfig
@@ -37,6 +37,19 @@ config DEBUG_PINCTRL
help
Say Y here to add some extra checks and diagnostics to PINCTRL calls.
+config PINCTRL_AMB
+ bool "Ambarella pin controller support"
+ depends on ARCH_AMBARELLA || COMPILE_TEST
+ select GENERIC_PINCONF
+ select GPIOLIB
+ select GPIOLIB_IRQCHIP
+ select 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..fb79a8b02209
--- /dev/null
+++ b/drivers/pinctrl/pinctrl-ambarella.c
@@ -0,0 +1,839 @@
+// 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 &= (~(0x1 << offset));
+ data |= (((altfunc >> i) & 0x1) << offset);
+ writel_relaxed(data, soc->iomux_base + IOMUX_OFFSET(bank, i));
+ }
+
+ amb_iomux_commit(soc);
+}
+
+/* enable a specified pinmux by writing to registers */
+static int amb_pinmux_set_mux(struct pinctrl_dev *pctldev,
+ unsigned int selector, unsigned int group)
+{
+ struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+ const struct ambpin_group *grp;
+ u32 i, pin, alt, bank, offset;
+ unsigned long flags;
+
+ grp = &soc->groups[group];
+
+ raw_spin_lock_irqsave(&soc->lock, flags);
+ for (i = 0; i < grp->num_pins; i++) {
+ pin = AMBA_PINMUX_TO_PIN(grp->pinmux[i]);
+ alt = AMBA_PINMUX_TO_ALT(grp->pinmux[i]);
+ bank = PINID_TO_BANK(pin);
+ offset = PINID_TO_OFFSET(pin);
+ amb_pinmux_set_altfunc(soc, bank, offset, alt);
+ }
+ raw_spin_unlock_irqrestore(&soc->lock, flags);
+
+ return 0;
+}
+
+static int amb_pinmux_gpio_request_enable(struct pinctrl_dev *pctldev,
+ struct pinctrl_gpio_range *range,
+ unsigned int pin)
+{
+ struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+ u32 bank, offset;
+ unsigned long flags;
+
+ if (!range || !range->gc) {
+ dev_err(soc->dev, "invalid range: %p\n", range);
+ return -EINVAL;
+ }
+
+ if (test_and_set_bit(pin, soc->used))
+ return -EBUSY;
+
+ bank = PINID_TO_BANK(pin);
+ offset = PINID_TO_OFFSET(pin);
+
+ raw_spin_lock_irqsave(&soc->lock, flags);
+ amb_pinmux_set_altfunc(soc, bank, offset, 0);
+ raw_spin_unlock_irqrestore(&soc->lock, flags);
+
+ return 0;
+}
+
+static void amb_pinmux_gpio_disable_free(struct pinctrl_dev *pctldev,
+ struct pinctrl_gpio_range *range,
+ unsigned int pin)
+{
+ struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+
+ dev_dbg(soc->dev, "disable pin %u as GPIO\n", pin);
+ /* Set the pin to some default state, GPIO is usually default */
+
+ clear_bit(pin, soc->used);
+}
+
+/* list of pinmux callbacks for the pinmux vertical in pinctrl core */
+static const struct pinmux_ops amb_pinmux_ops = {
+ .request = amb_pinmux_request,
+ .free = amb_pinmux_free,
+ .get_functions_count = amb_pinmux_get_fcount,
+ .get_function_name = amb_pinmux_get_fname,
+ .get_function_groups = amb_pinmux_get_groups,
+ .set_mux = amb_pinmux_set_mux,
+ .gpio_request_enable = amb_pinmux_gpio_request_enable,
+ .gpio_disable_free = amb_pinmux_gpio_disable_free,
+};
+
+static int amb_drive_strength_to_reg(struct amb_pinctrl_soc_data *soc,
+ u32 strength)
+{
+ if (soc->data->have_ds2) {
+ switch (strength) {
+ case 3:
+ return 0;
+ case 4:
+ case 5:
+ return 1;
+ case 6:
+ return 2;
+ case 7:
+ case 8:
+ return 3;
+ case 9:
+ return 4;
+ case 12:
+ return 5;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ switch (strength) {
+ case 2:
+ return 0;
+ case 4:
+ return 1;
+ case 8:
+ return 2;
+ case 12:
+ return 3;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int amb_reg_to_drive_strength(struct amb_pinctrl_soc_data *soc, u32 ds)
+{
+ static const int ds2_ma[] = { 3, 4, 6, 8, 9, 12 };
+ static const int ds_ma[] = { 2, 4, 8, 12 };
+
+ if (soc->data->have_ds2) {
+ if (ds >= ARRAY_SIZE(ds2_ma))
+ return -EINVAL;
+
+ return ds2_ma[ds];
+ }
+
+ if (ds >= ARRAY_SIZE(ds_ma))
+ return -EINVAL;
+
+ return ds_ma[ds];
+}
+
+/* set the pin config settings for a specified pin */
+static int amb_pinconf_set(struct pinctrl_dev *pctldev, unsigned int pin,
+ unsigned long *configs, unsigned int num_configs)
+{
+ struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+ 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, ds;
+ u32 bank, offset;
+ int strength;
+
+ bank = PINID_TO_BANK(pin);
+ if (bank >= soc->data->nr_banks) {
+ seq_puts(s, " (no pinconf)");
+ return;
+ }
+
+ offset = PINID_TO_OFFSET(pin);
+
+ regmap_read(soc->pull_regmap, soc->data->pull_en[bank], &pull_en);
+ pull_en = (pull_en >> offset) & 1;
+ regmap_read(soc->pull_regmap, soc->data->pull_dir[bank], &pull_dir);
+ pull_dir = (pull_dir >> offset) & 1;
+ seq_printf(s, " pull: %s,",
+ pull_en ? (pull_dir ? "up" : "down") : "disable");
+
+ regmap_read(soc->ds_regmap, soc->data->ds0[bank], &ds0);
+ ds0 = (ds0 >> offset) & 1;
+ regmap_read(soc->ds_regmap, soc->data->ds1[bank], &ds1);
+ ds1 = (ds1 >> offset) & 1;
+ if (soc->data->have_ds2) {
+ regmap_read(soc->ds_regmap, soc->data->ds2[bank], &ds2);
+ ds2 = (ds2 >> offset) & 1;
+ ds = (ds2 << 2) | (ds1 << 1) | ds0;
+ } else {
+ ds = (ds0 << 1) | ds1;
+ }
+
+ strength = amb_reg_to_drive_strength(soc, ds);
+ if (strength < 0)
+ seq_puts(s, " drive-strength: invalid");
+ else
+ seq_printf(s, " drive-strength: %dmA", strength);
+}
+#endif
+
+/* list of pinconfig callbacks for pinconfig vertical in the pinctrl code */
+static const struct pinconf_ops amb_pinconf_ops = {
+ .is_generic = true,
+ .pin_config_get = amb_pinconf_get,
+ .pin_config_set = amb_pinconf_set,
+ .pin_config_group_set = amb_pinconf_group_set,
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+ .pin_config_dbg_show = amb_pinconf_dbg_show,
+#endif
+};
+
+/* register the pinctrl interface with the pinctrl subsystem */
+static int amb_pinctrl_register(struct amb_pinctrl_soc_data *soc)
+{
+ struct pinctrl_pin_desc *pindesc;
+ struct pinctrl_desc *amb_pinctrl_desc;
+ unsigned int pin;
+
+ /* dynamically populate the pin number and pin name for pindesc */
+ pindesc = devm_kcalloc(soc->dev, soc->npins, sizeof(*pindesc),
+ GFP_KERNEL);
+ if (!pindesc)
+ return -ENOMEM;
+
+ for (pin = 0; pin < soc->npins; pin++) {
+ pindesc[pin].number = pin;
+ pindesc[pin].name = devm_kasprintf(soc->dev, GFP_KERNEL,
+ "io%u", pin);
+ if (!pindesc[pin].name)
+ return -ENOMEM;
+ }
+
+ amb_pinctrl_desc = devm_kzalloc(soc->dev, sizeof(*amb_pinctrl_desc), GFP_KERNEL);
+ if (!amb_pinctrl_desc)
+ return -ENOMEM;
+
+ amb_pinctrl_desc->name = dev_name(soc->dev);
+ amb_pinctrl_desc->pins = pindesc;
+ amb_pinctrl_desc->npins = soc->npins;
+ amb_pinctrl_desc->pctlops = &amb_pctrl_ops;
+ amb_pinctrl_desc->pmxops = &amb_pinmux_ops;
+ amb_pinctrl_desc->confops = &amb_pinconf_ops;
+ amb_pinctrl_desc->owner = THIS_MODULE;
+
+ soc->pctl = devm_pinctrl_register(soc->dev, amb_pinctrl_desc, soc);
+ if (IS_ERR(soc->pctl)) {
+ dev_err(soc->dev, "could not register pinctrl driver\n");
+ return PTR_ERR(soc->pctl);
+ }
+
+ return 0;
+}
+
+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));
+
+ 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] 13+ messages in thread
* Re: [PATCH v4 3/4] gpio: pl061: add Ambarella CV75 register layout variant
2026-08-14 6:25 ` Long Zhao
(?)
@ 2026-08-14 6:35 ` sashiko-bot
-1 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-08-14 6:35 UTC (permalink / raw)
To: Long Zhao; +Cc: robh, conor+dt, devicetree
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Global replacement of strictly ordered MMIO accessors with relaxed variants strips required memory barriers, risking device synchronization and IRQ storms.
- [High] Unconditional clearing of the Alternate Function Select (`afsel`) register in probe forces all pins to GPIO mode, breaking bootloader-configured peripherals.
--
--- Patch [3]: [PATCH v4 3/4] gpio: pl061: add Ambarella CV75 register layout variant ---
commit 2ccca896232c8172aca5451ae64d6ee831bac07f
Author: Long Zhao <longzhao@ambarella.com>
gpio: pl061: add Ambarella CV75 register layout variant
This commit extends the gpio-pl061 driver with a per-variant register layout
to support the Ambarella CV75 GPIO banks. It introduces wrapper functions for
read/write operations and variant-specific data structures.
> diff --git a/drivers/gpio/gpio-pl061.c b/drivers/gpio/gpio-pl061.c
> index 919cf86fd590f..d9e5a9f25681c 100644
> --- a/drivers/gpio/gpio-pl061.c
> +++ b/drivers/gpio/gpio-pl061.c
[ ... ]
> @@ -26,41 +26,94 @@
[ ... ]
> +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);
> +}
[Severity: High]
Is it safe to replace the strictly ordered MMIO accessors with relaxed
variants globally in these wrappers?
By dropping the implicit memory barriers previously provided by readb() and
writeb(), could this lead to out-of-order MMIO operations and system lockups?
For example, in pl061_irq_ack(), clearing the interrupt uses this new relaxed
wrapper. Because writeb_relaxed() lacks the wmb() barrier provided by writeb(),
could the write clearing the peripheral interrupt remain in the CPU store buffer
when the IRQ handler completes and unmasks the parent IRQ? This might cause
the CPU to instantly re-take the uncleared interrupt, resulting in an IRQ
storm.
Similarly, could bit-banging operations using GPIO be reordered with respect to
DMA memory stores, breaking device data transfers?
[ ... ]
> @@ -335,7 +402,14 @@ 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->afsel != PL061_REG_NONE)
> + pl061_write(pl061, 0, pl061->variant->afsel);
[Severity: High]
Does writing 0 to the entire afsel (Alternate Function Select) register
unconditionally during pl061_probe() reset the multiplexing for all pins
on the bank back to GPIO mode?
Could this forcibly disconnect any active peripherals (like UART, SPI, I2C)
that were already configured by the bootloader? If the system console UART
shares this GPIO bank, it seems like the system would immediately lose serial
output on boot.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260814-cv75-v4-b-v4-0-ef2e83ec0eac@ambarella.com?part=3
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v4 1/4] dt-bindings: pinctrl: add Ambarella CV75 pinctrl
2026-08-14 6:25 ` Long Zhao
(?)
@ 2026-08-14 6:35 ` sashiko-bot
-1 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-08-14 6:35 UTC (permalink / raw)
To: Long Zhao; +Cc: conor+dt, robh, devicetree
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The binding schema prevents configuring bias and drive strength for GPIO pins because 'function' is strictly required (with no 'gpio' option), the 'groups' enum lacks GPIO options, and the 'pins' property is forbidden.
--
--- Patch [1]: [PATCH v4 1/4] dt-bindings: pinctrl: add Ambarella CV75 pinctrl ---
commit a8e38fe4533be470b102e4e747568db3d0143b1a
Author: Long Zhao <longzhao@ambarella.com>
dt-bindings: pinctrl: add Ambarella CV75 pinctrl
Document the CV75 pin controller binding using generic function and
groups properties for pinmux configuration.
> diff --git a/Documentation/devicetree/bindings/pinctrl/ambarella,cv75-pinctrl.yaml b/Documentation/devicetree/bindings/pinctrl/ambarella,cv75-pinctrl.yaml
> new file mode 100644
> index 0000000000000..9d65da32d3d28
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/pinctrl/ambarella,cv75-pinctrl.yaml
[ ... ]
> +patternProperties:
> + "^[a-z0-9-]+-pins$":
> + type: object
> + allOf:
> + - $ref: pinmux-node.yaml#
> + - $ref: pincfg-node.yaml#
> + additionalProperties: false
[Severity: Medium]
Will this combination of 'additionalProperties: false' and omitting a
definition for 'pins' prevent configuring individual pins?
This appears to reject the standard 'pins' property, meaning users cannot
specify pin-level configurations for GPIOs.
> +
> + properties:
> + function:
[ ... ]
> + drive-strength:
> + enum: [3, 4, 5, 6, 7, 8, 9, 12]
> +
> + required:
> + - function
> + - groups
[Severity: Medium]
Does requiring both 'function' and 'groups' unconditionally prevent
config-only nodes, such as configuring bias or drive strength for individual
GPIO pins?
Since 'gpio' is not an option in the 'function' enum, and 'pins' is blocked
by the earlier 'additionalProperties: false', it seems impossible to define a
valid node for GPIO configuration without incorrectly multiplexing the pin to a
peripheral function.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260814-cv75-v4-b-v4-0-ef2e83ec0eac@ambarella.com?part=1
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v4 4/4] pinctrl: ambarella: add CV75 pin controller
2026-08-14 6:25 ` Long Zhao
(?)
@ 2026-08-14 6:40 ` sashiko-bot
-1 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-08-14 6:40 UTC (permalink / raw)
To: Long Zhao; +Cc: conor+dt, robh, devicetree
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] PINCTRL_AMB depends on an undefined Kconfig symbol (ARCH_AMBARELLA) and lacks HAS_IOMEM dependency.
- [High] Atomic IOMUX commit mechanism is defeated by executing commits per-pin, exposing partial hardware states.
- [Medium] Kernel stack information leak via uninitialized stack variables in amb_pinconf_dbg_show().
--
--- Patch [4]: [PATCH v4 4/4] pinctrl: ambarella: add CV75 pin controller ---
commit 839ad92b1e2074392112c76fcb7c264408262830
Author: Long Zhao <longzhao@ambarella.com>
pinctrl: ambarella: add CV75 pin controller
Add an Ambarella pinmux-only pinctrl driver with CV75 function/group
tables. GPIO is handled by the PL061 driver.
> diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig
> index eda54aa5fde6f..8436815bf816c 100644
> --- a/drivers/pinctrl/Kconfig
> +++ b/drivers/pinctrl/Kconfig
> @@ -37,6 +37,19 @@ 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
[Severity: High]
Does ARCH_AMBARELLA exist in the Kconfig tree? If not, this driver can only
be enabled when COMPILE_TEST is set, making it inaccessible to production
configurations.
Also, since this driver selects GPIO_PL061 and MFD_SYSCON and uses IOMEM ops,
would it cause unmet dependency warnings and build failures on architectures
lacking IOMEM (like UML) under COMPILE_TEST without a HAS_IOMEM dependency?
> + select GENERIC_PINCONF
[ ... ]
> diff --git a/drivers/pinctrl/pinctrl-ambarella.c b/drivers/pinctrl/pinctrl-ambarella.c
> new file mode 100644
> index 0000000000000..fb79a8b02209a
> --- /dev/null
> +++ b/drivers/pinctrl/pinctrl-ambarella.c
[ ... ]
> +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 &= (~(0x1 << offset));
> + data |= (((altfunc >> i) & 0x1) << offset);
> + writel_relaxed(data, soc->iomux_base + IOMUX_OFFSET(bank, i));
> + }
> +
> + amb_iomux_commit(soc);
[Severity: High]
Does calling amb_iomux_commit() here defeat the hardware's atomic commit
mechanism when configuring a group of pins?
Looking at amb_pinmux_set_mux(), it iteratively configures each pin:
static int amb_pinmux_set_mux(...) {
...
for (i = 0; i < grp->num_pins; i++) {
...
amb_pinmux_set_altfunc(soc, bank, offset, alt);
}
}
Committing after each individual pin exposes intermediate electrical states
for multi-pin interfaces (like Ethernet RGMII), which could cause bus
contention or interface glitching. Would it be safer to commit once after
all pins in the group are configured?
> +}
[ ... ]
> +#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, ds;
> + u32 bank, offset;
> + int strength;
> +
> + bank = PINID_TO_BANK(pin);
> + if (bank >= soc->data->nr_banks) {
> + seq_puts(s, " (no pinconf)");
> + return;
> + }
> +
> + offset = PINID_TO_OFFSET(pin);
> +
> + regmap_read(soc->pull_regmap, soc->data->pull_en[bank], &pull_en);
> + pull_en = (pull_en >> offset) & 1;
[Severity: Medium]
If regmap_read() fails (e.g. due to a syscon read error), won't pull_en
and other variables remain uninitialized?
The uninitialized stack values are then masked and later printed via
seq_printf(), which leaks kernel stack memory to userspace. Should the
return values of these regmap_read() calls be checked before using the
variables?
> + regmap_read(soc->pull_regmap, soc->data->pull_dir[bank], &pull_dir);
> + pull_dir = (pull_dir >> offset) & 1;
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260814-cv75-v4-b-v4-0-ef2e83ec0eac@ambarella.com?part=4
^ permalink raw reply [flat|nested] 13+ messages in thread
end of thread, other threads:[~2026-08-14 6:40 UTC | newest]
Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-14 6:25 [PATCH v4 0/4] Ambarella CV75: pinctrl and PL061 GPIO Long Zhao via B4 Relay
2026-08-14 6:25 ` Long Zhao
2026-08-14 6:25 ` [PATCH v4 1/4] dt-bindings: pinctrl: add Ambarella CV75 pinctrl Long Zhao via B4 Relay
2026-08-14 6:25 ` Long Zhao
2026-08-14 6:35 ` sashiko-bot
2026-08-14 6:25 ` [PATCH v4 2/4] dt-bindings: gpio: pl061: add Ambarella CV75 variant Long Zhao via B4 Relay
2026-08-14 6:25 ` Long Zhao
2026-08-14 6:25 ` [PATCH v4 3/4] gpio: pl061: add Ambarella CV75 register layout variant Long Zhao via B4 Relay
2026-08-14 6:25 ` Long Zhao
2026-08-14 6:35 ` sashiko-bot
2026-08-14 6:25 ` [PATCH v4 4/4] pinctrl: ambarella: add CV75 pin controller Long Zhao via B4 Relay
2026-08-14 6:25 ` Long Zhao
2026-08-14 6:40 ` sashiko-bot
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.