/***************************************************************************
*       NAME:  UTILLIB.C $Revision: 1.12 $
**      COPYRIGHT:
**      "Copyright (c) 1994,1995 by e-Tek Labs"
**
**       "This software is furnished under a license and may be used,
**       copied, or disclosed only in accordance with the terms of such
**       license and with the inclusion of the above copyright notice.
**       This software or any other copies thereof may not be provided or
**       otherwise made available to any other person. No title to and
**       ownership of the software is hereby transfered."
****************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <dos.h>
#include <io.h>

#include "hardware.h"
#include "stuff.h"
#include "sbosdefs.h"
#include "utillib.h"
#include "dma.h"
#include "error.h"

#define NEXT_OFFSET		0L
#define PREV_OFFSET		4L
#define SIZE_OFFSET		8L
#define MEM_HEADER_SIZE SIZE_OFFSET+4L
#define RESERVED_DRAM   20      // Save lower 20K for data structs

;/* Plug and Play Logical Devices */
#define IWL_PNP_AUDIO  0
#define IWL_PNP_EXT    1
#define IWL_PNP_GAME   2
#define IWL_PNP_ADLIB  3
#define IWL_PNP_MPU401 4

/* Plug and Play Indirect Registers */
#define IW_PCCCI  0x02
#define IW_PWAKEI 0x03
#define IW_PLDNI  0x07
#define IW_PUACTI 0x30
#define IW_PURCI  0x31
#define IW_P2X0HI 0x60
#define IW_P2X0LI 0x61
#define IW_P3X0HI 0x62
#define IW_P3X0LI 0x63
#define IW_PHCAI  0x64
#define IW_PLCAI  0x65
#define IW_PUI1SI 0x70
#define IW_PUI2SI 0x72
#define IW_PUD1SI 0x74
#define IW_PUD2SI 0x75
#define IW_PPWRI  0xF2
#define IW_PSBISI 0x70

extern PORT_STRUC ports;
extern BOARD_CFG *config;

extern unsigned char mixer_mask;
int base_port;
unsigned long free_mem = 0L;

#define MAX_DMA 8

static DMA_ENTRY	dma[MAX_DMA] = {
/* DMA channel 0 */
			{0,0,0x04,0x00,DMA0_PAGE,DMA0_ADDR,DMA0_CNT,
			 DMA1_SNGL,DMA1_MODE,DMA1_CLRFF,0x48,0x44},

/* DMA channel 1 */
			{0,1,0x05,0x01,DMA1_PAGE,DMA1_ADDR,DMA1_CNT,
			 DMA1_SNGL,DMA1_MODE,DMA1_CLRFF,0x49,0x45},

/* DMA channel 2 */
			{0,0,0x06,0x02,DMA2_PAGE,DMA2_ADDR,DMA2_CNT,
			 DMA1_SNGL,DMA1_MODE,DMA1_CLRFF,0x4A,0x46},

/* DMA channel 3 */
			{0,2,0x07,0x03,DMA3_PAGE,DMA3_ADDR,DMA3_CNT,
			 DMA1_SNGL,DMA1_MODE,DMA1_CLRFF,0x4B,0x47},

/* DMA channel 4 */
			{0,0,0x04,0x00,DMA4_PAGE,DMA4_ADDR,DMA4_CNT,
			 DMA2_SNGL,DMA2_MODE,DMA2_CLRFF,0x48,0x44},

/* DMA channel 5 */
			{0,3,0x05,0x01,DMA5_PAGE,DMA5_ADDR,DMA5_CNT,
			 DMA2_SNGL,DMA2_MODE,DMA2_CLRFF,0x49,0x45},

/* DMA channel 6 */
			{0,4,0x06,0x02,DMA6_PAGE,DMA6_ADDR,DMA6_CNT,
			 DMA2_SNGL,DMA2_MODE,DMA2_CLRFF,0x4A,0x46},

/* DMA channel 7 */
			{0,5,0x07,0x03,DMA7_PAGE,DMA7_ADDR,DMA7_CNT,
			 DMA2_SNGL,DMA2_MODE,DMA2_CLRFF,0x4B,0x47},
};

void
PokeString(unsigned long location,void far *data,unsigned int length)
{
	unsigned int port;

    outp(ports.reg_index,SET_DRAM_LOW);
    outpw(ports.reg_data_low,LSW(location));	/* 16 bits */
    outp(ports.reg_index,SET_DRAM_HIGH);
    outp(ports.reg_data_high,LSB(MSW(location)));	/* 8 bits */
    outp(ports.reg_index,IW_LMSBAI);		// Point to 16 bit I/O port
	port = ports.reg_data_low;

    asm push ds
    asm mov dx,port;
    asm mov cx,length;
	asm add cx,1		// in case its odd ...
	asm shr cx,1		// divide by 2 for 16 bit outs
    asm lds si,data
    asm cld
    asm rep outsw 
    asm pop ds
}

