[PATCH v2 07/10] pinctrl: ambarella: add Ambarella pin controller

Long Zhao via B4 Relay <[email protected]>
Newsgroups dev.linux.lists.soc,org.infradead.lists.linux-arm-kernel,org.kernel.feeds.b4-sent,org.kernel.vger.linux-clk,org.kernel.vger.linux-devicetree,org.kernel.vger.linux-gpio,org.kernel.vger.linux-kernel,org.kernel.vger.linux-serial
Message-ID <20260806-longzhao-upstream-cv75-v2-v2-7-6b09707c5fe9@ambarella.com>
From: Long Zhao <[email protected]>

Add the Ambarella pinmux/GPIO controller driver used by CV75.

Signed-off-by: Long Zhao <[email protected]>
---
 drivers/pinctrl/Kconfig                  |   15 +
 drivers/pinctrl/Makefile                 |    2 +
 drivers/pinctrl/pinctrl-ambarella-cv75.c |   68 ++
 drivers/pinctrl/pinctrl-ambarella.c      | 1510 ++++++++++++++++++++++++++++++
 drivers/pinctrl/pinctrl-ambarella.h      |   48 +
 5 files changed, 1643 insertions(+)

diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig
index c2cdd7b2c49b..572a4a898a99 100644
--- a/drivers/pinctrl/Kconfig
+++ b/drivers/pinctrl/Kconfig
@@ -37,6 +37,21 @@ config DEBUG_PINCTRL
 	help
 	  Say Y here to add some extra checks and diagnostics to PINCTRL calls.
 
+config PINCTRL_AMB
+	bool "Ambarella pin controller support"
+	depends on ARCH_AMBARELLA || COMPILE_TEST
+	select GENERIC_PINCONF
+	select GPIOLIB
+	select GPIOLIB_IRQCHIP
+	select MFD_SYSCON
+	select PINCONF
+	select PINMUX
+	help
+	  Say Y here to enable the pin controller found on Ambarella SoCs.
+	  The driver provides pin multiplexing and pin configuration as well
+	  as GPIO and GPIO interrupt support. It is required to configure
+	  peripheral pins on systems using an Ambarella SoC.
+
 config PINCTRL_AMD
 	bool "AMD GPIO pin control"
 	depends on HAS_IOMEM
diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile
index a35d71135abf..ce0825f4fc1b 100644
--- a/drivers/pinctrl/Makefile
+++ b/drivers/pinctrl/Makefile
@@ -10,6 +10,8 @@ obj-$(CONFIG_GENERIC_PINCONF)	+= pinconf-generic.o
 obj-$(CONFIG_GENERIC_PINCTRL)	+= pinctrl-generic.o
 obj-$(CONFIG_OF)		+= devicetree.o
 
+obj-$(CONFIG_PINCTRL_AMB)	+= pinctrl-ambarella.o
+obj-$(CONFIG_PINCTRL_AMB)	+= pinctrl-ambarella-cv75.o
 obj-$(CONFIG_PINCTRL_AMD)	+= pinctrl-amd.o
 obj-$(CONFIG_PINCTRL_AMDISP)	+= pinctrl-amdisp.o
 obj-$(CONFIG_PINCTRL_APPLE_GPIO) += pinctrl-apple-gpio.o
