gpio: Add RZ/G2L GPIO driver

This driver adds support for the gpio features of the GPIO/PFC module in
the Renesas RZ/G2L (R9A07G044) SoC.

The new `rzg2l-pfc-gpio` driver is bound to the same device tree node as
the `rzg2l-pfc-pinctrl` driver as the same hardware block provides both
GPIO and pin multiplexing features.

This patch is based on the corresponding Linux v6.5 driver
(commit 52e12027d50affbf60c6c9c64db8017391b0c22e).

Signed-off-by: Paul Barker <paul.barker.ct@bp.renesas.com>
Reviewed-by: Biju Das <biju.das.jz@bp.renesas.com>
Reviewed-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>
Reviewed-by: Marek Vasut <marek.vasut+renesas@mailbox.org>
diff --git a/arch/arm/mach-rmobile/Kconfig b/arch/arm/mach-rmobile/Kconfig
index 35e9023..714eb44 100644
--- a/arch/arm/mach-rmobile/Kconfig
+++ b/arch/arm/mach-rmobile/Kconfig
@@ -76,6 +76,7 @@
 	imply MULTI_DTB_FIT_USER_DEFINED_AREA
 	imply PINCTRL_RZG2L
 	imply RENESAS_SDHI
+	imply RZG2L_GPIO
 	imply SYS_MALLOC_F
 	help
 	  Enable support for the Renesas RZ/G2L family of SoCs. Currently
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 9bf6e42..74baa98 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -659,4 +659,11 @@
 	help
 	  Support ADP5585 GPIO expander.
 
+config RZG2L_GPIO
+	bool "Renesas RZ/G2L family GPIO driver"
+	depends on DM_GPIO && PINCTRL_RZG2L
+	help
+	  Support the gpio functionality of the pin function controller (PFC)
+	  on the Renesas RZ/G2L SoC family.
+
 endif
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index 64a36c4..c8b3fd7 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -74,3 +74,4 @@
 obj-$(CONFIG_$(SPL_TPL_)TURRIS_OMNIA_MCU)	+= turris_omnia_mcu.o
 obj-$(CONFIG_FTGPIO010)		+= ftgpio010.o
 obj-$(CONFIG_ADP5585_GPIO)	+= adp5585_gpio.o
