2012-06-14 04:57:16

by Kuninori Morimoto

[permalink] [raw]
Subject: Re: [PATCH 0/2][resend] gpio: pcf857x: enable gpio_to_irq()


These patches add gpio_to_irq() support for gpio-pcf857x.

Kuninori Morimoto (2):
gpio: pcf857x: share 8/16 bit access functions
gpio: pcf857x: enable gpio_to_irq() support

I'm using pcf857x, and gpio_keys_polled driver, since pcf857x didn't support irq.
pcf857x has some pins, but interrupt is only 1 pin.
So, these patches add irq demux support on pcf857x driver,
and enabled gpio_to_irq().
Now, I can use both gpio_keys / gpio_keys_polled

These patches are based on latest linus/master branch


2012-06-14 04:58:11

by Kuninori Morimoto

[permalink] [raw]
Subject: [PATCH 1/2][resend] gpio: pcf857x: share 8/16 bit access functions

This patch adds 8/16 bit write/read functions,
and shared gpio input/output/set/get functions.

Signed-off-by: Kuninori Morimoto <[email protected]>
---
drivers/gpio/gpio-pcf857x.c | 93 ++++++++++++++----------------------------
1 files changed, 31 insertions(+), 62 deletions(-)

diff --git a/drivers/gpio/gpio-pcf857x.c b/drivers/gpio/gpio-pcf857x.c
index 2d1de9e..076e236 100644
--- a/drivers/gpio/gpio-pcf857x.c
+++ b/drivers/gpio/gpio-pcf857x.c
@@ -61,61 +61,28 @@ struct pcf857x {
struct i2c_client *client;
struct mutex lock; /* protect 'out' */
unsigned out; /* software latch */
+
+ int (*write)(struct i2c_client *client, unsigned data);
+ int (*read)(struct i2c_client *client);
};

/*-------------------------------------------------------------------------*/

/* Talk to 8-bit I/O expander */

-static int pcf857x_input8(struct gpio_chip *chip, unsigned offset)
-{
- struct pcf857x *gpio = container_of(chip, struct pcf857x, chip);
- int status;
-
- mutex_lock(&gpio->lock);
- gpio->out |= (1 << offset);
- status = i2c_smbus_write_byte(gpio->client, gpio->out);
- mutex_unlock(&gpio->lock);
-
- return status;
-}
-
-static int pcf857x_get8(struct gpio_chip *chip, unsigned offset)
-{
- struct pcf857x *gpio = container_of(chip, struct pcf857x, chip);
- s32 value;
-
- value = i2c_smbus_read_byte(gpio->client);
- return (value < 0) ? 0 : (value & (1 << offset));
-}
-
-static int pcf857x_output8(struct gpio_chip *chip, unsigned offset, int value)
+static int i2c_write_le8(struct i2c_client *client, unsigned data)
{
- struct pcf857x *gpio = container_of(chip, struct pcf857x, chip);
- unsigned bit = 1 << offset;
- int status;
-
- mutex_lock(&gpio->lock);
- if (value)
- gpio->out |= bit;
- else
- gpio->out &= ~bit;
- status = i2c_smbus_write_byte(gpio->client, gpio->out);
- mutex_unlock(&gpio->lock);
-
- return status;
+ return i2c_smbus_write_byte(client, data);
}

-static void pcf857x_set8(struct gpio_chip *chip, unsigned offset, int value)
+static int i2c_read_le8(struct i2c_client *client)
{
- pcf857x_output8(chip, offset, value);
+ return (int)i2c_smbus_read_byte(client);
}

-/*-------------------------------------------------------------------------*/
-
/* Talk to 16-bit I/O expander */

-static int i2c_write_le16(struct i2c_client *client, u16 word)
+static int i2c_write_le16(struct i2c_client *client, unsigned word)
{
u8 buf[2] = { word & 0xff, word >> 8, };
int status;
@@ -135,29 +102,31 @@ static int i2c_read_le16(struct i2c_client *client)
return (buf[1] << 8) | buf[0];
}

-static int pcf857x_input16(struct gpio_chip *chip, unsigned offset)
+/*-------------------------------------------------------------------------*/
+
+static int pcf857x_input(struct gpio_chip *chip, unsigned offset)
{
struct pcf857x *gpio = container_of(chip, struct pcf857x, chip);
int status;

mutex_lock(&gpio->lock);
gpio->out |= (1 << offset);
- status = i2c_write_le16(gpio->client, gpio->out);
+ status = gpio->write(gpio->client, gpio->out);
mutex_unlock(&gpio->lock);

return status;
}

