The Flexcard comprise an interrupt controller for the attached
tinys, timer, a Flexray related trigger and a second one for DMA.
Both controllers share a single IRQ line.

Add an interrupt domain for the non-DMA interrupts.

Signed-off-by: Benedikt Spranger <[email protected]>
Signed-off-by: Holger Dengler <[email protected]>
cc: Lee Jones <[email protected]>
---
 drivers/mfd/Kconfig          |   1 +
 drivers/mfd/Makefile         |   1 +
 drivers/mfd/flexcard_core.c  |  14 ++-
 drivers/mfd/flexcard_irq.c   | 238 +++++++++++++++++++++++++++++++++++++++++++
 include/linux/mfd/flexcard.h |   6 ++
 5 files changed, 258 insertions(+), 2 deletions(-)
 create mode 100644 drivers/mfd/flexcard_irq.c

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index a5a12da..85fedf6 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -302,6 +302,7 @@ config MFD_EXYNOS_LPASS
 config MFD_FLEXCARD
        tristate "Eberspaecher Flexcard PMC II Carrier Board"
        select MFD_CORE
+       select IRQ_DOMAIN
        depends on PCI
        help
          This is the core driver for the Eberspaecher Flexcard
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 843e57c..7d9feb4 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -212,4 +212,5 @@ obj-$(CONFIG_MFD_MT6397)    += mt6397-core.o
 
 obj-$(CONFIG_MFD_ALTERA_A10SR) += altera-a10sr.o
 flexcard-objs                  := flexcard_core.o
+flexcard-objs                  := flexcard_core.o flexcard_irq.o
 obj-$(CONFIG_MFD_FLEXCARD)     += flexcard.o
diff --git a/drivers/mfd/flexcard_core.c b/drivers/mfd/flexcard_core.c
index 73eb726..b7aead5 100644
--- a/drivers/mfd/flexcard_core.c
+++ b/drivers/mfd/flexcard_core.c
@@ -192,7 +192,8 @@ static int flexcard_tiny_probe(struct flexcard_device *priv)
                offset += FLEXCARD_CAN_OFFSET;
        }
 
