]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
gpio: kempld: Implement the interrupt controller
authorAlban Bedel <alban.bedel@lht.dlh.de>
Wed, 11 Mar 2026 14:31:20 +0000 (15:31 +0100)
committerBartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
Mon, 16 Mar 2026 09:05:33 +0000 (10:05 +0100)
Add a GPIO IRQ chip implementation for the kempld GPIO controller. Of
note is only how the parent IRQ is obtained.

The IRQ for the GPIO controller can be configured in the BIOS, along
with the IRQ for the I2C controller. These IRQ are returned by ACPI
but this information is only usable if both IRQ are configured. When
only one is configured, only one is returned making it impossible to
know which one it is.

Luckily the BIOS will set the configured IRQ in the PLD registers, so
it can be read from there instead, and that also work on platforms
without ACPI.

The vendor driver allowed to override the IRQ using a module
parameters, so there are boards in field which used this parameter
instead of properly configuring the BIOS. This implementation provides
this as well for compatibility.

Signed-off-by: Alban Bedel <alban.bedel@lht.dlh.de>
Link: https://patch.msgid.link/20260311143120.2179347-5-alban.bedel@lht.dlh.de
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
drivers/gpio/Kconfig
drivers/gpio/gpio-kempld.c
include/linux/mfd/kempld.h

index b45fb799e36c19794e56bbac7539250c09beaeb7..d665afe19709709e41c27a8f4e225c767576f9de 100644 (file)
@@ -1440,6 +1440,7 @@ config GPIO_JANZ_TTL
 config GPIO_KEMPLD
        tristate "Kontron ETX / COMexpress GPIO"
        depends on MFD_KEMPLD
+       select GPIOLIB_IRQCHIP
        help
          This enables support for the PLD GPIO interface on some Kontron ETX
          and COMexpress (ETXexpress) modules.
index 7dd94ff6f2df40bb8653ab504d3390240c6324d6..5a63df3ea5fa77aae7a0a3dc40b66b8492c979fc 100644 (file)
@@ -11,6 +11,7 @@
 #include <linux/module.h>
 #include <linux/bitops.h>
 #include <linux/errno.h>
+#include <linux/interrupt.h>
 #include <linux/platform_device.h>
 #include <linux/gpio/driver.h>
 #include <linux/mfd/kempld.h>
 #define KEMPLD_GPIO_MASK(x)            (BIT((x) % 8))
 #define KEMPLD_GPIO_DIR                        0x40
 #define KEMPLD_GPIO_LVL                        0x42
+#define KEMPLD_GPIO_STS                        0x44
 #define KEMPLD_GPIO_EVT_LVL_EDGE       0x46
+#define KEMPLD_GPIO_EVT_LOW_HIGH       0x48
 #define KEMPLD_GPIO_IEN                        0x4A
