/*
*   DIVAPC ARM C source
*
*  CPU.C.HPC:  HPC Communications
*
*   Versions
*
*   2.00 10-11-95  INH  Original
*/

#include <stdlib.h>  /* For malloc */

#include "Sys.h.StdTypes"
#include "Sys.h.Sys"
#include "Sys.h.HrdState"
#include "Sys.h.FEState"
#include "Sys.h.cpu"
#include "Sys.h.config"
#include "Sys.h.transfer"

#include "Cpu.h.cpu"
#include "Cpu.h.hpc"

/* Port addresses ----------------------- */

#define HPC_PORT_FIRST   0x300
#define HPC_PORT_LAST	 (HPC_PORT_FIRST+7)

#define HPC_Cmd_Port     (HPC_PORT_FIRST+0)
#define HPC_Status_Port  (HPC_PORT_FIRST+0)
#define HPC_Req_Port     (HPC_PORT_FIRST+0x2)
#define HPC_DataA_Port   (HPC_PORT_FIRST+0x4)
#define HPC_DataB_Port   (HPC_PORT_FIRST+0x6)

#define HPC_IRQ_LINE  15

/* Extras defined locally */
#define HPC_DebugB_Port  (HPC_PORT_FIRST+0x1)
#define HPC_DebugW_Port  (HPC_PORT_FIRST+0x0)

/* Commands ------------------------------- */

#define HPC_WRITE_IO    0x00
#define HPC_READ_IO	0x10
#define HPC_WRITE_MEM	0x20
#define HPC_READ_MEM	0x30
#define HPC_DO_CALL	0x40
#define HPC_DO_CALL_IRQ	0x50
#define HPC_IRQ_NEEDED_BIT 0x10
#define HPC_RELEASE_BUF	0x60

#define HPC_BUFSEL_BIT 	   1
#define HPC_BUFFER_A 	   0
#define HPC_BUFFER_B 	   1

#define HPC_RESET_ACK	0x80
#define HPC_SUSPEND_ACK	0x82
#define HPC_MULTI_REQ	0x84
#define HPC_QUIT_REQ	0x86
#define HPC_FREEZE_REQ	0x88

/* Status given by ARM --------------------- */

#define HPCST_SUSPEND	0x80
#define HPCST_REQUEST   0x40
#define HPCST_READY     0x20
#define HPCST_LOCK_B	0x02
#define HPCST_LOCK_A	0x01


#define HPC_BUF_LEN 0x4000

typedef enum
{
  IDLE   = 0,   /* Not in use */
  IN_USE,       /* Command being prepared or results returned */
  CMD_PENDING     /* Command ready to be executed */
}
  BufState_t;

typedef struct
{
  BufState_t state;
  BYTE *data_ptr;   /* Current position for read/write via ports */
  int   data_len;   /* Current number of bytes left */
  int   dataport;   /* Port number */
  bool  irq_needed; /* True if Irq needed on completion */
  BYTE *bufptr;     /* Buffer for holding results */
  int   PCbufaddr;  /* PC physical address of buffer */
}
  HPCBUFFER;

/* Global variables ------------------------ */

bool HPC_Suspended;           /* True if CPU is in a 'suspend' state */
bool HPC_NoSuspendForCmd = false; /* True for Geminis, etc */

static bool HPC_ExtSuspReq;   /* True if suspend (external to HPC suspend)
                                   is requested */
static bool HPC_IntSuspReq;   /* True if suspend due to HPC command is
                                   requested */

static HPCBUFFER HPC_Bufs[2]; /* Buffer A and buffer B */

/* Main HPC callback ************************** */

/* This callback is set when either the PC has just sent us a DO_CALL
   command, or when it has acknowledged a suspend.
*/

static Callback HPC_CB;

