/*
 * 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.