diff mbox series

[1/2,SRU,J] UBUNTU: SAUCE: ODM: gpio: add M058SSAN gpio driver

Message ID 20231228032345.1148513-2-acelan.kao@canonical.com
State New
Headers show
Series Add ODM driver gpio-m058ssan | expand

Commit Message

AceLan Kao Dec. 28, 2023, 3:23 a.m. UTC
From: Filippo Copetti <filippo.copetti@eurotech.com>

BugLink: https://bugs.launchpad.net/bugs/2045386

this driver adds support for M058SSAN chip, implementing an 8 isolated
digital inputs and 8 isolated digital outputs

Signed-off-by: Filippo Copetti <filippo.copetti@eurotech.com>
Signed-off-by: Chia-Lin Kao (AceLan) <acelan.kao@canonical.com>
---
 MAINTAINERS                  |   5 +
 drivers/gpio/Kconfig         |   6 ++
 drivers/gpio/Makefile        |   1 +
 drivers/gpio/gpio-m058ssan.c | 192 +++++++++++++++++++++++++++++++++++
 4 files changed, 204 insertions(+)
 create mode 100644 drivers/gpio/gpio-m058ssan.c
diff mbox series

Patch

diff --git a/MAINTAINERS b/MAINTAINERS
index d43553fe7e0b3..947dcd981d23a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13429,6 +13429,11 @@  F:	drivers/nubus/
 F:	include/linux/nubus.h
 F:	include/uapi/linux/nubus.h
 
+NUVOTON M058SSAN GPIO DRIVER
+M:	Filippo Copetti <filippo.copetti@eurotech.com>
+S:	Maintained
+F:	drivers/gpio/gpio-m058ssan.c
+
 NVIDIA (rivafb and nvidiafb) FRAMEBUFFER DRIVER
 M:	Antonino Daplas <adaplas@gmail.com>
 L:	linux-fbdev@vger.kernel.org
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 1883277836f88..6af9b50f5ffa6 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -1030,6 +1030,12 @@  config GPIO_MAX732X_IRQ
 	  Say yes here to enable the max732x to be used as an interrupt
 	  controller. It requires the driver to be built in the kernel.
 
+config GPIO_M058SSAN
+	tristate "M085 8-DI 8-DO I2C expander"
+	depends on UBUNTU_ODM_DRIVERS
+	help
+	  Say yes here to enable the M058SSAN gpio driver.
+
 config GPIO_PCA953X
 	tristate "PCA95[357]x, PCA9698, TCA64xx, and MAX7310 I/O ports"
 	select REGMAP_I2C
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index 9ef8d56a62327..ed76dc626698b 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -84,6 +84,7 @@  obj-$(CONFIG_GPIO_LP873X)		+= gpio-lp873x.o
 obj-$(CONFIG_GPIO_LP87565)		+= gpio-lp87565.o
 obj-$(CONFIG_GPIO_LPC18XX)		+= gpio-lpc18xx.o
 obj-$(CONFIG_GPIO_LPC32XX)		+= gpio-lpc32xx.o
+obj-$(CONFIG_GPIO_M058SSAN)		+= gpio-m058ssan.o
 obj-$(CONFIG_GPIO_MADERA)		+= gpio-madera.o
 obj-$(CONFIG_GPIO_MAX3191X)		+= gpio-max3191x.o
 obj-$(CONFIG_GPIO_MAX7300)		+= gpio-max7300.o