static void HPC_callback ( int x )
{
  int i;
  bool completion_irq = false;
  NotUsed(x);

  /* Check commands */

  HPC_IntSuspReq = false; /* Assume not needed until proven otherwise */

  for ( i=0; i<=1; i++ )
  {
    if ( HPC_Bufs[i].state == CMD_PENDING )
    {
      if ( HPC_NoSuspendForCmd || HPC_Suspended )
      {
        SYS_DispatchHPC ( HPC_Bufs[i].bufptr );
        HPC_Bufs[i].state = IN_USE;
        if ( HPC_Bufs[i].irq_needed )
          completion_irq = true;
      }
      else /* Command found, needs suspend */
      {
        HPC_IntSuspReq = true;
        HPC_Bufs[i].irq_needed = true; /* Wake up on completion */
      }
    }
  }

  /* If an external suspend has been acknowledged, stop processing now */
  if ( HPC_ExtSuspReq && HPC_Suspended )
  {
    CPU_StopRun();
    return;
  }

  /* If PC needs to be given a completion interrupt, suspended in order
     to complete a command, or reminded about an external suspend, give
     it an interrupt. The PC will be un-suspended by this irq, and will
     only be considered re-suspended if it comes back with a SUSPEND_ACK
     command later */

  if ( completion_irq || HPC_IntSuspReq || HPC_ExtSuspReq )
  {
    SYS_Interrupt ( HPC_IRQ_LINE );
    HPC_Suspended = false;
  }
}

/* Do_Cmd_Write routine ********************************************
*
*  This handles writes by the PC to HPC_Cmd_Port
*/

static void Do_Cmd_Write( int data )
{
  HPCBUFFER *pB = &HPC_Bufs[data & HPC_BUFSEL_BIT];
  int tmp;

  switch (data & ~HPC_BUFSEL_BIT)
  {
    case HPC_WRITE_IO:
      pB->state = IN_USE;
      pB->data_ptr = pB->bufptr;
      pB->data_len = HPC_BUF_LEN;
      tmp = CPU_BlockOut ( pB->dataport, pB->data_ptr, pB->data_len );
      pB->data_ptr += tmp;
      pB->data_len -= tmp;
      return;

    case HPC_READ_IO:
      pB->state = IN_USE;
      pB->data_ptr = pB->bufptr;
      pB->data_len = HPC_BUF_LEN;
      tmp = CPU_BlockIn ( pB->dataport, pB->data_ptr, pB->data_len );
      pB->data_ptr += tmp;
      pB->data_len -= tmp;
      return;

    case HPC_READ_MEM:
    case HPC_WRITE_MEM:
      pB->state = IN_USE;
      /* Area is uncached, no further action needed */
      return;

    case HPC_DO_CALL:
    case HPC_DO_CALL_IRQ:
      pB->irq_needed = data & HPC_IRQ_NEEDED_BIT;
      pB->state = CMD_PENDING;
      SYS_SetCallback ( &HPC_CB, HPC_callback, 0 );
      return;

    case HPC_RELEASE_BUF:
      pB->state = IDLE;
      pB->data_len = 0;   /* Stop transfers */
      return;

    case HPC_RESET_ACK:
      CPU_ResetOK();
      SYS_Activity(10);  /* Stops stiffing on Ctrl-Alt-Del */
      return;

    case HPC_SUSPEND_ACK:
      HPC_Suspended = true;
      SYS_SetCallback ( &HPC_CB, HPC_callback, 0 );
      return;

    case HPC_FREEZE_REQ:
      SYS_FEState.SuspendRequest = true;
      SYS_FEState.FreezeRequest = true;
      return;

    case HPC_MULTI_REQ:
      SYS_FEState.SuspendRequest = true;
      return;

    case HPC_QUIT_REQ:
      SYS_FEState.SuspendRequest = true;
      SYS_FEState.QuitRequest = true;
      return;

    default:
      break;
  }

  SYS_trace("Do_Cmd_Write, bad data %X", data);
}

/* --------------------------------- */

static int ReadStatusReg(void)
{
  int t=0;

  if (HPC_IntSuspReq || HPC_ExtSuspReq)
    t |= HPCST_SUSPEND;

  if ( HPC_Bufs[HPC_BUFFER_A].state != CMD_PENDING &&
       HPC_Bufs[HPC_BUFFER_B].state != CMD_PENDING
     )
     t |= HPCST_READY;

  if  (HPC_Bufs[HPC_BUFFER_A].state != IDLE)
     t |= HPCST_LOCK_A;

  if  (HPC_Bufs[HPC_BUFFER_B].state != IDLE)
     t |= HPCST_LOCK_B;

  return t;
}

