1 /*
2  * arch/powerpc/platforms/embedded6xx/hlwd-pic.c
3  *
4  * Nintendo Wii "Hollywood" interrupt controller support.
5  * Copyright (C) 2009 The GameCube Linux Team
6  * Copyright (C) 2009 Albert Herranz
7  *
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU General Public License
10  * as published by the Free Software Foundation; either version 2
11  * of the License, or (at your option) any later version.
12  *
13  */
14 #define DRV_MODULE_NAME "hlwd-pic"
15 #define pr_fmt(fmt) DRV_MODULE_NAME ": " fmt
16 
17 #include <linux/kernel.h>
18 #include <linux/irq.h>
19 #include <linux/of.h>
20 #include <linux/of_address.h>
21 #include <linux/of_irq.h>
22 #include <asm/io.h>
23 
24 #include "hlwd-pic.h"
25 
26 #define HLWD_NR_IRQS	32
27 
28 /*
29  * Each interrupt has a corresponding bit in both
30  * the Interrupt Cause (ICR) and Interrupt Mask (IMR) registers.
31  *
32  * Enabling/disabling an interrupt line involves asserting/clearing
33  * the corresponding bit in IMR. ACK'ing a request simply involves
34  * asserting the corresponding bit in ICR.
35  */
36 #define HW_BROADWAY_ICR		0x00
37 #define HW_BROADWAY_IMR		0x04
38 #define HW_STARLET_ICR		0x08
39 #define HW_STARLET_IMR		0x0c
40 
41 
42 /*
43  * IRQ chip hooks.
44  *
45  */
46 
47 static void hlwd_pic_mask_and_ack(struct irq_data *d)
48 {
49 	int irq = irqd_to_hwirq(d);
50 	void __iomem *io_base = irq_data_get_irq_chip_data(d);
51 	u32 mask = 1 << irq;
52 
53 	clrbits32(io_base + HW_BROADWAY_IMR, mask);
54 	out_be32(io_base + HW_BROADWAY_ICR, mask);
55 }
56 
57 static void hlwd_pic_ack(struct irq_data *d)
58 {
59 	int irq = irqd_to_hwirq(d);
60 	void __iomem *io_base = irq_data_get_irq_chip_data(d);
61 
62 	out_be32(io_base + HW_BROADWAY_ICR, 1 << irq);
63 }
64 
65 static void hlwd_pic_mask(struct irq_data *d)
66 {
67 	int irq = irqd_to_hwirq(d);
68 	void __iomem *io_base = irq_data_get_irq_chip_data(d);
69 
70 	clrbits32(io_base + HW_BROADWAY_IMR, 1 << irq);
71 }
72 
73 static void hlwd_pic_unmask(struct irq_data *d)
74 {
75 	int irq = irqd_to_hwirq(d);
76 	void __iomem *io_base = irq_data_get_irq_chip_data(d);
77 
78 	setbits32(io_base + HW_BROADWAY_IMR, 1 << irq);
79 
80 	/* Make sure the ARM (aka. Starlet) doesn't handle this interrupt. */
81 	clrbits32(io_base + HW_STARLET_IMR, 1 << irq);
82 }
83 
84 
85 static struct irq_chip hlwd_pic = {
86 	.name		= "hlwd-pic",
87 	.irq_ack	= hlwd_pic_ack,
88 	.irq_mask_ack	= hlwd_pic_mask_and_ack,
89 	.irq_mask	= hlwd_pic_mask,
90 	.irq_unmask	= hlwd_pic_unmask,
91 };
92 
93 /*
94  * IRQ host hooks.
95  *
96  */
97 
98 static struct irq_domain *hlwd_irq_host;
99 
100 static int hlwd_pic_map(struct irq_domain *h, unsigned int virq,
101 			   irq_hw_number_t hwirq)
102 {
103 	irq_set_chip_data(virq, h->host_data);
104 	irq_set_status_flags(virq, IRQ_LEVEL);
105 	irq_set_chip_and_handler(virq, &hlwd_pic, handle_level_irq);
106 	return 0;
107 }
108 
109 static const struct irq_domain_ops hlwd_irq_domain_ops = {
110 	.map = hlwd_pic_map,
111 };
112 
113 static unsigned int __hlwd_pic_get_irq(struct irq_domain *h)
114 {
115 	void __iomem *io_base = h->host_data;
116 	int irq;
117 	u32 irq_status;
118 
119 	irq_status = in_be32(io_base + HW_BROADWAY_ICR) &
120 		     in_be32(io_base + HW_BROADWAY_IMR);
121 	if (irq_status == 0)
122 		return 0;	/* no more IRQs pending */
123 
124 	irq = __ffs(irq_status);
125 	return irq_linear_revmap(h, irq);
126 }
127 
128 static void hlwd_pic_irq_cascade(struct irq_desc *desc)
129 {
130 	struct irq_chip *chip = irq_desc_get_chip(desc);
131 	struct irq_domain *irq_domain = irq_desc_get_handler_data(desc);
132 	unsigned int virq;
133 
134 	raw_spin_lock(&desc->lock);
135 	chip->irq_mask(&desc->irq_data); /* IRQ_LEVEL */
136 	raw_spin_unlock(&desc->lock);
137 
138 	virq = __hlwd_pic_get_irq(irq_domain);
139 	if (virq)
140 		generic_handle_irq(virq);
141 	else
142 		pr_err("spurious interrupt!\n");
143 
144 	raw_spin_lock(&desc->lock);
145 	chip->irq_ack(&desc->irq_data); /* IRQ_LEVEL */
146 	if (!irqd_irq_disabled(&desc->irq_data) && chip->irq_unmask)
147 		chip->irq_unmask(&desc->irq_data);
148 	raw_spin_unlock(&desc->lock);
149 }
150 
151 /*
152  * Platform hooks.
153  *
154  */
155 
156 static void __hlwd_quiesce(void __iomem *io_base)
157 {
158 	/* mask and ack all IRQs */
159 	out_be32(io_base + HW_BROADWAY_IMR, 0);
160 	out_be32(io_base + HW_BROADWAY_ICR, 0xffffffff);
161 }
162 
163 static struct irq_domain *hlwd_pic_init(struct device_node *np)
164 {
165 	struct irq_domain *irq_domain;
166 	struct resource res;
167 	void __iomem *io_base;
168 	int retval;
169 
170 	retval = of_address_to_resource(np, 0, &res);
171 	if (retval) {
172 		pr_err("no io memory range found\n");
173 		return NULL;
174 	}
175 	io_base = ioremap(res.start, resource_size(&res));
176 	if (!io_base) {
177 		pr_err("ioremap failed\n");
178 		return NULL;
179 	}
180 
181 	pr_info("controller at 0x%08x mapped to 0x%p\n", res.start, io_base);
182 
183 	__hlwd_quiesce(io_base);
184 
185 	irq_domain = irq_domain_add_linear(np, HLWD_NR_IRQS,
186 					   &hlwd_irq_domain_ops, io_base);
187 	if (!irq_domain) {
188 		pr_err("failed to allocate irq_domain\n");
189 		iounmap(io_base);
190 		return NULL;
191 	}
192 
193 	return irq_domain;
194 }
195 
196 unsigned int hlwd_pic_get_irq(void)
197 {
198 	return __hlwd_pic_get_irq(hlwd_irq_host);
199 }
200 
201 /*
202  * Probe function.
203  *
204  */
205 
206 void hlwd_pic_probe(void)
207 {
208 	struct irq_domain *host;
209 	struct device_node *np;
210 	const u32 *interrupts;
211 	int cascade_virq;
212 
213 	for_each_compatible_node(np, NULL, "nintendo,hollywood-pic") {
214 		interrupts = of_get_property(np, "interrupts", NULL);
215 		if (interrupts) {
216 			host = hlwd_pic_init(np);
217 			BUG_ON(!host);
218 			cascade_virq = irq_of_parse_and_map(np, 0);
219 			irq_set_handler_data(cascade_virq, host);
220 			irq_set_chained_handler(cascade_virq,
221 						hlwd_pic_irq_cascade);
222 			hlwd_irq_host = host;
223 			break;
224 		}
225 	}
226 }
227 
228 /**
229  * hlwd_quiesce() - quiesce hollywood irq controller
230  *
231  * Mask and ack all interrupt sources.
232  *
233  */
234 void hlwd_quiesce(void)
235 {
236 	void __iomem *io_base = hlwd_irq_host->host_data;
237 
238 	__hlwd_quiesce(io_base);
239 }
240 
241