150397Sobrien// SPDX-License-Identifier: GPL-2.0-only
250397Sobrien/*
350397Sobrien * drivers/irq/irq-nvic.c
450397Sobrien *
550397Sobrien * Copyright (C) 2008 ARM Limited, All Rights Reserved.
650397Sobrien * Copyright (C) 2013 Pengutronix
750397Sobrien *
850397Sobrien * Support for the Nested Vectored Interrupt Controller found on the
950397Sobrien * ARMv7-M CPUs (Cortex-M3/M4)
1050397Sobrien */
1150397Sobrien#define pr_fmt(fmt)	KBUILD_MODNAME ": " fmt
1250397Sobrien
1350397Sobrien#include <linux/init.h>
1450397Sobrien#include <linux/kernel.h>
1550397Sobrien#include <linux/slab.h>
1650397Sobrien#include <linux/err.h>
1750397Sobrien#include <linux/io.h>
1850397Sobrien#include <linux/of.h>
1950397Sobrien#include <linux/of_address.h>
2050397Sobrien#include <linux/irq.h>
2150397Sobrien#include <linux/irqchip.h>
2250397Sobrien#include <linux/irqdomain.h>
2350397Sobrien
2450397Sobrien#include <asm/v7m.h>
2550397Sobrien#include <asm/exception.h>
2650397Sobrien
2750397Sobrien#define NVIC_ISER		0x000
2850397Sobrien#define NVIC_ICER		0x080
2950397Sobrien#define NVIC_IPR		0x400
3050397Sobrien
3150397Sobrien#define NVIC_MAX_BANKS		16
3250397Sobrien/*
3350397Sobrien * Each bank handles 32 irqs. Only the 16th (= last) bank handles only
3450397Sobrien * 16 irqs.
3550397Sobrien */
3650397Sobrien#define NVIC_MAX_IRQ		((NVIC_MAX_BANKS - 1) * 32 + 16)
3750397Sobrien
3850397Sobrienstatic struct irq_domain *nvic_irq_domain;
3950397Sobrien
4050397Sobrienstatic void __irq_entry nvic_handle_irq(struct pt_regs *regs)
4150397Sobrien{
4250397Sobrien	unsigned long icsr = readl_relaxed(BASEADDR_V7M_SCB + V7M_SCB_ICSR);
4350397Sobrien	irq_hw_number_t hwirq = (icsr & V7M_SCB_ICSR_VECTACTIVE) - 16;
4450397Sobrien
4550397Sobrien	generic_handle_domain_irq(nvic_irq_domain, hwirq);
4650397Sobrien}
4750397Sobrien
4850397Sobrienstatic int nvic_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
4950397Sobrien				unsigned int nr_irqs, void *arg)
5050397Sobrien{
5150397Sobrien	int i, ret;
5250397Sobrien	irq_hw_number_t hwirq;
5350397Sobrien	unsigned int type = IRQ_TYPE_NONE;
5450397Sobrien	struct irq_fwspec *fwspec = arg;
5550397Sobrien
5650397Sobrien	ret = irq_domain_translate_onecell(domain, fwspec, &hwirq, &type);
5750397Sobrien	if (ret)
5850397Sobrien		return ret;
5950397Sobrien
6050397Sobrien	for (i = 0; i < nr_irqs; i++)
6150397Sobrien		irq_map_generic_chip(domain, virq + i, hwirq + i);
6250397Sobrien
6350397Sobrien	return 0;
6450397Sobrien}
6550397Sobrien
6650397Sobrienstatic const struct irq_domain_ops nvic_irq_domain_ops = {
6750397Sobrien	.translate = irq_domain_translate_onecell,
6850397Sobrien	.alloc = nvic_irq_domain_alloc,
6950397Sobrien	.free = irq_domain_free_irqs_top,
7050397Sobrien};
7150397Sobrien
7250397Sobrienstatic int __init nvic_of_init(struct device_node *node,
7350397Sobrien			       struct device_node *parent)
7450397Sobrien{
7550397Sobrien	unsigned int clr = IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_NOAUTOEN;
7650397Sobrien	unsigned int irqs, i, ret, numbanks;
7750397Sobrien	void __iomem *nvic_base;
7850397Sobrien
7950397Sobrien	numbanks = (readl_relaxed(V7M_SCS_ICTR) &
8050397Sobrien		    V7M_SCS_ICTR_INTLINESNUM_MASK) + 1;
8150397Sobrien
8250397Sobrien	nvic_base = of_iomap(node, 0);
8350397Sobrien	if (!nvic_base) {
8450397Sobrien		pr_warn("unable to map nvic registers\n");
8550397Sobrien		return -ENOMEM;
8650397Sobrien	}
8750397Sobrien
8850397Sobrien	irqs = numbanks * 32;
8950397Sobrien	if (irqs > NVIC_MAX_IRQ)
9050397Sobrien		irqs = NVIC_MAX_IRQ;
9150397Sobrien
9250397Sobrien	nvic_irq_domain =
9350397Sobrien		irq_domain_add_linear(node, irqs, &nvic_irq_domain_ops, NULL);
9450397Sobrien
9550397Sobrien	if (!nvic_irq_domain) {
9650397Sobrien		pr_warn("Failed to allocate irq domain\n");
9750397Sobrien		iounmap(nvic_base);
9850397Sobrien		return -ENOMEM;
9950397Sobrien	}
10050397Sobrien
10150397Sobrien	ret = irq_alloc_domain_generic_chips(nvic_irq_domain, 32, 1,
10250397Sobrien					     "nvic_irq", handle_fasteoi_irq,
10350397Sobrien					     clr, 0, IRQ_GC_INIT_MASK_CACHE);
10450397Sobrien	if (ret) {
10550397Sobrien		pr_warn("Failed to allocate irq chips\n");
10650397Sobrien		irq_domain_remove(nvic_irq_domain);
10750397Sobrien		iounmap(nvic_base);
10850397Sobrien		return ret;
10950397Sobrien	}
11050397Sobrien
11150397Sobrien	for (i = 0; i < numbanks; ++i) {
11250397Sobrien		struct irq_chip_generic *gc;
11350397Sobrien
11450397Sobrien		gc = irq_get_domain_generic_chip(nvic_irq_domain, 32 * i);
11550397Sobrien		gc->reg_base = nvic_base + 4 * i;
11650397Sobrien		gc->chip_types[0].regs.enable = NVIC_ISER;
11750397Sobrien		gc->chip_types[0].regs.disable = NVIC_ICER;
11850397Sobrien		gc->chip_types[0].chip.irq_mask = irq_gc_mask_disable_reg;
11950397Sobrien		gc->chip_types[0].chip.irq_unmask = irq_gc_unmask_enable_reg;
12050397Sobrien		/* This is a no-op as end of interrupt is signaled by the
12150397Sobrien		 * exception return sequence.
12250397Sobrien		 */
12350397Sobrien		gc->chip_types[0].chip.irq_eoi = irq_gc_noop;
12450397Sobrien
12550397Sobrien		/* disable interrupts */
12650397Sobrien		writel_relaxed(~0, gc->reg_base + NVIC_ICER);
12750397Sobrien	}
12850397Sobrien
12950397Sobrien	/* Set priority on all interrupts */
13050397Sobrien	for (i = 0; i < irqs; i += 4)
13150397Sobrien		writel_relaxed(0, nvic_base + NVIC_IPR + i);
13250397Sobrien
13350397Sobrien	set_handle_irq(nvic_handle_irq);
13450397Sobrien	return 0;
13550397Sobrien}
13650397SobrienIRQCHIP_DECLARE(armv7m_nvic, "arm,armv7m-nvic", nvic_of_init);
13750397Sobrien