/*
 * Auich BeOS Driver for Intel Southbridge audio
 *
 * Copyright (c) 2003, Jerome Duval (jerome.duval@free.fr)
 
 * 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.
 *
 * 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 <driver_settings.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "auich.h"
#include "debug.h"
#include "config.h"
#include "util.h"
#include "io.h"
#include <fcntl.h>
#include <unistd.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 *);
int32 auich_int(void *arg);
status_t auich_init(auich_dev * card);
 
pci_module_info	*pci;
 
int32 num_cards;
auich_dev cards[NUM_CARDS];
int32 num_names;
char * names[NUM_CARDS*20+1];
 
volatile bool	int_thread_exit = false;
thread_id 		int_thread_id = -1;
 
extern device_hooks multi_hooks;
 
auich_settings current_settings = {
	48000,	// sample rate
	4096,	// buffer frames
	4,	// buffer count
	false	// use thread
};
 
/* The SIS7012 chipset has SR and PICB registers swapped when compared to Intel */
#define	GET_REG_PICB(x)		(IS_SIS7012(x) ? AUICH_REG_X_SR : AUICH_REG_X_PICB)
#define	GET_REG_SR(x)		(IS_SIS7012(x) ? AUICH_REG_X_PICB : AUICH_REG_X_SR)
 
static void
dump_hardware_regs(device_config *config)
{
	LOG(("GLOB_CNT = %#08x\n", auich_reg_read_32(config, AUICH_REG_GLOB_CNT)));
	LOG(("GLOB_STA = %#08x\n", auich_reg_read_32(config, AUICH_REG_GLOB_STA)));
	LOG(("PI AUICH_REG_X_BDBAR = %#x\n", auich_reg_read_32(config, AUICH_REG_X_BDBAR + AUICH_REG_PI_BASE)));
	LOG(("PI AUICH_REG_X_CIV = %#x\n", auich_reg_read_8(config, AUICH_REG_X_CIV + AUICH_REG_PI_BASE)));
	LOG(("PI AUICH_REG_X_LVI = %#x\n", auich_reg_read_8(config, AUICH_REG_X_LVI + AUICH_REG_PI_BASE)));
	LOG(("PI     REG_X_SR = %#x\n", auich_reg_read_16(config, AUICH_REG_X_SR + AUICH_REG_PI_BASE)));
	LOG(("PI     REG_X_PICB = %#x\n", auich_reg_read_16(config, AUICH_REG_X_PICB + AUICH_REG_PI_BASE)));
	LOG(("PI AUICH_REG_X_PIV = %#x\n", auich_reg_read_8(config, AUICH_REG_X_PIV + AUICH_REG_PI_BASE)));
	LOG(("PI AUICH_REG_X_CR = %#x\n", auich_reg_read_8(config, AUICH_REG_X_CR + AUICH_REG_PI_BASE)));
	LOG(("PO AUICH_REG_X_BDBAR = %#x\n", auich_reg_read_32(config, AUICH_REG_X_BDBAR + AUICH_REG_PO_BASE)));
	LOG(("PO AUICH_REG_X_CIV = %#x\n", auich_reg_read_8(config, AUICH_REG_X_CIV + AUICH_REG_PO_BASE)));
	LOG(("PO AUICH_REG_X_LVI = %#x\n", auich_reg_read_8(config, AUICH_REG_X_LVI + AUICH_REG_PO_BASE)));
	LOG(("PO     REG_X_SR = %#x\n", auich_reg_read_16(config, AUICH_REG_X_SR + AUICH_REG_PO_BASE)));
	LOG(("PO     REG_X_PICB = %#x\n", auich_reg_read_16(config, AUICH_REG_X_PICB + AUICH_REG_PO_BASE)));
	LOG(("PO AUICH_REG_X_PIV = %#x\n", auich_reg_read_8(config, AUICH_REG_X_PIV + AUICH_REG_PO_BASE)));
	LOG(("PO AUICH_REG_X_CR = %#x\n", auich_reg_read_8(config, AUICH_REG_X_CR + AUICH_REG_PO_BASE)));
}
 
