ARM: pxa: pxa_cplds: add lubbock and mainstone IO
authorRobert Jarzmik <robert.jarzmik@free.fr>
Fri, 24 Apr 2015 20:22:19 +0000 (22:22 +0200)
committerRobert Jarzmik <robert.jarzmik@free.fr>
Thu, 30 Apr 2015 20:52:12 +0000 (22:52 +0200)
Historically, this support was in arch/arm/mach-pxa/lubbock.c and
arch/arm/mach-pxa/mainstone.c. When gpio-pxa was moved to drivers/pxa,
it became a driver, and its initialization and probing happened at
postcore initcall. The lubbock code used to install the chained lubbock
interrupt handler at init_irq() time.

The consequence of the gpio-pxa change is that the installed chained irq
handler lubbock_irq_handler() was overwritten in pxa_gpio_probe(_dt)(),
removing :
 - the handler
 - the falling edge detection setting of GPIO0, which revealed the
   interrupt request from the lubbock IO board.

As a fix, move the gpio0 chained handler setup to a place where we have
the guarantee that pxa_gpio_probe() was called before, so that lubbock
handler becomes the true IRQ chained handler of GPIO0, demuxing the
lubbock IO board interrupts.

This patch moves all that handling to a mfd driver. It's only purpose
for the time being is the interrupt handling, but in the future it
should encompass all the motherboard CPLDs handling :
 - leds
 - switches
 - hexleds

The same logic applies to mainstone board.

Fixes: 157d2644cb0c ("ARM: pxa: change gpio to platform device")
Signed-off-by: Robert Jarzmik <robert.jarzmik@free.fr>
Acked-by: Arnd Bergmann <arnd@arndb.de>
arch/arm/mach-pxa/Kconfig
arch/arm/mach-pxa/Makefile
arch/arm/mach-pxa/pxa_cplds_irqs.c [new file with mode: 0644]

index 8896e71586f5e2255149db0ac4eb5cf90ece9e6b..f096836879634fb87897574a1476e26a196fb349 100644 (file)
@@ -691,4 +691,13 @@ config SHARPSL_PM_MAX1111
 config PXA310_ULPI
        bool
 
+config PXA_SYSTEMS_CPLDS
+       tristate "Motherboard cplds"
+       default ARCH_LUBBOCK || MACH_MAINSTONE
+       help
+         This driver supports the Lubbock and Mainstone multifunction chip
+         found on the pxa25x development platform system (Lubbock) and pxa27x
+         development platform system (Mainstone). This IO board supports the
+         interrupts handling, ethernet controller, flash chips, etc ...
+
 endif
index eb0bf7678a9909fdb473b791eb8f183877c73588..4087d334ecdfc6e36ace6ed23ce30fb9198ee6ef 100644 (file)
@@ -90,4 +90,5 @@ obj-$(CONFIG_MACH_RAUMFELD_CONNECTOR) += raumfeld.o
 obj-$(CONFIG_MACH_RAUMFELD_SPEAKER)    += raumfeld.o
 obj-$(CONFIG_MACH_ZIPIT2)      += z2.o
 
+obj-$(CONFIG_PXA_SYSTEMS_CPLDS)        += pxa_cplds_irqs.o
 obj-$(CONFIG_TOSA_BT)          += tosa-bt.o
