/*
 **********************************************************************
 *     hwaccess.c -- Hardware access layer
 *     Copyright 1999, 2000 Creative Labs, Inc.
 *
 **********************************************************************
 *
 *     Date                 Author          Summary of changes
 *     ----                 ------          ------------------
 *     October 20, 1999     Bertrand Lee    base code release
 *     December 9, 1999     Jon Taylor      rewrote the I/O subsystem
 *
 **********************************************************************
 *
 *     This program is free software; you can redistribute it and/or
 *     modify it under the terms of the GNU General Public License as
 *     published by the Free Software Foundation; either version 2 of
 *     the License, or (at your option) any later version.
 *
 *     This program is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 *
 *     You should have received a copy of the GNU General Public
 *     License along with this program; if not, write to the Free
 *     Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139,
 *     USA.
 *
 **********************************************************************
 */

#include "hwaccess.h"
#include "mycommon.h"

#include <linux/delay.h>

spinlock_t sblive_spinlock = SPIN_LOCK_UNLOCKED;

int halHWInit(struct sblive_hw *sb_hw);

int halInit(struct sblive_hw *sb_hw, struct sblive_config *config, u32 *hwflags)
{
	unsigned long ioaddr;

	/* should fill in default values here */
	sb_hw->paneffectsbus = 0;
	sb_hw->auxeffectsbus = 1;
	sb_hw->choruseffectsbus = 2;
	sb_hw->reverbeffectsbus = 3;

	/* Setup Critical Sections */
	spin_lock_init(&sb_hw->emu_lock);

	sb_hw->numvoices = NUM_G;
	sb_hw->dsCardCfg = *config;

	sb_hw->hwaddr = config->ioportbase[0];
	ioaddr = config->ioportbase[0];

	sb_hw->mixeraddx = (u32) (ioaddr + AC97DATA);
	sb_hw->hwconfigaddx = (u32) (ioaddr + HCFG);

	sb_hw->hw_irq.irq = config->IRQregs[0];
	if (sb_hw->hw_irq.irq) 
	{
		if (sb_hw->hw_irq.irq == 2)
		  sb_hw->hw_irq.irq = 9;
	}

	sb_hw->hw_irq.sb_hw = sb_hw;
	
	/* Init Card */
	if (halHWInit(sb_hw) != CTSTATUS_SUCCESS)
	  return CTSTATUS_ERROR;

	if (sblive_irqmgrInit(&sb_hw->hw_irq) != CTSTATUS_SUCCESS) 
	{
		DPF("Failed to initialize IRQ manager\n");
		sb_hw->hw_irq.irq = 0;
	}
	
	sblive_voiceInit(sb_hw);
	sblive_timerInit(sb_hw);
	emu10kaddxmgrInit(sb_hw);

	DPD("  hw control register -> %x\n", sblive_readfn0(sb_hw, HCFG));
	
	return CTSTATUS_SUCCESS;
}


/****************************************************************************/
/*   halExit(struct sblive_hw * sb_hw)                                      */
/****************************************************************************/
int halExit(struct sblive_hw * sb_hw)
{
	int ch;

	sblive_voiceExit(sb_hw);
	sblive_timerExit(sb_hw);
	emu10kaddxmgrExit(sb_hw);

	if (sb_hw->hw_irq.irq) 
	{
		sblive_writefn0(sb_hw,INTE, DISABLE);
		sblive_irqmgrExit(&sb_hw->hw_irq);
	}

	/** Shutdown the chip **/
	for (ch = 0; ch < NUM_G; ch++)
	  sblive_writeptr(sb_hw, DCYSUSV, ch, ENV_OFF);
	
	for (ch = 0; ch < NUM_G; ch++) 
	{
		sblive_writeptr(sb_hw, VTFT, ch, 0);
		sblive_writeptr(sb_hw, CVCF, ch, 0);
		sblive_writeptr(sb_hw, PTRX, ch, 0);
		sblive_writeptr(sb_hw, CPF, ch, 0);
	}

	/* Reset recording buffers */
	sblive_writeptr(sb_hw, MICBS, 0, 0);
	sblive_writeptr(sb_hw, MICBA, 0, 0);
	sblive_writeptr(sb_hw, FXBS, 0, 0);
	sblive_writeptr(sb_hw, FXBA, 0, 0);
	sblive_writeptr(sb_hw, FXWC, 0, 0);
	sblive_writeptr(sb_hw, ADCBS, 0, ADCBS_BUFSIZE_NONE);
	sblive_writeptr(sb_hw, ADCBA, 0, 0);
	sblive_writeptr(sb_hw, TCBS, 0, TCBS_BUFFSIZE_16K);
	sblive_writeptr(sb_hw, TCB, 0, 0);
	sblive_writeptr(sb_hw, DBG, 0, 0x8000);

	/* Disable channel interrupt */
	sblive_writeptr(sb_hw, CLIEL, 0, 0);
	sblive_writeptr(sb_hw, CLIEH, 0, 0);
	sblive_writeptr(sb_hw, SOLEL, 0, 0);
	sblive_writeptr(sb_hw, SOLEH, 0, 0);

	/* Disable audio and lock cache */
	sblive_writefn0(sb_hw, HCFG, HCFG_LOCKSOUNDCACHE | HCFG_LOCKTANKCACHE | HCFG_MUTEBUTTONENABLE);
	sblive_writeptr(sb_hw, PTB, 0, 0);

#ifdef TANKMEM
	free_pages(sb_hw->tankmem, sb_hw->tmemsizeIdx + 2);
#endif
	free_pages(sb_hw->virtualpagetable, 4);
#ifdef MEMTEST
	DPD("free_pages: [%x]\n", sb_hw->virtualpagetable);
#endif

	osFreeMemPhysical(sb_hw->silentpage);

	return CTSTATUS_SUCCESS;
}


