/*  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 "clib/hwapi.h"
#include "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  {
    pushf
    push cx
    push dx
    push di
    push es
    cld

    mov  cx, numBytes
    mov  dx, port
    les  di, dword ptr [dest]
    rep  insb

    pop  es
    pop  di
    pop  dx
    pop  cx
    popf
  }
}


/**
 * 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  {
    pushf
    push cx
    push dx
    push di
    push es
    cld

    mov  cx, numWords
    mov  dx, port
    les  di, dword ptr[dest]
    rep  insw

    pop  es
    pop  di
    pop  dx
    pop  cx
    popf
  }
}


/**
 * Wrapper for REP OUTSB instruction.
 *
 * \param port     i/o port to write to
 * \param src      memory buffer to read from
 * \param numBytes number of bytes to transfer
 */
void REP_OUTSB( int port, void *src, int numBytes )
{
  asm  {
    pushf
    push cx
    push dx
    push di
    push ds
    cld

    mov  cx, numBytes
    mov  dx, port
    lds  si, dword ptr [src]
    rep  outsb

    pop  ds
    pop  di
    pop  dx
    pop  cx
    popf
  }
}


/**
 * Wrapper for REP OUTSW instruction.
 *
 * \param port     i/o port to write to
 * \param src      memory buffer to read from
 * \param numWords number of words to transfer
 */
void REP_OUTSW( int port, void *src, int numWords )
{
  asm  {
    pushf
    push cx
    push dx
    push di
    push ds
    cld

    mov  cx, numWords
    mov  dx, port
    lds  si, dword ptr [src]
    rep  outsw

    pop  ds
    pop  di
    pop  dx
    pop  cx
    popf
  }
}


/**
 * 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
 * \todo  re-calibrate this function (push/pop added)
 */
void usleep(unsigned us)
{
  asm {
      push  cx
      mov   cx, us
    l1:
      nop
      nop
      loop  l1
      pop   cx
  }
}


/**
 * 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 be used without
 *          any external hardware 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 {
    push  ax
    push  bx
    push  cx
    push  dx
    
    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

    pop   dx
    pop   cx
    pop   bx
    pop   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 {
    push  ax
    push  bx
    push  cx
    push  dx

    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

    pop   dx
    pop   cx
    pop   bx
    pop   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 {
    push  ax
    push  bx
    push  cx
    push  dx

    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

    pop   dx
    pop   cx
    pop   bx
    pop   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);
  }
}

