xref: /linux/drivers/irqchip/irq-sun4i.c (revision e9df9e221665d40928e25a02c2700ac12eda7270)
1d7fbc6caSMaxime Ripard /*
2d7fbc6caSMaxime Ripard  * Allwinner A1X SoCs IRQ chip driver.
3d7fbc6caSMaxime Ripard  *
4d7fbc6caSMaxime Ripard  * Copyright (C) 2012 Maxime Ripard
5d7fbc6caSMaxime Ripard  *
6d7fbc6caSMaxime Ripard  * Maxime Ripard <maxime.ripard@free-electrons.com>
7d7fbc6caSMaxime Ripard  *
8d7fbc6caSMaxime Ripard  * Based on code from
9d7fbc6caSMaxime Ripard  * Allwinner Technology Co., Ltd. <www.allwinnertech.com>
10d7fbc6caSMaxime Ripard  * Benn Huang <benn@allwinnertech.com>
11d7fbc6caSMaxime Ripard  *
12d7fbc6caSMaxime Ripard  * This file is licensed under the terms of the GNU General Public
13d7fbc6caSMaxime Ripard  * License version 2.  This program is licensed "as is" without any
14d7fbc6caSMaxime Ripard  * warranty of any kind, whether express or implied.
15d7fbc6caSMaxime Ripard  */
16d7fbc6caSMaxime Ripard 
17d7fbc6caSMaxime Ripard #include <linux/io.h>
18d7fbc6caSMaxime Ripard #include <linux/irq.h>
19d7fbc6caSMaxime Ripard #include <linux/of.h>
20d7fbc6caSMaxime Ripard #include <linux/of_address.h>
21d7fbc6caSMaxime Ripard #include <linux/of_irq.h>
22d7fbc6caSMaxime Ripard 
23d7fbc6caSMaxime Ripard #include <asm/exception.h>
24d7fbc6caSMaxime Ripard #include <asm/mach/irq.h>
25d7fbc6caSMaxime Ripard 
26d7fbc6caSMaxime Ripard #include "irqchip.h"
27d7fbc6caSMaxime Ripard 
28d7fbc6caSMaxime Ripard #define SUN4I_IRQ_VECTOR_REG		0x00
29d7fbc6caSMaxime Ripard #define SUN4I_IRQ_PROTECTION_REG	0x08
30d7fbc6caSMaxime Ripard #define SUN4I_IRQ_NMI_CTRL_REG		0x0c
31d7fbc6caSMaxime Ripard #define SUN4I_IRQ_PENDING_REG(x)	(0x10 + 0x4 * x)
32d7fbc6caSMaxime Ripard #define SUN4I_IRQ_FIQ_PENDING_REG(x)	(0x20 + 0x4 * x)
33d7fbc6caSMaxime Ripard #define SUN4I_IRQ_ENABLE_REG(x)		(0x40 + 0x4 * x)
34d7fbc6caSMaxime Ripard #define SUN4I_IRQ_MASK_REG(x)		(0x50 + 0x4 * x)
35d7fbc6caSMaxime Ripard 
36d7fbc6caSMaxime Ripard static void __iomem *sun4i_irq_base;
37d7fbc6caSMaxime Ripard static struct irq_domain *sun4i_irq_domain;
38d7fbc6caSMaxime Ripard 
398783dd3aSStephen Boyd static void __exception_irq_entry sun4i_handle_irq(struct pt_regs *regs);
40d7fbc6caSMaxime Ripard 
41baaecfa7SAxel Lin static void sun4i_irq_ack(struct irq_data *irqd)
42d7fbc6caSMaxime Ripard {
43d7fbc6caSMaxime Ripard 	unsigned int irq = irqd_to_hwirq(irqd);
44d7fbc6caSMaxime Ripard 	unsigned int irq_off = irq % 32;
45d7fbc6caSMaxime Ripard 	int reg = irq / 32;
46d7fbc6caSMaxime Ripard 	u32 val;
47d7fbc6caSMaxime Ripard 
48d7fbc6caSMaxime Ripard 	val = readl(sun4i_irq_base + SUN4I_IRQ_PENDING_REG(reg));
49d7fbc6caSMaxime Ripard 	writel(val | (1 << irq_off),
50d7fbc6caSMaxime Ripard 	       sun4i_irq_base + SUN4I_IRQ_PENDING_REG(reg));
51d7fbc6caSMaxime Ripard }
52d7fbc6caSMaxime Ripard 
53d7fbc6caSMaxime Ripard static void sun4i_irq_mask(struct irq_data *irqd)
54d7fbc6caSMaxime Ripard {
55d7fbc6caSMaxime Ripard 	unsigned int irq = irqd_to_hwirq(irqd);
56d7fbc6caSMaxime Ripard 	unsigned int irq_off = irq % 32;
57d7fbc6caSMaxime Ripard 	int reg = irq / 32;
58d7fbc6caSMaxime Ripard 	u32 val;
59d7fbc6caSMaxime Ripard 
60d7fbc6caSMaxime Ripard 	val = readl(sun4i_irq_base + SUN4I_IRQ_ENABLE_REG(reg));
61d7fbc6caSMaxime Ripard 	writel(val & ~(1 << irq_off),
62d7fbc6caSMaxime Ripard 	       sun4i_irq_base + SUN4I_IRQ_ENABLE_REG(reg));
63d7fbc6caSMaxime Ripard }
64d7fbc6caSMaxime Ripard 
65d7fbc6caSMaxime Ripard static void sun4i_irq_unmask(struct irq_data *irqd)
66d7fbc6caSMaxime Ripard {
67d7fbc6caSMaxime Ripard 	unsigned int irq = irqd_to_hwirq(irqd);
68d7fbc6caSMaxime Ripard 	unsigned int irq_off = irq % 32;
69d7fbc6caSMaxime Ripard 	int reg = irq / 32;
70d7fbc6caSMaxime Ripard 	u32 val;
71d7fbc6caSMaxime Ripard 
72d7fbc6caSMaxime Ripard 	val = readl(sun4i_irq_base + SUN4I_IRQ_ENABLE_REG(reg));
73d7fbc6caSMaxime Ripard 	writel(val | (1 << irq_off),
74d7fbc6caSMaxime Ripard 	       sun4i_irq_base + SUN4I_IRQ_ENABLE_REG(reg));
75d7fbc6caSMaxime Ripard }
76d7fbc6caSMaxime Ripard 
77d7fbc6caSMaxime Ripard static struct irq_chip sun4i_irq_chip = {
78d7fbc6caSMaxime Ripard 	.name		= "sun4i_irq",
79d7fbc6caSMaxime Ripard 	.irq_mask	= sun4i_irq_mask,
80d7fbc6caSMaxime Ripard 	.irq_unmask	= sun4i_irq_unmask,
81d7fbc6caSMaxime Ripard };
82d7fbc6caSMaxime Ripard 
83*e9df9e22SHans de Goede /* IRQ 0 / the ENMI needs a late eoi call */
84*e9df9e22SHans de Goede static struct irq_chip sun4i_irq_chip_enmi = {
85*e9df9e22SHans de Goede 	.name		= "sun4i_irq",
86*e9df9e22SHans de Goede 	.irq_eoi	= sun4i_irq_ack,
87*e9df9e22SHans de Goede 	.irq_mask	= sun4i_irq_mask,
88*e9df9e22SHans de Goede 	.irq_unmask	= sun4i_irq_unmask,
89*e9df9e22SHans de Goede 	.flags		= IRQCHIP_EOI_THREADED | IRQCHIP_EOI_IF_HANDLED,
90*e9df9e22SHans de Goede };
91*e9df9e22SHans de Goede 
92d7fbc6caSMaxime Ripard static int sun4i_irq_map(struct irq_domain *d, unsigned int virq,
93d7fbc6caSMaxime Ripard 			 irq_hw_number_t hw)
94d7fbc6caSMaxime Ripard {
95*e9df9e22SHans de Goede 	if (hw == 0)
96*e9df9e22SHans de Goede 		irq_set_chip_and_handler(virq, &sun4i_irq_chip_enmi,
97*e9df9e22SHans de Goede 					 handle_fasteoi_irq);
98*e9df9e22SHans de Goede 	else
99d7fbc6caSMaxime Ripard 		irq_set_chip_and_handler(virq, &sun4i_irq_chip,
100d7fbc6caSMaxime Ripard 					 handle_level_irq);
101*e9df9e22SHans de Goede 
102d7fbc6caSMaxime Ripard 	set_irq_flags(virq, IRQF_VALID | IRQF_PROBE);
103d7fbc6caSMaxime Ripard 
104d7fbc6caSMaxime Ripard 	return 0;
105d7fbc6caSMaxime Ripard }
106d7fbc6caSMaxime Ripard 
107d7fbc6caSMaxime Ripard static struct irq_domain_ops sun4i_irq_ops = {
108d7fbc6caSMaxime Ripard 	.map = sun4i_irq_map,
109d7fbc6caSMaxime Ripard 	.xlate = irq_domain_xlate_onecell,
110d7fbc6caSMaxime Ripard };
111d7fbc6caSMaxime Ripard 
112d7fbc6caSMaxime Ripard static int __init sun4i_of_init(struct device_node *node,
113d7fbc6caSMaxime Ripard 				struct device_node *parent)
114d7fbc6caSMaxime Ripard {
115d7fbc6caSMaxime Ripard 	sun4i_irq_base = of_iomap(node, 0);
116d7fbc6caSMaxime Ripard 	if (!sun4i_irq_base)
117d7fbc6caSMaxime Ripard 		panic("%s: unable to map IC registers\n",
118d7fbc6caSMaxime Ripard 			node->full_name);
119d7fbc6caSMaxime Ripard 
120d7fbc6caSMaxime Ripard 	/* Disable all interrupts */
121d7fbc6caSMaxime Ripard 	writel(0, sun4i_irq_base + SUN4I_IRQ_ENABLE_REG(0));
122d7fbc6caSMaxime Ripard 	writel(0, sun4i_irq_base + SUN4I_IRQ_ENABLE_REG(1));
123d7fbc6caSMaxime Ripard 	writel(0, sun4i_irq_base + SUN4I_IRQ_ENABLE_REG(2));
124d7fbc6caSMaxime Ripard 
125649ff46eSHans de Goede 	/* Unmask all the interrupts, ENABLE_REG(x) is used for masking */
126d7fbc6caSMaxime Ripard 	writel(0, sun4i_irq_base + SUN4I_IRQ_MASK_REG(0));
127d7fbc6caSMaxime Ripard 	writel(0, sun4i_irq_base + SUN4I_IRQ_MASK_REG(1));
128d7fbc6caSMaxime Ripard 	writel(0, sun4i_irq_base + SUN4I_IRQ_MASK_REG(2));
129d7fbc6caSMaxime Ripard 
130d7fbc6caSMaxime Ripard 	/* Clear all the pending interrupts */
131d7fbc6caSMaxime Ripard 	writel(0xffffffff, sun4i_irq_base + SUN4I_IRQ_PENDING_REG(0));
132d7fbc6caSMaxime Ripard 	writel(0xffffffff, sun4i_irq_base + SUN4I_IRQ_PENDING_REG(1));
133d7fbc6caSMaxime Ripard 	writel(0xffffffff, sun4i_irq_base + SUN4I_IRQ_PENDING_REG(2));
134d7fbc6caSMaxime Ripard 
135d7fbc6caSMaxime Ripard 	/* Enable protection mode */
136d7fbc6caSMaxime Ripard 	writel(0x01, sun4i_irq_base + SUN4I_IRQ_PROTECTION_REG);
137d7fbc6caSMaxime Ripard 
138d7fbc6caSMaxime Ripard 	/* Configure the external interrupt source type */
139d7fbc6caSMaxime Ripard 	writel(0x00, sun4i_irq_base + SUN4I_IRQ_NMI_CTRL_REG);
140d7fbc6caSMaxime Ripard 
141d7fbc6caSMaxime Ripard 	sun4i_irq_domain = irq_domain_add_linear(node, 3 * 32,
142d7fbc6caSMaxime Ripard 						 &sun4i_irq_ops, NULL);
143d7fbc6caSMaxime Ripard 	if (!sun4i_irq_domain)
144d7fbc6caSMaxime Ripard 		panic("%s: unable to create IRQ domain\n", node->full_name);
145d7fbc6caSMaxime Ripard 
146d7fbc6caSMaxime Ripard 	set_handle_irq(sun4i_handle_irq);
147d7fbc6caSMaxime Ripard 
148d7fbc6caSMaxime Ripard 	return 0;
149d7fbc6caSMaxime Ripard }
150a7e8b4b5SMaxime Ripard IRQCHIP_DECLARE(allwinner_sun4i_ic, "allwinner,sun4i-a10-ic", sun4i_of_init);
151d7fbc6caSMaxime Ripard 
1528783dd3aSStephen Boyd static void __exception_irq_entry sun4i_handle_irq(struct pt_regs *regs)
153d7fbc6caSMaxime Ripard {
154d7fbc6caSMaxime Ripard 	u32 irq, hwirq;
155d7fbc6caSMaxime Ripard 
15656af0416SHans de Goede 	/*
15756af0416SHans de Goede 	 * hwirq == 0 can mean one of 3 things:
15856af0416SHans de Goede 	 * 1) no more irqs pending
15956af0416SHans de Goede 	 * 2) irq 0 pending
16056af0416SHans de Goede 	 * 3) spurious irq
16156af0416SHans de Goede 	 * So if we immediately get a reading of 0, check the irq-pending reg
16256af0416SHans de Goede 	 * to differentiate between 2 and 3. We only do this once to avoid
16356af0416SHans de Goede 	 * the extra check in the common case of 1 hapening after having
16456af0416SHans de Goede 	 * read the vector-reg once.
16556af0416SHans de Goede 	 */
166d7fbc6caSMaxime Ripard 	hwirq = readl(sun4i_irq_base + SUN4I_IRQ_VECTOR_REG) >> 2;
16756af0416SHans de Goede 	if (hwirq == 0 &&
16856af0416SHans de Goede 		  !(readl(sun4i_irq_base + SUN4I_IRQ_PENDING_REG(0)) & BIT(0)))
16956af0416SHans de Goede 		return;
17056af0416SHans de Goede 
17156af0416SHans de Goede 	do {
172d7fbc6caSMaxime Ripard 		irq = irq_find_mapping(sun4i_irq_domain, hwirq);
173d7fbc6caSMaxime Ripard 		handle_IRQ(irq, regs);
174d7fbc6caSMaxime Ripard 		hwirq = readl(sun4i_irq_base + SUN4I_IRQ_VECTOR_REG) >> 2;
17556af0416SHans de Goede 	} while (hwirq != 0);
176d7fbc6caSMaxime Ripard }
177