/* I/O Read Callback Handlers ************************************** */

/* Subroutines ---------------- */

static int ByteRead ( HPCBUFFER *pB )
{
  if (pB->data_len >= 1)
  {
    pB->data_len--;
    return *(pB->data_ptr)++;
  }
  return 0;
}

/* ---------------- */

static int WordRead ( HPCBUFFER *pB )
{
  BYTE *p;

  if (pB->data_len >= 2)
  {
    p = pB->data_ptr;
    pB->data_len-=2;
    pB->data_ptr+=2;
    return p[0] + (p[1] << 8);
  }
  return 0;
}

/* ---------------- */

static void ByteWrite ( HPCBUFFER *pB, int data)
{
  if (pB->data_len >= 1)
  {
    *(pB->data_ptr)++ = data;
    pB->data_len--;
  }
}

/* ---------------- */

static void WordWrite ( HPCBUFFER *pB, int data )
{
  if (pB->data_len >= 2)
  {
    *(pB->data_ptr)++ = data;
    *(pB->data_ptr)++ = data >> 8;
    pB->data_len-=2;
  }
}

/* Read byte ====================  */

static int HPC_In8 ( int addr )
{
  switch ( addr )
  {
    case HPC_DataA_Port:
      return ByteRead ( &HPC_Bufs[HPC_BUFFER_A] );
    case HPC_DataB_Port:
      return ByteRead ( &HPC_Bufs[HPC_BUFFER_B] );

    case HPC_Status_Port:
      return ReadStatusReg();

    default:
      break;
  }

  SYS_trace("HPC: byte IN from %Xh", addr);
  return 0xFF;
}


/* Read word ************************** */

static int HPC_In16 ( int addr )
{
  switch ( addr )
  {
    case HPC_DataA_Port:
      return WordRead ( &HPC_Bufs[HPC_BUFFER_A] );

    case HPC_DataB_Port:
      return WordRead ( &HPC_Bufs[HPC_BUFFER_B] );

    case HPC_Req_Port:
      /* HPC request is not yet implemented (INCOMPLETE!!) */
      return 0;

    default:
      break;
  }

  SYS_trace("HPC: word IN from %Xh", addr);
  return 0xFFFF;
}

/* I/O Write Callback handlers *************************************** */

static void HPC_Out8 ( int addr, int data )
{
  switch ( addr )
  {
    case HPC_DataA_Port:
      ByteWrite ( &HPC_Bufs[HPC_BUFFER_A], data );
      return;

    case HPC_DataB_Port:
      ByteWrite ( &HPC_Bufs[HPC_BUFFER_B], data );
      return;

    case HPC_Cmd_Port:
      Do_Cmd_Write(data);
      return;

    case HPC_DebugB_Port:
      if ( data >= 0x20 && data <= 0x7F )
        SYS_trace("DebugB %X '%c'", data, data );
      else
        SYS_trace("DebugB %X", data);
      return;
  }

  SYS_trace("HPC: byte OUT %Xh = %Xh", addr, data);
}

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

static void HPC_Out16 ( int addr, int data )
{
  switch ( addr )
  {
    case HPC_DataA_Port:
      WordWrite ( &HPC_Bufs[HPC_BUFFER_A], data );
      return;

    case HPC_DataB_Port:
      WordWrite ( &HPC_Bufs[HPC_BUFFER_B], data );
      return;

    case HPC_Req_Port: /* Not yet implemented */
      break;

    case HPC_DebugW_Port:
      SYS_trace("DebugW %X", data );
      return;
  }

  SYS_trace("HPC: word OUT %Xh = %Xh", addr, data);
}


/* 'Suspend' routine ************************************** */

void HPC_Suspend (void)
{
  HPC_ExtSuspReq = true;
  SYS_Interrupt ( HPC_IRQ_LINE );
}

/* 'Unsuspend' routine ************************************ */

void HPC_Unsuspend (void)
{
  HPC_ExtSuspReq = false;

  if ( HPC_Suspended )
  {
    SYS_Interrupt ( HPC_IRQ_LINE );
    HPC_Suspended = false; /* Assume it will wake up */
  }
}

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