/****************************************************************************/
/*  halHWInit (struct sblive_hw *sb_hw)                                     */
/****************************************************************************/
int halHWInit(struct sblive_hw *sb_hw)
{
	int nCh;
	u32 size = 0;
	u32 sizeIdx = 0;
	int status;

	/* Disable audio and lock cache */
	sblive_writefn0(sb_hw, HCFG, HCFG_LOCKSOUNDCACHE | HCFG_LOCKTANKCACHE | HCFG_MUTEBUTTONENABLE);

	/* Reset recording buffers */
	sblive_writeptr(sb_hw, MICBS, 0, 0);
	sblive_writeptr(sb_hw, MICBA, 0, 0);
	sblive_writeptr(sb_hw, FXBS, 0, 0);
	sblive_writeptr(sb_hw, FXBA, 0, 0);
	sblive_writeptr(sb_hw, ADCBS, 0, ADCBS_BUFSIZE_NONE);
	sblive_writeptr(sb_hw, ADCBA, 0, 0);

	/* Disable channel interrupt */
	sblive_writefn0(sb_hw,INTE, DISABLE);
	sblive_writeptr(sb_hw, CLIEL, 0, 0);
	sblive_writeptr(sb_hw, CLIEH, 0, 0);
	sblive_writeptr(sb_hw, SOLEL, 0, 0);
	sblive_writeptr(sb_hw, SOLEH, 0, 0);

	/* Init envelope engine */
	for (nCh = 0; nCh < NUM_G; nCh++) 
	{
		sblive_writeptr(sb_hw, DCYSUSV, nCh, ENV_OFF);
		sblive_writeptr(sb_hw, IP, nCh, 0);
		sblive_writeptr(sb_hw, VTFT, nCh, 0xffff);
		sblive_writeptr(sb_hw, CVCF, nCh, 0xffff);
		sblive_writeptr(sb_hw, PTRX, nCh, 0);
		sblive_writeptr(sb_hw, CPF, nCh, 0);
		sblive_writeptr(sb_hw, CCR, nCh, 0);

		sblive_writeptr(sb_hw, PSST, nCh, 0);
		sblive_writeptr(sb_hw, DSL, nCh, 0x10);
		sblive_writeptr(sb_hw, CCCA, nCh, 0);
		sblive_writeptr(sb_hw, Z1, nCh, 0);
		sblive_writeptr(sb_hw, Z2, nCh, 0);
		sblive_writeptr(sb_hw, FXRT, nCh, 0xd01c0000);

		sblive_writeptr(sb_hw, ATKHLDM, nCh, 0);
		sblive_writeptr(sb_hw, DCYSUSM, nCh, 0);
		sblive_writeptr(sb_hw, IFATN, nCh, 0xffff);
		sblive_writeptr(sb_hw, PEFE, nCh, 0);
		sblive_writeptr(sb_hw, FMMOD, nCh, 0);
		sblive_writeptr(sb_hw, TREMFRQ, nCh, 24);	/* 1 Hz */
		sblive_writeptr(sb_hw, FM2FRQ2, nCh, 24);	/* 1 Hz */
		sblive_writeptr(sb_hw, TEMPENV, nCh, 0);

		/*** These are last so OFF prevents writing ***/
		sblive_writeptr(sb_hw, LFOVAL2, nCh, 0);
		sblive_writeptr(sb_hw, LFOVAL1, nCh, 0);
		sblive_writeptr(sb_hw, ATKHLDV, nCh, 0);
		sblive_writeptr(sb_hw, ENVVOL, nCh, 0);
		sblive_writeptr(sb_hw, ENVVAL, nCh, 0);
	}


	/*
	   ** Init to 0x02109204 :
	   ** Clock accuracy    = 0     (1000ppm)
	   ** Sample Rate       = 2     (48kHz)
	   ** Audio Channel     = 1     (Left of 2)
	   ** Source Number     = 0     (Unspecified)
	   ** Generation Status = 1     (Original for Cat Code 12)
	   ** Cat Code          = 12    (Digital Signal Mixer)
	   ** Mode              = 0     (Mode 0)
	   ** Emphasis          = 0     (None)
	   ** CP                = 1     (Copyright unasserted)
	   ** AN                = 0     (Digital audio)
	   ** P                 = 0     (Consumer)
	 */
	
	sblive_writeptr(sb_hw, SPCS0, 0, SPCS_CLKACCY_1000PPM | 0x002000000 |
			SPCS_CHANNELNUM_LEFT | SPCS_SOURCENUM_UNSPEC|
			SPCS_GENERATIONSTATUS | 0x00001200 |
			SPCS_EMPHASIS_NONE | SPCS_COPYRIGHT);	/* SPDIF0 */

	sblive_writeptr(sb_hw, SPCS1, 0, SPCS_CLKACCY_1000PPM | 0x002000000 | 
			SPCS_CHANNELNUM_LEFT | SPCS_SOURCENUM_UNSPEC|
			SPCS_GENERATIONSTATUS | 0x00001200 |
			SPCS_EMPHASIS_NONE | SPCS_COPYRIGHT);	/* SPDIF1 */

	sblive_writeptr(sb_hw, SPCS2, 0, SPCS_CLKACCY_1000PPM | 0x002000000 | 
			SPCS_CHANNELNUM_LEFT | SPCS_SOURCENUM_UNSPEC|
			SPCS_GENERATIONSTATUS | 0x00001200 |
			SPCS_EMPHASIS_NONE | SPCS_COPYRIGHT);	/* SPDIF2 & SPDIF3 */

	sblive_fxInit(sb_hw);	/* initialize effects engine */

	size = TMEMSIZE;
	sizeIdx = TMEMSIZEREG;
	while (sizeIdx) 
	{
		/* FIXME (TANKMEM) */
#ifdef TANKMEM
		sb_hw->tmemphysaddr = __get_free_pages(GFP_KERNEL, sizeIdx + 2, 0);	/* DMA flag for ISA bus only */

		if (!sb_hw->tmemphysaddr)
		  status = CTSTATUS_NOMEMORY;
		else 
		{  /* Successfully allocated */
			status = CTSTATUS_SUCCESS;
			sb_hw->tankmem = sb_hw->tmemphysaddr;
			sb_hw->tankmemptr = phys_to_virt(sb_hw->tmemphysaddr);
			sb_hw->tmemsizeIdx = sizeIdx;
		}
#else /* !TANKMEM */
		sb_hw->tmemphysaddr = 0;
		sb_hw->tankmem = 0;
		sb_hw->tankmemptr = NULL;
		sb_hw->tmemsizeIdx = 0;
		size = 0;
		status = CTSTATUS_SUCCESS;
#endif /* TANKMEM */
		if (status == CTSTATUS_SUCCESS)
		  break;
		size /= 2;
		sizeIdx -= 1;
	}

	if (status == CTSTATUS_SUCCESS) 
	{
		u16 count;

		sb_hw->tmemsize = size;

		sb_hw->PTBphysaddr = __get_free_pages(GFP_KERNEL, 4, 0);
		if (!sb_hw->PTBphysaddr)
		  status = CTSTATUS_ERROR;
		else 
		{
#ifdef MEMTEST
			DPD("hwaccess.c: __get_free_pages: [%lx]\n", sb_hw->PTBphysaddr);
#endif
			status = CTSTATUS_SUCCESS;
			sb_hw->virtualpagetable = sb_hw->PTBphysaddr;
			
			/* In cardwo.c, we'll access through virtualpagetableptr to fill in the page table. */
			/* No need to do phys_to_virt to convert virtualpagetableptr to a virtual address. */
			sb_hw->virtualpagetableptr = (void *) sb_hw->PTBphysaddr;
		}

		if (status != CTSTATUS_SUCCESS) 
		{
			DPF("Failed to allocate physical memory (1)\n");
			osFreeMemPhysical(sb_hw->tankmem);

			return status;
		}
			       
		status = osAllocMemPhysical(EMUPAGESIZE, &sb_hw->silentpage, (void **)&sb_hw->silentpageptr, &sb_hw->silentpagephysaddx);
		
		if (status != CTSTATUS_SUCCESS) 
		{
			DPF("Failed to allocate physical memory (2)\n");
			osFreeMemPhysical(sb_hw->tankmem);
			osFreeMemPhysical(sb_hw->virtualpagetable);
			return status;
		} else
		  memset(sb_hw->silentpageptr, 0, EMUPAGESIZE); /* FIXME: Use bzero() */

		/* Init page table */
		/* All the entries are inialized to point to themselves */
		/* so that when illegal memory access occurs, the system */
		/* won't hang. */
		for (count = 0; count < (MAXPAGES + 1024); count++) 
		  sb_hw->virtualpagetableptr[count] = (sb_hw->silentpagephysaddx * 2) | count;

		/* Init page table & tank memory base register */
		/* get_free_pages returns virtual address, we need to convert it */
		/* to physical address and write it to page table base register. */
		/* is this correct? PTB_MASK is 0xfffff000, it may be a 4k alignment... */
		sblive_writeptr(sb_hw, PTB, 0, virt_to_bus((void *) sb_hw->PTBphysaddr));
		sblive_writeptr(sb_hw, TCB, 0, sb_hw->tmemphysaddr);
		sblive_writeptr(sb_hw, TCBS, 0, sizeIdx);
	} else 
	{
		sb_hw->tmemsize = 0;
		return CTSTATUS_ERROR;
	}

	for (nCh = 0; nCh < NUM_G; nCh++) 
	{
		sblive_writeptr(sb_hw, MAPA, nCh, MAP_PTI_MASK | (sb_hw->silentpagephysaddx * 2));
		sblive_writeptr(sb_hw, MAPB, nCh, MAP_PTI_MASK | (sb_hw->silentpagephysaddx * 2));
	}

	/* Hokay, now enable the AUD bit */
	/* Enable Audio = 1 */
	/* Mute Disable Audio = 0 */
	/* Lock Tank Memory = 1 */
	/* Lock Sound Memory = 0 */
	/* Auto Mute = 1 */
	 
	sblive_rmwac97(sb_hw, AC97_MASTERVOLUME, 0x8000, 0x8000);

	sblive_writeac97(sb_hw, AC97_MASTERVOLUME, 0);
	sblive_writeac97(sb_hw, AC97_PCMOUTVOLUME, 0);

	if (sb_hw->dsCardCfg.chiprev < 6)
	  sblive_writefn0(sb_hw, HCFG, HCFG_AUDIOENABLE | HCFG_LOCKTANKCACHE | HCFG_AUTOMUTE);
	else
	  /* With on-chip joystick */
	  sblive_writefn0(sb_hw, HCFG, HCFG_AUDIOENABLE | HCFG_LOCKTANKCACHE | HCFG_AUTOMUTE | HCFG_JOYENABLE);
	/* TOSLink detection */
	sb_hw->has_toslink = 0;
	
	size = sblive_readfn0(sb_hw, HCFG);
	if (size & (HCFG_GPINPUT0 | HCFG_GPINPUT1)) 
	{
		volatile unsigned delay;
		sblive_writefn0(sb_hw, HCFG, size | 0x800);
	
		for (delay = 0; delay < 512; delay++); 	/* FIXME: Use udelay() */

		if (size != (sblive_readfn0(sb_hw, HCFG) & ~0x800)) 
		{
			sb_hw->has_toslink = 1;
			sblive_writefn0(sb_hw, HCFG, size);
		}
	}
	
	return CTSTATUS_SUCCESS;
}

