/*

Name:
MDMA.C

Description:
DMA routines

Portability:

MSDOS:	BC(y)	Watcom(y)	DJGPP(y)
Win95:	n
Os2:	n
Linux:	n

(y) - yes
(n) - no (not possible or not useful)
(?) - may be possible, but not tested

*/
#include <dos.h>
#include <malloc.h>
#include <conio.h>
#include "mdma.h"


/* DMA Controler #1 (8-bit controller) */
#define DMA1_STAT       0x08            /* read status register */
#define DMA1_WCMD       0x08            /* write command register */
#define DMA1_WREQ       0x09            /* write request register */
#define DMA1_SNGL       0x0A            /* write single bit register */
#define DMA1_MODE       0x0B            /* write mode register */
#define DMA1_CLRFF      0x0C            /* clear byte ptr flip/flop */
#define DMA1_MCLR       0x0D            /* master clear register */
#define DMA1_CLRM       0x0E            /* clear mask register */
#define DMA1_WRTALL     0x0F            /* write all mask register */

/* DMA Controler #2 (16-bit controller) */
#define DMA2_STAT       0xD0            /* read status register */
#define DMA2_WCMD       0xD0            /* write command register */
#define DMA2_WREQ       0xD2            /* write request register */
#define DMA2_SNGL       0xD4            /* write single bit register */
#define DMA2_MODE       0xD6            /* write mode register */
#define DMA2_CLRFF      0xD8            /* clear byte ptr flip/flop */
#define DMA2_MCLR       0xDA            /* master clear register */
#define DMA2_CLRM       0xDC            /* clear mask register */
#define DMA2_WRTALL     0xDE            /* write all mask register */

#define DMA0_ADDR       0x00            /* chan 0 base adddress */
#define DMA0_CNT        0x01            /* chan 0 base count */
#define DMA1_ADDR       0x02            /* chan 1 base adddress */
#define DMA1_CNT        0x03            /* chan 1 base count */
#define DMA2_ADDR       0x04            /* chan 2 base adddress */
#define DMA2_CNT        0x05            /* chan 2 base count */
#define DMA3_ADDR       0x06            /* chan 3 base adddress */
#define DMA3_CNT        0x07            /* chan 3 base count */
#define DMA4_ADDR       0xC0            /* chan 4 base adddress */
#define DMA4_CNT        0xC2            /* chan 4 base count */
#define DMA5_ADDR       0xC4            /* chan 5 base adddress */
#define DMA5_CNT        0xC6            /* chan 5 base count */
#define DMA6_ADDR       0xC8            /* chan 6 base adddress */
#define DMA6_CNT        0xCA            /* chan 6 base count */
#define DMA7_ADDR       0xCC            /* chan 7 base adddress */
#define DMA7_CNT        0xCE            /* chan 7 base count */

#define DMA0_PAGE       0x87            /* chan 0 page register (refresh)*/
#define DMA1_PAGE       0x83            /* chan 1 page register */
#define DMA2_PAGE       0x81            /* chan 2 page register */
#define DMA3_PAGE       0x82            /* chan 3 page register */
#define DMA4_PAGE       0x8F            /* chan 4 page register (unuseable)*/
#define DMA5_PAGE       0x8B            /* chan 5 page register */
#define DMA6_PAGE       0x89            /* chan 6 page register */
#define DMA7_PAGE       0x8A            /* chan 7 page register */

#define MAX_DMA         8

#define DMA_DECREMENT   0x20    /* mask to make DMA hardware go backwards */

typedef struct {
	UBYTE dma_disable;      /* bits to disable dma channel */
	UBYTE dma_enable;       /* bits to enable dma channel */
	UWORD page;                     /* page port location */
	UWORD addr;                     /* addr port location */
	UWORD count;            /* count port location */
	UWORD single;           /* single mode port location */
	UWORD mode;                     /* mode port location */
	UWORD clear_ff;         /* clear flip-flop port location */
	UBYTE write;            /* bits for write transfer */
	UBYTE read;                     /* bits for read transfer */
} DMA_ENTRY;

/* Variables needed ... */

