diff mbox series

[v3,3/4] gpio: siul2-s32g2: add NXP S32G2/S32G3 SoCs support

Message ID 20240919134732.2626144-4-andrei.stefanescu@oss.nxp.com
State Superseded
Headers show
Series gpio: siul2-s32g2: add initial GPIO driver | expand

Commit Message

Andrei Stefanescu Sept. 19, 2024, 1:47 p.m. UTC
Add the GPIO driver for S32G2/S32G3 SoCs. This driver uses the SIUL2
(System Integration Unit Lite2) hardware module. There are two
SIUL2 hardware modules present, SIUL2_0(controlling GPIOs 0-101) and
SIUL2_1 for the rest.

The GPIOs are not fully contiguous, there are some gaps:
- GPIO102 up to GPIO111(inclusive) are invalid
- GPIO123 up to GPIO143(inclusive) are invalid

Some GPIOs are input only(i.e. GPI182) though this restriction
is not yet enforced in code.

This patch adds basic GPIO functionality(no interrupts, no
suspend/resume functions).

Signed-off-by: Ghennadi Procopciuc <ghennadi.procopciuc@nxp.com>
Signed-off-by: Larisa Grigore <larisa.grigore@nxp.com>
Signed-off-by: Phu Luu An <phu.luuan@nxp.com>
Signed-off-by: Andrei Stefanescu <andrei.stefanescu@oss.nxp.com>
---
 drivers/gpio/Kconfig            |  10 +
 drivers/gpio/Makefile           |   1 +
 drivers/gpio/gpio-siul2-s32g2.c | 576 ++++++++++++++++++++++++++++++++
 3 files changed, 587 insertions(+)
 create mode 100644 drivers/gpio/gpio-siul2-s32g2.c

Comments

kernel test robot Sept. 22, 2024, 2:35 p.m. UTC | #1
Hi Andrei,

kernel test robot noticed the following build errors:

