/***************************************************************************
*        NAME:  IWDMA.C $Revision: 1.6 $
**       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."
****************************************************************************
* $Log: iwdma.c $
* Revision 1.6  1995/11/21 17:29:03  sdsmith
* Changes for new Windows init.
* Revision 1.5  1995/10/27 16:19:59  sdsmith
* Changes for Wave compat tests in win95
* Revision 1.4  1995/06/09 14:13:48  sdsmith
* Mike's changes
* Revision 1.3  1995/06/09 05:16:13  mleibow
* Changed all unsigned chars for status to ints
* Revision 1.2  1995/06/09 05:00:13  mleibow
* Error codes were modified by a wrong type cast.
* Revision 1.1  1995/02/23 11:07:04  unknown
* Initial revision
***************************************************************************/

#include <dos.h>
#include "iw.h"
#include "iwl.h"
#include "globals.h"
#include "dma.h"

#if defined(_WINDOWS)
#define SEGINC 8u
#endif

volatile struct iwl_dma_parms iwl_dma_parms[NUM_CHANNELS];
static int iwl_dma_next_buffer(unsigned short);

/*
 * iwl_set_dma_active_state 
 *
 * sets the active state of a DMA channel specified by channel
 * also accounts for the same DMA channel being used for
 * multiple purposes
 *
 * input: channel = IW_DMA_CHAN_1|IW_DMA_CHAN_2|IW_DMA_CHAN_3
 *        state   = 0 = inactive 1 = active
 *
 */
/***************************************************************************

FUNCTION DEFINITION:
iwl_set_dma_active_state - set state of a virtual DMA channel

DESCRIPTION:
iwl_set_dma_active_state sets the state of a virtual DMA channel to the
state specified by the caller.

The virtual DMA channel can be one of:
  IW_DMA_CHAN_1
  IW_DMA_CHAN_2
  IW_DMA_CHAN_3

This routine also sets the state of all virtual channels that have the
same physical DMA channel number.

RETURNS: void
*/
void iwl_set_dma_active_state(
  int channel, /* virtual DMA channel to set */
  int state)   /* 0 = inactive; 1 = active */
{
  int i;

  iwl_dma_parms[channel].dma_active = state;
  for (i=0; i < NUM_CHANNELS; i++)
    if ((i != channel) &&
	(iwl_dma_parms[i].dma_channel == iwl_dma_parms[channel].dma_channel))
      iwl_dma_parms[i].dma_active = state;
}
    

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

FUNCTION DEFINITION:
iwl_set_channel_out - setup the digital audio playback channel

DESCRIPTION:
iwl_set_channel_out is used to set up the virtual DMA channel for digital
audio playback with a physical DMA channel number.  The virtual DMA channel
IW_DMA_CHAN_1 is normally used for playback through the synthesizer and
recording through the codec. The physical DMA channel number that is passed
in is checked to see if the synthesizer supports it.

RETURNS: int  - IW_OK is the physical channel is ok
                 IW_BAD_DMA if not
*/
static int iwl_set_channel_out(
  int channel) /* DMA physical channel number for playback */
{
  if (iw_dma_latch[channel] == 0) return(IW_BAD_DMA);
  os_init_channel(IW_DMA_CHAN_1, channel);
  iwl_dma_parms[IW_DMA_CHAN_1].dma_channel = channel;
  iwl_dma_parms[IW_DMA_CHAN_1].dma_active = 0;
  return(IW_OK);
}

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

FUNCTION DEFINITION:
iwl_set_channel_in - setup the digital audio recording channel

DESCRIPTION:
iwl_set_channel_in is used to set up the virtual DMA channel for digital
audio recording with a physical DMA channel number.  The virtual DMA channel
IW_DMA_CHAN_2 is normally used for playback through the synthesizer and
recording through the codec. The physical DMA channel number that is passed
in is checked to see if the synthesizer supports it.

RETURNS: int  - IW_OK is the physical channel is ok
                 IW_BAD_DMA if not
*/
static int iwl_set_channel_in(
  int channel) /* DMA physical channel number for recording */
{
  if (iw_dma_latch[channel] == 0) return(IW_BAD_DMA);
  os_init_channel(IW_DMA_CHAN_2, channel);
  iwl_dma_parms[IW_DMA_CHAN_2].dma_channel = channel;
  iwl_dma_parms[IW_DMA_CHAN_2].dma_active = 0;
  return(IW_OK);
}

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

FUNCTION DEFINITION:
iwl_set_channel_codec - setup the DMA channel for the daughter card