/* FIXME: This belongs in a headerfile */
/** Channel status message lengths **/
u8 gabMsgLenChannel[] =
{
	3,			/* 0x80 note off        */
	3,			/* 0x90 note on         */
	3,			/* 0xA0 key pressure    */
	3,			/* 0xB0 control change  */
	2,			/* 0xC0 program change  */
	2,			/* 0xD0 channel pressure */
	3,			/* 0xE0 pitch bend      */
	1
};

/** System status message lengths **/
u8 gabMsgLenSystem[] =
{
	1,			/* 0xF0 sysex begin     */
	2,			/* 0xF1 midi tcqf       */
	3,			/* 0xF2 song position   */
	2,			/* 0xF3 song select     */
	1,			/* 0xF4 undefined       */
	1,			/* 0xF5 undefined       */
	1,			/* 0xF6 tune request    */
	1,			/* 0xF7 sysex eox       */

	1,			/* 0xF8 timing clock    */
	1,			/* 0xF9 undefined       */
	1,			/* 0xFA start           */
	1,			/* 0xFB continue        */
	1,			/* 0xFC stop            */
	1,			/* 0xFD undefined       */
	1,			/* 0xFE active sensing  */
	1			/* 0xFF system reset    */
};


/****************************************************************************/
/** Function : srToPitch                                                   **/
/**                                                                        **/
/** Input    : sampleRate - sampling rate                                  **/
/**                                                                        **/
/** Return   : pitch value                                                 **/
/**                                                                        **/
/** About    : convert sampling rate to pitch                              **/
/**                                                                        **/
/** Note     : for 8010, sampling rate is at 48kHz, this function should   **/
/**            be changed.                                                 **/
/****************************************************************************/
u32 srToPitch(u32 sampleRate)
{
	int i;
	
	/* FIXME: These tables should be defined in a headerfile */
	static u32 logMagTable[128] = 
	{
		0x00000, 0x02dfc, 0x05b9e, 0x088e6, 0x0b5d6, 0x0e26f, 0x10eb3, 0x13aa2,
		0x1663f, 0x1918a, 0x1bc84, 0x1e72e, 0x2118b, 0x23b9a, 0x2655d, 0x28ed5,
		0x2b803, 0x2e0e8, 0x30985, 0x331db, 0x359eb, 0x381b6, 0x3a93d, 0x3d081,
		0x3f782, 0x41e42, 0x444c1, 0x46b01, 0x49101, 0x4b6c4, 0x4dc49, 0x50191,
		0x5269e, 0x54b6f, 0x57006, 0x59463, 0x5b888, 0x5dc74, 0x60029, 0x623a7,
		0x646ee, 0x66a00, 0x68cdd, 0x6af86, 0x6d1fa, 0x6f43c, 0x7164b, 0x73829,
		0x759d4, 0x77b4f, 0x79c9a, 0x7bdb5, 0x7dea1, 0x7ff5e, 0x81fed, 0x8404e,
		0x86082, 0x88089, 0x8a064, 0x8c014, 0x8df98, 0x8fef1, 0x91e20, 0x93d26,
		0x95c01, 0x97ab4, 0x9993e, 0x9b79f, 0x9d5d9, 0x9f3ec, 0xa11d8, 0xa2f9d,
		0xa4d3c, 0xa6ab5, 0xa8808, 0xaa537, 0xac241, 0xadf26, 0xafbe7, 0xb1885,
		0xb3500, 0xb5157, 0xb6d8c, 0xb899f, 0xba58f, 0xbc15e, 0xbdd0c, 0xbf899,
		0xc1404, 0xc2f50, 0xc4a7b, 0xc6587, 0xc8073, 0xc9b3f, 0xcb5ed, 0xcd07c,
		0xceaec, 0xd053f, 0xd1f73, 0xd398a, 0xd5384, 0xd6d60, 0xd8720, 0xda0c3,
		0xdba4a, 0xdd3b4, 0xded03, 0xe0636, 0xe1f4e, 0xe384a, 0xe512c, 0xe69f3,
		0xe829f, 0xe9b31, 0xeb3a9, 0xecc08, 0xee44c, 0xefc78, 0xf148a, 0xf2c83,
		0xf4463, 0xf5c2a, 0xf73da, 0xf8b71, 0xfa2f0, 0xfba57, 0xfd1a7, 0xfe8df
	};

	static char logSlopeTable[128] = 
	{
		0x5c, 0x5c, 0x5b, 0x5a, 0x5a, 0x59, 0x58, 0x58,
		0x57, 0x56, 0x56, 0x55, 0x55, 0x54, 0x53, 0x53,
		0x52, 0x52, 0x51, 0x51, 0x50, 0x50, 0x4f, 0x4f,
		0x4e, 0x4d, 0x4d, 0x4d, 0x4c, 0x4c, 0x4b, 0x4b,
		0x4a, 0x4a, 0x49, 0x49, 0x48, 0x48, 0x47, 0x47,
		0x47, 0x46, 0x46, 0x45, 0x45, 0x45, 0x44, 0x44,
		0x43, 0x43, 0x43, 0x42, 0x42, 0x42, 0x41, 0x41,
		0x41, 0x40, 0x40, 0x40, 0x3f, 0x3f, 0x3f, 0x3e,
		0x3e, 0x3e, 0x3d, 0x3d, 0x3d, 0x3c, 0x3c, 0x3c,
		0x3b, 0x3b, 0x3b, 0x3b, 0x3a, 0x3a, 0x3a, 0x39,
		0x39, 0x39, 0x39, 0x38, 0x38, 0x38, 0x38, 0x37,
		0x37, 0x37, 0x37, 0x36, 0x36, 0x36, 0x36, 0x35,
		0x35, 0x35, 0x35, 0x34, 0x34, 0x34, 0x34, 0x34,
		0x33, 0x33, 0x33, 0x33, 0x32, 0x32, 0x32, 0x32,
		0x32, 0x31, 0x31, 0x31, 0x31, 0x31, 0x30, 0x30,
		0x30, 0x30, 0x30, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f
	};

	if (sampleRate == 0)
	  return (0);	/* Bail out if no leading "1" */
	
	sampleRate *= 11185;	/* Scale 48000 to 0x20002380 */
	
	for (i = 31; i > 0; i--) 
	{
		if (sampleRate & 0x80000000) 
		{	/* Detect leading "1" */
			return (u32) (((s32) (i - 15) << 20) +
				      logMagTable[0x7f & (sampleRate >> 24)] +
				      (0x7f & (sampleRate >> 17)) *
				      logSlopeTable[0x7f & (sampleRate >> 24)]);
		}
		sampleRate = sampleRate << 1;
	}

	DPF("srToPitch: BUG!\n");
	return 0;		/* Should never reach this point */
}


