Commit 95fa6dbe authored by Sander Vanheule's avatar Sander Vanheule Committed by Bartosz Golaszewski

gpio: realtek-otto: Support per-cpu interrupts

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: default avatarSander Vanheule <sander@svanheule.net>
Signed-off-by: default avatarBartosz Golaszewski <brgl@bgdev.pl>
parent 512c5be3
// SPDX-License-Identifier: GPL-2.0-only // SPDX-License-Identifier: GPL-2.0-only
#include <linux/gpio/driver.h> #include <linux/gpio/driver.h>
#include <linux/cpumask.h>
#include <linux/irq.h> #include <linux/irq.h>
#include <linux/minmax.h> #include <linux/minmax.h>
#include <linux/mod_devicetable.h> #include <linux/mod_devicetable.h>
...@@ -55,6 +56,8 @@ ...@@ -55,6 +56,8 @@
struct realtek_gpio_ctrl { struct realtek_gpio_ctrl {
struct gpio_chip gc; struct gpio_chip gc;
void __iomem *base; void __iomem *base;
void __iomem *cpumask_base;
struct cpumask cpu_irq_maskable;
raw_spinlock_t lock; raw_spinlock_t lock;
u16 intr_mask[REALTEK_GPIO_PORTS_PER_BANK]; u16 intr_mask[REALTEK_GPIO_PORTS_PER_BANK];
u16 intr_type[REALTEK_GPIO_PORTS_PER_BANK]; u16 intr_type[REALTEK_GPIO_PORTS_PER_BANK];
...@@ -76,6 +79,11 @@ enum realtek_gpio_flags { ...@@ -76,6 +79,11 @@ enum realtek_gpio_flags {
* fields, and [BA, DC] for 2-bit fields. * fields, and [BA, DC] for 2-bit fields.
*/ */
GPIO_PORTS_REVERSED = BIT(1), 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) 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) ...@@ -247,14 +255,61 @@ static void realtek_gpio_irq_handler(struct irq_desc *desc)
chained_irq_exit(irq_chip, 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) static int realtek_gpio_irq_init(struct gpio_chip *gc)
{ {
struct realtek_gpio_ctrl *ctrl = gpiochip_get_data(gc); struct realtek_gpio_ctrl *ctrl = gpiochip_get_data(gc);
unsigned int port; unsigned int port;
int cpu;
for (port = 0; (port * 8) < gc->ngpio; port++) { for (port = 0; (port * 8) < gc->ngpio; port++) {
realtek_gpio_write_imr(ctrl, port, 0, 0); realtek_gpio_write_imr(ctrl, port, 0, 0);
realtek_gpio_clear_isr(ctrl, port, GENMASK(7, 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; return 0;
...@@ -266,6 +321,7 @@ static struct irq_chip realtek_gpio_irq_chip = { ...@@ -266,6 +321,7 @@ static struct irq_chip realtek_gpio_irq_chip = {
.irq_mask = realtek_gpio_irq_mask, .irq_mask = realtek_gpio_irq_mask,
.irq_unmask = realtek_gpio_irq_unmask, .irq_unmask = realtek_gpio_irq_unmask,
.irq_set_type = realtek_gpio_irq_set_type, .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[] = { static const struct of_device_id realtek_gpio_of_match[] = {
...@@ -290,8 +346,10 @@ static int realtek_gpio_probe(struct platform_device *pdev) ...@@ -290,8 +346,10 @@ static int realtek_gpio_probe(struct platform_device *pdev)
unsigned int dev_flags; unsigned int dev_flags;
struct gpio_irq_chip *girq; struct gpio_irq_chip *girq;
struct realtek_gpio_ctrl *ctrl; struct realtek_gpio_ctrl *ctrl;
struct resource *res;
u32 ngpios; u32 ngpios;
int err, irq; unsigned int nr_cpus;
int cpu, err, irq;
ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL); ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL);
if (!ctrl) if (!ctrl)
...@@ -352,6 +410,21 @@ static int realtek_gpio_probe(struct platform_device *pdev) ...@@ -352,6 +410,21 @@ static int realtek_gpio_probe(struct platform_device *pdev)
girq->init_hw = realtek_gpio_irq_init; 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); return devm_gpiochip_add_data(dev, &ctrl->gc, ctrl);
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment