/*  amd186.cpp
    Low-level AMD186ED functions

    18.06.2001: tk, assembly implementations for all PIO functions.
    12.04.2000: tk, initial implementation.

    Copyright (c)2000 by Thomas Kindler, thomas.kindler@gmx.de

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License as
    published by the Free Software Foundation; either version 2 of
    the License, or (at your option) any later version. Read the
    full License at http://www.gnu.org/copyleft for more details.
*/

// include files -----
//
#include <iostream>
#include <dos.h>
#include <stdio.h>
#include "hwapi.h"
#include "sc12/amd186.h"


/**
 * Wrapper for REP INSB instruction.
 *
 * \param port i/o port to read from
 * \param dest memory buffer to read to
 * \param numBytes number of bytes to transfer
 */
void REP_INSB( int port, void *dest, int numBytes )
{
  asm  {
    mov  cx, numBytes
    mov  dx, port
    mov  es, word ptr [dest+2]
    mov  di, word ptr [dest]
    cld
    rep  insb
  }
}

/**
 * Wrapper for REP INSW instruction.
 *
 * \param port i/o port to read from
 * \param dest memory buffer to read to
 * \param numWords number of words to transfer
 */
void REP_INSW( int port, void *dest, int numWords )
{
  asm  {
    mov  cx, numWords
    mov  dx, port
    mov  es, word ptr [dest+2]
    mov  di, word ptr [dest]
    cld
    rep  insw
  }
}

/**
 * Wrapper for REP OUTSB instruction.
 *
 * \param port i/o port to write to
 * \param dest memory buffer to read from
 * \param numBytes number of bytes to transfer
 */
void REP_OUTSB( int port, void *src, int numBytes )
{
  asm  {
    mov  cx, numBytes
    mov  dx, port
    mov  ds, word ptr [src+2]
    mov  si, word ptr [src]
    cld
    rep  outsb
  }
}

/**
 * Wrapper for REP OUTSW instruction.
 *
 * \param port i/o port to write to
 * \param dest memory buffer to read from
 * \param numWords number of words to transfer
 */
void REP_OUTSW( int port, void *src, int numWords )
{
  asm  {
    mov  cx, numWords
    mov  dx, port
    mov  ds, word ptr [src+2]
    mov  si, word ptr [src]
    cld
    rep  outsw
  }
}


/**
 * Wait some microseconds. This function has been tested on
 * an IPC@Chip SC12 at 20MHz, and is not very accurate. You
 * can time the actual delay by using the ZTimer functions.
 *
 * \note  for a value of 10000, the function actually
 *        waits 11149 microseconds.
 * \param us  time to wait in microseconds
 */
void usleep(unsigned us)
{
  asm {
      mov   cx, us
    l1:
      nop
      nop
      loop  l1
  }
}


/**
 * Test the mapping between SC12 and AMD PIO pins.
 * This function doesn't need to be called by a user program,
 * and is only useful if there's a new, incompatible revision
 * of the @chip. (as they always promise ;)
 *
 * \warning this function should only works correctly without
 *          any external hardware is connected.
 */
void AMD_TestPios()
{
  for (int i=0; i<=13; i++) {
    pfe_enable_pio(1<<i, PIO_O0);
    unsigned long a = AMD_GetPioDir();
    if (i == 13)
      pfe_enable_pio(1<<i, PIO_IPD);
    else
      pfe_enable_pio(1<<i, PIO_IPU);
    unsigned long b = AMD_GetPioDir();

    int j;
    for (j=0; j<32; j++)
      if ((a^b) & (1<<j)) break;

    printf( "%d -> %d, 0x%08lxL\n", i, j, a^b);
  }
}


/**
 * Set the AMD PIO Data register.
 * Please see the AMD186ED user manual before using this
 * function. Always remember that there is a strange mapping
 * between the SC12 PIO pins and the real AMD ones
 *
 * \param data data bits to write
 * \param mask only bits contained in the mask are touched
 * \sa AMD_PIO
 */
void AMD_SetPioData(unsigned long mask, unsigned long data)
{
  asm {
    mov   dx, PCB_PDATA0
    mov   bx, word ptr data
    mov   cx, word ptr mask
    and   bx, cx
    not   cx
    in    ax, dx
    and   ax, cx
    or    ax, bx
    out   dx, ax

    mov   dx, PCB_PDATA1
    mov   bx, word ptr data+2
    mov   cx, word ptr mask+2
    and   bx, cx
    not   cx
    in    ax, dx
    and   ax, cx
    or    ax, bx
    out   dx, ax
  }
}


/**
 * Set the AMD PIO Direction register.
 * Please see the AMD186ED user manual before using this
 * function. Always remember that there is a strange mapping
 * between the SC12 PIO pins and the real AMD ones 
 *
 * \param dir  direction bits to write
 * \param mask only bits contained in the mask are touched
 * \sa AMD_PIO
 */
void AMD_SetPioDir(unsigned long mask, unsigned long dir)
{
  asm {
    mov   dx, PCB_PDIR0
    mov   bx, word ptr dir
    mov   cx, word ptr mask
    and   bx, cx
    not   cx
    in    ax, dx
    and   ax, cx
    or    ax, bx
    out   dx, ax

    mov   dx, PCB_PDIR1
    mov   bx, word ptr dir+2
    mov   cx, word ptr mask+2
    and   bx, cx
    not   cx
    in    ax, dx
    and   ax, cx
    or    ax, bx
    out   dx, ax
  }
}


/**
 * Set the AMD PIO Mode register.
 * Please see the AMD186ED user manual before using this
 * function. Always remember that there is a strange mapping
 * between the SC12 PIO pins and the real AMD ones
 *
 * \param mode mode bits to write
 * \param mask only bits contained in the mask are touched
 * \sa AMD_PIO
 */