diff --git a/drivers/gpio/gpio-m058ssan.c b/drivers/gpio/gpio-m058ssan.c
new file mode 100644
index 0000000000000..44794daf9bd81
--- /dev/null
+++ b/drivers/gpio/gpio-m058ssan.c
@@ -0,0 +1,192 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for M058SSAN I2C GPO expander
+ *
+ * Copyright (C) 2023 Filippo Copetti <filippo.copetti@eurotech.com>
+ *
+ * Based on gpio-tpic2810.c
+ * Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com/
+ *	Andrew F. Davis <afd@ti.com>
+ */
+
+#include <linux/gpio/driver.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/property.h>
+
+/*
+ * GPIO bank #1 (offset 0...7) is configured as output
+ * GPIO bank #2 (offset 8...15) is configured as input
+ */
+
+#define M058SSAN_OUTPUT_BANK            0x01
+#define M058SSAN_INPUT_BANK             0x02
+#define M058SSAN_INPUT_OUTPUT_OFFSET    0x07
+
+/**
+ * m058ssan_read(gpio, &gpio->out, 0x02);
+ * o->out * struct m058ssan - GPIO driver data
+ * @chip: GPIO controller chip
+ * @lock: Protects write sequences
+ * @out: Buffer for device register
+ */
+struct m058ssan {
+	struct gpio_chip chip;
+	struct mutex lock;
+	u8 out;
+};
+
+static int m058ssan_read(struct m058ssan *gpio, u8 *value, u8 address)
+{
+	struct i2c_client *client = to_i2c_client(gpio->chip.parent);
+	int ret;
+
+	ret = i2c_smbus_read_byte_data(client, address);
+
+	if (ret < 0)
+		return ret;
+
+	*value = ret;
+
+	return 0;
+}
+
+static int m058ssan_write(struct m058ssan *gpio, u8 value, u8 address)
+{
+	struct i2c_client *client = to_i2c_client(gpio->chip.parent);
+
+	return i2c_smbus_write_byte_data(client, address, value);
+}
+
+static int m058ssan_get_direction(struct gpio_chip *chip, unsigned int offset)
+{
+	if (offset > M058SSAN_INPUT_OUTPUT_OFFSET)
+		return GPIO_LINE_DIRECTION_IN;
+	else
+		return GPIO_LINE_DIRECTION_OUT;
+}
+
+static int m058ssan_gpio_direction_input(struct gpio_chip *gc, unsigned int offset)
+{
+	if (offset > M058SSAN_INPUT_OUTPUT_OFFSET)
+		return 0;
+	else
+		return -1;
+}
+
+static int m058ssan_gpio_direction_output(struct gpio_chip *gc, unsigned int offset, int value)
+{
+	if (offset > M058SSAN_INPUT_OUTPUT_OFFSET)
+		return -1;
+	else
+		return 0;
+}
+
+static int m058ssan_get(struct gpio_chip *chip, unsigned int offset)
+{
+	struct m058ssan *gpio = gpiochip_get_data(chip);
+	u8 buffer;
+	int ret;
+
+	if (offset > M058SSAN_INPUT_OUTPUT_OFFSET) {
+		ret = m058ssan_read(gpio, &buffer, M058SSAN_INPUT_BANK);
+		offset -= (M058SSAN_INPUT_OUTPUT_OFFSET + 1);
+	} else {
+		ret = m058ssan_read(gpio, &buffer, M058SSAN_OUTPUT_BANK);
+	}
+	if (ret)
+		return ret;
+
+	return buffer & BIT(offset);
+}
+
+static void m058ssan_set(struct gpio_chip *chip, unsigned int offset, int value)
+{
+	struct m058ssan *gpio = gpiochip_get_data(chip);
+	u8 buffer;
+	int ret;
+
+	if (offset > M058SSAN_INPUT_OUTPUT_OFFSET)
+		return;
+
+	mutex_lock(&gpio->lock);
+
+	buffer = gpio->out;
+
+	if (value)
+		buffer |= BIT(offset);
+	else
+		buffer &= ~BIT(offset);
+
+	ret = m058ssan_write(gpio, buffer, M058SSAN_OUTPUT_BANK);
+
+	if (ret)
+		goto out;
+
+	gpio->out = buffer;
+
+out:
+	mutex_unlock(&gpio->lock);
+}
+
+static int m058ssan_probe(struct i2c_client *client)
+{
+	struct m058ssan *gpio;
+
+	gpio = devm_kzalloc(&client->dev, sizeof(*gpio), GFP_KERNEL);
+
+	if (!gpio)
+		return -ENOMEM;
+
+	gpio->chip.label = client->name;
+	gpio->chip.parent = &client->dev;
+	gpio->chip.owner = THIS_MODULE;
+	gpio->chip.get_direction = m058ssan_get_direction;
+	gpio->chip.direction_input = m058ssan_gpio_direction_input;
+	gpio->chip.direction_output = m058ssan_gpio_direction_output;
+	gpio->chip.get = m058ssan_get;
+	gpio->chip.set = m058ssan_set;
+	gpio->chip.base = -1;
+	gpio->chip.ngpio = 16;
+	gpio->chip.can_sleep = true;
+
+	mutex_init(&gpio->lock);
+
+	/* Read the current output level */
+	if (gpio->out > M058SSAN_INPUT_OUTPUT_OFFSET)
+		m058ssan_read(gpio, &gpio->out, M058SSAN_INPUT_BANK);
+	else
+		m058ssan_read(gpio, &gpio->out, M058SSAN_OUTPUT_BANK);
+
+	return devm_gpiochip_add_data(&client->dev, &gpio->chip, gpio);
+}
+
+static const struct i2c_device_id m058ssan_id_table[] = {
+	{"m058ssan", 4},
+	{ /* sentinel */ }
+};
+
+MODULE_DEVICE_TABLE(i2c, m058ssan_id_table);
+
+static const struct of_device_id m058ssan_of_match_table[] = {
+	{.compatible = "nuvoton,m058ssan", .data = (void *)4},
+	{ /* sentinel */ }
+};
+
+MODULE_DEVICE_TABLE(of, m058ssan_of_match_table);
+
+static struct i2c_driver m058ssan_driver = {
+	.driver = {
+		   .name = "m058ssan",
+		   .of_match_table = m058ssan_of_match_table,
+		   },
+	.probe_new = m058ssan_probe,
+	.id_table = m058ssan_id_table,
+};
+
+module_i2c_driver(m058ssan_driver);
+
+MODULE_AUTHOR("Filippo Copetti <filippo.copetti@eurotech.com>");
+MODULE_DESCRIPTION("GPIO expander driver for M058SSAN");
+MODULE_LICENSE("GPL v2");