pic_mpc5200.c revision 1.1 1 /* $NetBSD: pic_mpc5200.c,v 1.1 2026/06/27 13:28:35 rkujawa Exp $ */
2
3 /*-
4 * Copyright (c) 2026 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Radoslaw Kujawa and Robert Swindells.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 */
31
32 /*
33 * MPC5200B SIU interrupt controller.
34 */
35
36 #include <sys/cdefs.h>
37 __KERNEL_RCSID(0, "$NetBSD: pic_mpc5200.c,v 1.1 2026/06/27 13:28:35 rkujawa Exp $");
38
39 #include <sys/param.h>
40 #include <sys/systm.h>
41 #include <sys/device.h>
42 #include <sys/bus.h>
43
44 #include <dev/ofw/openfirm.h>
45
46 #include <machine/intr.h>
47
48 #include <powerpc/pic/picvar.h>
49
50 #include <powerpc/mpc5200/obiovar.h>
51 #include <powerpc/mpc5200/pic_mpc5200var.h>
52
53 /* SIU interrupt-controller registers (offsets from the controller base). */
54 #define SIU_PER_MASK 0x00 /* peripheral interrupt mask */
55 #define SIU_PER_PRI1 0x04 /* peripheral priority / HI-LO select 1 */
56 #define SIU_PER_PRI2 0x08 /* peripheral priority / HI-LO select 2 */
57 #define SIU_PER_PRI3 0x0c /* peripheral priority / HI-LO select 3 */
58 #define SIU_CTRL 0x10 /* external enable & type / critical enable */
59 #define SIU_MAIN_MASK 0x14 /* critical priority + main interrupt mask */
60 #define SIU_MAIN_PRI1 0x18 /* main priority 1 */
61 #define SIU_MAIN_PRI2 0x1c /* main priority 2 (0x1Cf in the draft -- typo) */
62 #define SIU_ENC_STATUS 0x24 /* encoded highest-priority pending source */
63 #define SIU_CRIT_STATUS 0x28 /* critical interrupt status */
64 #define SIU_MAIN_STATUS 0x2c /* main interrupt status */
65 #define SIU_PER_STATUS 0x30 /* peripheral interrupt status */
66
67 #define SIU_REG_SIZE 0x40 /* register window if OF omits a size */
68
69 /* Fields of the encoded-status register (SIU_ENC_STATUS). */
70 #define SIU_ENC_CRIT_VALID 0x00000400
71 #define SIU_ENC_CRIT_MASK 0x00000300
72 #define SIU_ENC_CRIT_SHIFT 8
73 #define SIU_ENC_MAIN_VALID 0x00200000
74 #define SIU_ENC_MAIN_MASK 0x001f0000
75 #define SIU_ENC_MAIN_SHIFT 16
76 #define SIU_ENC_PER_VALID 0x20000000
77 #define SIU_ENC_PER_MASK 0x1f000000
78 #define SIU_ENC_PER_SHIFT 24
79
80 #define SIU_ENC_CRIT_IS_CASCADE 2 /* critical "2" -> read peripheral */
81 #define SIU_ENC_MAIN_IS_CASCADE 4 /* main "4" -> read peripheral */
82
83 /* Reset state programmed at attach time. */
84 #define SIU_MAIN_MASK_INIT 0x0001efff /* mask all main but src 4 (per. cascade) */
85 #define SIU_PER_MASK_INIT 0xffffff00 /* mask all peripheral sources */
86
87 #define SIU_CTRL_INIT 0x0fc01001
88
89 /* Number of sources owned by this (primary) PIC. */
90 #define MPC5200_NIRQ 96
91
92 /* Band membership tests / source extraction for a flat IRQ number. */
93 #define MPC5200_IRQ_MAIN_BIT 0x20 /* flat 32.. -> main band */
94 #define MPC5200_IRQ_PER_BIT 0x40 /* flat 64.. -> peripheral band */
95 #define MPC5200_IRQ_SOURCE(irq) ((irq) & 0x1f)
96
97 static int mpc5200pic_match(device_t, cfdata_t, void *);
98 static void mpc5200pic_attach(device_t, device_t, void *);
99
100 CFATTACH_DECL_NEW(mpc5200pic, sizeof(struct mpc5200pic_softc),
101 mpc5200pic_match, mpc5200pic_attach, NULL, NULL);
102
103 static void mpc5200_enable_irq(struct pic_ops *, int, int);
104 static void mpc5200_disable_irq(struct pic_ops *, int);
105 static int mpc5200_get_irq(struct pic_ops *, int);
106 static void mpc5200_ack_irq(struct pic_ops *, int);
107 static void mpc5200_establish_irq(struct pic_ops *, int, int, int);
108
109 static inline uint32_t
110 siu_read(struct mpc5200_ops *m, bus_size_t off)
111 {
112 return bus_space_read_4(m->bst, m->bsh, off);
113 }
114
115 static inline void
116 siu_write(struct mpc5200_ops *m, bus_size_t off, uint32_t val)
117 {
118 bus_space_write_4(m->bst, m->bsh, off, val);
119 }
120
121 static bool
122 mpc5200pic_is_pic(int node, const char *name)
123 {
124 char compat[32];
125
126 if (OF_getprop(node, "compatible", compat, sizeof(compat)) > 0 &&
127 (strcmp(compat, "mpc5200-pic") == 0 ||
128 strcmp(compat, "mpc5200b-pic") == 0))
129 return true;
130
131 return strcmp(name, "pic") == 0;
132 }
133
134 static int
135 mpc5200pic_match(device_t parent, cfdata_t cf, void *aux)
136 {
137 struct obio_attach_args *oba = aux;
138
139 return mpc5200pic_is_pic(oba->obio_node, oba->obio_name) ? 1 : 0;
140 }
141
142 static void
143 mpc5200pic_attach(device_t parent, device_t self, void *aux)
144 {
145 struct mpc5200pic_softc *sc = device_private(self);
146 struct obio_attach_args *oba = aux;
147 struct mpc5200_ops *m = &sc->sc_pic;
148 struct pic_ops *pic = &m->pic;
149 bus_size_t size;
150
151 sc->sc_dev = self;
152
153 size = oba->obio_size != 0 ? oba->obio_size : SIU_REG_SIZE;
154 m->bst = oba->obio_bst;
155 if (bus_space_map(m->bst, oba->obio_addr, size, 0, &m->bsh) != 0) {
156 aprint_error(": can't map registers\n");
157 return;
158 }
159
160 pic->pic_cookie = m;
161 pic->pic_numintrs = MPC5200_NIRQ;
162 pic->pic_enable_irq = mpc5200_enable_irq;
163 pic->pic_reenable_irq = mpc5200_enable_irq;
164 pic->pic_disable_irq = mpc5200_disable_irq;
165 pic->pic_get_irq = mpc5200_get_irq;
166 pic->pic_ack_irq = mpc5200_ack_irq;
167 pic->pic_establish_irq = mpc5200_establish_irq;
168 pic->pic_finish_setup = NULL;
169 strlcpy(pic->pic_name, device_xname(self), sizeof(pic->pic_name));
170
171 m->main_mask = SIU_MAIN_MASK_INIT;
172 m->per_mask = SIU_PER_MASK_INIT;
173 m->ctrl = SIU_CTRL_INIT;
174
175 pic_add(pic);
176
177 siu_write(m, SIU_MAIN_MASK, m->main_mask);
178 siu_write(m, SIU_PER_MASK, m->per_mask);
179 siu_write(m, SIU_CTRL, m->ctrl);
180
181 /* Flat priorities: dispatch order comes from the encoded register. */
182 siu_write(m, SIU_PER_PRI1, 0);
183 siu_write(m, SIU_PER_PRI2, 0);
184 siu_write(m, SIU_PER_PRI3, 0);
185 siu_write(m, SIU_MAIN_PRI1, 0);
186 siu_write(m, SIU_MAIN_PRI2, 0);
187
188 aprint_normal(": MPC5200 SIU interrupt controller, %d sources\n",
189 MPC5200_NIRQ);
190 aprint_verbose_dev(self, "main_mask=0x%08x per_mask=0x%08x\n",
191 siu_read(m, SIU_MAIN_MASK), siu_read(m, SIU_PER_MASK));
192 }
193
194 static void
195 mpc5200_enable_irq(struct pic_ops *pic, int irq, int type)
196 {
197 struct mpc5200_ops *m = pic->pic_cookie;
198 uint32_t bit;
199
200 if (irq & MPC5200_IRQ_MAIN_BIT) { /* main */
201 if (MPC5200_IRQ_SOURCE(irq) > 16)
202 return;
203 bit = 1u << (16 - MPC5200_IRQ_SOURCE(irq));
204 m->main_mask &= ~bit;
205 siu_write(m, SIU_MAIN_MASK, m->main_mask);
206 } else if (irq & MPC5200_IRQ_PER_BIT) { /* peripheral */
207 if (MPC5200_IRQ_SOURCE(irq) > 23)
208 return;
209 bit = 1u << (31 - MPC5200_IRQ_SOURCE(irq));
210 m->per_mask &= ~bit;
211 siu_write(m, SIU_PER_MASK, m->per_mask);
212 } else { /* critical */
213 if (MPC5200_IRQ_SOURCE(irq) > 3)
214 return;
215 bit = (1u << (11 - irq)) | ((uint32_t)type << (22 - irq * 2));
216 m->ctrl |= bit;
217 siu_write(m, SIU_CTRL, m->ctrl);
218 }
219 }
220
221 static void
222 mpc5200_disable_irq(struct pic_ops *pic, int irq)
223 {
224 struct mpc5200_ops *m = pic->pic_cookie;
225 uint32_t bit;
226
227 /*
228 * Flat 36 == main source 4 is the peripheral cascade
229 */
230 if (irq == (32 + 4))
231 return;
232
233 if (irq & MPC5200_IRQ_MAIN_BIT) { /* main */
234 if (MPC5200_IRQ_SOURCE(irq) > 16)
235 return;
236 bit = 1u << (16 - MPC5200_IRQ_SOURCE(irq));
237 m->main_mask |= bit;
238 siu_write(m, SIU_MAIN_MASK, m->main_mask);
239 } else if (irq & MPC5200_IRQ_PER_BIT) { /* peripheral */
240 if (MPC5200_IRQ_SOURCE(irq) > 23)
241 return;
242 bit = 1u << (31 - MPC5200_IRQ_SOURCE(irq));
243 m->per_mask |= bit;
244 siu_write(m, SIU_PER_MASK, m->per_mask);
245 } else { /* critical */
246 if (MPC5200_IRQ_SOURCE(irq) > 3)
247 return;
248 bit = 1u << (11 - MPC5200_IRQ_SOURCE(irq));
249 m->ctrl &= ~bit;
250 siu_write(m, SIU_CTRL, m->ctrl);
251 }
252 }
253
254 static int
255 mpc5200_get_irq(struct pic_ops *pic, int mode)
256 {
257 struct mpc5200_ops *m = pic->pic_cookie;
258 uint32_t enc;
259 int source;
260
261 enc = siu_read(m, SIU_ENC_STATUS);
262
263 if (enc & SIU_ENC_CRIT_VALID) {
264 source = (enc & SIU_ENC_CRIT_MASK) >> SIU_ENC_CRIT_SHIFT;
265 if (source != SIU_ENC_CRIT_IS_CASCADE)
266 return source; /* flat 0..3 */
267 goto peripheral;
268 }
269 if (enc & SIU_ENC_MAIN_VALID) {
270 source = (enc & SIU_ENC_MAIN_MASK) >> SIU_ENC_MAIN_SHIFT;
271 if (source != SIU_ENC_MAIN_IS_CASCADE)
272 return source + 32; /* flat 32..48 */
273 goto peripheral;
274 }
275 if (enc & SIU_ENC_PER_VALID) {
276 peripheral:
277 source = (enc & SIU_ENC_PER_MASK) >> SIU_ENC_PER_SHIFT;
278 return source + 64; /* flat 64..87 */
279 }
280
281 return 255; /* no source pending: treated as spurious */
282 }
283
284 static void
285 mpc5200_ack_irq(struct pic_ops *pic, int irq)
286 {
287 /*
288 * The SIU has no end-of-interrupt register
289 */
290 }
291
292 static void
293 mpc5200_establish_irq(struct pic_ops *pic, int irq, int type, int maxlevel)
294 {
295 /*
296 * Nothing to program per source
297 */
298 }
299
300 /*
301 * Translate the index'th entry of an OF "interrupts" property into a flat IRQ.
302 */
303 static int
304 mpc5200_sense_to_ist(uint32_t sense)
305 {
306 /*
307 * The sense cell is only meaningful for the external (critical) IRQ
308 */
309 switch (sense) {
310 case 1:
311 return IST_EDGE_RISING;
312 case 2:
313 return IST_EDGE_FALLING;
314 default:
315 return IST_LEVEL;
316 }
317 }
318
319 int
320 obio_decode_interrupt(int node, int index, int *irqp, int *typep)
321 {
322 uint32_t intr[3 * 16]; /* up to 16 interrupts, 3 cells each */
323 uint32_t tier, source;
324 int len, count, base;
325
326 len = OF_getprop(node, "interrupts", intr, sizeof(intr));
327 if (len < (int)(3 * sizeof(uint32_t)))
328 return 0;
329 count = len / (int)(3 * sizeof(uint32_t));
330 if (index < 0 || index >= count)
331 return 0;
332
333 tier = intr[index * 3 + 0];
334 source = intr[index * 3 + 1];
335
336 switch (tier) {
337 case 0:
338 base = 0; /* critical -> flat 0.. */
339 break;
340 case 1:
341 base = 32; /* main -> flat 32.. */
342 break;
343 case 2:
344 base = 64; /* peripheral -> flat 64.. */
345 break;
346 case 3:
347 base = 96; /* BestComm -> flat 96.. (secondary PIC) */
348 break;
349 default:
350 return 0;
351 }
352
353 *irqp = base + source;
354 *typep = mpc5200_sense_to_ist(intr[index * 3 + 2]);
355 return 1;
356 }
357