diff --git a/arch/arm/mach-pxa/pxa_cplds_irqs.c b/arch/arm/mach-pxa/pxa_cplds_irqs.c
new file mode 100644 (file)
index 0000000..f1aeb54
--- /dev/null
@@ -0,0 +1,200 @@
+/*
+ * Intel Reference Systems cplds
+ *
+ * Copyright (C) 2014 Robert Jarzmik
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Cplds motherboard driver, supporting lubbock and mainstone SoC board.
+ */
+
+#include <linux/bitops.h>
+#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/mfd/core.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+
+#define FPGA_IRQ_MASK_EN 0x0
+#define FPGA_IRQ_SET_CLR 0x10
+
+#define CPLDS_NB_IRQ   32
+
+struct cplds {
+       void __iomem *base;
+       int irq;
+       unsigned int irq_mask;
+       struct gpio_desc *gpio0;
+       struct irq_domain *irqdomain;
+};
+
+static irqreturn_t cplds_irq_handler(int in_irq, void *d)
+{
+       struct cplds *fpga = d;
+       unsigned long pending;
+       unsigned int bit;
+
+       pending = readl(fpga->base + FPGA_IRQ_SET_CLR) & fpga->irq_mask;
+       for_each_set_bit(bit, &pending, CPLDS_NB_IRQ)
+               generic_handle_irq(irq_find_mapping(fpga->irqdomain, bit));
+
+       return IRQ_HANDLED;
+}
+
+static void cplds_irq_mask_ack(struct irq_data *d)
+{
+       struct cplds *fpga = irq_data_get_irq_chip_data(d);
+       unsigned int cplds_irq = irqd_to_hwirq(d);
+       unsigned int set, bit = BIT(cplds_irq);
+
+       fpga->irq_mask &= ~bit;
+       writel(fpga->irq_mask, fpga->base + FPGA_IRQ_MASK_EN);
+       set = readl(fpga->base + FPGA_IRQ_SET_CLR);
+       writel(set & ~bit, fpga->base + FPGA_IRQ_SET_CLR);
+}
+
+static void cplds_irq_unmask(struct irq_data *d)
+{
+       struct cplds *fpga = irq_data_get_irq_chip_data(d);
+       unsigned int cplds_irq = irqd_to_hwirq(d);
+       unsigned int bit = BIT(cplds_irq);
+
+       fpga->irq_mask |= bit;
+       writel(fpga->irq_mask, fpga->base + FPGA_IRQ_MASK_EN);
+}
+
+static struct irq_chip cplds_irq_chip = {
+       .name           = "pxa_cplds",
+       .irq_mask_ack   = cplds_irq_mask_ack,
+       .irq_unmask     = cplds_irq_unmask,
+       .flags          = IRQCHIP_MASK_ON_SUSPEND | IRQCHIP_SKIP_SET_WAKE,
+};
+
+static int cplds_irq_domain_map(struct irq_domain *d, unsigned int irq,
+                                  irq_hw_number_t hwirq)
+{
+       struct cplds *fpga = d->host_data;
+
+       irq_set_chip_and_handler(irq, &cplds_irq_chip, handle_level_irq);
+       irq_set_chip_data(irq, fpga);
+
+       return 0;
+}
+
+static const struct irq_domain_ops cplds_irq_domain_ops = {
+       .xlate = irq_domain_xlate_twocell,
+       .map = cplds_irq_domain_map,
+};
+
+static int cplds_resume(struct platform_device *pdev)
+{
+       struct cplds *fpga = platform_get_drvdata(pdev);
+
+       writel(fpga->irq_mask, fpga->base + FPGA_IRQ_MASK_EN);
+
+       return 0;
+}
+
+static int cplds_probe(struct platform_device *pdev)
+{
+       struct resource *res;
+       struct cplds *fpga;
+       int ret;
+       unsigned int base_irq = 0;
+       unsigned long irqflags = 0;
+
+       fpga = devm_kzalloc(&pdev->dev, sizeof(*fpga), GFP_KERNEL);
+       if (!fpga)
+               return -ENOMEM;
+
+       res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+       if (res) {
+               fpga->irq = (unsigned int)res->start;
+               irqflags = res->flags;
+       }
+       if (!fpga->irq)
+               return -ENODEV;
+
+       base_irq = platform_get_irq(pdev, 1);
+       if (base_irq < 0)
+               base_irq = 0;
+
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       fpga->base = devm_ioremap_resource(&pdev->dev, res);
+       if (IS_ERR(fpga->base))
+               return PTR_ERR(fpga->base);
+
+       platform_set_drvdata(pdev, fpga);
+
+       writel(fpga->irq_mask, fpga->base + FPGA_IRQ_MASK_EN);
+       writel(0, fpga->base + FPGA_IRQ_SET_CLR);
+
+       ret = devm_request_irq(&pdev->dev, fpga->irq, cplds_irq_handler,
+                              irqflags, dev_name(&pdev->dev), fpga);
+       if (ret == -ENOSYS)
+               return -EPROBE_DEFER;
+
+       if (ret) {
+               dev_err(&pdev->dev, "couldn't request main irq%d: %d\n",
+                       fpga->irq, ret);
+               return ret;
+       }
+
+       irq_set_irq_wake(fpga->irq, 1);
+       fpga->irqdomain = irq_domain_add_linear(pdev->dev.of_node,
+                                              CPLDS_NB_IRQ,
+                                              &cplds_irq_domain_ops, fpga);
+       if (!fpga->irqdomain)
+               return -ENODEV;
+
+       if (base_irq) {
+               ret = irq_create_strict_mappings(fpga->irqdomain, base_irq, 0,
+                                                CPLDS_NB_IRQ);
+               if (ret) {
+                       dev_err(&pdev->dev, "couldn't create the irq mapping %d..%d\n",
+                               base_irq, base_irq + CPLDS_NB_IRQ);
+                       return ret;
+               }
+       }
+
+       return 0;
+}
+
+static int cplds_remove(struct platform_device *pdev)
+{
+       struct cplds *fpga = platform_get_drvdata(pdev);
+
+       irq_set_chip_and_handler(fpga->irq, NULL, NULL);
+
+       return 0;
+}
+
+static const struct of_device_id cplds_id_table[] = {
+       { .compatible = "intel,lubbock-cplds-irqs", },
+       { .compatible = "intel,mainstone-cplds-irqs", },
+       { }
+};
+MODULE_DEVICE_TABLE(of, cplds_id_table);
+
+static struct platform_driver cplds_driver = {
+       .driver         = {
+               .name   = "pxa_cplds_irqs",
+               .of_match_table = of_match_ptr(cplds_id_table),
+       },
+       .probe          = cplds_probe,
+       .remove         = cplds_remove,
+       .resume         = cplds_resume,
+};
+
+module_platform_driver(cplds_driver);
+
+MODULE_DESCRIPTION("PXA Cplds interrupts driver");
+MODULE_AUTHOR("Robert Jarzmik <robert.jarzmik@free.fr>");
+MODULE_LICENSE("GPL");