[PATCH 05/11] mfd: flexcard: add interrupt support

From: Holger Dengler
Date: Wed Mar 25 2015 - 05:53:03 EST


From: Benedikt Spranger <b.spranger@xxxxxxxxxxxxx>

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 <dengler@xxxxxxxxxxxxx>
Signed-off-by: Benedikt Spranger <b.spranger@xxxxxxxxxxxxx>
cc: Samuel Ortiz <sameo@xxxxxxxxxxxxxxx>
cc: Lee Jones <lee.jones@xxxxxxxxxx>
---
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 majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/