+#define KEMPLD_GPIO_OUT_LVL            0x4E
+
+/* The IRQ to use if none was configured in the BIOS */
+static unsigned int gpio_irq;
+module_param_hw(gpio_irq, uint, irq, 0444);
+MODULE_PARM_DESC(gpio_irq, "Set legacy GPIO IRQ (1-15)");
 
 struct kempld_gpio_data {
        struct gpio_chip                chip;
        struct kempld_device_data       *pld;
        u8                              out_lvl_reg;
+
+       struct mutex                    irq_lock;
+       u16                             ien;
+       u16                             evt_low_high;
+       u16                             evt_lvl_edge;
 };
 
 /*
@@ -193,6 +207,180 @@ static int kempld_gpio_pincount(struct kempld_device_data *pld)
        return evt ? __ffs(evt) : 16;
 }
 
+static void kempld_irq_mask(struct irq_data *data)
+{
+       struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
+       struct kempld_gpio_data *gpio = gpiochip_get_data(chip);
+
+       gpio->ien &= ~BIT(irqd_to_hwirq(data));
+       gpiochip_disable_irq(chip, irqd_to_hwirq(data));
+}
+
+static void kempld_irq_unmask(struct irq_data *data)
+{
+       struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
+       struct kempld_gpio_data *gpio = gpiochip_get_data(chip);
+
+       gpiochip_enable_irq(chip, irqd_to_hwirq(data));
+       gpio->ien |= BIT(irqd_to_hwirq(data));
+}
+
+static int kempld_irq_set_type(struct irq_data *data, unsigned int type)
+{
+       struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
+       struct kempld_gpio_data *gpio = gpiochip_get_data(chip);
+
+       switch (type) {
+       case IRQ_TYPE_EDGE_RISING:
+               gpio->evt_low_high |= BIT(data->hwirq);
+               gpio->evt_lvl_edge |= BIT(data->hwirq);
+               break;
+       case IRQ_TYPE_EDGE_FALLING:
+               gpio->evt_low_high &= ~BIT(data->hwirq);
+               gpio->evt_lvl_edge |= BIT(data->hwirq);
+               break;
+       case IRQ_TYPE_LEVEL_HIGH:
+               gpio->evt_low_high |= BIT(data->hwirq);
+               gpio->evt_lvl_edge &= ~BIT(data->hwirq);
+               break;
+       case IRQ_TYPE_LEVEL_LOW:
+               gpio->evt_low_high &= ~BIT(data->hwirq);
+               gpio->evt_lvl_edge &= ~BIT(data->hwirq);
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static void kempld_irq_bus_lock(struct irq_data *data)
+{
+       struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
+       struct kempld_gpio_data *gpio = gpiochip_get_data(chip);
+
+       mutex_lock(&gpio->irq_lock);
+}
+
+static void kempld_irq_bus_sync_unlock(struct irq_data *data)
+{
+       struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
+       struct kempld_gpio_data *gpio = gpiochip_get_data(chip);
+       struct kempld_device_data *pld = gpio->pld;
+
+       kempld_get_mutex(pld);
+       kempld_write16(pld, KEMPLD_GPIO_EVT_LVL_EDGE, gpio->evt_lvl_edge);
+       kempld_write16(pld, KEMPLD_GPIO_EVT_LOW_HIGH, gpio->evt_low_high);
+       kempld_write16(pld, KEMPLD_GPIO_IEN, gpio->ien);
+       kempld_release_mutex(pld);
+
+       mutex_unlock(&gpio->irq_lock);
+}
+
+static const struct irq_chip kempld_irqchip = {
+       .name                   = "kempld-gpio",
+       .irq_mask               = kempld_irq_mask,
+       .irq_unmask             = kempld_irq_unmask,
+       .irq_set_type           = kempld_irq_set_type,
+       .irq_bus_lock           = kempld_irq_bus_lock,
+       .irq_bus_sync_unlock    = kempld_irq_bus_sync_unlock,
+       .flags                  = IRQCHIP_IMMUTABLE,
+       GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static irqreturn_t kempld_gpio_irq_handler(int irq, void *data)
+{
+       struct kempld_gpio_data *gpio = data;
+       struct gpio_chip *chip = &gpio->chip;
+       unsigned int pin, child_irq;
+       unsigned long status;
+
+       kempld_get_mutex(gpio->pld);
+
+       status = kempld_read16(gpio->pld, KEMPLD_GPIO_STS);
+       if (status)
+               kempld_write16(gpio->pld, KEMPLD_GPIO_STS, status);
+
+       kempld_release_mutex(gpio->pld);
+
+       status &= gpio->ien;
+       if (!status)
+               return IRQ_NONE;
+
+       for_each_set_bit(pin, &status, chip->ngpio) {
+               child_irq = irq_find_mapping(chip->irq.domain, pin);
+               handle_nested_irq(child_irq);
+       }
+
+       return IRQ_HANDLED;
+}
+
+static int kempld_gpio_irq_init(struct device *dev,
+                               struct kempld_gpio_data *gpio)
+{
+       struct kempld_device_data *pld = gpio->pld;
+       struct gpio_chip *chip = &gpio->chip;
+       struct gpio_irq_chip *girq;
+       unsigned int irq;
+       int ret;
+
+       /* Get the IRQ configured by the BIOS in the PLD */
+       kempld_get_mutex(pld);
+       irq = kempld_read8(pld, KEMPLD_IRQ_GPIO);
+       kempld_release_mutex(pld);
+
+       if (irq == 0xff) {
+               dev_info(dev, "GPIO controller has no IRQ support\n");
+               return 0;
+       }
+
+       /* Allow overriding the IRQ with the module parameter */
+       if (gpio_irq > 0) {
+               dev_warn(dev, "Forcing IRQ to %d\n", gpio_irq);
+               irq &= ~KEMPLD_IRQ_GPIO_MASK;
+               irq |= gpio_irq & KEMPLD_IRQ_GPIO_MASK;
+       }
+
+       if (!(irq & KEMPLD_IRQ_GPIO_MASK)) {
+               dev_warn(dev, "No IRQ configured\n");
+               return 0;
+       }
+
+       /* Get the current config, disable all child interrupts, clear them
+        * and set the parent IRQ
+        */
+       kempld_get_mutex(pld);
+       gpio->evt_low_high = kempld_read16(pld, KEMPLD_GPIO_EVT_LOW_HIGH);
+       gpio->evt_lvl_edge = kempld_read16(pld, KEMPLD_GPIO_EVT_LVL_EDGE);
+       kempld_write16(pld, KEMPLD_GPIO_IEN, 0);
+       kempld_write16(pld, KEMPLD_GPIO_STS, 0xFFFF);
+       kempld_write16(pld, KEMPLD_IRQ_GPIO, irq);
+       kempld_release_mutex(pld);
+
+       girq = &chip->irq;
+       gpio_irq_chip_set_chip(girq, &kempld_irqchip);
+
+       girq->parent_handler = NULL;
+       girq->num_parents = 0;
+       girq->parents = NULL;
+       girq->default_type = IRQ_TYPE_NONE;
+       girq->handler = handle_simple_irq;
+       girq->threaded = true;
+
+       mutex_init(&gpio->irq_lock);
+
+       ret = devm_request_threaded_irq(dev, irq & KEMPLD_IRQ_GPIO_MASK,
+                                       NULL, kempld_gpio_irq_handler,
+                                       IRQF_ONESHOT, chip->label,
+                                       gpio);
+       if (ret) {
+               dev_err(dev, "failed to request irq %d\n", irq);
+               return ret;
+       }
+
+       return 0;
+}
+
 static int kempld_gpio_probe(struct platform_device *pdev)
 {
        struct device *dev = &pdev->dev;
@@ -247,6 +435,10 @@ static int kempld_gpio_probe(struct platform_device *pdev)
                return -ENODEV;
        }
 
+       ret = kempld_gpio_irq_init(dev, gpio);
+       if (ret)
+               return ret;
+
        ret = devm_gpiochip_add_data(dev, chip, gpio);
        if (ret) {
                dev_err(dev, "Could not register GPIO chip\n");
index 643c096b93ace419e272471a3b9cd0120d9d4c36..2dbd80abfd1d29d23c86659c1440452fe9847393 100644 (file)
@@ -37,6 +37,7 @@
 #define KEMPLD_SPEC_GET_MINOR(x)       (x & 0x0f)
 #define KEMPLD_SPEC_GET_MAJOR(x)       ((x >> 4) & 0x0f)
 #define KEMPLD_IRQ_GPIO                        0x35
+#define KEMPLD_IRQ_GPIO_MASK           0x0f
 #define KEMPLD_IRQ_I2C                 0x36
 #define KEMPLD_CFG                     0x37
 #define KEMPLD_CFG_GPIO_I2C_MUX                (1 << 0)