gpio: Add QUICC Engine GPIOs driver

The mpc832x has GPIOs handled by the QUICC Engine.
The registers are different from the one for the
non QE mpc83xx GPIOs.

Implement a GPIO driver for those.

Signed-off-by: Christophe Leroy <christophe.leroy@csgroup.eu>
diff --git a/arch/powerpc/include/asm/arch-mpc83xx/gpio.h b/arch/powerpc/include/asm/arch-mpc83xx/gpio.h
index 19c2506..df95d22 100644
--- a/arch/powerpc/include/asm/arch-mpc83xx/gpio.h
+++ b/arch/powerpc/include/asm/arch-mpc83xx/gpio.h
@@ -22,6 +22,11 @@
        uint ngpios;
 };
 
+struct qe_gpio_plat {
+	ulong addr;
+	unsigned long size;
+};
+
 #ifndef DM_GPIO
 void mpc83xx_gpio_init_f(void);
 void mpc83xx_gpio_init_r(void);
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 7d5ddbd..9bf6e42 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -547,6 +547,24 @@
 	  value setting, the open-drain feature, which can configure individual
 	  GPIOs to work as open-drain outputs, is supported.
 
+config QE_GPIO
+	bool "Freescale QUICC ENGINE GPIO driver"
+	depends on DM_GPIO
+	depends on QE
+	help
+	  This driver supports the QUICC Engine GPIOs of MPC83XX CPUs.
+	  Each GPIO bank is identified by its own entry in the device tree,
+	  i.e.
+
+	  qe_pio_a: gpio-controller@1400 {
+		compatible = "fsl,mpc8323-qe-pario-bank";
+		reg = <0x1400 0x18>;
+		gpio-controller;
+		#gpio-cells = <2>;
+	  };
+
+	  Each bank has 32 GPIOs.
+
 config MPC8XX_GPIO
 	bool "Freescale MPC8XX GPIO driver"
 	depends on DM_GPIO
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index 1e81e36..64a36c4 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -38,6 +38,7 @@
 obj-$(CONFIG_DA8XX_GPIO)	+= da8xx_gpio.o
 obj-$(CONFIG_ALTERA_PIO)	+= altera_pio.o
 obj-$(CONFIG_MPC8XXX_GPIO)	+= mpc8xxx_gpio.o
+obj-$(CONFIG_QE_GPIO)	+= qe_gpio.o
 obj-$(CONFIG_MPC8XX_GPIO)	+= mpc8xx_gpio.o
 obj-$(CONFIG_MPC83XX_SPISEL_BOOT)	+= mpc83xx_spisel_boot.o
 obj-$(CONFIG_SH_GPIO_PFC)	+= sh_pfc.o