diff --git a/drivers/pinctrl/pinctrl-ambarella-cv75.c b/drivers/pinctrl/pinctrl-ambarella-cv75.c
new file mode 100644
index 000000000000..af4641bafebb
--- /dev/null
+++ b/drivers/pinctrl/pinctrl-ambarella-cv75.c
@@ -0,0 +1,68 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Ambarella CV75 pinctrl data
+ *
+ * Copyright (C) 2026, Ambarella, Inc.
+ */
+
+#include <linux/kernel.h>
+
+#include "pinctrl-ambarella.h"
+
+static const u32 cv75_uart0tx_pinmux[] = {
+	AMBA_PINMUX(44, 1),
+};
+
+static const u32 cv75_uart0rx_pinmux[] = {
+	AMBA_PINMUX(45, 1),
+};
+
+static const struct ambpin_group_desc cv75_pin_groups[] = {
+	{
+		.name = "uart0tx",
+		.pinmux = cv75_uart0tx_pinmux,
+		.num_pins = ARRAY_SIZE(cv75_uart0tx_pinmux),
+	},
+	{
+		.name = "uart0rx",
+		.pinmux = cv75_uart0rx_pinmux,
+		.num_pins = ARRAY_SIZE(cv75_uart0rx_pinmux),
+	},
+};
+
+static const char * const cv75_uart0_groups[] = {
+	"uart0tx",
+	"uart0rx",
+};
+
+static const struct ambpin_function cv75_pin_functions[] = {
+	{
+		.name = "uart0",
+		.groups = cv75_uart0_groups,
+		.num_groups = ARRAY_SIZE(cv75_uart0_groups),
+	},
+};
+
+const struct amb_pinctrl_data ambarella_cv75_pinctrl_data = {
+	.ds0 = {
+		0x314, 0x320, 0x32c,
+	},
+	.ds1 = {
+		0x318, 0x324, 0x330,
+	},
+	.ds2 = {
+		0x31c, 0x328, 0x334,
+	},
+	.pull_en = {
+		0x60, 0x64, 0x68,
+	},
+	.pull_dir = {
+		0x7c, 0x80, 0x84,
+	},
+	.have_ds2 = true,
+	.clk_au_dedicated_pin = 96,
+	.groups = cv75_pin_groups,
+	.nr_groups = ARRAY_SIZE(cv75_pin_groups),
+	.functions = cv75_pin_functions,
+	.nr_functions = ARRAY_SIZE(cv75_pin_functions),
+};
diff --git a/drivers/pinctrl/pinctrl-ambarella.c b/drivers/pinctrl/pinctrl-ambarella.c
new file mode 100644
index 000000000000..bb79e06c4160
--- /dev/null
+++ b/drivers/pinctrl/pinctrl-ambarella.c
@@ -0,0 +1,1510 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Pinctrl driver for Ambarella SoCs
+ *
+ * History:
+ *	2013/12/18 - [Cao Rongrong] created file
+ *
+ * Copyright (C) 2012-2026, Ambarella, Inc.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/bitmap.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/of_irq.h>
+#include <linux/pm.h>
+#include <linux/slab.h>
+#include <linux/regmap.h>
+#include <linux/mfd/syscon.h>
+#include <linux/irqdomain.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/gpio/driver.h>
+#include <linux/pinctrl/pinctrl.h>
+#include <linux/pinctrl/pinmux.h>
+#include <linux/pinctrl/pinconf.h>
+#include <linux/pinctrl/pinconf-generic.h>
+#include <linux/seq_file.h>
+
+#include "pinconf.h"
+#include "pinctrl-ambarella.h"
+
+/* ==========================================================================*/
+
+#define GPIO_DATA_OFFSET		0x00
+#define GPIO_DIR_OFFSET			0x04
+#define GPIO_IS_OFFSET			0x08
+#define GPIO_IBE_OFFSET			0x0c
+#define GPIO_IEV_OFFSET			0x10
+#define GPIO_IE_OFFSET			0x14
+#define GPIO_AFSEL_OFFSET		0x18
+#define GPIO_RIS_OFFSET			0x1c
+#define GPIO_MIS_OFFSET			0x20
+#define GPIO_IC_OFFSET			0x24
+#define GPIO_MASK_OFFSET		0x28
+#define GPIO_ENABLE_OFFSET		0x2c
+
+#define IOMUX_OFFSET(bank, n)		(((bank) * 0xc) + ((n) * 4))
+#define IOMUX_CTRL_SET_OFFSET		0xf0
+
+/* ==========================================================================*/
+
+#define AMBA_MAX_PINS			(AMBA_MAX_BANKS * 32)
+
+#define PINID_TO_BANK(p)		((p) >> 5)
+#define PINID_TO_OFFSET(p)		((p) & 0x1f)
+
+struct amb_pinctrl_pm_state {
+	u32 iomux[3];
+	u32 pull[2];
+	u32 ds[3];
+	u32 data;
+	u32 dir;
+	u32 is;
+	u32 ibe;
+	u32 iev;
+	u32 ie;
+	u32 afsel;
+	u32 mask;
+};
+
+struct ambpin_group {
+	const char		*name;
+	const u32		*pinmux;
+	unsigned int		*pins;
+	unsigned int		num_pins;
+};
+
+struct amb_pinctrl_soc_data;
+
+struct amb_gpio_bank {
+	struct amb_pinctrl_soc_data	*soc;
+	void __iomem			*base;
+	struct gpio_chip		gc;
+	struct irq_domain		*domain;
+	unsigned int			pin_base;
+	unsigned int			hw_id;
+	unsigned int			irq_wake_mask;
+	int				irq;
+};
+
+struct amb_pinctrl_soc_data {
+	struct device			*dev;
+	const struct amb_pinctrl_data	*data;
+	struct amb_gpio_bank		banks[AMBA_MAX_BANKS];
+	void __iomem			*iomux_base;
+	struct regmap			*ds_regmap;
+	struct regmap			*pull_regmap;
+	unsigned int			bank_num;
+	unsigned int			npins;
+	unsigned long			used[BITS_TO_LONGS(AMBA_MAX_PINS)];
+	raw_spinlock_t lock;
+
+	struct pinctrl_dev		*pctl;
+
+	const struct ambpin_function	*functions;
+	unsigned int			nr_functions;
+	struct ambpin_group		*groups;
+	unsigned int			nr_groups;
+
+	struct amb_pinctrl_pm_state	pm[AMBA_MAX_BANKS];
+};
+
+static struct amb_gpio_bank *amb_irq_data_to_bank(struct irq_data *data)
+{
+	struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
+
+	return gpiochip_get_data(gc);
+}
+
+static struct amb_gpio_bank *
+amb_pin_to_bank(struct amb_pinctrl_soc_data *soc, unsigned int pin)
+{
+	unsigned int i;
+
+	for (i = 0; i < soc->bank_num; i++) {
+		struct amb_gpio_bank *bank = &soc->banks[i];
+
+		if (pin >= bank->pin_base &&
+		    pin < bank->pin_base + bank->gc.ngpio)
+			return bank;
+	}
+
+	return NULL;
+}
+
+/* Exclusive end of the GPIO pin number space (max pin_base + ngpio). */
+static unsigned int amb_gpio_pins_end(const struct amb_pinctrl_soc_data *soc)
+{
+	unsigned int end = 0, i;
+
+	for (i = 0; i < soc->bank_num; i++)
+		end = max(end, soc->banks[i].pin_base + soc->banks[i].gc.ngpio);
+
+	return end;
+}
+
+/* check if the selector is a valid pin group selector */
+static int amb_get_group_count(struct pinctrl_dev *pctldev)
+{
+	struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+
+	return soc->nr_groups;
+}
+
+/* return the name of the group selected by the group selector */
+static const char *amb_get_group_name(struct pinctrl_dev *pctldev,
+				      unsigned int selector)
+{
+	struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+
+	return soc->groups[selector].name;
+}
+
+/* return the pin numbers associated with the specified group */
+static int amb_get_group_pins(struct pinctrl_dev *pctldev,
+			      unsigned int selector, const unsigned int **pins,
+			      unsigned int *num_pins)
+{
+	struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+
+	*pins = soc->groups[selector].pins;
+	*num_pins = soc->groups[selector].num_pins;
+
+	return 0;
+}
+
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+static void amb_pin_dbg_show(struct pinctrl_dev *pctldev,
+			     struct seq_file *s, unsigned int pin)
+{
+	seq_printf(s, " %s", pinctrl_dev_get_devname(pctldev));
+}
+#endif
+
+/* list of pinctrl callbacks for the pinctrl core */
+static const struct pinctrl_ops amb_pctrl_ops = {
+	.get_groups_count	= amb_get_group_count,
+	.get_group_name		= amb_get_group_name,
+	.get_group_pins		= amb_get_group_pins,
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+	.pin_dbg_show		= amb_pin_dbg_show,
+#endif
+	.dt_node_to_map		= pinconf_generic_dt_node_to_map_all,
+	.dt_free_map		= pinconf_generic_dt_free_map,
+};
+
+/* check if the selector is a valid pin function selector */
+static int amb_pinmux_request(struct pinctrl_dev *pctldev, unsigned int pin)
+{
+	struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+
+	if (test_and_set_bit(pin, soc->used))
+		return -EBUSY;
+
+	return 0;
+}
+
+/* check if the selector is a valid pin function selector */
+static int amb_pinmux_free(struct pinctrl_dev *pctldev, unsigned int pin)
+{
+	struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+
+	clear_bit(pin, soc->used);
+
+	return 0;
+}
+
+/* check if the selector is a valid pin function selector */
+static int amb_pinmux_get_fcount(struct pinctrl_dev *pctldev)
+{
+	struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+
+	return soc->nr_functions;
+}
+
+/* return the name of the pin function specified */
+static const char *amb_pinmux_get_fname(struct pinctrl_dev *pctldev,
+					unsigned int selector)
+{
+	struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+
+	return soc->functions[selector].name;
+}
+
+/* return the groups associated for the specified function selector */
+static int amb_pinmux_get_groups(struct pinctrl_dev *pctldev,
+				 unsigned int selector,
+				 const char * const **groups,
+				 unsigned int * const num_groups)
+{
+	struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+
+	*groups = soc->functions[selector].groups;
+	*num_groups = soc->functions[selector].num_groups;
+
+	return 0;
+}
+
+static void amb_pinmux_set_altfunc(struct amb_pinctrl_soc_data *soc,
+				   u32 bank, u32 offset, u32 altfunc)
+{
+	u32 i, data;
+
+	/* On CV3 platform, only ARM cluster0 (safety domain) can access pinctrl registers */
+	if (soc->data->hsm_domain_id != 0)
+		return;
+
+	for (i = 0; i < 3; i++) {
+		data = readl_relaxed(soc->iomux_base + IOMUX_OFFSET(bank, i));
+		data &= (~(0x1 << offset));
+		data |= (((altfunc >> i) & 0x1) << offset);
+		writel_relaxed(data, soc->iomux_base + IOMUX_OFFSET(bank, i));
+	}
+
+	writel_relaxed(0x1, soc->iomux_base + IOMUX_CTRL_SET_OFFSET);
+	writel_relaxed(0x0, soc->iomux_base + IOMUX_CTRL_SET_OFFSET);
+}
+
+/* enable a specified pinmux by writing to registers */
+static int amb_pinmux_set_mux(struct pinctrl_dev *pctldev,
+			      unsigned int selector, unsigned int group)
+{
+	struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+	const struct ambpin_group *grp;
+	u32 i, pin, alt, bank, offset;
+	unsigned long flags;
+
+	grp = &soc->groups[group];
+
+	raw_spin_lock_irqsave(&soc->lock, flags);
+	for (i = 0; i < grp->num_pins; i++) {
+		pin = AMBA_PINMUX_TO_PIN(grp->pinmux[i]);
+		alt = AMBA_PINMUX_TO_ALT(grp->pinmux[i]);
+		bank = PINID_TO_BANK(pin);
+		offset = PINID_TO_OFFSET(pin);
+		amb_pinmux_set_altfunc(soc, bank, offset, alt);
+	}
+	raw_spin_unlock_irqrestore(&soc->lock, flags);
+
+	return 0;
+}
+
+static int amb_pinmux_gpio_request_enable(struct pinctrl_dev *pctldev,
+					  struct pinctrl_gpio_range *range,
+					  unsigned int pin)
+{
+	struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+	u32 bank, offset;
+	unsigned long flags;
+
+	if (!range || !range->gc) {
+		dev_err(soc->dev, "invalid range: %p\n", range);
+		return -EINVAL;
+	}
+
+	if (test_and_set_bit(pin, soc->used))
+		return -EBUSY;
+
+	bank = PINID_TO_BANK(pin);
+	offset = PINID_TO_OFFSET(pin);
+
+	raw_spin_lock_irqsave(&soc->lock, flags);
+	amb_pinmux_set_altfunc(soc, bank, offset, 0);
+	raw_spin_unlock_irqrestore(&soc->lock, flags);
+
+	return 0;
+}
+
+static void amb_pinmux_gpio_disable_free(struct pinctrl_dev *pctldev,
+					 struct pinctrl_gpio_range *range,
+					 unsigned int pin)
+{
+	struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+
+	dev_dbg(soc->dev, "disable pin %u as GPIO\n", pin);
+	/* Set the pin to some default state, GPIO is usually default */
+
+	clear_bit(pin, soc->used);
+}
+
+/* list of pinmux callbacks for the pinmux vertical in pinctrl core */
+static const struct pinmux_ops amb_pinmux_ops = {
+	.request		= amb_pinmux_request,
+	.free			= amb_pinmux_free,
+	.get_functions_count	= amb_pinmux_get_fcount,
+	.get_function_name	= amb_pinmux_get_fname,
+	.get_function_groups	= amb_pinmux_get_groups,
+	.set_mux                = amb_pinmux_set_mux,
+	.gpio_request_enable	= amb_pinmux_gpio_request_enable,
+	.gpio_disable_free	= amb_pinmux_gpio_disable_free,
+};
+
+static int amb_drive_strength_to_reg(struct amb_pinctrl_soc_data *soc,
+				     u32 strength)
+{
+	if (soc->data->have_ds2) {
+		switch (strength) {
+		case 3:
+			return 0;
+		case 4:
+		case 5:
+			return 1;
+		case 6:
+			return 2;
+		case 7:
+		case 8:
+			return 3;
+		case 9:
+			return 4;
+		case 12:
+			return 5;
+		default:
+			return -EINVAL;
+		}
+	}
+
+	switch (strength) {
+	case 2:
+		return 0;
+	case 4:
+		return 1;
+	case 8:
+		return 2;
+	case 12:
+		return 3;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int amb_reg_to_drive_strength(struct amb_pinctrl_soc_data *soc, u32 ds)
+{
+	static const int ds2_ma[] = { 3, 4, 6, 8, 9, 12 };
+	static const int ds_ma[] = { 2, 4, 8, 12 };
+
+	if (soc->data->have_ds2) {
+		if (ds >= ARRAY_SIZE(ds2_ma))
+			return -EINVAL;
+
+		return ds2_ma[ds];
+	}
+
+	if (ds >= ARRAY_SIZE(ds_ma))
+		return -EINVAL;
+
+	return ds_ma[ds];
+}
+
+/* set the pin config settings for a specified pin */
+static int amb_pinconf_set(struct pinctrl_dev *pctldev, unsigned int pin,
+			   unsigned long *configs, unsigned int num_configs)
+{
+	struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+	struct amb_gpio_bank *gpio_bank;
+	u32 i, bank, offset;
+	unsigned long config, flags;
+	enum pin_config_param param;
+	u32 arg;
+	int ds;
+
+	gpio_bank = amb_pin_to_bank(soc, pin);
+	if (!gpio_bank)
+		return -EINVAL;
+
+	bank = gpio_bank->hw_id;
+	offset = pin - gpio_bank->pin_base;
+
+	raw_spin_lock_irqsave(&soc->lock, flags);
+	for (i = 0; i < num_configs; i++) {
+		config = configs[i];
+		param = pinconf_to_config_param(config);
+		arg = pinconf_to_config_argument(config);
+
+		switch (param) {
+		case PIN_CONFIG_BIAS_DISABLE:
+			regmap_update_bits(soc->pull_regmap,
+					   soc->data->pull_en[bank], BIT(offset), 0);
+			break;
+		case PIN_CONFIG_BIAS_PULL_DOWN:
+		case PIN_CONFIG_BIAS_PULL_UP:
+			regmap_update_bits(soc->pull_regmap, soc->data->pull_dir[bank],
+					   BIT(offset),
+					   (param == PIN_CONFIG_BIAS_PULL_UP) ?
+					   BIT(offset) : 0);
+			regmap_update_bits(soc->pull_regmap, soc->data->pull_en[bank],
+					   BIT(offset), BIT(offset));
+			break;
+		case PIN_CONFIG_DRIVE_STRENGTH:
+			ds = amb_drive_strength_to_reg(soc, arg);
+			if (ds < 0) {
+				raw_spin_unlock_irqrestore(&soc->lock, flags);
+				return ds;
+			}
+			if (soc->data->have_ds2) {
+				regmap_update_bits(soc->ds_regmap,
+						   soc->data->ds0[bank], BIT(offset),
+						   (ds & BIT(0)) ? BIT(offset) : 0);
+				regmap_update_bits(soc->ds_regmap,
+						   soc->data->ds1[bank], BIT(offset),
+						   (ds & BIT(1)) ? BIT(offset) : 0);
+				regmap_update_bits(soc->ds_regmap,
+						   soc->data->ds2[bank], BIT(offset),
+						   (ds & BIT(2)) ? BIT(offset) : 0);
+			} else {
+				regmap_update_bits(soc->ds_regmap,
+						   soc->data->ds0[bank], BIT(offset),
+						   (ds & BIT(1)) ? BIT(offset) : 0);
+				regmap_update_bits(soc->ds_regmap,
+						   soc->data->ds1[bank], BIT(offset),
+						   (ds & BIT(0)) ? BIT(offset) : 0);
+			}
+			break;
+		default:
+			raw_spin_unlock_irqrestore(&soc->lock, flags);
+			return -EOPNOTSUPP;
+		}
+	}
+	raw_spin_unlock_irqrestore(&soc->lock, flags);
+
+	return 0;
+}
+
+static int amb_pinconf_group_set(struct pinctrl_dev *pctldev,
+				 unsigned int selector,
+				 unsigned long *configs,
+				 unsigned int num_configs)
+{
+	struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+	const struct ambpin_group *grp = &soc->groups[selector];
+	int ret;
+	u32 i;
+
+	for (i = 0; i < grp->num_pins; i++) {
+		ret = amb_pinconf_set(pctldev, grp->pins[i], configs,
+				      num_configs);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+/* get the pin config settings for a specified pin */
+static int amb_pinconf_get(struct pinctrl_dev *pctldev,
+			   unsigned int pin, unsigned long *config)
+{
+	struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+	struct amb_gpio_bank *gpio_bank;
+	enum pin_config_param param = pinconf_to_config_param(*config);
+	u32 bank, offset, pull_en, pull_dir, ds0, ds1, ds2, ds;
+	int ret, strength;
+
+	gpio_bank = amb_pin_to_bank(soc, pin);
+	if (!gpio_bank)
+		return -EINVAL;
+
+	bank = gpio_bank->hw_id;
+	offset = pin - gpio_bank->pin_base;
+
+	switch (param) {
+	case PIN_CONFIG_BIAS_DISABLE:
+	case PIN_CONFIG_BIAS_PULL_DOWN:
+	case PIN_CONFIG_BIAS_PULL_UP:
+		ret = regmap_read(soc->pull_regmap, soc->data->pull_en[bank],
+				  &pull_en);
+		if (ret)
+			return ret;
+
+		ret = regmap_read(soc->pull_regmap, soc->data->pull_dir[bank],
+				  &pull_dir);
+		if (ret)
+			return ret;
+
+		pull_en = (pull_en >> offset) & 1;
+		pull_dir = (pull_dir >> offset) & 1;
+
+		if (param == PIN_CONFIG_BIAS_DISABLE) {
+			if (pull_en)
+				return -EINVAL;
+			*config = pinconf_to_config_packed(param, 0);
+			return 0;
+		}
+
+		if (!pull_en)
+			return -EINVAL;
+		if (param == PIN_CONFIG_BIAS_PULL_UP && !pull_dir)
+			return -EINVAL;
+		if (param == PIN_CONFIG_BIAS_PULL_DOWN && pull_dir)
+			return -EINVAL;
+
+		*config = pinconf_to_config_packed(param, 1);
+		return 0;
+
+	case PIN_CONFIG_DRIVE_STRENGTH:
+		ret = regmap_read(soc->ds_regmap, soc->data->ds0[bank], &ds0);
+		if (ret)
+			return ret;
+
+		ret = regmap_read(soc->ds_regmap, soc->data->ds1[bank], &ds1);
+		if (ret)
+			return ret;
+
+		ds0 = (ds0 >> offset) & 1;
+		ds1 = (ds1 >> offset) & 1;
+		if (soc->data->have_ds2) {
+			ret = regmap_read(soc->ds_regmap, soc->data->ds2[bank], &ds2);
+			if (ret)
+				return ret;
+
+			ds2 = (ds2 >> offset) & 1;
+			ds = (ds2 << 2) | (ds1 << 1) | ds0;
+		} else {
+			ds = (ds0 << 1) | ds1;
+		}
+
+		strength = amb_reg_to_drive_strength(soc, ds);
+		if (strength < 0)
+			return strength;
+
+		*config = pinconf_to_config_packed(param, strength);
+		return 0;
+
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+static void amb_pinconf_dbg_show(struct pinctrl_dev *pctldev,
+				 struct seq_file *s, unsigned int pin)
+{
+	struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+	struct amb_gpio_bank *gpio_bank;
+	u32 pull_en, pull_dir, ds0, ds1, ds2, ds;
+	u32 bank, offset;
+	int strength;
+
+	gpio_bank = amb_pin_to_bank(soc, pin);
+	if (!gpio_bank) {
+		seq_puts(s, " (no pinconf)");
+		return;
+	}
+
+	bank = gpio_bank->hw_id;
+	offset = pin - gpio_bank->pin_base;
+
+	regmap_read(soc->pull_regmap, soc->data->pull_en[bank], &pull_en);
+	pull_en = (pull_en >> offset) & 1;
+	regmap_read(soc->pull_regmap, soc->data->pull_dir[bank], &pull_dir);
+	pull_dir = (pull_dir >> offset) & 1;
+	seq_printf(s, " pull: %s,",
+		   pull_en ? (pull_dir ? "up" : "down") : "disable");
+
+	regmap_read(soc->ds_regmap, soc->data->ds0[bank], &ds0);
+	ds0 = (ds0 >> offset) & 1;
+	regmap_read(soc->ds_regmap, soc->data->ds1[bank], &ds1);
+	ds1 = (ds1 >> offset) & 1;
+	if (soc->data->have_ds2) {
+		regmap_read(soc->ds_regmap, soc->data->ds2[bank], &ds2);
+		ds2 = (ds2 >> offset) & 1;
+		ds = (ds2 << 2) | (ds1 << 1) | ds0;
+	} else {
+		ds = (ds0 << 1) | ds1;
+	}
+
+	strength = amb_reg_to_drive_strength(soc, ds);
+	if (strength < 0)
+		seq_puts(s, " drive-strength: invalid");
+	else
+		seq_printf(s, " drive-strength: %dmA", strength);
+}
+#endif
+
+/* list of pinconfig callbacks for pinconfig vertical in the pinctrl code */
+static const struct pinconf_ops amb_pinconf_ops = {
+	.is_generic		= true,
+	.pin_config_get		= amb_pinconf_get,
+	.pin_config_set		= amb_pinconf_set,
+	.pin_config_group_set	= amb_pinconf_group_set,
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+	.pin_config_dbg_show	= amb_pinconf_dbg_show,
+#endif
+};
+
+/* register the pinctrl interface with the pinctrl subsystem */
+static int amb_pinctrl_register(struct amb_pinctrl_soc_data *soc)
+{
+	struct pinctrl_pin_desc *pindesc;
+	struct pinctrl_desc *amb_pinctrl_desc;
+	unsigned int pin;
+
+	/* dynamically populate the pin number and pin name for pindesc */
+	pindesc = devm_kcalloc(soc->dev, soc->npins, sizeof(*pindesc),
+			       GFP_KERNEL);
+	if (!pindesc)
+		return -ENOMEM;
+
+	for (pin = 0; pin < soc->npins; pin++) {
+		pindesc[pin].number = pin;
+		pindesc[pin].name = devm_kasprintf(soc->dev, GFP_KERNEL,
+						   "io%u", pin);
+		if (!pindesc[pin].name)
+			return -ENOMEM;
+	}
+
+	amb_pinctrl_desc = devm_kzalloc(soc->dev, sizeof(*amb_pinctrl_desc), GFP_KERNEL);
+	if (!amb_pinctrl_desc)
+		return -ENOMEM;
+
+	amb_pinctrl_desc->name = dev_name(soc->dev);
+	amb_pinctrl_desc->pins = pindesc;
+	amb_pinctrl_desc->npins = soc->npins;
+	amb_pinctrl_desc->pctlops = &amb_pctrl_ops;
+	amb_pinctrl_desc->pmxops = &amb_pinmux_ops;
+	amb_pinctrl_desc->confops = &amb_pinconf_ops;
+	amb_pinctrl_desc->owner = THIS_MODULE;
+
+	soc->pctl = devm_pinctrl_register(soc->dev, amb_pinctrl_desc, soc);
+	if (IS_ERR(soc->pctl)) {
+		dev_err(soc->dev, "could not register pinctrl driver\n");
+		return PTR_ERR(soc->pctl);
+	}
+
+	return 0;
+}
+
+/* gpiolib gpio_set callback function */
+static int amb_gpio_set(struct gpio_chip *gc, unsigned int pin, int value)
+{
+	struct amb_gpio_bank *bank = gpiochip_get_data(gc);
+	struct amb_pinctrl_soc_data *soc = bank->soc;
+	u32 data;
+	unsigned long flags;
+
+	raw_spin_lock_irqsave(&soc->lock, flags);
+	writel_relaxed(BIT(pin), bank->base + GPIO_MASK_OFFSET);
+	data = value ? BIT(pin) : 0;
+	writel_relaxed(data, bank->base + GPIO_DATA_OFFSET);
+	raw_spin_unlock_irqrestore(&soc->lock, flags);
+
+	return 0;
+}
+
+/* gpiolib gpio_get callback function */
+static int amb_gpio_get(struct gpio_chip *gc, unsigned int pin)
+{
+	struct amb_gpio_bank *bank = gpiochip_get_data(gc);
+	struct amb_pinctrl_soc_data *soc = bank->soc;
+	u32 data;
+	unsigned long flags;
+
+	raw_spin_lock_irqsave(&soc->lock, flags);
+	writel_relaxed(BIT(pin), bank->base + GPIO_MASK_OFFSET);
+	data = readl_relaxed(bank->base + GPIO_DATA_OFFSET);
+	raw_spin_unlock_irqrestore(&soc->lock, flags);
+
+	return !!(data & BIT(pin));
+}
+
+static int amb_gpio_get_direction(struct gpio_chip *gc, unsigned int pin)
+{
+	struct amb_gpio_bank *bank = gpiochip_get_data(gc);
+	struct amb_pinctrl_soc_data *soc = bank->soc;
+	u32 data;
+	unsigned long flags;
+
+	raw_spin_lock_irqsave(&soc->lock, flags);
+	data = readl_relaxed(bank->base + GPIO_DIR_OFFSET);
+	raw_spin_unlock_irqrestore(&soc->lock, flags);
+
+	return data & BIT(pin) ? GPIO_LINE_DIRECTION_OUT : GPIO_LINE_DIRECTION_IN;
+}
+
+static int amb_gpio_set_direction(struct gpio_chip *gc, unsigned int pin, bool input)
+{
+	struct amb_gpio_bank *bank = gpiochip_get_data(gc);
+	struct amb_pinctrl_soc_data *soc = bank->soc;
+	u32 data;
+	unsigned long flags;
+
+	raw_spin_lock_irqsave(&soc->lock, flags);
+	data = readl_relaxed(bank->base + GPIO_DIR_OFFSET);
+	if (input)
+		data &= ~BIT(pin);
+	else
+		data |= BIT(pin);
+	writel_relaxed(data, bank->base + GPIO_DIR_OFFSET);
+	raw_spin_unlock_irqrestore(&soc->lock, flags);
+
+	return 0;
+}
+
+/* gpiolib gpio_direction_input callback function */
+static int amb_gpio_direction_input(struct gpio_chip *gc, unsigned int pin)
+{
+	return amb_gpio_set_direction(gc, pin, true);
+}
+
+/* gpiolib gpio_direction_output callback function */
+static int amb_gpio_direction_output(struct gpio_chip *gc, unsigned int pin, int value)
+{
+	struct amb_gpio_bank *bank = gpiochip_get_data(gc);
+	struct amb_pinctrl_soc_data *soc = bank->soc;
+	unsigned long flags;
+	u32 data;
+
+	raw_spin_lock_irqsave(&soc->lock, flags);
+
+	writel_relaxed(BIT(pin), bank->base + GPIO_MASK_OFFSET);
+	writel_relaxed(value ? BIT(pin) : 0,
+		       bank->base + GPIO_DATA_OFFSET);
+
+	data = readl_relaxed(bank->base + GPIO_DIR_OFFSET);
+	writel_relaxed(data | BIT(pin), bank->base + GPIO_DIR_OFFSET);
+
+	raw_spin_unlock_irqrestore(&soc->lock, flags);
+
+	return 0;
+}
+
+/* gpiolib gpio_to_irq callback function */
+static int amb_gpio_to_irq(struct gpio_chip *gc, unsigned int pin)
+{
+	struct amb_gpio_bank *bank = gpiochip_get_data(gc);
+
+	return irq_create_mapping(bank->domain, pin);
+}
+
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+static void amb_gpio_dbg_show(struct seq_file *s, struct gpio_chip *gc)
+{
+	struct amb_gpio_bank *bank = gpiochip_get_data(gc);
+	struct amb_pinctrl_soc_data *soc = bank->soc;
+	u32 afsel, data, dir, mask, iomux0, iomux1, iomux2, alt;
+	unsigned long flags;
+	u32 i;
+
+	raw_spin_lock_irqsave(&soc->lock, flags);
+	afsel = readl_relaxed(bank->base + GPIO_AFSEL_OFFSET);
+	dir = readl_relaxed(bank->base + GPIO_DIR_OFFSET);
+	mask = readl_relaxed(bank->base + GPIO_MASK_OFFSET);
+	writel_relaxed(0xffffffff, bank->base + GPIO_MASK_OFFSET);
+	data = readl_relaxed(bank->base + GPIO_DATA_OFFSET);
+	writel_relaxed(mask, bank->base + GPIO_MASK_OFFSET);
+
+	iomux0 = readl_relaxed(soc->iomux_base + IOMUX_OFFSET(bank->hw_id, 0));
+	iomux1 = readl_relaxed(soc->iomux_base + IOMUX_OFFSET(bank->hw_id, 1));
+	iomux2 = readl_relaxed(soc->iomux_base + IOMUX_OFFSET(bank->hw_id, 2));
+	raw_spin_unlock_irqrestore(&soc->lock, flags);
+
+	seq_printf(s, "\nGPIO[%d]:\n", bank->hw_id);
+	seq_printf(s, "GPIO_AFSEL:\t0x%08X\n", afsel);
+	seq_printf(s, "GPIO_DIR:\t0x%08X\n", dir);
+	seq_printf(s, "GPIO_MASK:\t0x%08X\n", mask);
+	seq_printf(s, "GPIO_DATA:\t0x%08X\n", data);
+	seq_printf(s, "IOMUX_REG%d_0:\t0x%08X\n", bank->hw_id, iomux0);
+	seq_printf(s, "IOMUX_REG%d_1:\t0x%08X\n", bank->hw_id, iomux1);
+	seq_printf(s, "IOMUX_REG%d_2:\t0x%08X\n", bank->hw_id, iomux2);
+
+	for (i = 0; i < gc->ngpio; i++) {
+
+		seq_printf(s, " gpio-%-3d", gc->base + i);
+
+		alt = ((iomux2 >> i) & 1) << 2;
+		alt |= ((iomux1 >> i) & 1) << 1;
+		alt |= ((iomux0 >> i) & 1) << 0;
+		if (alt) {
+			seq_printf(s, " [HW  ] (alt%d)\n", alt);
+		} else {
+			char *label __free(kfree) = gpiochip_dup_line_label(gc, i);
+			if (IS_ERR(label)) {
+				pr_debug("Failed to duplicate label\n");
+				continue;
+			}
+
+			seq_printf(s, " [GPIO] (%-20.20s) %s %s\n",
+				   label ? label : "",
+				   (dir & BIT(i)) ? "out" : "in ",
+				   (data & BIT(i)) ? "hi" : "lo");
+		}
+	}
+}
+#endif
+
+static void amb_gpio_irq_enable(struct irq_data *data)
+{
+	struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
+	struct amb_gpio_bank *bank = amb_irq_data_to_bank(data);
+	struct amb_pinctrl_soc_data *soc = bank->soc;
+	void __iomem *gpio_base = bank->base;
+	void __iomem *iomux_base = soc->iomux_base;
+	u32 i, val, offset;
+	unsigned long flags;
+
+	offset = irqd_to_hwirq(data);
+
+	gpiochip_enable_irq(gc, offset);
+
+	raw_spin_lock_irqsave(&soc->lock, flags);
+
+	val = readl_relaxed(gpio_base + GPIO_DIR_OFFSET);
+	val &= ~(0x1 << offset);
+	writel_relaxed(val, gpio_base + GPIO_DIR_OFFSET);
+
+	for (i = 0; i < 3; i++) {
+		val = readl_relaxed(iomux_base + IOMUX_OFFSET(bank->hw_id, i));
+		val &= ~(0x1 << offset);
+		writel_relaxed(val, iomux_base + IOMUX_OFFSET(bank->hw_id, i));
+	}
+	writel_relaxed(0x1, iomux_base + IOMUX_CTRL_SET_OFFSET);
+	writel_relaxed(0x0, iomux_base + IOMUX_CTRL_SET_OFFSET);
+
+	writel_relaxed(0x1 << offset, gpio_base + GPIO_IC_OFFSET);
+
+	val = readl_relaxed(gpio_base + GPIO_IE_OFFSET);
+	val |= 0x1 << offset;
+	writel_relaxed(val, gpio_base + GPIO_IE_OFFSET);
+
+	raw_spin_unlock_irqrestore(&soc->lock, flags);
+}
+
+static void amb_gpio_irq_disable(struct irq_data *data)
+{
+	struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
+	struct amb_gpio_bank *bank = amb_irq_data_to_bank(data);
+	struct amb_pinctrl_soc_data *soc = bank->soc;
+	void __iomem *gpio_base = bank->base;
+	u32 offset, ie;
+	unsigned long flags;
+
+	offset = irqd_to_hwirq(data);
+
+	raw_spin_lock_irqsave(&soc->lock, flags);
+	ie = readl_relaxed(gpio_base + GPIO_IE_OFFSET);
+	writel_relaxed(ie & ~(0x1 << offset), gpio_base + GPIO_IE_OFFSET);
+	writel_relaxed(0x1 << offset, gpio_base + GPIO_IC_OFFSET);
+	raw_spin_unlock_irqrestore(&soc->lock, flags);
+
+	gpiochip_disable_irq(gc, offset);
+}
+
+static void amb_gpio_irq_ack(struct irq_data *data)
+{
+	struct amb_gpio_bank *bank = amb_irq_data_to_bank(data);
+	struct amb_pinctrl_soc_data *soc = bank->soc;
+	void __iomem *gpio_base = bank->base;
+	u32 offset = irqd_to_hwirq(data);
+	unsigned long flags;
+
+	raw_spin_lock_irqsave(&soc->lock, flags);
+	writel_relaxed(0x1 << offset, gpio_base + GPIO_IC_OFFSET);
+	raw_spin_unlock_irqrestore(&soc->lock, flags);
+}
+
+static void amb_gpio_irq_mask(struct irq_data *data)
+{
+	struct amb_gpio_bank *bank = amb_irq_data_to_bank(data);
+	struct amb_pinctrl_soc_data *soc = bank->soc;
+	void __iomem *gpio_base = bank->base;
+	u32 offset, ie;
+	unsigned long flags;
+
+	offset = irqd_to_hwirq(data);
+
+	raw_spin_lock_irqsave(&soc->lock, flags);
+	ie = readl_relaxed(gpio_base + GPIO_IE_OFFSET);
+	writel_relaxed(ie & ~(0x1 << offset), gpio_base + GPIO_IE_OFFSET);
+	raw_spin_unlock_irqrestore(&soc->lock, flags);
+}
+
+static void amb_gpio_irq_mask_ack(struct irq_data *data)
+{
+	struct amb_gpio_bank *bank = amb_irq_data_to_bank(data);
+	struct amb_pinctrl_soc_data *soc = bank->soc;
+	void __iomem *gpio_base = bank->base;
+	u32 offset, ie;
+	unsigned long flags;
+
+	offset = irqd_to_hwirq(data);
+
+	raw_spin_lock_irqsave(&soc->lock, flags);
+	ie = readl_relaxed(gpio_base + GPIO_IE_OFFSET);
+	writel_relaxed(ie & ~(0x1 << offset), gpio_base + GPIO_IE_OFFSET);
+	writel_relaxed(0x1 << offset, gpio_base + GPIO_IC_OFFSET);
+	raw_spin_unlock_irqrestore(&soc->lock, flags);
+}
+
+static void amb_gpio_irq_unmask(struct irq_data *data)
+{
+	struct amb_gpio_bank *bank = amb_irq_data_to_bank(data);
+	struct amb_pinctrl_soc_data *soc = bank->soc;
+	void __iomem *gpio_base = bank->base;
+	u32 offset, ie;
+	unsigned long flags;
+
+	offset = irqd_to_hwirq(data);
+
+	raw_spin_lock_irqsave(&soc->lock, flags);
+	ie = readl_relaxed(gpio_base + GPIO_IE_OFFSET);
+	writel_relaxed(ie | (0x1 << offset), gpio_base + GPIO_IE_OFFSET);
+	raw_spin_unlock_irqrestore(&soc->lock, flags);
+}
+
+static int amb_gpio_irq_set_type(struct irq_data *data, unsigned int type)
+{
+	struct amb_gpio_bank *bank = amb_irq_data_to_bank(data);
+	struct amb_pinctrl_soc_data *soc = bank->soc;
+	void __iomem *gpio_base = bank->base;
+	u32 offset = irqd_to_hwirq(data);
+	u32 mask, bit, sense, bothedges, event;
+	unsigned long flags;
+
+	mask = ~BIT(offset);
+	bit = BIT(offset);
+
+	raw_spin_lock_irqsave(&soc->lock, flags);
+	sense = readl_relaxed(gpio_base + GPIO_IS_OFFSET);
+	bothedges = readl_relaxed(gpio_base + GPIO_IBE_OFFSET);
+	event = readl_relaxed(gpio_base + GPIO_IEV_OFFSET);
+
+	switch (type) {
+	case IRQ_TYPE_EDGE_RISING:
+		sense &= mask;
+		bothedges &= mask;
+		event |= bit;
+		irq_set_handler_locked(data, handle_edge_irq);
+		break;
+	case IRQ_TYPE_EDGE_FALLING:
+		sense &= mask;
+		bothedges &= mask;
+		event &= mask;
+		irq_set_handler_locked(data, handle_edge_irq);
+		break;
+	case IRQ_TYPE_EDGE_BOTH:
+		sense &= mask;
+		bothedges |= bit;
+		event &= mask;
+		irq_set_handler_locked(data, handle_edge_irq);
+		break;
+	case IRQ_TYPE_LEVEL_HIGH:
+		sense |= bit;
+		bothedges &= mask;
+		event |= bit;
+		irq_set_handler_locked(data, handle_level_irq);
+		break;
+	case IRQ_TYPE_LEVEL_LOW:
+		sense |= bit;
+		bothedges &= mask;
+		event &= mask;
+		irq_set_handler_locked(data, handle_level_irq);
+		break;
+	default:
+		raw_spin_unlock_irqrestore(&soc->lock, flags);
+		return -EINVAL;
+	}
+
+	writel_relaxed(sense, gpio_base + GPIO_IS_OFFSET);
+	writel_relaxed(bothedges, gpio_base + GPIO_IBE_OFFSET);
+	writel_relaxed(event, gpio_base + GPIO_IEV_OFFSET);
+	/* clear obsolete irq */
+	writel_relaxed(BIT(offset), gpio_base + GPIO_IC_OFFSET);
+	raw_spin_unlock_irqrestore(&soc->lock, flags);
+
+	return 0;
+}
+
+static int amb_gpio_irq_set_wake(struct irq_data *data, unsigned int on)
+{
+	if (IS_ENABLED(CONFIG_PM)) {
+		struct amb_gpio_bank *bank = amb_irq_data_to_bank(data);
+		struct amb_pinctrl_soc_data *soc = bank->soc;
+		u32 offset = irqd_to_hwirq(data);
+		unsigned long flags;
+		int ret;
+
+		ret = irq_set_irq_wake(bank->irq, on);
+		if (ret)
+			return ret;
+
+		raw_spin_lock_irqsave(&soc->lock, flags);
+		if (on)
+			bank->irq_wake_mask |= BIT(offset);
+		else
+			bank->irq_wake_mask &= ~BIT(offset);
+		raw_spin_unlock_irqrestore(&soc->lock, flags);
+
+		return 0;
+	}
+	return 0;
+}
+
+static struct irq_chip amb_gpio_irqchip = {
+	.name		= "GPIO",
+	.irq_enable	= amb_gpio_irq_enable,
+	.irq_disable	= amb_gpio_irq_disable,
+	.irq_ack	= amb_gpio_irq_ack,
+	.irq_mask	= amb_gpio_irq_mask,
+	.irq_mask_ack	= amb_gpio_irq_mask_ack,
+	.irq_unmask	= amb_gpio_irq_unmask,
+	.irq_set_type	= amb_gpio_irq_set_type,
+	.irq_set_wake	= amb_gpio_irq_set_wake,
+	.irq_request_resources = gpiochip_irq_reqres,
+	.irq_release_resources = gpiochip_irq_relres,
+	.flags		= IRQCHIP_SET_TYPE_MASKED | IRQCHIP_MASK_ON_SUSPEND,
+};
+
+static int amb_gpio_irqdomain_map(struct irq_domain *d, unsigned int irq,
+				  irq_hw_number_t hwirq)
+{
+	struct amb_gpio_bank *bank = d->host_data;
+
+	irq_set_chip_data(irq, &bank->gc);
+	irq_set_chip_and_handler(irq, &amb_gpio_irqchip, handle_level_irq);
+	irq_set_noprobe(irq);
+
+	return 0;
+}
+
+static const struct irq_domain_ops amb_gpio_irq_domain_ops = {
+	.map = amb_gpio_irqdomain_map,
+	.xlate = irq_domain_xlate_twocell,
+};
+
+static void amb_gpio_handle_irq(struct irq_desc *desc)
+{
+	struct amb_gpio_bank *bank;
+	struct irq_chip *irqchip;
+	u32 bit, gpio_mis;
+
+	irqchip = irq_desc_get_chip(desc);
+	chained_irq_enter(irqchip, desc);
+
+	bank = irq_desc_get_handler_data(desc);
+	gpio_mis = readl_relaxed(bank->base + GPIO_MIS_OFFSET);
+	while (gpio_mis) {
+		bit = __ffs(gpio_mis);
+		generic_handle_domain_irq(bank->domain, bit);
+		gpio_mis &= ~BIT(bit);
+	}
+
+	chained_irq_exit(irqchip, desc);
+}
+
+static int amb_gpio_parse_dt(struct amb_pinctrl_soc_data *soc)
+{
+	struct of_phandle_args args;
+	struct resource res;
+	unsigned int hw_id, i;
+	int rval;
+
+	for_each_available_child_of_node_scoped(soc->dev->of_node, np) {
+		struct amb_gpio_bank *bank;
+
+		if (!of_property_present(np, "gpio-controller"))
+			continue;
+
+		if (soc->bank_num >= AMBA_MAX_BANKS)
+			return dev_err_probe(soc->dev, -EINVAL,
+					     "too many GPIO banks\n");
+
+		rval = of_parse_phandle_with_fixed_args(np, "gpio-ranges", 3, 0,
+						       &args);
+		if (rval)
+			return dev_err_probe(soc->dev, rval,
+					     "%pOF: invalid gpio-ranges\n", np);
+
+		if (args.np != soc->dev->of_node || args.args[0] ||
+		    args.args[1] % 32 || !args.args[2] || args.args[2] > 32) {
+			of_node_put(args.np);
+			return dev_err_probe(soc->dev, -EINVAL,
+					     "%pOF: invalid GPIO pin range\n", np);
+		}
+
+		hw_id = args.args[1] / 32;
+		of_node_put(args.np);
+		if (hw_id >= AMBA_MAX_BANKS)
+			return dev_err_probe(soc->dev, -EINVAL,
+					     "%pOF: invalid GPIO bank\n", np);
+
+		for (i = 0; i < soc->bank_num; i++) {
+			if (soc->banks[i].hw_id == hw_id)
+				return dev_err_probe(soc->dev, -EINVAL,
+						     "%pOF: duplicate GPIO bank\n",
+						     np);
+		}
+
+		bank = &soc->banks[soc->bank_num];
+		bank->soc = soc;
+		bank->pin_base = args.args[1];
+		bank->hw_id = hw_id;
+		bank->gc.ngpio = args.args[2];
+		bank->gc.fwnode = of_fwnode_handle(np);
+
+		rval = of_address_to_resource(np, 0, &res);
+		if (rval)
+			return dev_err_probe(soc->dev, rval,
+					     "%pOF: couldn't get registers\n", np);
+
+		bank->base = devm_ioremap_resource(soc->dev, &res);
+		if (IS_ERR(bank->base))
+			return dev_err_probe(soc->dev, PTR_ERR(bank->base),
+					     "%pOF: couldn't map registers\n", np);
+
+		bank->irq = of_irq_get(np, 0);
+		if (bank->irq < 0)
+			return dev_err_probe(soc->dev, bank->irq,
+					     "%pOF: couldn't get interrupt\n", np);
+
+		soc->bank_num++;
+	}
+
+	if (!soc->bank_num)
+		return dev_err_probe(soc->dev, -ENODEV,
+				     "no gpio-controller child nodes\n");
+
+	return 0;
+}
+
+static void amb_gpio_irq_cleanup(void *data)
+{
+	struct amb_gpio_bank *bank = data;
+
+	irq_set_chained_handler_and_data(bank->irq, NULL, NULL);
+	irq_domain_remove(bank->domain);
+	bank->domain = NULL;
+}
+
+static int amb_gpio_register(struct amb_pinctrl_soc_data *soc)
+{
+	struct amb_gpio_bank *bank;
+	unsigned int i;
+	int rval;
+
+	for (i = 0; i < soc->bank_num; i++) {
+		bank = &soc->banks[i];
+		bank->gc.label = devm_kasprintf(soc->dev, GFP_KERNEL,
+					       "%s-gpio%u", dev_name(soc->dev),
+					       bank->hw_id);
+		if (!bank->gc.label)
+			return -ENOMEM;
+
+		bank->gc.base = -1;
+		bank->gc.request = gpiochip_generic_request;
+		bank->gc.free = gpiochip_generic_free;
+		bank->gc.direction_input = amb_gpio_direction_input;
+		bank->gc.direction_output = amb_gpio_direction_output;
+		bank->gc.get_direction = amb_gpio_get_direction;
+		bank->gc.get = amb_gpio_get;
+		bank->gc.set = amb_gpio_set;
+		bank->gc.to_irq = amb_gpio_to_irq;
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+		bank->gc.dbg_show = amb_gpio_dbg_show;
+#endif
+		bank->gc.parent = soc->dev;
+
+		bank->domain = irq_domain_add_linear(to_of_node(bank->gc.fwnode),
+						    bank->gc.ngpio,
+						    &amb_gpio_irq_domain_ops,
+						    bank);
+		if (!bank->domain)
+			return dev_err_probe(soc->dev, -ENODEV,
+					     "GPIO%u: failed to create irqdomain\n",
+					     i);
+
+		rval = devm_gpiochip_add_data(soc->dev, &bank->gc, bank);
+		if (rval) {
+			irq_domain_remove(bank->domain);
+			bank->domain = NULL;
+			return dev_err_probe(soc->dev, rval,
+					     "GPIO%u: gpiochip registration failed\n",
+					     i);
+		}
+
+		writel_relaxed(0xffffffff, bank->base + GPIO_ENABLE_OFFSET);
+		writel_relaxed(0x00000000, bank->base + GPIO_AFSEL_OFFSET);
+		writel_relaxed(0x00000000, bank->base + GPIO_MASK_OFFSET);
+
+		irq_set_irq_type(bank->irq, IRQ_TYPE_LEVEL_HIGH);
+		irq_set_chained_handler_and_data(bank->irq,
+						 amb_gpio_handle_irq, bank);
+
+		rval = devm_add_action_or_reset(soc->dev,
+					       amb_gpio_irq_cleanup, bank);
+		if (rval)
+			return rval;
+	}
+
+	return 0;
+}
+
+static int amb_pinctrl_probe(struct platform_device *pdev)
+{
+	struct amb_pinctrl_soc_data *soc;
+	struct device_node *np;
+	unsigned int *group_pins;
+	unsigned int gpio_pins, group, group_pin;
+	size_t nr_group_pins = 0;
+	int i, rval;
+
+	soc = devm_kzalloc(&pdev->dev, sizeof(*soc), GFP_KERNEL);
+	if (!soc)
+		return -ENOMEM;
+
+	soc->dev = &pdev->dev;
+	soc->data = of_device_get_match_data(&pdev->dev);
+	if (!soc->data)
+		return dev_err_probe(&pdev->dev, -EINVAL, "missing soc data");
+
+	np = pdev->dev.of_node;
+	soc->iomux_base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(soc->iomux_base))
+		return dev_err_probe(&pdev->dev, PTR_ERR(soc->iomux_base),
+							 "couldn't get iomux reg");
+
+	rval = amb_gpio_parse_dt(soc);
+	if (rval)
+		return rval;
+
+	soc->ds_regmap = syscon_regmap_lookup_by_phandle(np, "ambarella,drive-strength-syscon");
+	if (IS_ERR(soc->ds_regmap))
+		return dev_err_probe(&pdev->dev, PTR_ERR(soc->ds_regmap),
+				     "couldn't get drive-strength regmap");
+
+	soc->pull_regmap = syscon_regmap_lookup_by_phandle(np, "ambarella,pull-syscon");
+	if (IS_ERR(soc->pull_regmap))
+		return dev_err_probe(&pdev->dev, PTR_ERR(soc->pull_regmap),
+				     "couldn't get pull regmap");
+
+	gpio_pins = amb_gpio_pins_end(soc);
+	soc->npins = gpio_pins;
+	if (soc->data->clk_au_dedicated_pin >= gpio_pins)
+		soc->npins = soc->data->clk_au_dedicated_pin + 1;
+
+	if (soc->npins > AMBA_MAX_PINS)
+		return dev_err_probe(&pdev->dev, -EINVAL, "too many pins\n");
+
+	soc->nr_groups = soc->data->nr_groups;
+	soc->functions = soc->data->functions;
+	soc->nr_functions = soc->data->nr_functions;
+	if (!soc->data->groups || !soc->nr_groups ||
+	    !soc->functions || !soc->nr_functions)
+		return dev_err_probe(&pdev->dev, -EINVAL,
+				     "missing pin groups or functions\n");
+
+	soc->groups = devm_kcalloc(&pdev->dev, soc->nr_groups,
+				   sizeof(*soc->groups), GFP_KERNEL);
+	if (!soc->groups)
+		return -ENOMEM;
+
+	for (group = 0; group < soc->nr_groups; group++) {
+		const struct ambpin_group_desc *desc =
+			&soc->data->groups[group];
+
+		if (!desc->name || !desc->pinmux || !desc->num_pins)
+			return dev_err_probe(&pdev->dev, -EINVAL,
+					     "invalid pin group %u\n", group);
+
+		nr_group_pins += desc->num_pins;
+	}
+
+	group_pins = devm_kcalloc(&pdev->dev, nr_group_pins,
+				  sizeof(*group_pins), GFP_KERNEL);
+	if (!group_pins)
+		return -ENOMEM;
+
+	for (group = 0; group < soc->nr_groups; group++) {
+		const struct ambpin_group_desc *desc =
+			&soc->data->groups[group];
+		struct ambpin_group *grp = &soc->groups[group];
+
+		grp->name = desc->name;
+		grp->pinmux = desc->pinmux;
+		grp->pins = group_pins;
+		grp->num_pins = desc->num_pins;
+		for (group_pin = 0;
+		     group_pin < grp->num_pins;
+		     group_pin++) {
+			u32 pinmux = grp->pinmux[group_pin];
+			unsigned int pin = AMBA_PINMUX_TO_PIN(pinmux);
+			unsigned int alt = AMBA_PINMUX_TO_ALT(pinmux);
+
+			if (pin >= soc->npins || alt > 7)
+				return dev_err_probe(&pdev->dev, -EINVAL,
+						     "group %s has invalid pinmux %#x\n",
+						     grp->name, pinmux);
+
+			grp->pins[group_pin] = pin;
+		}
+		group_pins += grp->num_pins;
+	}
+
+	raw_spin_lock_init(&soc->lock);
+
+	/* Mark all pins unavailable, then clear pins that exist. */
+	bitmap_fill(soc->used, AMBA_MAX_PINS);
+	for (i = 0; i < soc->bank_num; i++) {
+		unsigned int pin;
+
+		for (pin = soc->banks[i].pin_base;
+		     pin < soc->banks[i].pin_base + soc->banks[i].gc.ngpio;
+		     pin++)
+			clear_bit(pin, soc->used);
+	}
+
+	if (soc->data->clk_au_dedicated_pin >= gpio_pins &&
+	    soc->data->clk_au_dedicated_pin < AMBA_MAX_PINS)
+		clear_bit(soc->data->clk_au_dedicated_pin, soc->used);
+
+	rval = amb_pinctrl_register(soc);
+	if (rval)
+		return dev_err_probe(&pdev->dev, rval, "pinctrl register failed!");
+
+	rval = amb_gpio_register(soc);
+	if (rval)
+		return dev_err_probe(&pdev->dev, rval, "gpio register failed!");
+
+	platform_set_drvdata(pdev, soc);
+	dev_info(&pdev->dev, "Ambarella pinctrl driver registered");
+
+	return 0;
+}
+
+static int amb_pinctrl_suspend_noirq(struct device *dev)
+{
+	struct amb_pinctrl_soc_data *soc = dev_get_drvdata(dev);
+	u32 bank, hw, i;
+
+	for (i = 0; i < soc->bank_num; i++) {
+		hw = soc->banks[i].hw_id;
+
+		regmap_read(soc->pull_regmap, soc->data->pull_en[hw], &soc->pm[i].pull[0]);
+		regmap_read(soc->pull_regmap, soc->data->pull_dir[hw], &soc->pm[i].pull[1]);
+
+		regmap_read(soc->ds_regmap, soc->data->ds0[hw], &soc->pm[i].ds[0]);
+		regmap_read(soc->ds_regmap, soc->data->ds1[hw], &soc->pm[i].ds[1]);
+		if (soc->data->have_ds2)
+			regmap_read(soc->ds_regmap, soc->data->ds2[hw], &soc->pm[i].ds[2]);
+
+		soc->pm[i].iomux[0] = readl_relaxed(soc->iomux_base + IOMUX_OFFSET(hw, 0));
+		soc->pm[i].iomux[1] = readl_relaxed(soc->iomux_base + IOMUX_OFFSET(hw, 1));
+		soc->pm[i].iomux[2] = readl_relaxed(soc->iomux_base + IOMUX_OFFSET(hw, 2));
+
+		soc->pm[i].afsel = readl_relaxed(soc->banks[i].base + GPIO_AFSEL_OFFSET);
+		soc->pm[i].dir = readl_relaxed(soc->banks[i].base + GPIO_DIR_OFFSET);
+		soc->pm[i].is = readl_relaxed(soc->banks[i].base + GPIO_IS_OFFSET);
+		soc->pm[i].ibe = readl_relaxed(soc->banks[i].base + GPIO_IBE_OFFSET);
+		soc->pm[i].iev = readl_relaxed(soc->banks[i].base + GPIO_IEV_OFFSET);
+		soc->pm[i].ie = readl_relaxed(soc->banks[i].base + GPIO_IE_OFFSET);
+		soc->pm[i].mask = readl_relaxed(soc->banks[i].base + GPIO_MASK_OFFSET);
+		writel_relaxed(0xffffffff, soc->banks[i].base + GPIO_MASK_OFFSET);
+		soc->pm[i].data = readl_relaxed(soc->banks[i].base + GPIO_DATA_OFFSET);
+
+		if (soc->banks[i].irq_wake_mask)
+			writel_relaxed(soc->banks[i].irq_wake_mask,
+				       soc->banks[i].base + GPIO_IE_OFFSET);
+	}
+
+	if (soc->data->clk_au_dedicated_pin >= amb_gpio_pins_end(soc)) {
+		bank = PINID_TO_BANK(soc->data->clk_au_dedicated_pin);
+		soc->pm[bank].iomux[0] =
+			readl_relaxed(soc->iomux_base + IOMUX_OFFSET(bank, 0));
+		soc->pm[bank].iomux[1] =
+			readl_relaxed(soc->iomux_base + IOMUX_OFFSET(bank, 1));
+		soc->pm[bank].iomux[2] =
+			readl_relaxed(soc->iomux_base + IOMUX_OFFSET(bank, 2));
+	}
+
+	return 0;
+}
+
+static int amb_pinctrl_resume_noirq(struct device *dev)
+{
+	struct amb_pinctrl_soc_data *soc = dev_get_drvdata(dev);
+	u32 bank, hw, i;
+
+	for (i = 0; i < soc->bank_num; i++) {
+		hw = soc->banks[i].hw_id;
+
+		regmap_write(soc->pull_regmap, soc->data->pull_en[hw], soc->pm[i].pull[0]);
+		regmap_write(soc->pull_regmap, soc->data->pull_dir[hw], soc->pm[i].pull[1]);
+
+		regmap_write(soc->ds_regmap, soc->data->ds0[hw], soc->pm[i].ds[0]);
+		regmap_write(soc->ds_regmap, soc->data->ds1[hw], soc->pm[i].ds[1]);
+		if (soc->data->have_ds2)
+			regmap_write(soc->ds_regmap, soc->data->ds2[hw], soc->pm[i].ds[2]);
+
+		writel_relaxed(soc->pm[i].iomux[0], soc->iomux_base + IOMUX_OFFSET(hw, 0));
+		writel_relaxed(soc->pm[i].iomux[1], soc->iomux_base + IOMUX_OFFSET(hw, 1));
+		writel_relaxed(soc->pm[i].iomux[2], soc->iomux_base + IOMUX_OFFSET(hw, 2));
+
+		writel_relaxed(soc->pm[i].afsel, soc->banks[i].base + GPIO_AFSEL_OFFSET);
+		writel_relaxed(soc->pm[i].dir, soc->banks[i].base + GPIO_DIR_OFFSET);
+		/* Expose DATA writes while restoring the saved GPIO state. */
+		writel_relaxed(0xffffffff,
+			       soc->banks[i].base + GPIO_MASK_OFFSET);
+		writel_relaxed(soc->pm[i].data,
+			       soc->banks[i].base + GPIO_DATA_OFFSET);
+		/* Ensure DATA restore reaches hardware before restoring mask. */
+		wmb();
+		writel_relaxed(soc->pm[i].mask, soc->banks[i].base + GPIO_MASK_OFFSET);
+		writel_relaxed(soc->pm[i].is, soc->banks[i].base + GPIO_IS_OFFSET);
+		writel_relaxed(soc->pm[i].ibe, soc->banks[i].base + GPIO_IBE_OFFSET);
+		writel_relaxed(soc->pm[i].iev, soc->banks[i].base + GPIO_IEV_OFFSET);
+		writel_relaxed(soc->pm[i].ie, soc->banks[i].base + GPIO_IE_OFFSET);
+		writel_relaxed(0xffffffff, soc->banks[i].base + GPIO_ENABLE_OFFSET);
+	}
+
+	if (soc->data->clk_au_dedicated_pin >= amb_gpio_pins_end(soc)) {
+		bank = PINID_TO_BANK(soc->data->clk_au_dedicated_pin);
+		writel_relaxed(soc->pm[bank].iomux[0],
+			       soc->iomux_base + IOMUX_OFFSET(bank, 0));
+		writel_relaxed(soc->pm[bank].iomux[1],
+			       soc->iomux_base + IOMUX_OFFSET(bank, 1));
+		writel_relaxed(soc->pm[bank].iomux[2],
+			       soc->iomux_base + IOMUX_OFFSET(bank, 2));
+		/* Ensure dedicated-pin iomux writes reach hardware before commit. */
+		wmb();
+	}
+
+	writel_relaxed(0x1, soc->iomux_base + IOMUX_CTRL_SET_OFFSET);
+	writel_relaxed(0x0, soc->iomux_base + IOMUX_CTRL_SET_OFFSET);
+
+	return 0;
+}
+
+static DEFINE_NOIRQ_DEV_PM_OPS(amb_pinctrl_pm_ops,
+			       amb_pinctrl_suspend_noirq,
+			       amb_pinctrl_resume_noirq);
+
+static const struct of_device_id amb_pinctrl_dt_match[] = {
+	{
+		.compatible = "ambarella,cv75-pinctrl",
+		.data = &ambarella_cv75_pinctrl_data,
+	},
+	{},
+};
+MODULE_DEVICE_TABLE(of, amb_pinctrl_dt_match);
+
+static struct platform_driver amb_pinctrl_driver = {
+	.probe	= amb_pinctrl_probe,
+	.driver	= {
+		.name	= "ambarella-pinctrl",
+		.of_match_table = of_match_ptr(amb_pinctrl_dt_match),
+		.pm = pm_sleep_ptr(&amb_pinctrl_pm_ops),
+	},
+};
+
+static int __init amb_pinctrl_drv_register(void)
+{
+	return platform_driver_register(&amb_pinctrl_driver);
+}
+arch_initcall(amb_pinctrl_drv_register);
+
+MODULE_AUTHOR("Cao Rongrong <[email protected]>");
+MODULE_DESCRIPTION("Ambarella SoC pinctrl driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/pinctrl/pinctrl-ambarella.h b/drivers/pinctrl/pinctrl-ambarella.h
new file mode 100644
index 000000000000..5fe776393ee3
--- /dev/null
+++ b/drivers/pinctrl/pinctrl-ambarella.h
@@ -0,0 +1,48 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Ambarella pinctrl common data definitions
+ *
+ * Copyright (C) 2012-2026, Ambarella, Inc.
+ */
+
+#ifndef _PINCTRL_AMBARELLA_H
+#define _PINCTRL_AMBARELLA_H
+
+#include <linux/types.h>
+
+#define AMBA_MAX_BANKS			8
+
+#define AMBA_PINMUX(pin, alt)		(((alt) << 12) | (pin))
+#define AMBA_PINMUX_TO_PIN(mux)		((mux) & 0xfff)
+#define AMBA_PINMUX_TO_ALT(mux)		(((mux) >> 12) & 0xf)
+
+struct ambpin_group_desc {
+	const char		*name;
+	const u32		*pinmux;
+	unsigned int		num_pins;
+};
+
+struct ambpin_function {
+	const char		*name;
+	const char * const	*groups;
+	unsigned int		num_groups;
+};
+
+struct amb_pinctrl_data {
+	unsigned int		ds0[AMBA_MAX_BANKS];
+	unsigned int		ds1[AMBA_MAX_BANKS];
+	unsigned int		ds2[AMBA_MAX_BANKS];
+	unsigned int		pull_en[AMBA_MAX_BANKS];
+	unsigned int		pull_dir[AMBA_MAX_BANKS];
+	bool			have_ds2;
+	u32			hsm_domain_id;
+	u32			clk_au_dedicated_pin;
+	const struct ambpin_group_desc *groups;
+	unsigned int		nr_groups;
+	const struct ambpin_function *functions;
+	unsigned int		nr_functions;
+};
+
+extern const struct amb_pinctrl_data ambarella_cv75_pinctrl_data;
+
+#endif /* _PINCTRL_AMBARELLA_H */

-- 
2.34.1
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.