static DMA_ENTRY mydma[MAX_DMA] = {

/* DMA channel 0 */
			{0x04,0x00,DMA0_PAGE,DMA0_ADDR,DMA0_CNT,
			 DMA1_SNGL,DMA1_MODE,DMA1_CLRFF,0x48,0x44},

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

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

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

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

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

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

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


/*

Each specialised DMA code part should provide the following things:

In MDMA.H:

  -	a DMAMEM typedef, which should contain all the data that the
	routines need for maintaining/allocating/freeing dma memory.


In MDMA.C:

  -	2 macros ENTER_CRITICAL and LEAVE_CRITICAL

  - A function 'static BOOL MDma_AllocMem0(DMAMEM *dm,UWORD size)'
	which should perform the actual dma-memory allocation. It should
	use DMAMEM *dm to store all it's information.

  - A function 'static void MDma_FreeMem0(DMAMEM *dm)' to free the memory

  - A function 'static ULONG MDma_GetLinearPtr(DMAMEM *dm)' which should
	return the linear 20 bits pointer to the actual dmabuffer.. this
	function is	used by MDma_Start

  - A function 'void *MDma_GetPtr(DMAMEM *dm)' which should return a pointer
	to the dmabuffer. If the dma memory can't be accessed directly it should
	return a pointer to a FAKE dma buffer (DJGPP!!)

  - A function 'void MDma_Commit(DMAMEM *dm,UWORD index,UWORD count)'. This
	function will be called each time a routine wrote something to the
	dmabuffer (returned by MDma_GetPtr()). In the case of a FAKE dmabuffer
	this routine should take care of copying the data from the fake buffer to
	the real dma memory ('count' bytes from byteoffset 'index').

*/



#ifdef __WATCOMC__

/****************************************************************************
********************* Watcom C specialised DMA code: ************************
****************************************************************************/

#define ENTER_CRITICAL IRQ_PUSH_OFF()
extern void IRQ_PUSH_OFF (void);
#pragma aux IRQ_PUSH_OFF =      \
	"pushfd",                   \
		"cli"           \
                modify [esp];

#define LEAVE_CRITICAL IRQ_POP()
extern void IRQ_POP (void);
#pragma aux IRQ_POP =   \
		"popfd"         \
                modify [esp];


static BOOL MDma_AllocMem0(DMAMEM *dm,UWORD size)
/*
	Allocates a dma buffer of 'size' bytes.
	returns FALSE if failed.
*/
{
	static union REGS r;
	ULONG p;

	/* allocate TWICE the size of the requested dma buffer..
	this fixes the 'page-crossing' bug of previous versions */

	r.x.eax = 0x0100;           /* DPMI allocate DOS memory */
	r.x.ebx = ((size*2) + 15) >> 4; /* Number of paragraphs requested */

	int386 (0x31, &r, &r);

	if( r.x.cflag ) return 0;	/* failed */

	dm->raw_selector=r.x.edx;

	/* convert the segment into a linear address */

	p=(r.x.eax&0xffff)<<4;

	/* if the first half of the allocated memory crosses a page
	boundary, return the second half which is then guaranteed to
	be page-continuous */

	if( (p>>16) != ((p+size-1)>>16) ) p+=size;

		dm->continuous=(void *)p;

	return 1;
}


static void MDma_FreeMem0(DMAMEM *dm)
{
	static union REGS r;
	r.x.eax = 0x0101;           	/* DPMI free DOS memory */
	r.x.edx = dm->raw_selector;     /* base selector */
	int386 (0x31, &r, &r);
}


static ULONG MDma_GetLinearPtr(DMAMEM *dm)
{
	return (ULONG)dm->continuous;
}


void *MDma_GetPtr(DMAMEM *dm)
{
	return(dm->continuous);
}


void MDma_Commit(DMAMEM *dm,UWORD index,UWORD count)
{
	/* This function doesnt do anything here (WATCOM C
	   can access dma memory directly) */
}


#elif defined(__DJGPP__)
/****************************************************************************
*********************** DJGPP specialised DMA code: *************************
****************************************************************************/
#define ENTER_CRITICAL __asm__( "pushf \n\t cli" )
#define LEAVE_CRITICAL __asm__( "popf \n\t" )
#include <sys/farptr.h>

static BOOL MDma_AllocMem0(DMAMEM *dm,UWORD size)
/*
	Allocates a dma buffer of 'size' bytes - one in the code segment and
		one in the lower 1 Mb physical mem.
	It doesn't check if the dma mem is page-continuous, and can only be
		used to allocate exactly 1 block.
*/
{
	dm->raw.size = (size + 15) >> 4;
	if (_go32_dpmi_allocate_dos_memory(&(dm->raw)))
	  return 0;
	dm->continuous = (void *) malloc(size);
		return 1;
}



static void MDma_FreeMem0(DMAMEM *dm)
{
	_go32_dpmi_free_dos_memory(&(dm->raw));
	free(dm->continuous);
}

static ULONG MDma_GetLinearPtr(DMAMEM *dm)
{
	return (ULONG) dm->raw.rm_segment << 4;
}


void *MDma_GetPtr(DMAMEM *dm)
{
	return(dm->continuous);
}

void MDma_Commit(DMAMEM *dm,UWORD index,UWORD count)
{
   char *src = &(((UBYTE*)dm->continuous)[index]);
   ULONG dest = 16 * dm->raw.rm_segment + (ULONG) index;
   _farsetsel(_go32_conventional_mem_selector());
   while(count--) {
	  _farnspokeb(dest++, *(src++));
   }
}

#else

/****************************************************************************
********************* Borland C specialised DMA code: ***********************
****************************************************************************/

#define ENTER_CRITICAL asm{ pushf; cli }
#define LEAVE_CRITICAL asm{ popf }

#define LPTR(ptr) (((ULONG)FP_SEG(ptr)<<4)+FP_OFF(ptr))
#define NPTR(ptr) MK_FP(FP_SEG(p)+(FP_OFF(p)>>4),FP_OFF(p)&15)


static BOOL MDma_AllocMem0(DMAMEM *dm,UWORD size)
/*
	Allocates a dma buffer of 'size' bytes.
	returns FALSE if failed.
*/
{
	char huge *p;
	ULONG s;

	/* allocate TWICE the size of the requested dma buffer..
	so we can always get a page-contiguous dma buffer */

	if((dm->raw=malloc((ULONG)size*2))==NULL) return 0;

	p=(char huge *)dm->raw;
	s=LPTR(p);

	/* if the first half of the allocated memory crosses a page
	boundary, return the second half which is then guaranteed to
	be page-continuous */

	if( (s>>16) != ((s+size-1)>>16) ) p+=size;

	/* put the page-continuous pointer into DMAMEM */

	dm->continuous=NPTR(p);

	return 1;
}


static void MDma_FreeMem0(DMAMEM *dm)
{
	free(dm->raw);
}


static ULONG MDma_GetLinearPtr(DMAMEM *dm)
{
	return LPTR(dm->continuous);
}


void *MDma_GetPtr(DMAMEM *dm)
{
	return(dm->continuous);
}

#pragma argsused

void MDma_Commit(DMAMEM *dm,UWORD index,UWORD count)
{
	/* This function doesnt do anything here (BORLAND C
	   can access dma memory directly) */
}

#endif


/****************************************************************************
************************* General DMA code: *********************************
****************************************************************************/


DMAMEM *MDma_AllocMem(UWORD size)
{
	DMAMEM *p;

	/* allocate dma memory structure */

	if(!(p=(DMAMEM *)malloc(sizeof(DMAMEM)))) return NULL;

	/* allocate dma memory */

	if(!MDma_AllocMem0(p,size)){

		/* didn't succeed? -> free everything & return NULL */

		free(p);
		return NULL;
	}

	return p;
}


void MDma_FreeMem(DMAMEM *p)
{
	MDma_FreeMem0(p);
	free(p);
}


int MDma_Start(int channel,DMAMEM *dm,UWORD size,int type)
{
	DMA_ENTRY *tdma;
	ULONG s_20bit,e_20bit;
	UWORD spage,saddr,tcount;
	UWORD epage,eaddr;
	UBYTE cur_mode;

	tdma=&mydma[channel];           /* point to this dma data */

	/* Convert the pc address to a 20 bit physical
	   address that the DMA controller needs */

	s_20bit = MDma_GetLinearPtr(dm);

	e_20bit = s_20bit + size - 1;
	spage = s_20bit>>16;
	epage = e_20bit>>16;

	if(spage != epage) return 0;

	if(channel>=4){
		/* if 16-bit xfer, then addr,count & size are divided by 2 */
		s_20bit = s_20bit >> 1;
		e_20bit = e_20bit >> 1;
		size = size >> 1;
	}

	saddr=s_20bit&0xffff;

	tcount = size-1;

	switch (type){

		case READ_DMA:
			cur_mode = tdma->read;
			break;

		case WRITE_DMA:
			cur_mode = tdma->write;
			break;

		case INDEF_READ:
			cur_mode = tdma->read | 0x10;   /* turn on auto init */
			break;

		case INDEF_WRITE:
			cur_mode = tdma->write | 0x10;  /* turn on auto init */
			break;
	}

	ENTER_CRITICAL;
	outportb(tdma->single,tdma->dma_disable);               /* disable channel */
	outportb(tdma->mode,cur_mode);                                  /* set mode */
	outportb(tdma->clear_ff,0);                                             /* clear f/f */
	outportb(tdma->addr,saddr&0xff);                                /* LSB */
	outportb(tdma->addr,saddr>>8);                                  /* MSB */
	outportb(tdma->page,spage);                                             /* page # */
	outportb(tdma->clear_ff,0);                                             /* clear f/f */
	outportb(tdma->count,tcount&0x0ff);                             /* LSB count */
	outportb(tdma->count,tcount>>8);                                /* MSB count */
	outportb(tdma->single,tdma->dma_enable);                /* enable */
	LEAVE_CRITICAL;

	return 1;
}


void MDma_Stop(int channel)
{
	DMA_ENTRY *tdma;
	tdma=&mydma[channel];						/* point to this dma data */
	outportb(tdma->single,tdma->dma_disable);   /* disable chan */
}


UWORD MDma_Todo(int channel)
{
	UWORD creg;
	UWORD val1,val2;

	DMA_ENTRY *tdma=&mydma[channel];

	creg=tdma->count;

	ENTER_CRITICAL;

	outportb(tdma->clear_ff,0xff);

	redo:
	val1=inportb(creg);
	val1|=inportb(creg)<<8;
	val2=inportb(creg);
	val2|=inportb(creg)<<8;

	val1-=val2;
	if((SWORD)val1>64) goto redo;
	if((SWORD)val1<-64) goto redo;

	LEAVE_CRITICAL;

	if(channel>3) val2<<=1;

	return val2;
}
