Linux GPIO subsystem development
 help / color / mirror / Atom feed
From: Long Zhao <longzhao@ambarella.com>
To: Arnd Bergmann <arnd@arndb.de>,
	Alexandre Belloni <alexandre.belloni@bootlin.com>,
	<linux-arm-kernel@lists.infradead.org>, <soc@lists.linux.dev>
Cc: Rob Herring <robh@kernel.org>,
	Krzysztof Kozlowski <krzk+dt@kernel.org>,
	Conor Dooley <conor+dt@kernel.org>, <devicetree@vger.kernel.org>,
	"Michael Turquette" <mturquette@baylibre.com>,
	Stephen Boyd <sboyd@kernel.org>, <linux-clk@vger.kernel.org>,
	Linus Walleij <linusw@kernel.org>, <linux-gpio@vger.kernel.org>,
	Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
	Jiri Slaby <jirislaby@kernel.org>, <linux-serial@vger.kernel.org>,
	Catalin Marinas <catalin.marinas@arm.com>,
	Will Deacon <will@kernel.org>, <linux-kernel@vger.kernel.org>,
	Long Zhao <longzhao@ambarella.com>
Subject: [PATCH 07/10] pinctrl: ambarella: add Ambarella pin controller
Date: Thu, 30 Jul 2026 18:43:27 +0800	[thread overview]
Message-ID: <20260730104330.81367-8-longzhao@ambarella.com> (raw)
In-Reply-To: <20260730104330.81367-1-longzhao@ambarella.com>

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

