drivers: gpio: implement MAX77663 GPIO cell

MAXIM Semiconductor's PMIC, MAX77663 has 8 GPIO pins and 3 GPIO-like
pins. It also supports interrupts from these pins.

Add GPIO driver for these pins to control via GPIO APIs.

Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index ba42b07..250f1eb 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -238,6 +238,15 @@
 	 original maxim device has 8 push/pull outputs,
 	 some clones offers 16bit.
 
+config MAX77663_GPIO
+	bool "MAX77663 GPIO cell of PMIC driver"
+	depends on DM_GPIO && DM_PMIC_MAX77663
+	help
+	  GPIO driver for MAX77663 PMIC from Maxim Semiconductor.
+	  MAX77663 PMIC has 8 pins that can be configured as GPIOs
+	  and 3 GPIO-like pins dedicated for power/reset buttons
+	  and LID sensor.
+
 config MCP230XX_GPIO
 	bool "MCP230XX GPIO driver"
 	depends on DM
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index c8b3fd7..dab3eb9 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -68,6 +68,7 @@
 obj-$(CONFIG_SIFIVE_GPIO)	+= sifive-gpio.o
 obj-$(CONFIG_NOMADIK_GPIO)	+= nmk_gpio.o
 obj-$(CONFIG_MAX7320_GPIO)	+= max7320_gpio.o
+obj-$(CONFIG_$(SPL_)MAX77663_GPIO)	+= max77663_gpio.o
 obj-$(CONFIG_SL28CPLD_GPIO)	+= sl28cpld-gpio.o
 obj-$(CONFIG_ZYNQMP_GPIO_MODEPIN)	+= zynqmp_gpio_modepin.o
 obj-$(CONFIG_SLG7XL45106_I2C_GPO)	+= gpio_slg7xl45106.o