-static int pcf857x_get16(struct gpio_chip *chip, unsigned offset)
+static int pcf857x_get(struct gpio_chip *chip, unsigned offset)
{
struct pcf857x *gpio = container_of(chip, struct pcf857x, chip);
int value;

- value = i2c_read_le16(gpio->client);
+ value = gpio->read(gpio->client);
return (value < 0) ? 0 : (value & (1 << offset));
}

-static int pcf857x_output16(struct gpio_chip *chip, unsigned offset, int value)
+static int pcf857x_output(struct gpio_chip *chip, unsigned offset, int value)
{
struct pcf857x *gpio = container_of(chip, struct pcf857x, chip);
unsigned bit = 1 << offset;
@@ -168,15 +137,15 @@ static int pcf857x_output16(struct gpio_chip *chip, unsigned offset, int value)
gpio->out |= bit;
else
gpio->out &= ~bit;
- status = i2c_write_le16(gpio->client, gpio->out);
+ status = gpio->write(gpio->client, gpio->out);
mutex_unlock(&gpio->lock);

return status;
}

-static void pcf857x_set16(struct gpio_chip *chip, unsigned offset, int value)
+static void pcf857x_set(struct gpio_chip *chip, unsigned offset, int value)
{
- pcf857x_output16(chip, offset, value);
+ pcf857x_output(chip, offset, value);
}

/*-------------------------------------------------------------------------*/
@@ -200,10 +169,15 @@ static int pcf857x_probe(struct i2c_client *client,

mutex_init(&gpio->lock);

- gpio->chip.base = pdata ? pdata->gpio_base : -1;
- gpio->chip.can_sleep = 1;
- gpio->chip.dev = &client->dev;
- gpio->chip.owner = THIS_MODULE;
+ gpio->chip.base = pdata ? pdata->gpio_base : -1;
+ gpio->chip.can_sleep = 1;
+ gpio->chip.dev = &client->dev;
+ gpio->chip.owner = THIS_MODULE;
+ gpio->chip.get = pcf857x_get;
+ gpio->chip.set = pcf857x_set;
+ gpio->chip.direction_input = pcf857x_input;
+ gpio->chip.direction_output = pcf857x_output;
+ gpio->chip.ngpio = id->driver_data;

/* NOTE: the OnSemi jlc1562b is also largely compatible with
* these parts, notably for output. It has a low-resolution
@@ -216,12 +190,9 @@ static int pcf857x_probe(struct i2c_client *client,
*
* NOTE: we don't distinguish here between *4 and *4a parts.
*/
- gpio->chip.ngpio = id->driver_data;
if (gpio->chip.ngpio == 8) {
- gpio->chip.direction_input = pcf857x_input8;
- gpio->chip.get = pcf857x_get8;
- gpio->chip.direction_output = pcf857x_output8;
- gpio->chip.set = pcf857x_set8;
+ gpio->write = i2c_write_le8;
+ gpio->read = i2c_read_le8;

if (!i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_BYTE))
@@ -238,10 +209,8 @@ static int pcf857x_probe(struct i2c_client *client,
* NOTE: we don't distinguish here between '75 and '75c parts.
*/
} else if (gpio->chip.ngpio == 16) {
- gpio->chip.direction_input = pcf857x_input16;
- gpio->chip.get = pcf857x_get16;
- gpio->chip.direction_output = pcf857x_output16;
- gpio->chip.set = pcf857x_set16;
+ gpio->write = i2c_write_le16;
+ gpio->read = i2c_read_le16;

if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
status = -EIO;
--
1.7.5.4

2012-06-14 04:58:29

by Kuninori Morimoto

[permalink] [raw]
Subject: [PATCH 2/2][resend] gpio: pcf857x: enable gpio_to_irq() support

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 <[email protected]>
---
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 <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,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

2012-06-14 06:15:32

by Paul Mundt

[permalink] [raw]
Subject: Re: [PATCH 2/2][resend] gpio: pcf857x: enable gpio_to_irq() support

On Wed, Jun 13, 2012 at 09:58:26PM -0700, Kuninori Morimoto wrote:
> +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,
> +};
> +
Just use dummy_irq_chip.

> +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);

