gpio: realtek-otto: Support per-cpu interrupts
authorSander Vanheule <sander@svanheule.net>
Sat, 9 Apr 2022 19:55:48 +0000 (21:55 +0200)
committerBartosz Golaszewski <brgl@bgdev.pl>
Mon, 11 Apr 2022 12:27:58 +0000 (14:27 +0200)
On SoCs with multiple cores, it is possible that the GPIO interrupt
controller supports assigning specific pins to one or more cores.

IRQ balancing can be performed on a line-by-line basis if the parent
interrupt is routed to all available cores, which is the default upon
initialisation.

Signed-off-by: Sander Vanheule <sander@svanheule.net>
Signed-off-by: Bartosz Golaszewski <brgl@bgdev.pl>
drivers/gpio/gpio-realtek-otto.c

index c838ad8ce55fb38832eb156adfed6c7b056e5a63..dd1b7656d23a417f9f09b1a0867dcbc1abe9ae85 100644 (file)
@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: GPL-2.0-only
 
 #include <linux/gpio/driver.h>
+#include <linux/cpumask.h>
 #include <linux/irq.h>
 #include <linux/minmax.h>
 #include <linux/mod_devicetable.h>
@@ -55,6 +56,8 @@
 struct realtek_gpio_ctrl {
        struct gpio_chip gc;
        void __iomem *base;
+       void __iomem *cpumask_base;
+       struct cpumask cpu_irq_maskable;
        raw_spinlock_t lock;
        u16 intr_mask[REALTEK_GPIO_PORTS_PER_BANK];
        u16 intr_type[REALTEK_GPIO_PORTS_PER_BANK];
@@ -76,6 +79,11 @@ enum realtek_gpio_flags {
         * fields, and [BA, DC] for 2-bit fields.
         */
        GPIO_PORTS_REVERSED = BIT(1),
+       /*
+        * Interrupts can be enabled per cpu. This requires a secondary IO
+        * range, where the per-cpu enable masks are located.
+        */
+       GPIO_INTERRUPTS_PER_CPU = BIT(2),
 };
 
 static struct realtek_gpio_ctrl *irq_data_to_ctrl(struct irq_data *data)
@@ -247,14 +255,61 @@ static void realtek_gpio_irq_handler(struct irq_desc *desc)
        chained_irq_exit(irq_chip, desc);
 }
 
+static inline void __iomem *realtek_gpio_irq_cpu_mask(struct realtek_gpio_ctrl *ctrl,
+       unsigned int port, int cpu)
+{
+       return ctrl->cpumask_base + ctrl->port_offset_u8(port) +
+               REALTEK_GPIO_PORTS_PER_BANK * cpu;
+}
+
+static int realtek_gpio_irq_set_affinity(struct irq_data *data,
+       const struct cpumask *dest, bool force)
+{
+       struct realtek_gpio_ctrl *ctrl = irq_data_to_ctrl(data);
+       unsigned int line = irqd_to_hwirq(data);
+       unsigned int port = line / 8;
+       unsigned int port_pin = line % 8;
+       void __iomem *irq_cpu_mask;
+       unsigned long flags;
+       int cpu;
+       u8 v;
+
+       if (!ctrl->cpumask_base)
+               return -ENXIO;
+
+       raw_spin_lock_irqsave(&ctrl->lock, flags);
+
+       for_each_cpu(cpu, &ctrl->cpu_irq_maskable) {
+               irq_cpu_mask = realtek_gpio_irq_cpu_mask(ctrl, port, cpu);
+               v = ioread8(irq_cpu_mask);
+
+               if (cpumask_test_cpu(cpu, dest))
+                       v |= BIT(port_pin);
+               else
+                       v &= ~BIT(port_pin);
+
+               iowrite8(v, irq_cpu_mask);
+       }
+
+       raw_spin_unlock_irqrestore(&ctrl->lock, flags);
+
+       irq_data_update_effective_affinity(data, dest);
+
+       return 0;
+}
+
 static int realtek_gpio_irq_init(struct gpio_chip *gc)
 {
        struct realtek_gpio_ctrl *ctrl = gpiochip_get_data(gc);
        unsigned int port;
+       int cpu;
 
        for (port = 0; (port * 8) < gc->ngpio; port++) {
                realtek_gpio_write_imr(ctrl, port, 0, 0);
                realtek_gpio_clear_isr(ctrl, port, GENMASK(7, 0));
+
+               for_each_cpu(cpu, &ctrl->cpu_irq_maskable)
+                       iowrite8(GENMASK(7, 0), realtek_gpio_irq_cpu_mask(ctrl, port, cpu));
        }
 
        return 0;
@@ -266,6 +321,7 @@ static struct irq_chip realtek_gpio_irq_chip = {
        .irq_mask = realtek_gpio_irq_mask,
        .irq_unmask = realtek_gpio_irq_unmask,
        .irq_set_type = realtek_gpio_irq_set_type,
+       .irq_set_affinity = realtek_gpio_irq_set_affinity,
 };
 
 static const struct of_device_id realtek_gpio_of_match[] = {
@@ -290,8 +346,10 @@ static int realtek_gpio_probe(struct platform_device *pdev)
        unsigned int dev_flags;
        struct gpio_irq_chip *girq;
        struct realtek_gpio_ctrl *ctrl;
+       struct resource *res;
        u32 ngpios;
-       int err, irq;
+       unsigned int nr_cpus;
+       int cpu, err, irq;
 
        ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL);
        if (!ctrl)
@@ -352,6 +410,21 @@ static int realtek_gpio_probe(struct platform_device *pdev)
                girq->init_hw = realtek_gpio_irq_init;
        }
 
+       cpumask_clear(&ctrl->cpu_irq_maskable);
+
+       if ((dev_flags & GPIO_INTERRUPTS_PER_CPU) && irq > 0) {
+               ctrl->cpumask_base = devm_platform_get_and_ioremap_resource(pdev, 1, &res);
+               if (IS_ERR(ctrl->cpumask_base))
+                       return dev_err_probe(dev, PTR_ERR(ctrl->cpumask_base),
+                               "missing CPU IRQ mask registers");
+
+               nr_cpus = resource_size(res) / REALTEK_GPIO_PORTS_PER_BANK;
+               nr_cpus = min(nr_cpus, num_present_cpus());
+
+               for (cpu = 0; cpu < nr_cpus; cpu++)
+                       cpumask_set_cpu(cpu, &ctrl->cpu_irq_maskable);
+       }
+
        return devm_gpiochip_add_data(dev, &ctrl->gc, ctrl);
 }