diff --git a/drivers/gpio/max77663_gpio.c b/drivers/gpio/max77663_gpio.c
new file mode 100644
index 0000000..ecb6047
--- /dev/null
+++ b/drivers/gpio/max77663_gpio.c
@@ -0,0 +1,178 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ *  Copyright(C) 2023 Svyatoslav Ryhel <clamor95@gmail.com>
+ */
+
+#include <dm.h>
+#include <asm/gpio.h>
+#include <power/max77663.h>
+#include <power/pmic.h>
+
+#define NUM_ENTRIES				11 /* 8 GPIOs + 3 KEYs  */
+#define NUM_GPIOS				8
+
+#define MAX77663_CNFG1_GPIO			0x36
+#define GPIO_REG_ADDR(offset)			(MAX77663_CNFG1_GPIO + (offset))
+
+#define MAX77663_CNFG_GPIO_DIR_MASK		BIT(1)
+#define MAX77663_CNFG_GPIO_DIR_INPUT		BIT(1)
+#define MAX77663_CNFG_GPIO_DIR_OUTPUT		0
+#define MAX77663_CNFG_GPIO_INPUT_VAL_MASK	BIT(2)
+#define MAX77663_CNFG_GPIO_OUTPUT_VAL_MASK	BIT(3)
+#define MAX77663_CNFG_GPIO_OUTPUT_VAL_HIGH	BIT(3)
+#define MAX77663_CNFG_GPIO_OUTPUT_VAL_LOW	0
+#define MAX77663_CNFG_IRQ			GENMASK(5, 4)
+
+#define MAX77663_ONOFFSTAT_REG			0x15
+#define   EN0					BIT(2) /* KEY 2 */
+#define   ACOK					BIT(1) /* KEY 1 */
+#define   LID					BIT(0) /* KEY 0 */
+
+static int max77663_gpio_direction_input(struct udevice *dev, unsigned int offset)
+{
+	int ret;
+
+	if (offset >= NUM_GPIOS)
+		return 0;
+
+	ret = pmic_clrsetbits(dev->parent, GPIO_REG_ADDR(offset),
+			      MAX77663_CNFG_GPIO_DIR_MASK,
+			      MAX77663_CNFG_GPIO_DIR_INPUT);
+	if (ret < 0)
+		log_debug("%s: CNFG_GPIOx dir update failed: %d\n", __func__, ret);
+
+	return ret;
+}
+
+static int max77663_gpio_direction_output(struct udevice *dev, unsigned int offset,
+					  int value)
+{
+	u8 val;
+	int ret;
+
+	if (offset >= NUM_GPIOS)
+		return -EINVAL;
+
+	val = (value) ? MAX77663_CNFG_GPIO_OUTPUT_VAL_HIGH :
+				MAX77663_CNFG_GPIO_OUTPUT_VAL_LOW;
+
+	ret = pmic_clrsetbits(dev->parent, GPIO_REG_ADDR(offset),
+			      MAX77663_CNFG_GPIO_OUTPUT_VAL_MASK, val);
+	if (ret < 0) {
+		log_debug("%s: CNFG_GPIOx val update failed: %d\n", __func__, ret);
+		return ret;
+	}
+
+	ret = pmic_clrsetbits(dev->parent, GPIO_REG_ADDR(offset),
+			      MAX77663_CNFG_GPIO_DIR_MASK,
+			      MAX77663_CNFG_GPIO_DIR_OUTPUT);
+	if (ret < 0)
+		log_debug("%s: CNFG_GPIOx dir update failed: %d\n", __func__, ret);
+
+	return ret;
+}
+
+static int max77663_gpio_get_value(struct udevice *dev, unsigned int offset)
+{
+	int ret;
+
+	if (offset >= NUM_GPIOS) {
+		ret = pmic_reg_read(dev->parent, MAX77663_ONOFFSTAT_REG);
+		if (ret < 0) {
+			log_debug("%s: ONOFFSTAT_REG read failed: %d\n", __func__, ret);
+			return ret;
+		}
+
+		return !!(ret & BIT(offset - NUM_GPIOS));
+	}
+
+	ret = pmic_reg_read(dev->parent, GPIO_REG_ADDR(offset));
+	if (ret < 0) {
+		log_debug("%s: CNFG_GPIOx read failed: %d\n", __func__, ret);
+		return ret;
+	}
+
+	if (ret & MAX77663_CNFG_GPIO_DIR_MASK)
+		return !!(ret & MAX77663_CNFG_GPIO_INPUT_VAL_MASK);
+	else
+		return !!(ret & MAX77663_CNFG_GPIO_OUTPUT_VAL_MASK);
+}
+
+static int max77663_gpio_set_value(struct udevice *dev, unsigned int offset,
+				   int value)
+{
+	u8 val;
+	int ret;
+
+	if (offset >= NUM_GPIOS)
+		return -EINVAL;
+
+	val = (value) ? MAX77663_CNFG_GPIO_OUTPUT_VAL_HIGH :
+				MAX77663_CNFG_GPIO_OUTPUT_VAL_LOW;
+
+	ret = pmic_clrsetbits(dev->parent, GPIO_REG_ADDR(offset),
+			      MAX77663_CNFG_GPIO_OUTPUT_VAL_MASK, val);
+	if (ret < 0)
+		log_debug("%s: CNFG_GPIO_OUT update failed: %d\n", __func__, ret);
+
+	return ret;
+}
+
+static int max77663_gpio_get_function(struct udevice *dev, unsigned int offset)
+{
+	int ret;
+
+	if (offset >= NUM_GPIOS)
+		return GPIOF_INPUT;
+
+	ret = pmic_reg_read(dev->parent, GPIO_REG_ADDR(offset));
+	if (ret < 0) {
+		log_debug("%s: CNFG_GPIOx read failed: %d\n", __func__, ret);
+		return ret;
+	}
+
+	if (ret & MAX77663_CNFG_GPIO_DIR_MASK)
+		return GPIOF_INPUT;
+	else
+		return GPIOF_OUTPUT;
+}
+
+static const struct dm_gpio_ops max77663_gpio_ops = {
+	.direction_input	= max77663_gpio_direction_input,
+	.direction_output	= max77663_gpio_direction_output,
+	.get_value		= max77663_gpio_get_value,
+	.set_value		= max77663_gpio_set_value,
+	.get_function		= max77663_gpio_get_function,
+};
+
+static int max77663_gpio_probe(struct udevice *dev)
+{
+	struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev);
+	int i, ret;
+
+	uc_priv->gpio_count = NUM_ENTRIES;
+	uc_priv->bank_name = "GPIO";
+
+	/*
+	 * GPIO interrupts may be left ON after bootloader, hence let's
+	 * pre-initialize hardware to the expected state by disabling all
+	 * the interrupts.
+	 */
+	for (i = 0; i < NUM_GPIOS; i++) {
+		ret = pmic_clrsetbits(dev->parent, GPIO_REG_ADDR(i),
+				      MAX77663_CNFG_IRQ, 0);
+		if (ret < 0) {
+			log_debug("%s: failed to disable interrupt: %d\n", __func__, ret);
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+U_BOOT_DRIVER(max77663_gpio) = {
+	.name	= MAX77663_GPIO_DRIVER,
+	.id	= UCLASS_GPIO,
+	.probe	= max77663_gpio_probe,
+	.ops	= &max77663_gpio_ops,
+};