[PATCH v2 07/10] pinctrl: ambarella: add Ambarella pin controller
Long Zhao via B4 Relay <[email protected]> Thu, 06 Aug 2026 17:34:15 +0800
| Newsgroups | org.kernel.vger.linux-serial,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 |
|---|---|
| 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