/*         ______   ___    ___ 
 *        /\  _  \ /\_ \  /\_ \ 
 *        \ \ \L\ \\//\ \ \//\ \      __     __   _ __   ___ 
 *         \ \  __ \ \ \ \  \ \ \   /'__`\ /'_ `\/\`'__\/ __`\
 *          \ \ \/\ \ \_\ \_ \_\ \_/\  __//\ \L\ \ \ \//\ \L\ \
 *           \ \_\ \_\/\____\/\____\ \____\ \____ \ \_\\ \____/
 *            \/_/\/_/\/____/\/____/\/____/\/___L\ \/_/ \/___/
 *                                           /\____/
 *                                           \_/__/
 *
 *      DMA routines for the sound code. I used to feel slightly guilty 
 *      because I pinched this code from MikMod, but then I looked at 
 *      the GUS SDK and realised that Jean Paul Mikkers took it from 
 *      there, so I think that justifies me in using it.
 *
 *      By Shawn Hargreaves.
 *
 *      See readme.txt for copyright information.
 */


#include "allegro.h"
#include "allegro/aintern.h"
#include "allegro/aintdos.h"



/* DMA controller #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 controller #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 */

/* stuff for each DMA channel */
#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 */


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



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

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

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

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

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

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

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

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



/* _dma_allocate_mem:
 *  Allocates the specified amount of conventional memory, ensuring that
 *  the returned block doesn't cross a page boundary. Sel will be set to
 *  the protected mode segment that should be used to free the block, and
 *  phys to the linear address of the block. On error, returns non-zero
 *  and sets sel and phys to 0.
 */
int _dma_allocate_mem(int bytes, int *sel, unsigned long *phys)
{
   int seg;

   /* allocate twice as much memory as we really need */
   seg = __dpmi_allocate_dos_memory((bytes*2 + 15) >> 4, sel);

   if (seg < 0) {
      *sel = 0;
      *phys = 0;
      return -1;
   }

   *phys = seg << 4;

   /* if it crosses a page boundary, use the second half of the block */
   if ((*phys>>16) != ((*phys+bytes)>>16))
      *phys += bytes;

   return 0;
}

END_OF_FUNCTION(_dma_allocate_mem);



/* dma_start:
 *  Starts the DMA controller for the specified channel, transferring
 *  size bytes from addr (the block must not cross a page boundary).
 *  If auto_init is set, it will use the endless repeat DMA mode.
 */
void _dma_start(int channel, unsigned long addr, int size, int auto_init, int input)
{
   DMA_ENTRY *tdma;
   unsigned long page, offset;
   int mode;

   tdma = &mydma[channel]; 
   page = addr >> 16;

   if (channel >= 4) {          /* 16 bit data is halved */
      addr >>= 1;
      size >>= 1;
   }

   offset = addr & 0xFFFF;
   size--;

   mode = (input) ? tdma->read : tdma->write;
   if (auto_init)
      mode |= 0x10;

   outportb(tdma->single, tdma->dma_disable);      /* disable channel */
   outportb(tdma->mode, mode);                     /* set mode */
   outportb(tdma->clear_ff, 0);                    /* clear flip-flop */
   outportb(tdma->addr, offset & 0xFF);            /* address LSB */
   outportb(tdma->addr, offset >> 8);              /* address MSB */
   outportb(tdma->page, page);                     /* page number */
   outportb(tdma->clear_ff, 0);                    /* clear flip-flop */
   outportb(tdma->count, size & 0xFF);             /* count LSB */
   outportb(tdma->count, size >> 8);               /* count MSB */
   outportb(tdma->single, tdma->dma_enable);       /* enable channel */
}

END_OF_FUNCTION(_dma_start);



/* dma_stop:
 *  Disables the specified DMA channel.
 */
void _dma_stop(int channel)
{
   DMA_ENTRY *tdma = &mydma[channel];
   outportb(tdma->single, tdma->dma_disable);
}

END_OF_FUNCTION(_dma_stop);



/* dma_todo:
 *  Returns the current position in a dma transfer. Interrupts should be
 *  disabled before calling this function.
 */
unsigned long _dma_todo(int channel)
{
   int val1, val2;
   DMA_ENTRY *tdma = &mydma[channel];

   outportb(tdma->clear_ff, 0xff);

   do {
      val1 = inportb(tdma->count);
      val1 |= inportb(tdma->count) << 8;
      val2 = inportb(tdma->count);
      val2 |= inportb(tdma->count) << 8;

      val1 -= val2;
   } while (val1 > 0x40);

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

   return val2;
}

END_OF_FUNCTION(_dma_todo);



/* _dma_lock_mem:
 *  Locks the memory used by the dma routines.
 */
void _dma_lock_mem()
{
   LOCK_VARIABLE(mydma);
   LOCK_FUNCTION(_dma_allocate_mem);
   LOCK_FUNCTION(_dma_start);
   LOCK_FUNCTION(_dma_stop);
   LOCK_FUNCTION(_dma_todo);
}

