From patchwork Fri Mar 28 17:59:00 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Boris BREZILLON X-Patchwork-Id: 3906411 Return-Path: X-Original-To: patchwork-linux-arm@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork1.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.19.201]) by patchwork1.web.kernel.org (Postfix) with ESMTP id 6F8CE9F2E8 for ; Fri, 28 Mar 2014 18:08:26 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id C186F20272 for ; Fri, 28 Mar 2014 18:08:24 +0000 (UTC) Received: from casper.infradead.org (casper.infradead.org [85.118.1.10]) (using TLSv1.2 with cipher DHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id D120420295 for ; Fri, 28 Mar 2014 18:08:22 +0000 (UTC) Received: from merlin.infradead.org ([2001:4978:20e::2]) by casper.infradead.org with esmtps (Exim 4.80.1 #2 (Red Hat Linux)) id 1WTb72-00021y-PY; Fri, 28 Mar 2014 18:02:46 +0000 Received: from localhost ([::1] helo=merlin.infradead.org) by merlin.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1WTb6E-0007gm-PK; Fri, 28 Mar 2014 18:01:50 +0000 Received: from mail-we0-x22f.google.com ([2a00:1450:400c:c03::22f]) by merlin.infradead.org with esmtps (Exim 4.80.1 #2 (Red Hat Linux)) id 1WTb4K-0007R0-EQ for linux-arm-kernel@lists.infradead.org; Fri, 28 Mar 2014 18:00:15 +0000 Received: by mail-we0-f175.google.com with SMTP id q58so2953084wes.34 for ; Fri, 28 Mar 2014 10:59:28 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=MAs37lgUIyZi51IPzy+T7ghFtiGxP14NB4tDoZx37Og=; b=iVdeCHs71AmqnbaWm4vj0F81JmePYh2Y2Dr+Q07K/US1FBZ4LlYkNMsaNe5nU7/NXD tWOu1gkQH4eHiEvciPh3qRkfel0I+OejJ3seaSYJtnJmFwVCW15Xmgr9hNXdnCzCy1Yi kYmbJ+ZgAi+xUNFv6kH2ZoDypnVrO3MO7t4dosppUIbkz0NsJUHFhg7debMnzsitzMJv 5Gm9jOLA4G9CZh5OT6EkTcMO/K65MiBwsukejQpcXfoXBMrNVlXfMj3yTybroaWbrfiE p6CejuOs2Cs6BGeSYLNyE3OT3T1QLIdgBYZwORCeqT7jyr497vaWBSNm/NbMNjYDuH/1 w2/w== X-Received: by 10.180.78.200 with SMTP id d8mr48953795wix.34.1396029567945; Fri, 28 Mar 2014 10:59:27 -0700 (PDT) Received: from localhost.localdomain (col31-4-88-188-80-5.fbx.proxad.net. [88.188.80.5]) by mx.google.com with ESMTPSA id mw3sm9217422wic.7.2014.03.28.10.59.24 for (version=TLSv1.1 cipher=ECDHE-RSA-RC4-SHA bits=128/128); Fri, 28 Mar 2014 10:59:27 -0700 (PDT) From: Boris BREZILLON To: Rob Landley , Nicolas Ferre , Jean-Christophe Plagniol-Villard , Thomas Gleixner Subject: [RFC PATCH v2 02/10] irqchip: atmel-aic: add new atmel AIC driver Date: Fri, 28 Mar 2014 18:59:00 +0100 Message-Id: <1396029548-10928-3-git-send-email-b.brezillon.dev@gmail.com> X-Mailer: git-send-email 1.8.3.2 In-Reply-To: <1396029548-10928-1-git-send-email-b.brezillon.dev@gmail.com> References: <1396029548-10928-1-git-send-email-b.brezillon.dev@gmail.com> X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20140328_135952_962385_92DDD167 X-CRM114-Status: GOOD ( 20.95 ) X-Spam-Score: -2.0 (--) Cc: devicetree@vger.kernel.org, Boris BREZILLON , linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org, linux-doc@vger.kernel.org X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org X-Spam-Status: No, score=-4.5 required=5.0 tests=BAYES_00, DKIM_ADSP_CUSTOM_MED, DKIM_SIGNED, FREEMAIL_FROM, RCVD_IN_DNSWL_MED, RP_MATCHES_RCVD, T_DKIM_INVALID, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP Add new atmel AIC (Advanced Interrupt Controller) driver based on the generic chip infrastructure. This driver is only compatible with dt enabled board and replaces the old implementation found in arch/arm/mach-at91/irq.c. It also provides a new way to handle AIC irq line muxing to avoid spurious interrupts on startup and shutdown: - disable all muxed interrupts during AIC probe - disable every muxed interrupt attached to a specific AIC interrupt line when this interrupt is disabled Signed-off-by: Boris BREZILLON --- drivers/irqchip/Makefile | 1 + drivers/irqchip/irq-atmel-aic.c | 851 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 852 insertions(+) create mode 100644 drivers/irqchip/irq-atmel-aic.c diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index 5194afb..b2950860 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -16,6 +16,7 @@ obj-$(CONFIG_ARCH_SPEAR3XX) += spear-shirq.o obj-$(CONFIG_ARM_GIC) += irq-gic.o obj-$(CONFIG_ARM_NVIC) += irq-nvic.o obj-$(CONFIG_ARM_VIC) += irq-vic.o +obj-$(CONFIG_ATMEL_AIC_IRQ) += irq-atmel-aic.o obj-$(CONFIG_IMGPDC_IRQ) += irq-imgpdc.o obj-$(CONFIG_SIRF_IRQ) += irq-sirfsoc.o obj-$(CONFIG_RENESAS_INTC_IRQPIN) += irq-renesas-intc-irqpin.o diff --git a/drivers/irqchip/irq-atmel-aic.c b/drivers/irqchip/irq-atmel-aic.c new file mode 100644 index 0000000..4e5feb2 --- /dev/null +++ b/drivers/irqchip/irq-atmel-aic.c @@ -0,0 +1,851 @@ +/* + * Atmel AT91 AIC (Advanced Interrupt Controller) driver + * + * Copyright (C) 2004 SAN People + * Copyright (C) 2004 ATMEL + * Copyright (C) Rick Bronson + * Copyright (C) 2013 Boris BREZILLON + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "irqchip.h" + +/* Number of irq lines managed by AIC */ +#define NR_AIC_IRQS 32 +#define NR_AIC5_IRQS 128 + +#define AT91_AIC5_SSR 0x0 +#define AT91_AIC5_INTSEL_MSK (0x7f << 0) + +#define AT91_AIC_IRQ_MIN_PRIORITY 0 +#define AT91_AIC_IRQ_MAX_PRIORITY 7 + +#define AT91_AIC_SMR(n) ((n) * 4) +#define AT91_AIC5_SMR 0x4 +#define AT91_AIC_PRIOR (7 << 0) +#define AT91_AIC_SRCTYPE (3 << 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) + +#define AT91_AIC_SVR(n) (0x80 + ((n) * 4)) +#define AT91_AIC5_SVR 0x8 +#define AT91_AIC_IVR 0x100 +#define AT91_AIC5_IVR 0x10 +#define AT91_AIC_FVR 0x104 +#define AT91_AIC5_FVR 0x14 +#define AT91_AIC_ISR 0x108 +#define AT91_AIC5_ISR 0x18 +#define AT91_AIC_IRQID (0x1f << 0) + +#define AT91_AIC_IPR 0x10c +#define AT91_AIC5_IPR0 0x20 +#define AT91_AIC5_IPR1 0x24 +#define AT91_AIC5_IPR2 0x28 +#define AT91_AIC5_IPR3 0x2c +#define AT91_AIC_IMR 0x110 +#define AT91_AIC5_IMR 0x30 +#define AT91_AIC_CISR 0x114 +#define AT91_AIC5_CISR 0x34 +#define AT91_AIC_NFIQ (1 << 0) +#define AT91_AIC_NIRQ (1 << 1) + +#define AT91_AIC_IECR 0x120 +#define AT91_AIC5_IECR 0x40 +#define AT91_AIC_IDCR 0x124 +#define AT91_AIC5_IDCR 0x44 +#define AT91_AIC_ICCR 0x128 +#define AT91_AIC5_ICCR 0x48 +#define AT91_AIC_ISCR 0x12c +#define AT91_AIC5_ISCR 0x4c +#define AT91_AIC_EOICR 0x130 +#define AT91_AIC5_EOICR 0x38 +#define AT91_AIC_SPU 0x134 +#define AT91_AIC5_SPU 0x3c +#define AT91_AIC_DCR 0x138 +#define AT91_AIC5_DCR 0x6c +#define AT91_AIC_DCR_PROT (1 << 0) +#define AT91_AIC_DCR_GMSK (1 << 1) + +#define AT91_AIC_FFER 0x140 +#define AT91_AIC5_FFER 0x50 +#define AT91_AIC_FFDR 0x144 +#define AT91_AIC5_FFDR 0x54 +#define AT91_AIC_FFSR 0x148 +#define AT91_AIC5_FFSR 0x58 + +enum aic_mux_irq_type { + AIC_MUX_1REG_IRQ, + AIC_MUX_3REG_IRQ, +}; + +struct aic_mux_irq { + struct list_head node; + enum aic_mux_irq_type type; + void __iomem *regs; + u32 mask; +}; + +struct aic_chip_data { + u32 ext_irqs; + struct list_head mux[32]; +}; + +static struct irq_domain *aic_domain; + +static asmlinkage void __exception_irq_entry +aic_handle(struct pt_regs *regs) +{ + struct irq_domain_chip_generic *dgc = aic_domain->gc; + struct irq_chip_generic *gc = dgc->gc[0]; + u32 irqnr; + u32 irqstat; + + irqnr = irq_reg_readl(gc->reg_base + AT91_AIC_IVR); + irqstat = irq_reg_readl(gc->reg_base + AT91_AIC_ISR); + + irqnr = irq_find_mapping(aic_domain, irqnr); + + if (!irqstat) + irq_reg_writel(0, gc->reg_base + AT91_AIC_EOICR); + else + handle_IRQ(irqnr, regs); +} + +static asmlinkage void __exception_irq_entry +aic5_handle(struct pt_regs *regs) +{ + struct irq_domain_chip_generic *dgc = aic_domain->gc; + struct irq_chip_generic *gc = dgc->gc[0]; + u32 irqnr; + u32 irqstat; + + irqnr = irq_reg_readl(gc->reg_base + AT91_AIC5_IVR); + irqstat = irq_reg_readl(gc->reg_base + AT91_AIC5_ISR); + + irqnr = irq_find_mapping(aic_domain, irqnr); + + if (!irqstat) + irq_reg_writel(0, gc->reg_base + AT91_AIC5_EOICR); + else + handle_IRQ(irqnr, regs); +} + +static void aic5_mask(struct irq_data *d) +{ + struct irq_domain *domain = d->domain; + struct irq_domain_chip_generic *dgc = domain->gc; + struct irq_chip_generic *gc = dgc->gc[0]; + + /* Disable interrupt on AIC5 */ + irq_gc_lock(gc); + irq_reg_writel(d->hwirq, gc->reg_base + AT91_AIC5_SSR); + irq_reg_writel(1, gc->reg_base + AT91_AIC5_IDCR); + gc->mask_cache &= ~d->mask; + irq_gc_unlock(gc); +} + +static void aic5_unmask(struct irq_data *d) +{ + struct irq_domain *domain = d->domain; + struct irq_domain_chip_generic *dgc = domain->gc; + struct irq_chip_generic *gc = dgc->gc[0]; + + /* Enable interrupt on AIC5 */ + irq_gc_lock(gc); + irq_reg_writel(d->hwirq, gc->reg_base + AT91_AIC5_SSR); + irq_reg_writel(1, gc->reg_base + AT91_AIC5_IECR); + gc->mask_cache |= d->mask; + irq_gc_unlock(gc); +} + +static int aic_retrigger(struct irq_data *d) +{ + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + + /* Enable interrupt on AIC5 */ + irq_gc_lock(gc); + irq_reg_writel(d->mask, gc->reg_base + AT91_AIC_ISCR); + irq_gc_unlock(gc); + + return 0; +} + +static int aic5_retrigger(struct irq_data *d) +{ + struct irq_domain *domain = d->domain; + struct irq_domain_chip_generic *dgc = domain->gc; + struct irq_chip_generic *gc = dgc->gc[0]; + + /* Enable interrupt on AIC5 */ + irq_gc_lock(gc); + irq_reg_writel(d->hwirq, gc->reg_base + AT91_AIC5_SSR); + irq_reg_writel(1, gc->reg_base + AT91_AIC5_ISCR); + irq_gc_unlock(gc); + + return 0; +} + +static int aic_to_srctype(struct irq_data *d, unsigned type) +{ + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + struct aic_chip_data *aic = gc->private; + + switch (type) { + case IRQ_TYPE_LEVEL_HIGH: + return AT91_AIC_SRCTYPE_HIGH; + case IRQ_TYPE_EDGE_RISING: + return AT91_AIC_SRCTYPE_RISING; + case IRQ_TYPE_LEVEL_LOW: + if (d->mask & aic->ext_irqs) + return AT91_AIC_SRCTYPE_LOW; + break; + case IRQ_TYPE_EDGE_FALLING: + if (d->mask & aic->ext_irqs) + return AT91_AIC_SRCTYPE_FALLING; + break; + default: + break; + } + + return -EINVAL; +} + +static int aic_set_type(struct irq_data *d, unsigned type) +{ + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + unsigned int smr; + int srctype; + + srctype = aic_to_srctype(d, type); + if (srctype < 0) + return srctype; + + smr = irq_reg_readl(gc->reg_base + AT91_AIC_SMR(d->hwirq)) & + ~AT91_AIC_SRCTYPE; + irq_reg_writel(smr | srctype, gc->reg_base + AT91_AIC_SMR(d->hwirq)); + + return 0; +} + +static int aic5_set_type(struct irq_data *d, unsigned type) +{ + struct irq_domain *domain = d->domain; + struct irq_domain_chip_generic *dgc = domain->gc; + struct irq_chip_generic *gc = dgc->gc[0]; + unsigned int smr; + int srctype; + + srctype = aic_to_srctype(d, type); + if (srctype < 0) + return srctype; + + irq_gc_lock(gc); + irq_reg_writel(d->hwirq, gc->reg_base + AT91_AIC5_SSR); + smr = irq_reg_readl(gc->reg_base + AT91_AIC5_SMR) & ~AT91_AIC_SRCTYPE; + irq_reg_writel(smr | srctype, gc->reg_base + AT91_AIC5_SMR); + irq_gc_unlock(gc); + + return 0; +} + +static void aic_mux_disable_irqs(struct list_head *mux_list) +{ + struct aic_mux_irq *irq; + + list_for_each_entry(irq, mux_list, node) { + if (irq->type == AIC_MUX_1REG_IRQ) + writel(readl(irq->regs) & ~irq->mask, irq->regs); + else + writel(irq->mask, irq->regs); + } +} + +static void aic_shutdown(struct irq_data *d) +{ + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + struct irq_chip_type *ct = irq_data_get_chip_type(d); + struct aic_chip_data *aic = gc->private; + int idx = d->hwirq % 32; + + aic_mux_disable_irqs(&aic->mux[idx]); + ct->chip.irq_mask(d); +} + +#ifdef CONFIG_PM +static void aic_suspend(struct irq_data *d) +{ + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + + irq_gc_lock(gc); + irq_reg_writel(gc->mask_cache, gc->reg_base + AT91_AIC_IDCR); + irq_reg_writel(gc->wake_active, gc->reg_base + AT91_AIC_IECR); + irq_gc_unlock(gc); +} + +static void aic5_suspend(struct irq_data *d) +{ + struct irq_domain *domain = d->domain; + struct irq_domain_chip_generic *dgc = domain->gc; + struct irq_chip_generic *bgc = dgc->gc[0]; + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + int i; + u32 mask; + + irq_gc_lock(bgc); + for (i = 0; i < dgc->irqs_per_chip; i++) { + mask = 1 << i; + if ((mask & gc->mask_cache) == (mask & gc->wake_active)) + continue; + + irq_reg_writel(i + gc->irq_base, + bgc->reg_base + AT91_AIC5_SSR); + if (mask & gc->wake_active) + irq_reg_writel(1, bgc->reg_base + AT91_AIC5_IECR); + else + irq_reg_writel(1, bgc->reg_base + AT91_AIC5_IDCR); + } + irq_gc_unlock(bgc); +} + +static void aic_resume(struct irq_data *d) +{ + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + + irq_gc_lock(gc); + irq_reg_writel(gc->wake_active, gc->reg_base + AT91_AIC_IDCR); + irq_reg_writel(gc->mask_cache, gc->reg_base + AT91_AIC_IECR); + irq_gc_unlock(gc); +} + +static void aic5_resume(struct irq_data *d) +{ + struct irq_domain *domain = d->domain; + struct irq_domain_chip_generic *dgc = domain->gc; + struct irq_chip_generic *bgc = dgc->gc[0]; + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + int i; + u32 mask; + + irq_gc_lock(bgc); + for (i = 0; i < dgc->irqs_per_chip; i++) { + mask = 1 << i; + if ((mask & gc->mask_cache) == (mask & gc->wake_active)) + continue; + + irq_reg_writel(i + gc->irq_base, + bgc->reg_base + AT91_AIC5_SSR); + if (mask & gc->mask_cache) + irq_reg_writel(1, bgc->reg_base + AT91_AIC5_IECR); + else + irq_reg_writel(1, bgc->reg_base + AT91_AIC5_IDCR); + } + irq_gc_unlock(bgc); +} + +static void aic_pm_shutdown(struct irq_data *d) +{ + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + + irq_gc_lock(gc); + irq_reg_writel(0xffffffff, gc->reg_base + AT91_AIC_IDCR); + irq_reg_writel(0xffffffff, gc->reg_base + AT91_AIC_ICCR); + irq_gc_unlock(gc); +} + +static void aic5_pm_shutdown(struct irq_data *d) +{ + struct irq_domain *domain = d->domain; + struct irq_domain_chip_generic *dgc = domain->gc; + struct irq_chip_generic *bgc = dgc->gc[0]; + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + int i; + + irq_gc_lock(bgc); + for (i = 0; i < dgc->irqs_per_chip; i++) { + irq_reg_writel(i + gc->irq_base, + bgc->reg_base + AT91_AIC5_SSR); + irq_reg_writel(1, bgc->reg_base + AT91_AIC5_IDCR); + irq_reg_writel(1, bgc->reg_base + AT91_AIC5_ICCR); + } + irq_gc_unlock(bgc); +} +#else +#define aic_suspend NULL +#define aic5_suspend NULL +#define aic_resume NULL +#define aic5_resume NULL +#define aic_pm_shutdown NULL +#define aic5_pm_shutdown NULL +#endif /* CONFIG_PM */ + +static void __init aic_mux_hw_init(struct irq_domain *domain) +{ + struct aic_chip_data *aic = domain->host_data; + int i; + + for (i = 0; i < domain->revmap_size; i++) + aic_mux_disable_irqs(&aic[i / 32].mux[i % 32]); +} + +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(0, gc->reg_base + AT91_AIC_EOICR); + + /* + * 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(0xffffffff, gc->reg_base + AT91_AIC_SPU); + + /* No debugging in AIC: Debug (Protect) Control Register */ + irq_reg_writel(0, gc->reg_base + AT91_AIC_DCR); + + /* Disable and clear all interrupts initially */ + irq_reg_writel(0xffffffff, gc->reg_base + AT91_AIC_IDCR); + irq_reg_writel(0xffffffff, gc->reg_base + AT91_AIC_ICCR); + + for (i = 0; i < 32; i++) + irq_reg_writel(i, gc->reg_base + AT91_AIC_SVR(i)); + + aic_mux_hw_init(domain); +} + +static void __init aic5_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(0, gc->reg_base + AT91_AIC5_EOICR); + + /* + * 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(0xffffffff, gc->reg_base + AT91_AIC5_SPU); + + /* No debugging in AIC: Debug (Protect) Control Register */ + irq_reg_writel(0, gc->reg_base + AT91_AIC5_DCR); + + /* Disable and clear all interrupts initially */ + for (i = 0; i < domain->revmap_size; i++) { + irq_reg_writel(i, gc->reg_base + AT91_AIC5_SSR); + irq_reg_writel(i, gc->reg_base + AT91_AIC5_SVR); + irq_reg_writel(1, gc->reg_base + AT91_AIC5_IDCR); + irq_reg_writel(1, gc->reg_base + AT91_AIC5_ICCR); + } + + aic_mux_hw_init(domain); +} + +static int at91_aic_common_irq_domain_xlate(struct irq_domain *d, + struct device_node *ctrlr, + const u32 *intspec, + unsigned int intsize, + irq_hw_number_t *out_hwirq, + unsigned int *out_type) +{ + if (WARN_ON(intsize < 3)) + return -EINVAL; + + if (WARN_ON((intspec[2] < AT91_AIC_IRQ_MIN_PRIORITY) || + (intspec[2] > AT91_AIC_IRQ_MAX_PRIORITY))) + return -EINVAL; + + *out_hwirq = intspec[0]; + *out_type = intspec[1] & IRQ_TYPE_SENSE_MASK; + + return 0; +} + +static int aic_irq_domain_xlate(struct irq_domain *d, + struct device_node *ctrlr, + const u32 *intspec, unsigned int intsize, + irq_hw_number_t *out_hwirq, + unsigned int *out_type) +{ + struct irq_domain_chip_generic *dgc = d->gc; + struct irq_chip_generic *gc; + unsigned long smr; + int idx; + int ret; + + if (!dgc) + return -EINVAL; + + ret = at91_aic_common_irq_domain_xlate(d, ctrlr, intspec, intsize, + out_hwirq, out_type); + if (ret) + return ret; + + idx = intspec[0] / dgc->irqs_per_chip; + if (idx >= dgc->num_chips) + return -EINVAL; + + gc = dgc->gc[idx]; + + irq_gc_lock(gc); + smr = irq_reg_readl(gc->reg_base + AT91_AIC5_SMR) & ~AT91_AIC_PRIOR; + irq_reg_writel(intspec[2] | smr, gc->reg_base + AT91_AIC5_SMR); + 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 int aic5_irq_domain_xlate(struct irq_domain *d, + struct device_node *ctrlr, + const u32 *intspec, unsigned int intsize, + irq_hw_number_t *out_hwirq, + unsigned int *out_type) +{ + struct irq_domain_chip_generic *dgc = d->gc; + struct irq_chip_generic *gc; + unsigned long smr; + int ret; + + if (!dgc) + return -EINVAL; + + ret = at91_aic_common_irq_domain_xlate(d, ctrlr, intspec, intsize, + out_hwirq, out_type); + if (ret) + return ret; + + gc = dgc->gc[0]; + + irq_gc_lock(gc); + irq_reg_writel(*out_hwirq, gc->reg_base + AT91_AIC5_SSR); + smr = irq_reg_readl(gc->reg_base + AT91_AIC5_SMR) & ~AT91_AIC_PRIOR; + irq_reg_writel(intspec[2] | smr, gc->reg_base + AT91_AIC5_SMR); + irq_gc_unlock(gc); + + return 0; +} + +static const struct irq_domain_ops aic5_irq_ops = { + .map = irq_map_generic_chip, + .xlate = aic5_irq_domain_xlate, +}; + +static struct aic_mux_irq *aic_mux_irq_of_init(struct device_node *node) +{ + struct aic_mux_irq *irq; + void __iomem *regs; + u32 mask; + int ret; + + ret = of_property_read_u32(node, "atmel,aic-mux-reg-mask", &mask); + if (ret) + return ERR_PTR(-EINVAL); + + regs = of_iomap(node, 0); + if (!regs) + return ERR_PTR(-EINVAL); + + irq = kzalloc(sizeof(*irq), GFP_KERNEL); + if (!irq) + return ERR_PTR(-ENOMEM); + + INIT_LIST_HEAD(&irq->node); + + irq->regs = regs; + irq->mask = mask; + + return irq; +} + +static struct aic_mux_irq *aic_mux_1reg_irq_of_init(struct device_node *node) +{ + struct aic_mux_irq *irq; + + irq = aic_mux_irq_of_init(node); + if (!IS_ERR(irq)) + irq->type = AIC_MUX_1REG_IRQ; + + return irq; +} + +static struct aic_mux_irq *aic_mux_3reg_irq_of_init(struct device_node *node) +{ + struct aic_mux_irq *irq; + + irq = aic_mux_irq_of_init(node); + if (!IS_ERR(irq)) + irq->type = AIC_MUX_3REG_IRQ; + + return irq; +} + +static const struct of_device_id aic_mux_irq_of_match[] __initconst = { + { + .compatible = "atmel,aic-mux-1reg-irq", + .data = aic_mux_1reg_irq_of_init, + }, + { + .compatible = "atmel,aic-mux-3reg-irq", + .data = aic_mux_3reg_irq_of_init, + }, + { /*sentinel*/ } +}; + +static void __init aic_ext_irq_of_init(struct irq_domain *domain) +{ + struct device_node *node = domain->of_node; + struct irq_chip_generic *gc; + struct aic_chip_data *aic; + struct property *prop; + const __be32 *p; + u32 hwirq; + + gc = irq_get_domain_generic_chip(aic_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(aic_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 % 32)); + } +} + +static void __init aic_mux_of_init(struct irq_domain *domain) +{ + struct device_node *node = domain->of_node; + struct device_node *mux_node; + const struct of_device_id *match; + struct aic_mux_irq *irq; + struct aic_mux_irq * (*mux_of_init)(struct device_node *); + struct irq_chip_generic *gc; + struct aic_chip_data *aic; + struct list_head *mux_list; + u32 hwirq; + + for_each_child_of_node(node, mux_node) { + match = of_match_node(aic_mux_irq_of_match, mux_node); + if (!match) + continue; + + if (of_property_read_u32(mux_node, "reg", &hwirq)) { + pr_warn("AIC: missing reg property in mux definition\n"); + continue; + } + + gc = irq_get_domain_generic_chip(aic_domain, hwirq); + if (!gc) { + pr_warn("AIC: irq %d >= %d skip it\n", + hwirq, domain->revmap_size); + continue; + } + + aic = gc->private; + mux_list = &aic->mux[hwirq % 32]; + + mux_of_init = match->data; + + irq = mux_of_init(mux_node); + if (IS_ERR(irq)) + continue; + + list_add_tail(&irq->node, mux_list); + } +} + +static int __init aic_common_of_init(struct device_node *node, + const struct irq_domain_ops *ops, + const char *name, int maxirq) +{ + struct irq_chip_generic *gc; + struct aic_chip_data *aic; + void __iomem *reg_base; + int nirqs = maxirq; + int nchips; + int ret; + int i; + int j; + u32 tmp; + + if (aic_domain) + return -EEXIST; + + if (of_get_property(node, "atmel,aic-irq-mapping", &tmp)) + nirqs = tmp * BITS_PER_BYTE; + + nchips = DIV_ROUND_UP(nirqs, 32); + + reg_base = of_iomap(node, 0); + if (!reg_base) + return -ENOMEM; + + aic = kzalloc(nchips * sizeof(*aic), GFP_KERNEL); + if (!aic) { + ret = -ENOMEM; + goto err_iounmap; + } + + aic_domain = irq_domain_add_linear(node, nirqs, ops, aic); + if (!aic_domain) { + ret = -ENOMEM; + goto err_free_aic; + } + + ret = irq_alloc_domain_generic_chips(aic_domain, 32, 1, name, + handle_level_irq, 0, 0, + IRQCHIP_SKIP_SET_WAKE); + if (ret) + goto err_domain_remove; + + for (i = 0; i < nchips; i++) { + gc = irq_get_domain_generic_chip(aic_domain, i * 32); + + gc->reg_base = reg_base; + + if (!of_property_read_u32_index(node, "atmel,irq-mapping", + i, &tmp)) { + gc->unused = ~tmp; + gc->wake_enabled = tmp; + } else { + gc->unused = 0; + gc->wake_enabled = ~0; + } + + gc->chip_types[0].type = IRQ_TYPE_SENSE_MASK; + gc->chip_types[0].handler = handle_fasteoi_irq; + 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_shutdown; + + for (j = 0; j < 32; j++) + INIT_LIST_HEAD(&aic[i].mux[j]); + + gc->private = &aic[i]; + } + + aic_mux_of_init(aic_domain); + aic_ext_irq_of_init(aic_domain); + + return 0; + +err_domain_remove: + irq_domain_remove(aic_domain); + +err_free_aic: + kfree(aic); + +err_iounmap: + iounmap(reg_base); + + return ret; +} + +static int __init aic_of_init(struct device_node *node, + struct device_node *parent) +{ + struct irq_chip_generic *gc; + int ret; + + ret = aic_common_of_init(node, &aic_irq_ops, "atmel-aic", + NR_AIC_IRQS); + if (ret) + return ret; + + gc = irq_get_domain_generic_chip(aic_domain, 0); + + gc->chip_types[0].regs.eoi = AT91_AIC_EOICR; + gc->chip_types[0].regs.enable = AT91_AIC_IECR; + gc->chip_types[0].regs.disable = AT91_AIC_IDCR; + gc->chip_types[0].chip.irq_mask = irq_gc_mask_disable_reg; + gc->chip_types[0].chip.irq_unmask = irq_gc_unmask_enable_reg; + 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; + + aic_hw_init(aic_domain); + set_handle_irq(aic_handle); + + return 0; +} +IRQCHIP_DECLARE(at91_aic, "atmel,at91rm9200-aic", aic_of_init); + +static int __init aic5_of_init(struct device_node *node, + struct device_node *parent) +{ + struct irq_chip_generic *gc; + int ret; + int i; + int nchips; + + ret = aic_common_of_init(node, &aic5_irq_ops, "atmel-aic5", + NR_AIC5_IRQS); + if (ret) + return ret; + + nchips = aic_domain->revmap_size / 32; + for (i = 0; i < nchips; i++) { + gc = irq_get_domain_generic_chip(aic_domain, i * 32); + + gc->chip_types[0].regs.eoi = AT91_AIC5_EOICR; + gc->chip_types[0].chip.irq_mask = aic5_mask; + gc->chip_types[0].chip.irq_unmask = aic5_unmask; + gc->chip_types[0].chip.irq_retrigger = aic5_retrigger; + gc->chip_types[0].chip.irq_set_type = aic5_set_type; + gc->chip_types[0].chip.irq_suspend = aic5_suspend; + gc->chip_types[0].chip.irq_resume = aic5_resume; + gc->chip_types[0].chip.irq_pm_shutdown = aic5_pm_shutdown; + } + + aic5_hw_init(aic_domain); + set_handle_irq(aic5_handle); + + return 0; +} +IRQCHIP_DECLARE(at91_aic5, "atmel,sama5d3-aic", aic5_of_init);