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
43static 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)
64volatile u_int8_t *cbus_isreg = (u_int8_t *)CBUS_INTR_STAT_REG;
65
66/* autoconf stuff */
67int cbus_match(struct device *, void *, void *);
68void cbus_attach(struct device *, struct device *, void *);
69int cbus_print(void *, const char *);
70
71struct cbus_softc {
72	struct device sc_dev;
73	struct cbus_isr_t cbus_isr[NCBUSISR];
74	u_int8_t registered;
75};
76
77const struct cfattach cbus_ca = {
78	sizeof(struct cbus_softc), cbus_match, cbus_attach
79};
80
81struct cfdriver cbus_cd = {
82	NULL, "cbus", DV_DULL
83};
84
85/* prototypes */
86void cbus_isrdispatch(int);
87int cbus_intr(void *);
88
89int
90cbus_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
103void
104cbus_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
132int
133cbus_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 */
152int
153cbus_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 */
196int
197cbus_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 */
240void
241cbus_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 */
286u_int8_t
287cbus_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 */
326int
327cbus_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