1 /*
2  *  CLPS711X IRQ driver
3  *
4  *  Copyright (C) 2013 Alexander Shiyan <shc_work@mail.ru>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  */
11 
12 #include <linux/io.h>
13 #include <linux/irq.h>
14 #include <linux/irqchip.h>
15 #include <linux/irqdomain.h>
16 #include <linux/of_address.h>
17 #include <linux/of_irq.h>
18 #include <linux/slab.h>
19 
20 #include <asm/exception.h>
21 #include <asm/mach/irq.h>
22 
23 #define CLPS711X_INTSR1	(0x0240)
24 #define CLPS711X_INTMR1	(0x0280)
25 #define CLPS711X_BLEOI	(0x0600)
26 #define CLPS711X_MCEOI	(0x0640)
27 #define CLPS711X_TEOI	(0x0680)
28 #define CLPS711X_TC1EOI	(0x06c0)
29 #define CLPS711X_TC2EOI	(0x0700)
30 #define CLPS711X_RTCEOI	(0x0740)
31 #define CLPS711X_UMSEOI	(0x0780)
32 #define CLPS711X_COEOI	(0x07c0)
33 #define CLPS711X_INTSR2	(0x1240)
34 #define CLPS711X_INTMR2	(0x1280)
35 #define CLPS711X_SRXEOF	(0x1600)
36 #define CLPS711X_KBDEOI	(0x1700)
37 #define CLPS711X_INTSR3	(0x2240)
38 #define CLPS711X_INTMR3	(0x2280)
39 
40 static const struct {
41 #define CLPS711X_FLAG_EN	(1 << 0)
42 #define CLPS711X_FLAG_FIQ	(1 << 1)
43 	unsigned int	flags;
44 	phys_addr_t	eoi;
45 } clps711x_irqs[] = {
46 	[1]	= { CLPS711X_FLAG_FIQ, CLPS711X_BLEOI, },
47 	[3]	= { CLPS711X_FLAG_FIQ, CLPS711X_MCEOI, },
48 	[4]	= { CLPS711X_FLAG_EN, CLPS711X_COEOI, },
49 	[5]	= { CLPS711X_FLAG_EN, },
50 	[6]	= { CLPS711X_FLAG_EN, },
51 	[7]	= { CLPS711X_FLAG_EN, },
52 	[8]	= { CLPS711X_FLAG_EN, CLPS711X_TC1EOI, },
53 	[9]	= { CLPS711X_FLAG_EN, CLPS711X_TC2EOI, },
54 	[10]	= { CLPS711X_FLAG_EN, CLPS711X_RTCEOI, },
55 	[11]	= { CLPS711X_FLAG_EN, CLPS711X_TEOI, },
56 	[12]	= { CLPS711X_FLAG_EN, },
57 	[13]	= { CLPS711X_FLAG_EN, },
58 	[14]	= { CLPS711X_FLAG_EN, CLPS711X_UMSEOI, },
59 	[15]	= { CLPS711X_FLAG_EN, CLPS711X_SRXEOF, },
60 	[16]	= { CLPS711X_FLAG_EN, CLPS711X_KBDEOI, },
61 	[17]	= { CLPS711X_FLAG_EN, },
62 	[18]	= { CLPS711X_FLAG_EN, },
63 	[28]	= { CLPS711X_FLAG_EN, },
64 	[29]	= { CLPS711X_FLAG_EN, },
65 	[32]	= { CLPS711X_FLAG_FIQ, },
66 };
67 
68 static struct {
69 	void __iomem		*base;
70 	void __iomem		*intmr[3];
71 	void __iomem		*intsr[3];
72 	struct irq_domain	*domain;
73 	struct irq_domain_ops	ops;
74 } *clps711x_intc;
75 
76 static asmlinkage void __exception_irq_entry clps711x_irqh(struct pt_regs *regs)
77 {
78 	u32 irqstat;
79 
80 	do {
81 		irqstat = readw_relaxed(clps711x_intc->intmr[0]) &
82 			  readw_relaxed(clps711x_intc->intsr[0]);
83 		if (irqstat)
84 			handle_domain_irq(clps711x_intc->domain,
85 					  fls(irqstat) - 1, regs);
86 
87 		irqstat = readw_relaxed(clps711x_intc->intmr[1]) &
88 			  readw_relaxed(clps711x_intc->intsr[1]);
89 		if (irqstat)
90 			handle_domain_irq(clps711x_intc->domain,
91 					  fls(irqstat) - 1 + 16, regs);
92 	} while (irqstat);
93 }
94 
95 static void clps711x_intc_eoi(struct irq_data *d)
96 {
97 	irq_hw_number_t hwirq = irqd_to_hwirq(d);
98 
99 	writel_relaxed(0, clps711x_intc->base + clps711x_irqs[hwirq].eoi);
100 }
101 
102 static void clps711x_intc_mask(struct irq_data *d)
103 {
104 	irq_hw_number_t hwirq = irqd_to_hwirq(d);
105 	void __iomem *intmr = clps711x_intc->intmr[hwirq / 16];
106 	u32 tmp;
107 
108 	tmp = readl_relaxed(intmr);
109 	tmp &= ~(1 << (hwirq % 16));
110 	writel_relaxed(tmp, intmr);
111 }
112 
113 static void clps711x_intc_unmask(struct irq_data *d)
114 {
115 	irq_hw_number_t hwirq = irqd_to_hwirq(d);
116 	void __iomem *intmr = clps711x_intc->intmr[hwirq / 16];
117 	u32 tmp;
118 
119 	tmp = readl_relaxed(intmr);
120 	tmp |= 1 << (hwirq % 16);
121 	writel_relaxed(tmp, intmr);
122 }
123 
124 static struct irq_chip clps711x_intc_chip = {
125 	.name		= "clps711x-intc",
126 	.irq_eoi	= clps711x_intc_eoi,
127 	.irq_mask	= clps711x_intc_mask,
128 	.irq_unmask	= clps711x_intc_unmask,
129 };
130 
131 static int __init clps711x_intc_irq_map(struct irq_domain *h, unsigned int virq,
132 					irq_hw_number_t hw)
133 {
134 	irq_flow_handler_t handler = handle_level_irq;
135 	unsigned int flags = 0;
136 
137 	if (!clps711x_irqs[hw].flags)
138 		return 0;
139 
140 	if (clps711x_irqs[hw].flags & CLPS711X_FLAG_FIQ) {
141 		handler = handle_bad_irq;
142 		flags |= IRQ_NOAUTOEN;
143 	} else if (clps711x_irqs[hw].eoi) {
144 		handler = handle_fasteoi_irq;
145 	}
146 
147 	/* Clear down pending interrupt */
148 	if (clps711x_irqs[hw].eoi)
149 		writel_relaxed(0, clps711x_intc->base + clps711x_irqs[hw].eoi);
150 
151 	irq_set_chip_and_handler(virq, &clps711x_intc_chip, handler);
152 	irq_modify_status(virq, IRQ_NOPROBE, flags);
153 
154 	return 0;
155 }
156 
157 static int __init _clps711x_intc_init(struct device_node *np,
158 				      phys_addr_t base, resource_size_t size)
159 {
160 	int err;
161 
162 	clps711x_intc = kzalloc(sizeof(*clps711x_intc), GFP_KERNEL);
163 	if (!clps711x_intc)
164 		return -ENOMEM;
165 
166 	clps711x_intc->base = ioremap(base, size);
167 	if (!clps711x_intc->base) {
168 		err = -ENOMEM;
169 		goto out_kfree;
170 	}
171 
172 	clps711x_intc->intsr[0] = clps711x_intc->base + CLPS711X_INTSR1;
173 	clps711x_intc->intmr[0] = clps711x_intc->base + CLPS711X_INTMR1;
174 	clps711x_intc->intsr[1] = clps711x_intc->base + CLPS711X_INTSR2;
175 	clps711x_intc->intmr[1] = clps711x_intc->base + CLPS711X_INTMR2;
176 	clps711x_intc->intsr[2] = clps711x_intc->base + CLPS711X_INTSR3;
177 	clps711x_intc->intmr[2] = clps711x_intc->base + CLPS711X_INTMR3;
178 
179 	/* Mask all interrupts */
180 	writel_relaxed(0, clps711x_intc->intmr[0]);
181 	writel_relaxed(0, clps711x_intc->intmr[1]);
182 	writel_relaxed(0, clps711x_intc->intmr[2]);
183 
184 	err = irq_alloc_descs(-1, 0, ARRAY_SIZE(clps711x_irqs), numa_node_id());
185 	if (IS_ERR_VALUE(err))
186 		goto out_iounmap;
187 
188 	clps711x_intc->ops.map = clps711x_intc_irq_map;
189 	clps711x_intc->ops.xlate = irq_domain_xlate_onecell;
190 	clps711x_intc->domain =
191 		irq_domain_add_legacy(np, ARRAY_SIZE(clps711x_irqs),
192 				      0, 0, &clps711x_intc->ops, NULL);
193 	if (!clps711x_intc->domain) {
194 		err = -ENOMEM;
195 		goto out_irqfree;
196 	}
197 
198 	irq_set_default_host(clps711x_intc->domain);
199 	set_handle_irq(clps711x_irqh);
200 
201 #ifdef CONFIG_FIQ
202 	init_FIQ(0);
203 #endif
204 
205 	return 0;
206 
207 out_irqfree:
208 	irq_free_descs(0, ARRAY_SIZE(clps711x_irqs));
209 
210 out_iounmap:
211 	iounmap(clps711x_intc->base);
212 
213 out_kfree:
214 	kfree(clps711x_intc);
215 
216 	return err;
217 }
218 
219 void __init clps711x_intc_init(phys_addr_t base, resource_size_t size)
220 {
221 	BUG_ON(_clps711x_intc_init(NULL, base, size));
222 }
223 
224 #ifdef CONFIG_IRQCHIP
225 static int __init clps711x_intc_init_dt(struct device_node *np,
226 					struct device_node *parent)
227 {
228 	struct resource res;
229 	int err;
230 
231 	err = of_address_to_resource(np, 0, &res);
232 	if (err)
233 		return err;
234 
235 	return _clps711x_intc_init(np, res.start, resource_size(&res));
236 }
237 IRQCHIP_DECLARE(clps711x, "cirrus,clps711x-intc", clps711x_intc_init_dt);
238 #endif
239