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