]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
gpio: pca953x: handle short interrupt pulses on PCAL devices
authorErnest Van Hoecke <ernest.vanhoecke@toradex.com>
Wed, 17 Dec 2025 15:30:25 +0000 (16:30 +0100)
committerBartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
Fri, 2 Jan 2026 09:37:35 +0000 (10:37 +0100)
GPIO drivers with latch input support may miss short pulses on input
pins even when input latching is enabled. The generic interrupt logic in
the pca953x driver reports interrupts by comparing the current input
value against the previously sampled one and only signals an event when
a level change is observed between two reads.

For short pulses, the first edge is captured when the input register is
read, but if the signal returns to its previous level before the read,
the second edge is not observed. As a result, successive pulses can
produce identical input values at read time and no level change is
detected, causing interrupts to be missed. Below timing diagram shows
this situation where the top signal is the input pin level and the
bottom signal indicates the latched value.

─────┐     ┌──*───────────────┐     ┌──*─────────────────┐     ┌──*───
     │     │  .               │     │  .                 │     │  .
     │     │  │               │     │  │                 │     │  │
     └──*──┘  │               └──*──┘  │                 └──*──┘  │
Input   │     │                  │     │                    │     │
        ▼     │                  ▼     │                    ▼     │
       IRQ    │                 IRQ    │                   IRQ    │
              .                        .                          .
─────┐        .┌──────────────┐        .┌────────────────┐        .┌──
     │         │              │         │                │         │
     │         │              │         │                │         │
     └────────*┘              └────────*┘                └────────*┘
Latched       │                        │                          │
              ▼                        ▼                          ▼
            READ 0                   READ 0                     READ 0
                                   NO CHANGE                  NO CHANGE

PCAL variants provide an interrupt status register that records which
pins triggered an interrupt, but the status and input registers cannot
be read atomically. The interrupt status is only cleared when the input
port is read, and the input value must also be read to determine the
triggering edge. If another interrupt occurs on a different line after
the status register has been read but before the input register is
sampled, that event will not be reflected in the earlier status
snapshot, so relying solely on the interrupt status register is also
insufficient.

Support for input latching and interrupt status handling was previously
added by [1], but the interrupt status-based logic was reverted by [2]
due to these issues. This patch addresses the original problem by
combining both sources of information. Events indicated by the interrupt
status register are merged with events detected through the existing
level-change logic. As a result:

* short pulses, whose second edges are invisible, are detected via the
  interrupt status register, and
* interrupts that occur between the status and input reads are still
  caught by the generic level-change logic.

This significantly improves robustness on devices that signal interrupts
as short pulses, while avoiding the issues that led to the earlier
reversion. In practice, even if only the first edge of a pulse is
observable, the interrupt is reliably detected.

This fixes missed interrupts from an Ilitek touch controller with its
interrupt line connected to a PCAL6416A, where active-low pulses are
approximately 200 us long.

[1] commit 44896beae605 ("gpio: pca953x: add PCAL9535 interrupt support for Galileo Gen2")
[2] commit d6179f6c6204 ("gpio: pca953x: Improve interrupt support")

Fixes: d6179f6c6204 ("gpio: pca953x: Improve interrupt support")
Signed-off-by: Ernest Van Hoecke <ernest.vanhoecke@toradex.com>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Link: https://lore.kernel.org/r/20251217153050.142057-1-ernestvanhoecke@gmail.com
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
drivers/gpio/gpio-pca953x.c

index 0a3916cc2772a19de90c7683cb7dc7010524f71e..8727ae54bc578b7dfd34ae5f0ccf770e7dc0fb9c 100644 (file)
@@ -943,14 +943,35 @@ static bool pca953x_irq_pending(struct pca953x_chip *chip, unsigned long *pendin
        DECLARE_BITMAP(old_stat, MAX_LINE);
        DECLARE_BITMAP(cur_stat, MAX_LINE);
        DECLARE_BITMAP(new_stat, MAX_LINE);
+       DECLARE_BITMAP(int_stat, MAX_LINE);
        DECLARE_BITMAP(trigger, MAX_LINE);
        DECLARE_BITMAP(edges, MAX_LINE);
        int ret;
 
+       if (chip->driver_data & PCA_PCAL) {
+               /* Read INT_STAT before it is cleared by the input-port read. */
+               ret = pca953x_read_regs(chip, PCAL953X_INT_STAT, int_stat);
+               if (ret)
+                       return false;
+       }
+
        ret = pca953x_read_regs(chip, chip->regs->input, cur_stat);
        if (ret)
                return false;
 
+       if (chip->driver_data & PCA_PCAL) {
+               /* Detect short pulses via INT_STAT. */
+               bitmap_and(trigger, int_stat, chip->irq_mask, gc->ngpio);
+
+               /* Apply filter for rising/falling edge selection. */
+               bitmap_replace(new_stat, chip->irq_trig_fall, chip->irq_trig_raise,
+                              cur_stat, gc->ngpio);
+
+               bitmap_and(int_stat, new_stat, trigger, gc->ngpio);
+       } else {
+               bitmap_zero(int_stat, gc->ngpio);
+       }
+
        /* Remove output pins from the equation */
        pca953x_read_regs(chip, chip->regs->direction, reg_direction);
 
@@ -964,7 +985,8 @@ static bool pca953x_irq_pending(struct pca953x_chip *chip, unsigned long *pendin
 
        if (bitmap_empty(chip->irq_trig_level_high, gc->ngpio) &&
            bitmap_empty(chip->irq_trig_level_low, gc->ngpio)) {
-               if (bitmap_empty(trigger, gc->ngpio))
+               if (bitmap_empty(trigger, gc->ngpio) &&
+                   bitmap_empty(int_stat, gc->ngpio))
                        return false;
        }
 
@@ -972,6 +994,7 @@ static bool pca953x_irq_pending(struct pca953x_chip *chip, unsigned long *pendin
        bitmap_and(old_stat, chip->irq_trig_raise, new_stat, gc->ngpio);
        bitmap_or(edges, old_stat, cur_stat, gc->ngpio);
        bitmap_and(pending, edges, trigger, gc->ngpio);
+       bitmap_or(pending, pending, int_stat, gc->ngpio);
 
        bitmap_and(cur_stat, new_stat, chip->irq_trig_level_high, gc->ngpio);
        bitmap_and(cur_stat, cur_stat, chip->irq_mask, gc->ngpio);