/*  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 <dos.h>
#include <stdio.h>
#include "hwapi.h"
#include "amd186.h"


/**
 * 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;

  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 &= ~(DCON_CHG | DCON_ST);
  control |= DCON_STOP;

  outpw(channel ? PCB_D1CON : PCB_D0CON,  control);
}


/**
 * Get information about current DMA Transfer.
 *
 * This information might be inaccurate if the transfer is
 * still running. You should _never_ trust the returned
 * src/dest addresses, when using src/dest in/decrement!
 *
 * \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)
{
  unsigned  sh, sl, dh, dl;
  sh = inpw(channel ? PCB_D1SRCH : PCB_D0SRCH) & 0x0f;
  sl = inpw(channel ? PCB_D1SRCL : PCB_D0SRCL);
  dh = inpw(channel ? PCB_D1SRCH : PCB_D0SRCH) & 0x0f;
  dl = inpw(channel ? PCB_D1DSTL : PCB_D0DSTL);

  dma->length  = inpw(channel ? PCB_D1TC  : PCB_D0TC);
  dma->control = inpw(channel ? PCB_D1CON : PCB_D0CON);

  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;
}