/* auich Memory management */
 
static auich_mem *
auich_mem_new(auich_dev *card, size_t size)
{
	auich_mem *mem;
 
	if ((mem = malloc(sizeof(*mem))) == NULL)
		return (NULL);
 
	mem->area = alloc_mem(&mem->phy_base, &mem->log_base, size, "auich buffer",
		true);
	mem->size = size;
	if (mem->area < B_OK) {
		free(mem);
		return NULL;
	}
	return mem;
}
 
 
static void
auich_mem_delete(auich_mem *mem)
{
	if (mem->area > B_OK)
		delete_area(mem->area);
	free(mem);
}
 
 
static void *
auich_mem_alloc(auich_dev *card, size_t size)
{
	auich_mem *mem;
 
	mem = auich_mem_new(card, size);
	if (mem == NULL)
		return (NULL);
 
	LIST_INSERT_HEAD(&(card->mems), mem, next);
 
	return mem;
}
 
 
static void
auich_mem_free(auich_dev *card, void *ptr)
{
	auich_mem 		*mem;
 
	LIST_FOREACH(mem, &card->mems, next) {
		if (mem->log_base != ptr)
			continue;
		LIST_REMOVE(mem, next);
 
		auich_mem_delete(mem);
		break;
	}
}
 
/*	auich stream functions */
 
