1 /* $OpenBSD: cbus.c,v 1.9 2021/03/11 11:16:58 jsg Exp $ */
2
3 /*
4 * Copyright (c) 2014 Kenji Aoyama.
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19 /*
20 * PC-9801 extension board slot bus ('C-bus') driver for LUNA-88K2.
21 */
22
23 #include <sys/param.h>
24 #include <sys/device.h>
25 #include <sys/malloc.h>
26 #include <sys/systm.h>
27
28 #include <machine/asm_macro.h> /* ff1() */
29 #include <machine/autoconf.h>
30 #include <machine/board.h> /* PC_BASE */
31
32 #include <luna88k/cbus/cbusvar.h>
33 #include <luna88k/luna88k/isr.h> /* isrlink_autovec() */
34
35 #if 0
36 #define CBUS_DEBUG
37 #endif
38
39 #include "ne.h"
40 #include "necsb.h"
41 #include "pcic.h"
42
43 static struct cbus_attach_args cbus_devs[] = {
44 #if NNE > 0
45 /* NE-2000 compatible ethernet */
46 { "ne", -1, -1, -1, -1, -1 },
47 #endif
48 #if NNECSB > 0
49 /* PC-9801-86 sound board */
50 { "necsb", -1, -1, -1, -1, -1 },
51 #endif
52 #if NPCIC > 0
53 /* PC-9801-102 & PC-9821X[AE]-01 PCMCIA board */
54 { "pcic", -1, -1, -1, -1, -1 },
55 #endif
56 /* C-bus "generic" driver */
57 { "pcex", -1, -1, -1, -1, -1 }
58 };
59
60 /*
61 * C-bus interrupt status register
62 */
63 #define CBUS_INTR_STAT_REG (PC_BASE + 0x1100000)
64 volatile u_int8_t *cbus_isreg = (u_int8_t *)CBUS_INTR_STAT_REG;
65
66 /* autoconf stuff */
67 int cbus_match(struct device *, void *, void *);
68 void cbus_attach(struct device *, struct device *, void *);
69 int cbus_print(void *, const char *);
70
71 struct cbus_softc {
72 struct device sc_dev;
73 struct cbus_isr_t cbus_isr[NCBUSISR];
74 u_int8_t registered;
75 };
76
77 const struct cfattach cbus_ca = {
78 sizeof(struct cbus_softc), cbus_match, cbus_attach
79 };
80
81 struct cfdriver cbus_cd = {
82 NULL, "cbus", DV_DULL
83 };
84
85 /* prototypes */
86 void cbus_isrdispatch(int);
87 int cbus_intr(void *);
88
89 int
cbus_match(struct device * parent,void * cf,void * aux)90 cbus_match(struct device *parent, void *cf, void *aux)
91 {
92 struct mainbus_attach_args *ma = aux;
93
94 if (strcmp(ma->ma_name, cbus_cd.cd_name))
95 return 0;
96 #if 0
97 if (badaddr((vaddr_t)ma->ma_addr, 4))
98 return 0;
99 #endif
100 return 1;
101 }
102
103 void
cbus_attach(struct device * parent,struct device * self,void * args)104 cbus_attach(struct device *parent, struct device *self, void *args)
105 {
106 struct cbus_softc *sc = (struct cbus_softc *)self;
107 struct mainbus_attach_args *ma = args;
108 int i;
109
110 for (i = 0; i < NCBUSISR; i++) {
111 struct cbus_isr_t *ci = &sc->cbus_isr[i];
112 ci->isr_func = NULL;
113 ci->isr_arg = NULL;
114 ci->isr_intlevel = ci->isr_ipl = -1;
115 /* clearing interrupt flags (INT0-INT6) */
116 *cbus_isreg = (u_int8_t)(6 - i);
117 }
118 sc->registered = 0x00;
119
120 /* register C-bus interrupt service routine on mainbus */
121 isrlink_autovec(cbus_intr, (void *)self, ma->ma_ilvl,
122 ISRPRI_TTY, self->dv_xname);
123
124 printf("\n");
125
126 for (i = 0; i < sizeof(cbus_devs)/sizeof(cbus_devs[0]); i++)
127 config_found(self, &cbus_devs[i], cbus_print);
128
129 return;
130 }
131
132 int
cbus_print(void * aux,const char * pnp)133 cbus_print(void *aux, const char *pnp)
134 {
135 struct cbus_attach_args *caa = aux;
136
137 if (pnp)
138 printf("%s at %s", caa->ca_name, pnp); /* not configured */
139 if (caa->ca_iobase != -1)
140 printf(" port 0x%x", caa->ca_iobase);
141 if (caa->ca_maddr != -1)
142 printf(" addr 0x%x", caa->ca_maddr);
143 if (caa->ca_int != -1)
144 printf(" int %d", caa->ca_int);
145
146 return UNCONF;
147 }
148
149 /*
150 * Register a C-bus interrupt service routine.
151 */
152 int
cbus_isrlink(int (* func)(void *),void * arg,int intlevel,int ipl,const char * name)153 cbus_isrlink(int (*func)(void *), void *arg, int intlevel, int ipl,
154 const char *name)
155 {
156 struct cbus_softc *sc = NULL;
157 struct cbus_isr_t *ci;
158
159 if (cbus_cd.cd_ndevs != 0)
160 sc = cbus_cd.cd_devs[0];
161 if (sc == NULL)
162 panic("cbus_isrlink: can't find cbus_softc");
163
164 #ifdef DIAGNOSTIC
165 if (intlevel < 0 || intlevel >= NCBUSISR) {
166 printf("cbus_isrlink: bad INT level %d\n", intlevel);
167 return -1;
168 }
169 #endif
170
171 ci = &sc->cbus_isr[intlevel];
172
173 if (ci->isr_func != NULL) {
174 printf("cbus_isrlink: isr already assigned on INT%d\n",
175 intlevel);
176 return -1;
177 }
178
179 /* set the entry */
180 ci->isr_func = func;
181 ci->isr_arg = arg;
182 ci->isr_intlevel = intlevel;
183 ci->isr_ipl = ipl;
184 evcount_attach(&ci->isr_count, name, &ci->isr_intlevel);
185 sc->registered |= (1 << (6 - intlevel));
186 #ifdef CBUS_DEBUG
187 printf("cbus_isrlink: sc->registered = 0x%02x\n", sc->registered);
188 #endif
189
190 return 0;
191 }
192
193 /*
194 * Unregister a C-bus interrupt service routine.
195 */
196 int
cbus_isrunlink(int (* func)(void *),int intlevel)197 cbus_isrunlink(int (*func)(void *), int intlevel)
198 {
199 struct cbus_softc *sc = NULL;
200 struct cbus_isr_t *ci;
201
202 if (cbus_cd.cd_ndevs != 0)
203 sc = cbus_cd.cd_devs[0];
204 if (sc == NULL)
205 panic("cbus_isrunlink: can't find cbus_softc");
206
207 #ifdef DIAGNOSTIC
208 if (intlevel < 0 || intlevel >= NCBUSISR) {
209 printf("cbus_isrunlink: bad INT level %d\n", intlevel);
210 return -1;
211 }
212 #endif
213
214 ci = &sc->cbus_isr[intlevel];
215
216 if (ci->isr_func == NULL) {
217 printf("cbus_isrunlink: isr not assigned on INT%d\n", intlevel);
218 return -1;
219 }
220
221 /* reset the entry */
222 ci->isr_func = NULL;
223 ci->isr_arg = NULL;
224 ci->isr_intlevel = ci->isr_ipl = -1;
225 evcount_detach(&ci->isr_count);
226 sc->registered &= ~(1 << (6 - intlevel));
227
228 /* clear interrupt flags */
229 *cbus_isreg = (u_int8_t)(6 - intlevel);
230 #ifdef CBUS_DEBUG
231 printf("cbus_isrunlink: sc->registered = 0x%02x\n", sc->registered);
232 #endif
233
234 return 0;
235 }
236
237 /*
238 * Dispatch C-bus interrupt service routines.
239 */
240 void
cbus_isrdispatch(int intlevel)241 cbus_isrdispatch(int intlevel)
242 {
243 int rc, s;
244 static int straycount, unexpected;
245 struct cbus_softc *sc = NULL;
246 struct cbus_isr_t *ci;
247
248 if (cbus_cd.cd_ndevs != 0)
249 sc = cbus_cd.cd_devs[0];
250 if (sc == NULL)
251 panic("cbus_isrdispatch: can't find cbus_softc");
252
253 #ifdef DIAGNOSTIC
254 if (intlevel < 0 || intlevel >= NCBUSISR)
255 panic("cbus_isrdispatch: bad INT level 0x%d", intlevel);
256 #endif
257
258 ci = &sc->cbus_isr[intlevel];
259
260 if (ci->isr_func == NULL) {
261 printf("cbus_isrdispatch: INT%d unexpected\n", intlevel);
262 if (++unexpected > 10)
263 panic("too many unexpected interrupts");
264 return;
265 }
266
267 s = splraise(ci->isr_ipl);
268 rc = ci->isr_func(ci->isr_arg);
269 splx(s);
270
271 if (rc != 0)
272 ci->isr_count.ec_count++;
273
274 if (rc)
275 straycount = 0;
276 else if (++straycount > 50)
277 panic("cbus_isrdispatch: too many stray interrupts");
278 else
279 printf("cbus_isrdispatch: stray INT%d, IPL=%d\n", intlevel,
280 ci->isr_ipl);
281 }
282
283 /*
284 * Return registered status of interrupt service routines.
285 */
286 u_int8_t
cbus_intr_registered(void)287 cbus_intr_registered(void)
288 {
289 struct cbus_softc *sc = NULL;
290
291 if (cbus_cd.cd_ndevs != 0)
292 sc = cbus_cd.cd_devs[0];
293 if (sc == NULL)
294 panic("cbus_intr_used: can't find cbus_softc");
295
296 return sc->registered;
297 }
298
299 /*
300 * Note about interrupt on PC-9801 extension board slot
301 *
302 * PC-9801 extension board slot bus (so-called 'C-bus' in Japan) use 8 own
303 * interrupt levels, INT0-INT6, and NMI. On LUNA-88K2, they all trigger
304 * level 4 interrupt on mainbus, so we need to check the dedicated interrupt
305 * status register to know which C-bus interrupt is occurred.
306 *
307 * The interrupt status register for C-bus is located at
308 * (u_int8_t *)CBUS_INTR_STAT. Each bit of the register becomes 0 when
309 * corresponding C-bus interrupt has occurred, otherwise 1.
310 *
311 * bit 7 = NMI(?)
312 * bit 6 = INT0
313 * bit 5 = INT1
314 * :
315 * bit 0 = INT6
316 *
317 * To clear the C-bus interrupt flag, write the corresponding 'bit' number
318 * (as u_int_8) to the register. For example, if you want to clear INT1,
319 * you should write '5' like:
320 * *(u_int8_t *)CBUS_INTR_STAT = 5;
321 */
322
323 /*
324 * Interrupt handler on mainbus.
325 */
326 int
cbus_intr(void * arg)327 cbus_intr(void *arg)
328 {
329 struct cbus_softc *sc = (struct cbus_softc *)arg;
330 u_int8_t intr_status;
331 int n;
332
333 /*
334 * LUNA-88K2's interrupt level 4 is shared with other devices,
335 * such as le(4), for example. So we check:
336 * - the value of our C-bus interrupt status register, and
337 * - if the INT level is what we are looking for.
338 */
339 intr_status = *cbus_isreg & sc->registered;
340 if (intr_status == sc->registered) return 0; /* Not for me */
341
342 #ifdef CBUS_DEBUG
343 printf("cbus_intr: called, *cbus_isreg=0x%02x, registered = 0x%02x\n",
344 *cbus_isreg, sc->registered);
345 #endif
346 /* Make the bit pattern that we should process */
347 intr_status = intr_status ^ sc->registered;
348 #ifdef CBUS_DEBUG
349 printf("cbus_intr: processing 0x%02x\n", intr_status);
350 #endif
351
352 /* Process, and clear each interrupt flag */
353 while ((n = ff1(intr_status)) != 32) {
354 cbus_isrdispatch(6 - n);
355 *cbus_isreg = (u_int8_t)n;
356 intr_status &= ~(1 << n);
357 }
358
359 return 1;
360 }
361