DESCRIPTION:
iwl_set_channel_codec is used to set up the virtual DMA channel used by 
the codec when the codec is on a daughter card only.  In this case the 
same physical and virtual DMA channel is used for both recording and
playback.

RETURNS: int  - IW_OK
*/
static int iwl_set_channel_codec(
  int channel) /* codec physical DMA channel number */
{
  os_init_channel(IW_DMA_CHAN_3, channel);
  iwl_dma_parms[IW_DMA_CHAN_3].dma_channel = channel;
  iwl_dma_parms[IW_DMA_CHAN_3].dma_active = 0;
  return(IW_OK);
}

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

FUNCTION DEFINITION:
iwl_dma_init - initialize the virtual DMA structures

DESCRIPTION:
Initializes the virtual DMA structures with the record and playback 
channels for the synthesizer.  The codec DMA channel is only needed if
a daughter card is present and in that case is initialized elsewhere.


RETURNS: int - IW_OK if initialization succeeded
               IW_BAD_DMA
	       IW_NO_MORE_HANDLERS
*/
int iwl_dma_init(void)
{
  int status;

  /* Determine the dma channel used for playback. */
  status = iwl_set_channel_out(iwl_channel_out);
  if (status) return(status);
  /* Determine the input dma channel. */
  status = iwl_set_channel_in(iwl_channel_in);
  if (status) return(status);
  if (iw_add_handler(IW_IRQ_HANDLER_DMA, (int (RFAR *)(void))iwl_dma_next_buffer)) {
    return(IW_NO_MORE_HANDLERS);
  }
  return(IW_OK);
}

void iwl_dma_deinit(void)
{
  iw_remove_handler(IW_IRQ_HANDLER_DMA, (int (RFAR *)(void))iwl_dma_next_buffer);
}

/* Read from or Write to DRAM's */
/***************************************************************************

FUNCTION DEFINITION:
dram_xfer - sets up virtual DMA channel for transfer to dram

DESRIPTION:
dram_xfer programs the virtual DMA channel IW_DMA_CHAN_1 with the data
necessary to start the transfer to dram.  Control is then transferred
to start the DMA transfer.

RETURNS: void

*/
static void dram_xfer( 
  unsigned long address_20bit, /* physical address of DMA buffer */
  unsigned long size,          /* size of DMA buffer */
  unsigned long dram_address,  /* address of dram to receive data */
  unsigned char dma_control,   /* controls flags for DMA registers */
  unsigned short flags)        /* DMA transfer direction flags */
{
/* get page and addr */
  if (flags & IW_DMA) {
    iwl_dma_parms[IW_DMA_CHAN_1].current_page = DMA_GET_PAGE(address_20bit);
    iwl_dma_parms[IW_DMA_CHAN_1].current_addr = DMA_GET_ADDR(address_20bit);
    iwl_dma_parms[IW_DMA_CHAN_1].size = size;
    iwl_dma_parms[IW_DMA_CHAN_1].flags = flags;
    iwl_dma_parms[IW_DMA_CHAN_1].amount_to_xfer = 0;
    iwl_dma_parms[IW_DMA_CHAN_1].amount_xferred = 0;
    if (!(dma_control & IW_DMA_WIDTH_16)) {
      if (iwl_dma_parms[IW_DMA_CHAN_1].dma_channel >= 4) { /* 16bits wide. */
	dram_address = iwl_convert_to_16bit( dram_address );
	dma_control |= IW_DMA_WIDTH_16;
      }
    }
    IWL_OUT_W(SET_DMA_ADDR_LOW, (unsigned short)(dram_address>>4));
    IWL_OUT_B(SET_DMA_ADDR_HIGH, (unsigned char)((dram_address>>16)&0xF0));
    dma_control |= IW_DMA_ENABLE|IW_DMA_IRQ_ENABLE;
    iwl_dma_parms[IW_DMA_CHAN_1].dma_control = dma_control;
  }
    
//  dma_control |= IW_DMA_RATE_DIV_1|IW_DMA_RATE_DIV_2;
//  dma_control &= ~(IW_DMA_RATE_DIV_1|IW_DMA_RATE_DIV_2);

  iwl_dma_next_buffer(flags);
}


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

FUNCTION DEFINITION:
iw_dram_xfer - transfer data to DRAM

DESCRIPTION:
iw_dram_xfer initiates transfer of data to DRAM.  This routine first makes
sure the virtual DMA channel IW_DMA_CHAN_1 is free.  

If so, and the DMA 
channel is a 16bit channel, we check if either the dma buffer physical
address or the dram address starts on an odd byte boundary.  If this is 
the case we poke the data to the dram.

If the dram address does not start on a 32 byte boundary, the extra data
is poked until the 32 byte boundary is reached.  The dram address and
dma buffer are reset to the new starting point.