void
PeekString(unsigned int port, unsigned long location,
           void far *data,unsigned int length,int rom_flag)
{
unsigned char temp;

	if (rom_flag)
		{
    	outp(ports.reg_index,IW_LMCI);
		temp = inp(ports.reg_data_high);
		temp |= 0x02;
		outp(ports.reg_data_high,temp);
		}

    outp(ports.reg_index,SET_DRAM_LOW);
    outpw(ports.reg_data_low,LSW(location));	/* 16 bits */
    outp(ports.reg_index,SET_DRAM_HIGH);
    outp(ports.reg_data_high,LSB(MSW(location)));	/* 8 bits */

    asm mov dx,port;
    asm mov cx,length;
    asm les di,data
    asm cld
    asm rep insb

	if (rom_flag)
		{
    	outp(ports.reg_index,IW_LMCI);
		temp = inp(ports.reg_data_high);
		temp &= ~0x02;
		outp(ports.reg_data_high,temp);
		}
}

unsigned char
PeekData(unsigned long address)
{
    unsigned char	ret;

    outp(ports.reg_index,SET_DRAM_LOW);
    outpw(ports.reg_data_low,LSW(address));	/* 16 bits */
    outp(ports.reg_index,SET_DRAM_HIGH);
    outp(ports.reg_data_high,LSB(MSW(address)));	/* 8 bits */
    ret = inp(ports.reg_dram_io);

    return (ret);
}

void
PokeData(unsigned long address, unsigned char data)
{
    outp(ports.reg_index,SET_DRAM_LOW);
    outpw(ports.reg_data_low,LSW(address));	/* 16 bits */
    outp(ports.reg_index,SET_DRAM_HIGH);
    outp(ports.reg_data_high,LSB(MSW(address)));	/* 8 bits */
    outp(ports.reg_dram_io,data);
}

void
PokeLong(unsigned long location,unsigned long value)
{
PokeString(location,&value,sizeof(value));
}

unsigned long
PeekLong(unsigned long location)
{
unsigned long value;

PeekString(ports.reg_dram_io,location,&value,sizeof(value),FALSE);
return(value);
}

void
syn_delay(void)
{
int i;

for (i=0;i<10;i++)
	(void)inp(ports.reg_voice_sel);	/* pick a port .... */
}

/***************************************************************
 * This function performs a full reset & initialization on the 
 * Synth
 ***************************************************************/