static bool HPC_Reset()
{
  int i;
  for ( i=0; i<1; i++ )
  {
    HPC_Bufs[i].state = IDLE;
    HPC_Bufs[i].data_len = 0;   /* Stop transfers */
  }

  HPC_IntSuspReq = false;
  HPC_ExtSuspReq = false;
  HPC_Suspended  = false;
  return false;
}

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

static void HPC_Set_Req ( int serv_id )
{
  SYS_trace("HPC request %X", serv_id );
  /* Not currently implemented */
}

/* HPC System calls routine ****************************************** */

struct HPC_SYSINFO_PARAMS
{
  WORD hpc_id;
  WORD reason;
};

struct HPC_SYSINFO_RESULT
{
  LONG identifier;
  BYTE ver_minor;
  BYTE ver_major;
  BYTE pad1;
  BYTE pad2;
  LONG buf_A_physaddr;
  LONG buf_B_physaddr;
};

typedef union
{
  struct HPC_SYSINFO_PARAMS In;
  struct HPC_SYSINFO_RESULT Out;
}
  HPC_SYSINFO_BLK;

/* ------------------------ */

static void HPC_SysFunctions ( BYTE *buf )
{
  HPC_SYSINFO_BLK *pB = (HPC_SYSINFO_BLK *)buf;

  if ( pB->In.reason == 0 ) /* System info cmd */
  {
    pB->Out.identifier = 0xEE435048;
    pB->Out.ver_minor  = 0;
    pB->Out.ver_major  = 1;
    pB->Out.pad1 = pB->Out.pad2 = 0;
    if ( CFG.SharedMemFlags & SM_DONT_USE )
    {
      pB->Out.buf_A_physaddr = 0xFFFFFFFF;
      pB->Out.buf_B_physaddr = 0xFFFFFFFF;
    }
    else
    {
      pB->Out.buf_A_physaddr = HPC_Bufs[HPC_BUFFER_A].PCbufaddr;
      pB->Out.buf_B_physaddr = HPC_Bufs[HPC_BUFFER_B].PCbufaddr;
    }

  }
  else  /* Unknown command */
    pB->Out.identifier = 0xFFFFFFFF;
}

/* HPC_SetConfig ************************************************** */

static bool HPC_SetConfig ( void )
{
  int i;
  struct SharedMem SM;

  /* Get buffers from shared memory, or else malloc it */

  for ( i=HPC_BUFFER_A; i <= HPC_BUFFER_B; i++)
  {
    if ( (CFG.SharedMemFlags & SM_DONT_ALLOC) == 0 )
    {
      SM.length = HPC_BUF_LEN;
      if ( CPU_AllocSharedMem ( &SM ) )
      {
        HPC_Bufs[i].PCbufaddr = SM.PCaddr;
        HPC_Bufs[i].bufptr = SM.ARMaddr;
        continue;
      }
    }

    HPC_Bufs[i].PCbufaddr = 0xFFFFFFFF;
    HPC_Bufs[i].bufptr = (BYTE *) malloc ( HPC_BUF_LEN );
    if ( HPC_Bufs[i].bufptr == NULL )
    {
      SYS_error ( true, "ienomem" );
      return false;
    }
  }

  /* Fill in the rest */

  HPC_Bufs[HPC_BUFFER_A].dataport = HPC_DataA_Port;
  HPC_Bufs[HPC_BUFFER_B].dataport = HPC_DataB_Port;

  SYS_registerIO ( HPC_PORT_FIRST, HPC_PORT_LAST,
              HPC_In8, HPC_In16, HPC_Out8, HPC_Out16, 0 );

  SYS_registerHPC ( 0, HPC_SysFunctions, HPC_NORMAL, 0 );
  return false;
}

/* HPC_Init ******************************************************* */

bool HPC_Init ( void )
{
  HPC_Reset();

  SYS_State.HPC_SetRequest = HPC_Set_Req;

  SYS_registerEvent ( SYS_HardReset, HPC_Reset, 0 );
  SYS_registerEvent ( SYS_SetConfig, HPC_SetConfig, 0 );

  return true;
}