/****************************************************************************/
/** Function : sumVolumeToAttenuation                                      **/
/**                                                                        **/
/** Input    : inputValue - input volume                                   **/
/**                                                                        **/
/** Return   : attenuation value                                           **/
/**                                                                        **/
/** About    : convert volume to attenuation                               **/
/****************************************************************************/
/* Returns an attenuation based upon a cumulative volume value */
/* Algorithm calculates 0x200 - 0x10 log2 (input) */
u8 sumVolumeToAttenuation(u32 value)
{
	u16 count = 16;
	s16 ans;

	if (value == 0)
	  return 0xFF;

	/* Find first SET bit. This is the integer part of the value */
	while ((value & 0x10000) == 0) 
	{
		value <<= 1;
		count--;
	}

	/* The REST of the data is the fractional part. */
	ans = (s16) (0x110 - ((count << 4) + ((value & 0x0FFFFL) >> 12)));
	if (ans > 0xFF)
	  ans = 0xFF;

	return (u8) ans;
}

/**************************************************************/
/* write/read PCI function 0 registers                        */
/**************************************************************/
void sblive_writefn0(struct sblive_hw *sb_hw, u8 reg, u32 data)
{
        unsigned long flags;

        spin_lock_irqsave(&sb_hw->emu_lock, flags);
        outl(data, sb_hw->hwaddr + reg);
        spin_unlock_irqrestore(&sb_hw->emu_lock, flags);

        return;
}