void
SynthReset(int voices)
{
register int	v;
unsigned int iwl_lmcfi;
int i;

/* Set these to zero so the they don't get summed in for voices that are */
/* not running. If their volumes are not at zero, whatever value they */
/* are pointing at, will get summed into the output. By setting that */
/* location to 0, that voice will have no contribution to the output */
/* (2 locations are done in case voice is set to 16 bits ... ) */
//PokeData(0L,0);
//PokeData(1L,0);

/* Pull a reset on the synth */
	outp(ports.reg_index,MASTER_RESET);
	outp(ports.reg_data_high,0x00);

/* Wait a little while ... */
	syn_delay();
	syn_delay();

/* Release Reset */
	outp(ports.reg_index,MASTER_RESET);
	outp(ports.reg_data_high,SYN_MASTER_RESET);

/* Wait a little while ... */
	syn_delay();
	syn_delay();

/* Reset the MIDI port also */
	outp(ports.reg_midi_control,MIDI_RESET);

	syn_delay();
	syn_delay();

	outp(ports.reg_midi_control,0x00);

/* Clear all interrupts. */
	outp(ports.reg_index,DMA_CONTROL);
	outp(ports.reg_data_high,0x00);
	outp(ports.reg_index,TIMER_CONTROL);
	outp(ports.reg_data_high,0x00);

/* Set the number of active voices */
	outp(ports.reg_index,SET_VOICES);	
	outp(ports.reg_data_high,(char)((voices-1) | 0xC0));

/* Clear interrupts on voices. */
/* Reading the status ports will clear the irqs. */
	(void)inp (ports.reg_irq_status);	
	outp(ports.reg_index,DMA_CONTROL);
	(void)inp (ports.reg_data_high);
	outp(ports.reg_index,SAMPLE_CONTROL);
	(void)inp (ports.reg_data_high);
	outp(ports.reg_index,GET_IRQV);
	(void)inp (ports.reg_data_high);
	
	for (v=0;v<voices;v++)
		{
		/* Select the proper voice */
		outp(ports.reg_voice_sel,v);

		/* Stop the voice and volume */
		outp(ports.reg_index,SET_CONTROL);
		outp(ports.reg_data_high,VOICE_STOPPED|STOP_VOICE);
		outp(ports.reg_index,SET_VOLUME_CONTROL);
		outp(ports.reg_data_high,VOLUME_STOPPED|STOP_VOLUME);
		
		syn_delay(); /* Wait 4.8 micos. or more. */

		/* Initialize each voice specific registers. This is not */
		/* really necessary, but is nice for completeness sake .. */
		/* Each application will set up these to whatever values */
		/* it needs. */
		outp(ports.reg_index,SET_FREQUENCY);
		outpw(ports.reg_data_low,0x0000);
		outp(ports.reg_index,SET_START_HIGH);
		outpw(ports.reg_data_low,0);
		outp(ports.reg_index,SET_START_LOW);
 		outpw(ports.reg_data_low,0);
		outp(ports.reg_index,SET_END_HIGH);
		outpw(ports.reg_data_low,0);
		outp(ports.reg_index,SET_END_LOW);
		outpw(ports.reg_data_low,0);
		outp(ports.reg_index,SET_VOLUME_RATE);
		outp(ports.reg_data_high,0x01);
		outp(ports.reg_index,SET_VOLUME_START);
		outp(ports.reg_data_high,0x10);
		outp(ports.reg_index,SET_VOLUME_END);
		outp(ports.reg_data_high,0xe0);
		outp(ports.reg_index,SET_VOLUME);
		outpw(ports.reg_data_low,0x0000);

		outp(ports.reg_index,SET_ACC_HIGH);
		outpw(ports.reg_data_low,0);
		outp(ports.reg_index,SET_ACC_LOW);
		outpw(ports.reg_data_low,0);
		outp(ports.reg_index,SET_BALANCE);
		outp(ports.reg_data_high,0x07);
		}

	(void)inp (ports.reg_irq_status);	
	outp(ports.reg_index,DMA_CONTROL);
	(void)inp (ports.reg_data_high);
	outp(ports.reg_index,GET_IRQV);
	(void)inp (ports.reg_data_high);
	
	/* Set up synth Chip for interrupts & enable DACs. */
	outp(ports.reg_index,MASTER_RESET);
	outp(ports.reg_data_high,SYN_MASTER_RESET|SYN_OUTPUT_ENABLE|SYN_MASTER_IRQ);

	outp( ports.reg_index, SET_GLOBAL_MODE ); 
	outp( ports.reg_data_high, 0x01 );// Go to enhanced mode 

	outp( ports.reg_index, IW_ICMPTI ); 
	outp( ports.reg_data_high, 0x0f );	// Serial out, compat off (lock out dma/irq)

    /* Set local memory for auto increment */
	outp( ports.reg_index, IW_LMCI); 
	outp( ports.reg_data_high,IWL_LMCI_AI);

// Enable all voices ....
	for (i=0;i<voices;i++)
		{
		outp(ports.reg_voice_sel,i);
		outp(ports.reg_index, SET_VOICE_MODE); 
		outp(ports.reg_data_high,0x00);		// enable the voice ...
		}
}

void
EnableOutput(void)
{
mixer_mask &= ~ENABLE_OUTPUT;
outp(ports.reg_mixer,mixer_mask);
}

void
DisableOutput(void)
{
mixer_mask |= ENABLE_OUTPUT;
outp(ports.reg_mixer,mixer_mask);
}

int SynOpen(BOARD_CFG *config,int voices)
{
DMA_ENTRY *tdma;

tdma  = &dma[config->orig_dma1];		/* point to this dma data */
outp(tdma->single,tdma->dma_disable);		/* disable chan */
tdma  = &dma[config->orig_dma1];		/* point to this dma data */
outp(tdma->single,tdma->dma_disable);		/* disable chan */

tdma  = &dma[config->sb_dma];		/* point to this dma data */
outp(tdma->single,tdma->dma_disable);		/* disable chan */
DisableOutput();

SynthReset(voices);

return(0);
}

void SynClose(void)
{
EnableOutput();
}

unsigned long	
convert_to_16bit(unsigned long address)
// unsigned long address;		/* 20 bit IW dram address */
{
unsigned long	hold_address;

	hold_address = address;

	/* Convert to 16 translated address. */
	address = address >> 1;

	/* Zero out bit 17. */
	address &= 0x0001ffffL;

	/* Reset bits 18 and 19. */
	address |= (hold_address & 0x000c0000L);

	return(address);
}

