mirror of
https://github.com/torvalds/linux.git
synced 2024-11-11 14:42:24 +00:00
114b5f8f7e
Core changes: - A patch series from Hans Verkuil to make it possible to enable/disable IRQs on a GPIO line at runtime and drive GPIO lines as output without having to put/get them from scratch. The irqchip callbacks have been improved so that they can use only the fastpatch callbacks to enable/disable irqs like any normal irqchip, especially the gpiod_lock_as_irq() has been improved to be callable in fastpath context. A bunch of rework had to be done to achieve this but it is a big win since I never liked to restrict this to slowpath. The only call requireing slowpath was try_module_get() and this is kept at the .request_resources() slowpath callback. In the GPIO CEC driver this is a big win sine a single line is used for both outgoing and incoming traffic, and this needs to use IRQs for incoming traffic while actively driving the line for outgoing traffic. - Janusz Krzysztofik improved the GPIO array API to pass a "cookie" (struct gpio_array) and a bitmap for setting or getting multiple GPIO lines at once. This improvement orginated in a specific need to speed up an OMAP1 driver and has led to a much better API and real performance gains when the state of the array can be used to bypass a lot of checks and code when we want things to go really fast. The previous code would minimize the number of calls down to the driver callbacks assuming the CPU speed was orders of magnitude faster than the I/O latency, but this assumption was wrong on several platforms: what we needed to do was to profile and improve the speed on the hot path of the array functions and this change is now completed. - Clean out the painful and hard to grasp BNF experiments from the device tree bindings. Future approaches are looking into using JSON schema for this purpose. (Rob Herring is floating a patch series.) New drivers: - The RCAR driver now supports r8a774a1 (RZ/G2M). - Synopsys GPIO via CREGs driver. Major improvements: - Modernization of the EP93xx driver to use irqdomain and other contemporary concepts. - The ingenic driver has been merged into the Ingenic pin control driver and removed from the GPIO subsystem. - Debounce support in the ftgpio010 driver. -----BEGIN PGP SIGNATURE----- iQIcBAABAgAGBQJbzdyOAAoJEEEQszewGV1zfYcP/0HBEAOPhHD/i5OQxfKs1msh mFT/t/IbTmRpCgbEv4CDx4Kc/InE0sUnQr1TL/1WvU6uObM6Ncxq5Z90MvyrgzYu BqQHq2k2tORvkVSNRxcfD/BAAoo1EerXts1kDhutvdKfepfS6DxpENwzvsFgkVlq 2jj1cdZztjv8A+9cspHDpQP+jDvl1VSc10nR5fRu1TttSpUwzRJaB30NBNXJmMJc 5KUr67lEbsQRPsBvFErU11bydPqhfT+pXmODcfIwS0EtATQ8WC5mkSb/Ooei0fvT oZ7uR3Os8tMf7isOKssEyFabKwhnfOEt6TBt9em0TfUtInOo0Dc7r8TfBcn57fyZ xg2R9DQEVRfac8bjhF/BI5KHuN9IMGDDvj6XApumQVliZbISRjMnh3jte6RpcV0A Ejqz8FeDY13qvEdOnW1EPpwmXdDVWiEAq0ebGLStKNls+/4gB2HmyxGUOzJf+og5 hujsxcJzGQqjCe0moeY/1d7vsy0ZjbHoS+p5fy79U212y2O7onEzFU92AX89bxKC rx2eCNmiZxCUy1nqu8edO62VnH6QdnqG3o+a4DJfCSHPvFM/E/NX9zHemZubQQ4I rYXNy4bL4tEG9cqWMfBxWrpiDZw7H6l8kXwdZG8IMyRU9BcKu96amgZ+jBXwzoaB JZelAAUWB9APghJYFr7o =YosT -----END PGP SIGNATURE----- Merge tag 'gpio-v4.20-1' of git://git.kernel.org/pub/scm/linux/kernel/git/linusw/linux-gpio Pull GPIO updates from Linus Walleij: "This is the bulk of GPIO changes for the v4.20 series: Core changes: - A patch series from Hans Verkuil to make it possible to enable/disable IRQs on a GPIO line at runtime and drive GPIO lines as output without having to put/get them from scratch. The irqchip callbacks have been improved so that they can use only the fastpatch callbacks to enable/disable irqs like any normal irqchip, especially the gpiod_lock_as_irq() has been improved to be callable in fastpath context. A bunch of rework had to be done to achieve this but it is a big win since I never liked to restrict this to slowpath. The only call requireing slowpath was try_module_get() and this is kept at the .request_resources() slowpath callback. In the GPIO CEC driver this is a big win sine a single line is used for both outgoing and incoming traffic, and this needs to use IRQs for incoming traffic while actively driving the line for outgoing traffic. - Janusz Krzysztofik improved the GPIO array API to pass a "cookie" (struct gpio_array) and a bitmap for setting or getting multiple GPIO lines at once. This improvement orginated in a specific need to speed up an OMAP1 driver and has led to a much better API and real performance gains when the state of the array can be used to bypass a lot of checks and code when we want things to go really fast. The previous code would minimize the number of calls down to the driver callbacks assuming the CPU speed was orders of magnitude faster than the I/O latency, but this assumption was wrong on several platforms: what we needed to do was to profile and improve the speed on the hot path of the array functions and this change is now completed. - Clean out the painful and hard to grasp BNF experiments from the device tree bindings. Future approaches are looking into using JSON schema for this purpose. (Rob Herring is floating a patch series.) New drivers: - The RCAR driver now supports r8a774a1 (RZ/G2M). - Synopsys GPIO via CREGs driver. Major improvements: - Modernization of the EP93xx driver to use irqdomain and other contemporary concepts. - The ingenic driver has been merged into the Ingenic pin control driver and removed from the GPIO subsystem. - Debounce support in the ftgpio010 driver" * tag 'gpio-v4.20-1' of git://git.kernel.org/pub/scm/linux/kernel/git/linusw/linux-gpio: (116 commits) gpio: Clarify kerneldoc on gpiochip_set_chained_irqchip() gpio: Remove unused 'irqchip' argument to gpiochip_set_cascaded_irqchip() gpio: Drop parent irq assignment during cascade setup mmc: pwrseq_simple: Fix incorrect handling of GPIO bitmap gpio: fix SNPS_CREG kconfig dependency warning gpiolib: Initialize gdev field before is used gpio: fix kernel-doc after devres.c file rename gpio: fix doc string for devm_gpiochip_add_data() to not talk about irq_chip gpio: syscon: Fix possible NULL ptr usage gpiolib: Show correct direction from the beginning pinctrl: msm: Use init_valid_mask exported function gpiolib: Add init_valid_mask exported function GPIO: add single-register GPIO via CREG driver dt-bindings: Document the Synopsys GPIO via CREG bindings gpio: mockup: use device properties instead of platform_data gpio: Slightly more helpful debugfs gpio: omap: Remove set but not used variable 'dev' gpio: omap: drop omap_gpio_list Accept partial 'gpio-line-names' property. gpio: omap: get rid of the conditional PM runtime calls ...
500 lines
12 KiB
C
500 lines
12 KiB
C
/*
|
|
* GPIO Chip driver for Analog Devices
|
|
* ADP5588/ADP5587 I/O Expander and QWERTY Keypad Controller
|
|
*
|
|
* Copyright 2009-2010 Analog Devices Inc.
|
|
*
|
|
* Licensed under the GPL-2 or later.
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/init.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/gpio/driver.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/irq.h>
|
|
|
|
#include <linux/platform_data/adp5588.h>
|
|
|
|
#define DRV_NAME "adp5588-gpio"
|
|
|
|
/*
|
|
* Early pre 4.0 Silicon required to delay readout by at least 25ms,
|
|
* since the Event Counter Register updated 25ms after the interrupt
|
|
* asserted.
|
|
*/
|
|
#define WA_DELAYED_READOUT_REVID(rev) ((rev) < 4)
|
|
|
|
struct adp5588_gpio {
|
|
struct i2c_client *client;
|
|
struct gpio_chip gpio_chip;
|
|
struct mutex lock; /* protect cached dir, dat_out */
|
|
/* protect serialized access to the interrupt controller bus */
|
|
struct mutex irq_lock;
|
|
unsigned gpio_start;
|
|
unsigned irq_base;
|
|
uint8_t dat_out[3];
|
|
uint8_t dir[3];
|
|
uint8_t int_lvl[3];
|
|
uint8_t int_en[3];
|
|
uint8_t irq_mask[3];
|
|
uint8_t irq_stat[3];
|
|
uint8_t int_input_en[3];
|
|
uint8_t int_lvl_cached[3];
|
|
};
|
|
|
|
static int adp5588_gpio_read(struct i2c_client *client, u8 reg)
|
|
{
|
|
int ret = i2c_smbus_read_byte_data(client, reg);
|
|
|
|
if (ret < 0)
|
|
dev_err(&client->dev, "Read Error\n");
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int adp5588_gpio_write(struct i2c_client *client, u8 reg, u8 val)
|
|
{
|
|
int ret = i2c_smbus_write_byte_data(client, reg, val);
|
|
|
|
if (ret < 0)
|
|
dev_err(&client->dev, "Write Error\n");
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int adp5588_gpio_get_value(struct gpio_chip *chip, unsigned off)
|
|
{
|
|
struct adp5588_gpio *dev = gpiochip_get_data(chip);
|
|
unsigned bank = ADP5588_BANK(off);
|
|
unsigned bit = ADP5588_BIT(off);
|
|
int val;
|
|
|
|
mutex_lock(&dev->lock);
|
|
|
|
if (dev->dir[bank] & bit)
|
|
val = dev->dat_out[bank];
|
|
else
|
|
val = adp5588_gpio_read(dev->client, GPIO_DAT_STAT1 + bank);
|
|
|
|
mutex_unlock(&dev->lock);
|
|
|
|
return !!(val & bit);
|
|
}
|
|
|
|
static void adp5588_gpio_set_value(struct gpio_chip *chip,
|
|
unsigned off, int val)
|
|
{
|
|
unsigned bank, bit;
|
|
struct adp5588_gpio *dev = gpiochip_get_data(chip);
|
|
|
|
bank = ADP5588_BANK(off);
|
|
bit = ADP5588_BIT(off);
|
|
|
|
mutex_lock(&dev->lock);
|
|
if (val)
|
|
dev->dat_out[bank] |= bit;
|
|
else
|
|
dev->dat_out[bank] &= ~bit;
|
|
|
|
adp5588_gpio_write(dev->client, GPIO_DAT_OUT1 + bank,
|
|
dev->dat_out[bank]);
|
|
mutex_unlock(&dev->lock);
|
|
}
|
|
|
|
static int adp5588_gpio_direction_input(struct gpio_chip *chip, unsigned off)
|
|
{
|
|
int ret;
|
|
unsigned bank;
|
|
struct adp5588_gpio *dev = gpiochip_get_data(chip);
|
|
|
|
bank = ADP5588_BANK(off);
|
|
|
|
mutex_lock(&dev->lock);
|
|
dev->dir[bank] &= ~ADP5588_BIT(off);
|
|
ret = adp5588_gpio_write(dev->client, GPIO_DIR1 + bank, dev->dir[bank]);
|
|
mutex_unlock(&dev->lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int adp5588_gpio_direction_output(struct gpio_chip *chip,
|
|
unsigned off, int val)
|
|
{
|
|
int ret;
|
|
unsigned bank, bit;
|
|
struct adp5588_gpio *dev = gpiochip_get_data(chip);
|
|
|
|
bank = ADP5588_BANK(off);
|
|
bit = ADP5588_BIT(off);
|
|
|
|
mutex_lock(&dev->lock);
|
|
dev->dir[bank] |= bit;
|
|
|
|
if (val)
|
|
dev->dat_out[bank] |= bit;
|
|
else
|
|
dev->dat_out[bank] &= ~bit;
|
|
|
|
ret = adp5588_gpio_write(dev->client, GPIO_DAT_OUT1 + bank,
|
|
dev->dat_out[bank]);
|
|
ret |= adp5588_gpio_write(dev->client, GPIO_DIR1 + bank,
|
|
dev->dir[bank]);
|
|
mutex_unlock(&dev->lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
#ifdef CONFIG_GPIO_ADP5588_IRQ
|
|
static int adp5588_gpio_to_irq(struct gpio_chip *chip, unsigned off)
|
|
{
|
|
struct adp5588_gpio *dev = gpiochip_get_data(chip);
|
|
|
|
return dev->irq_base + off;
|
|
}
|
|
|
|
static void adp5588_irq_bus_lock(struct irq_data *d)
|
|
{
|
|
struct adp5588_gpio *dev = irq_data_get_irq_chip_data(d);
|
|
|
|
mutex_lock(&dev->irq_lock);
|
|
}
|
|
|
|
/*
|
|
* genirq core code can issue chip->mask/unmask from atomic context.
|
|
* This doesn't work for slow busses where an access needs to sleep.
|
|
* bus_sync_unlock() is therefore called outside the atomic context,
|
|
* syncs the current irq mask state with the slow external controller
|
|
* and unlocks the bus.
|
|
*/
|
|
|
|
static void adp5588_irq_bus_sync_unlock(struct irq_data *d)
|
|
{
|
|
struct adp5588_gpio *dev = irq_data_get_irq_chip_data(d);
|
|
int i;
|
|
|
|
for (i = 0; i <= ADP5588_BANK(ADP5588_MAXGPIO); i++) {
|
|
if (dev->int_input_en[i]) {
|
|
mutex_lock(&dev->lock);
|
|
dev->dir[i] &= ~dev->int_input_en[i];
|
|
dev->int_input_en[i] = 0;
|
|
adp5588_gpio_write(dev->client, GPIO_DIR1 + i,
|
|
dev->dir[i]);
|
|
mutex_unlock(&dev->lock);
|
|
}
|
|
|
|
if (dev->int_lvl_cached[i] != dev->int_lvl[i]) {
|
|
dev->int_lvl_cached[i] = dev->int_lvl[i];
|
|
adp5588_gpio_write(dev->client, GPIO_INT_LVL1 + i,
|
|
dev->int_lvl[i]);
|
|
}
|
|
|
|
if (dev->int_en[i] ^ dev->irq_mask[i]) {
|
|
dev->int_en[i] = dev->irq_mask[i];
|
|
adp5588_gpio_write(dev->client, GPIO_INT_EN1 + i,
|
|
dev->int_en[i]);
|
|
}
|
|
}
|
|
|
|
mutex_unlock(&dev->irq_lock);
|
|
}
|
|
|
|
static void adp5588_irq_mask(struct irq_data *d)
|
|
{
|
|
struct adp5588_gpio *dev = irq_data_get_irq_chip_data(d);
|
|
unsigned gpio = d->irq - dev->irq_base;
|
|
|
|
dev->irq_mask[ADP5588_BANK(gpio)] &= ~ADP5588_BIT(gpio);
|
|
}
|
|
|
|
static void adp5588_irq_unmask(struct irq_data *d)
|
|
{
|
|
struct adp5588_gpio *dev = irq_data_get_irq_chip_data(d);
|
|
unsigned gpio = d->irq - dev->irq_base;
|
|
|
|
dev->irq_mask[ADP5588_BANK(gpio)] |= ADP5588_BIT(gpio);
|
|
}
|
|
|
|
static int adp5588_irq_set_type(struct irq_data *d, unsigned int type)
|
|
{
|
|
struct adp5588_gpio *dev = irq_data_get_irq_chip_data(d);
|
|
uint16_t gpio = d->irq - dev->irq_base;
|
|
unsigned bank, bit;
|
|
|
|
if ((type & IRQ_TYPE_EDGE_BOTH)) {
|
|
dev_err(&dev->client->dev, "irq %d: unsupported type %d\n",
|
|
d->irq, type);
|
|
return -EINVAL;
|
|
}
|
|
|
|
bank = ADP5588_BANK(gpio);
|
|
bit = ADP5588_BIT(gpio);
|
|
|
|
if (type & IRQ_TYPE_LEVEL_HIGH)
|
|
dev->int_lvl[bank] |= bit;
|
|
else if (type & IRQ_TYPE_LEVEL_LOW)
|
|
dev->int_lvl[bank] &= ~bit;
|
|
else
|
|
return -EINVAL;
|
|
|
|
dev->int_input_en[bank] |= bit;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct irq_chip adp5588_irq_chip = {
|
|
.name = "adp5588",
|
|
.irq_mask = adp5588_irq_mask,
|
|
.irq_unmask = adp5588_irq_unmask,
|
|
.irq_bus_lock = adp5588_irq_bus_lock,
|
|
.irq_bus_sync_unlock = adp5588_irq_bus_sync_unlock,
|
|
.irq_set_type = adp5588_irq_set_type,
|
|
};
|
|
|
|
static int adp5588_gpio_read_intstat(struct i2c_client *client, u8 *buf)
|
|
{
|
|
int ret = i2c_smbus_read_i2c_block_data(client, GPIO_INT_STAT1, 3, buf);
|
|
|
|
if (ret < 0)
|
|
dev_err(&client->dev, "Read INT_STAT Error\n");
|
|
|
|
return ret;
|
|
}
|
|
|
|
static irqreturn_t adp5588_irq_handler(int irq, void *devid)
|
|
{
|
|
struct adp5588_gpio *dev = devid;
|
|
unsigned status, bank, bit, pending;
|
|
int ret;
|
|
status = adp5588_gpio_read(dev->client, INT_STAT);
|
|
|
|
if (status & ADP5588_GPI_INT) {
|
|
ret = adp5588_gpio_read_intstat(dev->client, dev->irq_stat);
|
|
if (ret < 0)
|
|
memset(dev->irq_stat, 0, ARRAY_SIZE(dev->irq_stat));
|
|
|
|
for (bank = 0, bit = 0; bank <= ADP5588_BANK(ADP5588_MAXGPIO);
|
|
bank++, bit = 0) {
|
|
pending = dev->irq_stat[bank] & dev->irq_mask[bank];
|
|
|
|
while (pending) {
|
|
if (pending & (1 << bit)) {
|
|
handle_nested_irq(dev->irq_base +
|
|
(bank << 3) + bit);
|
|
pending &= ~(1 << bit);
|
|
|
|
}
|
|
bit++;
|
|
}
|
|
}
|
|
}
|
|
|
|
adp5588_gpio_write(dev->client, INT_STAT, status); /* Status is W1C */
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int adp5588_irq_setup(struct adp5588_gpio *dev)
|
|
{
|
|
struct i2c_client *client = dev->client;
|
|
struct adp5588_gpio_platform_data *pdata =
|
|
dev_get_platdata(&client->dev);
|
|
unsigned gpio;
|
|
int ret;
|
|
|
|
adp5588_gpio_write(client, CFG, ADP5588_AUTO_INC);
|
|
adp5588_gpio_write(client, INT_STAT, -1); /* status is W1C */
|
|
adp5588_gpio_read_intstat(client, dev->irq_stat); /* read to clear */
|
|
|
|
dev->irq_base = pdata->irq_base;
|
|
mutex_init(&dev->irq_lock);
|
|
|
|
for (gpio = 0; gpio < dev->gpio_chip.ngpio; gpio++) {
|
|
int irq = gpio + dev->irq_base;
|
|
irq_set_chip_data(irq, dev);
|
|
irq_set_chip_and_handler(irq, &adp5588_irq_chip,
|
|
handle_level_irq);
|
|
irq_set_nested_thread(irq, 1);
|
|
irq_modify_status(irq, IRQ_NOREQUEST, IRQ_NOPROBE);
|
|
}
|
|
|
|
ret = request_threaded_irq(client->irq,
|
|
NULL,
|
|
adp5588_irq_handler,
|
|
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
|
|
dev_name(&client->dev), dev);
|
|
if (ret) {
|
|
dev_err(&client->dev, "failed to request irq %d\n",
|
|
client->irq);
|
|
goto out;
|
|
}
|
|
|
|
dev->gpio_chip.to_irq = adp5588_gpio_to_irq;
|
|
adp5588_gpio_write(client, CFG,
|
|
ADP5588_AUTO_INC | ADP5588_INT_CFG | ADP5588_GPI_INT);
|
|
|
|
return 0;
|
|
|
|
out:
|
|
dev->irq_base = 0;
|
|
return ret;
|
|
}
|
|
|
|
static void adp5588_irq_teardown(struct adp5588_gpio *dev)
|
|
{
|
|
if (dev->irq_base)
|
|
free_irq(dev->client->irq, dev);
|
|
}
|
|
|
|
#else
|
|
static int adp5588_irq_setup(struct adp5588_gpio *dev)
|
|
{
|
|
struct i2c_client *client = dev->client;
|
|
dev_warn(&client->dev, "interrupt support not compiled in\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void adp5588_irq_teardown(struct adp5588_gpio *dev)
|
|
{
|
|
}
|
|
#endif /* CONFIG_GPIO_ADP5588_IRQ */
|
|
|
|
static int adp5588_gpio_probe(struct i2c_client *client,
|
|
const struct i2c_device_id *id)
|
|
{
|
|
struct adp5588_gpio_platform_data *pdata =
|
|
dev_get_platdata(&client->dev);
|
|
struct adp5588_gpio *dev;
|
|
struct gpio_chip *gc;
|
|
int ret, i, revid;
|
|
|
|
if (!pdata) {
|
|
dev_err(&client->dev, "missing platform data\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (!i2c_check_functionality(client->adapter,
|
|
I2C_FUNC_SMBUS_BYTE_DATA)) {
|
|
dev_err(&client->dev, "SMBUS Byte Data not Supported\n");
|
|
return -EIO;
|
|
}
|
|
|
|
dev = devm_kzalloc(&client->dev, sizeof(*dev), GFP_KERNEL);
|
|
if (!dev)
|
|
return -ENOMEM;
|
|
|
|
dev->client = client;
|
|
|
|
gc = &dev->gpio_chip;
|
|
gc->direction_input = adp5588_gpio_direction_input;
|
|
gc->direction_output = adp5588_gpio_direction_output;
|
|
gc->get = adp5588_gpio_get_value;
|
|
gc->set = adp5588_gpio_set_value;
|
|
gc->can_sleep = true;
|
|
|
|
gc->base = pdata->gpio_start;
|
|
gc->ngpio = ADP5588_MAXGPIO;
|
|
gc->label = client->name;
|
|
gc->owner = THIS_MODULE;
|
|
gc->names = pdata->names;
|
|
|
|
mutex_init(&dev->lock);
|
|
|
|
ret = adp5588_gpio_read(dev->client, DEV_ID);
|
|
if (ret < 0)
|
|
goto err;
|
|
|
|
revid = ret & ADP5588_DEVICE_ID_MASK;
|
|
|
|
for (i = 0, ret = 0; i <= ADP5588_BANK(ADP5588_MAXGPIO); i++) {
|
|
dev->dat_out[i] = adp5588_gpio_read(client, GPIO_DAT_OUT1 + i);
|
|
dev->dir[i] = adp5588_gpio_read(client, GPIO_DIR1 + i);
|
|
ret |= adp5588_gpio_write(client, KP_GPIO1 + i, 0);
|
|
ret |= adp5588_gpio_write(client, GPIO_PULL1 + i,
|
|
(pdata->pullup_dis_mask >> (8 * i)) & 0xFF);
|
|
ret |= adp5588_gpio_write(client, GPIO_INT_EN1 + i, 0);
|
|
if (ret)
|
|
goto err;
|
|
}
|
|
|
|
if (pdata->irq_base) {
|
|
if (WA_DELAYED_READOUT_REVID(revid)) {
|
|
dev_warn(&client->dev, "GPIO int not supported\n");
|
|
} else {
|
|
ret = adp5588_irq_setup(dev);
|
|
if (ret)
|
|
goto err;
|
|
}
|
|
}
|
|
|
|
ret = devm_gpiochip_add_data(&client->dev, &dev->gpio_chip, dev);
|
|
if (ret)
|
|
goto err_irq;
|
|
|
|
dev_info(&client->dev, "IRQ Base: %d Rev.: %d\n",
|
|
pdata->irq_base, revid);
|
|
|
|
if (pdata->setup) {
|
|
ret = pdata->setup(client, gc->base, gc->ngpio, pdata->context);
|
|
if (ret < 0)
|
|
dev_warn(&client->dev, "setup failed, %d\n", ret);
|
|
}
|
|
|
|
i2c_set_clientdata(client, dev);
|
|
|
|
return 0;
|
|
|
|
err_irq:
|
|
adp5588_irq_teardown(dev);
|
|
err:
|
|
return ret;
|
|
}
|
|
|
|
static int adp5588_gpio_remove(struct i2c_client *client)
|
|
{
|
|
struct adp5588_gpio_platform_data *pdata =
|
|
dev_get_platdata(&client->dev);
|
|
struct adp5588_gpio *dev = i2c_get_clientdata(client);
|
|
int ret;
|
|
|
|
if (pdata->teardown) {
|
|
ret = pdata->teardown(client,
|
|
dev->gpio_chip.base, dev->gpio_chip.ngpio,
|
|
pdata->context);
|
|
if (ret < 0) {
|
|
dev_err(&client->dev, "teardown failed %d\n", ret);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
if (dev->irq_base)
|
|
free_irq(dev->client->irq, dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct i2c_device_id adp5588_gpio_id[] = {
|
|
{DRV_NAME, 0},
|
|
{}
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(i2c, adp5588_gpio_id);
|
|
|
|
static struct i2c_driver adp5588_gpio_driver = {
|
|
.driver = {
|
|
.name = DRV_NAME,
|
|
},
|
|
.probe = adp5588_gpio_probe,
|
|
.remove = adp5588_gpio_remove,
|
|
.id_table = adp5588_gpio_id,
|
|
};
|
|
|
|
module_i2c_driver(adp5588_gpio_driver);
|
|
|
|
MODULE_AUTHOR("Michael Hennerich <michael.hennerich@analog.com>");
|
|
MODULE_DESCRIPTION("GPIO ADP5588 Driver");
|
|
MODULE_LICENSE("GPL");
|