162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci// Copyright 2021 Jonathan Neuschäfer 362306a36Sopenharmony_ci 462306a36Sopenharmony_ci#include <linux/irqchip.h> 562306a36Sopenharmony_ci#include <linux/of_address.h> 662306a36Sopenharmony_ci#include <linux/of_irq.h> 762306a36Sopenharmony_ci#include <linux/printk.h> 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#include <asm/exception.h> 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci#define AIC_SCR(x) ((x)*4) /* Source control registers */ 1262306a36Sopenharmony_ci#define AIC_GEN 0x84 /* Interrupt group enable control register */ 1362306a36Sopenharmony_ci#define AIC_GRSR 0x88 /* Interrupt group raw status register */ 1462306a36Sopenharmony_ci#define AIC_IRSR 0x100 /* Interrupt raw status register */ 1562306a36Sopenharmony_ci#define AIC_IASR 0x104 /* Interrupt active status register */ 1662306a36Sopenharmony_ci#define AIC_ISR 0x108 /* Interrupt status register */ 1762306a36Sopenharmony_ci#define AIC_IPER 0x10c /* Interrupt priority encoding register */ 1862306a36Sopenharmony_ci#define AIC_ISNR 0x110 /* Interrupt source number register */ 1962306a36Sopenharmony_ci#define AIC_IMR 0x114 /* Interrupt mask register */ 2062306a36Sopenharmony_ci#define AIC_OISR 0x118 /* Output interrupt status register */ 2162306a36Sopenharmony_ci#define AIC_MECR 0x120 /* Mask enable command register */ 2262306a36Sopenharmony_ci#define AIC_MDCR 0x124 /* Mask disable command register */ 2362306a36Sopenharmony_ci#define AIC_SSCR 0x128 /* Source set command register */ 2462306a36Sopenharmony_ci#define AIC_SCCR 0x12c /* Source clear command register */ 2562306a36Sopenharmony_ci#define AIC_EOSCR 0x130 /* End of service command register */ 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_ci#define AIC_SCR_SRCTYPE_LOW_LEVEL (0 << 6) 2862306a36Sopenharmony_ci#define AIC_SCR_SRCTYPE_HIGH_LEVEL (1 << 6) 2962306a36Sopenharmony_ci#define AIC_SCR_SRCTYPE_NEG_EDGE (2 << 6) 3062306a36Sopenharmony_ci#define AIC_SCR_SRCTYPE_POS_EDGE (3 << 6) 3162306a36Sopenharmony_ci#define AIC_SCR_PRIORITY(x) (x) 3262306a36Sopenharmony_ci#define AIC_SCR_PRIORITY_MASK 0x7 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci#define AIC_NUM_IRQS 32 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_cistruct wpcm450_aic { 3762306a36Sopenharmony_ci void __iomem *regs; 3862306a36Sopenharmony_ci struct irq_domain *domain; 3962306a36Sopenharmony_ci}; 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_cistatic struct wpcm450_aic *aic; 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_cistatic void wpcm450_aic_init_hw(void) 4462306a36Sopenharmony_ci{ 4562306a36Sopenharmony_ci int i; 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_ci /* Disable (mask) all interrupts */ 4862306a36Sopenharmony_ci writel(0xffffffff, aic->regs + AIC_MDCR); 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_ci /* 5162306a36Sopenharmony_ci * Make sure the interrupt controller is ready to serve new interrupts. 5262306a36Sopenharmony_ci * Reading from IPER indicates that the nIRQ signal may be deasserted, 5362306a36Sopenharmony_ci * and writing to EOSCR indicates that interrupt handling has finished. 5462306a36Sopenharmony_ci */ 5562306a36Sopenharmony_ci readl(aic->regs + AIC_IPER); 5662306a36Sopenharmony_ci writel(0, aic->regs + AIC_EOSCR); 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci /* Initialize trigger mode and priority of each interrupt source */ 5962306a36Sopenharmony_ci for (i = 0; i < AIC_NUM_IRQS; i++) 6062306a36Sopenharmony_ci writel(AIC_SCR_SRCTYPE_HIGH_LEVEL | AIC_SCR_PRIORITY(7), 6162306a36Sopenharmony_ci aic->regs + AIC_SCR(i)); 6262306a36Sopenharmony_ci} 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_cistatic void __exception_irq_entry wpcm450_aic_handle_irq(struct pt_regs *regs) 6562306a36Sopenharmony_ci{ 6662306a36Sopenharmony_ci int hwirq; 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci /* Determine the interrupt source */ 6962306a36Sopenharmony_ci /* Read IPER to signal that nIRQ can be de-asserted */ 7062306a36Sopenharmony_ci hwirq = readl(aic->regs + AIC_IPER) / 4; 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ci generic_handle_domain_irq(aic->domain, hwirq); 7362306a36Sopenharmony_ci} 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_cistatic void wpcm450_aic_eoi(struct irq_data *d) 7662306a36Sopenharmony_ci{ 7762306a36Sopenharmony_ci /* Signal end-of-service */ 7862306a36Sopenharmony_ci writel(0, aic->regs + AIC_EOSCR); 7962306a36Sopenharmony_ci} 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_cistatic void wpcm450_aic_mask(struct irq_data *d) 8262306a36Sopenharmony_ci{ 8362306a36Sopenharmony_ci unsigned int mask = BIT(d->hwirq); 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci /* Disable (mask) the interrupt */ 8662306a36Sopenharmony_ci writel(mask, aic->regs + AIC_MDCR); 8762306a36Sopenharmony_ci} 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_cistatic void wpcm450_aic_unmask(struct irq_data *d) 9062306a36Sopenharmony_ci{ 9162306a36Sopenharmony_ci unsigned int mask = BIT(d->hwirq); 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci /* Enable (unmask) the interrupt */ 9462306a36Sopenharmony_ci writel(mask, aic->regs + AIC_MECR); 9562306a36Sopenharmony_ci} 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_cistatic int wpcm450_aic_set_type(struct irq_data *d, unsigned int flow_type) 9862306a36Sopenharmony_ci{ 9962306a36Sopenharmony_ci /* 10062306a36Sopenharmony_ci * The hardware supports high/low level, as well as rising/falling edge 10162306a36Sopenharmony_ci * modes, and the DT binding accommodates for that, but as long as 10262306a36Sopenharmony_ci * other modes than high level mode are not used and can't be tested, 10362306a36Sopenharmony_ci * they are rejected in this driver. 10462306a36Sopenharmony_ci */ 10562306a36Sopenharmony_ci if ((flow_type & IRQ_TYPE_SENSE_MASK) != IRQ_TYPE_LEVEL_HIGH) 10662306a36Sopenharmony_ci return -EINVAL; 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci return 0; 10962306a36Sopenharmony_ci} 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_cistatic struct irq_chip wpcm450_aic_chip = { 11262306a36Sopenharmony_ci .name = "wpcm450-aic", 11362306a36Sopenharmony_ci .irq_eoi = wpcm450_aic_eoi, 11462306a36Sopenharmony_ci .irq_mask = wpcm450_aic_mask, 11562306a36Sopenharmony_ci .irq_unmask = wpcm450_aic_unmask, 11662306a36Sopenharmony_ci .irq_set_type = wpcm450_aic_set_type, 11762306a36Sopenharmony_ci}; 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_cistatic int wpcm450_aic_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hwirq) 12062306a36Sopenharmony_ci{ 12162306a36Sopenharmony_ci if (hwirq >= AIC_NUM_IRQS) 12262306a36Sopenharmony_ci return -EPERM; 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ci irq_set_chip_and_handler(irq, &wpcm450_aic_chip, handle_fasteoi_irq); 12562306a36Sopenharmony_ci irq_set_chip_data(irq, aic); 12662306a36Sopenharmony_ci irq_set_probe(irq); 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci return 0; 12962306a36Sopenharmony_ci} 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_cistatic const struct irq_domain_ops wpcm450_aic_ops = { 13262306a36Sopenharmony_ci .map = wpcm450_aic_map, 13362306a36Sopenharmony_ci .xlate = irq_domain_xlate_twocell, 13462306a36Sopenharmony_ci}; 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_cistatic int __init wpcm450_aic_of_init(struct device_node *node, 13762306a36Sopenharmony_ci struct device_node *parent) 13862306a36Sopenharmony_ci{ 13962306a36Sopenharmony_ci if (parent) 14062306a36Sopenharmony_ci return -EINVAL; 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci aic = kzalloc(sizeof(*aic), GFP_KERNEL); 14362306a36Sopenharmony_ci if (!aic) 14462306a36Sopenharmony_ci return -ENOMEM; 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci aic->regs = of_iomap(node, 0); 14762306a36Sopenharmony_ci if (!aic->regs) { 14862306a36Sopenharmony_ci pr_err("Failed to map WPCM450 AIC registers\n"); 14962306a36Sopenharmony_ci kfree(aic); 15062306a36Sopenharmony_ci return -ENOMEM; 15162306a36Sopenharmony_ci } 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci wpcm450_aic_init_hw(); 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci set_handle_irq(wpcm450_aic_handle_irq); 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci aic->domain = irq_domain_add_linear(node, AIC_NUM_IRQS, &wpcm450_aic_ops, aic); 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci return 0; 16062306a36Sopenharmony_ci} 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ciIRQCHIP_DECLARE(wpcm450_aic, "nuvoton,wpcm450-aic", wpcm450_aic_of_init); 163