/* Listing 4 */

/*****************************************************
	DMA.C 

	Generic Routines for programming the DMA controller
	8237, on the IBM PC or compatible

	Copyright Don Bradley, 1991.

	Permission is granted for used of these routines
	in any manner as long as this copyright notice is
	included.

	Tested using Quick C 2.5 and MSC 6.0 on a 
	Toshiba T5200.

 *****************************************************/

#include <conio.h>
#include <malloc.h>
#include <stdio.h>

#include "dma.h"

#define MASK_REG			0xd4
#define MODE_REG			0xd6
#define COUNT_REG			0xc6
#define MEM_PAGE5_REG		0x8b
#define MEM_PAGE6_REG		0x89
#define MEM_PAGE7_REG		0x8a
#define MEM_OFFSET_REG		0xc4

static int far *dma_buffer[10];

int dma(int dma_chan, int mode, int far *buffer, 
		unsigned int buffer_len)
	{
	long physaddr;
	int port, page_port;

	// values passed OK?
	if (dma_chan < 5 || dma_chan > 7)
		return (FALSE);

	if (buffer == 0)
		return (FALSE);

	// Initalize 8237 DMA Controller

	// Disable DMA Channel first
	disable_dma(dma_chan);

	// Setup DMA mode register
	outp(MODE_REG, mode | (dma_chan - 4));

	/* Setup page and offset regs */
	switch (dma_chan) {
	case 5:
		page_port = MEM_PAGE5_REG;
		break;
	case 6:
		page_port = MEM_PAGE6_REG;
		break;
	case 7:
		page_port = MEM_PAGE7_REG;
		break;
		}

	physaddr = (((long)buffer>>12) & 0xFFFF0L) +
		  ((long)buffer & 0xFFFFL);
	
	// output DMA buffer page number.
	outp(page_port, (int) (physaddr >> 16));

	// Shift left 1 bit for working with words(ints)
	physaddr /= 2;

	// Output DMA buffer offset.
	port = MEM_OFFSET_REG + (dma_chan - 5) * 4;
	outp(port, (int)physaddr & 0xFF);
	outp(port, (int)(physaddr>>8) & 0xFF);

	// Transfer Count, low byte first then high byte.
	port = COUNT_REG+(dma_chan-5)*4;
	outp(port, (buffer_len & 0xFF));
	outp(port, ((buffer_len >> 8) & 0xFF));

	return (TRUE);
	}

void disable_dma(int chan)
/*& Disable DMA channel */
	{
	if (chan < 5 || chan > 7)
		return;

	outp(MASK_REG, DMA_DISABLE | (chan - 4));
	}

void enable_dma(int chan)
/*& Enable DMA channel */
	{
	if (chan < 5 || chan > 7)
		return;

	outp(MASK_REG, DMA_ENABLE | (chan - 4));
	}

int far *alloc_dma_buffer(int dma_chn, 
		unsigned int size)
/*& Allocates a DMA buffer containing no page 
	 boundary */
	{
	long physaddr, page, offset, extra;
	int far *buffer;

	// Values passed OK?
	if (dma_chn < 5 || dma_chn > 7)
		return (NULL);

	if(!size)
		return(NULL);

	// Create dma buffer
	size *= sizeof(int);

	if(!(buffer = (int far *)_fmalloc(size)))
		return (NULL);

	physaddr = (((long)buffer & 0xFFFF0000L)>>12) +
		((long)buffer & 0xFFFFL);
	
	page = (physaddr & 0xFFFF0000L) >> 16;
	offset = physaddr & 0xFFFFL;

	if(offset + size > 0xFFFFL) {
		extra = offset + size - 0xFFFFL + 10;
		size += extra;
		
		// can't have a buffer > 64K
		if(size > 0xFFFFL)
			return(NULL);
		
		if(!(buffer = (int far *)_expand(buffer, size)))
			return(NULL);

		page++;
		offset = 0;
		}
	
	// save actual buffer address for deallocation
	dma_buffer[dma_chn] = buffer;

	// return proper buffer address for DMA usage
	buffer = (int far *)((page<<28) + offset);
	return(buffer);	
	}

void free_dma_buffer(int dma_chn)
	{
	// values passed OK?
	if (dma_chn < 5 || dma_chn > 7)
		return;

	_ffree(dma_buffer[dma_chn]);
	}
