Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752159Ab2FNE63 (ORCPT ); Thu, 14 Jun 2012 00:58:29 -0400 Received: from mail-pb0-f46.google.com ([209.85.160.46]:42450 "EHLO mail-pb0-f46.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751930Ab2FNE62 (ORCPT ); Thu, 14 Jun 2012 00:58:28 -0400 Date: Wed, 13 Jun 2012 21:58:26 -0700 (PDT) Message-ID: <87aa06qykw.wl%kuninori.morimoto.gx@renesas.com> To: Linus Walleij , Grant Likely Cc: Kuninori Morimoto , linux-kernel@vger.kernel.org In-Reply-To: <87d352qymy.wl%kuninori.morimoto.gx@renesas.com> References: <877gw2uis5.wl%kuninori.morimoto.gx@renesas.com> <87d352qymy.wl%kuninori.morimoto.gx@renesas.com> From: Kuninori Morimoto Subject: [PATCH 2/2][resend] gpio: pcf857x: enable gpio_to_irq() support MIME-Version: 1.0 (generated by SEMI 1.14.6 - "Maruoka") Content-Type: text/plain; charset=US-ASCII Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 7079 Lines: 249 pcf857x chip has some pins, but interrupt pin is only 1 pin. And gpiolib requests 1 interrupt per 1 gpio pin. Thus, this patch added demuxed irq to pcf857x driver, and enabled gpio_to_irq(). Signed-off-by: Kuninori Morimoto --- drivers/gpio/gpio-pcf857x.c | 146 +++++++++++++++++++++++++++++++++++++++++++ include/linux/i2c/pcf857x.h | 3 + 2 files changed, 149 insertions(+), 0 deletions(-) diff --git a/drivers/gpio/gpio-pcf857x.c b/drivers/gpio/gpio-pcf857x.c index 076e236..a51d4f5b 100644 --- a/drivers/gpio/gpio-pcf857x.c +++ b/drivers/gpio/gpio-pcf857x.c @@ -23,7 +23,12 @@ #include #include #include +#include +#include +#include #include +#include +#include static const struct i2c_device_id pcf857x_id[] = { @@ -60,7 +65,13 @@ struct pcf857x { struct gpio_chip chip; struct i2c_client *client; struct mutex lock; /* protect 'out' */ + struct work_struct work; /* irq demux work */ + struct irq_domain *irq_domain; /* for irq demux */ + spinlock_t slock; /* protect irq demux */ unsigned out; /* software latch */ + unsigned status; /* current status */ + int irq; /* real irq number */ + int irq_demux_base; /* irq demux base */ int (*write)(struct i2c_client *client, unsigned data); int (*read)(struct i2c_client *client); @@ -150,6 +161,123 @@ static void pcf857x_set(struct gpio_chip *chip, unsigned offset, int value) /*-------------------------------------------------------------------------*/ +static int pcf857x_to_irq(struct gpio_chip *chip, unsigned offset) +{ + struct pcf857x *gpio = container_of(chip, struct pcf857x, chip); + + return irq_find_mapping(gpio->irq_domain, offset); +} + +static void pcf857x_irq_demux_work(struct work_struct *work) +{ + struct pcf857x *gpio = container_of(work, + struct pcf857x, + work); + int change, i, status; + unsigned long flags; + + status = gpio->read(gpio->client); + + spin_lock_irqsave(&gpio->slock, flags); + change = gpio->status ^ status; + + for (i = 0; i < gpio->chip.ngpio; i++) + if (change & BIT(i)) + generic_handle_irq(irq_find_mapping(gpio->irq_domain, i)); + + gpio->status = status; + spin_unlock_irqrestore(&gpio->slock, flags); +} + +static irqreturn_t pcf857x_irq_demux(int irq, void *data) +{ + struct pcf857x *gpio = data; + + /* + * pcf857x can't read/write data here, + * since i2c data access might go to sleep. + */ + schedule_work(&gpio->work); + + return IRQ_HANDLED; +} + +static void pcf857x_nothing(struct irq_data *data) +{ + /* do nothing */ +} + +static struct irq_chip pcf857x_irq_chip = { + .name = "pcf857x cascade", + .irq_mask = pcf857x_nothing, + .irq_unmask = pcf857x_nothing, +}; + +static int pcf857x_irq_domain_map(struct irq_domain *domain, unsigned int virq, + irq_hw_number_t hw) +{ + irq_set_chip_and_handler(virq, + &pcf857x_irq_chip, + handle_level_irq); + return 0; +} + +static struct irq_domain_ops pcf857x_irq_domain_ops = { + .map = pcf857x_irq_domain_map, +}; + +static void pcf857x_irq_domain_cleanup(struct pcf857x *gpio) +{ + if (gpio->irq_demux_base > 0) + irq_free_descs(gpio->irq_demux_base, gpio->chip.ngpio); + + if (gpio->irq_domain) + irq_domain_remove(gpio->irq_domain); + + if (gpio->irq) + free_irq(gpio->irq, gpio); +} + +static int pcf857x_irq_domain_init(struct pcf857x *gpio, + struct pcf857x_platform_data *pdata, + struct device *dev) +{ + int status; + + /* create demuxed irq */ + gpio->irq_demux_base = irq_alloc_descs(-1, 0, gpio->chip.ngpio, 0); + if (gpio->irq_demux_base < 0) + return -ENODEV; + + gpio->irq_domain = irq_domain_add_legacy(dev->of_node, + gpio->chip.ngpio, + gpio->irq_demux_base, + 0, + &pcf857x_irq_domain_ops, + NULL); + if (!gpio->irq_domain) + goto fail; + + /* enable real irq */ + status = request_irq(pdata->irq, pcf857x_irq_demux, 0, + dev_name(dev), gpio); + if (status) + goto fail; + + /* enable gpio_to_irq() */ + INIT_WORK(&gpio->work, pcf857x_irq_demux_work); + gpio->chip.to_irq = pcf857x_to_irq; + gpio->irq = pdata->irq; + + return 0; + +fail: + pcf857x_irq_domain_cleanup(gpio); + return -EINVAL; +} + +/*-------------------------------------------------------------------------*/ + static int pcf857x_probe(struct i2c_client *client, const struct i2c_device_id *id) { @@ -168,6 +296,7 @@ static int pcf857x_probe(struct i2c_client *client, return -ENOMEM; mutex_init(&gpio->lock); + spin_lock_init(&gpio->slock); gpio->chip.base = pdata ? pdata->gpio_base : -1; gpio->chip.can_sleep = 1; @@ -179,6 +308,15 @@ static int pcf857x_probe(struct i2c_client *client, gpio->chip.direction_output = pcf857x_output; gpio->chip.ngpio = id->driver_data; + /* enable gpio_to_irq() if platform has settings */ + if (pdata->irq) { + status = pcf857x_irq_domain_init(gpio, pdata, &client->dev); + if (status < 0) { + dev_err(&client->dev, "irq_domain init failed\n"); + goto fail; + } + } + /* NOTE: the OnSemi jlc1562b is also largely compatible with * these parts, notably for output. It has a low-resolution * DAC instead of pin change IRQs; and its inputs can be the @@ -248,6 +386,7 @@ static int pcf857x_probe(struct i2c_client *client, * all-ones reset state. Otherwise it flags pins to be driven low. */ gpio->out = pdata ? ~pdata->n_latch : ~0; + gpio->status = gpio->out; status = gpiochip_add(&gpio->chip); if (status < 0) @@ -278,6 +417,10 @@ static int pcf857x_probe(struct i2c_client *client, fail: dev_dbg(&client->dev, "probe error %d for '%s'\n", status, client->name); + + if (pdata->irq) + pcf857x_irq_domain_cleanup(gpio); + kfree(gpio); return status; } @@ -299,6 +442,9 @@ static int pcf857x_remove(struct i2c_client *client) } } + if (pdata->irq) + pcf857x_irq_domain_cleanup(gpio); + status = gpiochip_remove(&gpio->chip); if (status == 0) kfree(gpio); diff --git a/include/linux/i2c/pcf857x.h b/include/linux/i2c/pcf857x.h index 0767a2a..781e6bd 100644 --- a/include/linux/i2c/pcf857x.h +++ b/include/linux/i2c/pcf857x.h @@ -10,6 +10,7 @@ * @setup: optional callback issued once the GPIOs are valid * @teardown: optional callback issued before the GPIOs are invalidated * @context: optional parameter passed to setup() and teardown() + * @irq: optional interrupt number * * In addition to the I2C_BOARD_INFO() state appropriate to each chip, * the i2c_board_info used with the pcf875x driver must provide its @@ -39,6 +40,8 @@ struct pcf857x_platform_data { int gpio, unsigned ngpio, void *context); void *context; + + int irq; }; #endif /* __LINUX_PCF857X_H */ -- 1.7.5.4 -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/