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 -- To unsubscribe from this list: send the line "unsubscribe dmaengine" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html