void sblive_wrtmskfn0(struct sblive_hw *sb_hw, u8 reg, u32 mask, u32 data)
{
        unsigned long flags;

        data &= mask;

        spin_lock_irqsave(&sb_hw->emu_lock, flags);
        data |= inl(sb_hw->hwaddr + reg) & ~mask;
        outl(data, sb_hw->hwaddr + reg);
        spin_unlock_irqrestore(&sb_hw->emu_lock, flags);

        return;
}

u32 sblive_readfn0(struct sblive_hw *sb_hw, u8 reg)
{
	u32 val;
	unsigned long flags;

	spin_lock_irqsave(&sb_hw->emu_lock, flags);
	val = inl(sb_hw->hwaddr + reg);
	spin_unlock_irqrestore(&sb_hw->emu_lock, flags);
	return val;
}

u32 sblive_rdmskfn0(struct sblive_hw *sb_hw, u8 reg, u32 mask)
{
        u32 val;
        unsigned long flags;

        spin_lock_irqsave(&sb_hw->emu_lock, flags);
        val = inl(sb_hw->hwaddr + reg);
        spin_unlock_irqrestore(&sb_hw->emu_lock, flags);
        return val & mask;
}

/****************************************************************************/
/* write/read Emu10k1 pointer-offset register set, accessed through         */
/*  the PTR and DATA registers                                              */
/****************************************************************************/
void sblive_writeptr(struct sblive_hw *sb_hw, u32 reg, u32 channel, u32 data)
{
        u32 regptr;
        unsigned long flags;

        regptr = ((reg << 16) & PTR_ADDRESS_MASK) | (channel & PTR_CHANNELNUM_MASK);

        if (reg & 0xff000000)
	{
		u32 mask;
                u8 size, offset;

                size = (reg >> 24) & 0x3f;
                offset = (reg >> 16) & 0x1f;
                mask = ((1 << size) - 1) << offset;
                data = (data << offset ) & mask;

                spin_lock_irqsave(&sb_hw->emu_lock, flags);
                outl(regptr, sb_hw->hwaddr + PTR);
                data |= inl(sb_hw->hwaddr + DATA) & ~mask;
                outl(data, sb_hw->hwaddr + DATA);
                spin_unlock_irqrestore(&sb_hw->emu_lock, flags);
	} else 
	{
                spin_lock_irqsave(&sb_hw->emu_lock, flags);
                outl(regptr, sb_hw->hwaddr + PTR);
                outl(data, sb_hw->hwaddr + DATA);
                spin_unlock_irqrestore(&sb_hw->emu_lock, flags);
        }

        return;
}