[auto build test ERROR on brgl/gpio/for-next]
[also build test ERROR on driver-core/driver-core-testing driver-core/driver-core-next driver-core/driver-core-linus robh/for-next linus/master v6.11 next-20240920]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Andrei-Stefanescu/drivers-provide-devm_platform_get_and_ioremap_resource_byname/20240919-215018
base:   https://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux.git gpio/for-next
patch link:    https://lore.kernel.org/r/20240919134732.2626144-4-andrei.stefanescu%40oss.nxp.com
patch subject: [PATCH v3 3/4] gpio: siul2-s32g2: add NXP S32G2/S32G3 SoCs support
config: powerpc-randconfig-r133-20240921 (https://download.01.org/0day-ci/archive/20240922/202409222107.1XGvOBqS-lkp@intel.com/config)
compiler: powerpc-linux-gcc (GCC) 14.1.0
reproduce: (https://download.01.org/0day-ci/archive/20240922/202409222107.1XGvOBqS-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202409222107.1XGvOBqS-lkp@intel.com/

All errors (new ones prefixed by >>, old ones prefixed by <<):

WARNING: modpost: missing MODULE_DESCRIPTION() in mm/kasan/kasan_test_module.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/usb/serial/usb-serial-simple.o
WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/devfreq/governor_userspace.o
>> ERROR: modpost: "__udivdi3" [drivers/gpio/gpio-siul2-s32g2.ko] undefined!
Amit Singh Tomar Sept. 22, 2024, 9:47 p.m. UTC | #2
Hi,

> 
> Add the GPIO driver for S32G2/S32G3 SoCs. This driver uses the SIUL2
> (System Integration Unit Lite2) hardware module. There are two
> SIUL2 hardware modules present, SIUL2_0(controlling GPIOs 0-101) and
> SIUL2_1 for the rest.
> 
> The GPIOs are not fully contiguous, there are some gaps:
> - GPIO102 up to GPIO111(inclusive) are invalid
> - GPIO123 up to GPIO143(inclusive) are invalid
> 
> Some GPIOs are input only(i.e. GPI182) though this restriction
> is not yet enforced in code.
> 
> This patch adds basic GPIO functionality(no interrupts, no
> suspend/resume functions).
> 
> Signed-off-by: Ghennadi Procopciuc <ghennadi.procopciuc@nxp.com>
> Signed-off-by: Larisa Grigore <larisa.grigore@nxp.com>
> Signed-off-by: Phu Luu An <phu.luuan@nxp.com>
> Signed-off-by: Andrei Stefanescu <andrei.stefanescu@oss.nxp.com>
> ---
>    drivers/gpio/Kconfig            |  10 +
>    drivers/gpio/Makefile           |   1 +
>    drivers/gpio/gpio-siul2-s32g2.c | 576 ++++++++++++++++++++++++++++++++
>    3 files changed, 587 insertions(+)
>    create mode 100644 drivers/gpio/gpio-siul2-s32g2.c
> 
> diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
> index 58f43bcced7c..75a6ca60ebc7 100644
> --- a/drivers/gpio/Kconfig
> +++ b/drivers/gpio/Kconfig
> @@ -643,6 +643,16 @@ config GPIO_SIOX
>    	  Say yes here to support SIOX I/O devices. These are units connected
>    	  via a SIOX bus and have a number of fixed-direction I/O lines.
>    
> +config GPIO_SIUL2_S32G2
> +        tristate "GPIO driver for S32G2/S32G3"
> +        depends on ARCH_S32 || COMPILE_TEST
> +        depends on OF_GPIO
> +        select REGMAP_MMIO
> +        help
> +          This enables support for the SIUL2 GPIOs found on the S32G2/S32G3
> +          chips. Say yes here to enable the SIUL2 to be used as an GPIO
> +          controller for S32G2/S32G3 platforms.
> +
>    config GPIO_SNPS_CREG
>    	bool "Synopsys GPIO via CREG (Control REGisters) driver"
>    	depends on ARC || COMPILE_TEST
> diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
> index 64dd6d9d730d..fb6e770a64b9 100644
> --- a/drivers/gpio/Makefile
> +++ b/drivers/gpio/Makefile
> @@ -149,6 +149,7 @@ obj-$(CONFIG_GPIO_SCH)			+= gpio-sch.o
>    obj-$(CONFIG_GPIO_SIFIVE)		+= gpio-sifive.o
>    obj-$(CONFIG_GPIO_SIM)			+= gpio-sim.o
>    obj-$(CONFIG_GPIO_SIOX)			+= gpio-siox.o
> +obj-$(CONFIG_GPIO_SIUL2_S32G2)		+= gpio-siul2-s32g2.o
>    obj-$(CONFIG_GPIO_SL28CPLD)		+= gpio-sl28cpld.o
>    obj-$(CONFIG_GPIO_SLOPPY_LOGIC_ANALYZER) += gpio-sloppy-logic-analyzer.o
>    obj-$(CONFIG_GPIO_SODAVILLE)		+= gpio-sodaville.o
> diff --git a/drivers/gpio/gpio-siul2-s32g2.c b/drivers/gpio/gpio-siul2-s32g2.c
> new file mode 100644
> index 000000000000..a69cbb3bcfaf
> --- /dev/null
> +++ b/drivers/gpio/gpio-siul2-s32g2.c
> @@ -0,0 +1,576 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * SIUL2 GPIO support.
> + *
> + * Copyright (c) 2016 Freescale Semiconductor, Inc.
> + * Copyright 2018-2024 NXP
> +  */
> +
> +#include <linux/err.h>
> +#include <linux/init.h>
> +#include <linux/io.h>
> +#include <linux/platform_device.h>
> +#include <linux/module.h>
> +#include <linux/gpio/driver.h>
> +#include <linux/pinctrl/consumer.h>
> +#include <linux/regmap.h>
> +#include <linux/types.h>
> +
> +/* PGPDOs are 16bit registers that come in big endian
> + * order if they are grouped in pairs of two.
> + *
> + * For example, the order is PGPDO1, PGPDO0, PGPDO3, PGPDO2...
> + */
> +#define SIUL2_PGPDO(N)		(((N) ^ 1) * 2)
> +#define S32G2_SIUL2_NUM		2
> +#define S32G2_PADS_DTS_TAG_LEN	(7)
nit: Parentheses are not required here, please remove it.
> +
> +#define SIUL2_GPIO_16_PAD_SIZE	16
> +
> +/**
> + * struct siul2_device_data  - platform data attached to the compatible.
> + * @pad_access: access table for I/O pads, consists of S32G2_SIUL2_NUM tables.
> + * @reset_cnt: reset the pin name counter to zero when switching to SIUL2_1.
> + */
> +struct siul2_device_data {
> +	const struct regmap_access_table **pad_access;
> +	const bool reset_cnt;
> +};
> +
> +/**
> + * struct siul2_desc - describes a SIUL2 hw module.
> + * @pad_access: array of valid I/O pads.
> + * @opadmap: the regmap of the Parallel GPIO Pad Data Out Register.
> + * @ipadmap: the regmap of the Parallel GPIO Pad Data In Register.
> + * @gpio_base: the first GPIO pin.
> + * @gpio_num: the number of GPIO pins.
> + */
> +struct siul2_desc {
> +	const struct regmap_access_table *pad_access;
> +	struct regmap *opadmap;
> +	struct regmap *ipadmap;
> +	u32 gpio_base;
> +	u32 gpio_num;
> +};
> +
> +/**
> + * struct siul2_gpio_dev - describes a group of GPIO pins.
> + * @platdata: the platform data.
> + * @siul2: SIUL2_0 and SIUL2_1 modules information.
> + * @pin_dir_bitmap: the bitmap with pin directions.
> + * @gc: the GPIO chip.
> + * @lock: mutual access to bitmaps.
> + */
> +struct siul2_gpio_dev {
> +	const struct siul2_device_data *platdata;
> +	struct siul2_desc siul2[S32G2_SIUL2_NUM];
> +	unsigned long *pin_dir_bitmap;
> +	struct gpio_chip gc;
> +	raw_spinlock_t lock;
> +};
> +
> +static const struct regmap_range s32g2_siul20_pad_yes_ranges[] = {
> +	regmap_reg_range(SIUL2_PGPDO(0), SIUL2_PGPDO(0)),
> +	regmap_reg_range(SIUL2_PGPDO(1), SIUL2_PGPDO(1)),
> +	regmap_reg_range(SIUL2_PGPDO(2), SIUL2_PGPDO(2)),
> +	regmap_reg_range(SIUL2_PGPDO(3), SIUL2_PGPDO(3)),
> +	regmap_reg_range(SIUL2_PGPDO(4), SIUL2_PGPDO(4)),
> +	regmap_reg_range(SIUL2_PGPDO(5), SIUL2_PGPDO(5)),
> +	regmap_reg_range(SIUL2_PGPDO(6), SIUL2_PGPDO(6)),
> +};
> +
> +static const struct regmap_access_table s32g2_siul20_pad_access_table = {
> +	.yes_ranges	= s32g2_siul20_pad_yes_ranges,
> +	.n_yes_ranges	= ARRAY_SIZE(s32g2_siul20_pad_yes_ranges),
> +};
> +
> +static const struct regmap_range s32g2_siul21_pad_yes_ranges[] = {
> +	regmap_reg_range(SIUL2_PGPDO(7), SIUL2_PGPDO(7)),
> +	regmap_reg_range(SIUL2_PGPDO(9), SIUL2_PGPDO(9)),
> +	regmap_reg_range(SIUL2_PGPDO(10), SIUL2_PGPDO(10)),
> +	regmap_reg_range(SIUL2_PGPDO(11), SIUL2_PGPDO(11)),
> +};
> +
> +static const struct regmap_access_table s32g2_siul21_pad_access_table = {
> +	.yes_ranges	= s32g2_siul21_pad_yes_ranges,
> +	.n_yes_ranges	= ARRAY_SIZE(s32g2_siul21_pad_yes_ranges),
> +};
> +
> +static const struct regmap_access_table *s32g2_pad_access_table[] = {
> +	&s32g2_siul20_pad_access_table,
> +	&s32g2_siul21_pad_access_table
> +};
> +
> +static_assert(ARRAY_SIZE(s32g2_pad_access_table) == S32G2_SIUL2_NUM);
> +
> +static const struct siul2_device_data s32g2_device_data = {
> +	.pad_access	= s32g2_pad_access_table,
> +	.reset_cnt	= true,
> +};
> +
> +static int siul2_get_gpio_pinspec(struct platform_device *pdev,
> +				  struct of_phandle_args *pinspec,
> +				  unsigned int range_index)
> +{
> +	struct device_node *np = pdev->dev.of_node;
> +
> +	return of_parse_phandle_with_fixed_args(np, "gpio-ranges", 3,
> +						range_index, pinspec);
> +}
> +
> +static struct regmap *siul2_offset_to_regmap(struct siul2_gpio_dev *dev,
> +					     unsigned int offset,
> +					     bool input)
> +{
> +	struct siul2_desc *siul2;
> +	size_t i;
> +
> +	for (i = 0; i < ARRAY_SIZE(dev->siul2); i++) {
> +		siul2 = &dev->siul2[i];
> +		if (offset >= siul2->gpio_base &&
> +		    offset - siul2->gpio_base < siul2->gpio_num)
> +			return input ? siul2->ipadmap : siul2->opadmap;
> +	}
> +
> +	return NULL;
> +}
> +
> +static void siul2_gpio_set_direction(struct siul2_gpio_dev *dev,
> +				     unsigned int gpio, int dir)
> +{
> +	guard(raw_spinlock_irqsave)(&dev->lock);
> +
> +	if (dir == GPIO_LINE_DIRECTION_IN)
> +		__clear_bit(gpio, dev->pin_dir_bitmap);
> +	else
> +		__set_bit(gpio, dev->pin_dir_bitmap);
> +}
> +
> +static int siul2_get_direction(struct siul2_gpio_dev *dev,
> +			       unsigned int gpio)
> +{
> +	return test_bit(gpio, dev->pin_dir_bitmap) ? GPIO_LINE_DIRECTION_OUT :
> +						     GPIO_LINE_DIRECTION_IN;
> +}
> +
> +static struct siul2_gpio_dev *to_siul2_gpio_dev(struct gpio_chip *chip)
> +{
> +	return container_of(chip, struct siul2_gpio_dev, gc);
> +}
> +
> +static int siul2_gpio_dir_in(struct gpio_chip *chip, unsigned int gpio)
> +{
> +	struct siul2_gpio_dev *gpio_dev;
> +	int ret = 0;
> +
> +	ret = pinctrl_gpio_direction_input(chip, gpio);
> +	if (ret)
> +		return ret;
> +
> +	gpio_dev = to_siul2_gpio_dev(chip);
> +	siul2_gpio_set_direction(gpio_dev, gpio, GPIO_LINE_DIRECTION_IN);
> +
> +	return 0;
> +}
> +
> +static int siul2_gpio_get_dir(struct gpio_chip *chip, unsigned int gpio)
> +{
> +	return siul2_get_direction(to_siul2_gpio_dev(chip), gpio);
> +}
> +
> +static unsigned int siul2_pin2pad(unsigned int pin)
> +{
> +	return pin / SIUL2_GPIO_16_PAD_SIZE;
> +}
> +
> +static u16 siul2_pin2mask(unsigned int pin)
> +{
> +	/**
> +	 * From Reference manual :
> +	 * PGPDOx[PPDOy] = GPDO(x × 16) + (15 - y)[PDO_(x × 16) + (15 - y)]
> +	 */
> +	return BIT(SIUL2_GPIO_16_PAD_SIZE - 1 - pin % SIUL2_GPIO_16_PAD_SIZE);
> +}
> +
> +static void siul2_gpio_set_val(struct gpio_chip *chip, unsigned int offset,
> +			       int value)
> +{
> +	struct siul2_gpio_dev *gpio_dev = to_siul2_gpio_dev(chip);
> +	unsigned int pad, reg_offset;
> +	struct regmap *regmap;
> +	u16 mask;
> +
> +	mask = siul2_pin2mask(offset);
> +	pad = siul2_pin2pad(offset);
> +
> +	reg_offset = SIUL2_PGPDO(pad);
> +	regmap = siul2_offset_to_regmap(gpio_dev, offset, false);
> +	if (!regmap)
> +		return;
> +
> +	value = value ? mask : 0;
> +
> +	regmap_update_bits(regmap, reg_offset, mask, value);
> +}
> +
> +static int siul2_gpio_dir_out(struct gpio_chip *chip, unsigned int gpio,
> +			      int val)
> +{
> +	struct siul2_gpio_dev *gpio_dev;
> +	int ret = 0;
> +
> +	gpio_dev = to_siul2_gpio_dev(chip);
> +	siul2_gpio_set_val(chip, gpio, val);
> +
> +	ret = pinctrl_gpio_direction_output(chip, gpio);
> +	if (ret)
> +		return ret;
> +
> +	siul2_gpio_set_direction(gpio_dev, gpio, GPIO_LINE_DIRECTION_OUT);
> +
> +	return 0;
> +}
> +
> +static void siul2_gpio_set(struct gpio_chip *chip, unsigned int offset,
> +			   int value)
> +{
> +	struct siul2_gpio_dev *gpio_dev = to_siul2_gpio_dev(chip);
> +
> +	if (!gpio_dev)
> +		return;
> +
> +	if (siul2_get_direction(gpio_dev, offset) == GPIO_LINE_DIRECTION_IN)
> +		return;
> +
> +	siul2_gpio_set_val(chip, offset, value);
> +}
> +
> +static int siul2_gpio_get(struct gpio_chip *chip, unsigned int offset)
> +{
> +	struct siul2_gpio_dev *gpio_dev = to_siul2_gpio_dev(chip);
> +	unsigned int mask, pad, reg_offset, data = 0;
> +	struct regmap *regmap;
> +
> +	mask = siul2_pin2mask(offset);
> +	pad = siul2_pin2pad(offset);
> +
> +	reg_offset = SIUL2_PGPDO(pad);
> +	regmap = siul2_offset_to_regmap(gpio_dev, offset, true);
> +	if (!regmap)
> +		return -EINVAL;
> +
> +	regmap_read(regmap, reg_offset, &data);
> +
> +	return !!(data & mask);
> +}
> +
> +static const struct regmap_config siul2_regmap_conf = {
> +	.val_bits = 32,
> +	.reg_bits = 32,
> +	.reg_stride = 4,
> +	.cache_type = REGCACHE_FLAT,
> +};
> +
> +static struct regmap *common_regmap_init(struct platform_device *pdev,
> +					 struct regmap_config *conf,
> +					 const char *name)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct resource *res;
> +	resource_size_t size;
> +	void __iomem *base;
> +
> +	base = devm_platform_get_and_ioremap_resource_byname(pdev, name, &res);
> +	if (IS_ERR(base)) {
> +		dev_err(&pdev->dev, "Failed to get MEM resource: %s\n", name);
> +		return ERR_PTR(-EINVAL);
> +	}
> +
> +	size = resource_size(res);
> +	conf->val_bits = conf->reg_stride * 8;
> +	conf->max_register = size - conf->reg_stride;
> +	conf->name = name;
> +	conf->use_raw_spinlock = true;
> +
> +	if (conf->cache_type != REGCACHE_NONE)
> +		conf->num_reg_defaults_raw = size / conf->reg_stride;
> +
> +	return devm_regmap_init_mmio(dev, base, conf);
> +}
> +
> +static bool not_writable(__always_unused struct device *dev,
> +			 __always_unused unsigned int reg)
> +{
> +	return false;
> +}
> +
> +static struct regmap *init_padregmap(struct platform_device *pdev,
> +				     struct siul2_gpio_dev *gpio_dev,
> +				     int selector, bool input)
> +{
> +	const struct siul2_device_data *platdata = gpio_dev->platdata;
> +	struct regmap_config regmap_conf = siul2_regmap_conf;
> +	char dts_tag[S32G2_PADS_DTS_TAG_LEN];
> +	int err;
> +
> +	regmap_conf.reg_stride = 2;
> +
> +	if (selector != 0 && selector != 1)
> +		return ERR_PTR(-EINVAL);
> +
> +	regmap_conf.rd_table = platdata->pad_access[selector];
> +
> +	err = snprintf(dts_tag, ARRAY_SIZE(dts_tag),  "%cpads%d",
> +		       input ? 'i' : 'o', selector);
> +	if (err < 0)
> +		return ERR_PTR(-EINVAL);
> +
> +	if (input) {
> +		regmap_conf.writeable_reg = not_writable;
> +		regmap_conf.cache_type = REGCACHE_NONE;
> +	} else {
> +		regmap_conf.wr_table = platdata->pad_access[selector];
> +	}
> +
> +	return common_regmap_init(pdev, &regmap_conf, dts_tag);
> +}
> +
> +static int siul2_gpio_pads_init(struct platform_device *pdev,
> +				struct siul2_gpio_dev *gpio_dev)
> +{
> +	struct device *dev = &pdev->dev;
> +	size_t i;
> +
> +	for (i = 0; i < ARRAY_SIZE(gpio_dev->siul2); i++) {
> +		gpio_dev->siul2[i].opadmap = init_padregmap(pdev, gpio_dev, i,
> +							    false);
> +		if (IS_ERR(gpio_dev->siul2[i].opadmap)) {
> +			dev_err(dev,
> +				"Failed to initialize opad2%zu regmap config\n",
> +				i);
> +			return PTR_ERR(gpio_dev->siul2[i].opadmap);
> +		}
> +
> +		gpio_dev->siul2[i].ipadmap = init_padregmap(pdev, gpio_dev, i,
> +							    true);
> +		if (IS_ERR(gpio_dev->siul2[i].ipadmap)) {
> +			dev_err(dev,
> +				"Failed to initialize ipad2%zu regmap config\n",
> +				i);
> +			return PTR_ERR(gpio_dev->siul2[i].ipadmap);
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static int siul2_gen_names(struct device *dev, unsigned int cnt, char **names,
> +			   char *ch_index, unsigned int *num_index)
> +{
> +	unsigned int i;
> +
> +	for (i = 0; i < cnt; i++) {
> +		if (i != 0 && !(*num_index % 16))
> +			(*ch_index)++;
> +
> +		names[i] = devm_kasprintf(dev, GFP_KERNEL, "P%c_%02d",
> +					  *ch_index, 0xFU & (*num_index)++);
> +		if (!names[i])
> +			return -ENOMEM;
> +	}
> +
> +	return 0;
> +}
> +
> +static int siul2_gpio_remove_reserved_names(struct device *dev,
> +					    struct siul2_gpio_dev *gpio_dev,
> +					    char **names)
> +{
> +	struct device_node *np = dev->of_node;
> +	int num_ranges, i, j, ret;
> +	u32 base_gpio, num_gpio;
> +
> +	/* Parse the gpio-reserved-ranges to know which GPIOs to exclude. */
> +
> +	num_ranges = of_property_count_u32_elems(dev->of_node,
> +						 "gpio-reserved-ranges");
> +
> +	/* The "gpio-reserved-ranges" is optional. */
> +	if (num_ranges < 0)
> +		return 0;
> +	num_ranges /= 2;
> +
> +	for (i = 0; i < num_ranges; i++) {
> +		ret = of_property_read_u32_index(np, "gpio-reserved-ranges",
> +						 i * 2, &base_gpio);
> +		if (ret) {
> +			dev_err(dev, "Could not parse the start GPIO: %d\n",
> +				ret);
> +			return ret;
> +		}
> +
> +		ret = of_property_read_u32_index(np, "gpio-reserved-ranges",
> +						 i * 2 + 1, &num_gpio);
> +		if (ret) {
> +			dev_err(dev, "Could not parse num. GPIOs: %d\n", ret);
> +			return ret;
> +		}
> +
> +		if (base_gpio + num_gpio > gpio_dev->gc.ngpio) {
> +			dev_err(dev, "Reserved GPIOs outside of GPIO range\n");
> +			return -EINVAL;
> +		}
> +
> +		/* Remove names set for reserved GPIOs. */
> +		for (j = base_gpio; j < base_gpio + num_gpio; j++) {
> +			devm_kfree(dev, names[j]);
> +			names[j] = NULL;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static int siul2_gpio_populate_names(struct device *dev,
> +				     struct siul2_gpio_dev *gpio_dev)
> +{
> +	unsigned int num_index = 0;
> +	char ch_index = 'A';
> +	char **names;
> +	int i, ret;
> +
> +	names = devm_kcalloc(dev, gpio_dev->gc.ngpio, sizeof(*names),
> +			     GFP_KERNEL);
> +	if (!names)
> +		return -ENOMEM;
> +
> +	for (i = 0; i < S32G2_SIUL2_NUM; i++) {
> +		ret = siul2_gen_names(dev, gpio_dev->siul2[i].gpio_num,
> +				      names + gpio_dev->siul2[i].gpio_base,
> +				      &ch_index, &num_index);
> +		if (ret) {
> +			dev_err(dev, "Could not set names for SIUL2_%d GPIOs\n",
> +				i);
> +			return ret;
> +		}
> +
> +		if (gpio_dev->platdata->reset_cnt)
> +			num_index = 0;
> +
> +		ch_index++;
> +	}
> +
> +	ret = siul2_gpio_remove_reserved_names(dev, gpio_dev, names);
> +	if (ret)
> +		return ret;
> +
> +	gpio_dev->gc.names = (const char *const *)names;
> +
> +	return 0;
> +}
> +
> +static int siul2_gpio_probe(struct platform_device *pdev)
> +{
> +	struct siul2_gpio_dev *gpio_dev;
> +	struct device *dev = &pdev->dev;
> +	struct of_phandle_args pinspec;
> +	size_t i, bitmap_size;
> +	struct gpio_chip *gc;
> +	int ret = 0;
> +
> +	gpio_dev = devm_kzalloc(dev, sizeof(*gpio_dev), GFP_KERNEL);
> +	if (!gpio_dev)
> +		return -ENOMEM;
> +
> +	gpio_dev->platdata = &s32g2_device_data;
> +
> +	for (i = 0; i < S32G2_SIUL2_NUM; i++)
> +		gpio_dev->siul2[i].pad_access =
> +			gpio_dev->platdata->pad_access[i];
> +
> +	ret = siul2_gpio_pads_init(pdev, gpio_dev);
> +	if (ret)
> +		return ret;
> +
> +	gc = &gpio_dev->gc;
> +
> +	platform_set_drvdata(pdev, gpio_dev);
> +
> +	raw_spin_lock_init(&gpio_dev->lock);
> +
> +	for (i = 0; i < ARRAY_SIZE(gpio_dev->siul2); i++) {
> +		ret = siul2_get_gpio_pinspec(pdev, &pinspec, i);
> +		if (ret) {
> +			dev_err(dev,
> +				"unable to get pinspec %zu from device tree\n",
> +				i);
> +			return -EINVAL;
> +		}
> +
> +		of_node_put(pinspec.np);
> +
> +		if (pinspec.args_count != 3) {
> +			dev_err(dev, "Invalid pinspec count: %d\n",
> +				pinspec.args_count);
> +			return -EINVAL;
> +		}
> +
> +		gpio_dev->siul2[i].gpio_base = pinspec.args[1];
> +		gpio_dev->siul2[i].gpio_num = pinspec.args[2];
> +	}
> +
> +	gc->base = -1;
> +
> +	/* In some cases, there is a gap between the SIUL GPIOs. */
> +	gc->ngpio = gpio_dev->siul2[S32G2_SIUL2_NUM - 1].gpio_base +
> +		    gpio_dev->siul2[S32G2_SIUL2_NUM - 1].gpio_num;
> +
> +	ret = siul2_gpio_populate_names(&pdev->dev, gpio_dev);
> +	if (ret)
> +		return ret;
> +
> +	bitmap_size = BITS_TO_LONGS(gc->ngpio) *
> +		      sizeof(*gpio_dev->pin_dir_bitmap);
> +	gpio_dev->pin_dir_bitmap = devm_kzalloc(dev, bitmap_size, GFP_KERNEL);
> +	if (!gpio_dev->pin_dir_bitmap)
> +		return -ENOMEM;
> +
> +	gc->parent = dev;
> +	gc->label = dev_name(dev);
> +
> +	gc->set = siul2_gpio_set;
> +	gc->get = siul2_gpio_get;
> +	gc->set_config = gpiochip_generic_config;
> +	gc->request = gpiochip_generic_request;
> +	gc->free = gpiochip_generic_free;
> +	gc->direction_output = siul2_gpio_dir_out;
> +	gc->direction_input = siul2_gpio_dir_in;
> +	gc->get_direction = siul2_gpio_get_dir;
> +	gc->owner = THIS_MODULE;
> +
> +	ret = devm_gpiochip_add_data(dev, gc, gpio_dev);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "unable to add gpiochip\n");
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id siul2_gpio_dt_ids[] = {
> +	{ .compatible = "nxp,s32g2-siul2-gpio" },
> +	{ /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, siul2_gpio_dt_ids);
> +
> +static struct platform_driver siul2_gpio_driver = {
> +	.driver			= {
> +		.name		= "s32g2-siul2-gpio",
> +		.of_match_table = siul2_gpio_dt_ids,
> +	},
> +	.probe			= siul2_gpio_probe,
> +};
> +
> +module_platform_driver(siul2_gpio_driver);
> +
> +MODULE_AUTHOR("NXP");
> +MODULE_DESCRIPTION("NXP SIUL2 GPIO");
> +MODULE_LICENSE("GPL");
> -- 
> 2.45.2
> 
>
Andrei Stefanescu Sept. 23, 2024, 10:57 a.m. UTC | #3
Hi,

>> +#define SIUL2_PGPDO(N)        (((N) ^ 1) * 2)
>> +#define S32G2_SIUL2_NUM        2
>> +#define S32G2_PADS_DTS_TAG_LEN    (7)
> nit: Parentheses are not required here, please remove it.

Thank you for the review! I will remove the parentheses
from "S32G2_PADS_DTS_TAG_LEN" in v4.

Best regards,
Andrei
diff mbox series

Patch

diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 58f43bcced7c..75a6ca60ebc7 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -643,6 +643,16 @@  config GPIO_SIOX
 	  Say yes here to support SIOX I/O devices. These are units connected
 	  via a SIOX bus and have a number of fixed-direction I/O lines.
 
+config GPIO_SIUL2_S32G2
+        tristate "GPIO driver for S32G2/S32G3"
+        depends on ARCH_S32 || COMPILE_TEST
+        depends on OF_GPIO
+        select REGMAP_MMIO
+        help
+          This enables support for the SIUL2 GPIOs found on the S32G2/S32G3
+          chips. Say yes here to enable the SIUL2 to be used as an GPIO
+          controller for S32G2/S32G3 platforms.
+
 config GPIO_SNPS_CREG
 	bool "Synopsys GPIO via CREG (Control REGisters) driver"
 	depends on ARC || COMPILE_TEST
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index 64dd6d9d730d..fb6e770a64b9 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -149,6 +149,7 @@  obj-$(CONFIG_GPIO_SCH)			+= gpio-sch.o
 obj-$(CONFIG_GPIO_SIFIVE)		+= gpio-sifive.o
 obj-$(CONFIG_GPIO_SIM)			+= gpio-sim.o
 obj-$(CONFIG_GPIO_SIOX)			+= gpio-siox.o
+obj-$(CONFIG_GPIO_SIUL2_S32G2)		+= gpio-siul2-s32g2.o
 obj-$(CONFIG_GPIO_SL28CPLD)		+= gpio-sl28cpld.o
 obj-$(CONFIG_GPIO_SLOPPY_LOGIC_ANALYZER) += gpio-sloppy-logic-analyzer.o
 obj-$(CONFIG_GPIO_SODAVILLE)		+= gpio-sodaville.o
diff --git a/drivers/gpio/gpio-siul2-s32g2.c b/drivers/gpio/gpio-siul2-s32g2.c
new file mode 100644
index 000000000000..a69cbb3bcfaf
--- /dev/null
+++ b/drivers/gpio/gpio-siul2-s32g2.c
@@ -0,0 +1,576 @@ 
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * SIUL2 GPIO support.
+ *
+ * Copyright (c) 2016 Freescale Semiconductor, Inc.
+ * Copyright 2018-2024 NXP
+  */
+
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/platform_device.h>
+#include <linux/module.h>
+#include <linux/gpio/driver.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+
+/* PGPDOs are 16bit registers that come in big endian
+ * order if they are grouped in pairs of two.
+ *
+ * For example, the order is PGPDO1, PGPDO0, PGPDO3, PGPDO2...
+ */
+#define SIUL2_PGPDO(N)		(((N) ^ 1) * 2)
+#define S32G2_SIUL2_NUM		2
+#define S32G2_PADS_DTS_TAG_LEN	(7)
+
+#define SIUL2_GPIO_16_PAD_SIZE	16
+
+/**
+ * struct siul2_device_data  - platform data attached to the compatible.
+ * @pad_access: access table for I/O pads, consists of S32G2_SIUL2_NUM tables.
+ * @reset_cnt: reset the pin name counter to zero when switching to SIUL2_1.
+ */
+struct siul2_device_data {
+	const struct regmap_access_table **pad_access;
+	const bool reset_cnt;
+};
+
+/**
+ * struct siul2_desc - describes a SIUL2 hw module.
+ * @pad_access: array of valid I/O pads.
+ * @opadmap: the regmap of the Parallel GPIO Pad Data Out Register.
+ * @ipadmap: the regmap of the Parallel GPIO Pad Data In Register.
+ * @gpio_base: the first GPIO pin.
+ * @gpio_num: the number of GPIO pins.
+ */
+struct siul2_desc {
+	const struct regmap_access_table *pad_access;
+	struct regmap *opadmap;
+	struct regmap *ipadmap;
+	u32 gpio_base;
+	u32 gpio_num;
+};
+
+/**
+ * struct siul2_gpio_dev - describes a group of GPIO pins.
+ * @platdata: the platform data.
+ * @siul2: SIUL2_0 and SIUL2_1 modules information.
+ * @pin_dir_bitmap: the bitmap with pin directions.
+ * @gc: the GPIO chip.
+ * @lock: mutual access to bitmaps.
+ */
+struct siul2_gpio_dev {
+	const struct siul2_device_data *platdata;
+	struct siul2_desc siul2[S32G2_SIUL2_NUM];
+	unsigned long *pin_dir_bitmap;
+	struct gpio_chip gc;
+	raw_spinlock_t lock;
+};
+
+static const struct regmap_range s32g2_siul20_pad_yes_ranges[] = {
+	regmap_reg_range(SIUL2_PGPDO(0), SIUL2_PGPDO(0)),
+	regmap_reg_range(SIUL2_PGPDO(1), SIUL2_PGPDO(1)),
+	regmap_reg_range(SIUL2_PGPDO(2), SIUL2_PGPDO(2)),
+	regmap_reg_range(SIUL2_PGPDO(3), SIUL2_PGPDO(3)),
+	regmap_reg_range(SIUL2_PGPDO(4), SIUL2_PGPDO(4)),
+	regmap_reg_range(SIUL2_PGPDO(5), SIUL2_PGPDO(5)),
+	regmap_reg_range(SIUL2_PGPDO(6), SIUL2_PGPDO(6)),
+};
+
+static const struct regmap_access_table s32g2_siul20_pad_access_table = {
+	.yes_ranges	= s32g2_siul20_pad_yes_ranges,
+	.n_yes_ranges	= ARRAY_SIZE(s32g2_siul20_pad_yes_ranges),
+};
+
+static const struct regmap_range s32g2_siul21_pad_yes_ranges[] = {
+	regmap_reg_range(SIUL2_PGPDO(7), SIUL2_PGPDO(7)),
+	regmap_reg_range(SIUL2_PGPDO(9), SIUL2_PGPDO(9)),
+	regmap_reg_range(SIUL2_PGPDO(10), SIUL2_PGPDO(10)),
+	regmap_reg_range(SIUL2_PGPDO(11), SIUL2_PGPDO(11)),
+};
+
+static const struct regmap_access_table s32g2_siul21_pad_access_table = {
+	.yes_ranges	= s32g2_siul21_pad_yes_ranges,
+	.n_yes_ranges	= ARRAY_SIZE(s32g2_siul21_pad_yes_ranges),
+};
+
+static const struct regmap_access_table *s32g2_pad_access_table[] = {
+	&s32g2_siul20_pad_access_table,
+	&s32g2_siul21_pad_access_table
+};
+
+static_assert(ARRAY_SIZE(s32g2_pad_access_table) == S32G2_SIUL2_NUM);
+
+static const struct siul2_device_data s32g2_device_data = {
+	.pad_access	= s32g2_pad_access_table,
+	.reset_cnt	= true,
+};
+
+static int siul2_get_gpio_pinspec(struct platform_device *pdev,
+				  struct of_phandle_args *pinspec,
+				  unsigned int range_index)
+{
+	struct device_node *np = pdev->dev.of_node;
+
+	return of_parse_phandle_with_fixed_args(np, "gpio-ranges", 3,
+						range_index, pinspec);
+}
+
+static struct regmap *siul2_offset_to_regmap(struct siul2_gpio_dev *dev,
+					     unsigned int offset,
+					     bool input)
+{
+	struct siul2_desc *siul2;
+	size_t i;
+
+	for (i = 0; i < ARRAY_SIZE(dev->siul2); i++) {
+		siul2 = &dev->siul2[i];
+		if (offset >= siul2->gpio_base &&
+		    offset - siul2->gpio_base < siul2->gpio_num)
+			return input ? siul2->ipadmap : siul2->opadmap;
+	}
+
+	return NULL;
+}
+
+static void siul2_gpio_set_direction(struct siul2_gpio_dev *dev,
+				     unsigned int gpio, int dir)
+{
+	guard(raw_spinlock_irqsave)(&dev->lock);
+
+	if (dir == GPIO_LINE_DIRECTION_IN)
+		__clear_bit(gpio, dev->pin_dir_bitmap);
+	else
+		__set_bit(gpio, dev->pin_dir_bitmap);
+}
+
+static int siul2_get_direction(struct siul2_gpio_dev *dev,
+			       unsigned int gpio)
+{
+	return test_bit(gpio, dev->pin_dir_bitmap) ? GPIO_LINE_DIRECTION_OUT :
+						     GPIO_LINE_DIRECTION_IN;
+}
+
+static struct siul2_gpio_dev *to_siul2_gpio_dev(struct gpio_chip *chip)
+{
+	return container_of(chip, struct siul2_gpio_dev, gc);
+}
+
+static int siul2_gpio_dir_in(struct gpio_chip *chip, unsigned int gpio)
+{
+	struct siul2_gpio_dev *gpio_dev;
+	int ret = 0;
+
+	ret = pinctrl_gpio_direction_input(chip, gpio);
+	if (ret)
+		return ret;
+
+	gpio_dev = to_siul2_gpio_dev(chip);
+	siul2_gpio_set_direction(gpio_dev, gpio, GPIO_LINE_DIRECTION_IN);
+
+	return 0;
+}
+
+static int siul2_gpio_get_dir(struct gpio_chip *chip, unsigned int gpio)
+{
+	return siul2_get_direction(to_siul2_gpio_dev(chip), gpio);
+}
+
+static unsigned int siul2_pin2pad(unsigned int pin)
+{
+	return pin / SIUL2_GPIO_16_PAD_SIZE;
+}
+
+static u16 siul2_pin2mask(unsigned int pin)
+{
+	/**
+	 * From Reference manual :
+	 * PGPDOx[PPDOy] = GPDO(x × 16) + (15 - y)[PDO_(x × 16) + (15 - y)]
+	 */
+	return BIT(SIUL2_GPIO_16_PAD_SIZE - 1 - pin % SIUL2_GPIO_16_PAD_SIZE);
+}
+
+static void siul2_gpio_set_val(struct gpio_chip *chip, unsigned int offset,
+			       int value)
+{
+	struct siul2_gpio_dev *gpio_dev = to_siul2_gpio_dev(chip);
+	unsigned int pad, reg_offset;
+	struct regmap *regmap;
+	u16 mask;
+
+	mask = siul2_pin2mask(offset);
+	pad = siul2_pin2pad(offset);
+
+	reg_offset = SIUL2_PGPDO(pad);
+	regmap = siul2_offset_to_regmap(gpio_dev, offset, false);
+	if (!regmap)
+		return;
+
+	value = value ? mask : 0;
+
+	regmap_update_bits(regmap, reg_offset, mask, value);
+}
+
+static int siul2_gpio_dir_out(struct gpio_chip *chip, unsigned int gpio,
+			      int val)
+{
+	struct siul2_gpio_dev *gpio_dev;
+	int ret = 0;
+
+	gpio_dev = to_siul2_gpio_dev(chip);
+	siul2_gpio_set_val(chip, gpio, val);
+
+	ret = pinctrl_gpio_direction_output(chip, gpio);
+	if (ret)
+		return ret;
+
+	siul2_gpio_set_direction(gpio_dev, gpio, GPIO_LINE_DIRECTION_OUT);
+
+	return 0;
+}
+
+static void siul2_gpio_set(struct gpio_chip *chip, unsigned int offset,
+			   int value)
+{
+	struct siul2_gpio_dev *gpio_dev = to_siul2_gpio_dev(chip);
+
+	if (!gpio_dev)
+		return;
+
+	if (siul2_get_direction(gpio_dev, offset) == GPIO_LINE_DIRECTION_IN)
+		return;
+
+	siul2_gpio_set_val(chip, offset, value);
+}
+
+static int siul2_gpio_get(struct gpio_chip *chip, unsigned int offset)
+{
+	struct siul2_gpio_dev *gpio_dev = to_siul2_gpio_dev(chip);
+	unsigned int mask, pad, reg_offset, data = 0;
+	struct regmap *regmap;
+
+	mask = siul2_pin2mask(offset);
+	pad = siul2_pin2pad(offset);
+
+	reg_offset = SIUL2_PGPDO(pad);
+	regmap = siul2_offset_to_regmap(gpio_dev, offset, true);
+	if (!regmap)
+		return -EINVAL;
+
+	regmap_read(regmap, reg_offset, &data);
+
+	return !!(data & mask);
+}
+
+static const struct regmap_config siul2_regmap_conf = {
+	.val_bits = 32,
+	.reg_bits = 32,
+	.reg_stride = 4,
+	.cache_type = REGCACHE_FLAT,
+};
+
+static struct regmap *common_regmap_init(struct platform_device *pdev,
+					 struct regmap_config *conf,
+					 const char *name)
+{
+	struct device *dev = &pdev->dev;
+	struct resource *res;
+	resource_size_t size;
+	void __iomem *base;
+
+	base = devm_platform_get_and_ioremap_resource_byname(pdev, name, &res);
+	if (IS_ERR(base)) {
+		dev_err(&pdev->dev, "Failed to get MEM resource: %s\n", name);
+		return ERR_PTR(-EINVAL);
+	}
+
+	size = resource_size(res);
+	conf->val_bits = conf->reg_stride * 8;
+	conf->max_register = size - conf->reg_stride;
+	conf->name = name;
+	conf->use_raw_spinlock = true;
+
+	if (conf->cache_type != REGCACHE_NONE)
+		conf->num_reg_defaults_raw = size / conf->reg_stride;
+
+	return devm_regmap_init_mmio(dev, base, conf);
+}
+
+static bool not_writable(__always_unused struct device *dev,
+			 __always_unused unsigned int reg)
+{
+	return false;
+}
+
+static struct regmap *init_padregmap(struct platform_device *pdev,
+				     struct siul2_gpio_dev *gpio_dev,
+				     int selector, bool input)
+{
+	const struct siul2_device_data *platdata = gpio_dev->platdata;
+	struct regmap_config regmap_conf = siul2_regmap_conf;
+	char dts_tag[S32G2_PADS_DTS_TAG_LEN];
+	int err;
+
+	regmap_conf.reg_stride = 2;
+
+	if (selector != 0 && selector != 1)
+		return ERR_PTR(-EINVAL);
+
+	regmap_conf.rd_table = platdata->pad_access[selector];
+
+	err = snprintf(dts_tag, ARRAY_SIZE(dts_tag),  "%cpads%d",
+		       input ? 'i' : 'o', selector);
+	if (err < 0)
+		return ERR_PTR(-EINVAL);
+
+	if (input) {
+		regmap_conf.writeable_reg = not_writable;
+		regmap_conf.cache_type = REGCACHE_NONE;
+	} else {
+		regmap_conf.wr_table = platdata->pad_access[selector];
+	}
+
+	return common_regmap_init(pdev, &regmap_conf, dts_tag);
+}
+
+static int siul2_gpio_pads_init(struct platform_device *pdev,
+				struct siul2_gpio_dev *gpio_dev)
+{
+	struct device *dev = &pdev->dev;
+	size_t i;
+
+	for (i = 0; i < ARRAY_SIZE(gpio_dev->siul2); i++) {
+		gpio_dev->siul2[i].opadmap = init_padregmap(pdev, gpio_dev, i,
+							    false);
+		if (IS_ERR(gpio_dev->siul2[i].opadmap)) {
+			dev_err(dev,
+				"Failed to initialize opad2%zu regmap config\n",
+				i);
+			return PTR_ERR(gpio_dev->siul2[i].opadmap);
+		}
+
+		gpio_dev->siul2[i].ipadmap = init_padregmap(pdev, gpio_dev, i,
+							    true);
+		if (IS_ERR(gpio_dev->siul2[i].ipadmap)) {
+			dev_err(dev,
+				"Failed to initialize ipad2%zu regmap config\n",
+				i);
+			return PTR_ERR(gpio_dev->siul2[i].ipadmap);
+		}
+	}
+
+	return 0;
+}
+
+static int siul2_gen_names(struct device *dev, unsigned int cnt, char **names,
+			   char *ch_index, unsigned int *num_index)
+{
+	unsigned int i;
+
+	for (i = 0; i < cnt; i++) {
+		if (i != 0 && !(*num_index % 16))
+			(*ch_index)++;
+
+		names[i] = devm_kasprintf(dev, GFP_KERNEL, "P%c_%02d",
+					  *ch_index, 0xFU & (*num_index)++);
+		if (!names[i])
+			return -ENOMEM;
+	}
+
+	return 0;
+}
+
+static int siul2_gpio_remove_reserved_names(struct device *dev,
+					    struct siul2_gpio_dev *gpio_dev,
+					    char **names)
+{
+	struct device_node *np = dev->of_node;
+	int num_ranges, i, j, ret;
+	u32 base_gpio, num_gpio;
+
+	/* Parse the gpio-reserved-ranges to know which GPIOs to exclude. */
+
+	num_ranges = of_property_count_u32_elems(dev->of_node,
+						 "gpio-reserved-ranges");
+
+	/* The "gpio-reserved-ranges" is optional. */
+	if (num_ranges < 0)
+		return 0;
+	num_ranges /= 2;
+
+	for (i = 0; i < num_ranges; i++) {
+		ret = of_property_read_u32_index(np, "gpio-reserved-ranges",
+						 i * 2, &base_gpio);
+		if (ret) {
+			dev_err(dev, "Could not parse the start GPIO: %d\n",
+				ret);
+			return ret;
+		}
+
+		ret = of_property_read_u32_index(np, "gpio-reserved-ranges",
+						 i * 2 + 1, &num_gpio);
+		if (ret) {
+			dev_err(dev, "Could not parse num. GPIOs: %d\n", ret);
+			return ret;
+		}
+
+		if (base_gpio + num_gpio > gpio_dev->gc.ngpio) {
+			dev_err(dev, "Reserved GPIOs outside of GPIO range\n");
+			return -EINVAL;
+		}
+
+		/* Remove names set for reserved GPIOs. */
+		for (j = base_gpio; j < base_gpio + num_gpio; j++) {
+			devm_kfree(dev, names[j]);
+			names[j] = NULL;
+		}
+	}
+
+	return 0;
+}
+
+static int siul2_gpio_populate_names(struct device *dev,
+				     struct siul2_gpio_dev *gpio_dev)
+{
+	unsigned int num_index = 0;
+	char ch_index = 'A';
+	char **names;
+	int i, ret;
+
+	names = devm_kcalloc(dev, gpio_dev->gc.ngpio, sizeof(*names),
+			     GFP_KERNEL);
+	if (!names)
+		return -ENOMEM;
+
+	for (i = 0; i < S32G2_SIUL2_NUM; i++) {
+		ret = siul2_gen_names(dev, gpio_dev->siul2[i].gpio_num,
+				      names + gpio_dev->siul2[i].gpio_base,
+				      &ch_index, &num_index);
+		if (ret) {
+			dev_err(dev, "Could not set names for SIUL2_%d GPIOs\n",
+				i);
+			return ret;
+		}
+
+		if (gpio_dev->platdata->reset_cnt)
+			num_index = 0;
+
+		ch_index++;
+	}
+
+	ret = siul2_gpio_remove_reserved_names(dev, gpio_dev, names);
+	if (ret)
+		return ret;
+
+	gpio_dev->gc.names = (const char *const *)names;
+
+	return 0;
+}
+
+static int siul2_gpio_probe(struct platform_device *pdev)
+{
+	struct siul2_gpio_dev *gpio_dev;
+	struct device *dev = &pdev->dev;
+	struct of_phandle_args pinspec;
+	size_t i, bitmap_size;
+	struct gpio_chip *gc;
+	int ret = 0;
+
+	gpio_dev = devm_kzalloc(dev, sizeof(*gpio_dev), GFP_KERNEL);
+	if (!gpio_dev)
+		return -ENOMEM;
+
+	gpio_dev->platdata = &s32g2_device_data;
+
+	for (i = 0; i < S32G2_SIUL2_NUM; i++)
+		gpio_dev->siul2[i].pad_access =
+			gpio_dev->platdata->pad_access[i];
+
+	ret = siul2_gpio_pads_init(pdev, gpio_dev);
+	if (ret)
+		return ret;
+
+	gc = &gpio_dev->gc;
+
+	platform_set_drvdata(pdev, gpio_dev);
+
+	raw_spin_lock_init(&gpio_dev->lock);
+
+	for (i = 0; i < ARRAY_SIZE(gpio_dev->siul2); i++) {
+		ret = siul2_get_gpio_pinspec(pdev, &pinspec, i);
+		if (ret) {
+			dev_err(dev,
+				"unable to get pinspec %zu from device tree\n",
+				i);
+			return -EINVAL;
+		}
+
+		of_node_put(pinspec.np);
+
+		if (pinspec.args_count != 3) {
+			dev_err(dev, "Invalid pinspec count: %d\n",
+				pinspec.args_count);
+			return -EINVAL;
+		}
+
+		gpio_dev->siul2[i].gpio_base = pinspec.args[1];
+		gpio_dev->siul2[i].gpio_num = pinspec.args[2];
+	}
+
+	gc->base = -1;
+
+	/* In some cases, there is a gap between the SIUL GPIOs. */
+	gc->ngpio = gpio_dev->siul2[S32G2_SIUL2_NUM - 1].gpio_base +
+		    gpio_dev->siul2[S32G2_SIUL2_NUM - 1].gpio_num;
+
+	ret = siul2_gpio_populate_names(&pdev->dev, gpio_dev);
+	if (ret)
+		return ret;
+
+	bitmap_size = BITS_TO_LONGS(gc->ngpio) *
+		      sizeof(*gpio_dev->pin_dir_bitmap);
+	gpio_dev->pin_dir_bitmap = devm_kzalloc(dev, bitmap_size, GFP_KERNEL);
+	if (!gpio_dev->pin_dir_bitmap)
+		return -ENOMEM;
+
+	gc->parent = dev;
+	gc->label = dev_name(dev);
+
+	gc->set = siul2_gpio_set;
+	gc->get = siul2_gpio_get;
+	gc->set_config = gpiochip_generic_config;
+	gc->request = gpiochip_generic_request;
+	gc->free = gpiochip_generic_free;
+	gc->direction_output = siul2_gpio_dir_out;
+	gc->direction_input = siul2_gpio_dir_in;
+	gc->get_direction = siul2_gpio_get_dir;
+	gc->owner = THIS_MODULE;
+
+	ret = devm_gpiochip_add_data(dev, gc, gpio_dev);
+	if (ret)
+		return dev_err_probe(dev, ret, "unable to add gpiochip\n");
+
+	return 0;
+}
+
+static const struct of_device_id siul2_gpio_dt_ids[] = {
+	{ .compatible = "nxp,s32g2-siul2-gpio" },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, siul2_gpio_dt_ids);
+
+static struct platform_driver siul2_gpio_driver = {
+	.driver			= {
+		.name		= "s32g2-siul2-gpio",
+		.of_match_table = siul2_gpio_dt_ids,
+	},
+	.probe			= siul2_gpio_probe,
+};
+
+module_platform_driver(siul2_gpio_driver);
+
+MODULE_AUTHOR("NXP");
+MODULE_DESCRIPTION("NXP SIUL2 GPIO");
+MODULE_LICENSE("GPL");