162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. 462306a36Sopenharmony_ci * Copyright 2008 Juergen Beisert, kernel@pengutronix.de 562306a36Sopenharmony_ci */ 662306a36Sopenharmony_ci 762306a36Sopenharmony_ci#include <linux/module.h> 862306a36Sopenharmony_ci#include <linux/irq.h> 962306a36Sopenharmony_ci#include <linux/irqdomain.h> 1062306a36Sopenharmony_ci#include <linux/irqchip.h> 1162306a36Sopenharmony_ci#include <linux/io.h> 1262306a36Sopenharmony_ci#include <linux/of.h> 1362306a36Sopenharmony_ci#include <linux/of_address.h> 1462306a36Sopenharmony_ci#include <asm/mach/irq.h> 1562306a36Sopenharmony_ci#include <asm/exception.h> 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci#include "common.h" 1862306a36Sopenharmony_ci#include "hardware.h" 1962306a36Sopenharmony_ci#include "irq-common.h" 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ci#define AVIC_INTCNTL 0x00 /* int control reg */ 2262306a36Sopenharmony_ci#define AVIC_NIMASK 0x04 /* int mask reg */ 2362306a36Sopenharmony_ci#define AVIC_INTENNUM 0x08 /* int enable number reg */ 2462306a36Sopenharmony_ci#define AVIC_INTDISNUM 0x0C /* int disable number reg */ 2562306a36Sopenharmony_ci#define AVIC_INTENABLEH 0x10 /* int enable reg high */ 2662306a36Sopenharmony_ci#define AVIC_INTENABLEL 0x14 /* int enable reg low */ 2762306a36Sopenharmony_ci#define AVIC_INTTYPEH 0x18 /* int type reg high */ 2862306a36Sopenharmony_ci#define AVIC_INTTYPEL 0x1C /* int type reg low */ 2962306a36Sopenharmony_ci#define AVIC_NIPRIORITY(x) (0x20 + 4 * (7 - (x))) /* int priority */ 3062306a36Sopenharmony_ci#define AVIC_NIVECSR 0x40 /* norm int vector/status */ 3162306a36Sopenharmony_ci#define AVIC_FIVECSR 0x44 /* fast int vector/status */ 3262306a36Sopenharmony_ci#define AVIC_INTSRCH 0x48 /* int source reg high */ 3362306a36Sopenharmony_ci#define AVIC_INTSRCL 0x4C /* int source reg low */ 3462306a36Sopenharmony_ci#define AVIC_INTFRCH 0x50 /* int force reg high */ 3562306a36Sopenharmony_ci#define AVIC_INTFRCL 0x54 /* int force reg low */ 3662306a36Sopenharmony_ci#define AVIC_NIPNDH 0x58 /* norm int pending high */ 3762306a36Sopenharmony_ci#define AVIC_NIPNDL 0x5C /* norm int pending low */ 3862306a36Sopenharmony_ci#define AVIC_FIPNDH 0x60 /* fast int pending high */ 3962306a36Sopenharmony_ci#define AVIC_FIPNDL 0x64 /* fast int pending low */ 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_ci#define AVIC_NUM_IRQS 64 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_ci/* low power interrupt mask registers */ 4462306a36Sopenharmony_ci#define MX25_CCM_LPIMR0 0x68 4562306a36Sopenharmony_ci#define MX25_CCM_LPIMR1 0x6C 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_cistatic void __iomem *avic_base; 4862306a36Sopenharmony_cistatic void __iomem *mx25_ccm_base; 4962306a36Sopenharmony_cistatic struct irq_domain *domain; 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_ci#ifdef CONFIG_FIQ 5262306a36Sopenharmony_cistatic int avic_set_irq_fiq(unsigned int hwirq, unsigned int type) 5362306a36Sopenharmony_ci{ 5462306a36Sopenharmony_ci unsigned int irqt; 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_ci if (hwirq >= AVIC_NUM_IRQS) 5762306a36Sopenharmony_ci return -EINVAL; 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci if (hwirq < AVIC_NUM_IRQS / 2) { 6062306a36Sopenharmony_ci irqt = imx_readl(avic_base + AVIC_INTTYPEL) & ~(1 << hwirq); 6162306a36Sopenharmony_ci imx_writel(irqt | (!!type << hwirq), avic_base + AVIC_INTTYPEL); 6262306a36Sopenharmony_ci } else { 6362306a36Sopenharmony_ci hwirq -= AVIC_NUM_IRQS / 2; 6462306a36Sopenharmony_ci irqt = imx_readl(avic_base + AVIC_INTTYPEH) & ~(1 << hwirq); 6562306a36Sopenharmony_ci imx_writel(irqt | (!!type << hwirq), avic_base + AVIC_INTTYPEH); 6662306a36Sopenharmony_ci } 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci return 0; 6962306a36Sopenharmony_ci} 7062306a36Sopenharmony_ci#endif /* CONFIG_FIQ */ 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_cistatic struct mxc_extra_irq avic_extra_irq = { 7462306a36Sopenharmony_ci#ifdef CONFIG_FIQ 7562306a36Sopenharmony_ci .set_irq_fiq = avic_set_irq_fiq, 7662306a36Sopenharmony_ci#endif 7762306a36Sopenharmony_ci}; 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci#ifdef CONFIG_PM 8062306a36Sopenharmony_cistatic u32 avic_saved_mask_reg[2]; 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_cistatic void avic_irq_suspend(struct irq_data *d) 8362306a36Sopenharmony_ci{ 8462306a36Sopenharmony_ci struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); 8562306a36Sopenharmony_ci struct irq_chip_type *ct = gc->chip_types; 8662306a36Sopenharmony_ci int idx = d->hwirq >> 5; 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci avic_saved_mask_reg[idx] = imx_readl(avic_base + ct->regs.mask); 8962306a36Sopenharmony_ci imx_writel(gc->wake_active, avic_base + ct->regs.mask); 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci if (mx25_ccm_base) { 9262306a36Sopenharmony_ci u8 offs = d->hwirq < AVIC_NUM_IRQS / 2 ? 9362306a36Sopenharmony_ci MX25_CCM_LPIMR0 : MX25_CCM_LPIMR1; 9462306a36Sopenharmony_ci /* 9562306a36Sopenharmony_ci * The interrupts which are still enabled will be used as wakeup 9662306a36Sopenharmony_ci * sources. Allow those interrupts in low-power mode. 9762306a36Sopenharmony_ci * The LPIMR registers use 0 to allow an interrupt, the AVIC 9862306a36Sopenharmony_ci * registers use 1. 9962306a36Sopenharmony_ci */ 10062306a36Sopenharmony_ci imx_writel(~gc->wake_active, mx25_ccm_base + offs); 10162306a36Sopenharmony_ci } 10262306a36Sopenharmony_ci} 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_cistatic void avic_irq_resume(struct irq_data *d) 10562306a36Sopenharmony_ci{ 10662306a36Sopenharmony_ci struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); 10762306a36Sopenharmony_ci struct irq_chip_type *ct = gc->chip_types; 10862306a36Sopenharmony_ci int idx = d->hwirq >> 5; 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci imx_writel(avic_saved_mask_reg[idx], avic_base + ct->regs.mask); 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ci if (mx25_ccm_base) { 11362306a36Sopenharmony_ci u8 offs = d->hwirq < AVIC_NUM_IRQS / 2 ? 11462306a36Sopenharmony_ci MX25_CCM_LPIMR0 : MX25_CCM_LPIMR1; 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci imx_writel(0xffffffff, mx25_ccm_base + offs); 11762306a36Sopenharmony_ci } 11862306a36Sopenharmony_ci} 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci#else 12162306a36Sopenharmony_ci#define avic_irq_suspend NULL 12262306a36Sopenharmony_ci#define avic_irq_resume NULL 12362306a36Sopenharmony_ci#endif 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_cistatic __init void avic_init_gc(int idx, unsigned int irq_start) 12662306a36Sopenharmony_ci{ 12762306a36Sopenharmony_ci struct irq_chip_generic *gc; 12862306a36Sopenharmony_ci struct irq_chip_type *ct; 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci gc = irq_alloc_generic_chip("mxc-avic", 1, irq_start, avic_base, 13162306a36Sopenharmony_ci handle_level_irq); 13262306a36Sopenharmony_ci gc->private = &avic_extra_irq; 13362306a36Sopenharmony_ci gc->wake_enabled = IRQ_MSK(32); 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci ct = gc->chip_types; 13662306a36Sopenharmony_ci ct->chip.irq_mask = irq_gc_mask_clr_bit; 13762306a36Sopenharmony_ci ct->chip.irq_unmask = irq_gc_mask_set_bit; 13862306a36Sopenharmony_ci ct->chip.irq_ack = irq_gc_mask_clr_bit; 13962306a36Sopenharmony_ci ct->chip.irq_set_wake = irq_gc_set_wake; 14062306a36Sopenharmony_ci ct->chip.irq_suspend = avic_irq_suspend; 14162306a36Sopenharmony_ci ct->chip.irq_resume = avic_irq_resume; 14262306a36Sopenharmony_ci ct->regs.mask = !idx ? AVIC_INTENABLEL : AVIC_INTENABLEH; 14362306a36Sopenharmony_ci ct->regs.ack = ct->regs.mask; 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ci irq_setup_generic_chip(gc, IRQ_MSK(32), 0, IRQ_NOREQUEST, 0); 14662306a36Sopenharmony_ci} 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_cistatic void __exception_irq_entry avic_handle_irq(struct pt_regs *regs) 14962306a36Sopenharmony_ci{ 15062306a36Sopenharmony_ci u32 nivector; 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ci do { 15362306a36Sopenharmony_ci nivector = imx_readl(avic_base + AVIC_NIVECSR) >> 16; 15462306a36Sopenharmony_ci if (nivector == 0xffff) 15562306a36Sopenharmony_ci break; 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci generic_handle_domain_irq(domain, nivector); 15862306a36Sopenharmony_ci } while (1); 15962306a36Sopenharmony_ci} 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci/* 16262306a36Sopenharmony_ci * This function initializes the AVIC hardware and disables all the 16362306a36Sopenharmony_ci * interrupts. It registers the interrupt enable and disable functions 16462306a36Sopenharmony_ci * to the kernel for each interrupt source. 16562306a36Sopenharmony_ci */ 16662306a36Sopenharmony_cistatic void __init mxc_init_irq(void __iomem *irqbase) 16762306a36Sopenharmony_ci{ 16862306a36Sopenharmony_ci struct device_node *np; 16962306a36Sopenharmony_ci int irq_base; 17062306a36Sopenharmony_ci int i; 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci avic_base = irqbase; 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci np = of_find_compatible_node(NULL, NULL, "fsl,imx25-ccm"); 17562306a36Sopenharmony_ci mx25_ccm_base = of_iomap(np, 0); 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci if (mx25_ccm_base) { 17862306a36Sopenharmony_ci /* 17962306a36Sopenharmony_ci * By default, we mask all interrupts. We set the actual mask 18062306a36Sopenharmony_ci * before we go into low-power mode. 18162306a36Sopenharmony_ci */ 18262306a36Sopenharmony_ci imx_writel(0xffffffff, mx25_ccm_base + MX25_CCM_LPIMR0); 18362306a36Sopenharmony_ci imx_writel(0xffffffff, mx25_ccm_base + MX25_CCM_LPIMR1); 18462306a36Sopenharmony_ci } 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci /* put the AVIC into the reset value with 18762306a36Sopenharmony_ci * all interrupts disabled 18862306a36Sopenharmony_ci */ 18962306a36Sopenharmony_ci imx_writel(0, avic_base + AVIC_INTCNTL); 19062306a36Sopenharmony_ci imx_writel(0x1f, avic_base + AVIC_NIMASK); 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_ci /* disable all interrupts */ 19362306a36Sopenharmony_ci imx_writel(0, avic_base + AVIC_INTENABLEH); 19462306a36Sopenharmony_ci imx_writel(0, avic_base + AVIC_INTENABLEL); 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci /* all IRQ no FIQ */ 19762306a36Sopenharmony_ci imx_writel(0, avic_base + AVIC_INTTYPEH); 19862306a36Sopenharmony_ci imx_writel(0, avic_base + AVIC_INTTYPEL); 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_ci irq_base = irq_alloc_descs(-1, 0, AVIC_NUM_IRQS, numa_node_id()); 20162306a36Sopenharmony_ci WARN_ON(irq_base < 0); 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_ci np = of_find_compatible_node(NULL, NULL, "fsl,avic"); 20462306a36Sopenharmony_ci domain = irq_domain_add_legacy(np, AVIC_NUM_IRQS, irq_base, 0, 20562306a36Sopenharmony_ci &irq_domain_simple_ops, NULL); 20662306a36Sopenharmony_ci WARN_ON(!domain); 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_ci for (i = 0; i < AVIC_NUM_IRQS / 32; i++, irq_base += 32) 20962306a36Sopenharmony_ci avic_init_gc(i, irq_base); 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci /* Set default priority value (0) for all IRQ's */ 21262306a36Sopenharmony_ci for (i = 0; i < 8; i++) 21362306a36Sopenharmony_ci imx_writel(0, avic_base + AVIC_NIPRIORITY(i)); 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_ci set_handle_irq(avic_handle_irq); 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci#ifdef CONFIG_FIQ 21862306a36Sopenharmony_ci /* Initialize FIQ */ 21962306a36Sopenharmony_ci init_FIQ(FIQ_START); 22062306a36Sopenharmony_ci#endif 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci printk(KERN_INFO "MXC IRQ initialized\n"); 22362306a36Sopenharmony_ci} 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_cistatic int __init imx_avic_init(struct device_node *node, 22662306a36Sopenharmony_ci struct device_node *parent) 22762306a36Sopenharmony_ci{ 22862306a36Sopenharmony_ci void __iomem *avic_base; 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_ci avic_base = of_iomap(node, 0); 23162306a36Sopenharmony_ci BUG_ON(!avic_base); 23262306a36Sopenharmony_ci mxc_init_irq(avic_base); 23362306a36Sopenharmony_ci return 0; 23462306a36Sopenharmony_ci} 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ciIRQCHIP_DECLARE(imx_avic, "fsl,avic", imx_avic_init); 237