gusc.c revision 166901
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 166901 2007-02-23 12:19:07Z piso $");
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		bus_setup_intr(dev, scp->irq, INTR_TYPE_AV, NULL, 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,
422		int flags, driver_filter_t *filter, driver_intr_t *intr, void *arg, void **cookiep)
423{
424	sc_p scp = (sc_p)device_get_softc(dev);
425	devclass_t devclass;
426
427	if (filter != NULL) {
428		printf("gusc.c: we cannot use a filter here\n");
429		return (EINVAL);
430	}
431	devclass = device_get_devclass(child);
432	if (strcmp(devclass_get_name(devclass), "midi") == 0) {
433		scp->midi_intr.intr = intr;
434		scp->midi_intr.arg = arg;
435		return 0;
436	} else if (strcmp(devclass_get_name(devclass), "pcm") == 0) {
437		scp->pcm_intr.intr = intr;
438		scp->pcm_intr.arg = arg;
439		return 0;
440	}
441	return bus_generic_setup_intr(dev, child, irq, flags, filter, intr,
442				      arg, cookiep);
443}
444
445static device_t
446find_masterdev(sc_p scp)
447{
448	int i, units;
449	devclass_t devclass;
450	device_t dev;
451
452	devclass = device_get_devclass(scp->dev);
453	units = devclass_get_maxunit(devclass);
454	dev = NULL;
455	for (i = 0 ; i < units ; i++) {
456		dev = devclass_get_device(devclass, i);
457		if (isa_get_vendorid(dev) == isa_get_vendorid(scp->dev)
458		    && isa_get_logicalid(dev) == LOGICALID_PCM
459		    && isa_get_serial(dev) == isa_get_serial(scp->dev))
460			break;
461	}
462	if (i == units)
463		return (NULL);
464
465	return (dev);
466}
467
468static int io_range[3]  = {0x10, 0x8  , 0x4  };
469static int io_offset[3] = {0x0 , 0x100, 0x10c};
470static int
471alloc_resource(sc_p scp)
472{
473	int i, base, lid, flags;
474	device_t dev;
475
476	flags = 0;
477	if (isa_get_vendorid(scp->dev))
478		lid = isa_get_logicalid(scp->dev);
479	else {
480		lid = LOGICALID_NOPNP;
481		flags = device_get_flags(scp->dev);
482	}
483	switch(lid) {
484	case LOGICALID_PCM:
485	case LOGICALID_NOPNP:		/* XXX Non-PnP */
486		if (lid == LOGICALID_NOPNP)
487			base = isa_get_port(scp->dev);
488		else
489			base = 0;
490		for (i = 0 ; i < sizeof(scp->io) / sizeof(*scp->io) ; i++) {
491			if (scp->io[i] == NULL) {
492				scp->io_rid[i] = i;
493				if (base == 0)
494					scp->io[i] = bus_alloc_resource(scp->dev, SYS_RES_IOPORT, &scp->io_rid[i],
495									0, ~0, io_range[i], RF_ACTIVE);
496				else
497					scp->io[i] = bus_alloc_resource(scp->dev, SYS_RES_IOPORT, &scp->io_rid[i],
498									base + io_offset[i],
499									base + io_offset[i] + io_range[i] - 1
500									, io_range[i], RF_ACTIVE);
501				if (scp->io[i] == NULL)
502					return (1);
503				scp->io_alloced[i] = 0;
504			}
505		}
506		if (scp->irq == NULL) {
507			scp->irq_rid = 0;
508			scp->irq =
509				bus_alloc_resource_any(scp->dev, SYS_RES_IRQ,
510						       &scp->irq_rid,
511						       RF_ACTIVE|RF_SHAREABLE);
512			if (scp->irq == NULL)
513				return (1);
514			scp->irq_alloced = 0;
515		}
516		for (i = 0 ; i < sizeof(scp->drq) / sizeof(*scp->drq) ; i++) {
517			if (scp->drq[i] == NULL) {
518				scp->drq_rid[i] = i;
519				if (base == 0 || i == 0)
520					scp->drq[i] =
521						bus_alloc_resource_any(
522							scp->dev, SYS_RES_DRQ,
523							&scp->drq_rid[i],
524							RF_ACTIVE);
525				else if ((flags & DV_F_DUAL_DMA) != 0)
526					/* XXX The secondary drq is specified in the flag. */
527					scp->drq[i] = bus_alloc_resource(scp->dev, SYS_RES_DRQ, &scp->drq_rid[i],
528									 flags & DV_F_DRQ_MASK,
529									 flags & DV_F_DRQ_MASK, 1, RF_ACTIVE);
530				if (scp->drq[i] == NULL)
531					return (1);
532				scp->drq_alloced[i] = 0;
533			}
534		}
535		break;
536	case LOGICALID_OPL:
537		if (scp->io[0] == NULL) {
538			scp->io_rid[0] = 0;
539			scp->io[0] = bus_alloc_resource(scp->dev, SYS_RES_IOPORT, &scp->io_rid[0],
540							0, ~0, io_range[0], RF_ACTIVE);
541			if (scp->io[0] == NULL)
542				return (1);
543			scp->io_alloced[0] = 0;
544		}
545		break;
546	case LOGICALID_MIDI:
547		if (scp->io[0] == NULL) {
548			scp->io_rid[0] = 0;
549			scp->io[0] = bus_alloc_resource(scp->dev, SYS_RES_IOPORT, &scp->io_rid[0],
550							0, ~0, io_range[0], RF_ACTIVE);
551			if (scp->io[0] == NULL)
552				return (1);
553			scp->io_alloced[0] = 0;
554		}
555		if (scp->irq == NULL) {
556			/* The irq is shared with pcm audio. */
557			dev = find_masterdev(scp);
558			if (dev == NULL)
559				return (1);
560			scp->irq_rid = 0;
561			scp->irq = BUS_ALLOC_RESOURCE(dev, NULL, SYS_RES_IRQ, &scp->irq_rid,
562						      0, ~0, 1, RF_ACTIVE | RF_SHAREABLE);
563			if (scp->irq == NULL)
564				return (1);
565			scp->irq_alloced = 0;
566		}
567		break;
568	}
569	return (0);
570}
571
572static int
573release_resource(sc_p scp)
574{
575	int i, lid, flags;
576	device_t dev;
577
578	flags = 0;
579	if (isa_get_vendorid(scp->dev))
580		lid = isa_get_logicalid(scp->dev);
581	else {
582		lid = LOGICALID_NOPNP;
583		flags = device_get_flags(scp->dev);
584	}
585	switch(lid) {
586	case LOGICALID_PCM:
587	case LOGICALID_NOPNP:		/* XXX Non-PnP */
588		for (i = 0 ; i < sizeof(scp->io) / sizeof(*scp->io) ; i++) {
589			if (scp->io[i] != NULL) {
590				bus_release_resource(scp->dev, SYS_RES_IOPORT, scp->io_rid[i], scp->io[i]);
591				scp->io[i] = NULL;
592			}
593		}
594		if (scp->irq != NULL) {
595			bus_release_resource(scp->dev, SYS_RES_IRQ, scp->irq_rid, scp->irq);
596			scp->irq = NULL;
597		}
598		for (i = 0 ; i < sizeof(scp->drq) / sizeof(*scp->drq) ; i++) {
599			if (scp->drq[i] != NULL) {
600				bus_release_resource(scp->dev, SYS_RES_DRQ, scp->drq_rid[i], scp->drq[i]);
601				scp->drq[i] = NULL;
602			}
603		}
604		break;
605	case LOGICALID_OPL:
606		if (scp->io[0] != NULL) {
607			bus_release_resource(scp->dev, SYS_RES_IOPORT, scp->io_rid[0], scp->io[0]);
608			scp->io[0] = NULL;
609		}
610		break;
611	case LOGICALID_MIDI:
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		if (scp->irq != NULL) {
617			/* The irq is shared with pcm audio. */
618			dev = find_masterdev(scp);
619			if (dev == NULL)
620				return (1);
621			BUS_RELEASE_RESOURCE(dev, NULL, SYS_RES_IOPORT, scp->irq_rid, scp->irq);
622			scp->irq = NULL;
623		}
624		break;
625	}
626	return (0);
627}
628
629static device_method_t gusc_methods[] = {
630	/* Device interface */
631	DEVMETHOD(device_probe,		gusc_probe),
632	DEVMETHOD(device_attach,	gusc_attach),
633	DEVMETHOD(device_detach,	bus_generic_detach),
634	DEVMETHOD(device_shutdown,	bus_generic_shutdown),
635	DEVMETHOD(device_suspend,	bus_generic_suspend),
636	DEVMETHOD(device_resume,	bus_generic_resume),
637
638	/* Bus interface */
639	DEVMETHOD(bus_print_child,	bus_generic_print_child),
640	DEVMETHOD(bus_alloc_resource,	gusc_alloc_resource),
641	DEVMETHOD(bus_release_resource,	gusc_release_resource),
642	DEVMETHOD(bus_activate_resource, bus_generic_activate_resource),
643	DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource),
644	DEVMETHOD(bus_setup_intr,	gusc_setup_intr),
645	DEVMETHOD(bus_teardown_intr,	bus_generic_teardown_intr),
646
647	{ 0, 0 }
648};
649
650static driver_t gusc_driver = {
651	"gusc",
652	gusc_methods,
653	sizeof(struct gusc_softc),
654};
655
656/*
657 * gusc can be attached to an isa bus.
658 */
659DRIVER_MODULE(snd_gusc, isa, gusc_driver, gusc_devclass, 0, 0);
660DRIVER_MODULE(snd_gusc, acpi, gusc_driver, gusc_devclass, 0, 0);
661MODULE_DEPEND(snd_gusc, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER);
662MODULE_VERSION(snd_gusc, 1);
663
664
665