|  | // SPDX-License-Identifier: GPL-2.0-or-later | 
|  | // | 
|  | // Copyright (C) 2006, 2019 Texas Instruments. | 
|  | // | 
|  | // Interrupt handler for DaVinci boards. | 
|  |  | 
|  | #include <linux/kernel.h> | 
|  | #include <linux/init.h> | 
|  | #include <linux/interrupt.h> | 
|  | #include <linux/irq.h> | 
|  | #include <linux/irqchip/irq-davinci-aintc.h> | 
|  | #include <linux/io.h> | 
|  | #include <linux/irqdomain.h> | 
|  |  | 
|  | #include <asm/exception.h> | 
|  |  | 
|  | #define DAVINCI_AINTC_FIQ_REG0		0x00 | 
|  | #define DAVINCI_AINTC_FIQ_REG1		0x04 | 
|  | #define DAVINCI_AINTC_IRQ_REG0		0x08 | 
|  | #define DAVINCI_AINTC_IRQ_REG1		0x0c | 
|  | #define DAVINCI_AINTC_IRQ_IRQENTRY	0x14 | 
|  | #define DAVINCI_AINTC_IRQ_ENT_REG0	0x18 | 
|  | #define DAVINCI_AINTC_IRQ_ENT_REG1	0x1c | 
|  | #define DAVINCI_AINTC_IRQ_INCTL_REG	0x20 | 
|  | #define DAVINCI_AINTC_IRQ_EABASE_REG	0x24 | 
|  | #define DAVINCI_AINTC_IRQ_INTPRI0_REG	0x30 | 
|  | #define DAVINCI_AINTC_IRQ_INTPRI7_REG	0x4c | 
|  |  | 
|  | static void __iomem *davinci_aintc_base; | 
|  | static struct irq_domain *davinci_aintc_irq_domain; | 
|  |  | 
|  | static inline void davinci_aintc_writel(unsigned long value, int offset) | 
|  | { | 
|  | writel_relaxed(value, davinci_aintc_base + offset); | 
|  | } | 
|  |  | 
|  | static inline unsigned long davinci_aintc_readl(int offset) | 
|  | { | 
|  | return readl_relaxed(davinci_aintc_base + offset); | 
|  | } | 
|  |  | 
|  | static __init void | 
|  | davinci_aintc_setup_gc(void __iomem *base, | 
|  | unsigned int irq_start, unsigned int num) | 
|  | { | 
|  | struct irq_chip_generic *gc; | 
|  | struct irq_chip_type *ct; | 
|  |  | 
|  | gc = irq_get_domain_generic_chip(davinci_aintc_irq_domain, irq_start); | 
|  | gc->reg_base = base; | 
|  | gc->irq_base = irq_start; | 
|  |  | 
|  | ct = gc->chip_types; | 
|  | ct->chip.irq_ack = irq_gc_ack_set_bit; | 
|  | ct->chip.irq_mask = irq_gc_mask_clr_bit; | 
|  | ct->chip.irq_unmask = irq_gc_mask_set_bit; | 
|  |  | 
|  | ct->regs.ack = DAVINCI_AINTC_IRQ_REG0; | 
|  | ct->regs.mask = DAVINCI_AINTC_IRQ_ENT_REG0; | 
|  | irq_setup_generic_chip(gc, IRQ_MSK(num), IRQ_GC_INIT_MASK_CACHE, | 
|  | IRQ_NOREQUEST | IRQ_NOPROBE, 0); | 
|  | } | 
|  |  | 
|  | static asmlinkage void __exception_irq_entry | 
|  | davinci_aintc_handle_irq(struct pt_regs *regs) | 
|  | { | 
|  | int irqnr = davinci_aintc_readl(DAVINCI_AINTC_IRQ_IRQENTRY); | 
|  |  | 
|  | /* | 
|  | * Use the formula for entry vector index generation from section | 
|  | * 8.3.3 of the manual. | 
|  | */ | 
|  | irqnr >>= 2; | 
|  | irqnr -= 1; | 
|  |  | 
|  | handle_domain_irq(davinci_aintc_irq_domain, irqnr, regs); | 
|  | } | 
|  |  | 
|  | /* ARM Interrupt Controller Initialization */ | 
|  | void __init davinci_aintc_init(const struct davinci_aintc_config *config) | 
|  | { | 
|  | unsigned int irq_off, reg_off, prio, shift; | 
|  | void __iomem *req; | 
|  | int ret, irq_base; | 
|  | const u8 *prios; | 
|  |  | 
|  | req = request_mem_region(config->reg.start, | 
|  | resource_size(&config->reg), | 
|  | "davinci-cp-intc"); | 
|  | if (!req) { | 
|  | pr_err("%s: register range busy\n", __func__); | 
|  | return; | 
|  | } | 
|  |  | 
|  | davinci_aintc_base = ioremap(config->reg.start, | 
|  | resource_size(&config->reg)); | 
|  | if (!davinci_aintc_base) { | 
|  | pr_err("%s: unable to ioremap register range\n", __func__); | 
|  | return; | 
|  | } | 
|  |  | 
|  | /* Clear all interrupt requests */ | 
|  | davinci_aintc_writel(~0x0, DAVINCI_AINTC_FIQ_REG0); | 
|  | davinci_aintc_writel(~0x0, DAVINCI_AINTC_FIQ_REG1); | 
|  | davinci_aintc_writel(~0x0, DAVINCI_AINTC_IRQ_REG0); | 
|  | davinci_aintc_writel(~0x0, DAVINCI_AINTC_IRQ_REG1); | 
|  |  | 
|  | /* Disable all interrupts */ | 
|  | davinci_aintc_writel(0x0, DAVINCI_AINTC_IRQ_ENT_REG0); | 
|  | davinci_aintc_writel(0x0, DAVINCI_AINTC_IRQ_ENT_REG1); | 
|  |  | 
|  | /* Interrupts disabled immediately, IRQ entry reflects all */ | 
|  | davinci_aintc_writel(0x0, DAVINCI_AINTC_IRQ_INCTL_REG); | 
|  |  | 
|  | /* we don't use the hardware vector table, just its entry addresses */ | 
|  | davinci_aintc_writel(0, DAVINCI_AINTC_IRQ_EABASE_REG); | 
|  |  | 
|  | /* Clear all interrupt requests */ | 
|  | davinci_aintc_writel(~0x0, DAVINCI_AINTC_FIQ_REG0); | 
|  | davinci_aintc_writel(~0x0, DAVINCI_AINTC_FIQ_REG1); | 
|  | davinci_aintc_writel(~0x0, DAVINCI_AINTC_IRQ_REG0); | 
|  | davinci_aintc_writel(~0x0, DAVINCI_AINTC_IRQ_REG1); | 
|  |  | 
|  | prios = config->prios; | 
|  | for (reg_off = DAVINCI_AINTC_IRQ_INTPRI0_REG; | 
|  | reg_off <= DAVINCI_AINTC_IRQ_INTPRI7_REG; reg_off += 4) { | 
|  | for (shift = 0, prio = 0; shift < 32; shift += 4, prios++) | 
|  | prio |= (*prios & 0x07) << shift; | 
|  | davinci_aintc_writel(prio, reg_off); | 
|  | } | 
|  |  | 
|  | irq_base = irq_alloc_descs(-1, 0, config->num_irqs, 0); | 
|  | if (irq_base < 0) { | 
|  | pr_err("%s: unable to allocate interrupt descriptors: %d\n", | 
|  | __func__, irq_base); | 
|  | return; | 
|  | } | 
|  |  | 
|  | davinci_aintc_irq_domain = irq_domain_add_legacy(NULL, | 
|  | config->num_irqs, irq_base, 0, | 
|  | &irq_domain_simple_ops, NULL); | 
|  | if (!davinci_aintc_irq_domain) { | 
|  | pr_err("%s: unable to create interrupt domain\n", __func__); | 
|  | return; | 
|  | } | 
|  |  | 
|  | ret = irq_alloc_domain_generic_chips(davinci_aintc_irq_domain, 32, 1, | 
|  | "AINTC", handle_edge_irq, | 
|  | IRQ_NOREQUEST | IRQ_NOPROBE, 0, 0); | 
|  | if (ret) { | 
|  | pr_err("%s: unable to allocate generic irq chips for domain\n", | 
|  | __func__); | 
|  | return; | 
|  | } | 
|  |  | 
|  | for (irq_off = 0, reg_off = 0; | 
|  | irq_off < config->num_irqs; | 
|  | irq_off += 32, reg_off += 0x04) | 
|  | davinci_aintc_setup_gc(davinci_aintc_base + reg_off, | 
|  | irq_base + irq_off, 32); | 
|  |  | 
|  | set_handle_irq(davinci_aintc_handle_irq); | 
|  | } |