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");
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] = ((uint32)stream->buffer->phy_base) +
172			i * stream->blksize;
173		page[2*i + 1] = AUVIA_DMAOP_FLAG | stream->blksize;
174	}
175
176	page[2*stream->bufcount - 1] &= ~AUVIA_DMAOP_FLAG;
177	page[2*stream->bufcount - 1] |= AUVIA_DMAOP_EOL;
178
179	auvia_reg_write_32(&stream->card->config, stream->base + AUVIA_RP_DMAOPS_BASE,
180		(uint32)stream->dmaops_phy_base);
181
182	if(stream->use & AUVIA_USE_RECORD)
183		auvia_codec_write(&stream->card->config, AC97_PCM_L_R_ADC_RATE,
184			(uint16)stream->sample_rate);
185	else
186		auvia_codec_write(&stream->card->config, AC97_PCM_FRONT_DAC_RATE,
187			(uint16)stream->sample_rate);
188
189	if(IS_8233(&stream->card->config)) {
190		if(stream->base != AUVIA_8233_MP_BASE) {
191			value = auvia_reg_read_32(&stream->card->config, stream->base
192				+ AUVIA_8233_RP_RATEFMT);
193			value &= ~(AUVIA_8233_RATEFMT_48K | AUVIA_8233_RATEFMT_STEREO
194				| AUVIA_8233_RATEFMT_16BIT);
195			if(stream->use & AUVIA_USE_PLAY)
196				value |= AUVIA_8233_RATEFMT_48K * (stream->sample_rate / 20)
197					/ (48000 / 20);
198			value |= (stream->channels == 2 ? AUVIA_8233_RATEFMT_STEREO : 0)
199				| (stream->b16 ? AUVIA_8233_RATEFMT_16BIT : 0);
200			auvia_reg_write_32(&stream->card->config, stream->base
201				+ AUVIA_8233_RP_RATEFMT, value);
202		} else {
203			static const uint32 slottab[7] = {0, 0xff000011, 0xff000021,
204				0xff000521, 0xff004321, 0xff054321, 0xff654321};
205			value = (stream->b16 ? AUVIA_8233_MP_FORMAT_16BIT : AUVIA_8233_MP_FORMAT_8BIT)
206				| ((stream->channels << 4) & AUVIA_8233_MP_FORMAT_CHANNEL_MASK) ;
207			auvia_reg_write_8(&stream->card->config, stream->base
208				+ AUVIA_8233_OFF_MP_FORMAT, value);
209			auvia_reg_write_32(&stream->card->config, stream->base
210				+ AUVIA_8233_OFF_MP_STOP, slottab[stream->channels]);
211		}
212	}
213	//auvia_codec_write(&stream->card->config, AC97_SPDIF_CONTROL, (uint16)stream->sample_rate);
214
215	return B_OK;
216}
217
218
219status_t
220auvia_stream_get_nth_buffer(auvia_stream *stream, uint8 chan, uint8 buf,
221					char** buffer, size_t *stride)
222{
223	uint8 			sample_size, frame_size;
224	LOG(("auvia_stream_get_nth_buffer\n"));
225
226	sample_size = stream->b16 + 1;
227	frame_size = sample_size * stream->channels;
228
229	*buffer = stream->buffer->log_base + (buf * stream->bufframes * frame_size)
230		+ chan * sample_size;
231	*stride = frame_size;
232
233	return B_OK;
234}
235
236
237static uint32
238auvia_stream_curaddr(auvia_stream *stream)
239{
240	uint32 addr;
241	if(IS_8233(&stream->card->config)) {
242		addr = auvia_reg_read_32(&stream->card->config, stream->base + AUVIA_RP_DMAOPS_BASE);
243		TRACE(("stream_curaddr %p, phy_base %p\n", addr, (uint32)stream->dmaops_phy_base));
244		return (addr - (uint32)stream->dmaops_phy_base - 4) / 8;
245	} else {
246		addr = auvia_reg_read_32(&stream->card->config, stream->base + AUVIA_RP_DMAOPS_BASE);
247		TRACE(("stream_curaddr %p, phy_base %p\n", addr, (uint32)stream->dmaops_phy_base));
248		return (addr - (uint32)stream->dmaops_phy_base - 8) / 8;
249	}
250}
251
252
253void
254auvia_stream_start(auvia_stream *stream, void (*inth) (void *), void *inthparam)
255{
256	LOG(("auvia_stream_start\n"));
257
258	stream->inth = inth;
259	stream->inthparam = inthparam;
260
261	stream->state |= AUVIA_STATE_STARTED;
262
263	if(IS_8233(&stream->card->config)) {
264		if(stream->base != AUVIA_8233_MP_BASE) {
265			auvia_reg_write_8(&stream->card->config, stream->base + AUVIA_8233_RP_DXS_LVOL, 0);
266			auvia_reg_write_8(&stream->card->config, stream->base + AUVIA_8233_RP_DXS_RVOL, 0);
267		}
268		auvia_reg_write_8(&stream->card->config, stream->base + AUVIA_RP_CONTROL,
269			AUVIA_RPCTRL_START | AUVIA_RPCTRL_AUTOSTART | AUVIA_RPCTRL_STOP
270			| AUVIA_RPCTRL_EOL | AUVIA_RPCTRL_FLAG);
271	} else {
272		uint8 regvalue = (stream->channels > 1 ? AUVIA_RPMODE_STEREO : 0)
273			| (stream->b16 == 1 ? AUVIA_RPMODE_16BIT : 0)
274			| AUVIA_RPMODE_INTR_FLAG | AUVIA_RPMODE_INTR_EOL | AUVIA_RPMODE_AUTOSTART;
275		auvia_reg_write_8(&stream->card->config, stream->base + AUVIA_RP_MODE, regvalue);
276		auvia_reg_write_8(&stream->card->config, stream->base + AUVIA_RP_CONTROL,
277			AUVIA_RPCTRL_START);
278	}
279}
280
281
282void
283auvia_stream_halt(auvia_stream *stream)
284{
285	LOG(("auvia_stream_halt\n"));
286
287	stream->state &= ~AUVIA_STATE_STARTED;
288
289	auvia_reg_write_8(&stream->card->config, stream->base + AUVIA_RP_CONTROL,
290		AUVIA_RPCTRL_TERMINATE);
291}
292
293
294auvia_stream *
295auvia_stream_new(auvia_dev *card, uint8 use, uint32 bufframes, uint8 bufcount)
296{
297	auvia_stream *stream;
298	cpu_status status;
299	LOG(("auvia_stream_new\n"));
300
301	stream = malloc(sizeof(auvia_stream));
302	if (stream == NULL)
303		return (NULL);
304	stream->card = card;
305	stream->use = use;
306	stream->state = !AUVIA_STATE_STARTED;
307	stream->b16 = 0;
308	stream->sample_rate = 0;
309	stream->channels = 0;
310	stream->bufframes = bufframes;
311	stream->bufcount = bufcount;
312	stream->inth = NULL;
313	stream->inthparam = NULL;
314	stream->buffer = NULL;
315	stream->blksize = 0;
316	stream->trigblk = 0;
317	stream->blkmod = 0;
318
319	if(use & AUVIA_USE_PLAY) {
320		if(IS_8233(&card->config))
321			stream->base = AUVIA_8233_MP_BASE;
322			//stream->base = AUVIA_PLAY_BASE;
323		else
324			stream->base = AUVIA_PLAY_BASE;
325	} else {
326		if(IS_8233(&card->config))
327			stream->base = AUVIA_8233_RECORD_BASE;
328		else
329			stream->base = AUVIA_RECORD_BASE;
330	}
331
332	stream->frames_count = 0;
333	stream->real_time = 0;
334	stream->buffer_cycle = 0;
335	stream->update_needed = false;
336
337	/* allocate memory for our dma ops */
338	stream->dmaops_area = alloc_mem(&stream->dmaops_phy_base, &stream->dmaops_log_base,
339		VIA_TABLE_SIZE, "auvia dmaops");
340
341	if (stream->dmaops_area < B_OK) {
342		PRINT(("couldn't allocate memory\n"));
343		free(stream);
344		return NULL;
345	}
346
347	status = lock();
348	LIST_INSERT_HEAD((&card->streams), stream, next);
349	unlock(status);
350
351	return stream;
352}
353
354
355void
356auvia_stream_delete(auvia_stream *stream)
357{
358	cpu_status status;
359	LOG(("auvia_stream_delete\n"));
360
361	auvia_stream_halt(stream);
362
363	auvia_reg_write_32(&stream->card->config, stream->base + AUVIA_RP_DMAOPS_BASE, 0);
364
365	if (stream->dmaops_area > B_OK)
366		delete_area(stream->dmaops_area);
367
368	if(stream->buffer)
369		auvia_mem_free(stream->card, stream->buffer->log_base);
370
371	status = lock();
372	LIST_REMOVE(stream, next);
373	unlock(status);
374
375	free(stream);
376}
377
378/* Auvia interrupt */
379
380static int32
381auvia_int(void *arg)
382{
383	auvia_dev	 *card = arg;
384	bool gotone = false;
385	uint32       curblk;
386	auvia_stream *stream;
387
388	if(auvia_reg_read_32(&card->config, AUVIA_SGD_SHADOW)
389		& card->interrupt_mask) {
390
391		LIST_FOREACH(stream, &card->streams, next)
392			if(auvia_reg_read_8(&card->config, stream->base + AUVIA_RP_STAT) & AUVIA_RPSTAT_INTR) {
393				gotone = true;
394				//TRACE(("interrupt\n"));
395
396				curblk = auvia_stream_curaddr(stream);
397				TRACE(("RPSTAT_INTR at trigblk %lu, stream->trigblk %lu\n", curblk, stream->trigblk));
398				if (curblk == stream->trigblk) {
399					//TRACE(("AUVIA_RPSTAT_INTR at trigblk %lu\n", curblk));
400
401					if(stream->inth)
402						stream->inth(stream->inthparam);
403
404					stream->trigblk++;
405					stream->trigblk %= stream->blkmod;
406				}
407
408				auvia_reg_write_8(&card->config, stream->base + AUVIA_RP_STAT, AUVIA_RPSTAT_INTR);
409			}
410	} else {
411		TRACE(("SGD_SHADOW %x %x\n", card->interrupt_mask,
412			auvia_reg_read_32(&card->config, AUVIA_SGD_SHADOW)));
413	}
414
415	if(gotone)
416		return B_INVOKE_SCHEDULER;
417
418	TRACE(("Got unhandled interrupt\n"));
419	return B_UNHANDLED_INTERRUPT;
420}
421
422/*	Auvia driver functions */
423
424/* detect presence of our hardware */
425status_t
426init_hardware(void)
427{
428	int ix=0;
429	pci_info info;
430	status_t err = ENODEV;
431
432	LOG_CREATE();
433
434	PRINT(("init_hardware()\n"));
435
436	if (get_module(B_PCI_MODULE_NAME, (module_info **)&pci))
437		return ENOSYS;
438
439	while ((*pci->get_nth_pci_info)(ix, &info) == B_OK) {
440		if (info.vendor_id == VIATECH_VENDOR_ID &&
441			(info.device_id == VIATECH_82C686_AC97_DEVICE_ID
442			|| info.device_id == VIATECH_8233_AC97_DEVICE_ID
443			)) {
444			err = B_OK;
445		}
446		ix++;
447	}
448
449	put_module(B_PCI_MODULE_NAME);
450
451	return err;
452}
453
454static void
455make_device_names(
456	auvia_dev * card)
457{
458	sprintf(card->name, "audio/hmulti/auvia/%ld", card-cards+1);
459	names[num_names++] = card->name;
460
461	names[num_names] = NULL;
462}
463
464
465static status_t
466auvia_init(auvia_dev * card)
467{
468	uint32 pr;
469
470	pr = (*pci->read_pci_config)(card->info.bus, card->info.device,
471		card->info.function, AUVIA_PCICONF_JUNK, 4);
472	PRINT(("AUVIA_PCICONF_JUNK before: %lx\n", pr));
473	pr &= ~AUVIA_PCICONF_ENABLES;
474	pr |= AUVIA_PCICONF_ACLINKENAB | AUVIA_PCICONF_ACNOTRST
475		| AUVIA_PCICONF_ACVSR | AUVIA_PCICONF_ACSGD;
476	pr &= ~(AUVIA_PCICONF_ACFM | AUVIA_PCICONF_ACSB);
477	(*pci->write_pci_config)(card->info.bus, card->info.device,
478		card->info.function, AUVIA_PCICONF_JUNK, 4, pr );
479	snooze(100);
480	pr = (*pci->read_pci_config)(card->info.bus, card->info.device,
481		card->info.function, AUVIA_PCICONF_JUNK, 4);
482	PRINT(("AUVIA_PCICONF_JUNK after: %lx\n", pr));
483
484	if(IS_8233(&card->config)) {
485		card->interrupt_mask =
486			AUVIA_8233_SGD_STAT_FLAG_EOL |
487			AUVIA_8233_SGD_STAT_FLAG_EOL << 4 |
488			AUVIA_8233_SGD_STAT_FLAG_EOL << 8 |
489			AUVIA_8233_SGD_STAT_FLAG_EOL << 12 |
490			AUVIA_8233_SGD_STAT_FLAG_EOL << 16 |
491			AUVIA_8233_SGD_STAT_FLAG_EOL << 24 |
492			AUVIA_8233_SGD_STAT_FLAG_EOL << 28;
493	} else {
494		card->interrupt_mask = AUVIA_SGD_STAT_ALL | (AUVIA_SGD_STAT_ALL << 4);
495	}
496
497
498	/* Init streams list */
499	LIST_INIT(&(card->streams));
500
501	/* Init mems list */
502	LIST_INIT(&(card->mems));
503
504	return B_OK;
505}
506
507
508static void
509auvia_shutdown(auvia_dev *card)
510{
511	PRINT(("shutdown(%p)\n", card));
512	ac97_detach(card->config.ac97);
513	remove_io_interrupt_handler(card->config.irq, auvia_int, card);
514}
515
516
517static status_t
518auvia_setup(auvia_dev * card)
519{
520	status_t err = B_OK;
521	unsigned char cmd;
522
523	PRINT(("auvia_setup(%p)\n", card));
524
525	make_device_names(card);
526
527	card->config.subvendor_id = card->info.u.h0.subsystem_vendor_id;
528	card->config.subsystem_id = card->info.u.h0.subsystem_id;
529	card->config.nabmbar = card->info.u.h0.base_registers[0];
530	card->config.irq = card->info.u.h0.interrupt_line;
531	card->config.type = 0;
532	if(card->info.device_id == VIATECH_82C686_AC97_DEVICE_ID)
533		card->config.type |= TYPE_686;
534	if(card->info.device_id == VIATECH_8233_AC97_DEVICE_ID)
535		card->config.type |= TYPE_8233;
536
537	PRINT(("%s deviceid = %#04x chiprev = %x model = %x enhanced at %lx\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 : %lx\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 %ld 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