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