gpio: support gpio expand chip aw9110

Signed-off-by: Jake Wu <jake.wu@rock-chips.com>
Change-Id: I370b77f578e7937712eedec830a1334ce938667b
This commit is contained in:
Jake Wu
2022-07-28 09:24:40 +08:00
committed by Tao Huang
parent 60da6cd444
commit 5fb8464990
3 changed files with 516 additions and 0 deletions

View File

@@ -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

View File

@@ -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

500
drivers/gpio/gpio-aw9110.c Normal file
View File

@@ -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 <linux/gpio/driver.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/irqdomain.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/of_gpio.h>
#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<<offset))
return GPIO_LINE_DIRECTION_IN;
return GPIO_LINE_DIRECTION_OUT;
}
static int aw9110_direction_input(struct gpio_chip *chip, unsigned int offset)
{
struct aw9110 *gpio = gpiochip_get_data(chip);
mutex_lock(&gpio->lock);
/* set direct */
gpio->direct |= (1<<offset);
if (offset < 4)
gpio->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<<offset);
if (offset < 4)
gpio->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 <jake.wu@rock-chips.com>");
MODULE_DESCRIPTION("AW9110 i2c expander gpio driver");