Signed-off-by: Long Zhao <longzhao@ambarella.com>
---
 drivers/pinctrl/Kconfig             |   15 +
 drivers/pinctrl/Makefile            |    1 +
 drivers/pinctrl/pinctrl-ambarella.c | 1561 +++++++++++++++++++++++++++
 3 files changed, 1577 insertions(+)
 create mode 100644 drivers/pinctrl/pinctrl-ambarella.c

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..7cd9820d3088 100644
--- a/drivers/pinctrl/Makefile
+++ b/drivers/pinctrl/Makefile
@@ -10,6 +10,7 @@ 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_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.c b/drivers/pinctrl/pinctrl-ambarella.c
new file mode 100644
index 000000000000..641b50a0624d
--- /dev/null
+++ b/drivers/pinctrl/pinctrl-ambarella.c
@@ -0,0 +1,1561 @@
+// 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/io.h>
+#include <linux/of.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 "core.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 MAX_BANK_NUM                   8
+#define MAX_PIN_NUM                    (MAX_BANK_NUM * 32)
+
+#define PINID_TO_BANK(p)               ((p) >> 5)
+#define PINID_TO_OFFSET(p)             ((p) & 0x1f)
+
+#define MUXIDS_TO_PINID(m)             ((m) & 0xfff)
+#define MUXIDS_TO_ALT(m)               (((m) >> 12) & 0xf)
+
+struct ambpin_group {
+       const char              *name;
+       const char              *function;
+       unsigned int            *pins;
+       unsigned int            num_pins;
+       u8                      *alt;
+       unsigned long           *configs;
+       unsigned int            num_configs;
+};
+
+struct ambpin_function {
+       const char              *name;
+       const char              **groups;
+       unsigned int            num_groups;
+};
+
+struct amb_pinctrl_data {
+       unsigned int            ds0[MAX_BANK_NUM];
+       unsigned int            ds1[MAX_BANK_NUM];
+       unsigned int            ds2[MAX_BANK_NUM];
+       unsigned int            pull_en[MAX_BANK_NUM];
+       unsigned int            pull_dir[MAX_BANK_NUM];
+       bool                    have_ds2;
+       u32                     hsm_domain_id;
+       u32                     clk_au_dedicated_pin;
+};
+
+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 amb_pinctrl_soc_data {
+       struct device                   *dev;
+       const struct amb_pinctrl_data   *data;
+       void __iomem                    *gpio_base[MAX_BANK_NUM];
+       void __iomem                    *iomux_base;
+       struct regmap                   *ds_regmap;
+       struct regmap                   *pull_regmap;
+       unsigned int                    ds0[MAX_BANK_NUM];
+       unsigned int                    ds1[MAX_BANK_NUM];
+       unsigned int                    ds2[MAX_BANK_NUM];
+       unsigned int                    pull_en[MAX_BANK_NUM];
+       unsigned int                    pull_dir[MAX_BANK_NUM];
+       unsigned int                    bank_num;
+       unsigned int                    have_ds2;
+       int                             irq[MAX_BANK_NUM];
+       unsigned int                    irq_wake_mask[MAX_BANK_NUM];
+       unsigned long                   used[BITS_TO_LONGS(MAX_PIN_NUM)];
+       raw_spinlock_t lock;
+
+       struct pinctrl_dev              *pctl;
+       struct gpio_chip                *gc;
+       struct irq_domain               *domain;
+
+       struct ambpin_function          *functions;
+       unsigned int                    nr_functions;
+       struct ambpin_group             *groups;
+       unsigned int                    nr_groups;
+
+       struct amb_pinctrl_pm_state     pm[MAX_BANK_NUM];
+
+       /* Domain ID used in HSM boot. */
+       u32                             hsm_domain_id;
+       /* Dedicated clk_au pin used on some chips. */
+       u32                             clk_au_dedicated_pin;
+};
+
+static __iomem void *
+amb_get_gpio_base(const struct amb_pinctrl_soc_data *soc,
+                 struct irq_data *data)
+{
+       return soc->gpio_base[PINID_TO_BANK(irqd_to_hwirq(data))];
+}
+
+/* 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)
+{
+       struct pin_desc *desc;
+
+       seq_printf(s, " %s", dev_name(pctldev->dev));
+
+       desc = pin_desc_get(pctldev, pin);
+       if (desc) {
+               seq_printf(s, " owner: %s%s%s%s",
+                          desc->mux_owner ? desc->mux_owner : "",
+                          desc->mux_owner && desc->gpio_owner ? " " : "",
+                          desc->gpio_owner ? desc->gpio_owner : "",
+                          !desc->mux_owner && !desc->gpio_owner ? "NULL" : "");
+       } else {
+               seq_puts(s, " not registered");
+       }
+}
+#endif
+
+#if IS_ENABLED(CONFIG_OF)
+static int amb_dt_node_to_map(struct pinctrl_dev *pctldev,
+                             struct device_node *np,
+                             struct pinctrl_map **map,
+                             unsigned int *num_maps)
+{
+       struct amb_pinctrl_soc_data *soc = pinctrl_dev_get_drvdata(pctldev);
+       struct ambpin_group *grp = NULL;
+       struct pinctrl_map *new_map;
+       u32 i, new_num;
+
+       /* find the group of this node by name */
+       for (i = 0; i < soc->nr_groups; i++) {
+               if (!strcmp(soc->groups[i].name, np->name)) {
+                       grp = &soc->groups[i];
+                       break;
+               }
+       }
+       if (!grp) {
+               dev_err(soc->dev, "unable to find group for node %s\n", np->name);
+               return -EINVAL;
+       }
+
+       new_num = !!grp->num_pins + !!grp->num_configs;
+       new_map = kcalloc(new_num, sizeof(*new_map), GFP_KERNEL);
+       if (!new_map)
+               return -ENOMEM;
+
+       *map = new_map;
+       *num_maps = new_num;
+
+       /* create mux map */
+       if (grp->num_pins) {
+               new_map[0].type = PIN_MAP_TYPE_MUX_GROUP;
+               new_map[0].data.mux.group = grp->name;
+               new_map[0].data.mux.function = grp->function;
+               new_map++;
+       }
+
+       if (grp->num_configs) {
+               new_map[0].type = PIN_MAP_TYPE_CONFIGS_GROUP;
+               new_map[0].data.configs.group_or_pin = grp->name;
+               new_map[0].data.configs.configs = grp->configs;
+               new_map[0].data.configs.num_configs = grp->num_configs;
+       }
+
+       return 0;
+}
+
+static void amb_dt_free_map(struct pinctrl_dev *pctldev,
+                           struct pinctrl_map *map, unsigned int num_maps)
+{
+       kfree(map);
+}
+#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
+#if IS_ENABLED(CONFIG_OF)
+       .dt_node_to_map         = amb_dt_node_to_map,
+       .dt_free_map            = amb_dt_free_map,
+#endif
+};
+
+/* 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->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, bank, offset;
+       unsigned long flags;
+
+       grp = &soc->groups[group];
+
+       raw_spin_lock_irqsave(&soc->lock, flags);
+       for (i = 0; i < grp->num_pins; i++) {
+               bank = PINID_TO_BANK(grp->pins[i]);
+               offset = PINID_TO_OFFSET(grp->pins[i]);
+               amb_pinmux_set_altfunc(soc, bank, offset,  grp->alt[i]);
+       }
+       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->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;
+       }
+}
+
+/* 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, flags;
+       enum pin_config_param param;
+       u32 arg;
+       int ds;
+
+       bank = PINID_TO_BANK(pin);
+       offset = PINID_TO_OFFSET(pin);
+
+       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->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->pull_dir[bank],
+                                          BIT(offset),
+                                          (param == PIN_CONFIG_BIAS_PULL_UP) ?
+                                          BIT(offset) : 0);
+                       regmap_update_bits(soc->pull_regmap, soc->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->have_ds2) {
+                               regmap_update_bits(soc->ds_regmap,
+                                                  soc->ds0[bank], BIT(offset),
+                                                  (ds & BIT(0)) ? BIT(offset) : 0);
+                               regmap_update_bits(soc->ds_regmap,
+                                                  soc->ds1[bank], BIT(offset),
+                                                  (ds & BIT(1)) ? BIT(offset) : 0);
+                               regmap_update_bits(soc->ds_regmap,
+                                                  soc->ds2[bank], BIT(offset),
+                                                  (ds & BIT(2)) ? BIT(offset) : 0);
+                       } else {
+                               regmap_update_bits(soc->ds_regmap,
+                                                  soc->ds0[bank], BIT(offset),
+                                                  (ds & BIT(1)) ? BIT(offset) : 0);
+                               regmap_update_bits(soc->ds_regmap,
+                                                  soc->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)
+{
+       dev_WARN_ONCE(pctldev->dev, true, "NOT Implemented.\n");
+       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, drv_strength;
+       u32 bank, offset;
+
+       bank = PINID_TO_BANK(pin);
+       offset = PINID_TO_OFFSET(pin);
+
+       regmap_read(soc->pull_regmap, soc->pull_en[bank], &pull_en);
+       pull_en = (pull_en >> offset) & 0x1;
+       regmap_read(soc->pull_regmap, soc->pull_dir[bank], &pull_dir);
+       pull_dir = (pull_dir >> offset) & 0x1;
+       seq_printf(s, " pull: %s, ", pull_en ? pull_dir ? "up" : "down" : "disable");
+
+       regmap_read(soc->ds_regmap, soc->ds0[bank], &ds0);
+       ds0 = (ds0 >> offset) & 0x1;
+       regmap_read(soc->ds_regmap, soc->ds1[bank], &ds1);
+       ds1 = (ds1 >> offset) & 0x1;
+       if (soc->have_ds2) {
+               regmap_read(soc->ds_regmap, soc->ds2[bank], &ds2);
+               ds2 = (ds2 >> offset) & 0x1;
+               drv_strength = (ds2 << 2) | (ds1 << 1) | ds0;
+               seq_printf(s, "drive-strength: %s",
+                          drv_strength == 5 ? "12mA(lvl5)" :
+                          drv_strength == 4 ? "9mA(lvl4)" :
+                          drv_strength == 3 ? "7.5mA(lvl3)" :
+                          drv_strength == 2 ? "6mA(lvl2)" :
+                          drv_strength == 1 ? "4.5mA(lvl1)" :
+                          drv_strength == 0 ? "3mA(lvl0)" : "invalid");
+       } else {
+               drv_strength = (ds0 << 1) | ds1;
+               seq_printf(s, "drive-strength: %s",
+                          drv_strength == 3 ? "12mA" :
+                          drv_strength == 2 ? "8mA" :
+                          drv_strength == 1 ? "4mA" : "2mA");
+       }
+}
+#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
+};
+
+static int amb_pinctrl_get_function_name(struct device_node *np,
+                                        const char **function)
+{
+       if (of_property_read_string(np, "function", function))
+               *function = np->name;
+
+       return 0;
+}
+
+static int amb_pinctrl_count_configs(struct device_node *np)
+{
+       unsigned int bias = 0, count = 0;
+
+       if (of_property_read_bool(np, "bias-disable"))
+               bias++;
+       if (of_property_read_bool(np, "bias-pull-down"))
+               bias++;
+       if (of_property_read_bool(np, "bias-pull-up"))
+               bias++;
+       if (bias > 1)
+               return -EINVAL;
+
+       count += bias;
+       if (of_find_property(np, "drive-strength", NULL))
+               count++;
+
+       return count;
+}
+
+static int amb_pinctrl_parse_configs(struct amb_pinctrl_soc_data *soc,
+                                    struct device_node *np,
+                                    struct ambpin_group *grp)
+{
+       u32 drive;
+       int num_configs, i = 0;
+
+       num_configs = amb_pinctrl_count_configs(np);
+       if (num_configs < 0)
+               return dev_err_probe(soc->dev, num_configs,
+                                    "%pOF has conflicting bias properties\n", np);
+       if (!num_configs)
+               return 0;
+
+       grp->configs = devm_kcalloc(soc->dev, num_configs,
+                                   sizeof(*grp->configs), GFP_KERNEL);
+       if (!grp->configs)
+               return -ENOMEM;
+
+       if (of_property_read_bool(np, "bias-disable"))
+               grp->configs[i++] =
+                       pinconf_to_config_packed(PIN_CONFIG_BIAS_DISABLE, 0);
+       else if (of_property_read_bool(np, "bias-pull-down"))
+               grp->configs[i++] =
+                       pinconf_to_config_packed(PIN_CONFIG_BIAS_PULL_DOWN, 1);
+       else if (of_property_read_bool(np, "bias-pull-up"))
+               grp->configs[i++] =
+                       pinconf_to_config_packed(PIN_CONFIG_BIAS_PULL_UP, 1);
+
+       if (!of_property_read_u32(np, "drive-strength", &drive))
+               grp->configs[i++] =
+                       pinconf_to_config_packed(PIN_CONFIG_DRIVE_STRENGTH,
+                                                drive);
+
+       grp->num_configs = i;
+
+       return 0;
+}
+
+static int amb_pinctrl_parse_group(struct amb_pinctrl_soc_data *soc,
+                                  struct device_node *np, int idx,
+                                  const char **out_name)
+{
+       struct ambpin_group *grp = &soc->groups[idx];
+       struct device *dev = soc->dev;
+       struct property *prop;
+       int length;
+       int ret;
+       u32 i;
+
+       grp->name = np->name;
+       amb_pinctrl_get_function_name(np, &grp->function);
+
+       prop = of_find_property(np, "pinmux", &length);
+       if (prop) {
+               grp->num_pins = length / sizeof(u32);
+
+               grp->pins = devm_kcalloc(dev, grp->num_pins, sizeof(*grp->pins), GFP_KERNEL);
+               if (!grp->pins)
+                       return -ENOMEM;
+
+               grp->alt = devm_kcalloc(dev, grp->num_pins, sizeof(*grp->alt), GFP_KERNEL);
+               if (!grp->alt)
+                       return -ENOMEM;
+
+               of_property_read_u32_array(np, "pinmux", grp->pins,
+                                          grp->num_pins);
+
+               for (i = 0; i < grp->num_pins; i++) {
+                       grp->alt[i] = MUXIDS_TO_ALT(grp->pins[i]);
+                       grp->pins[i] = MUXIDS_TO_PINID(grp->pins[i]);
+               }
+       }
+
+       if (!grp->num_pins)
+               return dev_err_probe(soc->dev, -EINVAL,
+                                    "%pOF: missing pinmux property\n", np);
+
+       ret = amb_pinctrl_parse_configs(soc, np, grp);
+       if (ret)
+               return ret;
+
+       if (out_name)
+               *out_name = grp->name;
+
+       return 0;
+}
+
+static int amb_pinctrl_find_function(struct amb_pinctrl_soc_data *soc,
+                                    const char *function)
+{
+       int i;
+
+       for (i = 0; i < soc->nr_functions; i++) {
+               if (soc->functions[i].name &&
+                   !strcmp(soc->functions[i].name, function))
+                       return i;
+       }
+
+       return -ENOENT;
+}
+
+static int amb_pinctrl_parse_dt(struct amb_pinctrl_soc_data *soc)
+{
+       struct device_node *np = soc->dev->of_node;
+       struct device_node *child;
+       int idxf = 0, idxg = 0;
+       int i, ret;
+
+       /* Count total functions and groups */
+       for_each_child_of_node(np, child) {
+               if (of_find_property(child, "gpio-controller", NULL))
+                       continue;
+
+               soc->nr_groups++;
+       }
+
+       if (!soc->nr_groups) {
+               dev_err(soc->dev, "no group is defined\n");
+               return -ENOENT;
+       }
+
+       soc->functions = devm_kcalloc(soc->dev, soc->nr_groups,
+                                     sizeof(struct ambpin_function), GFP_KERNEL);
+       if (!soc->functions)
+               return -ENOMEM;
+
+       soc->groups = devm_kcalloc(soc->dev, soc->nr_groups,
+                                  sizeof(struct ambpin_group), GFP_KERNEL);
+       if (!soc->groups)
+               return -ENOMEM;
+
+       /* Parse groups and collect unique functions. */
+       idxg = 0;
+       for_each_child_of_node(np, child) {
+               if (of_find_property(child, "gpio-controller", NULL))
+                       continue;
+
+               ret = amb_pinctrl_parse_group(soc, child, idxg, NULL);
+               if (ret) {
+                       of_node_put(child);
+                       return ret;
+               }
+
+               i = amb_pinctrl_find_function(soc, soc->groups[idxg].function);
+               if (i < 0) {
+                       soc->functions[idxf].name = soc->groups[idxg].function;
+                       i = idxf++;
+               }
+
+               soc->functions[i].num_groups++;
+               idxg++;
+       }
+
+       soc->nr_functions = idxf;
+
+       for (idxf = 0; idxf < soc->nr_functions; idxf++) {
+               soc->functions[idxf].groups =
+                       devm_kcalloc(soc->dev, soc->functions[idxf].num_groups,
+                                    sizeof(*soc->functions[idxf].groups),
+                                    GFP_KERNEL);
+               if (!soc->functions[idxf].groups)
+                       return -ENOMEM;
+
+               soc->functions[idxf].num_groups = 0;
+       }
+
+       for (idxg = 0; idxg < soc->nr_groups; idxg++) {
+               i = amb_pinctrl_find_function(soc, soc->groups[idxg].function);
+               if (i < 0)
+                       return -EINVAL;
+
+               soc->functions[i].groups[soc->functions[i].num_groups++] =
+                       soc->groups[idxg].name;
+       }
+
+       return 0;
+}
+
+/* register the pinctrl interface with the pinctrl subsystem */
+static int amb_pinctrl_register(struct amb_pinctrl_soc_data *soc)
+{
+       struct pinctrl_pin_desc *pindesc;
+       int pin, pin_num, rval;
+       struct pinctrl_desc *amb_pinctrl_desc;
+
+       /* Addition one for clk_au dedicated pin */
+       pin_num = soc->bank_num * 32 + 16;
+
+       /* dynamically populate the pin number and pin name for pindesc */
+       pindesc = devm_kcalloc(soc->dev, pin_num, sizeof(*pindesc), GFP_KERNEL);
+       if (!pindesc)
+               return -ENOMEM;
+
+       for (pin = 0; pin < pin_num; pin++) {
+               pindesc[pin].number = pin;
+               pindesc[pin].name = devm_kasprintf(soc->dev, GFP_KERNEL, "io%d", 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 = pin_num;
+       amb_pinctrl_desc->pctlops = &amb_pctrl_ops;
+       amb_pinctrl_desc->pmxops = &amb_pinmux_ops;
+       amb_pinctrl_desc->confops = &amb_pinconf_ops;
+
+       rval = amb_pinctrl_parse_dt(soc);
+       if (rval)
+               return rval;
+
+       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_pinctrl_soc_data *soc = gpiochip_get_data(gc);
+       u32 bank, offset, data;
+       unsigned long flags;
+
+       bank = PINID_TO_BANK(pin);
+       offset = PINID_TO_OFFSET(pin);
+
+       raw_spin_lock_irqsave(&soc->lock, flags);
+       writel_relaxed(0x1 << offset, soc->gpio_base[bank] + GPIO_MASK_OFFSET);
+       data = (value == 0) ? 0 : 0x1 << offset;
+       writel_relaxed(data, soc->gpio_base[bank] + 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_pinctrl_soc_data *soc = gpiochip_get_data(gc);
+       u32 bank, offset, data;
+       unsigned long flags;
+
+       bank = PINID_TO_BANK(pin);
+       offset = PINID_TO_OFFSET(pin);
+
+       raw_spin_lock_irqsave(&soc->lock, flags);
+       writel_relaxed(0x1 << offset, soc->gpio_base[bank] + GPIO_MASK_OFFSET);
+       data = readl_relaxed(soc->gpio_base[bank] + GPIO_DATA_OFFSET);
+       raw_spin_unlock_irqrestore(&soc->lock, flags);
+
+       return (data >> offset) & 0x1;
+}
+
+static int amb_gpio_get_direction(struct gpio_chip *gc, unsigned int pin)
+{
+       struct amb_pinctrl_soc_data *soc = gpiochip_get_data(gc);
+       u32 bank, offset, data;
+       unsigned long flags;
+
+       bank = PINID_TO_BANK(pin);
+       offset = PINID_TO_OFFSET(pin);
+
+       raw_spin_lock_irqsave(&soc->lock, flags);
+       data = readl_relaxed(soc->gpio_base[bank] + GPIO_DIR_OFFSET);
+       data = (data >> offset) & 0x1;
+       raw_spin_unlock_irqrestore(&soc->lock, flags);
+
+       return data ? 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_pinctrl_soc_data *soc = gpiochip_get_data(gc);
+       u32 bank, offset, data;
+       unsigned long flags;
+
+       bank = PINID_TO_BANK(pin);
+       offset = PINID_TO_OFFSET(pin);
+
+       raw_spin_lock_irqsave(&soc->lock, flags);
+       data = readl_relaxed(soc->gpio_base[bank] + GPIO_DIR_OFFSET);
+       if (input)
+               writel_relaxed(data & ~(0x1 << offset), soc->gpio_base[bank] + GPIO_DIR_OFFSET);
+       else
+               writel_relaxed(data | (0x1 << offset), soc->gpio_base[bank] + 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)
+{
+       amb_gpio_set_direction(gc, pin, false);
+       amb_gpio_set(gc, pin, value);
+       return 0;
+}
+
+/* gpiolib gpio_to_irq callback function */
+static int amb_gpio_to_irq(struct gpio_chip *gc, unsigned int pin)
+{
+       struct amb_pinctrl_soc_data *soc = gpiochip_get_data(gc);
+
+       return irq_create_mapping(soc->domain, pin);
+}
+
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+static void amb_gpio_dbg_show(struct seq_file *s, struct gpio_chip *gc)
+{
+       struct amb_pinctrl_soc_data *soc = gpiochip_get_data(gc);
+       u32 afsel, data, dir, mask, iomux0, iomux1, iomux2, alt;
+       u32 i, bank, offset;
+
+       for (i = 0; i < gc->ngpio; i++) {
+               offset = PINID_TO_OFFSET(i);
+               if (!offset) {
+                       bank = PINID_TO_BANK(i);
+
+                       afsel = readl_relaxed(soc->gpio_base[bank] + GPIO_AFSEL_OFFSET);
+                       dir = readl_relaxed(soc->gpio_base[bank] + GPIO_DIR_OFFSET);
+                       mask = readl_relaxed(soc->gpio_base[bank] + GPIO_MASK_OFFSET);
+                       writel_relaxed(0xffffffff, soc->gpio_base[bank] + GPIO_MASK_OFFSET);
+                       data = readl_relaxed(soc->gpio_base[bank] + GPIO_DATA_OFFSET);
+                       writel_relaxed(mask, soc->gpio_base[bank] + GPIO_MASK_OFFSET);
+
+                       seq_printf(s, "\nGPIO[%d]:\t[%d - %d]\n", bank, i, i + 32 - 1);
+                       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);
+
+                       iomux0 = readl_relaxed(soc->iomux_base + bank * 12);
+                       iomux1 = readl_relaxed(soc->iomux_base + bank * 12 + 4);
+                       iomux2 = readl_relaxed(soc->iomux_base + bank * 12 + 8);
+                       seq_printf(s, "IOMUX_REG%d_0:\t0x%08X\n", bank, iomux0);
+                       seq_printf(s, "IOMUX_REG%d_1:\t0x%08X\n", bank, iomux1);
+                       seq_printf(s, "IOMUX_REG%d_2:\t0x%08X\n", bank, iomux2);
+               }
+
+               seq_printf(s, " gpio-%-3d", gc->base + i);
+
+               alt = ((iomux2 >> offset) & 1) << 2;
+               alt |= ((iomux1 >> offset) & 1) << 1;
+               alt |= ((iomux0 >> offset) & 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 & (1 << offset)) ? "out" : "in ",
+                                  (data & (1 << offset)) ? "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_pinctrl_soc_data *soc = gpiochip_get_data(gc);
+       void __iomem *gpio_base = amb_get_gpio_base(soc, data);
+       void __iomem *iomux_base = soc->iomux_base;
+       u32 i, val, bank, offset;
+       unsigned long flags;
+
+       bank = PINID_TO_BANK(irqd_to_hwirq(data));
+       offset = PINID_TO_OFFSET(irqd_to_hwirq(data));
+
+       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, i));
+               val &= ~(0x1 << offset);
+               writel_relaxed(val, iomux_base + IOMUX_OFFSET(bank, 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_pinctrl_soc_data *soc = gpiochip_get_data(gc);
+       void __iomem *gpio_base = amb_get_gpio_base(soc, data);
+       u32 offset, ie;
+       unsigned long flags;
+
+       offset = PINID_TO_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_ack(struct irq_data *data)
+{
+       struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
+       struct amb_pinctrl_soc_data *soc = gpiochip_get_data(gc);
+       void __iomem *gpio_base = amb_get_gpio_base(soc, data);
+       u32 offset = PINID_TO_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 gpio_chip *gc = irq_data_get_irq_chip_data(data);
+       struct amb_pinctrl_soc_data *soc = gpiochip_get_data(gc);
+       void __iomem *gpio_base = amb_get_gpio_base(soc, data);
+       u32 offset, ie;
+       unsigned long flags;
+
+       offset = PINID_TO_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 gpio_chip *gc = irq_data_get_irq_chip_data(data);
+       struct amb_pinctrl_soc_data *soc = gpiochip_get_data(gc);
+       void __iomem *gpio_base = amb_get_gpio_base(soc, data);
+       u32 offset, ie;
+       unsigned long flags;
+
+       offset = PINID_TO_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 gpio_chip *gc = irq_data_get_irq_chip_data(data);
+       struct amb_pinctrl_soc_data *soc = gpiochip_get_data(gc);
+       void __iomem *gpio_base = amb_get_gpio_base(soc, data);
+       u32 offset, ie;
+       unsigned long flags;
+
+       offset = PINID_TO_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 gpio_chip *gc = irq_data_get_irq_chip_data(data);
+       struct amb_pinctrl_soc_data *soc = gpiochip_get_data(gc);
+       void __iomem *gpio_base = amb_get_gpio_base(soc, data);
+       u32 offset = PINID_TO_OFFSET(irqd_to_hwirq(data));
+       u32 mask, bit, sense, bothedges, event;
+       unsigned long flags;
+
+       mask = ~(0x1 << offset);
+       bit = (0x1 << offset);
+       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:
+               pr_err("%s: irq[%d] type[%d] fail!\n",
+                      __func__, data->irq, type);
+               return -EINVAL;
+       }
+
+       raw_spin_lock_irqsave(&soc->lock, flags);
+       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(0x1 << 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 gpio_chip *gc = irq_data_get_irq_chip_data(data);
+               struct amb_pinctrl_soc_data *soc = gpiochip_get_data(gc);
+               u32 bank = PINID_TO_BANK(irqd_to_hwirq(data));
+               u32 offset = PINID_TO_OFFSET(irqd_to_hwirq(data));
+               unsigned long flags;
+
+               raw_spin_lock_irqsave(&soc->lock, flags);
+               if (on)
+                       soc->irq_wake_mask[bank] |= (1 << offset);
+               else
+                       soc->irq_wake_mask[bank] &= ~(1 << offset);
+               raw_spin_unlock_irqrestore(&soc->lock, flags);
+       }
+       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,
+       .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_pinctrl_soc_data *soc = d->host_data;
+
+       irq_set_chip_data(irq, soc->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_pinctrl_soc_data *soc;
+       struct irq_chip *irqchip;
+       u32 i, bit, gpio_mis, gpio_hwirq;
+
+       irqchip = irq_desc_get_chip(desc);
+       chained_irq_enter(irqchip, desc);
+
+       soc = irq_desc_get_handler_data(desc);
+
+       /* find the GPIO bank generating this irq */
+       for (i = 0; i < soc->bank_num; i++) {
+               if (soc->irq[i] == irq_desc_get_irq(desc))
+                       break;
+       }
+
+       if (i == soc->bank_num)
+               goto out;
+
+       gpio_mis = readl_relaxed(soc->gpio_base[i] + GPIO_MIS_OFFSET);
+       while (gpio_mis) {
+               bit = __ffs(gpio_mis);
+               gpio_hwirq = i * 32 + bit;
+               generic_handle_domain_irq(soc->domain, gpio_hwirq);
+               gpio_mis &= ~BIT(bit);
+       }
+
+out:
+       chained_irq_exit(irqchip, desc);
+}
+
+/* register the pinctrl interface with the pinctrl subsystem */
+static int amb_gpio_register(struct amb_pinctrl_soc_data *soc)
+{
+       struct device_node *np;
+       int i, rval = -ENODEV;
+
+       for_each_child_of_node(soc->dev->of_node, np) {
+               if (of_find_property(np, "gpio-controller", NULL)) {
+                       rval = 0;
+                       break;
+               }
+       }
+       if (rval < 0) {
+               dev_err(soc->dev, "no gpio-controller child node\n");
+               return rval;
+       }
+
+       soc->gc = devm_kzalloc(soc->dev, sizeof(struct gpio_chip), GFP_KERNEL);
+       if (!soc->gc)
+               return -ENOMEM;
+       soc->gc->label = dev_name(soc->dev);
+       soc->gc->base = -1;
+       soc->gc->request = gpiochip_generic_request;
+       soc->gc->free = gpiochip_generic_free;
+       soc->gc->direction_input = amb_gpio_direction_input;
+       soc->gc->direction_output = amb_gpio_direction_output;
+       soc->gc->get_direction = amb_gpio_get_direction;
+       soc->gc->get = amb_gpio_get;
+       soc->gc->set = amb_gpio_set;
+       soc->gc->to_irq = amb_gpio_to_irq;
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+       soc->gc->dbg_show  = amb_gpio_dbg_show;
+#endif
+       soc->gc->parent = soc->dev;
+       soc->gc->fwnode = of_fwnode_handle(np);
+       soc->gc->ngpio = soc->bank_num * 32;
+
+       rval = devm_gpiochip_add_data(soc->dev, soc->gc, soc);
+       if (rval) {
+               dev_err(soc->dev, "gpiochip_add_data failed %d\n", rval);
+               return rval;
+       }
+
+       for (i = 0; i < soc->bank_num; i++) {
+               writel_relaxed(0xffffffff, soc->gpio_base[i] + GPIO_ENABLE_OFFSET);
+               writel_relaxed(0x00000000, soc->gpio_base[i] + GPIO_AFSEL_OFFSET);
+               writel_relaxed(0x00000000, soc->gpio_base[i] + GPIO_MASK_OFFSET);
+       }
+
+       /* Initialize GPIO irq */
+       soc->domain = irq_domain_add_linear(np, soc->gc->ngpio,
+                                           &amb_gpio_irq_domain_ops, soc);
+       if (!soc->domain) {
+               dev_err(soc->dev, "Failed to create irqdomain\n");
+               return -ENODEV;
+       }
+
+       for (i = 0; i < soc->bank_num; i++) {
+               irq_set_irq_type(soc->irq[i], IRQ_TYPE_LEVEL_HIGH);
+               irq_set_chained_handler_and_data(soc->irq[i], amb_gpio_handle_irq, soc);
+       }
+
+       return 0;
+}
+
+static int amb_pinctrl_probe(struct platform_device *pdev)
+{
+       struct amb_pinctrl_soc_data *soc;
+       struct device_node *np;
+       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->bank_num = of_irq_count(np);
+       if (!soc->bank_num || soc->bank_num > MAX_BANK_NUM)
+               return dev_err_probe(&pdev->dev, -EINVAL, "Invalid gpio bank(irq)");
+
+       for (i = 0; i < soc->bank_num; i++) {
+               soc->gpio_base[i] = devm_platform_ioremap_resource(pdev, i);
+               if (IS_ERR(soc->gpio_base[i]))
+                       return dev_err_probe(&pdev->dev, PTR_ERR(soc->gpio_base[i]),
+                                                                "couldn't get gpio[%d] reg", i);
+
+               soc->irq[i] = platform_get_irq(pdev, i);
+               if (soc->irq[i] < 0)
+                       return dev_err_probe(&pdev->dev, -ENODEV, "couldn't get gpio[%d] irq", i);
+       }
+
+       soc->iomux_base = devm_platform_ioremap_resource(pdev, i);
+       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");
+
+       for (i = 0; i < soc->bank_num; i++) {
+               soc->ds0[i] = soc->data->ds0[i];
+               soc->ds1[i] = soc->data->ds1[i];
+               soc->ds2[i] = soc->data->ds2[i];
+               soc->pull_en[i] = soc->data->pull_en[i];
+               soc->pull_dir[i] = soc->data->pull_dir[i];
+       }
+
+       soc->have_ds2 = soc->data->have_ds2;
+       soc->hsm_domain_id = soc->data->hsm_domain_id;
+       soc->clk_au_dedicated_pin = soc->data->clk_au_dedicated_pin;
+
+       raw_spin_lock_init(&soc->lock);
+
+       /* not allowed to use for non-existed pins */
+       for (i = soc->bank_num * 32; i < MAX_BANK_NUM * 32; i++)
+               set_bit(i, soc->used);
+
+       if (soc->clk_au_dedicated_pin)
+               clear_bit(soc->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 i;
+
+       for (i = 0; i < soc->bank_num; i++) {
+               regmap_read(soc->pull_regmap, soc->pull_en[i], &soc->pm[i].pull[0]);
+               regmap_read(soc->pull_regmap, soc->pull_dir[i], &soc->pm[i].pull[1]);
+
+               regmap_read(soc->ds_regmap, soc->ds0[i], &soc->pm[i].ds[0]);
+               regmap_read(soc->ds_regmap, soc->ds1[i], &soc->pm[i].ds[1]);
+               if (soc->have_ds2)
+                       regmap_read(soc->ds_regmap, soc->ds2[i], &soc->pm[i].ds[2]);
+
+               soc->pm[i].iomux[0] = readl_relaxed(soc->iomux_base + IOMUX_OFFSET(i, 0));
+               soc->pm[i].iomux[1] = readl_relaxed(soc->iomux_base + IOMUX_OFFSET(i, 1));
+               soc->pm[i].iomux[2] = readl_relaxed(soc->iomux_base + IOMUX_OFFSET(i, 2));
+
+               soc->pm[i].afsel = readl_relaxed(soc->gpio_base[i] + GPIO_AFSEL_OFFSET);
+               soc->pm[i].dir = readl_relaxed(soc->gpio_base[i] + GPIO_DIR_OFFSET);
+               soc->pm[i].is = readl_relaxed(soc->gpio_base[i] + GPIO_IS_OFFSET);
+               soc->pm[i].ibe = readl_relaxed(soc->gpio_base[i] + GPIO_IBE_OFFSET);
+               soc->pm[i].iev = readl_relaxed(soc->gpio_base[i] + GPIO_IEV_OFFSET);
+               soc->pm[i].ie = readl_relaxed(soc->gpio_base[i] + GPIO_IE_OFFSET);
+               soc->pm[i].mask = readl_relaxed(soc->gpio_base[i] + GPIO_MASK_OFFSET);
+               writel_relaxed(0xffffffff, soc->gpio_base[i] + GPIO_MASK_OFFSET);
+               soc->pm[i].data = readl_relaxed(soc->gpio_base[i] + GPIO_DATA_OFFSET);
+
+               if (soc->irq_wake_mask[i])
+                       writel_relaxed(soc->irq_wake_mask[i], soc->gpio_base[i] + GPIO_IE_OFFSET);
+       }
+
+       if (soc->clk_au_dedicated_pin) {
+               i = PINID_TO_BANK(soc->clk_au_dedicated_pin);
+               soc->pm[i].iomux[0] = readl_relaxed(soc->iomux_base + IOMUX_OFFSET(i, 0));
+               soc->pm[i].iomux[1] = readl_relaxed(soc->iomux_base + IOMUX_OFFSET(i, 1));
+               soc->pm[i].iomux[2] = readl_relaxed(soc->iomux_base + IOMUX_OFFSET(i, 2));
+       }
+
+       return 0;
+}
+
+static int amb_pinctrl_resume_noirq(struct device *dev)
+{
+       struct amb_pinctrl_soc_data *soc = dev_get_drvdata(dev);
+       u32 i;
+
+       for (i = 0; i < soc->bank_num; i++) {
+               regmap_write(soc->pull_regmap, soc->pull_en[i], soc->pm[i].pull[0]);
+               regmap_write(soc->pull_regmap, soc->pull_dir[i], soc->pm[i].pull[1]);
+
+               regmap_write(soc->ds_regmap, soc->ds0[i], soc->pm[i].ds[0]);
+               regmap_write(soc->ds_regmap, soc->ds1[i], soc->pm[i].ds[1]);
+               if (soc->have_ds2)
+                       regmap_write(soc->ds_regmap, soc->ds2[i], soc->pm[i].ds[2]);
+
+               writel_relaxed(soc->pm[i].iomux[0], soc->iomux_base + IOMUX_OFFSET(i, 0));
+               writel_relaxed(soc->pm[i].iomux[1], soc->iomux_base + IOMUX_OFFSET(i, 1));
+               writel_relaxed(soc->pm[i].iomux[2], soc->iomux_base + IOMUX_OFFSET(i, 2));
+
+               writel_relaxed(soc->pm[i].afsel, soc->gpio_base[i] + GPIO_AFSEL_OFFSET);
+               writel_relaxed(soc->pm[i].dir, soc->gpio_base[i] + GPIO_DIR_OFFSET);
+               /* Expose DATA writes while restoring the saved GPIO state. */
+               writel_relaxed(0xffffffff,
+                              soc->gpio_base[i] + GPIO_MASK_OFFSET);
+               writel_relaxed(soc->pm[i].data, soc->gpio_base[i] + GPIO_DATA_OFFSET);
+               /* Ensure DATA restore reaches hardware before restoring mask. */
+               wmb();
+               writel_relaxed(soc->pm[i].mask, soc->gpio_base[i] + GPIO_MASK_OFFSET);
+               writel_relaxed(soc->pm[i].is, soc->gpio_base[i] + GPIO_IS_OFFSET);
+               writel_relaxed(soc->pm[i].ibe, soc->gpio_base[i] + GPIO_IBE_OFFSET);
+               writel_relaxed(soc->pm[i].iev, soc->gpio_base[i] + GPIO_IEV_OFFSET);
+               writel_relaxed(soc->pm[i].ie, soc->gpio_base[i] + GPIO_IE_OFFSET);
+               writel_relaxed(0xffffffff, soc->gpio_base[i] + GPIO_ENABLE_OFFSET);
+       }
+
+       if (soc->clk_au_dedicated_pin) {
+               i = PINID_TO_BANK(soc->clk_au_dedicated_pin);
+               writel_relaxed(soc->pm[i].iomux[0], soc->iomux_base + IOMUX_OFFSET(i, 0));
+               writel_relaxed(soc->pm[i].iomux[1], soc->iomux_base + IOMUX_OFFSET(i, 1));
+               writel_relaxed(soc->pm[i].iomux[2], soc->iomux_base + IOMUX_OFFSET(i, 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 amb_pinctrl_data 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,
+};
+
+static const struct of_device_id amb_pinctrl_dt_match[] = {
+       {
+               .compatible = "ambarella,cv75-pinctrl",
+               .data = &cv75_pinctrl_data,
+       },
+       {},
+};
+MODULE_DEVICE_TABLE(of, amb_pinctrl_dt_match);
+
+static void amb_pinctrl_remove(struct platform_device *pdev)
+{
+       struct amb_pinctrl_soc_data *soc = platform_get_drvdata(pdev);
+       unsigned int i;
+
+       for (i = 0; i < soc->bank_num; i++)
+               irq_set_chained_handler_and_data(soc->irq[i], NULL, NULL);
+
+       if (soc->domain)
+               irq_domain_remove(soc->domain);
+}
+
+static struct platform_driver amb_pinctrl_driver = {
+       .probe  = amb_pinctrl_probe,
+       .remove = amb_pinctrl_remove,
+       .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");
--
2.34.1

NOTE: This email (including attachments) contain Ambarella Proprietary and/or Confidential Information and is intended solely for the use of the individual(s) to whom it is addressed. Any unauthorized review, use, disclosure, distribute, copy, or print is prohibited. If you are not an intended recipient, please contact the sender by reply email and destroy all copies of the original message. Thank you.

  parent reply	other threads:[~2026-07-30 11:06 UTC|newest]

Thread overview: 20+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-30 10:43 [PATCH 00/10] Ambarella CV75 SoC minimal bring-up Long Zhao
2026-07-30 10:43 ` [PATCH 01/10] dt-bindings: arm: add Ambarella CV75 platforms Long Zhao
2026-07-30 10:43 ` [PATCH 02/10] dt-bindings: soc: add Ambarella secure scratchpad Long Zhao
2026-07-30 10:43 ` [PATCH 03/10] dt-bindings: clock: add Ambarella CV75 RCT clock controller Long Zhao
2026-07-30 12:42   ` Rob Herring (Arm)
2026-07-30 14:11   ` Rob Herring
2026-07-30 10:43 ` [PATCH 04/10] dt-bindings: pinctrl: add Ambarella CV75 pinctrl Long Zhao
2026-07-30 12:42   ` Rob Herring (Arm)
2026-07-30 10:43 ` [PATCH 05/10] dt-bindings: serial: add Ambarella UART Long Zhao
2026-07-30 10:49   ` Krzysztof Kozlowski
2026-07-30 10:43 ` [PATCH 06/10] clk: ambarella: add CV75 CCU driver Long Zhao
2026-07-30 10:43 ` Long Zhao [this message]
2026-07-30 10:43 ` [PATCH 08/10] serial: ambarella: add Ambarella UART driver Long Zhao
2026-07-30 10:54   ` Greg Kroah-Hartman
2026-07-30 10:43 ` [PATCH 09/10] arm64: ambarella: add ARCH_AMBARELLA and CV75 EVK DT Long Zhao
2026-07-30 12:18   ` Marc Zyngier
2026-07-30 10:43 ` [PATCH 10/10] MAINTAINERS: add ARM/AMBARELLA SoC support Long Zhao
2026-07-30 10:48 ` [PATCH 00/10] Ambarella CV75 SoC minimal bring-up Krzysztof Kozlowski
2026-07-30 11:05   ` [EXT] " Long Zhao
2026-07-30 11:56     ` Alexandre Belloni

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260730104330.81367-8-longzhao@ambarella.com \
    --to=longzhao@ambarella.com \
    --cc=alexandre.belloni@bootlin.com \
    --cc=arnd@arndb.de \
    --cc=catalin.marinas@arm.com \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=gregkh@linuxfoundation.org \
    --cc=jirislaby@kernel.org \
    --cc=krzk+dt@kernel.org \
    --cc=linusw@kernel.org \
    --cc=linux-arm-kernel@lists.infradead.org \
    --cc=linux-clk@vger.kernel.org \
    --cc=linux-gpio@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-serial@vger.kernel.org \
    --cc=mturquette@baylibre.com \
    --cc=robh@kernel.org \
    --cc=sboyd@kernel.org \
    --cc=soc@lists.linux.dev \
    --cc=will@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox