/*
 * Auvia BeOS Driver for Via VT82xx Southbridge audio
 *
 * Copyright (c) 2003, Jerome Duval (jerome.duval@free.fr)
 
 * This code is derived from software contributed to The NetBSD Foundation
 * by Tyler C. Sarna
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the NetBSD
 *	Foundation, Inc. and its contributors.
 * 4. Neither the name of The NetBSD Foundation nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
 
#include <KernelExport.h>
#include <PCI.h>
#include <string.h>
#include <stdio.h>
#include "auvia.h"
#include "debug.h"
#include "config.h"
#include "util.h"
#include "io.h"
#include <fcntl.h>
#include <unistd.h>
#include <malloc.h>
#include "ac97.h"

status_t init_hardware(void);
status_t init_driver(void);
void uninit_driver(void);
const char ** publish_devices(void);
device_hooks * find_device(const char *);

static char pci_name[] = B_PCI_MODULE_NAME;
static pci_module_info	*pci;

int32 num_cards;
auvia_dev cards[NUM_CARDS];
int32 num_names;
char * names[NUM_CARDS*20+1];

extern device_hooks multi_hooks;

/* Auvia Memory management */

static auvia_mem *
auvia_mem_new(auvia_dev *card, size_t size)
{
	auvia_mem *mem;

	if ((mem = malloc(sizeof(*mem))) == NULL)
		return (NULL);

	mem->area = alloc_mem(&mem->phy_base, &mem->log_base, size, "auvia buffer");
	mem->size = size;
	if (mem->area < B_OK) {
		free(mem);
		return NULL;
	}
	return mem;
}

static void
auvia_mem_delete(auvia_mem *mem)
{
	if(mem->area > B_OK)
		delete_area(mem->area);
	free(mem);
}

void *
auvia_mem_alloc(auvia_dev *card, size_t size)
{
	auvia_mem *mem;
	
	mem = auvia_mem_new(card, size);
	if (mem == NULL)
		return (NULL);

	LIST_INSERT_HEAD(&(card->mems), mem, next);

	return mem;
}

void
auvia_mem_free(auvia_dev *card, void *ptr)
{
	auvia_mem 		*mem;
	
	LIST_FOREACH(mem, &card->mems, next) {
		if (mem->log_base != ptr)
			continue;
		LIST_REMOVE(mem, next);
		
		auvia_mem_delete(mem);
		break;
	}
}

/*	Auvia stream functions */

status_t
auvia_stream_set_audioparms(auvia_stream *stream, uint8 channels,
     uint8 b16, uint32 sample_rate)
{
	uint8 			sample_size, frame_size;
	LOG(("auvia_stream_set_audioparms\n"));

	if ((stream->channels == channels) &&
		(stream->b16 == b16) && 
		(stream->sample_rate == sample_rate))
		return B_OK;
	
	if(stream->buffer)
		auvia_mem_free(stream->card, stream->buffer->log_base);
		
	stream->b16 = b16;
	stream->sample_rate = sample_rate;
	stream->channels = channels;
	
	sample_size = stream->b16 + 1;
	frame_size = sample_size * stream->channels;
	
	stream->buffer = auvia_mem_alloc(stream->card, stream->bufframes * frame_size * stream->bufcount);
	
	stream->trigblk = 0;	/* This shouldn't be needed */
	stream->blkmod = stream->bufcount;
	stream->blksize = stream->bufframes * frame_size;
		
	return B_OK;
}

status_t
auvia_stream_commit_parms(auvia_stream *stream)
{
	int             i;
	uint32      	*page;
	uint32			value;
	LOG(("auvia_stream_commit_parms\n"));
	
	page = stream->dmaops_log_base;
	
	for(i=0; i<stream->bufcount; i++) {
		page[2*i] = ((uint32)stream->buffer->phy_base) + 
			i * stream->blksize;
		page[2*i + 1] = AUVIA_DMAOP_FLAG | stream->blksize;
	}
	
	page[2*stream->bufcount - 1] &= ~AUVIA_DMAOP_FLAG;
	page[2*stream->bufcount - 1] |= AUVIA_DMAOP_EOL;
	
	auvia_reg_write_32(&stream->card->config, stream->base + AUVIA_RP_DMAOPS_BASE, 
		(uint32)stream->dmaops_phy_base);
		
	if(stream->use & AUVIA_USE_RECORD)
		auvia_codec_write(&stream->card->config, AC97_PCM_LR_ADC_RATE, (uint16)stream->sample_rate);
	else
		auvia_codec_write(&stream->card->config, AC97_PCM_FRONT_DAC_RATE, (uint16)stream->sample_rate);
	
	if(IS_8233(&stream->card->config)) {
		if(stream->base != AUVIA_8233_MP_BASE) {
			value = auvia_reg_read_32(&stream->card->config, stream->base + AUVIA_8233_RP_RATEFMT);
			value &= ~(AUVIA_8233_RATEFMT_48K | AUVIA_8233_RATEFMT_STEREO | AUVIA_8233_RATEFMT_16BIT);
			if(stream->use & AUVIA_USE_PLAY)
				value |= AUVIA_8233_RATEFMT_48K * (stream->sample_rate / 20) / (48000 / 20);
			value |= (stream->channels == 2 ? AUVIA_8233_RATEFMT_STEREO : 0)
				| (stream->b16 ? AUVIA_8233_RATEFMT_16BIT : 0);
			auvia_reg_write_32(&stream->card->config, stream->base + AUVIA_8233_RP_RATEFMT, value);
		} else {
			static const uint32 slottab[7] = {0, 0xff000011, 0xff000021, 0xff000521,
											0xff004321, 0xff054321, 0xff654321};
			value = (stream->b16 ? AUVIA_8233_MP_FORMAT_16BIT : AUVIA_8233_MP_FORMAT_8BIT)
				| ((stream->channels << 4) & AUVIA_8233_MP_FORMAT_CHANNEL_MASK) ;
			auvia_reg_write_8(&stream->card->config, stream->base + AUVIA_8233_OFF_MP_FORMAT, value);
			auvia_reg_write_32(&stream->card->config, stream->base + AUVIA_8233_OFF_MP_STOP, 
				slottab[stream->channels]);
		}
	}
	//auvia_codec_write(&stream->card->config, AC97_SPDIF_CONTROL, (uint16)stream->sample_rate);
		
	return B_OK;
}

status_t
auvia_stream_get_nth_buffer(auvia_stream *stream, uint8 chan, uint8 buf, 
					char** buffer, size_t *stride)
{
	uint8 			sample_size, frame_size;
	LOG(("auvia_stream_get_nth_buffer\n"));
	
	sample_size = stream->b16 + 1;
	frame_size = sample_size * stream->channels;
	
	*buffer = stream->buffer->log_base + (buf * stream->bufframes * frame_size)
		+ chan * sample_size;
	*stride = frame_size;
	
	return B_OK;
}

static uint32
auvia_stream_curaddr(auvia_stream *stream)
{
	uint32 addr;
	if(IS_8233(&stream->card->config)) {
		addr = auvia_reg_read_32(&stream->card->config, stream->base + AUVIA_RP_DMAOPS_BASE);
		TRACE(("stream_curaddr %p, phy_base %p\n", addr, (uint32)stream->dmaops_phy_base));
		return (addr - (uint32)stream->dmaops_phy_base - 4) / 8;
	} else {
		addr = auvia_reg_read_32(&stream->card->config, stream->base + AUVIA_RP_DMAOPS_BASE);
		TRACE(("stream_curaddr %p, phy_base %p\n", addr, (uint32)stream->dmaops_phy_base));
		return (addr - (uint32)stream->dmaops_phy_base - 8) / 8;
	}
}

void
auvia_stream_start(auvia_stream *stream, void (*inth) (void *), void *inthparam)
{
	LOG(("auvia_stream_start\n"));
	
	stream->inth = inth;
	stream->inthparam = inthparam;
		
	stream->state |= AUVIA_STATE_STARTED;
	
	if(IS_8233(&stream->card->config)) {
		if(stream->base != AUVIA_8233_MP_BASE) {
			auvia_reg_write_8(&stream->card->config, stream->base + AUVIA_8233_RP_DXS_LVOL, 0);
			auvia_reg_write_8(&stream->card->config, stream->base + AUVIA_8233_RP_DXS_RVOL, 0);
		}
		auvia_reg_write_8(&stream->card->config, stream->base + AUVIA_RP_CONTROL, 
			AUVIA_RPCTRL_START | AUVIA_RPCTRL_AUTOSTART | AUVIA_RPCTRL_STOP
			| AUVIA_RPCTRL_EOL | AUVIA_RPCTRL_FLAG);
	} else {
		uint8 regvalue = (stream->channels > 1 ? AUVIA_RPMODE_STEREO : 0)
			| (stream->b16 == 1 ? AUVIA_RPMODE_16BIT : 0)
			| AUVIA_RPMODE_INTR_FLAG | AUVIA_RPMODE_INTR_EOL | AUVIA_RPMODE_AUTOSTART;
		auvia_reg_write_8(&stream->card->config, stream->base + AUVIA_RP_MODE, regvalue);
		auvia_reg_write_8(&stream->card->config, stream->base + AUVIA_RP_CONTROL, AUVIA_RPCTRL_START);
	}
}

void
auvia_stream_halt(auvia_stream *stream)
{
	LOG(("auvia_stream_halt\n"));
			
	stream->state &= ~AUVIA_STATE_STARTED;
	
	auvia_reg_write_8(&stream->card->config, stream->base + AUVIA_RP_CONTROL, AUVIA_RPCTRL_TERMINATE);
}

auvia_stream *
auvia_stream_new(auvia_dev *card, uint8 use, uint32 bufframes, uint8 bufcount)
{
	auvia_stream *stream;
	cpu_status status;
	LOG(("auvia_stream_new\n"));

	stream = malloc(sizeof(auvia_stream));
	if (stream == NULL)
		return (NULL);
	stream->card = card;
	stream->use = use;
	stream->state = !AUVIA_STATE_STARTED;
	stream->b16 = 0;
	stream->sample_rate = 0;
	stream->channels = 0;
	stream->bufframes = bufframes;
	stream->bufcount = bufcount;
	stream->inth = NULL;
	stream->inthparam = NULL;
	stream->buffer = NULL;
	stream->blksize = 0;
	stream->trigblk = 0;
	stream->blkmod = 0;
	
	if(use & AUVIA_USE_PLAY) {
		if(IS_8233(&card->config))
			stream->base = AUVIA_8233_MP_BASE;
			//stream->base = AUVIA_PLAY_BASE;
		else
			stream->base = AUVIA_PLAY_BASE;
	} else {
		if(IS_8233(&card->config))
			stream->base = AUVIA_8233_RECORD_BASE;
		else
			stream->base = AUVIA_RECORD_BASE;
	}
		
	stream->frames_count = 0;
	stream->real_time = 0;
	stream->update_needed = false;
	
	/* allocate memory for our dma ops */
	stream->dmaops_area = alloc_mem(&stream->dmaops_phy_base, &stream->dmaops_log_base, 
		VIA_TABLE_SIZE, "auvia dmaops");
		
	if (stream->dmaops_area < B_OK) {
		PRINT(("couldn't allocate memory\n"));
		free(stream);
		return NULL;	
	}

	status = lock();
	LIST_INSERT_HEAD((&card->streams), stream, next);
	unlock(status);
	
	return stream;
}

void
auvia_stream_delete(auvia_stream *stream)
{
	cpu_status status;
	LOG(("auvia_stream_delete\n"));
	
	auvia_stream_halt(stream);
	
	auvia_reg_write_32(&stream->card->config, stream->base + AUVIA_RP_DMAOPS_BASE, 0);
	
	if (stream->dmaops_area > B_OK)
		delete_area(stream->dmaops_area);
		
	if(stream->buffer)
		auvia_mem_free(stream->card, stream->buffer->log_base);
	
	status = lock();
	LIST_REMOVE(stream, next);
	unlock(status);
	
	free(stream);
}

/* Auvia interrupt */

int32 auvia_int(void *arg)
{
	auvia_dev	 *card = arg;
	bool gotone = false;
	uint32       curblk;
	auvia_stream *stream;
	
	if(auvia_reg_read_32(&card->config, AUVIA_SGD_SHADOW) 
		& card->interrupt_mask) {
	
		LIST_FOREACH(stream, &card->streams, next)
			if(auvia_reg_read_8(&card->config, stream->base + AUVIA_RP_STAT) & AUVIA_RPSTAT_INTR) {
				gotone = true;
				//TRACE(("interrupt\n"));
				
				curblk = auvia_stream_curaddr(stream);
				TRACE(("RPSTAT_INTR at trigblk %lu, stream->trigblk %lu\n", curblk, stream->trigblk));
				if (curblk == stream->trigblk) {
					//TRACE(("AUVIA_RPSTAT_INTR at trigblk %lu\n", curblk));
						
					if(stream->inth)
						stream->inth(stream->inthparam);
						
					stream->trigblk++;
					stream->trigblk %= stream->blkmod;
				}				
				
				auvia_reg_write_8(&card->config, stream->base + AUVIA_RP_STAT, AUVIA_RPSTAT_INTR);
			}
	} else {
		TRACE(("SGD_SHADOW %x %x\n", card->interrupt_mask, auvia_reg_read_32(&card->config, AUVIA_SGD_SHADOW)));
	}
	
	if(gotone)
		return B_HANDLED_INTERRUPT;

	TRACE(("Got unhandled interrupt\n"));
	return B_UNHANDLED_INTERRUPT;
}

/*	Auvia driver functions */

/* detect presence of our hardware */
status_t 
init_hardware(void)
{
	int ix=0;
	pci_info info;
	status_t err = ENODEV;
	
	LOG_CREATE();

	PRINT(("init_hardware()\n"));

	if (get_module(pci_name, (module_info **)&pci))
		return ENOSYS;

	while ((*pci->get_nth_pci_info)(ix, &info) == B_OK) {
		if (info.vendor_id == VIATECH_VENDOR_ID &&
			(info.device_id == VIATECH_82C686_AC97_DEVICE_ID
			|| info.device_id == VIATECH_8233_AC97_DEVICE_ID
			)) {
			err = B_OK;
		}
		ix++;
	}
		
	put_module(pci_name);

	return err;
}

static void
make_device_names(
	auvia_dev * card)
{
	sprintf(card->name, "audio/multi/auvia/%ld", card-cards+1);
	names[num_names++] = card->name;

	names[num_names] = NULL;
}


status_t
auvia_init(auvia_dev * card)
{
	uint32 pr;

	pr = (*pci->read_pci_config)(card->info.bus, card->info.device, card->info.function, AUVIA_PCICONF_JUNK, 4);
	PRINT(("AUVIA_PCICONF_JUNK before: %x\n", pr));
	pr &= ~AUVIA_PCICONF_ENABLES;
	pr |= AUVIA_PCICONF_ACLINKENAB | AUVIA_PCICONF_ACNOTRST | AUVIA_PCICONF_ACVSR | AUVIA_PCICONF_ACSGD;
	pr &= ~(AUVIA_PCICONF_ACFM | AUVIA_PCICONF_ACSB);
	(*pci->write_pci_config)(card->info.bus, card->info.device, card->info.function, AUVIA_PCICONF_JUNK, 4, pr );
	snooze(100); 
	pr = (*pci->read_pci_config)(card->info.bus, card->info.device, card->info.function, AUVIA_PCICONF_JUNK, 4);
	PRINT(("AUVIA_PCICONF_JUNK after: %x\n", pr));

	if(IS_8233(&card->config)) {
		card->interrupt_mask = 
			AUVIA_8233_SGD_STAT_FLAG_EOL |
			AUVIA_8233_SGD_STAT_FLAG_EOL << 4 |
			AUVIA_8233_SGD_STAT_FLAG_EOL << 8 |
			AUVIA_8233_SGD_STAT_FLAG_EOL << 12 |
			AUVIA_8233_SGD_STAT_FLAG_EOL << 16 |
			AUVIA_8233_SGD_STAT_FLAG_EOL << 24 |
			AUVIA_8233_SGD_STAT_FLAG_EOL << 28;
	} else {
		card->interrupt_mask = AUVIA_SGD_STAT_ALL | (AUVIA_SGD_STAT_ALL << 4);
	}

	
	/* Init streams list */
	LIST_INIT(&(card->streams));
	
	/* Init mems list */
	LIST_INIT(&(card->mems));
	
	return B_OK;
}

static status_t
auvia_setup(auvia_dev * card)
{
	status_t err = B_OK;
	unsigned char cmd;
	
	PRINT(("auvia_setup(%p)\n", card));

	make_device_names(card);
	
	card->config.nabmbar = card->info.u.h0.base_registers[0];
	card->config.irq = card->info.u.h0.interrupt_line;
	card->config.type = 0;
	if(card->info.device_id == VIATECH_82C686_AC97_DEVICE_ID)
		card->config.type |= TYPE_686;
	if(card->info.device_id == VIATECH_8233_AC97_DEVICE_ID)
		card->config.type |= TYPE_8233;
	
	PRINT(("%s deviceid = %#04x chiprev = %x model = %x enhanced at %x\n", card->name, card->info.device_id,
		card->info.revision, card->info.u.h0.subsystem_id, card->config.nabmbar));
		
	cmd = (*pci->read_pci_config)(card->info.bus, card->info.device, card->info.function, PCI_command, 2);
	PRINT(("PCI command before: %x\n", cmd));
	(*pci->write_pci_config)(card->info.bus, card->info.device, card->info.function, PCI_command, 2, cmd | PCI_command_io);
	cmd = (*pci->read_pci_config)(card->info.bus, card->info.device, card->info.function, PCI_command, 2);
	PRINT(("PCI command after: %x\n", cmd));

	/* reset the codec */	
	PRINT(("codec reset\n"));
	auvia_codec_write(&card->config, 0x00, 0x0000);
	snooze(50000); // 50 ms

	ac97_init(&card->config);
	ac97_amp_enable(&card->config, true);

	PRINT(("codec vendor id      = %#08x\n",ac97_get_vendor_id(&card->config)));
	PRINT(("codec description     = %s\n",ac97_get_vendor_id_description(&card->config)));
	PRINT(("codec 3d enhancement = %s\n",ac97_get_3d_stereo_enhancement(&card->config)));
	
	PRINT(("installing interrupt : %x\n", card->config.irq));
	install_io_interrupt_handler(card->config.irq, auvia_int, card, 0);
		
	/*PRINT(("codec master output = %#04x\n",auvia_codec_read(&card->config, 0x02)));
	PRINT(("codec aux output    = %#04x\n",auvia_codec_read(&card->config, 0x04)));
	PRINT(("codec mono output   = %#04x\n",auvia_codec_read(&card->config, 0x06)));
	PRINT(("codec pcm output    = %#04x\n",auvia_codec_read(&card->config, 0x18)));
	PRINT(("codec line in	    = %#04x\n",auvia_codec_read(&card->config, 0x10)));
	PRINT(("codec record line in= %#04x\n",auvia_codec_read(&card->config, 0x1a)));
	PRINT(("codec record gain   = %#04x\n",auvia_codec_read(&card->config, 0x1c)));*/
	
	PRINT(("writing codec registers\n"));
	// TODO : to move with AC97
	/* enable master output */
	auvia_codec_write(&card->config, AC97_MASTER_VOLUME, 0x0000);
	/* enable aux output */
	auvia_codec_write(&card->config, AC97_AUX_OUT_VOLUME, 0x0000);
	/* enable mono output */
	//auvia_codec_write(&card->config, AC97_MONO_VOLUME, 0x0004);
	/* enable pcm output */
	auvia_codec_write(&card->config, AC97_PCM_OUT_VOLUME, 0x0808);
	/* enable line in */
	//auvia_codec_write(&card->config, AC97_LINE_IN_VOLUME, 0x8808);
	/* set record line in */
	auvia_codec_write(&card->config, AC97_RECORD_SELECT, 0x0404);
	/* set record gain */
	//auvia_codec_write(&card->config, AC97_RECORD_GAIN, 0x0000);

	PRINT(("codec master output = %#04x\n",auvia_codec_read(&card->config, AC97_MASTER_VOLUME)));
	PRINT(("codec aux output    = %#04x\n",auvia_codec_read(&card->config, AC97_AUX_OUT_VOLUME)));
	PRINT(("codec mono output   = %#04x\n",auvia_codec_read(&card->config, AC97_MONO_VOLUME)));
	PRINT(("codec pcm output    = %#04x\n",auvia_codec_read(&card->config, AC97_PCM_OUT_VOLUME)));
	PRINT(("codec line in	    = %#04x\n",auvia_codec_read(&card->config, AC97_LINE_IN_VOLUME)));
	PRINT(("codec record line in= %#04x\n",auvia_codec_read(&card->config, AC97_RECORD_SELECT)));
	PRINT(("codec record gain   = %#04x\n",auvia_codec_read(&card->config, AC97_RECORD_GAIN)));
		
	if ((err = auvia_init(card)))
		return (err);
		
	PRINT(("init_driver done\n"));

	return err;
}


status_t
init_driver(void)
{
	int ix=0;
	
	pci_info info;
	num_cards = 0;
	
	PRINT(("init_driver()\n"));
	load_driver_symbols("auvia");

	if (get_module(pci_name, (module_info **) &pci))
		return ENOSYS;
		
	while ((*pci->get_nth_pci_info)(ix, &info) == B_OK) {
		if (info.vendor_id == VIATECH_VENDOR_ID &&
			(info.device_id == VIATECH_82C686_AC97_DEVICE_ID 
			|| info.device_id == VIATECH_8233_AC97_DEVICE_ID
			)) {
			if (num_cards == NUM_CARDS) {
				PRINT(("Too many auvia cards installed!\n"));
				break;
			}
			memset(&cards[num_cards], 0, sizeof(auvia_dev));
			cards[num_cards].info = info;
			if (auvia_setup(&cards[num_cards])) {
				PRINT(("Setup of auvia %ld failed\n", num_cards+1));
			}
			else {
				num_cards++;
			}
		}
		ix++;
	}
	if (!num_cards) {
		PRINT(("no cards\n"));
		put_module(pci_name);
		PRINT(("no suitable cards found\n"));
		return ENODEV;
	}

	
#if DEBUG
	//add_debugger_command("auvia", auvia_debug, "auvia [card# (1-n)]");
#endif
	return B_OK;
}


static void
auvia_shutdown(auvia_dev *card)
{
	PRINT(("shutdown(%p)\n", card));
	remove_io_interrupt_handler(card->config.irq, auvia_int, card);
}


void
uninit_driver(void)
{
	int ix, cnt = num_cards;
	num_cards = 0;

	PRINT(("uninit_driver()\n"));
	//remove_debugger_command("auvia", auvia_debug);

	for (ix=0; ix<cnt; ix++) {
		auvia_shutdown(&cards[ix]);
	}
	memset(&cards, 0, sizeof(cards));
	put_module(pci_name);
}


const char **
publish_devices(void)
{
	int ix = 0;
	PRINT(("publish_devices()\n"));

	for (ix=0; names[ix]; ix++) {
		PRINT(("publish %s\n", names[ix]));
	}
	return (const char **)names;
}


device_hooks *
find_device(const char * name)
{
	int ix;

	PRINT(("find_device(%s)\n", name));

	for (ix=0; ix<num_cards; ix++) {
		if (!strcmp(cards[ix].name, name)) {
			return &multi_hooks;
		}
	}
	PRINT(("find_device(%s) failed\n", name));
	return NULL;
}

int32	api_version = B_CUR_DRIVER_API_VERSION;