u32 sblive_readptr(struct sblive_hw *sb_hw, u32 reg, u32 channel)
{
        u32 regptr,val;
        unsigned long flags;

        regptr = ((reg << 16) & PTR_ADDRESS_MASK) | (channel & PTR_CHANNELNUM_MASK);

        if (reg & 0xff000000)
	{
	        u32 mask;
                u8 size, offset;

                size = (reg >> 24) & 0x3f;
                offset = (reg >> 16) & 0x1f;
                mask = ((1 << size) - 1) << offset;

                spin_lock_irqsave(&sb_hw->emu_lock, flags);
                outl(regptr, sb_hw->hwaddr + PTR);
                val = inl(sb_hw->hwaddr + DATA);
                spin_unlock_irqrestore(&sb_hw->emu_lock, flags);

                return (val & mask) >> offset;
	} else 
	{
                spin_lock_irqsave(&sb_hw->emu_lock, flags);
                outl(regptr, sb_hw->hwaddr + PTR);
                val = inl(sb_hw->hwaddr + DATA);
                spin_unlock_irqrestore(&sb_hw->emu_lock, flags);

                return val;
        }
}

/*****************************************************************************/
/*  halVoiceIntrEnable (struct sblive_hw *sb_hw, u32 voicenum)               */
/*****************************************************************************/
void halVoiceIntrEnable(struct sblive_hw *sb_hw, u32 voicenum)
{
	/* Voice interrupt */
	if (voicenum >= 32) 
	  sblive_writeptr(sb_hw, CLIEH | ((0x0100 | (voicenum - 32)) << 16), 0, 1);
	else 
	  sblive_writeptr(sb_hw, CLIEL | ((0x0100 | voicenum) << 16), 0, 1);
	
	return;
}


