gpio: altera: Fix one celled interrupts

Message ID 20180504164451.5492-1-chris.lesiak@licor.com
State New
Headers show
Series
  • gpio: altera: Fix one celled interrupts
Related show

Commit Message

Chris Lesiak May 4, 2018, 4:44 p.m.
As documented in the device tree binding, #interrupt-cells should be 1
for this driver because the interrupt type is fixed in the hardware.
When the device tree is configured as documented, an interrupt would
trigger kernel/irq/irqdomain.c: WARN_ON(intsize < 2).

The driver was attempting to use gpiochip_irqchip_add(), which uses
gpiochip_irqchip_add_key() and is documented to work only with two
celled IRQs.

This appears to have been broken in the v7 -> v8 transition of the
original patch adding the driver.

Signed-off-by: Chris Lesiak <chris.lesiak@licor.com>
---
 drivers/gpio/gpio-altera.c | 88 ++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 86 insertions(+), 2 deletions(-)

Patch

diff --git a/drivers/gpio/gpio-altera.c b/drivers/gpio/gpio-altera.c
index ccc02ed65b3c..2a989777eb66 100644
--- a/drivers/gpio/gpio-altera.c
+++ b/drivers/gpio/gpio-altera.c
@@ -112,6 +112,30 @@  static unsigned int altera_gpio_irq_startup(struct irq_data *d)
 	return 0;
 }
 
+static int altera_gpio_irq_reqres(struct irq_data *d)
+{
+	struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
+
+	if (!try_module_get(THIS_MODULE))
+		return -ENODEV;
+
+	if (gpiochip_lock_as_irq(chip, d->hwirq)) {
+		dev_err(chip->parent, "unable to lock HW IRQ %lu for IRQ\n",
+				d->hwirq);
+		module_put(THIS_MODULE);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static void altera_gpio_irq_relres(struct irq_data *d)
+{
+	struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
+
+	gpiochip_unlock_as_irq(chip, d->hwirq);
+	module_put(THIS_MODULE);
+}
+
 static struct irq_chip altera_irq_chip = {
 	.name		= "altera-gpio",
 	.irq_mask	= altera_gpio_irq_mask,
@@ -119,6 +143,8 @@  static struct irq_chip altera_irq_chip = {
 	.irq_set_type	= altera_gpio_irq_set_type,
 	.irq_startup	= altera_gpio_irq_startup,
 	.irq_shutdown	= altera_gpio_irq_mask,
+	.irq_request_resources = altera_gpio_irq_reqres,
+	.irq_release_resources = altera_gpio_irq_relres,
 };
 
 static int altera_gpio_get(struct gpio_chip *gc, unsigned offset)
@@ -252,6 +278,64 @@  static void altera_gpio_irq_leveL_high_handler(struct irq_desc *desc)
 	chained_irq_exit(chip, desc);
 }
 
+static int altera_gpio_irq_map(struct irq_domain *d, unsigned int irq,
+		irq_hw_number_t hwirq)
+{
+	struct gpio_chip *chip = d->host_data;
+
+	irq_set_chip_data(irq, chip);
+	/*
+	 * This lock class tells lockdep that GPIO irqs are in a different
+	 * category than their parents, so it won't report false recursion.
+	 */
+	irq_set_lockdep_class(irq, chip->lock_key);
+	irq_set_chip_and_handler(irq, chip->irqchip, chip->irq_handler);
+	irq_set_noprobe(irq);
+
+	return 0;
+}
+
+static void altera_gpio_irq_unmap(struct irq_domain *d, unsigned int irq)
+{
+	irq_set_chip_and_handler(irq, NULL, NULL);
+	irq_set_chip_data(irq, NULL);
+}
+
+static const struct irq_domain_ops altera_gpio_domain_ops = {
+	.map    = altera_gpio_irq_map,
+	.unmap  = altera_gpio_irq_unmap,
+	.xlate  = irq_domain_xlate_onecell,
+};
+
+static int altera_gpiochip_irqchip_add(struct gpio_chip *gpiochip,
+				struct irq_chip *irqchip)
+{
+	static struct lock_class_key key;
+
+	if (!gpiochip || !irqchip)
+		return -EINVAL;
+
+	if (!gpiochip->parent) {
+		pr_err("missing gpiochip .dev parent pointer\n");
+		return -EINVAL;
+	}
+	gpiochip->irq_nested = false;
+	gpiochip->irqchip = irqchip;
+	gpiochip->irq_handler = handle_bad_irq;
+	gpiochip->irq_default_type = IRQ_TYPE_NONE;
+	gpiochip->to_irq = NULL;
+	gpiochip->lock_key = &key;
+	gpiochip->irqdomain = irq_domain_add_simple(
+			gpiochip->parent->of_node, gpiochip->ngpio, 0,
+			&altera_gpio_domain_ops, gpiochip);
+	if (!gpiochip->irqdomain) {
+		gpiochip->irqchip = NULL;
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
 static int altera_gpio_probe(struct platform_device *pdev)
 {
 	struct device_node *node = pdev->dev.of_node;
@@ -305,8 +389,8 @@  static int altera_gpio_probe(struct platform_device *pdev)
 	}
 	altera_gc->interrupt_trigger = reg;
 
-	ret = gpiochip_irqchip_add(&altera_gc->mmchip.gc, &altera_irq_chip, 0,
-		handle_bad_irq, IRQ_TYPE_NONE);
+	ret = altera_gpiochip_irqchip_add(&altera_gc->mmchip.gc,
+		&altera_irq_chip);
 
 	if (ret) {
 		dev_err(&pdev->dev, "could not add irqchip\n");