diff --git a/drivers/gpio/qe_gpio.c b/drivers/gpio/qe_gpio.c
new file mode 100644
index 0000000..16e8d1e
--- /dev/null
+++ b/drivers/gpio/qe_gpio.c
@@ -0,0 +1,170 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2023 CR GROUP France
+ * Christophe Leroy <christophe.leroy@csgroup.eu>
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <mapmem.h>
+#include <asm/gpio.h>
+#include <asm/immap_83xx.h>
+#include <asm/io.h>
+#include <dm/of_access.h>
+
+#define QE_DIR_NONE	0
+#define QE_DIR_OUT	1
+#define QE_DIR_IN	2
+#define QE_DIR_IN_OUT	3
+
+struct qe_gpio_data {
+	/* The bank's register base in memory */
+	struct gpio_n __iomem *base;
+	/* The address of the registers; used to identify the bank */
+	phys_addr_t addr;
+};
+
+static inline u32 gpio_mask(uint gpio)
+{
+	return 1U << (31 - (gpio));
+}
+
+static inline u32 gpio_mask2(uint gpio)
+{
+	return 1U << (30 - ((gpio & 15) << 1));
+}
+
+static int qe_gpio_direction_input(struct udevice *dev, uint gpio)
+{
+	struct qe_gpio_data *data = dev_get_priv(dev);
+	struct gpio_n __iomem *base = data->base;
+	u32 mask2 = gpio_mask2(gpio);
+
+	if (gpio < 16)
+		clrsetbits_be32(&base->dir1, mask2 * QE_DIR_OUT, mask2 * QE_DIR_IN);
+	else
+		clrsetbits_be32(&base->dir2, mask2 * QE_DIR_OUT, mask2 * QE_DIR_IN);
+
+	return 0;
+}
+
+static int qe_gpio_set_value(struct udevice *dev, uint gpio, int value)
+{
+	struct qe_gpio_data *data = dev_get_priv(dev);
+	struct gpio_n __iomem *base = data->base;
+	u32 mask = gpio_mask(gpio);
+	u32 mask2 = gpio_mask2(gpio);
+
+	if (gpio < 16)
+		clrsetbits_be32(&base->dir1, mask2 * QE_DIR_IN, mask2 * QE_DIR_OUT);
+	else
+		clrsetbits_be32(&base->dir2, mask2 * QE_DIR_IN, mask2 * QE_DIR_OUT);
+
+	if (value)
+		setbits_be32(&base->pdat, mask);
+	else
+		clrbits_be32(&base->pdat, mask);
+
+	return 0;
+}
+
+static int qe_gpio_get_value(struct udevice *dev, uint gpio)
+{
+	struct qe_gpio_data *data = dev_get_priv(dev);
+	struct gpio_n __iomem *base = data->base;
+	u32 mask = gpio_mask(gpio);
+
+	return !!(in_be32(&base->pdat) & mask);
+}
+
+static int qe_gpio_get_function(struct udevice *dev, uint gpio)
+{
+	struct qe_gpio_data *data = dev_get_priv(dev);
+	struct gpio_n __iomem *base = data->base;
+	u32 mask2 = gpio_mask2(gpio);
+	int dir;
+
+	if (gpio < 16)
+		dir = in_be32(&base->dir1);
+	else
+		dir = in_be32(&base->dir2);
+
+	if ((dir & (mask2 * QE_DIR_IN_OUT)) == (mask2 & QE_DIR_IN))
+		return GPIOF_INPUT;
+	else if ((dir & (mask2 * QE_DIR_IN_OUT)) == (mask2 & QE_DIR_OUT))
+		return GPIOF_OUTPUT;
+	else
+		return GPIOF_UNKNOWN;
+}
+
+static int qe_gpio_of_to_plat(struct udevice *dev)
+{
+	struct qe_gpio_plat *plat = dev_get_plat(dev);
+
+	plat->addr = dev_read_addr_size_index(dev, 0, (fdt_size_t *)&plat->size);
+
+	return 0;
+}
+
+static int qe_gpio_plat_to_priv(struct udevice *dev)
+{
+	struct qe_gpio_data *priv = dev_get_priv(dev);
+	struct qe_gpio_plat *plat = dev_get_plat(dev);
+	unsigned long size = plat->size;
+
+	if (size == 0)
+		size = sizeof(struct gpio_n);
+
+	priv->addr = plat->addr;
+	priv->base = (void __iomem *)plat->addr;
+
+	if (!priv->base)
+		return -ENOMEM;
+
+	return 0;
+}
+
+static int qe_gpio_probe(struct udevice *dev)
+{
+	struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev);
+	struct qe_gpio_data *data = dev_get_priv(dev);
+	char name[32], *str;
+
+	qe_gpio_plat_to_priv(dev);
+
+	snprintf(name, sizeof(name), "QE@%.8llx",
+		 (unsigned long long)data->addr);
+	str = strdup(name);
+
+	if (!str)
+		return -ENOMEM;
+
+	uc_priv->bank_name = str;
+	uc_priv->gpio_count = 32;
+
+	return 0;
+}
+
+static const struct dm_gpio_ops gpio_qe_ops = {
+	.direction_input	= qe_gpio_direction_input,
+	.direction_output	= qe_gpio_set_value,
+	.get_value		= qe_gpio_get_value,
+	.set_value		= qe_gpio_set_value,
+	.get_function		= qe_gpio_get_function,
+};
+
+static const struct udevice_id qe_gpio_ids[] = {
+	{ .compatible = "fsl,mpc8323-qe-pario-bank"},
+	{ /* sentinel */ }
+};
+
+U_BOOT_DRIVER(gpio_qe) = {
+	.name	= "gpio_qe",
+	.id	= UCLASS_GPIO,
+	.ops	= &gpio_qe_ops,
+	.of_to_plat = qe_gpio_of_to_plat,
+	.plat_auto	= sizeof(struct qe_gpio_plat),
+	.of_match = qe_gpio_ids,
+	.probe	= qe_gpio_probe,
+	.priv_auto	= sizeof(struct qe_gpio_data),
+};