]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
gpio: tqmx86: fix broken IRQ_TYPE_EDGE_BOTH interrupt type
authorMatthias Schiffer <matthias.schiffer@ew.tq-group.com>
Thu, 30 May 2024 10:20:02 +0000 (12:20 +0200)
committerBartosz Golaszewski <bartosz.golaszewski@linaro.org>
Mon, 3 Jun 2024 12:13:13 +0000 (14:13 +0200)
The TQMx86 GPIO controller only supports falling and rising edge
triggers, but not both. Fix this by implementing a software both-edge
mode that toggles the edge type after every interrupt.

Fixes: b868db94a6a7 ("gpio: tqmx86: Add GPIO from for this IO controller")
Co-developed-by: Gregor Herburger <gregor.herburger@tq-group.com>
Signed-off-by: Gregor Herburger <gregor.herburger@tq-group.com>
Signed-off-by: Matthias Schiffer <matthias.schiffer@ew.tq-group.com>
Link: https://lore.kernel.org/r/515324f0491c4d44f4ef49f170354aca002d81ef.1717063994.git.matthias.schiffer@ew.tq-group.com
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
drivers/gpio/gpio-tqmx86.c

index 7e428c872a25794bc62293d6bcbbdc4a5b377a06..f2e7e8754d95d699c0c2e4aa334e362396c376f4 100644 (file)
 #define TQMX86_GPII_NONE       0
 #define TQMX86_GPII_FALLING    BIT(0)
 #define TQMX86_GPII_RISING     BIT(1)
+/* Stored in irq_type as a trigger type, but not actually valid as a register
+ * value, so the name doesn't use "GPII"
+ */
+#define TQMX86_INT_BOTH                (BIT(0) | BIT(1))
 #define TQMX86_GPII_MASK       (BIT(0) | BIT(1))
 #define TQMX86_GPII_BITS       2
 /* Stored in irq_type with GPII bits */
@@ -113,9 +117,15 @@ static void tqmx86_gpio_irq_config(struct tqmx86_gpio_data *gpio, int offset)
 {
        u8 type = TQMX86_GPII_NONE, gpiic;
 
-       if (gpio->irq_type[offset] & TQMX86_INT_UNMASKED)
+       if (gpio->irq_type[offset] & TQMX86_INT_UNMASKED) {
                type = gpio->irq_type[offset] & TQMX86_GPII_MASK;
 
+               if (type == TQMX86_INT_BOTH)
+                       type = tqmx86_gpio_get(&gpio->chip, offset + TQMX86_NGPO)
+                               ? TQMX86_GPII_FALLING
+                               : TQMX86_GPII_RISING;
+       }
+
        gpiic = tqmx86_gpio_read(gpio, TQMX86_GPIIC);
        gpiic &= ~(TQMX86_GPII_MASK << (offset * TQMX86_GPII_BITS));
        gpiic |= type << (offset * TQMX86_GPII_BITS);
@@ -169,7 +179,7 @@ static int tqmx86_gpio_irq_set_type(struct irq_data *data, unsigned int type)
                new_type = TQMX86_GPII_FALLING;
                break;
        case IRQ_TYPE_EDGE_BOTH:
-               new_type = TQMX86_GPII_FALLING | TQMX86_GPII_RISING;
+               new_type = TQMX86_INT_BOTH;
                break;
        default:
                return -EINVAL; /* not supported */
@@ -189,8 +199,8 @@ static void tqmx86_gpio_irq_handler(struct irq_desc *desc)
        struct gpio_chip *chip = irq_desc_get_handler_data(desc);
        struct tqmx86_gpio_data *gpio = gpiochip_get_data(chip);
        struct irq_chip *irq_chip = irq_desc_get_chip(desc);
-       unsigned long irq_bits;
-       int i = 0;
+       unsigned long irq_bits, flags;
+       int i;
        u8 irq_status;
 
        chained_irq_enter(irq_chip, desc);
@@ -199,6 +209,34 @@ static void tqmx86_gpio_irq_handler(struct irq_desc *desc)
        tqmx86_gpio_write(gpio, irq_status, TQMX86_GPIIS);
 
        irq_bits = irq_status;
+
+       raw_spin_lock_irqsave(&gpio->spinlock, flags);
+       for_each_set_bit(i, &irq_bits, TQMX86_NGPI) {
+               /*
+                * Edge-both triggers are implemented by flipping the edge
+                * trigger after each interrupt, as the controller only supports
+                * either rising or falling edge triggers, but not both.
+                *
+                * Internally, the TQMx86 GPIO controller has separate status
+                * registers for rising and falling edge interrupts. GPIIC
+                * configures which bits from which register are visible in the
+                * interrupt status register GPIIS and defines what triggers the
+                * parent IRQ line. Writing to GPIIS always clears both rising
+                * and falling interrupt flags internally, regardless of the
+                * currently configured trigger.
+                *
+                * In consequence, we can cleanly implement the edge-both
+                * trigger in software by first clearing the interrupt and then
+                * setting the new trigger based on the current GPIO input in
+                * tqmx86_gpio_irq_config() - even if an edge arrives between
+                * reading the input and setting the trigger, we will have a new
+                * interrupt pending.
+                */
+               if ((gpio->irq_type[i] & TQMX86_GPII_MASK) == TQMX86_INT_BOTH)
+                       tqmx86_gpio_irq_config(gpio, i);
+       }
+       raw_spin_unlock_irqrestore(&gpio->spinlock, flags);
+
        for_each_set_bit(i, &irq_bits, TQMX86_NGPI)
                generic_handle_domain_irq(gpio->chip.irq.domain,
                                          i + TQMX86_NGPO);