The (remaining) data is then transferred.

RETURNS: int - IW_OK
               IW_DMA_BUSY
*/
int  iw_dram_xfer(
  struct iw_dma_buff RFAR *odptr, /* physical address of DMA buffer */
  unsigned long size,             /* size of DMA buffer */
  unsigned long dram_address,     /* address of dram to receive data */
  unsigned char dma_control,      /* controls flags for DMA registers */
  unsigned short flags)           /* DMA transfer direction flags */
{
  unsigned short extra;
  struct iw_dma_buff dptr;

  ENTER;
  dptr.vptr = odptr->vptr;
  dptr.paddr = odptr->paddr;
  if (iwl_flags & F_IW_NO_DMA) goto pokeit;
  if (flags & IW_DMA) {
    if (!iw_dma_ready(IW_DMA_CHAN_1)) {
      LEAVE;
      return(IW_DMA_BUSY);
    }
    iwl_set_dma_active_state(IW_DMA_CHAN_1, 1);
    if (iwl_dma_parms[IW_DMA_CHAN_1].dma_channel >= 4) {
    /* 16 bit transfer */
      if (FP_OFF(dptr.paddr) & 1 && !(dram_address & 1)) {
pokeit:
	if (size) {
	  iw_poke_block(dptr.vptr, dram_address, size, dma_control);
	}
	iwl_flags |= F_GEN_TC;
	LEAVE;
	return(IW_OK);
      }
    }
    if ((extra=(31 & (unsigned short)dram_address)) != 0) {
      extra = 32 - extra;
      if ((unsigned long)extra > size) extra = (unsigned short)size;
      iw_poke_block(dptr.vptr, dram_address, extra, dma_control);
      dram_address += extra;
      size -= extra;
      dptr.paddr += extra;
#if defined(__WATCOMC__) && defined(__FLAT__) || defined(OS2)
      dptr.vptr += extra;
#elif defined(_WINDOWS)
		{
		unsigned long ofst;
		ofst = (unsigned long)FP_OFF(dptr.vptr) + extra;
		if(ofst>0xfffful)
			dptr.vptr = MK_FP(FP_SEG(dptr.vptr)+SEGINC,(unsigned int)ofst);
		else
			dptr.vptr = MK_FP(FP_SEG(dptr.vptr),(unsigned short)ofst);
		}
#else
       {
	unsigned long addr;
	addr = (unsigned long)FP_SEG(dptr.vptr) * 16 + FP_OFF(dptr.vptr) + extra;
	dptr.vptr = MK_FP((unsigned short)(addr>>4), (unsigned short)(addr&0x0f));
      }
#endif
    }
    if (size < 32) goto pokeit;
  }
  dram_xfer(dptr.paddr,size,dram_address,dma_control,flags);
  LEAVE;
  return(IW_OK);
}

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

FUNCTION DEFINITION:
iwl_dma_next_buffer - transfer data to DRAM

DESCRIPTION:
This routine programs the DMA controller for the IW_DMA_CHAN_1 virtual DMA
channel and starts the transfer to the dram.  If the DMA buffer is larger
than the DMA page size, this routine will limit the transfer to the 
DMA page size.

RETURNS: int - 1 if transfer successfully started
               0 otherwise
*/
static int iwl_dma_next_buffer(
  unsigned short flags) /* DMA transfer direction flags */
{
  unsigned long transfer_size;


  if (flags & IW_DMA) {
    if (iwl_dma_parms[IW_DMA_CHAN_1].dma_active == 0) return(0); /* maybe fake interrupt - I/O */
    iwl_dma_parms[IW_DMA_CHAN_1].amount_xferred += iwl_dma_parms[IW_DMA_CHAN_1].amount_to_xfer;
    if (iwl_dma_parms[IW_DMA_CHAN_1].size == 0) {
      iwl_set_dma_active_state(IW_DMA_CHAN_1, 0);
      return(0);
    }
    transfer_size = DMA_PAGE_SIZE - iwl_dma_parms[IW_DMA_CHAN_1].current_addr;
    if (transfer_size > iwl_dma_parms[IW_DMA_CHAN_1].size) transfer_size = iwl_dma_parms[IW_DMA_CHAN_1].size;
    iwl_dma_parms[IW_DMA_CHAN_1].amount_to_xfer = transfer_size;
    OS_PUSH_DISABLE();
    os_pgm_dma(IW_DMA_CHAN_1, iwl_dma_parms[IW_DMA_CHAN_1].amount_to_xfer,
               (iwl_dma_parms[IW_DMA_CHAN_1].dma_control & 0x02) ?
                   (DMAMODE_SINGLE_MODE|DMAMODE_WRITE) :
                   (DMAMODE_SINGLE_MODE|DMAMODE_READ),
               iwl_dma_parms[IW_DMA_CHAN_1].current_page,
               iwl_dma_parms[IW_DMA_CHAN_1].current_addr);
    OS_OUTPORTB(iwl_register_select, DMA_CONTROL);
    OS_OUTPORTB(iwl_data_high, iwl_dma_parms[IW_DMA_CHAN_1].dma_control);
    iw_delay();
    OS_POP_FLAGS();
    iwl_dma_parms[IW_DMA_CHAN_1].size -= transfer_size;
    iwl_dma_parms[IW_DMA_CHAN_1].current_addr += (unsigned short)transfer_size;
    iwl_dma_parms[IW_DMA_CHAN_1].current_page++;
    return(1);
  }
  return(0);
}

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

