[PATCH 04/12] mfd: flexcard: add interrupt support

From: Holger Dengler
Date: Tue Dec 13 2016 - 19:13:45 EST


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 <b.spranger@xxxxxxxxxxxxx>
Signed-off-by: Holger Dengler <dengler@xxxxxxxxxxxxx>
cc: Lee Jones <lee.jones@xxxxxxxxxx>
---
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 <b.spranger@xxxxxxxxxxxxx>
+ * Holger Dengler <dengler@xxxxxxxxxxxxx>
+ *
+ * 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