From 5fb84649903547c6fc5a2cd18a008823871ce6f8 Mon Sep 17 00:00:00 2001 From: Jake Wu Date: Thu, 28 Jul 2022 09:24:40 +0800 Subject: [PATCH] gpio: support gpio expand chip aw9110 Signed-off-by: Jake Wu Change-Id: I370b77f578e7937712eedec830a1334ce938667b --- drivers/gpio/Kconfig | 15 ++ drivers/gpio/Makefile | 1 + drivers/gpio/gpio-aw9110.c | 500 +++++++++++++++++++++++++++++++++++++ 3 files changed, 516 insertions(+) create mode 100644 drivers/gpio/gpio-aw9110.c diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index d82126d6135d..88edf9d46b37 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -924,6 +924,21 @@ config GPIO_ADNP enough to represent all pins, but the driver will assume a register layout for 64 pins (8 registers). +config GPIO_AW9110 + tristate "AW9110 I2C GPIO expanders" + select GPIOLIB_IRQCHIP + select IRQ_DOMAIN + help + Say yes here to provide access to I2C + GPIO expanders used for additional digital outputs or inputs. + Your board setup code will need to declare the expanders in + use, and assign numbers to the GPIOs they expose. Those GPIOs + can then be used from drivers and other kernel code, just like + other GPIOs, but only accessible from task contexts. + + This driver provides an in-kernel interface to those GPIOs using + platform-neutral GPIO calls. + config GPIO_GW_PLD tristate "Gateworks PLD GPIO Expander" depends on OF_GPIO diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 7b42304be907..87c2046799cf 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -36,6 +36,7 @@ obj-$(CONFIG_GPIO_ARIZONA) += gpio-arizona.o obj-$(CONFIG_GPIO_ASPEED) += gpio-aspeed.o obj-$(CONFIG_GPIO_ASPEED_SGPIO) += gpio-aspeed-sgpio.o obj-$(CONFIG_GPIO_ATH79) += gpio-ath79.o +obj-$(CONFIG_GPIO_AW9110) += gpio-aw9110.o obj-$(CONFIG_GPIO_BCM_KONA) += gpio-bcm-kona.o obj-$(CONFIG_GPIO_BCM_XGS_IPROC) += gpio-xgs-iproc.o obj-$(CONFIG_GPIO_BD70528) += gpio-bd70528.o diff --git a/drivers/gpio/gpio-aw9110.c b/drivers/gpio/gpio-aw9110.c new file mode 100644 index 000000000000..ab97726fc7ce --- /dev/null +++ b/drivers/gpio/gpio-aw9110.c @@ -0,0 +1,500 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for aw9110 I2C GPIO expanders + * + * Copyright (c) 2021 Rockchip Electronics Co. Ltd. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define REG_INPUT_P0 0x00 +#define REG_INPUT_P1 0x01 +#define REG_OUTPUT_P0 0x02 +#define REG_OUTPUT_P1 0x03 +#define REG_CONFIG_P0 0x04 +#define REG_CONFIG_P1 0x05 +#define REG_INT_P0 0x06 +#define REG_INT_P1 0x07 +#define REG_ID 0x10 +#define REG_CTRL 0x11 +#define REG_WORK_MODE_P0 0x12 +#define REG_WORK_MODE_P1 0x13 +#define REG_EN_BREATH 0x14 +#define REG_FADE_TIME 0x15 +#define REG_FULL_TIME 0x16 +#define REG_DLY0_BREATH 0x17 +#define REG_DLY1_BREATH 0x18 +#define REG_DLY2_BREATH 0x19 +#define REG_DLY3_BREATH 0x1a +#define REG_DLY4_BREATH 0x1b +#define REG_DLY5_BREATH 0x1c +#define REG_DIM00 0x20 +#define REG_DIM01 0x21 +#define REG_DIM02 0x22 +#define REG_DIM03 0x23 +#define REG_DIM04 0x24 +#define REG_DIM05 0x25 +#define REG_DIM06 0x26 +#define REG_DIM07 0x27 +#define REG_DIM08 0x28 +#define REG_DIM09 0x29 +#define REG_SWRST 0x7F +#define REG_81H 0x81 + + +static const struct i2c_device_id aw9110_id[] = { + { "aw9110", 10 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, aw9110_id); + +#ifdef CONFIG_OF +static const struct of_device_id aw9110_of_table[] = { + { .compatible = "awinic,aw9110" }, + { } +}; +MODULE_DEVICE_TABLE(of, aw9110_of_table); +#endif + + +struct aw9110 { + struct gpio_chip chip; + struct irq_chip irqchip; + struct i2c_client *client; + struct mutex lock; /* protect 'out' */ + unsigned int out; /* software latch */ + unsigned int direct; /* gpio direct */ + unsigned int status; /* current status */ + unsigned int irq_enabled; /* enabled irqs */ + + struct device *dev; + int shdn_en; /* shutdown ctrl */ + + int (*write)(struct i2c_client *client, u8 reg, u8 data); + int (*read)(struct i2c_client *client, u8 reg); +}; + + +static int aw9110_i2c_write_le8(struct i2c_client *client, u8 reg, u8 data) +{ + return i2c_smbus_write_byte_data(client, reg, data); +} + +static int aw9110_i2c_read_le8(struct i2c_client *client, u8 reg) +{ + return (int)i2c_smbus_read_byte_data(client, reg); +} + +static int aw9110_get(struct gpio_chip *chip, unsigned int offset) +{ + struct aw9110 *gpio = gpiochip_get_data(chip); + int value = 0; + + mutex_lock(&gpio->lock); + + if (offset < 4) { + value = gpio->read(gpio->client, REG_INPUT_P1); + mutex_unlock(&gpio->lock); + + value = (value < 0) ? value : !!(value & (1 << offset)); + } else { + value = gpio->read(gpio->client, REG_INPUT_P0); + mutex_unlock(&gpio->lock); + + value = (value < 0) ? value : !!((value<<4) & (1 << offset)); + } + + return value; +} + +static int aw9110_get_direction(struct gpio_chip *chip, unsigned int offset) +{ + struct aw9110 *gpio = gpiochip_get_data(chip); + unsigned int reg_val; + + reg_val = gpio->direct; + + dev_dbg(gpio->dev, "direct get: %04X, pin:%d\n", reg_val, offset); + + if (reg_val & (1<lock); + + /* set direct */ + gpio->direct |= (1<write(gpio->client, REG_CONFIG_P1, gpio->direct&0x0F); + else + gpio->write(gpio->client, REG_CONFIG_P0, (gpio->direct >> 4)&0x3F); + + mutex_unlock(&gpio->lock); + + dev_dbg(gpio->dev, "direct in: %04X, pin:%d\n", gpio->direct, offset); + + return 0; +} + +static int aw9110_direction_output(struct gpio_chip *chip, unsigned int offset, int value) +{ + struct aw9110 *gpio = gpiochip_get_data(chip); + + /* set level */ + chip->set(chip, offset, value); + + mutex_lock(&gpio->lock); + + /* set direct */ + gpio->direct &= ~(1<write(gpio->client, REG_CONFIG_P1, gpio->direct&0x0F); + else + gpio->write(gpio->client, REG_CONFIG_P0, (gpio->direct >> 4)&0x3F); + + mutex_unlock(&gpio->lock); + + dev_dbg(gpio->dev, "direct out: %04X, pin:%d\n", gpio->direct, offset); + return 0; +} + +static void aw9110_set(struct gpio_chip *chip, unsigned int offset, int value) +{ + struct aw9110 *gpio = gpiochip_get_data(chip); + unsigned int bit = 1 << offset; + + mutex_lock(&gpio->lock); + + if (value) + gpio->out |= bit; + else + gpio->out &= ~bit; + + if (offset < 4) + gpio->write(gpio->client, REG_OUTPUT_P1, gpio->out >> 0); + else + gpio->write(gpio->client, REG_OUTPUT_P0, gpio->out >> 4); + + mutex_unlock(&gpio->lock); +} + +/*-------------------------------------------------------------------------*/ + +static irqreturn_t aw9110_irq(int irq, void *data) +{ + struct aw9110 *gpio = data; + unsigned long change, i, status = 0; + + int value = 0; + int nirq; + + value = gpio->read(gpio->client, REG_INPUT_P1); + status |= (value < 0) ? 0 : value; + + value = gpio->read(gpio->client, REG_INPUT_P0); + status |= (value < 0) ? 0 : (value<<4); + + + /* + * call the interrupt handler iff gpio is used as + * interrupt source, just to avoid bad irqs + */ + mutex_lock(&gpio->lock); + change = (gpio->status ^ status) & gpio->irq_enabled; + gpio->status = status; + mutex_unlock(&gpio->lock); + + for_each_set_bit(i, &change, gpio->chip.ngpio) { + nirq = irq_find_mapping(gpio->chip.irq.domain, i); + if (nirq) { + dev_dbg(gpio->dev, "status:%04lx,change:%04lx,index:%ld,nirq:%d\n", + status, change, i, nirq); + handle_nested_irq(nirq); + } + } + + return IRQ_HANDLED; +} + +/* + * NOP functions + */ +static void aw9110_noop(struct irq_data *data) { } + +static int aw9110_irq_set_wake(struct irq_data *data, unsigned int on) +{ + struct aw9110 *gpio = irq_data_get_irq_chip_data(data); + + return irq_set_irq_wake(gpio->client->irq, on); +} + +static void aw9110_irq_enable(struct irq_data *data) +{ + struct aw9110 *gpio = irq_data_get_irq_chip_data(data); + + gpio->irq_enabled |= (1 << data->hwirq); +} + +static void aw9110_irq_disable(struct irq_data *data) +{ + struct aw9110 *gpio = irq_data_get_irq_chip_data(data); + + gpio->irq_enabled &= ~(1 << data->hwirq); +} + +static void aw9110_irq_bus_lock(struct irq_data *data) +{ + struct aw9110 *gpio = irq_data_get_irq_chip_data(data); + + mutex_lock(&gpio->lock); +} + +static void aw9110_irq_bus_sync_unlock(struct irq_data *data) +{ + struct aw9110 *gpio = irq_data_get_irq_chip_data(data); + + mutex_unlock(&gpio->lock); +} + +static void aw9110_state_init(struct aw9110 *gpio) +{ + /* out4-9 push-pull */ + gpio->write(gpio->client, REG_CTRL, (1<<4)); + + /* work mode : gpio */ + gpio->write(gpio->client, REG_WORK_MODE_P1, 0x0F); + gpio->write(gpio->client, REG_WORK_MODE_P0, 0x3F); + + /* default direct */ + gpio->direct = 0x03FF; /* 0: output, 1:input */ + gpio->write(gpio->client, REG_CONFIG_P1, gpio->direct & 0x0F); + gpio->write(gpio->client, REG_CONFIG_P0, (gpio->direct>>4) & 0x3F); + + /* interrupt enable */ + gpio->irq_enabled = 0x03FF; /* 0: disable 1:enable, chip: 0:enable, 1: disable */ + gpio->write(gpio->client, REG_INT_P1, ((~gpio->irq_enabled) >> 0)&0x0F); + gpio->write(gpio->client, REG_INT_P0, ((~gpio->irq_enabled) >> 4)&0x3F); + + /* clear interrupt */ + gpio->read(gpio->client, REG_INPUT_P1); + gpio->read(gpio->client, REG_INPUT_P1); +} + +static int aw9110_parse_dt(struct aw9110 *chip, struct i2c_client *client) +{ + struct device_node *np = client->dev.of_node; + int ret = 0; + + /* shdn_en */ + ret = of_get_named_gpio(np, "shdn_en", 0); + if (ret < 0) { + dev_err(chip->dev, "of get shdn_en failed\n"); + chip->shdn_en = -1; + } else { + chip->shdn_en = ret; + + ret = devm_gpio_request_one(chip->dev, chip->shdn_en, + GPIOF_OUT_INIT_LOW, "AW9110_SHDN_EN"); + if (ret) { + dev_err(chip->dev, + "devm_gpio_request_one shdn_en failed\n"); + return ret; + } + + /* enable chip */ + gpio_set_value(chip->shdn_en, 1); + } + + return 0; +} + +static int aw9110_check_dev_id(struct i2c_client *client) +{ + int ret; + + ret = aw9110_i2c_read_le8(client, REG_ID); + + if (ret < 0) { + dev_err(&client->dev, "fail to read dev id(%d)\n", ret); + return ret; + } + + dev_info(&client->dev, "dev id : 0x%02x\n", ret); + + return 0; +} + +/*-------------------------------------------------------------------------*/ + +static int aw9110_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct aw9110 *gpio; + int status; + + dev_info(&client->dev, "===aw9110 probe===\n"); + + /* Allocate, initialize, and register this gpio_chip. */ + gpio = devm_kzalloc(&client->dev, sizeof(*gpio), GFP_KERNEL); + if (!gpio) + return -ENOMEM; + + gpio->dev = &client->dev; + + aw9110_parse_dt(gpio, client); + + mutex_init(&gpio->lock); + + gpio->chip.base = -1; + gpio->chip.can_sleep = true; + gpio->chip.parent = &client->dev; + gpio->chip.owner = THIS_MODULE; + gpio->chip.get = aw9110_get; + gpio->chip.set = aw9110_set; + gpio->chip.get_direction = aw9110_get_direction; + gpio->chip.direction_input = aw9110_direction_input; + gpio->chip.direction_output = aw9110_direction_output; + gpio->chip.ngpio = id->driver_data; + + gpio->write = aw9110_i2c_write_le8; + gpio->read = aw9110_i2c_read_le8; + + gpio->chip.label = client->name; + + gpio->client = client; + i2c_set_clientdata(client, gpio); + + status = aw9110_check_dev_id(client); + if (status < 0) { + dev_err(&client->dev, "check device id fail(%d)\n", status); + goto fail; + } + + aw9110_state_init(gpio); + + /* Enable irqchip if we have an interrupt */ + if (client->irq) { + struct gpio_irq_chip *girq; + + gpio->irqchip.name = "aw9110"; + gpio->irqchip.irq_enable = aw9110_irq_enable; + gpio->irqchip.irq_disable = aw9110_irq_disable; + gpio->irqchip.irq_ack = aw9110_noop; + gpio->irqchip.irq_mask = aw9110_noop; + gpio->irqchip.irq_unmask = aw9110_noop; + gpio->irqchip.irq_set_wake = aw9110_irq_set_wake; + gpio->irqchip.irq_bus_lock = aw9110_irq_bus_lock; + gpio->irqchip.irq_bus_sync_unlock = aw9110_irq_bus_sync_unlock; + + status = devm_request_threaded_irq(&client->dev, client->irq, + NULL, aw9110_irq, IRQF_ONESHOT | + IRQF_TRIGGER_FALLING | IRQF_SHARED, + dev_name(&client->dev), gpio); + if (status) + goto fail; + + girq = &gpio->chip.irq; + girq->chip = &gpio->irqchip; + /* This will let us handle the parent IRQ in the driver */ + girq->parent_handler = NULL; + girq->num_parents = 0; + girq->parents = NULL; + girq->default_type = IRQ_TYPE_NONE; + girq->handler = handle_level_irq; + girq->threaded = true; + } + + status = devm_gpiochip_add_data(&client->dev, &gpio->chip, gpio); + if (status < 0) + goto fail; + + dev_info(&client->dev, "probed\n"); + + return 0; + +fail: + dev_err(&client->dev, "probe error %d for '%s'\n", status, + client->name); + + return status; +} + +static int aw9110_pm_resume(struct device *dev) +{ + struct aw9110 *gpio = dev->driver_data; + + /* out4-9 push-pull */ + gpio->write(gpio->client, REG_CTRL, (1<<4)); + + /* work mode : gpio */ + gpio->write(gpio->client, REG_WORK_MODE_P1, 0x0F); + gpio->write(gpio->client, REG_WORK_MODE_P0, 0x3F); + + /* direct */ + //gpio->direct = 0x03FF; /* 0: output, 1:input */ + gpio->write(gpio->client, REG_CONFIG_P1, gpio->direct & 0x0F); + gpio->write(gpio->client, REG_CONFIG_P0, (gpio->direct>>4) & 0x3F); + + /* out */ + gpio->write(gpio->client, REG_OUTPUT_P1, gpio->out >> 0); + gpio->write(gpio->client, REG_OUTPUT_P0, gpio->out >> 4); + + /* interrupt enable */ + //gpio->irq_enabled = 0x03FF; /* 0: disable 1:enable, chip: 0:enable, 1: disable */ + gpio->write(gpio->client, REG_INT_P1, ((~gpio->irq_enabled) >> 0)&0x0F); + gpio->write(gpio->client, REG_INT_P0, ((~gpio->irq_enabled) >> 4)&0x3F); + + return 0; +} + +static const struct dev_pm_ops aw9110_pm_ops = { + .resume = aw9110_pm_resume, +}; + +static struct i2c_driver aw9110_driver = { + .driver = { + .name = "aw9110", + .pm = &aw9110_pm_ops, + .of_match_table = of_match_ptr(aw9110_of_table), + }, + .probe = aw9110_probe, + .id_table = aw9110_id, +}; + +static int __init aw9110_init(void) +{ + return i2c_add_driver(&aw9110_driver); +} +/* register after i2c postcore initcall and before + * subsys initcalls that may rely on these GPIOs + */ +subsys_initcall(aw9110_init); + +static void __exit aw9110_exit(void) +{ + i2c_del_driver(&aw9110_driver); +} +module_exit(aw9110_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Jake Wu "); +MODULE_DESCRIPTION("AW9110 i2c expander gpio driver"); +