status_t
auich_stream_set_audioparms(auich_stream *stream, uint8 channels,
     uint8 b16, uint32 sample_rate)
{
	uint8 			sample_size, frame_size;
	LOG(("auich_stream_set_audioparms\n"));
 
	if ((stream->channels == channels)
		&& (stream->b16 == b16)
		&& (stream->sample_rate == sample_rate))
		return B_OK;
 
	if (stream->buffer)
		auich_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 = auich_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
auich_stream_commit_parms(auich_stream *stream)
{
	uint32 *page;
	uint32 i;
	LOG(("auich_stream_commit_parms\n"));
 
	auich_reg_write_8(&stream->card->config, stream->base + AUICH_REG_X_CR, 0);
	snooze(10000); // 10 ms
 
	auich_reg_write_8(&stream->card->config, 
		stream->base + AUICH_REG_X_CR, CR_RR);
	for (i = 10000; i > 0; i--) {
		if (0 == auich_reg_read_8(&stream->card->config, 
			stream->base + AUICH_REG_X_CR)) {
			LOG(("channel reset finished, %x, %d\n", stream->base, i));
			break;
		}
		spin(1);
	}
 
	if (i == 0)
		PRINT(("channel reset failed after 10ms\n"));
 
	page = stream->dmaops_log_base;
 
	for (i = 0; i < AUICH_DMALIST_MAX; i++) {
		page[2 * i] = ((uint32)stream->buffer->phy_base)
			+ (i % stream->bufcount) * stream->blksize;
		page[2 * i + 1] = AUICH_DMAF_IOC | (stream->blksize
			/ (IS_SIS7012(&stream->card->config) ? 1 : 2));
	}
 
	// set physical buffer descriptor base address
	auich_reg_write_32(&stream->card->config, stream->base + AUICH_REG_X_BDBAR,
		(uint32)stream->dmaops_phy_base);
 
	if (stream->use & AUICH_USE_RECORD)
		auich_codec_write(&stream->card->config, AC97_PCM_L_R_ADC_RATE, (uint16)stream->sample_rate);
	else
		auich_codec_write(&stream->card->config, AC97_PCM_FRONT_DAC_RATE, (uint16)stream->sample_rate);
 
	if (stream->use & AUICH_USE_RECORD)
		LOG(("rate : %d\n", auich_codec_read(&stream->card->config, AC97_PCM_L_R_ADC_RATE)));
	else
		LOG(("rate : %d\n", auich_codec_read(&stream->card->config, AC97_PCM_FRONT_DAC_RATE)));
	return B_OK;
}
 
 
status_t
auich_stream_get_nth_buffer(auich_stream *stream, uint8 chan, uint8 buf,
					char** buffer, size_t *stride)
{
	uint8 			sample_size, frame_size;
	LOG(("auich_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 uint8
auich_stream_curaddr(auich_stream *stream)
{
	uint8 index = auich_reg_read_8(&stream->card->config, stream->base + AUICH_REG_X_CIV);
	TRACE(("stream_curaddr %d\n", index));
	return index;
}
 
 
void
auich_stream_start(auich_stream *stream, void (*inth) (void *), void *inthparam)
{
	int32 civ;
	LOG(("auich_stream_start\n"));
 
	stream->inth = inth;
	stream->inthparam = inthparam;
 
	stream->state |= AUICH_STATE_STARTED;
 
	civ = auich_reg_read_8(&stream->card->config, stream->base + AUICH_REG_X_CIV);
 
	// step 1: clear status bits
	auich_reg_write_16(&stream->card->config,
		stream->base + GET_REG_SR(&stream->card->config),
		auich_reg_read_16(&stream->card->config, stream->base + GET_REG_SR(&stream->card->config)));
	auich_reg_read_16(&stream->card->config, stream->base + GET_REG_SR(&stream->card->config));
	// step 2: prepare buffer transfer
	auich_reg_write_8(&stream->card->config, stream->base + AUICH_REG_X_LVI, (civ + 2) % AUICH_DMALIST_MAX);
	auich_reg_read_8(&stream->card->config, stream->base + AUICH_REG_X_LVI);
	// step 3: enable interrupts & busmaster transfer
	auich_reg_write_8(&stream->card->config, stream->base + AUICH_REG_X_CR, CR_RPBM | CR_LVBIE | CR_FEIE | CR_IOCE);
	auich_reg_read_8(&stream->card->config, stream->base + AUICH_REG_X_CR);
 
#ifdef DEBUG
	dump_hardware_regs(&stream->card->config);
#endif
}
 
 
void
auich_stream_halt(auich_stream *stream)
{
	LOG(("auich_stream_halt\n"));
 
	stream->state &= ~AUICH_STATE_STARTED;
 
	auich_reg_write_8(&stream->card->config, stream->base + AUICH_REG_X_CR,
		auich_reg_read_8(&stream->card->config, stream->base + AUICH_REG_X_CR) & ~CR_RPBM);
}
 
 
auich_stream *
auich_stream_new(auich_dev *card, uint8 use, uint32 bufframes, uint8 bufcount)
{
	auich_stream *stream;
	cpu_status status;
	LOG(("auich_stream_new\n"));
 
	stream = malloc(sizeof(auich_stream));
	if (stream == NULL)
		return (NULL);
	stream->card = card;
	stream->use = use;
	stream->state = !AUICH_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 & AUICH_USE_PLAY) {
		stream->base = AUICH_REG_PO_BASE;
		stream->sta = STA_POINT;
	} else {
		stream->base = AUICH_REG_PI_BASE;
		stream->sta = STA_PIINT;
	}
 
	stream->frames_count = 0;
	stream->real_time = 0;
	stream->buffer_cycle = 0;
	stream->update_needed = false;
 
	/* allocate memory for our dma ops */
	stream->dmaops_area = alloc_mem(&stream->dmaops_phy_base, &stream->dmaops_log_base,
		sizeof(auich_dmalist) * AUICH_DMALIST_MAX, "auich dmaops", false);
 
	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
auich_stream_delete(auich_stream *stream)
{
	cpu_status status;
	int32 i;
	LOG(("auich_stream_delete\n"));
 
	auich_stream_halt(stream);
 
	auich_reg_write_8(&stream->card->config, stream->base + AUICH_REG_X_CR, 0);
	snooze(10000); // 10 ms
 
	auich_reg_write_8(&stream->card->config, stream->base + AUICH_REG_X_CR, CR_RR);
	for (i = 10000; i>=0; i--) {
		if (0 == auich_reg_read_8(&stream->card->config, stream->base + AUICH_REG_X_CR)) {
			LOG(("channel reset finished, %x, %d\n", stream->base, i));
			break;
		}
		spin(1);
	}
 
	if (i < 0) {
		LOG(("channel reset failed after 10ms\n"));
	}
 
	auich_reg_write_32(&stream->card->config, stream->base + AUICH_REG_X_BDBAR, 0);
 
	if (stream->dmaops_area > B_OK)
		delete_area(stream->dmaops_area);
 
	if (stream->buffer)
		auich_mem_free(stream->card, stream->buffer->log_base);
 
	status = lock();
	LIST_REMOVE(stream, next);
	unlock(status);
 
	free(stream);
}
 
/* auich interrupt */
 
int32
auich_int(void *arg)
{
	auich_dev	 	*card = arg;
	bool 			gotone 	= false;
	uint8       	curblk;
	auich_stream 	*stream = NULL;
	uint32			sta;
	uint16 			sr;
 
	// TRACE(("auich_int(%p)\n", card));
 
	sta = auich_reg_read_32(&card->config, AUICH_REG_GLOB_STA) & STA_INTMASK;
	if (sta & (STA_S0RI | STA_S1RI | STA_S2RI)) {
		// ignore and clear resume interrupt(s)
		auich_reg_write_32(&card->config, AUICH_REG_GLOB_STA, sta & (STA_S0RI | STA_S1RI | STA_S2RI));
		TRACE(("interrupt !! %x\n", sta));
		gotone = true;
		sta &= ~(STA_S0RI | STA_S1RI | STA_S2RI);
	}
 
	if (sta & card->interrupt_mask) {
		//TRACE(("interrupt !! %x\n", sta));
 
		LIST_FOREACH(stream, &card->streams, next)
			if (sta & stream->sta) {
				sr = auich_reg_read_16(&card->config,
					stream->base + GET_REG_SR(&stream->card->config));
				sr &= SR_MASK;
 
				if (!sr)
					continue;
 
				gotone = true;
 
				if (sr & SR_BCIS) {
					curblk = auich_stream_curaddr(stream);
 
					auich_reg_write_8(&card->config, stream->base + AUICH_REG_X_LVI,
						(curblk + 2) % AUICH_DMALIST_MAX);
 
					stream->trigblk = (curblk) % stream->blkmod;
 
					if (stream->inth)
						stream->inth(stream->inthparam);
				} else {
					TRACE(("interrupt !! sta %x, sr %x\n", sta, sr));
				}
 
				auich_reg_write_16(&card->config,
					stream->base + GET_REG_SR(&stream->card->config), sr);
				auich_reg_write_32(&card->config, AUICH_REG_GLOB_STA, stream->sta);
				sta &= ~stream->sta;
			}
 
		if (sta != 0) {
			dprintf("global status not fully handled %lx!\n", sta);
			auich_reg_write_32(&card->config, AUICH_REG_GLOB_STA, sta);
		}
	} else if (sta != 0) {
		dprintf("interrupt masked %lx, sta %lx\n", card->interrupt_mask, sta);
	}
 
	if (gotone)
		return B_INVOKE_SCHEDULER;
 
	TRACE(("Got unhandled interrupt\n"));
	return B_UNHANDLED_INTERRUPT;
}
 
 
static int32
auich_int_thread(void *data)
{
	cpu_status status;
	while (!int_thread_exit) {
		status = disable_interrupts();
		auich_int(data);
		restore_interrupts(status);
		snooze(1500);
	}
	return 0;
}
 
 
/*	auich driver functions */
 
static status_t
map_io_memory(device_config *config)
{
	if ((config->type & TYPE_ICH4) == 0)
		return B_OK;
 
	config->area_mmbar = map_mem(&config->log_mmbar, config->mmbar, ICH4_MMBAR_SIZE, "auich mmbar io");
	if (config->area_mmbar <= B_OK) {
		LOG(("mapping of mmbar io failed, error = %#x\n",config->area_mmbar));
		return B_ERROR;
	}
	LOG(("mapping of mmbar: area %#x, phys %#x, log %#x\n", config->area_mmbar, config->mmbar, config->log_mmbar));
 
	config->area_mbbar = map_mem(&config->log_mbbar, config->mbbar, ICH4_MBBAR_SIZE, "auich mbbar io");
	if (config->area_mbbar <= B_OK) {
		LOG(("mapping of mbbar io failed, error = %#x\n",config->area_mbbar));
		delete_area(config->area_mmbar);
		config->area_mmbar = -1;
		return B_ERROR;
	}
	LOG(("mapping of mbbar: area %#x, phys %#x, log %#x\n", config->area_mbbar, config->mbbar, config->log_mbbar));
 
	return B_OK;
}
 
 
static status_t
unmap_io_memory(device_config *config)
{
	status_t rv;
	if ((config->type & TYPE_ICH4) == 0)
		return B_OK;
	rv  = delete_area(config->area_mmbar);
	rv |= delete_area(config->area_mbbar);
	return rv;
}
 
/* 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(B_PCI_MODULE_NAME, (module_info **)&pci))
		return ENOSYS;
 
	while ((*pci->get_nth_pci_info)(ix, &info) == B_OK) {
		if ((info.vendor_id == INTEL_VENDOR_ID &&
			(info.device_id == INTEL_82443MX_AC97_DEVICE_ID
			|| info.device_id == INTEL_82801AA_AC97_DEVICE_ID
			|| info.device_id == INTEL_82801AB_AC97_DEVICE_ID
			|| info.device_id == INTEL_82801BA_AC97_DEVICE_ID
			|| info.device_id == INTEL_82801CA_AC97_DEVICE_ID
			|| info.device_id == INTEL_82801DB_AC97_DEVICE_ID
			|| info.device_id == INTEL_82801EB_AC97_DEVICE_ID
			|| info.device_id == INTEL_82801FB_AC97_DEVICE_ID
			|| info.device_id == INTEL_82801GB_AC97_DEVICE_ID
			|| info.device_id == INTEL_6300ESB_AC97_DEVICE_ID
			))
		|| (info.vendor_id == SIS_VENDOR_ID &&
			(info.device_id == SIS_SI7012_AC97_DEVICE_ID
			))
		|| (info.vendor_id == NVIDIA_VENDOR_ID &&
			(info.device_id == NVIDIA_nForce_AC97_DEVICE_ID
			|| info.device_id == NVIDIA_nForce2_AC97_DEVICE_ID
			|| info.device_id == NVIDIA_nForce2_400_AC97_DEVICE_ID
			|| info.device_id == NVIDIA_nForce3_AC97_DEVICE_ID
			|| info.device_id == NVIDIA_nForce3_250_AC97_DEVICE_ID
			|| info.device_id == NVIDIA_CK804_AC97_DEVICE_ID
			|| info.device_id == NVIDIA_MCP51_AC97_DEVICE_ID
			|| info.device_id == NVIDIA_MCP04_AC97_DEVICE_ID
			))
		|| (info.vendor_id == AMD_VENDOR_ID &&
			(info.device_id == AMD_AMD8111_AC97_DEVICE_ID
			|| info.device_id == AMD_AMD768_AC97_DEVICE_ID
			))
			)
		 {
			err = B_OK;
		}
		ix++;
	}
 
	put_module(B_PCI_MODULE_NAME);
 
	return err;
}
 
 
static void
make_device_names(
	auich_dev * card)
{
	sprintf(card->name, "audio/hmulti/auich/%ld", card-cards+1);
	names[num_names++] = card->name;
 
	names[num_names] = NULL;
}
 
 
status_t
auich_init(auich_dev * card)
{
	card->interrupt_mask = STA_PIINT | STA_POINT; //STA_INTMASK;
 
	/* Init streams list */
	LIST_INIT(&(card->streams));
 
	/* Init mems list */
	LIST_INIT(&(card->mems));
 
	return B_OK;
}
 
 
static status_t
auich_setup(auich_dev * card)
{
	status_t err = B_OK;
	status_t rv;
	unsigned char cmd;
	int i;
 
	PRINT(("auich_setup(%p)\n", card));
 
	make_device_names(card);
 
	card->config.subvendor_id = card->info.u.h0.subsystem_vendor_id;
	card->config.subsystem_id = card->info.u.h0.subsystem_id;
	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 == INTEL_82801DB_AC97_DEVICE_ID)
		|| (card->info.device_id == INTEL_82801EB_AC97_DEVICE_ID)
		|| (card->info.device_id == INTEL_82801FB_AC97_DEVICE_ID)
		|| (card->info.device_id == INTEL_82801GB_AC97_DEVICE_ID)
		|| (card->info.device_id == INTEL_6300ESB_AC97_DEVICE_ID))
		card->config.type |= TYPE_ICH4;
	if (card->info.device_id == SIS_SI7012_AC97_DEVICE_ID)
		card->config.type |= TYPE_SIS7012;
 
	PRINT(("%s deviceid = %#04x chiprev = %x model = %x enhanced at %lx\n",
		card->name, card->info.device_id, card->info.revision,
		card->info.u.h0.subsystem_id, card->config.nabmbar));
 
	if (IS_ICH4(&card->config)) {
		// memory mapped access
		card->config.mmbar = 0xfffffffe & (*pci->read_pci_config)
			(card->info.bus, card->info.device, card->info.function, 0x18, 4);
		card->config.mbbar = 0xfffffffe & (*pci->read_pci_config)
			(card->info.bus, card->info.device, card->info.function, 0x1C, 4);
		if (card->config.mmbar == 0 || card->config.mbbar == 0) {
			PRINT(("memory mapped IO not configured\n"));
			return B_ERROR;
		}
	} else {
		// pio access
		card->config.nambar = 0xfffffffe & (*pci->read_pci_config)
			(card->info.bus, card->info.device, card->info.function, 0x10, 4);
		card->config.nabmbar = 0xfffffffe & (*pci->read_pci_config)
			(card->info.bus, card->info.device, card->info.function, 0x14, 4);
		if (card->config.nambar == 0 || card->config.nabmbar == 0) {
			PRINT(("IO space not configured\n"));
			return B_ERROR;
		}
	}
 
	/* before doing anything else, map the IO memory */
	rv = map_io_memory(&card->config);
	if (rv != B_OK) {
		PRINT(("mapping of memory IO space failed\n"));
		return B_ERROR;
	}
 
	cmd = (*pci->read_pci_config)(card->info.bus, card->info.device,
		card->info.function, PCI_command, 2);
	PRINT(("PCI command before: %x\n", cmd));
	if (IS_ICH4(&card->config)) {
		(*pci->write_pci_config)(card->info.bus, card->info.device,
			card->info.function, PCI_command, 2, cmd | PCI_command_memory);
	} else {
		(*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));
 
	/* do a cold reset */
	LOG(("cold reset\n"));
	auich_reg_write_32(&card->config, AUICH_REG_GLOB_CNT, 0);
	snooze(50000); // 50 ms
	auich_reg_write_32(&card->config, AUICH_REG_GLOB_CNT, CNT_COLD | CNT_PRIE);
	LOG(("cold reset finished\n"));
	rv = auich_reg_read_32(&card->config, AUICH_REG_GLOB_CNT);
	if ((rv & CNT_COLD) == 0) {
		LOG(("cold reset failed\n"));
	}
 
	for (i = 0; i < 500; i++) {
		rv = auich_reg_read_32(&card->config, AUICH_REG_GLOB_STA);
		if (rv & STA_S0CR)
			break;
		snooze(1000);
	}
 
	if (!(rv & STA_S0CR)) { /* reset failure */
		/* It never return STA_S0CR in some cases */
		PRINT(("reset failure\n"));
	}
 
	/* attach the codec */
	PRINT(("codec attach\n"));
	ac97_attach(&card->config.ac97, (codec_reg_read)auich_codec_read,
		(codec_reg_write)auich_codec_write, &card->config,
		card->config.subvendor_id, card->config.subsystem_id);
 
	/* Print capabilities though there are no supports for now */
	if ((rv & STA_SAMPLE_CAP) == STA_POM20) {
		LOG(("20 bit precision support\n"));
	}
	if ((rv & STA_CHAN_CAP) == STA_PCM4) {
		LOG(("4ch PCM output support\n"));
	}
	if ((rv & STA_CHAN_CAP) == STA_PCM6) {
		LOG(("6ch PCM output support\n"));
	}
 
	if (current_settings.use_thread || card->config.irq == 0
		|| card->config.irq == 0xff) {
		int_thread_id = spawn_kernel_thread(auich_int_thread,
			"auich interrupt poller", B_REAL_TIME_PRIORITY, card);
		resume_thread(int_thread_id);
	} else {
		PRINT(("installing interrupt : %lx\n", card->config.irq));
		err = install_io_interrupt_handler(card->config.irq, auich_int,
			card, 0);
		if (err != B_OK) {
			PRINT(("failed to install interrupt\n"));
			ac97_detach(card->config.ac97);
			unmap_io_memory(&card->config);
			return err;
		}
	}
 
	if ((err = auich_init(card)) != B_OK)
		return err;
 
	PRINT(("init_driver done\n"));
 
	return err;
}
 
 
status_t
init_driver(void)
{
	int ix = 0;
	void *settings_handle;
	pci_info info;
	status_t err;
	num_cards = 0;
 
	PRINT(("init_driver()\n"));
 
	// get driver settings
	settings_handle = load_driver_settings(AUICH_SETTINGS);
	if (settings_handle != NULL) {
		current_settings.use_thread = get_driver_boolean_parameter (settings_handle, "use_thread", false, false);
		unload_driver_settings (settings_handle);
	}
 
	if (get_module(B_PCI_MODULE_NAME, (module_info **) &pci))
		return ENOSYS;
 
	while ((*pci->get_nth_pci_info)(ix++, &info) == B_OK) {
		if ((info.vendor_id == INTEL_VENDOR_ID
			&& (info.device_id == INTEL_82443MX_AC97_DEVICE_ID
			|| info.device_id == INTEL_82801AA_AC97_DEVICE_ID
			|| info.device_id == INTEL_82801AB_AC97_DEVICE_ID
			|| info.device_id == INTEL_82801BA_AC97_DEVICE_ID
			|| info.device_id == INTEL_82801CA_AC97_DEVICE_ID
			|| info.device_id == INTEL_82801DB_AC97_DEVICE_ID
			|| info.device_id == INTEL_82801EB_AC97_DEVICE_ID
			|| info.device_id == INTEL_82801FB_AC97_DEVICE_ID
			|| info.device_id == INTEL_82801GB_AC97_DEVICE_ID
			|| info.device_id == INTEL_6300ESB_AC97_DEVICE_ID
			))
		|| (info.vendor_id == SIS_VENDOR_ID
			&& (info.device_id == SIS_SI7012_AC97_DEVICE_ID
			))
		|| (info.vendor_id == NVIDIA_VENDOR_ID
			&& (info.device_id == NVIDIA_nForce_AC97_DEVICE_ID
			|| info.device_id == NVIDIA_nForce2_AC97_DEVICE_ID
			|| info.device_id == NVIDIA_nForce2_400_AC97_DEVICE_ID
			|| info.device_id == NVIDIA_nForce3_AC97_DEVICE_ID
			|| info.device_id == NVIDIA_nForce3_250_AC97_DEVICE_ID
			|| info.device_id == NVIDIA_CK804_AC97_DEVICE_ID
			|| info.device_id == NVIDIA_MCP51_AC97_DEVICE_ID
			|| info.device_id == NVIDIA_MCP04_AC97_DEVICE_ID
			))
		|| (info.vendor_id == AMD_VENDOR_ID
			&& (info.device_id == AMD_AMD8111_AC97_DEVICE_ID
			|| info.device_id == AMD_AMD768_AC97_DEVICE_ID
			))
			) {
			if (num_cards == NUM_CARDS) {
				PRINT(("Too many auich cards installed!\n"));
				break;
			}
			memset(&cards[num_cards], 0, sizeof(auich_dev));
			cards[num_cards].info = info;
#ifdef __HAIKU__
			if ((err = (*pci->reserve_device)(info.bus, info.device, info.function,
				DRIVER_NAME, &cards[num_cards])) < B_OK) {
				dprintf("%s: failed to reserve_device(%d, %d, %d,): %s\n",
					DRIVER_NAME, info.bus, info.device, info.function,
					strerror(err));
				continue;
			}
#endif
			if (auich_setup(&cards[num_cards])) {
				PRINT(("Setup of auich %ld failed\n", num_cards+1));
#ifdef __HAIKU__
				(*pci->unreserve_device)(info.bus, info.device, info.function,
					DRIVER_NAME, &cards[num_cards]);
#endif
			}
			else {
				num_cards++;
			}
		}
	}
	if (!num_cards) {
		PRINT(("no cards\n"));
		put_module(B_PCI_MODULE_NAME);
		PRINT(("no suitable cards found\n"));
		return ENODEV;
	}
 
 
#if DEBUG
	//add_debugger_command("auich", auich_debug, "auich [card# (1-n)]");
#endif
	return B_OK;
}
 
 
static void
auich_shutdown(auich_dev *card)
{
	PRINT(("shutdown(%p)\n", card));
	ac97_detach(card->config.ac97);
 
	card->interrupt_mask = 0;
 
	if (current_settings.use_thread) {
		status_t exit_value;
		int_thread_exit = true;
		wait_for_thread(int_thread_id, &exit_value);
	} else
		remove_io_interrupt_handler(card->config.irq, auich_int, card);
 
	unmap_io_memory(&card->config);
}
 
 
void
uninit_driver(void)
{
	int ix, cnt = num_cards;
	num_cards = 0;
 
	PRINT(("uninit_driver()\n"));
	//remove_debugger_command("auich", auich_debug);
 
	for (ix=0; ix<cnt; ix++) {
		auich_shutdown(&cards[ix]);
#ifdef __HAIKU__
		(*pci->unreserve_device)(cards[ix].info.bus,
			cards[ix].info.device, cards[ix].info.function,
			DRIVER_NAME, &cards[ix]);
#endif
	}
	memset(&cards, 0, sizeof(cards));
	put_module(B_PCI_MODULE_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;

V576 Incorrect format. Consider checking the second actual argument of the 'dprintf' function. The memsize type argument is expected.

V576 Incorrect format. Consider checking the second actual argument of the 'debug_printf' function. The memsize type argument is expected.

V576 Incorrect format. Consider checking the second actual argument of the 'dprintf' function. The memsize type argument is expected.

V576 Incorrect format. Consider checking the third actual argument of the 'dprintf' function. The memsize type argument is expected.

V576 Incorrect format. Consider checking the sixth actual argument of the 'debug_printf' function. The memsize type argument is expected.

V576 Incorrect format. Consider checking the second actual argument of the 'debug_printf' function. The memsize type argument is expected.