From 3462bed51195b86ba9dd45a72e6d77b2908e10cc Mon Sep 17 00:00:00 2001 From: Filippo Copetti Date: Thu, 14 Dec 2023 08:37:54 +0100 Subject: [PATCH 2/2] gpio: add M058SSAN gpio driver "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 --- 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 --git a/MAINTAINERS b/MAINTAINERS index 6c612a4f3c8e..ae2c184e7315 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13441,6 +13441,11 @@ F: drivers/nubus/ F: include/linux/nubus.h F: include/uapi/linux/nubus.h +NUVOTON M058SSAN GPIO DRIVER +M: Filippo Copetti +S: Maintained +F: drivers/gpio/gpio-m058ssan.c + NVIDIA (rivafb and nvidiafb) FRAMEBUFFER DRIVER M: Antonino Daplas L: linux-fbdev@vger.kernel.org diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index c1e8597e3227..d45d7b13e385 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 9ef8d56a6232..ed76dc626698 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 000000000000..44794daf9bd8 --- /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 + * + * Based on gpio-tpic2810.c + * Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com/ + * Andrew F. Davis + */ + +#include +#include +#include +#include +#include + +/* + * 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 "); +MODULE_DESCRIPTION("GPIO expander driver for M058SSAN"); +MODULE_LICENSE("GPL v2"); -- 2.17.1