FUNCTION DEFINITION:
iwl_set_synth_record_dma - setup DMA control registers for data sampling

DESCRIPTION:
iwl_set_synth_record_dma sets up the virtual DMA channel specified to
perform sampling.  The sampling is performed through the synthesizer and
is therefore 8-bit samples.  In this cases the DMA controller on the PC
is set to AUTOINIT mode to continuously transfer data from the ADC in
the synth to the DMA buffer.

RETURNS: int - IW_OK
               IW_DMA_BUSY
*/
int iwl_set_synth_record_dma(
  int channel,                  /* virtual DMA channel to use for transfer */
  struct iw_dma_buff RFAR *dma, /* PC DMA buffer information */
  UCHAR dma_control)            /* DMA control flags */
{
  ENTER;
  if (!iw_dma_ready(channel)) {
    LEAVE;
    return(IW_DMA_BUSY);
  }
  iwl_set_dma_active_state(channel, 1);
  iwl_dma_parms[channel].current_page = DMA_GET_PAGE(dma->paddr);
  iwl_dma_parms[channel].current_addr = DMA_GET_ADDR(dma->paddr);
  iwl_dma_parms[channel].size = dma->size;
  iwl_dma_parms[channel].amount_to_xfer = dma->size;
  iwl_dma_parms[channel].amount_xferred = 0;
  if (iwl_dma_parms[channel].dma_channel >= 4) { /* 16bits wide. */
    dma_control |= IW_DMA_WIDTH_16;
  }
  dma_control |= IW_DMA_ENABLE;
  iwl_dma_parms[channel].dma_control = dma_control;
  OS_PUSH_DISABLE();
  os_pgm_dma(channel, iwl_dma_parms[channel].size,
             DMAMODE_SINGLE_MODE|DMAMODE_WRITE|DMAMODE_AUTOINIT,
             iwl_dma_parms[channel].current_page,
             iwl_dma_parms[channel].current_addr);
  /* RECORD xfer. begins here. */
  OS_OUTPORTB(iwl_register_select, SAMPLE_CONTROL);
  OS_OUTPORTB(iwl_data_high, iwl_dma_parms[channel].dma_control);
  iw_delay();
  OS_POP_FLAGS();
  LEAVE;
  return(IW_OK);
}

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

FUNCTION DEFINITION:
iwl_dma_wait - wait for a DMA transfer to finish

DESCRIPTION:
iwl_dma_wait disables interrupts and polls the status registers in the
synthesizer to see when a DMA transfer on the virtual channel specified is 
completed.  This routine also uses a timeout to account for a hung DMA 
transfer.

The virutal channel can only be:
IW_DMA_CHAN1 or IW_DMA_CHAN_2

RETURNS: int - IW_OK - if DMA transfer completes successfully
               IW_DMA_HUNG  - if timeout occors
	       IW_BAD_DMA - if caller specifies an incorrect virual channel
*/
int iw_wait_dma(
  int channel) /* virtual DMA channel */
{
  unsigned char iwl_irq_status;

  unsigned long wait=120000L;

  if (channel != IW_DMA_CHAN_3) {
  /* at slow speed of 32K/s  640K will take 20s */
  /* this loop will delay for up to approx 30 seconds */
  OS_PUSH_DISABLE();
  while (iwl_dma_parms[channel].dma_active && --wait) {
    iwl_irq_status = OS_INPORTB(iwl_status_register);
    if (iwl_irq_status & DMA_TC_BIT || iwl_flags & F_GEN_TC) {
      iwl_process_irq1_interrupt();
    }
    iw_delay();
  }
  OS_POP_FLAGS();
  if (iwl_dma_parms[channel].dma_active && !wait) return(IW_DMA_HUNG);
  return(IW_OK);
  }
  else
    return(IW_BAD_DMA);
}

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