/*****************************************************************************/
/*  halSetStopOnLoop (struct sblive_hw *sb_hw, u32 voicenum)                 */
/*****************************************************************************/
void halSetStopOnLoop(struct sblive_hw *sb_hw, u32 voicenum)
{
	/* Voice interrupt */
	if (voicenum >= 32) 
	  sblive_writeptr(sb_hw, SOLEH | ((0x0100 | (voicenum - 32)) << 16), 0, 1);
	else 
	  sblive_writeptr(sb_hw, SOLEL | ((0x0100 | voicenum) << 16), 0, 1);

	return;	
}


/*****************************************************************************/
/*  halClearStopOnLoop (struct sblive_hw *sb_hw, u32 voicenum )              */
/*****************************************************************************/
void halClearStopOnLoop(struct sblive_hw *sb_hw, u32 voicenum)
{
	/* Voice interrupt */
	if (voicenum >= 32) 
	  sblive_writeptr(sb_hw, SOLEH | ((0x0100 | (voicenum - 32)) << 16), 0, 0);
	else 
	  sblive_writeptr(sb_hw, SOLEL | ((0x0100 | voicenum) << 16), 0, 0);
	
	return;
}


/*****************************************************************************/
/*  halVoiceIntrDisable (struct sblive_hw *sb_hw, u32 voicenum)              */
/*****************************************************************************/
void halVoiceIntrDisable(struct sblive_hw *sb_hw, u32 voicenum)
{
	if (voicenum >= 32) 
	  sblive_writeptr(sb_hw, CLIEH | ((0x0100 | (voicenum - 32)) << 16), 0, 0);
	else 
	  sblive_writeptr(sb_hw, CLIEL | ((0x0100 | voicenum) << 16), 0, 0);
	
	return;
}

/*****************************************************************************/
/*  halWC_WAIT (struct sblive_hw *sb_hw, u32 wait)                           */
/*****************************************************************************/
void halWC_WAIT(struct sblive_hw *sb_hw, u32 wait)
{
	volatile unsigned uCount;
	u32 newtime = 0, curtime;

	curtime = READ_FN0(sb_hw, WC_SAMPLECOUNTER); 
	while (wait--) 
	{
		uCount = 0;
		while (uCount++ < TIMEOUT) 
		{
			newtime = READ_FN0(sb_hw, WC_SAMPLECOUNTER); 
			if (newtime != curtime)
			  break;
		}
		
		if (uCount >= TIMEOUT)
		  break;
		
		curtime = newtime;
	}
}


/*****************************************************************************/
/*  sblive_readac97 (struct sblive_hw *sb_hw, u8 index, u16 *pdata)          */
/*****************************************************************************/
int sblive_readac97(struct sblive_hw *sb_hw, u8 index, u16 *pdata)
{
	unsigned long flags;
	
	spin_lock_irqsave(&sb_hw->emu_lock, flags);
	
	outb(index, sb_hw->mixeraddx + 2);
	*pdata = inw(sb_hw->mixeraddx);
	
	spin_unlock_irqrestore(&sb_hw->emu_lock, flags);

	return CTSTATUS_SUCCESS;
}


/*****************************************************************************/
/*  sblive_writeac97(struct sblive_hw *sb_hw, u8 index, u16 data)            */
/*****************************************************************************/
int sblive_writeac97(struct sblive_hw *sb_hw, u8 index, u16 data)
{
	unsigned long flags;
	
	spin_lock_irqsave(&sb_hw->emu_lock, flags);
	
	outb(index, sb_hw->mixeraddx + 2);
	outw(data, sb_hw->mixeraddx);
	
	spin_unlock_irqrestore(&sb_hw->emu_lock, flags);

	return CTSTATUS_SUCCESS;
}


/*****************************************************************************/
/*  sblive_rmwac97(struct sblive_hw *sb_hw, u8 index, u16 data, u16 mask)    */
/*****************************************************************************/
int sblive_rmwac97(struct sblive_hw *sb_hw, u8 index, u16 data, u16 mask)
{
	u16 temp;
	unsigned long flags;

	spin_lock_irqsave(&sb_hw->emu_lock, flags);
	
	outb(index, sb_hw->mixeraddx + 2);
	temp = inw(sb_hw->mixeraddx);
	temp &= ~mask;
	data &= mask;
	temp |= data;
	outw(temp, sb_hw->mixeraddx);
	
	spin_unlock_irqrestore(&sb_hw->emu_lock, flags);

	return CTSTATUS_SUCCESS;
}

/****************************************************************/
/*            MPU access functions                              */
/****************************************************************/

