]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
gpio: mpfs: Add interrupt support
authorConor Dooley <conor.dooley@microchip.com>
Wed, 18 Mar 2026 11:04:33 +0000 (11:04 +0000)
committerConor Dooley <conor.dooley@microchip.com>
Tue, 31 Mar 2026 13:13:14 +0000 (14:13 +0100)
Add support for interrupts to the PolarFire SoC GPIO driver. Each GPIO
has an independent interrupt that is wired to an interrupt mux that sits
between the controllers and the PLIC. The SoC has more GPIO lines than
connections from the mux to the PLIC, so some GPIOs must share PLIC
interrupts. The configuration is not static and is set at runtime,
conventionally by the platform's firmware. CoreGPIO, the version
intended for use in the FPGA fabric has two interrupt output ports, one
is IO_NUM bits wide, as is used in the hardened cores, and the other is
a single bit with all lines ORed together.

Acked-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
Reviewed-by: Linus Walleij <linusw@kernel.org>
Signed-off-by: Conor Dooley <conor.dooley@microchip.com>
drivers/gpio/Kconfig
drivers/gpio/gpio-mpfs.c

index b45fb799e36c19794e56bbac7539250c09beaeb7..1d1239323f61558fe033c910d21b885a47eb4214 100644 (file)
@@ -572,6 +572,7 @@ config GPIO_PL061
 config GPIO_POLARFIRE_SOC
        bool "Microchip FPGA GPIO support"
        select REGMAP_MMIO
+       select GPIOLIB_IRQCHIP
        help
          Say yes here to support the GPIO controllers on Microchip FPGAs.
 
index 9468795b96348ae61dc476c4935d80189e13e160..1a4cf213c723c27edc7df8c98d3118beadf443f2 100644 (file)
@@ -9,8 +9,9 @@
 #include <linux/device.h>
 #include <linux/errno.h>
 #include <linux/gpio/driver.h>
-#include <linux/init.h>
+#include <linux/interrupt.h>
 #include <linux/mod_devicetable.h>
+#include <linux/of_irq.h>
 #include <linux/platform_device.h>
 #include <linux/property.h>
 #include <linux/regmap.h>
@@ -18,7 +19,7 @@
 
 #define MPFS_GPIO_CTRL(i)              (0x4 * (i))
 #define MPFS_MAX_NUM_GPIO              32
-#define MPFS_GPIO_EN_INT               3
+#define MPFS_GPIO_EN_INT               BIT(3)
 #define MPFS_GPIO_EN_OUT_BUF           BIT(2)
 #define MPFS_GPIO_EN_IN                        BIT(1)
 #define MPFS_GPIO_EN_OUT               BIT(0)
@@ -52,6 +53,7 @@ static const struct regmap_config mpfs_gpio_regmap_config = {
        .reg_bits = 32,
        .reg_stride = 4,
        .val_bits = 32,
+       .use_raw_spinlock = true,
 };
 
 static int mpfs_gpio_direction_input(struct gpio_chip *gc, unsigned int gpio_index)
@@ -114,13 +116,98 @@ static int mpfs_gpio_set(struct gpio_chip *gc, unsigned int gpio_index, int valu
        return ret;
 }
 
