gusc.c revision 62483
1/*-
2 * Copyright (c) 1999 Seigo Tanimura
3 * Copyright (c) 1999 Ville-Pertti Keinonen
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 * SUCH DAMAGE.
26 *
27 * $FreeBSD: head/sys/dev/sound/isa/gusc.c 62483 2000-07-03 20:52:27Z cg $
28 */
29
30#include <sys/param.h>
31#include <sys/systm.h>
32#include <sys/kernel.h>
33#include <sys/bus.h>
34#include <sys/malloc.h>
35#include <sys/module.h>
36#include <machine/resource.h>
37#include <machine/bus.h>
38#include <machine/clock.h>
39#include <sys/rman.h>
40#include <sys/soundcard.h>
41#include <dev/sound/pcm/sound.h>
42#include <dev/sound/chip.h>
43#include "bus_if.h"
44
45#include <isa/isavar.h>
46#include <isa/isa_common.h>
47#ifdef __alpha__		/* XXX workaround a stupid warning */
48#include <alpha/isa/isavar.h>
49#endif
50
51#define LOGICALID_NOPNP 0
52#define LOGICALID_PCM   0x0000561e
53#define LOGICALID_OPL   0x0300561e
54#define LOGICALID_MIDI  0x0400561e
55
56/* Interrupt handler.  */
57struct gusc_ihandler {
58	void (*intr)(void *);
59	void *arg;
60};
61
62/* Here is the parameter structure per a device. */
63struct gusc_softc {
64	device_t dev; /* device */
65	int io_rid[3]; /* io port rids */
66	struct resource *io[3]; /* io port resources */
67	int io_alloced[3]; /* io port alloc flag */
68	int irq_rid; /* irq rids */
69	struct resource *irq; /* irq resources */
70	int irq_alloced; /* irq alloc flag */
71	int drq_rid[2]; /* drq rids */
72	struct resource *drq[2]; /* drq resources */
73	int drq_alloced[2]; /* drq alloc flag */
74
75	/* Interrupts are shared (XXX non-PnP only?) */
76	struct gusc_ihandler midi_intr;
77	struct gusc_ihandler pcm_intr;
78};
79
80typedef struct gusc_softc *sc_p;
81
82static int gusc_probe(device_t dev);
83static int gusc_attach(device_t dev);
84static int gusisa_probe(device_t dev);
85static void gusc_intr(void *);
86static struct resource *gusc_alloc_resource(device_t bus, device_t child, int type, int *rid,
87					      u_long start, u_long end, u_long count, u_int flags);
88static int gusc_release_resource(device_t bus, device_t child, int type, int rid,
89				   struct resource *r);
90
91#if notyet
92static device_t find_masterdev(sc_p scp);
93#endif /* notyet */
94static int alloc_resource(sc_p scp);
95static int release_resource(sc_p scp);
96
97static devclass_t gusc_devclass;
98
99static int
100gusc_probe(device_t dev)
101{
102	device_t child;
103	u_int32_t vend_id, logical_id;
104	char *s;
105	struct sndcard_func *func;
106
107	vend_id = isa_get_vendorid(dev);
108	if (vend_id == 0)
109		return gusisa_probe(dev);
110
111	logical_id = isa_get_logicalid(dev);
112	s = NULL;
113
114	if (vend_id == 0x0100561e) { /* Gravis */
115		switch (logical_id) {
116		case LOGICALID_PCM:
117			s = "Gravis UltraSound Plug & Play PCM";
118			func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT);
119			if (func == NULL)
120				return (ENOMEM);
121			bzero(func, sizeof(*func));
122			func->func = SCF_PCM;
123			child = device_add_child(dev, "pcm", -1);
124			device_set_ivars(child, func);
125			break;
126#if notyet
127		case LOGICALID_OPL:
128			s = "Gravis UltraSound Plug & Play OPL";
129			func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT);
130			if (func == NULL)
131				return (ENOMEM);
132			bzero(func, sizeof(*func));
133			func->func = SCF_SYNTH;
134			child = device_add_child(dev, "midi", -1);
135			device_set_ivars(child, func);
136			break;
137		case LOGICALID_MIDI:
138			s = "Gravis UltraSound Plug & Play MIDI";
139			func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT);
140			if (func == NULL)
141				return (ENOMEM);
142			bzero(func, sizeof(*func));
143			func->func = SCF_MIDI;
144			child = device_add_child(dev, "midi", -1);
145			device_set_ivars(child, func);
146			break;
147#endif /* notyet */
148		}
149	}
150
151	if (s != NULL) {
152		device_set_desc(dev, s);
153		return (0);
154	}
155
156	return (ENXIO);
157}
158
159static void
160port_wr(struct resource *r, int i, unsigned char v)
161{
162	bus_space_write_1(rman_get_bustag(r), rman_get_bushandle(r), i, v);
163}
164
165static int
166port_rd(struct resource *r, int i)
167{
168	return bus_space_read_1(rman_get_bustag(r), rman_get_bushandle(r), i);
169}
170
171/*
172 * Probe for an old (non-PnP) GUS card on the ISA bus.
173 */
174
175static int
176gusisa_probe(device_t dev)
177{
178	device_t child;
179	struct resource *res, *res2;
180	int base, rid, rid2, s, flags;
181	unsigned char val;
182
183	base = isa_get_port(dev);
184	flags = device_get_flags(dev);
185	rid = 1;
186	res = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid, base + 0x100,
187				 base + 0x107, 8, RF_ACTIVE);
188
189	if (res == NULL)
190		return ENXIO;
191
192	res2 = NULL;
193
194	/*
195	 * Check for the presence of some GUS card.  Reset the card,
196	 * then see if we can access the memory on it.
197	 */
198
199	port_wr(res, 3, 0x4c);
200	port_wr(res, 5, 0);
201	DELAY(30 * 1000);
202
203	port_wr(res, 3, 0x4c);
204	port_wr(res, 5, 1);
205	DELAY(30 * 1000);
206
207	s = splhigh();
208
209	/* Write to DRAM.  */
210
211	port_wr(res, 3, 0x43);		/* Register select */
212	port_wr(res, 4, 0);		/* Low addr */
213	port_wr(res, 5, 0);		/* Med addr */
214
215	port_wr(res, 3, 0x44);		/* Register select */
216	port_wr(res, 4, 0);		/* High addr */
217	port_wr(res, 7, 0x55);		/* DRAM */
218
219	/* Read from DRAM.  */
220
221	port_wr(res, 3, 0x43);		/* Register select */
222	port_wr(res, 4, 0);		/* Low addr */
223	port_wr(res, 5, 0);		/* Med addr */
224
225	port_wr(res, 3, 0x44);		/* Register select */
226	port_wr(res, 4, 0);		/* High addr */
227	val = port_rd(res, 7);		/* DRAM */
228
229	splx(s);
230
231	if (val != 0x55)
232		goto fail;
233
234	rid2 = 0;
235	res2 = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid2, base, base, 1,
236				  RF_ACTIVE);
237
238	if (res2 == NULL)
239		goto fail;
240
241	s = splhigh();
242	port_wr(res2, 0x0f, 0x20);
243	val = port_rd(res2, 0x0f);
244	splx(s);
245
246	if (val == 0xff || (val & 0x06) == 0)
247		val = 0;
248	else {
249		val = port_rd(res2, 0x506);	/* XXX Out of range.  */
250		if (val == 0xff)
251			val = 0;
252	}
253
254	bus_release_resource(dev, SYS_RES_IOPORT, rid2, res2);
255	bus_release_resource(dev, SYS_RES_IOPORT, rid, res);
256
257	if (val >= 10) {
258		struct sndcard_func *func;
259
260		/* Looks like a GUS MAX.  Set the rest of the resources.  */
261
262		bus_set_resource(dev, SYS_RES_IOPORT, 2, base + 0x10c, 8);
263
264		if (flags & DV_F_DUAL_DMA)
265			bus_set_resource(dev, SYS_RES_DRQ, 1,
266					 flags & DV_F_DRQ_MASK, 1);
267
268#if notyet
269		/* We can support the CS4231 and MIDI devices.  */
270
271		func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT);
272		if (func == NULL)
273			return ENOMEM;
274		bzero(func, sizeof *func);
275		func->func = SCF_MIDI;
276		child = device_add_child(dev, "midi", -1);
277		device_set_ivars(child, func);
278#endif /* notyet */
279
280		func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT);
281		if (func == NULL)
282			printf("xxx: gus pcm not attached, out of memory\n");
283		else {
284			bzero(func, sizeof *func);
285			func->func = SCF_PCM;
286			child = device_add_child(dev, "pcm", -1);
287			device_set_ivars(child, func);
288		}
289		device_set_desc(dev, "Gravis UltraSound MAX");
290		return 0;
291	} else {
292
293		/*
294		 * TODO: Support even older GUS cards.  MIDI should work on
295		 * all models.
296		 */
297		return ENXIO;
298	}
299
300fail:
301	bus_release_resource(dev, SYS_RES_IOPORT, rid, res);
302	return ENXIO;
303}
304
305static int
306gusc_attach(device_t dev)
307{
308	sc_p scp;
309	int unit;
310	void *ih;
311
312	scp = device_get_softc(dev);
313	unit = device_get_unit(dev);
314
315	bzero(scp, sizeof(*scp));
316
317	scp->dev = dev;
318	if (alloc_resource(scp)) {
319		release_resource(scp);
320		return (ENXIO);
321	}
322
323	bus_setup_intr(dev, scp->irq, INTR_TYPE_TTY, gusc_intr, scp, &ih);
324	bus_generic_attach(dev);
325
326	return (0);
327}
328
329/*
330 * Handle interrupts on GUS devices until there aren't any left.
331 */
332static void
333gusc_intr(void *arg)
334{
335	sc_p scp = (sc_p)arg;
336	int did_something;
337
338	do {
339		did_something = 0;
340		if (scp->pcm_intr.intr != NULL &&
341		    (port_rd(scp->io[2], 2) & 1)) {
342			(*scp->pcm_intr.intr)(scp->pcm_intr.arg);
343			did_something = 1;
344		}
345#if notyet
346		if (scp->midi_intr.intr != NULL &&
347		    (port_rd(scp->io[1], 0) & 0x80)) {
348			(*scp->midi_intr.intr)(scp->midi_intr.arg);
349			did_something = 1;
350		}
351#endif /* notyet */
352	} while (did_something != 0);
353}
354
355static struct resource *
356gusc_alloc_resource(device_t bus, device_t child, int type, int *rid,
357		      u_long start, u_long end, u_long count, u_int flags)
358{
359	sc_p scp;
360	int *alloced, rid_max, alloced_max;
361	struct resource **res;
362
363	scp = device_get_softc(bus);
364	switch (type) {
365	case SYS_RES_IOPORT:
366		alloced = scp->io_alloced;
367		res = scp->io;
368		rid_max = 2;
369		alloced_max = 2; /* pcm + midi (more to include synth) */
370		break;
371	case SYS_RES_IRQ:
372		alloced = &scp->irq_alloced;
373		res = &scp->irq;
374		rid_max = 0;
375		alloced_max = 2; /* pcm and midi share the single irq. */
376		break;
377	case SYS_RES_DRQ:
378		alloced = scp->drq_alloced;
379		res = scp->drq;
380		rid_max = 1;
381		alloced_max = 1;
382		break;
383	default:
384		return (NULL);
385	}
386
387	if (*rid > rid_max || alloced[*rid] == alloced_max)
388		return (NULL);
389
390	alloced[*rid]++;
391	return (res[*rid]);
392}
393
394static int
395gusc_release_resource(device_t bus, device_t child, int type, int rid,
396			struct resource *r)
397{
398	sc_p scp;
399	int *alloced, rid_max;
400
401	scp = device_get_softc(bus);
402	switch (type) {
403	case SYS_RES_IOPORT:
404		alloced = scp->io_alloced;
405		rid_max = 2;
406		break;
407	case SYS_RES_IRQ:
408		alloced = &scp->irq_alloced;
409		rid_max = 0;
410		break;
411	case SYS_RES_DRQ:
412		alloced = scp->drq_alloced;
413		rid_max = 1;
414		break;
415	default:
416		return (1);
417	}
418
419	if (rid > rid_max || alloced[rid] == 0)
420		return (1);
421
422	alloced[rid]--;
423	return (0);
424}
425
426static int
427gusc_setup_intr(device_t dev, device_t child, struct resource *irq,
428		int flags, driver_intr_t *intr, void *arg, void **cookiep)
429{
430	sc_p scp = (sc_p)device_get_softc(dev);
431	devclass_t devclass;
432
433	devclass = device_get_devclass(child);
434	if (strcmp(devclass_get_name(devclass), "midi") == 0) {
435		scp->midi_intr.intr = intr;
436		scp->midi_intr.arg = arg;
437		return 0;
438	} else if (strcmp(devclass_get_name(devclass), "pcm") == 0) {
439		scp->pcm_intr.intr = intr;
440		scp->pcm_intr.arg = arg;
441		return 0;
442	}
443	return bus_generic_setup_intr(dev, child, irq, flags, intr,
444				      arg, cookiep);
445}
446
447#if notyet
448static device_t
449find_masterdev(sc_p scp)
450{
451	int i, units;
452	devclass_t devclass;
453	device_t dev;
454
455	devclass = device_get_devclass(scp->dev);
456	units = devclass_get_maxunit(devclass);
457	dev = NULL;
458	for (i = 0 ; i < units ; i++) {
459		dev = devclass_get_device(devclass, i);
460		if (isa_get_vendorid(dev) == isa_get_vendorid(scp->dev)
461		    && isa_get_logicalid(dev) == LOGICALID_PCM
462		    && isa_get_serial(dev) == isa_get_serial(scp->dev))
463			break;
464	}
465	if (i == units)
466		return (NULL);
467
468	return (dev);
469}
470#endif /* notyet */
471
472static int io_range[3]  = {0x10, 0x8  , 0x4  };
473static int io_offset[3] = {0x0 , 0x100, 0x10c};
474static int
475alloc_resource(sc_p scp)
476{
477	int i, base, lid, flags;
478#if notyet
479	device_t dev;
480#endif /* notyet */
481
482	flags = 0;
483	if (isa_get_vendorid(scp->dev))
484		lid = isa_get_logicalid(scp->dev);
485	else {
486		lid = LOGICALID_NOPNP;
487		flags = device_get_flags(scp->dev);
488	}
489	switch(lid) {
490	case LOGICALID_PCM:
491	case LOGICALID_NOPNP:		/* XXX Non-PnP */
492		if (lid == LOGICALID_NOPNP)
493			base = isa_get_port(scp->dev);
494		else
495			base = 0;
496		for (i = 0 ; i < sizeof(scp->io) / sizeof(*scp->io) ; i++) {
497			if (scp->io[i] == NULL) {
498				scp->io_rid[i] = i;
499				if (base == 0)
500					scp->io[i] = bus_alloc_resource(scp->dev, SYS_RES_IOPORT, &scp->io_rid[i],
501									0, ~0, io_range[i], RF_ACTIVE);
502				else
503					scp->io[i] = bus_alloc_resource(scp->dev, SYS_RES_IOPORT, &scp->io_rid[i],
504									base + io_offset[i],
505									base + io_offset[i] + io_range[i] - 1
506									, io_range[i], RF_ACTIVE);
507				if (scp->io[i] == NULL)
508					return (1);
509				scp->io_alloced[i] = 0;
510			}
511		}
512		if (scp->irq == NULL) {
513			scp->irq_rid = 0;
514			scp->irq = bus_alloc_resource(scp->dev, SYS_RES_IRQ, &scp->irq_rid,
515						      0, ~0, 1, RF_ACTIVE | RF_SHAREABLE);
516			if (scp->irq == NULL)
517				return (1);
518			scp->irq_alloced = 0;
519		}
520		for (i = 0 ; i < sizeof(scp->drq) / sizeof(*scp->drq) ; i++) {
521			if (scp->drq[i] == NULL) {
522				scp->drq_rid[i] = i;
523				if (base == 0 || i == 0)
524					scp->drq[i] = bus_alloc_resource(scp->dev, SYS_RES_DRQ, &scp->drq_rid[i],
525									 0, ~0, 1, RF_ACTIVE);
526				else if ((flags & DV_F_DUAL_DMA) != 0)
527					/* XXX The secondary drq is specified in the flag. */
528					scp->drq[i] = bus_alloc_resource(scp->dev, SYS_RES_DRQ, &scp->drq_rid[i],
529									 flags & DV_F_DRQ_MASK,
530									 flags & DV_F_DRQ_MASK, 1, RF_ACTIVE);
531				if (scp->drq[i] == NULL)
532					return (1);
533				scp->drq_alloced[i] = 0;
534			}
535		}
536		break;
537#if notyet
538	case LOGICALID_OPL:
539		if (scp->io[0] == NULL) {
540			scp->io_rid[0] = 0;
541			scp->io[0] = bus_alloc_resource(scp->dev, SYS_RES_IOPORT, &scp->io_rid[0],
542							0, ~0, io_range[0], RF_ACTIVE);
543			if (scp->io[0] == NULL)
544				return (1);
545			scp->io_alloced[0] = 0;
546		}
547		break;
548	case LOGICALID_MIDI:
549		if (scp->io[0] == NULL) {
550			scp->io_rid[0] = 0;
551			scp->io[0] = bus_alloc_resource(scp->dev, SYS_RES_IOPORT, &scp->io_rid[0],
552							0, ~0, io_range[0], RF_ACTIVE);
553			if (scp->io[0] == NULL)
554				return (1);
555			scp->io_alloced[0] = 0;
556		}
557		if (scp->irq == NULL) {
558			/* The irq is shared with pcm audio. */
559			dev = find_masterdev(scp);
560			if (dev == NULL)
561				return (1);
562			scp->irq_rid = 0;
563			scp->irq = BUS_ALLOC_RESOURCE(dev, NULL, SYS_RES_IRQ, &scp->irq_rid,
564						      0, ~0, 1, RF_ACTIVE | RF_SHAREABLE);
565			if (scp->irq == NULL)
566				return (1);
567			scp->irq_alloced = 0;
568		}
569		break;
570#endif /* notyet */
571	}
572	return (0);
573}
574
575static int
576release_resource(sc_p scp)
577{
578	int i, lid, flags;
579#if notyet
580	device_t dev;
581#endif /* notyet */
582
583	flags = 0;
584	if (isa_get_vendorid(scp->dev))
585		lid = isa_get_logicalid(scp->dev);
586	else {
587		lid = LOGICALID_NOPNP;
588		flags = device_get_flags(scp->dev);
589	}
590	switch(lid) {
591	case LOGICALID_PCM:
592	case LOGICALID_NOPNP:		/* XXX Non-PnP */
593		for (i = 0 ; i < sizeof(scp->io) / sizeof(*scp->io) ; i++) {
594			if (scp->io[i] != NULL) {
595				bus_release_resource(scp->dev, SYS_RES_IOPORT, scp->io_rid[i], scp->io[i]);
596				scp->io[i] = NULL;
597			}
598		}
599		if (scp->irq != NULL) {
600			bus_release_resource(scp->dev, SYS_RES_IRQ, scp->irq_rid, scp->irq);
601			scp->irq = NULL;
602		}
603		for (i = 0 ; i < sizeof(scp->drq) / sizeof(*scp->drq) ; i++) {
604			if (scp->drq[i] != NULL) {
605				bus_release_resource(scp->dev, SYS_RES_DRQ, scp->drq_rid[i], scp->drq[i]);
606				scp->drq[i] = NULL;
607			}
608		}
609		break;
610#if notyet
611	case LOGICALID_OPL:
612		if (scp->io[0] != NULL) {
613			bus_release_resource(scp->dev, SYS_RES_IOPORT, scp->io_rid[0], scp->io[0]);
614			scp->io[0] = NULL;
615		}
616		break;
617	case LOGICALID_MIDI:
618		if (scp->io[0] != NULL) {
619			bus_release_resource(scp->dev, SYS_RES_IOPORT, scp->io_rid[0], scp->io[0]);
620			scp->io[0] = NULL;
621		}
622		if (scp->irq != NULL) {
623			/* The irq is shared with pcm audio. */
624			dev = find_masterdev(scp);
625			if (dev == NULL)
626				return (1);
627			BUS_RELEASE_RESOURCE(dev, NULL, SYS_RES_IOPORT, scp->irq_rid, scp->irq);
628			scp->irq = NULL;
629		}
630		break;
631#endif /* notyet */
632	}
633	return (0);
634}
635
636static device_method_t gusc_methods[] = {
637	/* Device interface */
638	DEVMETHOD(device_probe,		gusc_probe),
639	DEVMETHOD(device_attach,	gusc_attach),
640	DEVMETHOD(device_detach,	bus_generic_detach),
641	DEVMETHOD(device_shutdown,	bus_generic_shutdown),
642	DEVMETHOD(device_suspend,	bus_generic_suspend),
643	DEVMETHOD(device_resume,	bus_generic_resume),
644
645	/* Bus interface */
646	DEVMETHOD(bus_print_child,	bus_generic_print_child),
647	DEVMETHOD(bus_alloc_resource,	gusc_alloc_resource),
648	DEVMETHOD(bus_release_resource,	gusc_release_resource),
649	DEVMETHOD(bus_activate_resource, bus_generic_activate_resource),
650	DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource),
651	DEVMETHOD(bus_setup_intr,	gusc_setup_intr),
652	DEVMETHOD(bus_teardown_intr,	bus_generic_teardown_intr),
653
654	{ 0, 0 }
655};
656
657static driver_t gusc_driver = {
658	"gusc",
659	gusc_methods,
660	sizeof(struct gusc_softc),
661};
662
663/*
664 * gusc can be attached to an isa bus.
665 */
666DRIVER_MODULE(snd_gusc, isa, gusc_driver, gusc_devclass, 0, 0);
667MODULE_DEPEND(snd_gusc, snd_pcm, PCM_MINVER, PCM_PREFVER, PCM_MAXVER);
668MODULE_VERSION(snd_gusc, 1);
669
670
671