/*****************************************************************************/
/* Function Name : hwmpuWriteData                                            */
/*                                                                           */
/* Description   : Writing data to MPU with timeout                          */
/*                                                                           */
/* Input         : sb_hw | Port object                                       */
/*                 bData | contain the command                               */
/*                                                                           */
/* Return        : int | contain the status of this call                     */
/*                                                                           */
/*****************************************************************************/
int hwmpuWriteData(struct sblive_hw *sb_hw, u8 data)
{
	unsigned long flags;
	
	spin_lock_irqsave(&sb_hw->emu_lock, flags);
	
	if ((inb(sb_hw->hwaddr + MUSTAT) & MUSTAT_ORDYN) == 0) 
	{
		outb(data,  sb_hw->hwaddr + MUDATA);
		spin_unlock_irqrestore(&sb_hw->emu_lock, flags);
		return CTSTATUS_SUCCESS;
	}

	spin_unlock_irqrestore(&sb_hw->emu_lock, flags);
	return CTSTATUS_BUSY;
}


/*****************************************************************************/
/* Function Name : hwmpuReadData                                             */
/*                                                                           */
/* Description  : Reading data from MPU with timeout                         */
/*                                                                           */
/* Input         : sb_hw | Port object                                       */
/*                 bData | contain the command                               */
/*                                                                           */
/* Return        : int | contain the status of this call                     */
/*                                                                           */
/*****************************************************************************/
int hwmpuReadData(struct sblive_hw *sb_hw, u8 *data)
{
	unsigned long flags;
	
	spin_lock_irqsave(&sb_hw->emu_lock, flags);
	
	if ((inb(sb_hw->hwaddr + MUSTAT) & MUSTAT_IRDYN) == 0) 
	{
		*data = inb(sb_hw->hwaddr + MUDATA);
		spin_unlock_irqrestore(&sb_hw->emu_lock, flags);
		return CTSTATUS_SUCCESS;
	}
	
	spin_unlock_irqrestore(&sb_hw->emu_lock, flags);
	
	return CTSTATUS_NODATA;
}


/*****************************************************************************/
/* Function Name : hwmpuReset                                                */
/*                                                                           */
/* Description  : Hardware reset the MPU                                     */
/*                                                                           */
/* Input         : sb_hw | Port object                                       */
/*                                                                           */
/* Return        : int | contain the status of this call                     */
/*                                                                           */
/*****************************************************************************/
int hwmpuReset(struct sblive_hw *sb_hw)
{
	u8 status;
	unsigned long flags;
	
	DPF("hwmpuReset() called\n");

	if (sb_hw->mpuacqcount == 0) 
	{
		spin_lock_irqsave(&sb_hw->emu_lock, flags);
		outb(MUCMD_RESET, sb_hw->hwaddr + MUCMD);
		spin_unlock_irqrestore(&sb_hw->emu_lock, flags);

		halWC_WAIT(sb_hw, 8);

		spin_lock_irqsave(&sb_hw->emu_lock, flags);
		outb(MUCMD_RESET, sb_hw->hwaddr + MUCMD);
		spin_unlock_irqrestore(&sb_hw->emu_lock, flags);

		halWC_WAIT(sb_hw, 8);

		spin_lock_irqsave(&sb_hw->emu_lock, flags);
		outb(MUCMD_ENTERUARTMODE , sb_hw->hwaddr + MUCMD);
		spin_unlock_irqrestore(&sb_hw->emu_lock, flags);

		halWC_WAIT(sb_hw, 8);

		spin_lock_irqsave(&sb_hw->emu_lock, flags);
		status = inb(sb_hw->hwaddr + MUDATA);
		spin_unlock_irqrestore(&sb_hw->emu_lock, flags);
	
		if (status == 0xfe)
		  return CTSTATUS_SUCCESS;
		else
		  return CTSTATUS_ERROR;
	}
	
	
	return CTSTATUS_SUCCESS;
}


/*****************************************************************************/
/* Function Name : hwmpuAcquire                                              */
/*                                                                           */
/* Description   : Increase MPU acquire count                                */
/*                                                                           */
/* Input         : sb_hw | Port object                                       */
/*                                                                           */
/* Return        : int | contain the status of this call                     */
/*                                                                           */
/*****************************************************************************/
int hwmpuAcquire(struct sblive_hw *sb_hw)
{
	/* FIXME: This should be a macro */
	++sb_hw->mpuacqcount;

	return CTSTATUS_SUCCESS;
}


/*****************************************************************************/
/* Function Name : hwmpuRelease                                              */
/*                                                                           */
/* Description   : Decrease MPU acquire count                                */
/*                                                                           */
/* Input         : sb_hw | Port object                                       */
/*                                                                           */
/* Return        : int | contain the status of this call                     */
/*                                                                           */
/*****************************************************************************/
int hwmpuRelease(struct sblive_hw *sb_hw)
{
	/* FIXME: this should be a macro */
	--sb_hw->mpuacqcount;

	return CTSTATUS_SUCCESS;
}
