1/*
2 * Auvia BeOS Driver for Via VT82xx Southbridge audio
3 *
4 * Copyright (c) 2003, Jerome Duval (jerome.duval@free.fr)
5
6 * This code is derived from software contributed to The NetBSD Foundation
7 * by Tyler C. Sarna
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 *    notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 *    notice, this list of conditions and the following disclaimer in the
16 *    documentation and/or other materials provided with the distribution.
17 * 3. All advertising materials mentioning features or use of this software
18 *    must display the following acknowledgement:
19 *	This product includes software developed by the NetBSD
20 *	Foundation, Inc. and its contributors.
21 * 4. Neither the name of The NetBSD Foundation nor the names of its
22 *    contributors may be used to endorse or promote products derived
23 *    from this software without specific prior written permission.
24 *
25 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
26 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
27 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
28 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
29 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
30 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
31 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
32 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
33 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
34 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
35 * POSSIBILITY OF SUCH DAMAGE.
36 */
37
38#include <KernelExport.h>
39#include <PCI.h>
40#include <string.h>
41#include <stdio.h>
42#include "auvia.h"
43#include "debug.h"
44#include "config.h"
45#include "util.h"
46#include "io.h"
47#include <fcntl.h>
48#include <unistd.h>
49#include "ac97.h"
50
51status_t init_hardware(void);
52status_t init_driver(void);
53void uninit_driver(void);
54const char ** publish_devices(void);
55device_hooks * find_device(const char *);
56
57pci_module_info	*pci;
58
59int32 num_cards;
60auvia_dev cards[NUM_CARDS];
61int32 num_names;
62char * names[NUM_CARDS*20+1];
63
64extern device_hooks multi_hooks;
65
66/* Auvia Memory management */
67
68static auvia_mem *
69auvia_mem_new(auvia_dev *card, size_t size)
70{
71	auvia_mem *mem;
72
73	if ((mem = malloc(sizeof(*mem))) == NULL)
74		return (NULL);
75
76	mem->area = alloc_mem(&mem->phy_base, &mem->log_base, size, "auvia buffer", false);
77	mem->size = size;
78	if (mem->area < B_OK) {
79		free(mem);
80		return NULL;
81	}
82	return mem;
83}
84
85
86static void
87auvia_mem_delete(auvia_mem *mem)
88{
89	if(mem->area > B_OK)
90		delete_area(mem->area);
91	free(mem);
92}
93
94
95static void *
96auvia_mem_alloc(auvia_dev *card, size_t size)
97{
98	auvia_mem *mem;
99
100	mem = auvia_mem_new(card, size);
101	if (mem == NULL)
102		return (NULL);
103
104	LIST_INSERT_HEAD(&(card->mems), mem, next);
105
106	return mem;
107}
108
109
110static void
111auvia_mem_free(auvia_dev *card, void *ptr)
112{
113	auvia_mem 		*mem;
114
115	LIST_FOREACH(mem, &card->mems, next) {
116		if (mem->log_base != ptr)
117			continue;
118		LIST_REMOVE(mem, next);
119
120		auvia_mem_delete(mem);
121		break;
122	}
123}
124
125/*	Auvia stream functions */
126
127status_t
128auvia_stream_set_audioparms(auvia_stream *stream, uint8 channels,
129     uint8 b16, uint32 sample_rate)
130{
131	uint8 			sample_size, frame_size;
132	LOG(("auvia_stream_set_audioparms\n"));
133
134	if ((stream->channels == channels) &&
135		(stream->b16 == b16) &&
136		(stream->sample_rate == sample_rate))
137		return B_OK;
138
139	if(stream->buffer)
140		auvia_mem_free(stream->card, stream->buffer->log_base);
141
142	stream->b16 = b16;
143	stream->sample_rate = sample_rate;
144	stream->channels = channels;
145
146	sample_size = stream->b16 + 1;
147	frame_size = sample_size * stream->channels;
148
149	stream->buffer = auvia_mem_alloc(stream->card, stream->bufframes
150		* frame_size * stream->bufcount);
151
152	stream->trigblk = 0;	/* This shouldn't be needed */
153	stream->blkmod = stream->bufcount;
154	stream->blksize = stream->bufframes * frame_size;
155
156	return B_OK;
157}
158
159
160status_t
161auvia_stream_commit_parms(auvia_stream *stream)
162{
163	int i;
164	uint32* page;
165	uint32 value;
166	LOG(("auvia_stream_commit_parms\n"));
167
168	page = stream->dmaops_log_base;
169
170	for(i = 0; i < stream->bufcount; i++) {
171		page[2 * i] = stream->buffer->phy_base + i * stream->blksize;
172		page[2 * i + 1] = AUVIA_DMAOP_FLAG | stream->blksize;
173	}
174
175	page[2 * stream->bufcount - 1] &= ~AUVIA_DMAOP_FLAG;
176	page[2 * stream->bufcount - 1] |= AUVIA_DMAOP_EOL;
177
178	auvia_reg_write_32(&stream->card->config, stream->base + AUVIA_RP_DMAOPS_BASE,
179		stream->dmaops_phy_base);
180
181	if(stream->use & AUVIA_USE_RECORD)
182		auvia_codec_write(&stream->card->config, AC97_PCM_L_R_ADC_RATE,
183			(uint16)stream->sample_rate);
184	else
185		auvia_codec_write(&stream->card->config, AC97_PCM_FRONT_DAC_RATE,
186			(uint16)stream->sample_rate);
187
188	if(IS_8233(&stream->card->config)) {
189		if(stream->base != AUVIA_8233_MP_BASE) {
190			value = auvia_reg_read_32(&stream->card->config, stream->base
191				+ AUVIA_8233_RP_RATEFMT);
192			value &= ~(AUVIA_8233_RATEFMT_48K | AUVIA_8233_RATEFMT_STEREO
193				| AUVIA_8233_RATEFMT_16BIT);
194			if(stream->use & AUVIA_USE_PLAY)
195				value |= AUVIA_8233_RATEFMT_48K * (stream->sample_rate / 20)
196					/ (48000 / 20);
197			value |= (stream->channels == 2 ? AUVIA_8233_RATEFMT_STEREO : 0)
198				| (stream->b16 ? AUVIA_8233_RATEFMT_16BIT : 0);
199			auvia_reg_write_32(&stream->card->config, stream->base
200				+ AUVIA_8233_RP_RATEFMT, value);
201		} else {
202			static const uint32 slottab[7] = {0, 0xff000011, 0xff000021,
203				0xff000521, 0xff004321, 0xff054321, 0xff654321};
204			value = (stream->b16 ? AUVIA_8233_MP_FORMAT_16BIT : AUVIA_8233_MP_FORMAT_8BIT)
205				| ((stream->channels << 4) & AUVIA_8233_MP_FORMAT_CHANNEL_MASK);
206			auvia_reg_write_8(&stream->card->config, stream->base
207				+ AUVIA_8233_OFF_MP_FORMAT, value);
208			auvia_reg_write_32(&stream->card->config, stream->base
209				+ AUVIA_8233_OFF_MP_STOP, slottab[stream->channels]);
210		}
211	}
212	//auvia_codec_write(&stream->card->config, AC97_SPDIF_CONTROL, (uint16)stream->sample_rate);
213
214	return B_OK;
215}
216
217
218status_t
219auvia_stream_get_nth_buffer(auvia_stream *stream, uint8 chan, uint8 buf,
220					char** buffer, size_t *stride)
221{
222	uint8 			sample_size, frame_size;
223	LOG(("auvia_stream_get_nth_buffer\n"));
224
225	sample_size = stream->b16 + 1;
226	frame_size = sample_size * stream->channels;
227
228	*buffer =(char *)((addr_t)stream->buffer->log_base + (uintptr_t)(buf * stream->bufframes * frame_size))
229		+ chan * sample_size;
230	*stride = frame_size;
231
232	return B_OK;
233}
234
235
236static uint32
237auvia_stream_curaddr(auvia_stream *stream)
238{
239	uint32 addr;
240	if(IS_8233(&stream->card->config)) {
241		addr = auvia_reg_read_32(&stream->card->config, stream->base + AUVIA_RP_DMAOPS_BASE);
242		TRACE(("stream_curaddr %p, phy_base %p\n", addr, stream->dmaops_phy_base));
243		return (addr - stream->dmaops_phy_base - 4) / 8;
244	} else {
245		addr = auvia_reg_read_32(&stream->card->config, stream->base + AUVIA_RP_DMAOPS_BASE);
246		TRACE(("stream_curaddr %p, phy_base %p\n", addr, stream->dmaops_phy_base));
247		return (addr - stream->dmaops_phy_base - 8) / 8;
248	}
249}
250
251
252void
253auvia_stream_start(auvia_stream *stream, void (*inth) (void *), void *inthparam)
254{
255	LOG(("auvia_stream_start\n"));
256
257	stream->inth = inth;
258	stream->inthparam = inthparam;
259
260	stream->state |= AUVIA_STATE_STARTED;
261
262	if(IS_8233(&stream->card->config)) {
263		if(stream->base != AUVIA_8233_MP_BASE) {
264			auvia_reg_write_8(&stream->card->config, stream->base + AUVIA_8233_RP_DXS_LVOL, 0);
265			auvia_reg_write_8(&stream->card->config, stream->base + AUVIA_8233_RP_DXS_RVOL, 0);
266		}
267		auvia_reg_write_8(&stream->card->config, stream->base + AUVIA_RP_CONTROL,
268			AUVIA_RPCTRL_START | AUVIA_RPCTRL_AUTOSTART | AUVIA_RPCTRL_STOP
269			| AUVIA_RPCTRL_EOL | AUVIA_RPCTRL_FLAG);
270	} else {
271		uint8 regvalue = (stream->channels > 1 ? AUVIA_RPMODE_STEREO : 0)
272			| (stream->b16 == 1 ? AUVIA_RPMODE_16BIT : 0)
273			| AUVIA_RPMODE_INTR_FLAG | AUVIA_RPMODE_INTR_EOL | AUVIA_RPMODE_AUTOSTART;
274		auvia_reg_write_8(&stream->card->config, stream->base + AUVIA_RP_MODE, regvalue);
275		auvia_reg_write_8(&stream->card->config, stream->base + AUVIA_RP_CONTROL,
276			AUVIA_RPCTRL_START);
277	}
278}
279
280
281void
282auvia_stream_halt(auvia_stream *stream)
283{
284	LOG(("auvia_stream_halt\n"));
285
286	stream->state &= ~AUVIA_STATE_STARTED;
287
288	auvia_reg_write_8(&stream->card->config, stream->base + AUVIA_RP_CONTROL,
289		AUVIA_RPCTRL_TERMINATE);
290}
291
292
293auvia_stream *
294auvia_stream_new(auvia_dev *card, uint8 use, uint32 bufframes, uint8 bufcount)
295{
296	auvia_stream *stream;
297	cpu_status status;
298	LOG(("auvia_stream_new\n"));
299
300	stream = malloc(sizeof(auvia_stream));
301	if (stream == NULL)
302		return (NULL);
303	stream->card = card;
304	stream->use = use;
305	stream->state = !AUVIA_STATE_STARTED;
306	stream->b16 = 0;
307	stream->sample_rate = 0;
308	stream->channels = 0;
309	stream->bufframes = bufframes;
310	stream->bufcount = bufcount;
311	stream->inth = NULL;
312	stream->inthparam = NULL;
313	stream->buffer = NULL;
314	stream->blksize = 0;
315	stream->trigblk = 0;
316	stream->blkmod = 0;
317
318	if(use & AUVIA_USE_PLAY) {
319		if(IS_8233(&card->config))
320			stream->base = AUVIA_8233_MP_BASE;
321			//stream->base = AUVIA_PLAY_BASE;
322		else
323			stream->base = AUVIA_PLAY_BASE;
324	} else {
325		if(IS_8233(&card->config))
326			stream->base = AUVIA_8233_RECORD_BASE;
327		else
328			stream->base = AUVIA_RECORD_BASE;
329	}
330
331	stream->frames_count = 0;
332	stream->real_time = 0;
333	stream->buffer_cycle = 0;
334	stream->update_needed = false;
335
336	/* allocate memory for our dma ops */
337	stream->dmaops_area = alloc_mem(&stream->dmaops_phy_base, &stream->dmaops_log_base,
338		VIA_TABLE_SIZE, "auvia dmaops", false);
339
340	if (stream->dmaops_area < B_OK) {
341		PRINT(("couldn't allocate memory\n"));
342		free(stream);
343		return NULL;
344	}
345
346	status = lock();
347	LIST_INSERT_HEAD((&card->streams), stream, next);
348	unlock(status);
349
350	return stream;
351}
352
353
354void
355auvia_stream_delete(auvia_stream *stream)
356{
357	cpu_status status;
358	LOG(("auvia_stream_delete\n"));
359
360	auvia_stream_halt(stream);
361
362	auvia_reg_write_32(&stream->card->config, stream->base + AUVIA_RP_DMAOPS_BASE, 0);
363
364	if (stream->dmaops_area > B_OK)
365		delete_area(stream->dmaops_area);
366
367	if(stream->buffer)
368		auvia_mem_free(stream->card, stream->buffer->log_base);
369
370	status = lock();
371	LIST_REMOVE(stream, next);
372	unlock(status);
373
374	free(stream);
375}
376
377/* Auvia interrupt */
378
379static int32
380auvia_int(void *arg)
381{
382	auvia_dev	 *card = arg;
383	bool gotone = false;
384	uint32       curblk;
385	auvia_stream *stream;
386
387	if(auvia_reg_read_32(&card->config, AUVIA_SGD_SHADOW)
388		& card->interrupt_mask) {
389
390		LIST_FOREACH(stream, &card->streams, next)
391			if(auvia_reg_read_8(&card->config, stream->base + AUVIA_RP_STAT) & AUVIA_RPSTAT_INTR) {
392				gotone = true;
393				//TRACE(("interrupt\n"));
394
395				curblk = auvia_stream_curaddr(stream);
396				TRACE(("RPSTAT_INTR at trigblk %lu, stream->trigblk %lu\n", curblk, stream->trigblk));
397				if (curblk == stream->trigblk) {
398					//TRACE(("AUVIA_RPSTAT_INTR at trigblk %lu\n", curblk));
399
400					if(stream->inth)
401						stream->inth(stream->inthparam);
402
403					stream->trigblk++;
404					stream->trigblk %= stream->blkmod;
405				}
406
407				auvia_reg_write_8(&card->config, stream->base + AUVIA_RP_STAT, AUVIA_RPSTAT_INTR);
408			}
409	} else {
410		TRACE(("SGD_SHADOW %x %x\n", card->interrupt_mask,
411			auvia_reg_read_32(&card->config, AUVIA_SGD_SHADOW)));
412	}
413
414	if(gotone)
415		return B_INVOKE_SCHEDULER;
416
417	TRACE(("Got unhandled interrupt\n"));
418	return B_UNHANDLED_INTERRUPT;
419}
420
421/*	Auvia driver functions */
422
423/* detect presence of our hardware */
424status_t
425init_hardware(void)
426{
427	int ix=0;
428	pci_info info;
429	status_t err = ENODEV;
430
431	LOG_CREATE();
432
433	PRINT(("init_hardware()\n"));
434
435	if (get_module(B_PCI_MODULE_NAME, (module_info **)&pci))
436		return ENOSYS;
437
438	while ((*pci->get_nth_pci_info)(ix, &info) == B_OK) {
439		if (info.vendor_id == VIATECH_VENDOR_ID &&
440			(info.device_id == VIATECH_82C686_AC97_DEVICE_ID
441			|| info.device_id == VIATECH_8233_AC97_DEVICE_ID
442			)) {
443			err = B_OK;
444		}
445		ix++;
446	}
447
448	put_module(B_PCI_MODULE_NAME);
449
450	return err;
451}
452
453static void
454make_device_names(
455	auvia_dev * card)
456{
457	sprintf(card->name, "audio/hmulti/auvia/%ld", card-cards+1);
458	names[num_names++] = card->name;
459
460	names[num_names] = NULL;
461}
462
463
464static status_t
465auvia_init(auvia_dev * card)
466{
467	uint32 pr;
468
469	pr = (*pci->read_pci_config)(card->info.bus, card->info.device,
470		card->info.function, AUVIA_PCICONF_JUNK, 4);
471	PRINT(("AUVIA_PCICONF_JUNK before: %" B_PRIx32 "\n", pr));
472	pr &= ~AUVIA_PCICONF_ENABLES;
473	pr |= AUVIA_PCICONF_ACLINKENAB | AUVIA_PCICONF_ACNOTRST
474		| AUVIA_PCICONF_ACVSR | AUVIA_PCICONF_ACSGD;
475	pr &= ~(AUVIA_PCICONF_ACFM | AUVIA_PCICONF_ACSB);
476	(*pci->write_pci_config)(card->info.bus, card->info.device,
477		card->info.function, AUVIA_PCICONF_JUNK, 4, pr );
478	snooze(100);
479	pr = (*pci->read_pci_config)(card->info.bus, card->info.device,
480		card->info.function, AUVIA_PCICONF_JUNK, 4);
481	PRINT(("AUVIA_PCICONF_JUNK after: %" B_PRIx32 "\n", pr));
482
483	if(IS_8233(&card->config)) {
484		card->interrupt_mask =
485			AUVIA_8233_SGD_STAT_FLAG_EOL |
486			AUVIA_8233_SGD_STAT_FLAG_EOL << 4 |
487			AUVIA_8233_SGD_STAT_FLAG_EOL << 8 |
488			AUVIA_8233_SGD_STAT_FLAG_EOL << 12 |
489			AUVIA_8233_SGD_STAT_FLAG_EOL << 16 |
490			AUVIA_8233_SGD_STAT_FLAG_EOL << 24 |
491			AUVIA_8233_SGD_STAT_FLAG_EOL << 28;
492	} else {
493		card->interrupt_mask = AUVIA_SGD_STAT_ALL | (AUVIA_SGD_STAT_ALL << 4);
494	}
495
496
497	/* Init streams list */
498	LIST_INIT(&(card->streams));
499
500	/* Init mems list */
501	LIST_INIT(&(card->mems));
502
503	return B_OK;
504}
505
506
507static void
508auvia_shutdown(auvia_dev *card)
509{
510	PRINT(("shutdown(%p)\n", card));
511	ac97_detach(card->config.ac97);
512	remove_io_interrupt_handler(card->config.irq, auvia_int, card);
513}
514
515
516static status_t
517auvia_setup(auvia_dev * card)
518{
519	status_t err = B_OK;
520	unsigned char cmd;
521
522	PRINT(("auvia_setup(%p)\n", card));
523
524	make_device_names(card);
525
526	card->config.subvendor_id = card->info.u.h0.subsystem_vendor_id;
527	card->config.subsystem_id = card->info.u.h0.subsystem_id;
528	card->config.nabmbar = card->info.u.h0.base_registers[0];
529	card->config.irq = card->info.u.h0.interrupt_line;
530	card->config.type = 0;
531	if(card->info.device_id == VIATECH_82C686_AC97_DEVICE_ID)
532		card->config.type |= TYPE_686;
533	if(card->info.device_id == VIATECH_8233_AC97_DEVICE_ID)
534		card->config.type |= TYPE_8233;
535
536	PRINT(("%s deviceid = %#04x chiprev = %x model = %x enhanced "
537		"at %" B_PRIx32 "\n",
538		card->name, card->info.device_id, card->info.revision,
539		card->info.u.h0.subsystem_id, card->config.nabmbar));
540
541	cmd = (*pci->read_pci_config)(card->info.bus, card->info.device,
542		card->info.function, PCI_command, 2);
543	PRINT(("PCI command before: %x\n", cmd));
544	(*pci->write_pci_config)(card->info.bus, card->info.device,
545		card->info.function, PCI_command, 2, cmd | PCI_command_io);
546	cmd = (*pci->read_pci_config)(card->info.bus, card->info.device,
547		card->info.function, PCI_command, 2);
548	PRINT(("PCI command after: %x\n", cmd));
549
550	/* attach the codec */
551	PRINT(("codec attach\n"));
552	ac97_attach(&card->config.ac97, (codec_reg_read)auvia_codec_read,
553		(codec_reg_write)auvia_codec_write, &card->config,
554		card->config.subvendor_id, card->config.subsystem_id);
555
556	PRINT(("installing interrupt : %" B_PRIx32 "\n", card->config.irq));
557	err = install_io_interrupt_handler(card->config.irq, auvia_int, card, 0);
558	if (err != B_OK) {
559		PRINT(("failed to install interrupt\n"));
560		ac97_detach(card->config.ac97);
561		return err;
562	}
563
564	if ((err = auvia_init(card))) {
565		auvia_shutdown(card);
566		return err;
567	}
568
569	PRINT(("init_driver done\n"));
570
571	return err;
572}
573
574
575status_t
576init_driver(void)
577{
578	pci_info info;
579	status_t err;
580	int ix = 0;
581	num_cards = 0;
582
583	PRINT(("init_driver()\n"));
584
585	if (get_module(B_PCI_MODULE_NAME, (module_info **)&pci))
586		return ENOSYS;
587
588	while ((*pci->get_nth_pci_info)(ix++, &info) == B_OK) {
589		if (info.vendor_id == VIATECH_VENDOR_ID &&
590			(info.device_id == VIATECH_82C686_AC97_DEVICE_ID
591			|| info.device_id == VIATECH_8233_AC97_DEVICE_ID
592			)) {
593			if (num_cards == NUM_CARDS) {
594				PRINT(("Too many auvia cards installed!\n"));
595				break;
596			}
597			memset(&cards[num_cards], 0, sizeof(auvia_dev));
598			cards[num_cards].info = info;
599#ifdef __HAIKU__
600			if ((err = (*pci->reserve_device)(info.bus, info.device, info.function,
601				DRIVER_NAME, &cards[num_cards])) < B_OK) {
602				dprintf("%s: failed to reserve_device(%d, %d, %d,): %s\n",
603					DRIVER_NAME, info.bus, info.device, info.function,
604					strerror(err));
605				continue;
606			}
607#endif
608			if (auvia_setup(&cards[num_cards])) {
609				PRINT(("Setup of auvia %" B_PRId32 " failed\n", num_cards + 1));
610#ifdef __HAIKU__
611				(*pci->unreserve_device)(info.bus, info.device, info.function,
612					DRIVER_NAME, &cards[num_cards]);
613#endif
614			}
615			else {
616				num_cards++;
617			}
618		}
619	}
620	if (!num_cards) {
621		PRINT(("no cards\n"));
622		put_module(B_PCI_MODULE_NAME);
623		PRINT(("no suitable cards found\n"));
624		return ENODEV;
625	}
626
627
628#if DEBUG
629	//add_debugger_command("auvia", auvia_debug, "auvia [card# (1-n)]");
630#endif
631	return B_OK;
632}
633
634
635void
636uninit_driver(void)
637{
638	int ix, cnt = num_cards;
639	num_cards = 0;
640
641	PRINT(("uninit_driver()\n"));
642	//remove_debugger_command("auvia", auvia_debug);
643
644	for (ix=0; ix<cnt; ix++) {
645		auvia_shutdown(&cards[ix]);
646#ifdef __HAIKU__
647		(*pci->unreserve_device)(cards[ix].info.bus,
648			cards[ix].info.device, cards[ix].info.function,
649			DRIVER_NAME, &cards[ix]);
650#endif
651	}
652	memset(&cards, 0, sizeof(cards));
653	put_module(B_PCI_MODULE_NAME);
654}
655
656
657const char **
658publish_devices(void)
659{
660	int ix = 0;
661	PRINT(("publish_devices()\n"));
662
663	for (ix=0; names[ix]; ix++) {
664		PRINT(("publish %s\n", names[ix]));
665	}
666	return (const char **)names;
667}
668
669
670device_hooks *
671find_device(const char * name)
672{
673	int ix;
674
675	PRINT(("find_device(%s)\n", name));
676
677	for (ix=0; ix<num_cards; ix++) {
678		if (!strcmp(cards[ix].name, name)) {
679			return &multi_hooks;
680		}
681	}
682	PRINT(("find_device(%s) failed\n", name));
683	return NULL;
684}
685
686int32	api_version = B_CUR_DRIVER_API_VERSION;
687