+static int mpfs_gpio_irq_set_type(struct irq_data *data, unsigned int type)
+{
+       struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
+       struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc);
+       int gpio_index = irqd_to_hwirq(data) % 32;
+       u32 interrupt_type;
+
+       switch (type) {
+       case IRQ_TYPE_EDGE_BOTH:
+               interrupt_type = MPFS_GPIO_TYPE_INT_EDGE_BOTH;
+               break;
+       case IRQ_TYPE_EDGE_FALLING:
+               interrupt_type = MPFS_GPIO_TYPE_INT_EDGE_NEG;
+               break;
+       case IRQ_TYPE_EDGE_RISING:
+               interrupt_type = MPFS_GPIO_TYPE_INT_EDGE_POS;
+               break;
+       case IRQ_TYPE_LEVEL_HIGH:
+               interrupt_type = MPFS_GPIO_TYPE_INT_LEVEL_HIGH;
+               break;
+       case IRQ_TYPE_LEVEL_LOW:
+               interrupt_type = MPFS_GPIO_TYPE_INT_LEVEL_LOW;
+               break;
+       }
+
+       regmap_update_bits(mpfs_gpio->regs, MPFS_GPIO_CTRL(gpio_index),
+                          MPFS_GPIO_TYPE_INT_MASK, interrupt_type);
+
+       return 0;
+}
+
+static void mpfs_gpio_irq_unmask(struct irq_data *data)
+{
+       struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
+       struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc);
+       int gpio_index = irqd_to_hwirq(data) % 32;
+
+       gpiochip_enable_irq(gc, gpio_index);
+       mpfs_gpio_direction_input(gc, gpio_index);
+       regmap_update_bits(mpfs_gpio->regs, MPFS_GPIO_CTRL(gpio_index),
+                            MPFS_GPIO_EN_INT, MPFS_GPIO_EN_INT);
+}
+
+static void mpfs_gpio_irq_mask(struct irq_data *data)
+{
+       struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
+       struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc);
+       int gpio_index = irqd_to_hwirq(data) % 32;
+
+       regmap_update_bits(mpfs_gpio->regs, MPFS_GPIO_CTRL(gpio_index),
+                          MPFS_GPIO_EN_INT, 0);
+       gpiochip_disable_irq(gc, gpio_index);
+}
+
+static const struct irq_chip mpfs_gpio_irqchip = {
+       .name = "MPFS GPIO",
+       .irq_set_type = mpfs_gpio_irq_set_type,
+       .irq_mask = mpfs_gpio_irq_mask,
+       .irq_unmask = mpfs_gpio_irq_unmask,
+       .flags = IRQCHIP_IMMUTABLE | IRQCHIP_MASK_ON_SUSPEND,
+       GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static void mpfs_gpio_irq_handler(struct irq_desc *desc)
+{
+       struct irq_chip *irqchip = irq_desc_get_chip(desc);
+       struct mpfs_gpio_chip *mpfs_gpio = irq_desc_get_handler_data(desc);
+       unsigned long status;
+       u32 val;
+       int i;
+
+       chained_irq_enter(irqchip, desc);
+
+       regmap_read(mpfs_gpio->regs, MPFS_IRQ_REG, &val);
+       status = val;
+       for_each_set_bit(i, &status, MPFS_MAX_NUM_GPIO) {
+               regmap_write(mpfs_gpio->regs, MPFS_IRQ_REG, BIT(i));
+               generic_handle_domain_irq(mpfs_gpio->gc.irq.domain, i);
+       }
+
+       chained_irq_exit(irqchip, desc);
+}
+
 static int mpfs_gpio_probe(struct platform_device *pdev)
 {
        struct device *dev = &pdev->dev;
+       struct device_node *node = dev->of_node;
        struct mpfs_gpio_chip *mpfs_gpio;
+       struct gpio_irq_chip *girq;
        struct clk *clk;
        void __iomem *base;
-       int ngpios;
+       int ngpios, nirqs, ret;
 
        mpfs_gpio = devm_kzalloc(dev, sizeof(*mpfs_gpio), GFP_KERNEL);
        if (!mpfs_gpio)
@@ -157,6 +244,35 @@ static int mpfs_gpio_probe(struct platform_device *pdev)
        mpfs_gpio->gc.parent = dev;
        mpfs_gpio->gc.owner = THIS_MODULE;
 
+       nirqs = of_irq_count(node);
+       if (nirqs > MPFS_MAX_NUM_GPIO)
+               return -ENXIO;
+
+       if (nirqs) {
+               girq = &mpfs_gpio->gc.irq;
+
+               gpio_irq_chip_set_chip(girq, &mpfs_gpio_irqchip);
+
+               girq->num_parents = nirqs;
+               girq->parents = devm_kcalloc(&pdev->dev, girq->num_parents,
+                                            sizeof(*girq->parents), GFP_KERNEL);
+               if (!girq->parents)
+                       return -ENOMEM;
+
+               for (int i = 0; i < nirqs; i++) {
+                       ret = platform_get_irq(pdev, i);
+                       if (ret < 0)
+                               return ret;
+
+                       girq->parents[i] = ret;
+                       girq->parent_handler_data = mpfs_gpio;
+                       girq->parent_handler = mpfs_gpio_irq_handler;
+               }
+
+               girq->handler = handle_level_irq;
+               girq->default_type = IRQ_TYPE_NONE;
+       }
+
        return devm_gpiochip_add_data(dev, &mpfs_gpio->gc, mpfs_gpio);
 }