From: Benedikt Spranger <b.spran...@linutronix.de>

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: Holger Dengler <deng...@linutronix.de>
Signed-off-by: Benedikt Spranger <b.spran...@linutronix.de>
cc: Samuel Ortiz <sa...@linux.intel.com>
cc: Lee Jones <lee.jo...@linaro.org>
---
 drivers/mfd/Kconfig             |   1 +
 drivers/mfd/flexcard/Makefile   |   2 +-
 drivers/mfd/flexcard/core.c     |  14 +++-
 drivers/mfd/flexcard/flexcard.h |   2 +
 drivers/mfd/flexcard/irq.c      | 167 ++++++++++++++++++++++++++++++++++++++++
 drivers/mfd/flexcard/irq.h      |  50 ++++++++++++
 include/linux/mfd/flexcard.h    |   2 +
 7 files changed, 235 insertions(+), 3 deletions(-)
 create mode 100644 drivers/mfd/flexcard/irq.c
 create mode 100644 drivers/mfd/flexcard/irq.h

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 3765707..f624b7f 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -220,6 +220,7 @@ config MFD_DLN2
 config MFD_FLEXCARD
        tristate "Support for 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/flexcard/Makefile b/drivers/mfd/flexcard/Makefile
index 101000f..4f72c9c 100644
--- a/drivers/mfd/flexcard/Makefile
+++ b/drivers/mfd/flexcard/Makefile
@@ -1,2 +1,2 @@
 obj-$(CONFIG_MFD_FLEXCARD)     += flexcard.o
-flexcard-objs                  := core.o attr.o
+flexcard-objs                  := core.o attr.o irq.o
diff --git a/drivers/mfd/flexcard/core.c b/drivers/mfd/flexcard/core.c
index c73ae5c..a6f92b4 100644
--- a/drivers/mfd/flexcard/core.c
+++ b/drivers/mfd/flexcard/core.c
@@ -131,7 +131,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_clk_setup(struct flexcard_device *priv)
@@ -265,10 +266,16 @@ static int flexcard_probe(struct pci_dev *pdev,
                goto out_release;
        }
 
+       ret = flexcard_setup_irq(pdev);
+       if (ret) {
+               dev_err(&pdev->dev, "unable to setup irq controller: %d", ret);
+               goto out_unmap;
+       }
+
        ret = flexcard_tiny_probe(priv);
        if (ret) {
                dev_err(&pdev->dev, "unable to probe tinys: %d", ret);
-               goto out_unmap;
+               goto out_remove_irq;
        }
 
        ret = flexcard_clk_setup(priv);
@@ -300,6 +307,8 @@ out_deregister:
        misc_deregister(&priv->dev);
 out_remove:
        mfd_remove_devices(&pdev->dev);
+out_remove_irq:
+       flexcard_remove_irq(pdev);
 out_unmap:
        iounmap(priv->conf);
 out_release:
@@ -321,6 +330,7 @@ static void flexcard_remove(struct pci_dev *pdev)
        flexcard_misc_del_attrs(priv->dev.this_device);
        misc_deregister(&priv->dev);
        mfd_remove_devices(&pdev->dev);
+       flexcard_remove_irq(pdev);
        iounmap(priv->conf);
        pci_release_regions(pdev);
        pci_disable_device(pdev);
diff --git a/drivers/mfd/flexcard/flexcard.h b/drivers/mfd/flexcard/flexcard.h
index 1fe451e..038c138 100644
--- a/drivers/mfd/flexcard/flexcard.h
+++ b/drivers/mfd/flexcard/flexcard.h
@@ -3,5 +3,7 @@
 
 int flexcard_misc_add_attrs(struct device *dev);
 void flexcard_misc_del_attrs(struct device *dev);
+int flexcard_setup_irq(struct pci_dev *pdev);
+void flexcard_remove_irq(struct pci_dev *pdev);
 
 #endif /* __FLEXCARD_H */