-       return mfd_add_devices(&pdev->dev, 0, priv->cells, nr, NULL, 0, NULL);
+       return mfd_add_devices(&pdev->dev, 0, priv->cells, nr, NULL,
+                              0, priv->irq_domain);
 }
 
 static int flexcard_probe(struct pci_dev *pdev,
@@ -242,10 +243,16 @@ static int flexcard_probe(struct pci_dev *pdev,
        }
        priv->cardnr = ret;
 
+       ret = flexcard_setup_irq(pdev);
+       if (ret) {
+               dev_err(&pdev->dev, "unable to setup irq controller: %d", ret);
+               goto out_ida;
+       }
+
        ret = flexcard_tiny_probe(priv);
        if (ret) {
                dev_err(&pdev->dev, "unable to probe tinys: %d", ret);
-               goto out_ida;
+               goto out_remove_irq;
        }
 
        ret = flexcard_misc_setup(priv);
@@ -268,6 +275,8 @@ static int flexcard_probe(struct pci_dev *pdev,
 
 out_mfd_dev_remove:
        mfd_remove_devices(&pdev->dev);
+out_remove_irq:
+       flexcard_remove_irq(pdev);
 out_ida:
        ida_simple_remove(&flexcard_ida, priv->cardnr);
 out_unmap:
@@ -285,6 +294,7 @@ static void flexcard_remove(struct pci_dev *pdev)
        struct flexcard_device *priv = pci_get_drvdata(pdev);
 
        mfd_remove_devices(&pdev->dev);
+       flexcard_remove_irq(pdev);
        ida_simple_remove(&flexcard_ida, priv->cardnr);
        iounmap(priv->bar0);
        pci_release_regions(pdev);
diff --git a/drivers/mfd/flexcard_irq.c b/drivers/mfd/flexcard_irq.c
new file mode 100644
index 0000000..fa2063f
--- /dev/null
+++ b/drivers/mfd/flexcard_irq.c
@@ -0,0 +1,238 @@
+/*
+ * Eberspächer Flexcard PMC II Carrier Board PCI Driver - Interrupt controller
+ *
+ * Copyright (c) 2014 - 2016, Linutronix GmbH
+ * Author: Benedikt Spranger <[email protected]>
+ *         Holger Dengler <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation.
+ */
+
+#include <linux/export.h>
+#include <linux/flexcard.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/irqdomain.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/miscdevice.h>
+#include <linux/pci.h>
+
+#include <linux/mfd/core.h>
+#include <linux/mfd/flexcard.h>
+
+struct fc_irq_tab {
+       u32 mskcache;
+       u32 mskoffs;
+       u32 msk;
+       u32 ackoffs;
+       u32 ack;
+};
+
+#define to_irq_tab_ack(statbit, cofs, mskofs, mskbit, ackofs, ackbit)  \
+       [statbit] = {                                                   \
+                       .mskcache       = cofs,                         \
+                       .mskoffs        = mskofs,                       \
+                       .msk            = (1U << mskbit),               \
+                       .ackoffs        = ackofs,                       \
+                       .ack            = (1U << ackbit) }
+
+#define to_irq_tab(statbit, cofs, mskofs, mskbit)                      \
+       [statbit] = {                                                   \
+                       .mskcache       = cofs,                         \
+                       .mskoffs        = mskofs,                       \
+                       .msk            = (1U << mskbit) }
+
+#define DEVMSK_OFFS    offsetof(struct fc_bar0, conf.irc)
+#define DEVACK_OFFS    offsetof(struct fc_bar0, conf.irs)
+#define DEVMSK_CACHE   offsetof(struct flexcard_device, dev_irqmsk)
+
+#define dev_to_irq_tab_ack(s, m, a)                            \
+               to_irq_tab_ack(s, DEVMSK_CACHE, DEVMSK_OFFS, m, \
+                              DEVACK_OFFS, a)
+
+#define dev_to_irq_tab(s, m)                           \
+               to_irq_tab(s, DEVMSK_CACHE, DEVMSK_OFFS, m)
+
+static const struct fc_irq_tab flexcard_irq_tab[] = {
+       /* Device Interrupts */
+       dev_to_irq_tab_ack(28, 28, 0),  /* TIMER  */
+       dev_to_irq_tab_ack(29, 29, 1),  /* CC1CYS */
+       dev_to_irq_tab_ack(21, 30, 10), /* CC2CYS */
+       dev_to_irq_tab_ack(30, 18, 2),  /* CC3CYS */
+       dev_to_irq_tab_ack(25, 19, 6),  /* CC4CYS */
+       dev_to_irq_tab_ack(26, 26, 4),  /* WAKE1A */
+       dev_to_irq_tab_ack(27, 27, 5),  /* WAKE1B */
+       dev_to_irq_tab_ack(23, 24, 8),  /* WAKE2A */
+       dev_to_irq_tab_ack(22, 25, 9),  /* WAKE2B */
+       dev_to_irq_tab_ack(19, 22, 12), /* WAKE3A */
+       dev_to_irq_tab_ack(18, 23, 13), /* WAKE3B */
+       dev_to_irq_tab_ack(17, 20, 14), /* WAKE4A */
+       dev_to_irq_tab_ack(16, 21, 15), /* WAKE4B */
+       dev_to_irq_tab(31, 15),         /* CC1T0  */
+       dev_to_irq_tab(3, 14),          /* CC2T0  */
+       dev_to_irq_tab(24, 16),         /* CC3T0  */
+       dev_to_irq_tab(20, 17),         /* CC4T0  */
+};
+
+#define NR_FLEXCARD_IRQ                ARRAY_SIZE(flexcard_irq_tab)
+
+#define VALID_DEVIRQ_MSK       ((1U << 28) | \
+                                (1U << 29) | \
+                                (1U << 21) | \
+                                (1U << 30) | \
+                                (1U << 25) | \
+                                (1U << 26) | \
+                                (1U << 27) | \
+                                (1U << 23) | \
+                                (1U << 22) | \
+                                (1U << 19) | \
+                                (1U << 18) | \
+                                (1U << 17) | \
+                                (1U << 16) | \
+                                (1U << 31) | \
+                                (1U << 3)  | \
+                                (1U << 24) | \
+                                (1U << 20))
+
+static irqreturn_t flexcard_demux(int irq, void *data)
+{
+       struct flexcard_device *priv = data;
+       irqreturn_t ret = IRQ_NONE;
+       unsigned int slot, cur, stat;
+
+       stat = readl(&priv->bar0->conf.irs) & VALID_DEVIRQ_MSK;
+       while (stat) {
+               slot = __ffs(stat);
+               stat &= (1 << slot);
+               cur = irq_linear_revmap(priv->irq_domain, slot);
+               generic_handle_irq(cur);
+               ret = IRQ_HANDLED;
+       }
+       return ret;
+}
+
+static void flexcard_irq_ack(struct irq_data *d)
+{
+       struct flexcard_device *priv = irq_data_get_irq_chip_data(d);
+       const struct fc_irq_tab *tp = &flexcard_irq_tab[d->hwirq];
+       void __iomem *p = (void __iomem *)priv->bar0 + tp->ackoffs;
+
+       writel(tp->ack, p);
+}
+
+static void flexcard_irq_mask(struct irq_data *d)
+{
+       struct flexcard_device *priv = irq_data_get_irq_chip_data(d);
+       const struct fc_irq_tab *tp = &flexcard_irq_tab[d->hwirq];
+       void __iomem *p = (void __iomem *)priv->bar0 + tp->mskoffs;
+       u32 *msk = (void *)priv + tp->mskcache;
+
+       raw_spin_lock(&priv->irq_lock);
+       *msk &= ~tp->msk;
+       writel(*msk, p);
+       raw_spin_unlock(&priv->irq_lock);
+}
+
+static void flexcard_irq_unmask(struct irq_data *d)
+{
+       struct flexcard_device *priv = irq_data_get_irq_chip_data(d);
+       const struct fc_irq_tab *tp = &flexcard_irq_tab[d->hwirq];
+       void __iomem *p = (void __iomem *)priv->bar0 + tp->mskoffs;
+       u32 *msk = (void *)priv + tp->mskcache;
+
+       raw_spin_lock(&priv->irq_lock);
+       *msk |= tp->msk;
+       writel(*msk, p);
+       raw_spin_unlock(&priv->irq_lock);
+}
+
+static int flexcard_req_irq(struct pci_dev *pdev)
+{
+       struct flexcard_device *priv = pci_get_drvdata(pdev);
+       int ret;
+
+       ret = pci_enable_msi(pdev);
+       if (ret) {
+               dev_warn(&pdev->dev, "could not enable MSI\n");
+               /* shared PCI irq fallback */
+               return request_irq(pdev->irq, flexcard_demux,
+                                  IRQF_NO_THREAD | IRQF_SHARED,
+                                  "flexcard", priv);
+       }
+       dev_info(&pdev->dev, "MSI enabled\n");
+
+       ret = request_irq(pdev->irq, flexcard_demux, IRQF_NO_THREAD,
+                         "flexcard", priv);
+       if (ret)
+               pci_disable_msi(pdev);
+
+       return ret;
+}
+
+static struct irq_chip flexcard_irq_chip = {
+       .name           = "flexcard_irq",
+       .irq_ack        = flexcard_irq_ack,
+       .irq_mask       = flexcard_irq_mask,
+       .irq_unmask     = flexcard_irq_unmask,
+};
+
+static int flexcard_irq_domain_map(struct irq_domain *d, unsigned int irq,
+                                  irq_hw_number_t hw)
+{
+       struct flexcard_device *priv = d->host_data;
+
+       irq_set_chip_and_handler_name(irq, &flexcard_irq_chip,
+                                     handle_level_irq, "flexcard");
+       irq_set_chip_data(irq, priv);
+       irq_modify_status(irq, IRQ_NOREQUEST | IRQ_NOAUTOEN, IRQ_NOPROBE);
+
+       return 0;
+}
+
+static const struct irq_domain_ops flexcard_irq_domain_ops = {
+       .map = flexcard_irq_domain_map,
+};
+
+int flexcard_setup_irq(struct pci_dev *pdev)
+{
+       struct flexcard_device *priv = pci_get_drvdata(pdev);
+       struct irq_domain *domain;
+       int ret;
+
+       /* Make sure none of the subirqs is enabled */
+       writel(0, &priv->bar0->conf.irc);
+       writel(0, &priv->bar0->dma.dma_irer);
+
+       raw_spin_lock_init(&priv->irq_lock);
+
+       domain = irq_domain_add_linear(NULL, NR_FLEXCARD_IRQ,
+                                      &flexcard_irq_domain_ops, priv);
+       if (!domain) {
+               dev_err(&pdev->dev, "could not request irq domain\n");
+               return -ENODEV;
+       }
+
+       priv->irq_domain = domain;
+
+       ret = flexcard_req_irq(pdev);
+       if (ret)
+               irq_domain_remove(priv->irq_domain);
+
+       return ret;
+}
+
+void flexcard_remove_irq(struct pci_dev *pdev)
+{
+       struct flexcard_device *priv = pci_get_drvdata(pdev);
+
+       /* Disable all subirqs */
+       writel(0, &priv->bar0->conf.irc);
+       writel(0, &priv->bar0->dma.dma_irer);
+
+       free_irq(pdev->irq, priv);
+       pci_disable_msi(pdev);
+       irq_domain_remove(priv->irq_domain);
+}
diff --git a/include/linux/mfd/flexcard.h b/include/linux/mfd/flexcard.h
index 4116305..6cb8ad0 100644
--- a/include/linux/mfd/flexcard.h
+++ b/include/linux/mfd/flexcard.h
@@ -96,9 +96,15 @@ struct fc_bar0 {
 struct flexcard_device {
        unsigned int cardnr;
        struct pci_dev *pdev;
+       raw_spinlock_t irq_lock;
+       struct irq_domain *irq_domain;
        struct fc_bar0 __iomem *bar0;
        struct mfd_cell *cells;
        struct resource *res;
+       u32 dev_irqmsk;
 };
 
+int flexcard_setup_irq(struct pci_dev *pdev);
+void flexcard_remove_irq(struct pci_dev *pdev);
+
 #endif /* _LINUX_FLEXCARD_H */
-- 
2.1.4

Reply via email to