+obj-$(CONFIG_RZG2L_GPIO)	+= rzg2l-gpio.o
diff --git a/drivers/gpio/rzg2l-gpio.c b/drivers/gpio/rzg2l-gpio.c
new file mode 100644
index 0000000..7c908d0
--- /dev/null
+++ b/drivers/gpio/rzg2l-gpio.c
@@ -0,0 +1,170 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * RZ/G2L Pin Function Controller
+ *
+ * Copyright (C) 2021-2023 Renesas Electronics Corp.
+ */
+
+#include <common.h>
+#include <asm-generic/gpio.h>
+#include <asm/io.h>
+#include <dm/device.h>
+#include <dm/device_compat.h>
+#include <renesas/rzg2l-pfc.h>
+
+static void rzg2l_gpio_set(const struct rzg2l_pfc_data *data, u32 port, u8 pin,
+			   bool value)
+{
+	if (value)
+		setbits_8(data->base + P(port), BIT(pin));
+	else
+		clrbits_8(data->base + P(port), BIT(pin));
+}
+
+static int rzg2l_gpio_get_value(struct udevice *dev, unsigned int offset)
+{
+	const struct rzg2l_pfc_data *data =
+		(const struct rzg2l_pfc_data *)dev_get_driver_data(dev);
+	const u32 port = RZG2L_PINMUX_TO_PORT(offset);
+	const u8 pin = RZG2L_PINMUX_TO_PIN(offset);
+	u16 pm_state;
+
+	pm_state = (readw(data->base + PM(port)) >> (pin * 2)) & PM_MASK;
+	switch (pm_state) {
+	case PM_INPUT:
+		return !!(readb(data->base + PIN(port)) & BIT(pin));
+	case PM_OUTPUT:
+	case PM_OUTPUT_IEN:
+		return !!(readb(data->base + P(port)) & BIT(pin));
+	default:	/* PM_HIGH_Z */
+		return 0;
+	}
+}
+
+static int rzg2l_gpio_set_value(struct udevice *dev, unsigned int offset,
+				int value)
+{
+	const struct rzg2l_pfc_data *data =
+		(const struct rzg2l_pfc_data *)dev_get_driver_data(dev);
+	const u32 port = RZG2L_PINMUX_TO_PORT(offset);
+	const u8 pin = RZG2L_PINMUX_TO_PIN(offset);
+
+	rzg2l_gpio_set(data, port, pin, (bool)value);
+	return 0;
+}
+
+static void rzg2l_gpio_set_direction(const struct rzg2l_pfc_data *data,
+				     u32 port, u8 pin, bool output)
+{
+	clrsetbits_le16(data->base + PM(port), PM_MASK << (pin * 2),
+			(output ? PM_OUTPUT : PM_INPUT) << (pin * 2));
+}
+
+static int rzg2l_gpio_direction_input(struct udevice *dev, unsigned int offset)
+{
+	const struct rzg2l_pfc_data *data =
+		(const struct rzg2l_pfc_data *)dev_get_driver_data(dev);
+	const u32 port = RZG2L_PINMUX_TO_PORT(offset);
+	const u8 pin = RZG2L_PINMUX_TO_PIN(offset);
+
+	rzg2l_gpio_set_direction(data, port, pin, false);
+	return 0;
+}
+
+static int rzg2l_gpio_direction_output(struct udevice *dev, unsigned int offset,
+				       int value)
+{
+	const struct rzg2l_pfc_data *data =
+		(const struct rzg2l_pfc_data *)dev_get_driver_data(dev);
+	const u32 port = RZG2L_PINMUX_TO_PORT(offset);
+	const u8 pin = RZG2L_PINMUX_TO_PIN(offset);
+
+	rzg2l_gpio_set(data, port, pin, (bool)value);
+	rzg2l_gpio_set_direction(data, port, pin, true);
+	return 0;
+}
+
+static int rzg2l_gpio_request(struct udevice *dev, unsigned int offset,
+			      const char *label)
+{
+	const struct rzg2l_pfc_data *data =
+		(const struct rzg2l_pfc_data *)dev_get_driver_data(dev);
+	const u32 port = RZG2L_PINMUX_TO_PORT(offset);
+	const u8 pin = RZG2L_PINMUX_TO_PIN(offset);
+
+	if (!rzg2l_port_validate(data, port, pin)) {
+		dev_err(dev, "Invalid GPIO %u:%u\n", port, pin);
+		return -EINVAL;
+	}
+
+	/* Select GPIO mode in PMC Register */
+	clrbits_8(data->base + PMC(port), BIT(pin));
+
+	return 0;
+}
+
+static int rzg2l_gpio_get_function(struct udevice *dev, unsigned int offset)
+{
+	const struct rzg2l_pfc_data *data =
+		(const struct rzg2l_pfc_data *)dev_get_driver_data(dev);
+	const u32 port = RZG2L_PINMUX_TO_PORT(offset);
+	const u8 pin = RZG2L_PINMUX_TO_PIN(offset);
+	u16 pm_state;
+	u8 pmc_state;
+
+	if (!rzg2l_port_validate(data, port, pin)) {
+		/* This offset does not correspond to a valid GPIO pin. */
+		return -ENOENT;
+	}
+
+	/* Check if the pin is in GPIO or function mode. */
+	pmc_state = readb(data->base + PMC(port)) & BIT(pin);
+	if (pmc_state)
+		return GPIOF_FUNC;
+
+	/* Check the pin direction. */
+	pm_state = (readw(data->base + PM(port)) >> (pin * 2)) & PM_MASK;
+	switch (pm_state) {
+	case PM_INPUT:
+		return GPIOF_INPUT;
+	case PM_OUTPUT:
+	case PM_OUTPUT_IEN:
+		return GPIOF_OUTPUT;
+	default:	/* PM_HIGH_Z */
+		return GPIOF_UNUSED;
+	}
+}
+
+static const struct dm_gpio_ops rzg2l_gpio_ops = {
+	.direction_input	= rzg2l_gpio_direction_input,
+	.direction_output	= rzg2l_gpio_direction_output,
+	.get_value		= rzg2l_gpio_get_value,
+	.set_value		= rzg2l_gpio_set_value,
+	.request		= rzg2l_gpio_request,
+	.get_function		= rzg2l_gpio_get_function,
+};
+
+static int rzg2l_gpio_probe(struct udevice *dev)
+{
+	struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev);
+	struct ofnode_phandle_args args;
+	int ret;
+
+	uc_priv->bank_name = "rzg2l-pfc-gpio";
+	ret = ofnode_parse_phandle_with_args(dev_ofnode(dev), "gpio-ranges",
+					     NULL, 3, 0, &args);
+	if (ret < 0) {
+		dev_err(dev, "Failed to parse gpio-ranges: %d\n", ret);
+		return -EINVAL;
+	}
+
+	uc_priv->gpio_count = args.args[2];
+	return rzg2l_pfc_enable(dev);
+}
+
+U_BOOT_DRIVER(rzg2l_pfc_gpio) = {
+	.name		= "rzg2l-pfc-gpio",
+	.id		= UCLASS_GPIO,
+	.ops		= &rzg2l_gpio_ops,
+	.probe		= rzg2l_gpio_probe,
+};
diff --git a/drivers/pinctrl/renesas/rzg2l-pfc.c b/drivers/pinctrl/renesas/rzg2l-pfc.c
index ce4062f..7b045f7 100644
--- a/drivers/pinctrl/renesas/rzg2l-pfc.c
+++ b/drivers/pinctrl/renesas/rzg2l-pfc.c
@@ -566,8 +566,10 @@
 {
 	struct rzg2l_pfc_driver_data *driver_data;
 	struct rzg2l_pfc_data *data;
+	struct udevice *pinctrl_dev;
 	struct driver *drv;
 	unsigned int i;
+	int ret;
 
 	driver_data =
 		(struct rzg2l_pfc_driver_data *)dev_get_driver_data(parent);
@@ -594,9 +596,25 @@
 	if (!drv)
 		return -ENOENT;
 
-	return device_bind_with_driver_data(parent, drv, parent->name,
-					    (ulong)data, dev_ofnode(parent),
-					    NULL);
+	ret = device_bind_with_driver_data(parent, drv, parent->name,
+					   (ulong)data, dev_ofnode(parent),
+					   &pinctrl_dev);
+
+	if (!ret && IS_ENABLED(CONFIG_RZG2L_GPIO)) {
+		drv = lists_driver_lookup_name("rzg2l-pfc-gpio");
+		if (!drv) {
+			device_unbind(pinctrl_dev);
+			return -ENOENT;
+		}
+
+		ret = device_bind_with_driver_data(parent, drv, parent->name,
+						   (ulong)data,
+						   dev_ofnode(parent), NULL);
+		if (ret)
+			device_unbind(pinctrl_dev);
+	}
+
+	return ret;
 }
 
 U_BOOT_DRIVER(rzg2l_pfc) = {