summaryrefslogtreecommitdiffstats
path: root/drivers/gpio/gpio-pcf857x.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2012-10-03 01:05:10 +0200
committerLinus Torvalds <torvalds@linux-foundation.org>2012-10-03 01:05:10 +0200
commitdff8360a4a079692e65e55fbaa6c5dc605528403 (patch)
tree0ab8ef7595cdfb918b3fd9a8364c6ea6c9c2798f /drivers/gpio/gpio-pcf857x.c
parentworkqueue: avoid using deprecated functions (diff)
parentgpio: pcf857x: select IRQ_DOMAIN (diff)
downloadlinux-dff8360a4a079692e65e55fbaa6c5dc605528403.tar.xz
linux-dff8360a4a079692e65e55fbaa6c5dc605528403.zip
Merge tag 'gpio-for-v3.7' of git://git.kernel.org/pub/scm/linux/kernel/git/linusw/linux-gpio
Pull GPIO changes from Linus Walleij: "So this is the LW GPIO patch stack for v3.7: - refactoring from Thierry Redding at Arnd Bergmann's request to use the seq_file iterator interface in gpiolib. - A new driver for Avionic Design's N-bit GPIO expander. - Two instances of mutexes replaced by spinlocks from Axel Lin to code that is supposed to be fastpath compliant. - IRQ demuxer and gpio_to_irq() support for pcf857x by Kuninori Morimoto. - Dynamic GPIO numbers, device tree support, daisy chaining and some other fixes for the 74x164 driver by Maxime Ripard. - IRQ domain and device tree support for the tc3589x driver by Lee Jones. - Some conversion to use managed resources devm_* code. - Some instances of clk_prepare() or clk_prepare_enable() added to support the new, stricter common clock framework. - Some for_each_set_bit() simplifications. - Then a lot of fixes as we fixed up all of the above tripping over our own shoelaces and that kind of thing." * tag 'gpio-for-v3.7' of git://git.kernel.org/pub/scm/linux/kernel/git/linusw/linux-gpio: (34 commits) gpio: pcf857x: select IRQ_DOMAIN gpio: Document device_node's det_debounce gpio-lpc32xx: Add GPI_28 gpio: adnp: dt: Reference generic interrupt binding gpio: Add Avionic Design N-bit GPIO expander support gpio: pxa: using for_each_set_bit to simplify the code gpio_msm: using for_each_set_bit to simplify the code gpio: Enable the tc3298x GPIO expander driver for Device Tree gpio: Provide the tc3589x GPIO expander driver with an IRQ domain ARM: shmobile: kzm9g: use gpio-keys instead of gpio-keys-polled gpio: pcf857x: fixup smatch WARNING gpio: 74x164: Add support for the daisy-chaining gpio: 74x164: dts: Add documentation for the dt binding dt: Fix incorrect reference in gpio-led documentation gpio: 74x164: Add device tree support gpio: 74x164: Use dynamic gpio number assignment if no pdata is present gpio: 74x164: Use devm_kzalloc gpio: 74x164: Use module_spi_driver boiler plate function gpio: sx150x: Use irq_data_get_irq_chip_data() at appropriate places gpio: em: Use irq_data_get_irq_chip_data() at appropriate places ...
Diffstat (limited to 'drivers/gpio/gpio-pcf857x.c')
-rw-r--r--drivers/gpio/gpio-pcf857x.c122
1 files changed, 122 insertions, 0 deletions
diff --git a/drivers/gpio/gpio-pcf857x.c b/drivers/gpio/gpio-pcf857x.c
index 076e236d0da7..16af35cd2b10 100644
--- a/drivers/gpio/gpio-pcf857x.c
+++ b/drivers/gpio/gpio-pcf857x.c
@@ -23,7 +23,12 @@
#include <linux/gpio.h>
#include <linux/i2c.h>
#include <linux/i2c/pcf857x.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/workqueue.h>
static const struct i2c_device_id pcf857x_id[] = {
@@ -60,7 +65,12 @@ 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 (*write)(struct i2c_client *client, unsigned data);
int (*read)(struct i2c_client *client);
@@ -150,6 +160,100 @@ 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_create_mapping(gpio->irq_domain, offset);
+}
+
+static void pcf857x_irq_demux_work(struct work_struct *work)
+{
+ struct pcf857x *gpio = container_of(work,
+ struct pcf857x,
+ work);
+ unsigned long change, i, status, flags;
+
+ status = gpio->read(gpio->client);
+
+ spin_lock_irqsave(&gpio->slock, flags);
+
+ change = gpio->status ^ status;
+ for_each_set_bit(i, &change, gpio->chip.ngpio)
+ 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 int pcf857x_irq_domain_map(struct irq_domain *domain, unsigned int virq,
+ irq_hw_number_t hw)
+{
+ irq_set_chip_and_handler(virq,
+ &dummy_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_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;
+
+ gpio->irq_domain = irq_domain_add_linear(dev->of_node,
+ gpio->chip.ngpio,
+ &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 +272,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 +284,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 && 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 +362,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 +393,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 && pdata->irq)
+ pcf857x_irq_domain_cleanup(gpio);
+
kfree(gpio);
return status;
}
@@ -299,6 +418,9 @@ static int pcf857x_remove(struct i2c_client *client)
}
}
+ if (pdata && pdata->irq)
+ pcf857x_irq_domain_cleanup(gpio);
+
status = gpiochip_remove(&gpio->chip);
if (status == 0)
kfree(gpio);