summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorErnest Van Hoecke <ernest.vanhoecke@toradex.com>2025-12-16 13:53:08 +0100
committerErnest Van Hoecke <ernest.vanhoecke@toradex.com>2026-01-05 10:23:13 +0100
commite5c5595adb060ba4c1cd5420a99fd2ce02d11f21 (patch)
tree10aff73e8c5d19719c77a5d4c6b35c094c359ff0
parent2909722059efff87c984ede099de0cb726727a15 (diff)
gpio: pca953x: handle short interrupt pulses on PCAL devicestoradex_6.6-2.2.x-imx
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") Upstream-Status: Submitted [https://lore.kernel.org/all/20251217153050.142057-1-ernestvanhoecke@gmail.com/] Fixes: d6179f6c6204 ("gpio: pca953x: Improve interrupt support") Signed-off-by: Ernest Van Hoecke <ernest.vanhoecke@toradex.com>
-rw-r--r--drivers/gpio/gpio-pca953x.c25
1 files changed, 24 insertions, 1 deletions
diff --git a/drivers/gpio/gpio-pca953x.c b/drivers/gpio/gpio-pca953x.c
index 8a72520b63d3..1819b1031b74 100644
--- a/drivers/gpio/gpio-pca953x.c
+++ b/drivers/gpio/gpio-pca953x.c
@@ -833,13 +833,34 @@ 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);
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);
@@ -851,13 +872,15 @@ static bool pca953x_irq_pending(struct pca953x_chip *chip, unsigned long *pendin
bitmap_copy(chip->irq_stat, new_stat, gc->ngpio);
- if (bitmap_empty(trigger, gc->ngpio))
+ if (bitmap_empty(trigger, gc->ngpio) &&
+ bitmap_empty(int_stat, gc->ngpio))
return false;
bitmap_and(cur_stat, chip->irq_trig_fall, old_stat, gc->ngpio);
bitmap_and(old_stat, chip->irq_trig_raise, new_stat, gc->ngpio);
bitmap_or(new_stat, old_stat, cur_stat, gc->ngpio);
bitmap_and(pending, new_stat, trigger, gc->ngpio);
+ bitmap_or(pending, pending, int_stat, gc->ngpio);
return !bitmap_empty(pending, gc->ngpio);
}