Messages in this thread Patch in this message | | | From | Holger Dengler <> | Subject | [PATCH 05/11] mfd: flexcard: add interrupt support | Date | Wed, 25 Mar 2015 10:51:54 +0100 |
| |
From: Benedikt Spranger <b.spranger@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 <dengler@linutronix.de> Signed-off-by: Benedikt Spranger <b.spranger@linutronix.de> cc: Samuel Ortiz <sameo@linux.intel.com> cc: Lee Jones <lee.jones@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
| |