Given that you don't actually care where the IRQs are mapped there's no
need to bother with a legacy domain here, you can just use a linear one
outright. At which point you can drop your separate irq_desc
allocation/management.

2012-06-14 07:06:17

by Kuninori Morimoto

[permalink] [raw]
Subject: Re: [PATCH 2/2][resend] gpio: pcf857x: enable gpio_to_irq() support


Hi Paul

Thank you for checking patch

> > +static struct irq_chip pcf857x_irq_chip = {
> > + .name = "pcf857x cascade",
> > + .irq_mask = pcf857x_nothing,
> > + .irq_unmask = pcf857x_nothing,
> > +};
> > +
> Just use dummy_irq_chip.

Thanks. I didn't know that.
OK. I fix it in v2

> > +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);
>
> Given that you don't actually care where the IRQs are mapped there's no
> need to bother with a legacy domain here, you can just use a linear one
> outright. At which point you can drop your separate irq_desc
> allocation/management.

OK. I use linear in v2

Best regards
---
Kuninori Morimoto

2012-06-14 07:11:03

by Kuninori Morimoto

[permalink] [raw]
Subject: [PATCH 2/2 v2][resend] gpio: pcf857x: enable gpio_to_irq() support

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 <[email protected]>
---
v1 -> v2

- used dummy_irq_chip on irq_set_chip_and_handler()
- used irq_domain_add_linear() instead of irq_domain_add_legacy()

drivers/gpio/gpio-pcf857x.c | 124 +++++++++++++++++++++++++++++++++++++++++++
include/linux/i2c/pcf857x.h | 3 +
2 files changed, 127 insertions(+), 0 deletions(-)

diff --git a/drivers/gpio/gpio-pcf857x.c b/drivers/gpio/gpio-pcf857x.c
index 076e236..7f3a25c 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,102 @@ 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);
+ 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 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 +274,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 +286,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 +364,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 +395,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 +420,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

2012-06-14 07:23:35

by Paul Mundt

[permalink] [raw]
Subject: Re: [PATCH 2/2 v2][resend] gpio: pcf857x: enable gpio_to_irq() support

On Thu, Jun 14, 2012 at 12:10:59AM -0700, Kuninori Morimoto wrote:
> + for (i = 0; i < gpio->chip.ngpio; i++)
> + if (change & BIT(i))
> + generic_handle_irq(irq_find_mapping(gpio->irq_domain, i));
> +
You can probably get away with using for_each_set_bit() as well, which
would simplify this a bit.

Also, do you not have to worry about masking the parent IRQ while
iterating through the dispatch loop?

2012-06-14 07:59:22

by Kuninori Morimoto

[permalink] [raw]
Subject: Re: [PATCH 2/2 v2][resend] gpio: pcf857x: enable gpio_to_irq() support


Hi Paul

> On Thu, Jun 14, 2012 at 12:10:59AM -0700, Kuninori Morimoto wrote:
> > + for (i = 0; i < gpio->chip.ngpio; i++)
> > + if (change & BIT(i))
> > + generic_handle_irq(irq_find_mapping(gpio->irq_domain, i));
> > +
> You can probably get away with using for_each_set_bit() as well, which
> would simplify this a bit.
>
> Also, do you not have to worry about masking the parent IRQ while
> iterating through the dispatch loop?

This device automatically stops its IRQ until data was read.
And it is using spin_lock_irqsave(), and it needs to save prev status.
So, I guess masking parent IRQ is not needed.
But not enough/good ?

Best regards
---
Kuninori Morimoto

2012-06-15 02:40:44

by Kuninori Morimoto

[permalink] [raw]
Subject: Re: [PATCH 2/2 v3][resend] gpio: pcf857x: enable gpio_to_irq() support

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 <[email protected]>
---
v2 -> v3

- used for_each_set_bit()

drivers/gpio/gpio-pcf857x.c | 122 +++++++++++++++++++++++++++++++++++++++++++
include/linux/i2c/pcf857x.h | 3 +
2 files changed, 125 insertions(+), 0 deletions(-)

diff --git a/drivers/gpio/gpio-pcf857x.c b/drivers/gpio/gpio-pcf857x.c
index 076e236..12e3e48 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->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->irq)
+ pcf857x_irq_domain_cleanup(gpio);
+
kfree(gpio);
return status;
}
@@ -299,6 +418,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