void Download(void far *data_ptr,unsigned char mode,
              unsigned long dram_loc, unsigned int size, int wait)
{
unsigned char *tptr;
unsigned int i;
int sixteen=0;
unsigned char xorbit=0;

// Lets convert the data in place before using IO to send it down.....

if (mode & 0x40)
	sixteen = TRUE;

if (mode & 0x80)
	xorbit = 0x80;

tptr = data_ptr;
for (i=0;i<size;i++)
	{
	if (!sixteen)
		*tptr ^= xorbit;
	else
		if (i&0x01)		// odd bytes are high bytes ...
			*tptr ^= xorbit;
	tptr++;
	}

PokeString(dram_loc,data_ptr,size); // Away she goes !!!!!
}

void Upload(void far *data_ptr,unsigned long dram_loc, 
             unsigned int size, int wait)
{
PeekString(ports.reg_dram_io,dram_loc,data_ptr,size,FALSE);	// Here she comes !!!!!
}

int
SizeDram(void)
{
unsigned long i;
unsigned long loc;
unsigned char val;
unsigned char save0;
unsigned char save1;

unsigned int base_port;

	/* save first location ... */
	save0 = PeekData(0L);

	/* See if there is first block there.... */
	PokeData(0L,0xaa);
	val = PeekData(0L);
	if (val != 0xaa)
		return(0);

	/* Now zero it out so that I can check for mirroring .. */
	PokeData(0L,0x00);

	val = PeekData(0L);
	for (i=1L;i<1024L;i++)
		{
		/* check for mirroring ... */
		if (val != 0)
			break;
		loc = i<<10;

		/* save location so its a non-destructive sizing */
		save1 = PeekData(loc);

		PokeData(loc,0xaa);
		val = PeekData(loc);
		if (val != 0xaa)
			break;
		val = PeekData(0L);
		PokeData(loc,save1);
		}

/* Now restore location zero ... */
PokeData(0L,save0);

return((int)i);
}

int
Ping(void)
{
unsigned char val0,val1;
unsigned char save_val0;
unsigned char save_val1;

/* save current values ... */
save_val0 = PeekData(0L);
save_val1 = PeekData(1L);

PokeData(0L,0xAA);
PokeData(1L,0x55);
val0 = PeekData(0L);
val1 = PeekData(1L);

/* restore data to old values ... */
PokeData(0L,save_val0);
PokeData(1L,save_val1);

if ((val0 == (unsigned char)0xAA) && (val1 == (unsigned char)0x55))
	return(CARD_FOUND);
else
	return(NO_CARD);
}

int
Probe(void)
{
int val;

/* Pull a reset on the synth */
outp(ports.reg_index,MASTER_RESET);
outp(ports.reg_data_high,0x00);

/* Wait a little while ... */
syn_delay();
syn_delay();

/* Release Reset */
outp(ports.reg_index,MASTER_RESET);
outp(ports.reg_data_high,SYN_MASTER_RESET);

syn_delay();
syn_delay();

val = Ping();
return(val);
}

int 
GetCfg(BOARD_CFG *config)
{
base_port = config->lower_base_port;
return(TRUE);
}

int
DramInit(int size)
{
unsigned long value;

// Build the linked list now.

// reserve some DRAM in case we want to put structs down there ....
free_mem = 20*1024;

PokeLong(20L*1024L+NEXT_OFFSET,0L);
PokeLong(20L*1024L+PREV_OFFSET,0L);
if (size == 512)
	{
	PokeLong(20L*1024L+SIZE_OFFSET,((512L-20L)*1024)-128L);
	}
else
	{
	PokeLong(20L*1024L+SIZE_OFFSET,((1024L-20L)*1024)-128L);
	}

return(0);
}

int
DramAlloc(unsigned long size,void far *location)
{
unsigned long ptr;
unsigned long pool_size;
unsigned long size_left;
unsigned long prev;
unsigned long next;
unsigned long value;

/* Round size up to next 32 byte boundary */
size += 1;
size &= -2L;

ptr = free_mem;

while(ptr != 0L)
	{
	/* See if this buffer will fit */
	pool_size = PeekLong(ptr+SIZE_OFFSET);
	if (pool_size >= size)
		{
		size_left = pool_size - size;
		if (size_left < MEM_HEADER_SIZE)
			{
			next = PeekLong(ptr+NEXT_OFFSET);
			prev = PeekLong(ptr+PREV_OFFSET);

			if (next != 0L)
				PokeLong(next+PREV_OFFSET,prev);

			if (prev != 0L)
				PokeLong(prev+NEXT_OFFSET,next);
			else
				free_mem = next;
			
			*(unsigned long *)location = ptr;
			}
		else
			{
			/* adjust size of this pool. */
			value = pool_size-size;
			PokeLong(ptr+SIZE_OFFSET,value);
			/* calculate new address */
			*(unsigned long *)location = ptr + value;
			}
		return(DRAM_OK);
		}
	ptr = PeekLong(ptr+NEXT_OFFSET);	/* next please */
	}

return(NO_DRAM);
}

