[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <1451881723-2478-20-git-send-email-milo.kim@ti.com>
Date: Mon, 4 Jan 2016 13:28:43 +0900
From: Milo Kim <milo.kim@...com>
To: <tglx@...utronix.de>
CC: <jason@...edaemon.net>, <marc.zyngier@....com>,
<alexandre.belloni@...e-electrons.com>,
<boris.brezillon@...e-electrons.com>,
<ludovic.desroches@...el.com>, <nicolas.ferre@...el.com>,
<linux-kernel@...r.kernel.org>, Milo Kim <milo.kim@...com>
Subject: [PATCH 19/19] irqchip: atmel-aic: rename AIC driver and fix Kconfig
'irq-aic' is consolidated interrupt driver for Atmel SoCs.
Rename the driver file:
irq-atmel-aic-common.c -> irq-aic.c
Kconfig:
ATMEL_AIC5_IRQ is removed
Cc: Thomas Gleixner <tglx@...utronix.de>
Cc: Jason Cooper <jason@...edaemon.net>
Cc: Marc Zyngier <marc.zyngier@....com>
Cc: Alexandre Belloni <alexandre.belloni@...e-electrons.com>
Cc: Boris BREZILLON <boris.brezillon@...e-electrons.com>
Cc: Ludovic Desroches <ludovic.desroches@...el.com>
Cc: Nicolas Ferre <nicolas.ferre@...el.com>
Cc: linux-kernel@...r.kernel.org
Signed-off-by: Milo Kim <milo.kim@...com>
---
arch/arm/mach-at91/Kconfig | 2 +-
drivers/irqchip/Kconfig | 7 -
drivers/irqchip/Makefile | 3 +-
drivers/irqchip/irq-aic.c | 609 +++++++++++++++++++++++++++++++++
drivers/irqchip/irq-atmel-aic-common.c | 609 ---------------------------------
5 files changed, 611 insertions(+), 619 deletions(-)
create mode 100644 drivers/irqchip/irq-aic.c
delete mode 100644 drivers/irqchip/irq-atmel-aic-common.c
diff --git a/arch/arm/mach-at91/Kconfig b/arch/arm/mach-at91/Kconfig
index 23be2e4..315195c 100644
--- a/arch/arm/mach-at91/Kconfig
+++ b/arch/arm/mach-at91/Kconfig
@@ -122,7 +122,7 @@ config SOC_SAM_V7
config SOC_SAMA5
bool
- select ATMEL_AIC5_IRQ
+ select ATMEL_AIC_IRQ
select ATMEL_SDRAMC
select MEMORY
select SOC_SAM_V7
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index b5f5133..b926f79 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -67,13 +67,6 @@ config ATMEL_AIC_IRQ
select MULTI_IRQ_HANDLER
select SPARSE_IRQ
-config ATMEL_AIC5_IRQ
- bool
- select GENERIC_IRQ_CHIP
- select IRQ_DOMAIN
- select MULTI_IRQ_HANDLER
- select SPARSE_IRQ
-
config I8259
bool
select IRQ_DOMAIN
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index 6e43333..8d6494f 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -28,8 +28,7 @@ obj-$(CONFIG_ARM_GIC_V3_ITS) += irq-gic-v3-its.o irq-gic-v3-its-pci-msi.o irq-g
obj-$(CONFIG_HISILICON_IRQ_MBIGEN) += irq-mbigen.o
obj-$(CONFIG_ARM_NVIC) += irq-nvic.o
obj-$(CONFIG_ARM_VIC) += irq-vic.o
-obj-$(CONFIG_ATMEL_AIC_IRQ) += irq-atmel-aic-common.o
-obj-$(CONFIG_ATMEL_AIC5_IRQ) += irq-atmel-aic-common.o
+obj-$(CONFIG_ATMEL_AIC_IRQ) += irq-aic.o
obj-$(CONFIG_I8259) += irq-i8259.o
obj-$(CONFIG_IMGPDC_IRQ) += irq-imgpdc.o
obj-$(CONFIG_IRQ_MIPS_CPU) += irq-mips-cpu.o
diff --git a/drivers/irqchip/irq-aic.c b/drivers/irqchip/irq-aic.c
new file mode 100644
index 0000000..1d3978e
--- /dev/null
+++ b/drivers/irqchip/irq-aic.c
@@ -0,0 +1,609 @@
+/*
+ * Atmel AIC (Advanced Interrupt Controller) Driver
+ *
+ * Copyright (C) 2004 SAN People
+ * Copyright (C) 2004 ATMEL
+ * Copyright (C) Rick Bronson
+ * Copyright (C) 2014 Free Electrons
+ *
+ * Author: Boris BREZILLON <boris.brezillon@...e-electrons.com>
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/irqchip.h>
+#include <linux/irqdesc.h>
+#include <linux/irqdomain.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/slab.h>
+
+#include <asm/exception.h>
+#include <asm/mach/irq.h>
+
+#define AIC_IRQS_PER_CHIP 32
+#define NR_AT91RM9200_IRQS 32
+#define NR_SAMA5D2_IRQS 77
+#define NR_SAMA5D3_IRQS 48
+#define NR_SAMA5D4_IRQS 68
+
+#define AT91_AIC_SMR_BASE 0
+#define AT91_AIC_SVR_BASE 0x80
+#define AT91_AIC_IVR 0x100
+#define AT91_AIC_ISR 0x108
+#define AT91_AIC_IECR 0x120
+#define AT91_AIC_IDCR 0x124
+#define AT91_AIC_ICCR 0x128
+#define AT91_AIC_ISCR 0x12c
+#define AT91_AIC_EOICR 0x130
+#define AT91_AIC_SPU 0x134
+#define AT91_AIC_DCR 0x138
+#define AT91_INVALID_OFFSET (-1)
+
+#define AT91_AIC5_SSR 0x0
+#define AT91_AIC5_SMR 0x4
+#define AT91_AIC5_SVR 0x8
+#define AT91_AIC5_IVR 0x10
+#define AT91_AIC5_ISR 0x18
+#define AT91_AIC5_EOICR 0x38
+#define AT91_AIC5_SPU 0x3c
+#define AT91_AIC5_IECR 0x40
+#define AT91_AIC5_IDCR 0x44
+#define AT91_AIC5_ICCR 0x48
+#define AT91_AIC5_ISCR 0x4c
+#define AT91_AIC5_DCR 0x6c
+
+#define AT91_AIC_PRIOR GENMASK(2, 0)
+#define AT91_AIC_IRQ_MIN_PRIORITY 0
+#define AT91_AIC_IRQ_MAX_PRIORITY 7
+
+#define AT91_AIC_SRCTYPE GENMASK(6, 5)
+#define AT91_AIC_SRCTYPE_LOW (0 << 5)
+#define AT91_AIC_SRCTYPE_FALLING (1 << 5)
+#define AT91_AIC_SRCTYPE_HIGH (2 << 5)
+#define AT91_AIC_SRCTYPE_RISING (3 << 5)
+
+struct aic_chip_data {
+ u32 ext_irqs;
+};
+
+/**
+ * struct aic_reg_offset
+ *
+ * @eoi: End of interrupt command register
+ * @smr: Source mode register
+ * @ssr: Source select register
+ * @iscr: Interrupt set command register
+ * @idcr: Interrupt disable command register
+ * @iccr: Interrupt clear command register
+ * @iecr: Interrupt enable command register
+ * @spu: Spurious interrupt vector register
+ * @dcr: Debug control register
+ * @svr: Source vector register
+ * @ivr: Interrupt vector register
+ * @isr: Interrupt status register
+ *
+ * Each value means register offset.
+ */
+struct aic_reg_offset {
+ int eoi;
+ int smr;
+ int ssr;
+ int iscr;
+ int idcr;
+ int iccr;
+ int iecr;
+ int spu;
+ int dcr;
+ int svr;
+ int ivr;
+ int isr;
+};
+
+static const struct aic_reg_offset aic_regs = {
+ .eoi = AT91_AIC_EOICR,
+ .smr = AT91_AIC_SMR_BASE,
+ .ssr = AT91_INVALID_OFFSET, /* No SSR exists */
+ .iscr = AT91_AIC_ISCR,
+ .idcr = AT91_AIC_IDCR,
+ .iccr = AT91_AIC_ICCR,
+ .iecr = AT91_AIC_IECR,
+ .spu = AT91_AIC_SPU,
+ .dcr = AT91_AIC_DCR,
+ .svr = AT91_AIC_SVR_BASE,
+ .ivr = AT91_AIC_IVR,
+ .isr = AT91_AIC_ISR,
+};
+
+static const struct aic_reg_offset aic5_regs = {
+ .eoi = AT91_AIC5_EOICR,
+ .smr = AT91_AIC5_SMR,
+ .ssr = AT91_AIC5_SSR,
+ .iscr = AT91_AIC5_ISCR,
+ .idcr = AT91_AIC5_IDCR,
+ .iccr = AT91_AIC5_ICCR,
+ .iecr = AT91_AIC5_IECR,
+ .spu = AT91_AIC5_SPU,
+ .dcr = AT91_AIC5_DCR,
+ .svr = AT91_AIC5_SVR,
+ .ivr = AT91_AIC5_IVR,
+ .isr = AT91_AIC5_ISR,
+};
+
+static struct irq_domain *aic_domain;
+static const struct aic_reg_offset *aic_reg_data;
+
+static asmlinkage void __exception_irq_entry
+aic_handle(struct pt_regs *regs)
+{
+ struct irq_chip_generic *gc = irq_get_domain_generic_chip(aic_domain,
+ 0);
+ u32 hwirq = irq_reg_readl(gc, aic_reg_data->ivr);
+ u32 status = irq_reg_readl(gc, aic_reg_data->isr);
+
+ if (!status)
+ irq_reg_writel(gc, 0, aic_reg_data->eoi);
+ else
+ handle_domain_irq(aic_domain, hwirq, regs);
+}
+
+static inline bool aic_is_ssr_used(void)
+{
+ return aic_reg_data->ssr != AT91_INVALID_OFFSET;
+}
+
+static void aic_update_smr(struct irq_chip_generic *gc, int hwirq,
+ u32 mask, u32 val)
+{
+ int reg = aic_reg_data->smr;
+ u32 tmp = 0;
+
+ if (aic_is_ssr_used())
+ irq_reg_writel(gc, hwirq, aic_reg_data->ssr);
+ else
+ reg += hwirq * 4;
+
+ tmp = irq_reg_readl(gc, reg);
+ tmp &= mask;
+ tmp |= val;
+
+ irq_reg_writel(gc, tmp, reg);
+}
+
+static int aic_irq_domain_xlate(struct irq_domain *d, struct device_node *node,
+ const u32 *intspec, unsigned int intsize,
+ irq_hw_number_t *out_hwirq,
+ unsigned int *out_type)
+{
+ struct irq_chip_generic *gc = irq_get_domain_generic_chip(d, 0);
+ bool condition = (intsize < 3) ||
+ (intspec[2] < AT91_AIC_IRQ_MIN_PRIORITY) ||
+ (intspec[2] > AT91_AIC_IRQ_MAX_PRIORITY);
+
+ if (!gc || WARN_ON(condition))
+ return -EINVAL;
+
+ /*
+ * intspec[0]: HW IRQ number
+ * intspec[1]: IRQ flag
+ * intspec[2]: IRQ priority
+ */
+
+ *out_hwirq = intspec[0];
+ *out_type = intspec[1] & IRQ_TYPE_SENSE_MASK;
+
+ irq_gc_lock(gc);
+ aic_update_smr(gc, *out_hwirq, ~AT91_AIC_PRIOR, intspec[2]);
+ irq_gc_unlock(gc);
+
+ return 0;
+}
+
+static const struct irq_domain_ops aic_irq_ops = {
+ .map = irq_map_generic_chip,
+ .xlate = aic_irq_domain_xlate,
+};
+
+static void aic_irq_shutdown(struct irq_data *d)
+{
+ struct irq_chip_type *ct = irq_data_get_chip_type(d);
+
+ ct->chip.irq_mask(d);
+}
+
+static void aic_mask(struct irq_data *d)
+{
+ struct irq_domain *domain = d->domain;
+ struct irq_chip_generic *bgc = irq_get_domain_generic_chip(domain, 0);
+ struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
+ u32 mask = d->mask;
+
+ /*
+ * Disable interrupt. We always take the lock of the
+ * first irq chip as all chips share the same registers.
+ */
+ irq_gc_lock(bgc);
+
+ if (aic_is_ssr_used()) {
+ irq_reg_writel(gc, d->hwirq, aic_reg_data->ssr);
+ irq_reg_writel(gc, 1, aic_reg_data->idcr);
+ } else {
+ irq_reg_writel(gc, mask, aic_reg_data->idcr);
+ }
+
+ gc->mask_cache &= ~mask;
+
+ irq_gc_unlock(bgc);
+}
+
+static void aic_unmask(struct irq_data *d)
+{
+ struct irq_domain *domain = d->domain;
+ struct irq_chip_generic *bgc = irq_get_domain_generic_chip(domain, 0);
+ struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
+ u32 mask = d->mask;
+
+ /*
+ * Enable interrupt. We always take the lock of the
+ * first irq chip as all chips share the same registers.
+ */
+ irq_gc_lock(bgc);
+
+ if (aic_is_ssr_used()) {
+ irq_reg_writel(gc, d->hwirq, aic_reg_data->ssr);
+ irq_reg_writel(gc, 1, aic_reg_data->iecr);
+ } else {
+ irq_reg_writel(gc, mask, aic_reg_data->iecr);
+ }
+
+ gc->mask_cache |= mask;
+
+ irq_gc_unlock(bgc);
+}
+
+static int aic_retrigger(struct irq_data *d)
+{
+ struct irq_domain *domain = d->domain;
+ struct irq_chip_generic *bgc = irq_get_domain_generic_chip(domain, 0);
+
+ /* Set interrupt */
+ irq_gc_lock(bgc);
+
+ if (aic_is_ssr_used()) {
+ irq_reg_writel(bgc, d->hwirq, aic_reg_data->ssr);
+ irq_reg_writel(bgc, 1, aic_reg_data->iscr);
+ } else {
+ irq_reg_writel(bgc, d->mask, aic_reg_data->iscr);
+ }
+
+ irq_gc_unlock(bgc);
+
+ return 0;
+}
+
+static int aic_set_type(struct irq_data *d, unsigned int type)
+{
+ struct irq_domain *domain = d->domain;
+ struct irq_chip_generic *bgc = irq_get_domain_generic_chip(domain, 0);
+ struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
+ struct aic_chip_data *aic = gc->private;
+ u32 val;
+
+ switch (type) {
+ case IRQ_TYPE_LEVEL_HIGH:
+ val = AT91_AIC_SRCTYPE_HIGH;
+ break;
+ case IRQ_TYPE_EDGE_RISING:
+ val = AT91_AIC_SRCTYPE_RISING;
+ break;
+ case IRQ_TYPE_LEVEL_LOW:
+ if (!(d->mask & aic->ext_irqs))
+ return -EINVAL;
+
+ val = AT91_AIC_SRCTYPE_LOW;
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ if (!(d->mask & aic->ext_irqs))
+ return -EINVAL;
+
+ val = AT91_AIC_SRCTYPE_FALLING;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ irq_gc_lock(bgc);
+ aic_update_smr(bgc, d->hwirq, ~AT91_AIC_SRCTYPE, val);
+ irq_gc_unlock(bgc);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+
+enum aic_pm_mode {
+ AIC_PM_SUSPEND,
+ AIC_PM_RESUME,
+};
+
+static void aic_pm_ctrl_ssr(struct irq_data *d, enum aic_pm_mode mode)
+{
+ struct irq_domain *domain = d->domain;
+ struct irq_chip_generic *bgc = irq_get_domain_generic_chip(domain, 0);
+ struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
+ u32 mask;
+ u32 which;
+ int i;
+
+ if (mode == AIC_PM_SUSPEND)
+ which = gc->wake_active;
+ else
+ which = gc->mask_cache;
+
+ irq_gc_lock(bgc);
+
+ for (i = 0; i < AIC_IRQS_PER_CHIP; i++) {
+ mask = 1 << i;
+ if ((mask & gc->mask_cache) == (mask & gc->wake_active))
+ continue;
+
+ irq_reg_writel(bgc, i + gc->irq_base, aic_reg_data->ssr);
+
+ if (mask & which)
+ irq_reg_writel(bgc, 1, aic_reg_data->iecr);
+ else
+ irq_reg_writel(bgc, 1, aic_reg_data->idcr);
+ }
+
+ irq_gc_unlock(bgc);
+}
+
+static void aic_pm_ctrl(struct irq_data *d, enum aic_pm_mode mode)
+{
+ struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
+ u32 mask_idcr;
+ u32 mask_iecr;
+
+ if (mode == AIC_PM_SUSPEND) {
+ mask_idcr = gc->mask_cache;
+ mask_iecr = gc->wake_active;
+ } else {
+ mask_idcr = gc->wake_active;
+ mask_iecr = gc->mask_cache;
+ }
+
+ irq_gc_lock(gc);
+ irq_reg_writel(gc, mask_idcr, aic_reg_data->idcr);
+ irq_reg_writel(gc, mask_iecr, aic_reg_data->iecr);
+ irq_gc_unlock(gc);
+}
+
+static void aic_suspend(struct irq_data *d)
+{
+ if (aic_is_ssr_used())
+ aic_pm_ctrl_ssr(d, AIC_PM_SUSPEND);
+ else
+ aic_pm_ctrl(d, AIC_PM_SUSPEND);
+}
+
+static void aic_resume(struct irq_data *d)
+{
+ if (aic_is_ssr_used())
+ aic_pm_ctrl_ssr(d, AIC_PM_RESUME);
+ else
+ aic_pm_ctrl(d, AIC_PM_RESUME);
+}
+
+static void aic_pm_shutdown(struct irq_data *d)
+{
+ struct irq_domain *domain = d->domain;
+ struct irq_chip_generic *bgc = irq_get_domain_generic_chip(domain, 0);
+ struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
+ int i;
+
+ if (aic_is_ssr_used()) {
+ irq_gc_lock(bgc);
+ for (i = 0; i < AIC_IRQS_PER_CHIP; i++) {
+ irq_reg_writel(bgc, i + gc->irq_base,
+ aic_reg_data->ssr);
+ irq_reg_writel(bgc, 1, aic_reg_data->idcr);
+ irq_reg_writel(bgc, 1, aic_reg_data->iccr);
+ }
+ irq_gc_unlock(bgc);
+ } else {
+ irq_gc_lock(gc);
+ irq_reg_writel(gc, 0xffffffff, aic_reg_data->idcr);
+ irq_reg_writel(gc, 0xffffffff, aic_reg_data->iccr);
+ irq_gc_unlock(gc);
+ }
+}
+#else
+#define aic_suspend NULL
+#define aic_resume NULL
+#define aic_pm_shutdown NULL
+#endif /* CONFIG_PM */
+
+static int __init aic_get_num_chips(struct device_node *node)
+{
+ int nirqs;
+
+ /* Get total number of IRQs and configure register data */
+ if (of_device_is_compatible(node, "atmel,at91rm9200-aic")) {
+ nirqs = NR_AT91RM9200_IRQS;
+ aic_reg_data = &aic_regs;
+ } else if (of_device_is_compatible(node, "atmel,sama5d2-aic")) {
+ nirqs = NR_SAMA5D2_IRQS;
+ aic_reg_data = &aic5_regs;
+ } else if (of_device_is_compatible(node, "atmel,sama5d3-aic")) {
+ nirqs = NR_SAMA5D3_IRQS;
+ aic_reg_data = &aic5_regs;
+ } else if (of_device_is_compatible(node, "atmel,sama5d4-aic")) {
+ nirqs = NR_SAMA5D4_IRQS;
+ aic_reg_data = &aic5_regs;
+ } else {
+ return -EINVAL;
+ }
+
+ return DIV_ROUND_UP(nirqs, AIC_IRQS_PER_CHIP);
+}
+
+static void __init aic_ext_irq_of_init(struct irq_domain *domain)
+{
+ struct device_node *node = irq_domain_get_of_node(domain);
+ struct irq_chip_generic *gc;
+ struct aic_chip_data *aic;
+ struct property *prop;
+ const __be32 *p;
+ u32 hwirq;
+
+ gc = irq_get_domain_generic_chip(domain, 0);
+
+ aic = gc->private;
+ aic->ext_irqs |= 1;
+
+ of_property_for_each_u32(node, "atmel,external-irqs", prop, p, hwirq) {
+ gc = irq_get_domain_generic_chip(domain, hwirq);
+ if (!gc) {
+ pr_warn("AIC: external irq %d >= %d skip it\n",
+ hwirq, domain->revmap_size);
+ continue;
+ }
+
+ aic = gc->private;
+ aic->ext_irqs |= (1 << (hwirq % AIC_IRQS_PER_CHIP));
+ }
+}
+
+static void __init aic_hw_init(struct irq_domain *domain)
+{
+ struct irq_chip_generic *gc = irq_get_domain_generic_chip(domain, 0);
+ int i;
+
+ /*
+ * Perform 8 End Of Interrupt Command to make sure AIC
+ * will not Lock out nIRQ
+ */
+ for (i = 0; i < 8; i++)
+ irq_reg_writel(gc, 0, aic_reg_data->eoi);
+
+ /*
+ * Spurious Interrupt ID in Spurious Vector Register.
+ * When there is no current interrupt, the IRQ Vector Register
+ * reads the value stored in AIC_SPU
+ */
+ irq_reg_writel(gc, 0xffffffff, aic_reg_data->spu);
+
+ /* No debugging in AIC: Debug (Protect) Control Register */
+ irq_reg_writel(gc, 0, aic_reg_data->dcr);
+
+ /* Disable and clear all interrupts initially */
+ if (aic_is_ssr_used()) {
+ for (i = 0; i < domain->revmap_size; i++) {
+ irq_reg_writel(gc, i, aic_reg_data->ssr);
+ irq_reg_writel(gc, i, aic_reg_data->svr);
+ irq_reg_writel(gc, 1, aic_reg_data->idcr);
+ irq_reg_writel(gc, 1, aic_reg_data->iccr);
+ }
+ } else {
+ irq_reg_writel(gc, 0xffffffff, aic_reg_data->idcr);
+ irq_reg_writel(gc, 0xffffffff, aic_reg_data->iccr);
+
+ for (i = 0; i < NR_AT91RM9200_IRQS; i++)
+ irq_reg_writel(gc, i, aic_reg_data->svr + (i * 4));
+ }
+}
+
+static int __init aic_of_init(struct device_node *node,
+ struct device_node *parent)
+{
+ struct irq_chip_generic *gc;
+ struct irq_domain *domain;
+ struct aic_chip_data *aic;
+ void __iomem *reg_base;
+ int nchips;
+ int ret;
+ int i;
+
+ if (aic_domain)
+ return -EEXIST;
+
+ nchips = aic_get_num_chips(node);
+ if (nchips < 0)
+ return -EINVAL;
+
+ reg_base = of_iomap(node, 0);
+ if (!reg_base)
+ return -ENOMEM;
+
+ aic = kcalloc(nchips, sizeof(*aic), GFP_KERNEL);
+ if (!aic) {
+ ret = -ENOMEM;
+ goto err_iounmap;
+ }
+
+ domain = irq_domain_add_linear(node, nchips * AIC_IRQS_PER_CHIP,
+ &aic_irq_ops, aic);
+ if (!domain) {
+ ret = -ENOMEM;
+ goto err_free_aic;
+ }
+
+ ret = irq_alloc_domain_generic_chips(domain, AIC_IRQS_PER_CHIP, 1,
+ "atmel-aic", handle_fasteoi_irq,
+ IRQ_NOREQUEST | IRQ_NOPROBE |
+ IRQ_NOAUTOEN, 0, 0);
+ if (ret)
+ goto err_domain_remove;
+
+ for (i = 0; i < nchips; i++) {
+ gc = irq_get_domain_generic_chip(domain, i * AIC_IRQS_PER_CHIP);
+
+ gc->reg_base = reg_base;
+ gc->unused = 0;
+ gc->wake_enabled = ~0;
+
+ gc->chip_types[0].type = IRQ_TYPE_SENSE_MASK;
+ gc->chip_types[0].regs.eoi = aic_reg_data->eoi;
+ gc->chip_types[0].chip.irq_eoi = irq_gc_eoi;
+ gc->chip_types[0].chip.irq_set_wake = irq_gc_set_wake;
+ gc->chip_types[0].chip.irq_shutdown = aic_irq_shutdown;
+ gc->chip_types[0].chip.irq_mask = aic_mask;
+ gc->chip_types[0].chip.irq_unmask = aic_unmask;
+ gc->chip_types[0].chip.irq_retrigger = aic_retrigger;
+ gc->chip_types[0].chip.irq_set_type = aic_set_type;
+ gc->chip_types[0].chip.irq_suspend = aic_suspend;
+ gc->chip_types[0].chip.irq_resume = aic_resume;
+ gc->chip_types[0].chip.irq_pm_shutdown = aic_pm_shutdown;
+
+ gc->private = &aic[i];
+ }
+
+ aic_domain = domain;
+ aic_ext_irq_of_init(domain);
+ aic_hw_init(domain);
+ set_handle_irq(aic_handle);
+
+ return 0;
+
+err_domain_remove:
+ irq_domain_remove(domain);
+
+err_free_aic:
+ kfree(aic);
+
+err_iounmap:
+ iounmap(reg_base);
+ return ret;
+}
+IRQCHIP_DECLARE(at91rm9200_aic, "atmel,at91rm9200-aic", aic_of_init);
+IRQCHIP_DECLARE(sama5d2_aic5, "atmel,sama5d2-aic", aic_of_init);
+IRQCHIP_DECLARE(sama5d3_aic5, "atmel,sama5d3-aic", aic_of_init);
+IRQCHIP_DECLARE(sama5d4_aic5, "atmel,sama5d4-aic", aic_of_init);
diff --git a/drivers/irqchip/irq-atmel-aic-common.c b/drivers/irqchip/irq-atmel-aic-common.c
deleted file mode 100644
index 1d3978e..0000000
--- a/drivers/irqchip/irq-atmel-aic-common.c
+++ /dev/null
@@ -1,609 +0,0 @@
-/*
- * Atmel AIC (Advanced Interrupt Controller) Driver
- *
- * Copyright (C) 2004 SAN People
- * Copyright (C) 2004 ATMEL
- * Copyright (C) Rick Bronson
- * Copyright (C) 2014 Free Electrons
- *
- * Author: Boris BREZILLON <boris.brezillon@...e-electrons.com>
- *
- * This file is licensed under the terms of the GNU General Public
- * License version 2. This program is licensed "as is" without any
- * warranty of any kind, whether express or implied.
- */
-
-#include <linux/errno.h>
-#include <linux/init.h>
-#include <linux/io.h>
-#include <linux/irq.h>
-#include <linux/irqchip.h>
-#include <linux/irqdesc.h>
-#include <linux/irqdomain.h>
-#include <linux/kernel.h>
-#include <linux/module.h>
-#include <linux/of.h>
-#include <linux/of_address.h>
-#include <linux/slab.h>
-
-#include <asm/exception.h>
-#include <asm/mach/irq.h>
-
-#define AIC_IRQS_PER_CHIP 32
-#define NR_AT91RM9200_IRQS 32
-#define NR_SAMA5D2_IRQS 77
-#define NR_SAMA5D3_IRQS 48
-#define NR_SAMA5D4_IRQS 68
-
-#define AT91_AIC_SMR_BASE 0
-#define AT91_AIC_SVR_BASE 0x80
-#define AT91_AIC_IVR 0x100
-#define AT91_AIC_ISR 0x108
-#define AT91_AIC_IECR 0x120
-#define AT91_AIC_IDCR 0x124
-#define AT91_AIC_ICCR 0x128
-#define AT91_AIC_ISCR 0x12c
-#define AT91_AIC_EOICR 0x130
-#define AT91_AIC_SPU 0x134
-#define AT91_AIC_DCR 0x138
-#define AT91_INVALID_OFFSET (-1)
-
-#define AT91_AIC5_SSR 0x0
-#define AT91_AIC5_SMR 0x4
-#define AT91_AIC5_SVR 0x8
-#define AT91_AIC5_IVR 0x10
-#define AT91_AIC5_ISR 0x18
-#define AT91_AIC5_EOICR 0x38
-#define AT91_AIC5_SPU 0x3c
-#define AT91_AIC5_IECR 0x40
-#define AT91_AIC5_IDCR 0x44
-#define AT91_AIC5_ICCR 0x48
-#define AT91_AIC5_ISCR 0x4c
-#define AT91_AIC5_DCR 0x6c
-
-#define AT91_AIC_PRIOR GENMASK(2, 0)
-#define AT91_AIC_IRQ_MIN_PRIORITY 0
-#define AT91_AIC_IRQ_MAX_PRIORITY 7
-
-#define AT91_AIC_SRCTYPE GENMASK(6, 5)
-#define AT91_AIC_SRCTYPE_LOW (0 << 5)
-#define AT91_AIC_SRCTYPE_FALLING (1 << 5)
-#define AT91_AIC_SRCTYPE_HIGH (2 << 5)
-#define AT91_AIC_SRCTYPE_RISING (3 << 5)
-
-struct aic_chip_data {
- u32 ext_irqs;
-};
-
-/**
- * struct aic_reg_offset
- *
- * @eoi: End of interrupt command register
- * @smr: Source mode register
- * @ssr: Source select register
- * @iscr: Interrupt set command register
- * @idcr: Interrupt disable command register
- * @iccr: Interrupt clear command register
- * @iecr: Interrupt enable command register
- * @spu: Spurious interrupt vector register
- * @dcr: Debug control register
- * @svr: Source vector register
- * @ivr: Interrupt vector register
- * @isr: Interrupt status register
- *
- * Each value means register offset.
- */
-struct aic_reg_offset {
- int eoi;
- int smr;
- int ssr;
- int iscr;
- int idcr;
- int iccr;
- int iecr;
- int spu;
- int dcr;
- int svr;
- int ivr;
- int isr;
-};
-
-static const struct aic_reg_offset aic_regs = {
- .eoi = AT91_AIC_EOICR,
- .smr = AT91_AIC_SMR_BASE,
- .ssr = AT91_INVALID_OFFSET, /* No SSR exists */
- .iscr = AT91_AIC_ISCR,
- .idcr = AT91_AIC_IDCR,
- .iccr = AT91_AIC_ICCR,
- .iecr = AT91_AIC_IECR,
- .spu = AT91_AIC_SPU,
- .dcr = AT91_AIC_DCR,
- .svr = AT91_AIC_SVR_BASE,
- .ivr = AT91_AIC_IVR,
- .isr = AT91_AIC_ISR,
-};
-
-static const struct aic_reg_offset aic5_regs = {
- .eoi = AT91_AIC5_EOICR,
- .smr = AT91_AIC5_SMR,
- .ssr = AT91_AIC5_SSR,
- .iscr = AT91_AIC5_ISCR,
- .idcr = AT91_AIC5_IDCR,
- .iccr = AT91_AIC5_ICCR,
- .iecr = AT91_AIC5_IECR,
- .spu = AT91_AIC5_SPU,
- .dcr = AT91_AIC5_DCR,
- .svr = AT91_AIC5_SVR,
- .ivr = AT91_AIC5_IVR,
- .isr = AT91_AIC5_ISR,
-};
-
-static struct irq_domain *aic_domain;
-static const struct aic_reg_offset *aic_reg_data;
-
-static asmlinkage void __exception_irq_entry
-aic_handle(struct pt_regs *regs)
-{
- struct irq_chip_generic *gc = irq_get_domain_generic_chip(aic_domain,
- 0);
- u32 hwirq = irq_reg_readl(gc, aic_reg_data->ivr);
- u32 status = irq_reg_readl(gc, aic_reg_data->isr);
-
- if (!status)
- irq_reg_writel(gc, 0, aic_reg_data->eoi);
- else
- handle_domain_irq(aic_domain, hwirq, regs);
-}
-
-static inline bool aic_is_ssr_used(void)
-{
- return aic_reg_data->ssr != AT91_INVALID_OFFSET;
-}
-
-static void aic_update_smr(struct irq_chip_generic *gc, int hwirq,
- u32 mask, u32 val)
-{
- int reg = aic_reg_data->smr;
- u32 tmp = 0;
-
- if (aic_is_ssr_used())
- irq_reg_writel(gc, hwirq, aic_reg_data->ssr);
- else
- reg += hwirq * 4;
-
- tmp = irq_reg_readl(gc, reg);
- tmp &= mask;
- tmp |= val;
-
- irq_reg_writel(gc, tmp, reg);
-}
-
-static int aic_irq_domain_xlate(struct irq_domain *d, struct device_node *node,
- const u32 *intspec, unsigned int intsize,
- irq_hw_number_t *out_hwirq,
- unsigned int *out_type)
-{
- struct irq_chip_generic *gc = irq_get_domain_generic_chip(d, 0);
- bool condition = (intsize < 3) ||
- (intspec[2] < AT91_AIC_IRQ_MIN_PRIORITY) ||
- (intspec[2] > AT91_AIC_IRQ_MAX_PRIORITY);
-
- if (!gc || WARN_ON(condition))
- return -EINVAL;
-
- /*
- * intspec[0]: HW IRQ number
- * intspec[1]: IRQ flag
- * intspec[2]: IRQ priority
- */
-
- *out_hwirq = intspec[0];
- *out_type = intspec[1] & IRQ_TYPE_SENSE_MASK;
-
- irq_gc_lock(gc);
- aic_update_smr(gc, *out_hwirq, ~AT91_AIC_PRIOR, intspec[2]);
- irq_gc_unlock(gc);
-
- return 0;
-}
-
-static const struct irq_domain_ops aic_irq_ops = {
- .map = irq_map_generic_chip,
- .xlate = aic_irq_domain_xlate,
-};
-
-static void aic_irq_shutdown(struct irq_data *d)
-{
- struct irq_chip_type *ct = irq_data_get_chip_type(d);
-
- ct->chip.irq_mask(d);
-}
-
-static void aic_mask(struct irq_data *d)
-{
- struct irq_domain *domain = d->domain;
- struct irq_chip_generic *bgc = irq_get_domain_generic_chip(domain, 0);
- struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
- u32 mask = d->mask;
-
- /*
- * Disable interrupt. We always take the lock of the
- * first irq chip as all chips share the same registers.
- */
- irq_gc_lock(bgc);
-
- if (aic_is_ssr_used()) {
- irq_reg_writel(gc, d->hwirq, aic_reg_data->ssr);
- irq_reg_writel(gc, 1, aic_reg_data->idcr);
- } else {
- irq_reg_writel(gc, mask, aic_reg_data->idcr);
- }
-
- gc->mask_cache &= ~mask;
-
- irq_gc_unlock(bgc);
-}
-
-static void aic_unmask(struct irq_data *d)
-{
- struct irq_domain *domain = d->domain;
- struct irq_chip_generic *bgc = irq_get_domain_generic_chip(domain, 0);
- struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
- u32 mask = d->mask;
-
- /*
- * Enable interrupt. We always take the lock of the
- * first irq chip as all chips share the same registers.
- */
- irq_gc_lock(bgc);
-
- if (aic_is_ssr_used()) {
- irq_reg_writel(gc, d->hwirq, aic_reg_data->ssr);
- irq_reg_writel(gc, 1, aic_reg_data->iecr);
- } else {
- irq_reg_writel(gc, mask, aic_reg_data->iecr);
- }
-
- gc->mask_cache |= mask;
-
- irq_gc_unlock(bgc);
-}
-
-static int aic_retrigger(struct irq_data *d)
-{
- struct irq_domain *domain = d->domain;
- struct irq_chip_generic *bgc = irq_get_domain_generic_chip(domain, 0);
-
- /* Set interrupt */
- irq_gc_lock(bgc);
-
- if (aic_is_ssr_used()) {
- irq_reg_writel(bgc, d->hwirq, aic_reg_data->ssr);
- irq_reg_writel(bgc, 1, aic_reg_data->iscr);
- } else {
- irq_reg_writel(bgc, d->mask, aic_reg_data->iscr);
- }
-
- irq_gc_unlock(bgc);
-
- return 0;
-}
-
-static int aic_set_type(struct irq_data *d, unsigned int type)
-{
- struct irq_domain *domain = d->domain;
- struct irq_chip_generic *bgc = irq_get_domain_generic_chip(domain, 0);
- struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
- struct aic_chip_data *aic = gc->private;
- u32 val;
-
- switch (type) {
- case IRQ_TYPE_LEVEL_HIGH:
- val = AT91_AIC_SRCTYPE_HIGH;
- break;
- case IRQ_TYPE_EDGE_RISING:
- val = AT91_AIC_SRCTYPE_RISING;
- break;
- case IRQ_TYPE_LEVEL_LOW:
- if (!(d->mask & aic->ext_irqs))
- return -EINVAL;
-
- val = AT91_AIC_SRCTYPE_LOW;
- break;
- case IRQ_TYPE_EDGE_FALLING:
- if (!(d->mask & aic->ext_irqs))
- return -EINVAL;
-
- val = AT91_AIC_SRCTYPE_FALLING;
- break;
- default:
- return -EINVAL;
- }
-
- irq_gc_lock(bgc);
- aic_update_smr(bgc, d->hwirq, ~AT91_AIC_SRCTYPE, val);
- irq_gc_unlock(bgc);
-
- return 0;
-}
-
-#ifdef CONFIG_PM
-
-enum aic_pm_mode {
- AIC_PM_SUSPEND,
- AIC_PM_RESUME,
-};
-
-static void aic_pm_ctrl_ssr(struct irq_data *d, enum aic_pm_mode mode)
-{
- struct irq_domain *domain = d->domain;
- struct irq_chip_generic *bgc = irq_get_domain_generic_chip(domain, 0);
- struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
- u32 mask;
- u32 which;
- int i;
-
- if (mode == AIC_PM_SUSPEND)
- which = gc->wake_active;
- else
- which = gc->mask_cache;
-
- irq_gc_lock(bgc);
-
- for (i = 0; i < AIC_IRQS_PER_CHIP; i++) {
- mask = 1 << i;
- if ((mask & gc->mask_cache) == (mask & gc->wake_active))
- continue;
-
- irq_reg_writel(bgc, i + gc->irq_base, aic_reg_data->ssr);
-
- if (mask & which)
- irq_reg_writel(bgc, 1, aic_reg_data->iecr);
- else
- irq_reg_writel(bgc, 1, aic_reg_data->idcr);
- }
-
- irq_gc_unlock(bgc);
-}
-
-static void aic_pm_ctrl(struct irq_data *d, enum aic_pm_mode mode)
-{
- struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
- u32 mask_idcr;
- u32 mask_iecr;
-
- if (mode == AIC_PM_SUSPEND) {
- mask_idcr = gc->mask_cache;
- mask_iecr = gc->wake_active;
- } else {
- mask_idcr = gc->wake_active;
- mask_iecr = gc->mask_cache;
- }
-
- irq_gc_lock(gc);
- irq_reg_writel(gc, mask_idcr, aic_reg_data->idcr);
- irq_reg_writel(gc, mask_iecr, aic_reg_data->iecr);
- irq_gc_unlock(gc);
-}
-
-static void aic_suspend(struct irq_data *d)
-{
- if (aic_is_ssr_used())
- aic_pm_ctrl_ssr(d, AIC_PM_SUSPEND);
- else
- aic_pm_ctrl(d, AIC_PM_SUSPEND);
-}
-
-static void aic_resume(struct irq_data *d)
-{
- if (aic_is_ssr_used())
- aic_pm_ctrl_ssr(d, AIC_PM_RESUME);
- else
- aic_pm_ctrl(d, AIC_PM_RESUME);
-}
-
-static void aic_pm_shutdown(struct irq_data *d)
-{
- struct irq_domain *domain = d->domain;
- struct irq_chip_generic *bgc = irq_get_domain_generic_chip(domain, 0);
- struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
- int i;
-
- if (aic_is_ssr_used()) {
- irq_gc_lock(bgc);
- for (i = 0; i < AIC_IRQS_PER_CHIP; i++) {
- irq_reg_writel(bgc, i + gc->irq_base,
- aic_reg_data->ssr);
- irq_reg_writel(bgc, 1, aic_reg_data->idcr);
- irq_reg_writel(bgc, 1, aic_reg_data->iccr);
- }
- irq_gc_unlock(bgc);
- } else {
- irq_gc_lock(gc);
- irq_reg_writel(gc, 0xffffffff, aic_reg_data->idcr);
- irq_reg_writel(gc, 0xffffffff, aic_reg_data->iccr);
- irq_gc_unlock(gc);
- }
-}
-#else
-#define aic_suspend NULL
-#define aic_resume NULL
-#define aic_pm_shutdown NULL
-#endif /* CONFIG_PM */
-
-static int __init aic_get_num_chips(struct device_node *node)
-{
- int nirqs;
-
- /* Get total number of IRQs and configure register data */
- if (of_device_is_compatible(node, "atmel,at91rm9200-aic")) {
- nirqs = NR_AT91RM9200_IRQS;
- aic_reg_data = &aic_regs;
- } else if (of_device_is_compatible(node, "atmel,sama5d2-aic")) {
- nirqs = NR_SAMA5D2_IRQS;
- aic_reg_data = &aic5_regs;
- } else if (of_device_is_compatible(node, "atmel,sama5d3-aic")) {
- nirqs = NR_SAMA5D3_IRQS;
- aic_reg_data = &aic5_regs;
- } else if (of_device_is_compatible(node, "atmel,sama5d4-aic")) {
- nirqs = NR_SAMA5D4_IRQS;
- aic_reg_data = &aic5_regs;
- } else {
- return -EINVAL;
- }
-
- return DIV_ROUND_UP(nirqs, AIC_IRQS_PER_CHIP);
-}
-
-static void __init aic_ext_irq_of_init(struct irq_domain *domain)
-{
- struct device_node *node = irq_domain_get_of_node(domain);
- struct irq_chip_generic *gc;
- struct aic_chip_data *aic;
- struct property *prop;
- const __be32 *p;
- u32 hwirq;
-
- gc = irq_get_domain_generic_chip(domain, 0);
-
- aic = gc->private;
- aic->ext_irqs |= 1;
-
- of_property_for_each_u32(node, "atmel,external-irqs", prop, p, hwirq) {
- gc = irq_get_domain_generic_chip(domain, hwirq);
- if (!gc) {
- pr_warn("AIC: external irq %d >= %d skip it\n",
- hwirq, domain->revmap_size);
- continue;
- }
-
- aic = gc->private;
- aic->ext_irqs |= (1 << (hwirq % AIC_IRQS_PER_CHIP));
- }
-}
-
-static void __init aic_hw_init(struct irq_domain *domain)
-{
- struct irq_chip_generic *gc = irq_get_domain_generic_chip(domain, 0);
- int i;
-
- /*
- * Perform 8 End Of Interrupt Command to make sure AIC
- * will not Lock out nIRQ
- */
- for (i = 0; i < 8; i++)
- irq_reg_writel(gc, 0, aic_reg_data->eoi);
-
- /*
- * Spurious Interrupt ID in Spurious Vector Register.
- * When there is no current interrupt, the IRQ Vector Register
- * reads the value stored in AIC_SPU
- */
- irq_reg_writel(gc, 0xffffffff, aic_reg_data->spu);
-
- /* No debugging in AIC: Debug (Protect) Control Register */
- irq_reg_writel(gc, 0, aic_reg_data->dcr);
-
- /* Disable and clear all interrupts initially */
- if (aic_is_ssr_used()) {
- for (i = 0; i < domain->revmap_size; i++) {
- irq_reg_writel(gc, i, aic_reg_data->ssr);
- irq_reg_writel(gc, i, aic_reg_data->svr);
- irq_reg_writel(gc, 1, aic_reg_data->idcr);
- irq_reg_writel(gc, 1, aic_reg_data->iccr);
- }
- } else {
- irq_reg_writel(gc, 0xffffffff, aic_reg_data->idcr);
- irq_reg_writel(gc, 0xffffffff, aic_reg_data->iccr);
-
- for (i = 0; i < NR_AT91RM9200_IRQS; i++)
- irq_reg_writel(gc, i, aic_reg_data->svr + (i * 4));
- }
-}
-
-static int __init aic_of_init(struct device_node *node,
- struct device_node *parent)
-{
- struct irq_chip_generic *gc;
- struct irq_domain *domain;
- struct aic_chip_data *aic;
- void __iomem *reg_base;
- int nchips;
- int ret;
- int i;
-
- if (aic_domain)
- return -EEXIST;
-
- nchips = aic_get_num_chips(node);
- if (nchips < 0)
- return -EINVAL;
-
- reg_base = of_iomap(node, 0);
- if (!reg_base)
- return -ENOMEM;
-
- aic = kcalloc(nchips, sizeof(*aic), GFP_KERNEL);
- if (!aic) {
- ret = -ENOMEM;
- goto err_iounmap;
- }
-
- domain = irq_domain_add_linear(node, nchips * AIC_IRQS_PER_CHIP,
- &aic_irq_ops, aic);
- if (!domain) {
- ret = -ENOMEM;
- goto err_free_aic;
- }
-
- ret = irq_alloc_domain_generic_chips(domain, AIC_IRQS_PER_CHIP, 1,
- "atmel-aic", handle_fasteoi_irq,
- IRQ_NOREQUEST | IRQ_NOPROBE |
- IRQ_NOAUTOEN, 0, 0);
- if (ret)
- goto err_domain_remove;
-
- for (i = 0; i < nchips; i++) {
- gc = irq_get_domain_generic_chip(domain, i * AIC_IRQS_PER_CHIP);
-
- gc->reg_base = reg_base;
- gc->unused = 0;
- gc->wake_enabled = ~0;
-
- gc->chip_types[0].type = IRQ_TYPE_SENSE_MASK;
- gc->chip_types[0].regs.eoi = aic_reg_data->eoi;
- gc->chip_types[0].chip.irq_eoi = irq_gc_eoi;
- gc->chip_types[0].chip.irq_set_wake = irq_gc_set_wake;
- gc->chip_types[0].chip.irq_shutdown = aic_irq_shutdown;
- gc->chip_types[0].chip.irq_mask = aic_mask;
- gc->chip_types[0].chip.irq_unmask = aic_unmask;
- gc->chip_types[0].chip.irq_retrigger = aic_retrigger;
- gc->chip_types[0].chip.irq_set_type = aic_set_type;
- gc->chip_types[0].chip.irq_suspend = aic_suspend;
- gc->chip_types[0].chip.irq_resume = aic_resume;
- gc->chip_types[0].chip.irq_pm_shutdown = aic_pm_shutdown;
-
- gc->private = &aic[i];
- }
-
- aic_domain = domain;
- aic_ext_irq_of_init(domain);
- aic_hw_init(domain);
- set_handle_irq(aic_handle);
-
- return 0;
-
-err_domain_remove:
- irq_domain_remove(domain);
-
-err_free_aic:
- kfree(aic);
-
-err_iounmap:
- iounmap(reg_base);
- return ret;
-}
-IRQCHIP_DECLARE(at91rm9200_aic, "atmel,at91rm9200-aic", aic_of_init);
-IRQCHIP_DECLARE(sama5d2_aic5, "atmel,sama5d2-aic", aic_of_init);
-IRQCHIP_DECLARE(sama5d3_aic5, "atmel,sama5d3-aic", aic_of_init);
-IRQCHIP_DECLARE(sama5d4_aic5, "atmel,sama5d4-aic", aic_of_init);
--
2.6.4
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
Powered by blists - more mailing lists