Some GPIO chips allow to rise an IRQ on GPIO level changes but do not
provide an IRQ status for each separate line: only the current gpio
level can be retrieved.
Add support for these chips, emulating IRQ status by comparing GPIO
levels with the levels during the previous interrupt.
Signed-off-by: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
---
drivers/base/regmap/regmap-irq.c | 97 +++++++++++++++++++++++++++-------------
include/linux/regmap.h | 3 ++
2 files changed, 69 insertions(+), 31 deletions(-)
diff --git a/drivers/base/regmap/regmap-irq.c b/drivers/base/regmap/regmap-irq.c
index 6c6869188c31..c1b0dcd7280f 100644
--- a/drivers/base/regmap/regmap-irq.c
+++ b/drivers/base/regmap/regmap-irq.c
@@ -6,6 +6,7 @@
//
// Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+#include <linux/array_size.h>
#include <linux/device.h>
#include <linux/export.h>
#include <linux/interrupt.h>
@@ -33,6 +34,7 @@ struct regmap_irq_chip_data {
void *status_reg_buf;
unsigned int *main_status_buf;
unsigned int *status_buf;
+ unsigned int *prev_status_buf;
unsigned int *mask_buf;
unsigned int *mask_buf_def;
unsigned int *wake_buf;
@@ -332,27 +334,13 @@ static inline int read_sub_irq_data(struct regmap_irq_chip_data *data,
return ret;
}
-static irqreturn_t regmap_irq_thread(int irq, void *d)
+static int read_irq_data(struct regmap_irq_chip_data *data)
{
- struct regmap_irq_chip_data *data = d;
const struct regmap_irq_chip *chip = data->chip;
struct regmap *map = data->map;
int ret, i;
- bool handled = false;
u32 reg;
- if (chip->handle_pre_irq)
- chip->handle_pre_irq(chip->irq_drv_data);
-
- if (chip->runtime_pm) {
- ret = pm_runtime_get_sync(map->dev);
- if (ret < 0) {
- dev_err(map->dev, "IRQ thread failed to resume: %d\n",
- ret);
- goto exit;
- }
- }
-
/*
* Read only registers with active IRQs if the chip has 'main status
* register'. Else read in the statuses, using a single bulk read if
@@ -379,10 +367,8 @@ static irqreturn_t regmap_irq_thread(int irq, void *d)
reg = data->get_irq_reg(data, chip->main_status, i);
ret = regmap_read(map, reg, &data->main_status_buf[i]);
if (ret) {
- dev_err(map->dev,
- "Failed to read IRQ status %d\n",
- ret);
- goto exit;
+ dev_err(map->dev, "Failed to read IRQ status %d\n", ret);
+ return ret;
}
}
@@ -398,10 +384,8 @@ static irqreturn_t regmap_irq_thread(int irq, void *d)
ret = read_sub_irq_data(data, b);
if (ret != 0) {
- dev_err(map->dev,
- "Failed to read IRQ status %d\n",
- ret);
- goto exit;
+ dev_err(map->dev, "Failed to read IRQ status %d\n", ret);
+ return ret;
}
}
@@ -418,9 +402,8 @@ static irqreturn_t regmap_irq_thread(int irq, void *d)
data->status_reg_buf,
chip->num_regs);
if (ret != 0) {
- dev_err(map->dev, "Failed to read IRQ status: %d\n",
- ret);
- goto exit;
+ dev_err(map->dev, "Failed to read IRQ status: %d\n", ret);
+ return ret;
}
for (i = 0; i < data->chip->num_regs; i++) {
@@ -436,7 +419,7 @@ static irqreturn_t regmap_irq_thread(int irq, void *d)
break;
default:
BUG();
- goto exit;
+ return -EIO;
}
}
@@ -447,10 +430,8 @@ static irqreturn_t regmap_irq_thread(int irq, void *d)
ret = regmap_read(map, reg, &data->status_buf[i]);
if (ret != 0) {
- dev_err(map->dev,
- "Failed to read IRQ status: %d\n",
- ret);
- goto exit;
+ dev_err(map->dev, "Failed to read IRQ status: %d\n", ret);
+ return ret;
}
}
}
@@ -459,6 +440,42 @@ static irqreturn_t regmap_irq_thread(int irq, void *d)
for (i = 0; i < data->chip->num_regs; i++)
data->status_buf[i] = ~data->status_buf[i];
+ return 0;
+}
+
+static irqreturn_t regmap_irq_thread(int irq, void *d)
+{
+ struct regmap_irq_chip_data *data = d;
+ const struct regmap_irq_chip *chip = data->chip;
+ struct regmap *map = data->map;
+ int ret, i;
+ bool handled = false;
+ u32 reg;
+
+ if (chip->handle_pre_irq)
+ chip->handle_pre_irq(chip->irq_drv_data);
+
+ if (chip->runtime_pm) {
+ ret = pm_runtime_get_sync(map->dev);
+ if (ret < 0) {
+ dev_err(map->dev, "IRQ thread failed to resume: %d\n", ret);
+ goto exit;
+ }
+ }
+
+ ret = read_irq_data(data);
+ if (ret < 0)
+ goto exit;
+
+ if (chip->status_is_level) {
+ for (i = 0; i < data->chip->num_regs; i++) {
+ unsigned int val = data->status_buf[i];
+
+ data->status_buf[i] ^= data->prev_status_buf[i];
+ data->prev_status_buf[i] = val;
+ }
+ }
+
/*
* Ignore masked IRQs and ack if we need to; we ack early so
* there is no race between handling and acknowledging the
@@ -705,6 +722,13 @@ int regmap_add_irq_chip_fwnode(struct fwnode_handle *fwnode,
if (!d->status_buf)
goto err_alloc;
+ if (chip->status_is_level) {
+ d->prev_status_buf = kcalloc(chip->num_regs, sizeof(*d->prev_status_buf),
+ GFP_KERNEL);
+ if (!d->prev_status_buf)
+ goto err_alloc;
+ }
+
d->mask_buf = kcalloc(chip->num_regs, sizeof(*d->mask_buf),
GFP_KERNEL);
if (!d->mask_buf)
@@ -881,6 +905,15 @@ int regmap_add_irq_chip_fwnode(struct fwnode_handle *fwnode,
}
}
+ /* Store current levels */
+ if (chip->status_is_level) {
+ ret = read_irq_data(d);
+ if (ret < 0)
+ goto err_alloc;
+
+ memcpy(d->prev_status_buf, d->status_buf, array_size(d->prev_status_buf));
+ }
+
ret = regmap_irq_create_domain(fwnode, irq_base, chip, d);
if (ret)
goto err_alloc;
@@ -908,6 +941,7 @@ int regmap_add_irq_chip_fwnode(struct fwnode_handle *fwnode,
kfree(d->mask_buf);
kfree(d->main_status_buf);
kfree(d->status_buf);
+ kfree(d->prev_status_buf);
kfree(d->status_reg_buf);
if (d->config_buf) {
for (i = 0; i < chip->num_config_bases; i++)
@@ -985,6 +1019,7 @@ void regmap_del_irq_chip(int irq, struct regmap_irq_chip_data *d)
kfree(d->main_status_buf);
kfree(d->status_reg_buf);
kfree(d->status_buf);
+ kfree(d->prev_status_buf);
if (d->config_buf) {
for (i = 0; i < d->chip->num_config_bases; i++)
kfree(d->config_buf[i]);
diff --git a/include/linux/regmap.h b/include/linux/regmap.h
index d17c5ea3d55d..02b83f5499b8 100644
--- a/include/linux/regmap.h
+++ b/include/linux/regmap.h
@@ -1641,6 +1641,8 @@ struct regmap_irq_chip_data;
* @ack_invert: Inverted ack register: cleared bits for ack.
* @clear_ack: Use this to set 1 and 0 or vice-versa to clear interrupts.
* @status_invert: Inverted status register: cleared bits are active interrupts.
+ * @status_is_level: Status register is actuall signal level: Xor status
+ * register with previous value to get active interrupts.
* @wake_invert: Inverted wake register: cleared bits are wake enabled.
* @type_in_mask: Use the mask registers for controlling irq type. Use this if
* the hardware provides separate bits for rising/falling edge
@@ -1704,6 +1706,7 @@ struct regmap_irq_chip {
unsigned int ack_invert:1;
unsigned int clear_ack:1;
unsigned int status_invert:1;
+ unsigned int status_is_level:1;
unsigned int wake_invert:1;
unsigned int type_in_mask:1;
unsigned int clear_on_unmask:1;
--
2.39.5
On Fri, May 09, 2025 at 11:14:39AM +0200, Mathieu Dubois-Briand wrote:
> Some GPIO chips allow to rise an IRQ on GPIO level changes but do not
> provide an IRQ status for each separate line: only the current gpio
> level can be retrieved.
This doesn't build in a wide range of configurations (none at all
AFAICT):
/build/stage/linux/drivers/base/regmap/regmap-irq.c: In function ‘regmap_add_irq
_chip_fwnode’:
/build/stage/linux/drivers/base/regmap/regmap-irq.c:914:88: error: macro "array_
size" requires 2 arguments, but only 1 given
914 | memcpy(d->prev_status_buf, d->status_buf, array_size(d->
prev_status_buf));
|
^
In file included from /build/stage/linux/include/linux/string.h:13,
from /build/stage/linux/include/linux/bitmap.h:13,
from /build/stage/linux/include/linux/cpumask.h:12,
from /build/stage/linux/include/linux/smp.h:13,
from /build/stage/linux/include/linux/lockdep.h:14,
from /build/stage/linux/include/linux/spinlock.h:63,
from /build/stage/linux/include/linux/sched.h:2213,
from /build/stage/linux/include/linux/ratelimit.h:6,
from /build/stage/linux/include/linux/dev_printk.h:16,
from /build/stage/linux/include/linux/device.h:15,
from /build/stage/linux/drivers/base/regmap/regmap-irq.c:10:
/build/stage/linux/include/linux/overflow.h:327:9: note: macro "array_size" defined here
327 | #define array_size(a, b) size_mul(a, b)
| ^~~~~~~~~~
/build/stage/linux/drivers/base/regmap/regmap-irq.c:914:59: error: ‘array_size’ undeclared (first use in this function)
914 | memcpy(d->prev_status_buf, d->status_buf, array_size(d->prev_status_buf));
| ^~~~~~~~~~
/build/stage/linux/drivers/base/regmap/regmap-irq.c:914:59: note: each undeclared identifier is reported only once for each function it appears in
On Wed May 14, 2025 at 11:59 AM CEST, Mark Brown wrote: > On Fri, May 09, 2025 at 11:14:39AM +0200, Mathieu Dubois-Briand wrote: >> Some GPIO chips allow to rise an IRQ on GPIO level changes but do not >> provide an IRQ status for each separate line: only the current gpio >> level can be retrieved. > > This doesn't build in a wide range of configurations (none at all > AFAICT): > > /build/stage/linux/drivers/base/regmap/regmap-irq.c: In function ‘regmap_add_irq > _chip_fwnode’: > /build/stage/linux/drivers/base/regmap/regmap-irq.c:914:88: error: macro "array_ > size" requires 2 arguments, but only 1 given > 914 | memcpy(d->prev_status_buf, d->status_buf, array_size(d-> > prev_status_buf)); > | > ^ > In file included from /build/stage/linux/include/linux/string.h:13, > from /build/stage/linux/include/linux/bitmap.h:13, > from /build/stage/linux/include/linux/cpumask.h:12, > from /build/stage/linux/include/linux/smp.h:13, > from /build/stage/linux/include/linux/lockdep.h:14, > from /build/stage/linux/include/linux/spinlock.h:63, > from /build/stage/linux/include/linux/sched.h:2213, > from /build/stage/linux/include/linux/ratelimit.h:6, > from /build/stage/linux/include/linux/dev_printk.h:16, > from /build/stage/linux/include/linux/device.h:15, > from /build/stage/linux/drivers/base/regmap/regmap-irq.c:10: > /build/stage/linux/include/linux/overflow.h:327:9: note: macro "array_size" defined here > 327 | #define array_size(a, b) size_mul(a, b) > | ^~~~~~~~~~ > /build/stage/linux/drivers/base/regmap/regmap-irq.c:914:59: error: ‘array_size’ undeclared (first use in this function) > 914 | memcpy(d->prev_status_buf, d->status_buf, array_size(d->prev_status_buf)); > | ^~~~~~~~~~ > /build/stage/linux/drivers/base/regmap/regmap-irq.c:914:59: note: each undeclared identifier is reported only once for each function it appears in My bad, I somehow ended with this commit introducing bad code but having the next patch of the series fixing it. I will take care of that. Thanks for pointing this. Thanks for your review. Mathieu -- Mathieu Dubois-Briand, Bootlin Embedded Linux and Kernel engineering https://bootlin.com
© 2016 - 2025 Red Hat, Inc.