FUNCTION DEFINITION:
iwl_dma_ready - checks if a virtual DMA channel is busy

DESCRIPTION:
iwl_dma_ready checks if a virtual DMA channel is busy by returning the 
negation of the virtual DMA channel's active state.

The virtual DMA channel can be one of:
IW_DMA_CHAN_1
IW_DMA_CHAN_2
IW_DMA_CHAN_3

RETURNS: int - 1 if DMA channel is not in use
               0 otherwise

*/
int iw_dma_ready(
  int channel) /* virtual channel being checked */
{
  return (!iwl_dma_parms[channel].dma_active);
}

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

FUNCTION DEFINITION:
iw_stop_dma - shuts down all DMA activity on a virtual DMA channel

DESCRIPTION:
iw_stop_dma stops all activity in the virtual DMA channel specified by 
the caller.  The virtual channel can be one of:
IW_DMA_CHAN_1
IW_DMA_CHAN_2

NOTE: This routine only stops activity between the PC and the synthesizer.
      The codec has its own DMA routines.

RETURNS: void
*/
void iw_stop_dma(
  int channel) /* virtual channel to shutdown DMA activity */
{
  OS_PUSH_DISABLE();
  if (iwl_dma_parms[channel].dma_active) {
    iwl_set_dma_active_state(channel, 0);
    iwl_dma_parms[channel].dma_control &= ~(IW_DMA_ENABLE|IW_DMA_IRQ_ENABLE);
    if (iwl_dma_parms[channel].flags & IW_DMA) {
      os_stop_dma(IW_DMA_CHAN_1);
      OS_OUTPORTB(iwl_register_select, DMA_CONTROL);
      OS_OUTPORTB(iwl_data_high, iwl_dma_parms[channel].dma_control);
    } else {
      os_stop_dma(IW_DMA_CHAN_2);
      OS_OUTPORTB(iwl_register_select, SAMPLE_CONTROL);
      OS_OUTPORTB(iwl_data_high, iwl_dma_parms[channel].dma_control);
    }
  }
  OS_POP_FLAGS();
}

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

FUNCTION DEFINITION:
iwl_dma_count - get the DMA count on a virtual DMA channel

DESCRIPTION:
iwl_dma_count returns the current DMA count on the virtual DMA channel
specified by the caller.  The virtual channel can be one of:
IW_DMA_CHAN_1
IW_DMA_CHAN_2
IW_DMA_CHAN_3

RETURNS: unsigned long - current DMA count
*/
unsigned long iwl_dma_count(
  int channel) /* virtual DMA channel */
{
  unsigned long xfer_left;

  xfer_left = os_dma_count(channel);
  
  return(iwl_dma_parms[channel].size - xfer_left);
}

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

FUNCTION DEFINITION:
iw_amount_xferred - get the number of bytes transferred on a virtual DMA channel

DESCRIPTION:
iw_amount_xferred gets the number of bytes transferred on the virtual DMA 
channel specified by the caller.  The virtual channel can be one of:
IW_DMA_CHAN_1
IW_DMA_CHAN_2
IW_DMA_CHAN_3

RETURNS: unsigned long - bytes transferred
*/
unsigned long iw_amount_xferred(
  int channel) /* virtual DMA channel */
{
	unsigned long xfer_left;

	xfer_left = os_dma_count(channel);
	if (xfer_left) {
	    iwl_dma_parms[channel].amount_xferred += iwl_dma_parms[channel].amount_to_xfer - xfer_left;
	}
	return(iwl_dma_parms[channel].amount_xferred);
}

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

FUNCTION DEFINITION:
iw_dma_buffer_xferred - get number of bytes transferred from DMA buffer

DESCRIPTION:
iw_dma_buffer_xferred returns the number of bytes transferred from or to 
the dma buffer specified by the dma parameter.

INPUT: dma  - a pointer to a iw_dma_buff structure
              indicating the size of the whole dma buffer.
       channel - IW_DMA_CHAN_1|IW_DMA_CHAN_2|CHAN|3

RETURNS: unsigned long - number of bytes transferred
*/
unsigned long iw_dma_buffer_xferred(unsigned long dma_size, int channel)
{
   unsigned long xfer_left, xferred;

   /* Get the current DMA count */
   xfer_left = os_dma_count(channel);
   
   /* Since DMA counts backwards, find the amount transferred
    * in relation to the entire DMA buffer size.
    */
   if (xfer_left == dma_size) xfer_left = 0;
   xferred = dma_size - xfer_left;

   return(xferred);
}