void AMD_SetPioMode(unsigned long mask, unsigned long mode)
{
  asm {
    mov   dx, PCB_PIOMODE0
    mov   bx, word ptr mode
    mov   cx, word ptr mask
    and   bx, cx
    not   cx
    in    ax, dx
    and   ax, cx
    or    ax, bx
    out   dx, ax

    mov   dx, PCB_PIOMODE1
    mov   bx, word ptr mode+2
    mov   cx, word ptr mask+2
    and   bx, cx
    not   cx
    in    ax, dx
    and   ax, cx
    or    ax, bx
    out   dx, ax
  }
}


/**
 * Set PCS0-3 Chipselect Waitstates.
 * Note that you cannot modify the PCS5 and 6 waitstates. The
 * SC12 hardware manual says that they must remain set to 3
 * waitstates.
 *
 * \param waitstates number of waitstates, the value is rounded
 *        up, if there's no exact match. Possible values are
 *        0, 1, 2, 3, 5, 7, 9 and 15
 */
void AMD_SetPcsWaitStates(int waitstates)
{
  const int pacsBits[] = {
     0,  1,  2,  3,  8,  8,  9,  9,
    10, 10, 11, 11, 11, 11, 11, 11
  };

  if (waitstates > 15)  waitstates = 15;
  if (waitstates <  0)  waitstates =  0;

  outpw( PCB_PACS, (inpw(PCB_PACS) & 0xfff4) | pacsBits[waitstates] );
}


/**
 * Initiate a DMA Transfer. Please read chapter 9 of the AMD 186ED
 * user manual before using this function (the DMAInfo structure should
 * be quite self-explanatory then)
 *
 * \param channel DMA channel, either 0 or 1
 * \param dma filled DMAInfo structure
 */
void  AMD_StartDma(int channel, DmaInfo *dma)
{
  unsigned long src, dst;
  if (dma->control & DCON_SMIO)
    src = FP_SEG(dma->srcMem)*16L + FP_OFF(dma->srcMem);
  else
    src = dma->srcPort;

  if (dma->control & DCON_DMIO)
    dst = FP_SEG(dma->dstMem)*16L + FP_OFF(dma->dstMem);
  else
    dst = dma->dstPort;

  // stop previous transfer first
  //
  outpw(channel ? PCB_D1CON  : PCB_D0CON,  DCON_STOP);

  outpw(channel ? PCB_D1SRCL : PCB_D0SRCL, (unsigned)src);
  outpw(channel ? PCB_D1SRCH : PCB_D0SRCH, (unsigned)(src>>16));
  outpw(channel ? PCB_D1DSTL : PCB_D0DSTL, (unsigned)dst);
  outpw(channel ? PCB_D1DSTH : PCB_D0DSTH, (unsigned)(dst>>16));
  outpw(channel ? PCB_D1TC   : PCB_D0TC,   dma->length);
  outpw(channel ? PCB_D1CON  : PCB_D0CON,  dma->control | DCON_START);
}


/**
 * Immediately Stop a DMA Transfer.
 *
 * \param channel DMA channel to stop, either 0 or 1
 */
void AMD_StopDma(int channel)
{
  unsigned control;

  control = inpw(channel ? PCB_D1CON : PCB_D0CON);
  control = (control & ~DCON_START) | DCON_STOP;
  outpw(channel ? PCB_D1CON : PCB_D0CON,  control);
}


/**
 * Get information about current DMA Transfer.
 *
 * \note: in order to read the DMA status reliably, a running
 *   transfer will be interrupted while the registers are read.
 *   This might be an issue, if you do something very timing
 *   critical!
 *
 * \param channel DMA channel, either 0 or 1
 * \param dma pointer to DMAInfo structure. May not be null.
 */
void AMD_GetDmaInfo(int channel, DmaInfo *dma)
{
  // Stop DMA channel first..
  //
  dma->control = inpw(channel ? PCB_D1CON : PCB_D0CON);
  outpw(channel ? PCB_D1CON : PCB_D0CON,
        (dma->control & ~DCON_START) | DCON_STOP);

  unsigned  sh, sl, dh, dl;
  sh = inpw(channel ? PCB_D1SRCH : PCB_D0SRCH);
  sl = inpw(channel ? PCB_D1SRCL : PCB_D0SRCL);
  dh = inpw(channel ? PCB_D1SRCH : PCB_D0SRCH);
  dl = inpw(channel ? PCB_D1DSTL : PCB_D0DSTL);
  dma->length  = inpw(channel ? PCB_D1TC  : PCB_D0TC);

  if (dma->control & DCON_SMIO)
    dma->srcMem = MK_FP(sh << 12, sl);
  else
    dma->srcPort = sl;

  if (dma->control & DCON_DMIO)
    dma->dstMem = MK_FP(dh << 12, dl);
  else
    dma->dstPort = dl;

  // Restart DMA channel
  //
  if (dma->control & DCON_ST)
    outpw(channel ? PCB_D1CON : PCB_D0CON, dma->control | DCON_START);
}


/**
 * Enable/Disable DMA request pin.
 * This function switches the DRQ pin between normal
 * operation and PIO input with pullup.
 *
 * \param channel DMA channel, either 0 or 1
 * \param onoff   enable/disable DRQ
 */
void AMD_EnableDrq(int channel, int onoff)
{
  int pio = channel ? 13 : 12;
  if (onoff) {
    // mode 0, dir 0 - normal operation
    AMD_SetPioMode(1<<pio, 0);
    AMD_SetPioDir (1<<pio, 0);
  } else {
    // mode 0, dir 1 - input with pullup
    AMD_SetPioDir (1<<pio, 1<<pio);
    AMD_SetPioMode(1<<pio, 0);
  }
}
