]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
regmap: irq: Add support for chips without separate IRQ status
authorMathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
Thu, 22 May 2025 12:06:20 +0000 (14:06 +0200)
committerMark Brown <broonie@kernel.org>
Thu, 22 May 2025 12:11:50 +0000 (13:11 +0100)
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>
Link: https://patch.msgid.link/20250522-mdb-max7360-support-v9-5-74fc03517e41@bootlin.com
Signed-off-by: Mark Brown <broonie@kernel.org>
drivers/base/regmap/regmap-irq.c
include/linux/regmap.h

index e2bea802d502192ff03f91db054dffe0db37f963..d1585f07377606ea9c0db51980832e057b51131c 100644 (file)
@@ -6,11 +6,13 @@
 //
 // Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
 
+#include <linux/array_size.h>
 #include <linux/device.h>
 #include <linux/export.h>
 #include <linux/interrupt.h>
 #include <linux/irq.h>
 #include <linux/irqdomain.h>
+#include <linux/overflow.h>
 #include <linux/pm_runtime.h>
 #include <linux/regmap.h>
 #include <linux/slab.h>
@@ -33,6 +35,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 +335,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 +368,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 +385,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 +403,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 +420,7 @@ static irqreturn_t regmap_irq_thread(int irq, void *d)
                                break;
                        default:
                                BUG();
-                               goto exit;
+                               return -EIO;
                        }
                }
 
@@ -447,10 +431,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 +441,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 +723,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 +906,16 @@ 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->chip->num_regs, sizeof(d->prev_status_buf[0])));
+       }
+
        ret = regmap_irq_create_domain(fwnode, irq_base, chip, d);
        if (ret)
                goto err_alloc;
@@ -908,6 +943,7 @@ err_alloc:
        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 +1021,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]);
index d17c5ea3d55db5492b1e76d0202c2d7aac46dcc4..02b83f5499b8acd94cd504300219c30535a1a7d7 100644 (file)
@@ -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;