* [PATCH v3 2/3] gpio: pl061: add Ambarella CV75 register layout variant
2026-08-13 10:19 [PATCH v3 0/3] Ambarella CV75: pinctrl and PL061 GPIO Long Zhao via B4 Relay
2026-08-13 10:19 ` [PATCH v3 1/3] dt-bindings: pinctrl: add Ambarella CV75 pinctrl Long Zhao via B4 Relay
@ 2026-08-13 10:19 ` Long Zhao via B4 Relay
2026-08-13 10:30 ` sashiko-bot
2026-08-13 10:19 ` [PATCH v3 3/3] pinctrl: ambarella: add CV75 pin controller Long Zhao via B4 Relay
2 siblings, 1 reply; 7+ messages in thread
From: Long Zhao via B4 Relay @ 2026-08-13 10:19 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>
---
.../devicetree/bindings/gpio/pl061-gpio.yaml | 35 ++-
drivers/gpio/gpio-pl061.c | 280 ++++++++++++++++-----
2 files changed, 244 insertions(+), 71 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
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] 7+ messages in thread* [PATCH v3 3/3] pinctrl: ambarella: add CV75 pin controller
2026-08-13 10:19 [PATCH v3 0/3] Ambarella CV75: pinctrl and PL061 GPIO Long Zhao via B4 Relay
2026-08-13 10:19 ` [PATCH v3 1/3] dt-bindings: pinctrl: add Ambarella CV75 pinctrl Long Zhao via B4 Relay
2026-08-13 10:19 ` [PATCH v3 2/3] gpio: pl061: add Ambarella CV75 register layout variant Long Zhao via B4 Relay
@ 2026-08-13 10:19 ` Long Zhao via B4 Relay
2026-08-13 10:31 ` sashiko-bot
2 siblings, 1 reply; 7+ messages in thread
From: Long Zhao via B4 Relay @ 2026-08-13 10:19 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] 7+ messages in thread