Introduce driver for I2C based MCP230xx GPIO chips, which are quite common and already well supported by the Linux kernel.
Signed-off-by: Sebastian Reichel <sebastian.reic...@collabora.com> --- drivers/gpio/Kconfig | 10 ++ drivers/gpio/Makefile | 1 + drivers/gpio/mcp230xx_gpio.c | 235 +++++++++++++++++++++++++++++++++++ 3 files changed, 246 insertions(+) create mode 100644 drivers/gpio/mcp230xx_gpio.c diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index de4dc51d4b48..f93e736639bb 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -179,6 +179,16 @@ config LPC32XX_GPIO help Support for the LPC32XX GPIO driver. +config MCP230XX_GPIO + bool "MCP230XX GPIO driver" + depends on DM + help + Support for Microchip's MCP230XX I2C connected GPIO devices. + The following chips are supported: + - MCP23008 + - MCP23017 + - MCP23018 + config MSCC_SGPIO bool "Microsemi Serial GPIO driver" depends on DM_GPIO && SOC_VCOREIII diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 8541ba0b0aec..00ffcc753534 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -24,6 +24,7 @@ obj-$(CONFIG_KIRKWOOD_GPIO) += kw_gpio.o obj-$(CONFIG_KONA_GPIO) += kona_gpio.o obj-$(CONFIG_MARVELL_GPIO) += mvgpio.o obj-$(CONFIG_MARVELL_MFP) += mvmfp.o +obj-$(CONFIG_MCP230XX_GPIO) += mcp230xx_gpio.o obj-$(CONFIG_MXC_GPIO) += mxc_gpio.o obj-$(CONFIG_MXS_GPIO) += mxs_gpio.o obj-$(CONFIG_PCA953X) += pca953x.o diff --git a/drivers/gpio/mcp230xx_gpio.c b/drivers/gpio/mcp230xx_gpio.c new file mode 100644 index 000000000000..44def86ca7d9 --- /dev/null +++ b/drivers/gpio/mcp230xx_gpio.c @@ -0,0 +1,235 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2021, Collabora Ltd. + * Copyright (C) 2021, General Electric Company + * Author(s): Sebastian Reichel <sebastian.reic...@collabora.com> + */ + +#define LOG_CATEGORY UCLASS_GPIO + +#include <common.h> +#include <errno.h> +#include <dm.h> +#include <i2c.h> +#include <asm/gpio.h> +#include <dm/device_compat.h> +#include <dt-bindings/gpio/gpio.h> + +enum mcp230xx_type { + UNKNOWN = 0, + MCP23008, + MCP23017, + MCP23018, +}; + +#define MCP230XX_IODIR 0x00 +#define MCP230XX_GPPU 0x06 +#define MCP230XX_GPIO 0x09 +#define MCP230XX_OLAT 0x0a + +#define BANKSIZE 8 + +static int mcp230xx_read(struct udevice *dev, uint reg, uint offset) +{ + struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev); + int bank = offset / BANKSIZE; + int mask = 1 << (offset % BANKSIZE); + int shift = (uc_priv->gpio_count / BANKSIZE) - 1; + int ret; + + ret = dm_i2c_reg_read(dev, (reg << shift) + bank); + if (ret < 0) + return ret; + return !!(ret & mask); +} + +static int mcp230xx_write(struct udevice *dev, uint reg, uint offset, bool val) +{ + struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev); + int bank = offset / BANKSIZE; + int mask = 1 << (offset % BANKSIZE); + int shift = (uc_priv->gpio_count / BANKSIZE) - 1; + int regval; + + regval = dm_i2c_reg_read(dev, (reg << shift) + bank); + if (regval < 0) + return regval; + + regval &= ~mask; + if (val) + regval |= mask; + + return dm_i2c_reg_write(dev, (reg << shift) + bank, regval); +} + +static int mcp230xx_get_value(struct udevice *dev, uint offset) +{ + int ret; + + ret = mcp230xx_read(dev, MCP230XX_GPIO, offset); + if (ret < 0) { + dev_err(dev, "%s error: %d\n", __func__, ret); + return ret; + } + + return ret; +} + +static int mcp230xx_set_value(struct udevice *dev, uint offset, int val) +{ + int ret; + + ret = mcp230xx_write(dev, MCP230XX_GPIO, offset, val); + if (ret < 0) { + dev_err(dev, "%s error: %d\n", __func__, ret); + return ret; + } + + return ret; +} + +static int mcp230xx_get_flags(struct udevice *dev, unsigned int offset, + ulong *flags) +{ + int direction, pullup; + + pullup = mcp230xx_read(dev, MCP230XX_GPPU, offset); + if (pullup < 0) { + dev_err(dev, "%s error: %d\n", __func__, pullup); + return pullup; + } + + direction = mcp230xx_read(dev, MCP230XX_IODIR, offset); + if (direction < 0) { + dev_err(dev, "%s error: %d\n", __func__, direction); + return direction; + } + + *flags = direction ? GPIOD_IS_IN : GPIOD_IS_OUT; + + if (pullup) + *flags |= GPIOD_PULL_UP; + + return 0; +} + +static int mcp230xx_set_flags(struct udevice *dev, uint offset, ulong flags) +{ + int direction = !(flags & GPIOD_IS_OUT); + int pullup = !!(flags & GPIOD_PULL_UP); + ulong supported_mask; + int ret; + + /* Note: active-low is ignored (handled by core) */ + supported_mask = GPIOD_ACTIVE_LOW | GPIOD_IS_IN | GPIOD_IS_OUT | GPIOD_PULL_UP; + if (flags & ~supported_mask) + return -EINVAL; + + ret = mcp230xx_write(dev, MCP230XX_GPPU, offset, pullup); + if (ret) { + dev_err(dev, "%s error: %d\n", __func__, ret); + return ret; + } + + ret = mcp230xx_write(dev, MCP230XX_IODIR, offset, direction); + if (ret) { + dev_err(dev, "%s error: %d\n", __func__, ret); + return ret; + } + + return 0; +} + +static int mcp230xx_direction_input(struct udevice *dev, uint offset) +{ + return mcp230xx_set_flags(dev, offset, GPIOD_IS_IN); +} + +static int mcp230xx_direction_output(struct udevice *dev, uint offset, int val) +{ + int ret = mcp230xx_set_value(dev, offset, val); + if (ret < 0) { + dev_err(dev, "%s error: %d\n", __func__, ret); + return ret; + } + return mcp230xx_set_flags(dev, offset, GPIOD_IS_OUT); +} + +static int mcp230xx_get_function(struct udevice *dev, uint offset) +{ + int ret; + + ret = mcp230xx_read(dev, MCP230XX_IODIR, offset); + if (ret < 0) { + dev_err(dev, "%s error: %d\n", __func__, ret); + return ret; + } + + return ret ? GPIOF_INPUT : GPIOF_OUTPUT; +} + +static const struct dm_gpio_ops mcp230xx_ops = { + .direction_input = mcp230xx_direction_input, + .direction_output = mcp230xx_direction_output, + .get_value = mcp230xx_get_value, + .set_value = mcp230xx_set_value, + .get_function = mcp230xx_get_function, + .set_flags = mcp230xx_set_flags, + .get_flags = mcp230xx_get_flags, +}; + +static int mcp230xx_probe(struct udevice *dev) +{ + struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev); + char name[32], label[8], *str; + int addr, gpio_count, size; + const u8 *tmp; + + switch (dev_get_driver_data(dev)) { + case MCP23008: + gpio_count = 8; + break; + case MCP23017: + case MCP23018: + gpio_count = 16; + break; + default: + return -ENODEV; + } + + addr = dev_read_addr(dev); + tmp = dev_read_prop(dev, "label", &size); + if (tmp) { + memcpy(label, tmp, sizeof(label) - 1); + label[sizeof(label) - 1] = '\0'; + snprintf(name, sizeof(name), "%s@%x_", label, addr); + } else { + snprintf(name, sizeof(name), "gpio@%x_", addr); + } + + str = strdup(name); + if (!str) + return -ENOMEM; + + uc_priv->bank_name = str; + uc_priv->gpio_count = gpio_count; + + dev_dbg(dev, "%s is ready\n", str); + + return 0; +} + +static const struct udevice_id mcp230xx_ids[] = { + { .compatible = "microchip,mcp23008", .data = MCP23008, }, + { .compatible = "microchip,mcp23017", .data = MCP23017, }, + { .compatible = "microchip,mcp23018", .data = MCP23018, }, + { } +}; + +U_BOOT_DRIVER(mcp230xx) = { + .name = "mcp230xx", + .id = UCLASS_GPIO, + .ops = &mcp230xx_ops, + .probe = mcp230xx_probe, + .of_match = mcp230xx_ids, +}; -- 2.30.2