xref: /linux/drivers/irqchip/irq-sun4i.c (revision 03ab8e6297acd1bc0eedaa050e2a1635c576fd11)
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>
1941a83e06SJoel Porquet #include <linux/irqchip.h>
20d7fbc6caSMaxime Ripard #include <linux/of.h>
21d7fbc6caSMaxime Ripard #include <linux/of_address.h>
22d7fbc6caSMaxime Ripard #include <linux/of_irq.h>
23d7fbc6caSMaxime Ripard 
24d7fbc6caSMaxime Ripard #include <asm/exception.h>
25d7fbc6caSMaxime Ripard 
26d7fbc6caSMaxime Ripard #define SUN4I_IRQ_VECTOR_REG		0x00
27d7fbc6caSMaxime Ripard #define SUN4I_IRQ_PROTECTION_REG	0x08
28d7fbc6caSMaxime Ripard #define SUN4I_IRQ_NMI_CTRL_REG		0x0c
29d7fbc6caSMaxime Ripard #define SUN4I_IRQ_PENDING_REG(x)	(0x10 + 0x4 * x)
30d7fbc6caSMaxime Ripard #define SUN4I_IRQ_FIQ_PENDING_REG(x)	(0x20 + 0x4 * x)
31d4fc2ea0SMesih Kilinc #define SUN4I_IRQ_ENABLE_REG(data, x)	((data)->enable_reg_offset + 0x4 * x)
32d4fc2ea0SMesih Kilinc #define SUN4I_IRQ_MASK_REG(data, x)	((data)->mask_reg_offset + 0x4 * x)
33d4fc2ea0SMesih Kilinc #define SUN4I_IRQ_ENABLE_REG_OFFSET	0x40
34d4fc2ea0SMesih Kilinc #define SUN4I_IRQ_MASK_REG_OFFSET	0x50
35b0c4b9f3SMesih Kilinc #define SUNIV_IRQ_ENABLE_REG_OFFSET	0x20
36b0c4b9f3SMesih Kilinc #define SUNIV_IRQ_MASK_REG_OFFSET	0x30
37d7fbc6caSMaxime Ripard 
38177304cfSMesih Kilinc struct sun4i_irq_chip_data {
39177304cfSMesih Kilinc 	void __iomem *irq_base;
40177304cfSMesih Kilinc 	struct irq_domain *irq_domain;
41d4fc2ea0SMesih Kilinc 	u32 enable_reg_offset;
42d4fc2ea0SMesih Kilinc 	u32 mask_reg_offset;
43177304cfSMesih Kilinc };
44177304cfSMesih Kilinc 
45177304cfSMesih Kilinc static struct sun4i_irq_chip_data *irq_ic_data;
46d7fbc6caSMaxime Ripard 
478783dd3aSStephen Boyd static void __exception_irq_entry sun4i_handle_irq(struct pt_regs *regs);
48d7fbc6caSMaxime Ripard 
49baaecfa7SAxel Lin static void sun4i_irq_ack(struct irq_data *irqd)
50d7fbc6caSMaxime Ripard {
51d7fbc6caSMaxime Ripard 	unsigned int irq = irqd_to_hwirq(irqd);
52d7fbc6caSMaxime Ripard 
53915b78ceSHans de Goede 	if (irq != 0)
54915b78ceSHans de Goede 		return; /* Only IRQ 0 / the ENMI needs to be acked */
55915b78ceSHans de Goede 
56177304cfSMesih Kilinc 	writel(BIT(0), irq_ic_data->irq_base + SUN4I_IRQ_PENDING_REG(0));
57d7fbc6caSMaxime Ripard }
58d7fbc6caSMaxime Ripard 
59d7fbc6caSMaxime Ripard static void sun4i_irq_mask(struct irq_data *irqd)
60d7fbc6caSMaxime Ripard {
61d7fbc6caSMaxime Ripard 	unsigned int irq = irqd_to_hwirq(irqd);
62d7fbc6caSMaxime Ripard 	unsigned int irq_off = irq % 32;
63d7fbc6caSMaxime Ripard 	int reg = irq / 32;
64d7fbc6caSMaxime Ripard 	u32 val;
65d7fbc6caSMaxime Ripard 
66d4fc2ea0SMesih Kilinc 	val = readl(irq_ic_data->irq_base +
67d4fc2ea0SMesih Kilinc 			SUN4I_IRQ_ENABLE_REG(irq_ic_data, reg));
68d7fbc6caSMaxime Ripard 	writel(val & ~(1 << irq_off),
69d4fc2ea0SMesih Kilinc 	       irq_ic_data->irq_base + SUN4I_IRQ_ENABLE_REG(irq_ic_data, reg));
70d7fbc6caSMaxime Ripard }
71d7fbc6caSMaxime Ripard 
72d7fbc6caSMaxime Ripard static void sun4i_irq_unmask(struct irq_data *irqd)
73d7fbc6caSMaxime Ripard {
74d7fbc6caSMaxime Ripard 	unsigned int irq = irqd_to_hwirq(irqd);
75d7fbc6caSMaxime Ripard 	unsigned int irq_off = irq % 32;
76d7fbc6caSMaxime Ripard 	int reg = irq / 32;
77d7fbc6caSMaxime Ripard 	u32 val;
78d7fbc6caSMaxime Ripard 
79d4fc2ea0SMesih Kilinc 	val = readl(irq_ic_data->irq_base +
80d4fc2ea0SMesih Kilinc 			SUN4I_IRQ_ENABLE_REG(irq_ic_data, reg));
81d7fbc6caSMaxime Ripard 	writel(val | (1 << irq_off),
82d4fc2ea0SMesih Kilinc 	       irq_ic_data->irq_base + SUN4I_IRQ_ENABLE_REG(irq_ic_data, reg));
83d7fbc6caSMaxime Ripard }
84d7fbc6caSMaxime Ripard 
85d7fbc6caSMaxime Ripard static struct irq_chip sun4i_irq_chip = {
86d7fbc6caSMaxime Ripard 	.name		= "sun4i_irq",
87e9df9e22SHans de Goede 	.irq_eoi	= sun4i_irq_ack,
88e9df9e22SHans de Goede 	.irq_mask	= sun4i_irq_mask,
89e9df9e22SHans de Goede 	.irq_unmask	= sun4i_irq_unmask,
90e9df9e22SHans de Goede 	.flags		= IRQCHIP_EOI_THREADED | IRQCHIP_EOI_IF_HANDLED,
91e9df9e22SHans de Goede };
92e9df9e22SHans de Goede 
93d7fbc6caSMaxime Ripard static int sun4i_irq_map(struct irq_domain *d, unsigned int virq,
94d7fbc6caSMaxime Ripard 			 irq_hw_number_t hw)
95d7fbc6caSMaxime Ripard {
96915b78ceSHans de Goede 	irq_set_chip_and_handler(virq, &sun4i_irq_chip, handle_fasteoi_irq);
97d17cab44SRob Herring 	irq_set_probe(virq);
98d7fbc6caSMaxime Ripard 
99d7fbc6caSMaxime Ripard 	return 0;
100d7fbc6caSMaxime Ripard }
101d7fbc6caSMaxime Ripard 
10296009736SKrzysztof Kozlowski static const struct irq_domain_ops sun4i_irq_ops = {
103d7fbc6caSMaxime Ripard 	.map = sun4i_irq_map,
104d7fbc6caSMaxime Ripard 	.xlate = irq_domain_xlate_onecell,
105d7fbc6caSMaxime Ripard };
106d7fbc6caSMaxime Ripard 
107d7fbc6caSMaxime Ripard static int __init sun4i_of_init(struct device_node *node,
108d7fbc6caSMaxime Ripard 				struct device_node *parent)
109d7fbc6caSMaxime Ripard {
110177304cfSMesih Kilinc 	irq_ic_data->irq_base = of_iomap(node, 0);
111177304cfSMesih Kilinc 	if (!irq_ic_data->irq_base)
112e81f54c6SRob Herring 		panic("%pOF: unable to map IC registers\n",
113e81f54c6SRob Herring 			node);
114d7fbc6caSMaxime Ripard 
115d7fbc6caSMaxime Ripard 	/* Disable all interrupts */
116d4fc2ea0SMesih Kilinc 	writel(0, irq_ic_data->irq_base + SUN4I_IRQ_ENABLE_REG(irq_ic_data, 0));
117d4fc2ea0SMesih Kilinc 	writel(0, irq_ic_data->irq_base + SUN4I_IRQ_ENABLE_REG(irq_ic_data, 1));
118d4fc2ea0SMesih Kilinc 	writel(0, irq_ic_data->irq_base + SUN4I_IRQ_ENABLE_REG(irq_ic_data, 2));
119d7fbc6caSMaxime Ripard 
120649ff46eSHans de Goede 	/* Unmask all the interrupts, ENABLE_REG(x) is used for masking */
121d4fc2ea0SMesih Kilinc 	writel(0, irq_ic_data->irq_base + SUN4I_IRQ_MASK_REG(irq_ic_data, 0));
122d4fc2ea0SMesih Kilinc 	writel(0, irq_ic_data->irq_base + SUN4I_IRQ_MASK_REG(irq_ic_data, 1));
123d4fc2ea0SMesih Kilinc 	writel(0, irq_ic_data->irq_base + SUN4I_IRQ_MASK_REG(irq_ic_data, 2));
124d7fbc6caSMaxime Ripard 
125d7fbc6caSMaxime Ripard 	/* Clear all the pending interrupts */
126177304cfSMesih Kilinc 	writel(0xffffffff, irq_ic_data->irq_base + SUN4I_IRQ_PENDING_REG(0));
127177304cfSMesih Kilinc 	writel(0xffffffff, irq_ic_data->irq_base + SUN4I_IRQ_PENDING_REG(1));
128177304cfSMesih Kilinc 	writel(0xffffffff, irq_ic_data->irq_base + SUN4I_IRQ_PENDING_REG(2));
129d7fbc6caSMaxime Ripard 
130d7fbc6caSMaxime Ripard 	/* Enable protection mode */
131177304cfSMesih Kilinc 	writel(0x01, irq_ic_data->irq_base + SUN4I_IRQ_PROTECTION_REG);
132d7fbc6caSMaxime Ripard 
133d7fbc6caSMaxime Ripard 	/* Configure the external interrupt source type */
134177304cfSMesih Kilinc 	writel(0x00, irq_ic_data->irq_base + SUN4I_IRQ_NMI_CTRL_REG);
135d7fbc6caSMaxime Ripard 
136177304cfSMesih Kilinc 	irq_ic_data->irq_domain = irq_domain_add_linear(node, 3 * 32,
137d7fbc6caSMaxime Ripard 						 &sun4i_irq_ops, NULL);
138177304cfSMesih Kilinc 	if (!irq_ic_data->irq_domain)
139e81f54c6SRob Herring 		panic("%pOF: unable to create IRQ domain\n", node);
140d7fbc6caSMaxime Ripard 
141d7fbc6caSMaxime Ripard 	set_handle_irq(sun4i_handle_irq);
142d7fbc6caSMaxime Ripard 
143d7fbc6caSMaxime Ripard 	return 0;
144d7fbc6caSMaxime Ripard }
145b0c4b9f3SMesih Kilinc 
146b0c4b9f3SMesih Kilinc static int __init sun4i_ic_of_init(struct device_node *node,
147b0c4b9f3SMesih Kilinc 				   struct device_node *parent)
148b0c4b9f3SMesih Kilinc {
149b0c4b9f3SMesih Kilinc 	irq_ic_data = kzalloc(sizeof(struct sun4i_irq_chip_data), GFP_KERNEL);
15075768e39SZhen Lei 	if (!irq_ic_data)
151b0c4b9f3SMesih Kilinc 		return -ENOMEM;
152b0c4b9f3SMesih Kilinc 
153b0c4b9f3SMesih Kilinc 	irq_ic_data->enable_reg_offset = SUN4I_IRQ_ENABLE_REG_OFFSET;
154b0c4b9f3SMesih Kilinc 	irq_ic_data->mask_reg_offset = SUN4I_IRQ_MASK_REG_OFFSET;
155b0c4b9f3SMesih Kilinc 
156b0c4b9f3SMesih Kilinc 	return sun4i_of_init(node, parent);
157b0c4b9f3SMesih Kilinc }
158b0c4b9f3SMesih Kilinc 
159b0c4b9f3SMesih Kilinc IRQCHIP_DECLARE(allwinner_sun4i_ic, "allwinner,sun4i-a10-ic", sun4i_ic_of_init);
160b0c4b9f3SMesih Kilinc 
161b0c4b9f3SMesih Kilinc static int __init suniv_ic_of_init(struct device_node *node,
162b0c4b9f3SMesih Kilinc 				   struct device_node *parent)
163b0c4b9f3SMesih Kilinc {
164b0c4b9f3SMesih Kilinc 	irq_ic_data = kzalloc(sizeof(struct sun4i_irq_chip_data), GFP_KERNEL);
16575768e39SZhen Lei 	if (!irq_ic_data)
166b0c4b9f3SMesih Kilinc 		return -ENOMEM;
167b0c4b9f3SMesih Kilinc 
168b0c4b9f3SMesih Kilinc 	irq_ic_data->enable_reg_offset = SUNIV_IRQ_ENABLE_REG_OFFSET;
169b0c4b9f3SMesih Kilinc 	irq_ic_data->mask_reg_offset = SUNIV_IRQ_MASK_REG_OFFSET;
170b0c4b9f3SMesih Kilinc 
171b0c4b9f3SMesih Kilinc 	return sun4i_of_init(node, parent);
172b0c4b9f3SMesih Kilinc }
173b0c4b9f3SMesih Kilinc 
174b0c4b9f3SMesih Kilinc IRQCHIP_DECLARE(allwinner_sunvi_ic, "allwinner,suniv-f1c100s-ic",
175b0c4b9f3SMesih Kilinc 		suniv_ic_of_init);
176d7fbc6caSMaxime Ripard 
1778783dd3aSStephen Boyd static void __exception_irq_entry sun4i_handle_irq(struct pt_regs *regs)
178d7fbc6caSMaxime Ripard {
17921d06d91SMarc Zyngier 	u32 hwirq;
180d7fbc6caSMaxime Ripard 
18156af0416SHans de Goede 	/*
18256af0416SHans de Goede 	 * hwirq == 0 can mean one of 3 things:
18356af0416SHans de Goede 	 * 1) no more irqs pending
18456af0416SHans de Goede 	 * 2) irq 0 pending
18556af0416SHans de Goede 	 * 3) spurious irq
18656af0416SHans de Goede 	 * So if we immediately get a reading of 0, check the irq-pending reg
18756af0416SHans de Goede 	 * to differentiate between 2 and 3. We only do this once to avoid
188a359f757SIngo Molnar 	 * the extra check in the common case of 1 happening after having
18956af0416SHans de Goede 	 * read the vector-reg once.
19056af0416SHans de Goede 	 */
191177304cfSMesih Kilinc 	hwirq = readl(irq_ic_data->irq_base + SUN4I_IRQ_VECTOR_REG) >> 2;
19256af0416SHans de Goede 	if (hwirq == 0 &&
193177304cfSMesih Kilinc 		  !(readl(irq_ic_data->irq_base + SUN4I_IRQ_PENDING_REG(0)) &
194177304cfSMesih Kilinc 			  BIT(0)))
19556af0416SHans de Goede 		return;
19656af0416SHans de Goede 
19756af0416SHans de Goede 	do {
1980953fb26SMark Rutland 		generic_handle_domain_irq(irq_ic_data->irq_domain, hwirq);
199177304cfSMesih Kilinc 		hwirq = readl(irq_ic_data->irq_base +
200177304cfSMesih Kilinc 				SUN4I_IRQ_VECTOR_REG) >> 2;
20156af0416SHans de Goede 	} while (hwirq != 0);
202d7fbc6caSMaxime Ripard }
203