diff --git a/drivers/mfd/flexcard/irq.c b/drivers/mfd/flexcard/irq.c
new file mode 100644
index 0000000..fefcb24
--- /dev/null
+++ b/drivers/mfd/flexcard/irq.c
@@ -0,0 +1,167 @@
+/*
+ * Eberspaecher Flexcard PMC II Carrier Board PCI Driver - Interrupt controller
+ *
+ * Copyright (c) 2014,2015 Linutronix GmbH
+ * Author: Holger Dengler
+ *         Benedikt Spranger
+ *
+ * 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/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/export.h>
+#include <linux/flexcard.h>
+#include <linux/interrupt.h>
+#include <linux/irqdomain.h>
+#include <linux/miscdevice.h>
+#include <linux/pci.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/flexcard.h>
+
+#include "irq.h"
+
+static irqreturn_t flexcard_demux(int irq, void *data)
+{
+       struct flexcard_device *priv = data;
+       u32 stat;
+       int i, cur;
+
+       stat = readl(&priv->conf->irs);
+       if (!stat)
+               return IRQ_NONE;
+
+       for (i = 0; i < NR_FLEXCARD_IRQ; i++) {
+               if (stat & flexcard_irq_tab[i].status) {
+                       cur = irq_find_mapping(priv->irq_domain, i);
+                       if (cur)
+                               generic_handle_irq(cur);
+               }
+       }
+       return IRQ_HANDLED;
+}
+
+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 void flexcard_irq_ack(struct irq_data *d)
+{
+       struct flexcard_device *priv = irq_data_get_irq_chip_data(d);
+
+       if (flexcard_irq_tab[d->hwirq].reset)
+               writel(flexcard_irq_tab[d->hwirq].reset, &priv->conf->irs);
+}
+
+static void flexcard_irq_mask(struct irq_data *d)
+{
+       struct flexcard_device *priv = irq_data_get_irq_chip_data(d);
+       unsigned long flags;
+       u32 irc;
+
+       raw_spin_lock_irqsave(&priv->irq_lock, flags);
+       irc = readl(&priv->conf->irc);
+       irc &= ~flexcard_irq_tab[d->hwirq].enable;
+       writel(irc, &priv->conf->irc);
+       raw_spin_unlock_irqrestore(&priv->irq_lock, flags);
+}
+
+static void flexcard_irq_unmask(struct irq_data *d)
+{
+       struct flexcard_device *priv = irq_data_get_irq_chip_data(d);
+       unsigned long flags;
+       u32 irc;
+
+       raw_spin_lock_irqsave(&priv->irq_lock, flags);
+       irc = readl(&priv->conf->irc);
+       irc |= flexcard_irq_tab[d->hwirq].enable;
+       writel(irc, &priv->conf->irc);
+       raw_spin_unlock_irqrestore(&priv->irq_lock, flags);
+}
+
+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 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->conf->irc);
+       writel(0, &priv->conf->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->dma_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->conf->irc);
+       writel(0, &priv->conf->dma_irer);
+
+       free_irq(pdev->irq, priv);
+       pci_disable_msi(pdev);
+       irq_domain_remove(priv->irq_domain);
+}
diff --git a/drivers/mfd/flexcard/irq.h b/drivers/mfd/flexcard/irq.h
new file mode 100644
index 0000000..b18fb5b
--- /dev/null
+++ b/drivers/mfd/flexcard/irq.h
@@ -0,0 +1,50 @@
+#ifndef FLEXCARD_IRQ_H
+#define FLEXCARD_IRQ_H
+
+struct flexcard_irq_tab {
+       u32 enable;
+       u32 reset;
+       u32 status;
+};
+
+#define to_irq_tab(e, s) {                     \
+       .enable = (1 << e),                     \
+       .status = (1 << s),                     \
+}
+
+#define to_irq_tab_ack(e, r, s) {              \
+       .enable = (1 << e),                     \
+       .reset  = (1 << r),                     \
+       .status = (1 << s),                     \
+}
+
+/*
+ * Interrupt Controller Register S-Box
+ * unlike other irq controllers the FlexCard bits for enable, reset and status
+ * looks more like a cryptographic S-box. Make a const table to have a more
+ * easier access to this bits in the irqchip callback functions.
+ * The table contains the registers for PMC2-cards.
+ */
+static const struct flexcard_irq_tab flexcard_irq_tab[] = {
+       to_irq_tab_ack(28, 0, 28),      /* TIMER  */
+       to_irq_tab_ack(29, 1, 29),      /* CC1CYS */
+       to_irq_tab_ack(30, 10, 21),     /* CC2CYS */
+       to_irq_tab_ack(18, 2, 30),      /* CC3CYS */
+       to_irq_tab_ack(19, 6, 25),      /* CC4CYS */
+       to_irq_tab_ack(26, 4, 26),      /* WAKE1A */
+       to_irq_tab_ack(27, 5, 27),      /* WAKE1B */
+       to_irq_tab_ack(24, 8, 23),      /* WAKE2A */
+       to_irq_tab_ack(25, 9, 22),      /* WAKE2B */
+       to_irq_tab_ack(22, 12, 19),     /* WAKE3A */
+       to_irq_tab_ack(23, 13, 18),     /* WAKE3B */
+       to_irq_tab_ack(20, 14, 17),     /* WAKE4A */
+       to_irq_tab_ack(21, 15, 16),     /* WAKE4B */
+       to_irq_tab(15, 31),             /* CC1T0  */
+       to_irq_tab(14, 3),              /* CC2T0  */
+       to_irq_tab(16, 24),             /* CC3T0  */
+       to_irq_tab(17, 20),             /* CC4T0  */
+};
+
+#define NR_FLEXCARD_IRQ                ARRAY_SIZE(flexcard_irq_tab)
+
+#endif /* FLEXCARD_IRQ_H */
diff --git a/include/linux/mfd/flexcard.h b/include/linux/mfd/flexcard.h
index 84e155c..f5b789f 100644
--- a/include/linux/mfd/flexcard.h
+++ b/include/linux/mfd/flexcard.h
@@ -22,12 +22,14 @@
 #define FLEXCARD_MAX_NAME      16
 
 struct flexcard_device {
+       raw_spinlock_t irq_lock;
        struct pci_dev *pdev;
        struct fc_conf_bar __iomem *conf;
        struct mfd_cell *cells;
        struct resource *res;
        struct miscdevice dev;
        struct kref ref;
+       struct irq_domain *irq_domain;
        int cardnr;
        char name[FLEXCARD_MAX_NAME];
 };
-- 
2.1.4

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to