/*
 *   DIVAPC ARM C source
 *
 *   CPU.C.CPU - Podule interface routines, Diva/Callas/Elvis hardware
 *
 *  Versions
 *   0.1  13-09-91  INH  Original
 *        18-10-91       Podule finder, hardware initialisation
 *                       interrupt routines added
 *        23-10-91  INH  New version for DivaPC
 *        23-01-92       Suspend/resume changes added
 *        12-02-92       'Reset bug' fixup
 *                       Old ROM handlers finally removed
 *        05-03-92       Wording of warnings rewritten
 *        13-03-92       Uses SYS.h.ROM
 *        04-06-92       Better suspend/restore mechanism
 *        21-12-92       Uses card locks
 *        09-01-93       dly(!) replaced
 *        18-01-93       Hardware dependencies removed, CPUS_CauseInt added
 *        16-02-93       CPUS_IntsAvailable added
 *        07-12-93       Call SVC made easier
 *        11-04-94       Configurable slot number
 *        21-04-94       HardwareAddress/RISCOS 2 SWI fix
 *	  13-12-94	 Dummy A20gate handler
 * 1.77   16-05-95	 Amended to use callbacks / timer
 * 2.00   14-11-95       Now uses HPC
 * 2.12 1997.09.29 W     Trace for IO accesses added
 * 2.15 1997.10.14 RW    CPUs speedups
 */

#include "kernel.h"
#include "swis.h"
#include <stdio.h>

#include "sys.h.stdtypes"
#include "sys.h.sys"
#include "sys.h.hrdstate"
#include "sys.h.config"
#include "sys.h.cpu"
#include "sys.h.rom"
#include "sys.h.transfer"

#include "cpu.h.cpus"
#include "cpu.h.hpc"
#include "cpu.h.cpu"

#include "vid.h.modes"

#include "module.h.pcsupport"

/* Globals **************************************************** */

int  *CPU_pTimerWord;  /* Timer word in support module */

static bool  CPU_ResetInProgress;   /* True if reset in progress */

/* test routines ******************************************************* */
/* this routine allows IO traps to generate trace info
   Called from IOread/write functions of CPUs_g with
   R0= addres of trap
   R1= value written
   R2= access type (read/write 8/16 bit. IO is assumed */
/* mode values taken to match handler struct offsets in CPUs_g */
#define h_read8 0-16
#define h_read16 4-16
#define h_write8 8-16
#define h_write16 12-16

#pragma -s1
void CPU_TraceIO (int addr, int value, int mode)
{
  switch (mode)
  {
    case h_read8:
      SYS_trace ("read8 from IOaddr:%x",addr);
      break;

    case h_write8:
      SYS_trace ("write8 to IOaddr:%x, data:%x",addr,value);
      break;

    case h_read16:
      SYS_trace ("read16 from IOaddr:%x",addr);
      break;

    case h_write16:
      SYS_trace ("write16 to IOaddr:%x, data:%x",addr,value);
      break;
  }
}
#pragma -s0


/* Hardware routines ******************************************************* */

static int CPU_SlotNum;

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

static void delay( int nticks )
{
  int start = SYS_GetTime();
  while ( (SYS_GetTime() - start) < nticks ) ;
}

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

static bool CPU_Reset( void )
{
  int i;

  CPU_ResetInProgress = true;

  CPUS_StartReset (); /* Clear all bits */

  delay (30);

  for (i=0; i<10; i++)
    CPUS_FlushState();

  delay (2);

  CPUS_ReleaseReset ();
  delay (10);

  return false;
}

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

static int Podule_bases[8] =
{
  0x3000000, 0x3004000, 0x3008000, 0x300C000,
  0x3030000, 0x3034000, 0x3038000, 0x303C000
};

static bool CPU_SetConfig(void)
{
  int i;
  _kernel_swi_regs R;
  _kernel_oserror *err;

  CPU_SlotNum = CFG.BusSlot;

  if ( CPU_SlotNum < 0 )  /* -1 => Auto-find card */
  {
    for (i=0; i<9999; i++)
    {
      /* Does this podule exist? */
      R.r[3] = i;
      err = _kernel_swi( Podule_ReadID, &R, &R );

      if ( err == NULL )
      {
        if ( CPUS_TrySlot(i) )
        {
          CPU_SlotNum = i;
          break;
        }
      }
      else if ( err->errnum == 0x500 /* Bad podule no */ )
        break;
    }
  }
  else
    SYS_trace("Assume podule in slot %d - hope you're right",
         CPU_SlotNum );

  if ( CPU_SlotNum < 0 )    /* Still not Found! */
    SYS_error( true, "henotfound" );

  /* Find base address of card */

  R.r[3] = CPU_SlotNum;
  err = _kernel_swi ( Podule_HardwareAddress, &R, &R );

  if ( err != NULL || R.r[3] == CPU_SlotNum /* RISCOS 2 bug fix */ )
  {
    CPUS_PoduleBase = (char *)(Podule_bases[CPU_SlotNum & 7]);
  }
  else
  {
    /* Mystic formula as derived from Nicko & guesswork */
    CPUS_PoduleBase = (char *)((R.r[3] & ~0xFFF)-0x3C0000);
  }

  return false;
}

/* Dummy handlers ----------------------- */

static void DummyA20handler( int x )
{
  NotUsed(x);
}

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

static int Null_Rd ( int addr )
{
  NotUsed(addr);
  return 0xFFFF;
}

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

static void Null_Wr ( int addr, int data )
{
  NotUsed(addr);
  NotUsed(data);
}

/* ROM handlers/services ************************************** */

static BYTE ROMstore [ROM_Length];

/* These are not totally straightforward: the CPU only ever does
   8-bit accesses to the ROMs; however the Byte/Word signal is
   undefined, so we have to handle both eventualities.
*/

static int LastROMAddr = 0;

static int ROM_Rd8 ( int addr )
{
  LastROMAddr = addr;
  return  ROMstore [addr - ROM_First];
}

static int ROM_Rd16 ( int addr )
{
  LastROMAddr = addr;
  return  ROMstore [addr - ROM_First] +
         (ROMstore [addr - ROM_First] << 8);
}

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


static bool ROM_load( char * filename, int offset, int maxsize )
{ int size;
  FILE *infile;

  infile = fopen (filename, "rb");

  if ( infile == NULL )
  {
    SYS_error( true, "feromfile", filename);
    return false;
  }

  size=fread( ROMstore+offset, 1, maxsize, infile );
  fclose(infile);


  if ( size != maxsize )
  {
    SYS_error( true, "feromlen", filename, maxsize, size );
    return false;
  }

  return true;
}

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

static void ROM_Init()
{
  ROM_load("<Diva$Dir>.ROM", 0, ROM_Length);

  SYS_registerMem(ROM_First, ROM_Last,
           ROM_Rd8, ROM_Rd16, Null_Wr, Null_Wr, 0);

}


/* CPU_Run and related routines ************************************* */



static void CPU_TimedRun ( int timeout_cs )
{
  *CPUS_pStopFlag = 0;
  *CPU_pTimerWord = timeout_cs;

  do
  {
    CPUS_Run();
    SYS_DoCallbacks();
    *CPUS_pStopFlag = 0;
  }
    while ( *CPU_pTimerWord > 0 );
}

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

void CPU_StopRun ( void )
{
  *CPU_pTimerWord = 0;
  *CPUS_pStopFlag = 1;
}

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

void CPU_ResetOK ( void ) /* Called to say reset succeeded */
{
  CPU_ResetInProgress = false;
  CPU_StopRun();
}

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

static void EnsureReset( void )
{
  int i;

  for (i=0; i<5; i++)
  {
    CPU_TimedRun(100);
    if ( !CPU_ResetInProgress )
      return;

    SYS_trace ("Hard Reset");
    CPU_Reset();
  }

  SYS_error ( true, "henoreset");
}

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

void CPU_Run( bool suspend )
{
  if ( CPU_ResetInProgress )
    EnsureReset();

  HPC_Unsuspend();

  CPU_TimedRun ( 2 );  /* Run for 10-20ms */

  if ( suspend )
  {
    HPC_Suspend();
    CPU_TimedRun (300); /* 3 second 'suspend' timeout */
    if ( !HPC_Suspended )
      SYS_error ( true, "hecrashed" );
  }

}

/* Dummy shared memory support ************************************ */

bool CPU_AllocSharedMem ( struct SharedMem *pSM )
{
  NotUsed(pSM);
  return false;  /* Can't do it! */
}

/* Dummy DMA support ********************************************** */

static void DummyDMANotify ( uint mode, uint PCaddr, uint len )
{
  NotUsed(mode);
  NotUsed(PCaddr);
  NotUsed(len);
}

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

static uint DummyDMATransfer ( uint mode, uint PCaddr, uint len, BYTE *ptr )
{
  NotUsed(mode);
  NotUsed(PCaddr);
  NotUsed(len);
  NotUsed(ptr);
  return 0;
}

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

static void DummyDMArequest ( int chan )
{
  SYS_trace("Unsupported: DMA req(%d)", chan );
}

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

void DMA_Init(void)
{
  int i;

  SYS_State.DMA_Request = DummyDMArequest;
  SYS_State.DMAsAvailable = 0; /* Can't do DMA on Diva/Callas */

  SYS_State.NullDMAhandler.NotifyFn = DummyDMANotify;
  SYS_State.NullDMAhandler.TransferFn = DummyDMATransfer;
  SYS_State.NullDMAhandler.R12_val = 0;

  for ( i=0; i < SYS_nDMAslots; i++ )
    SYS_State.DMAhandlers[i] = SYS_State.NullDMAhandler;
}

/* Init routines *********************************** */




bool CPU_Init()
{
  _kernel_swi_regs R;

  SYS_registerMem (0xA0000, 0xFFFFF, Null_Rd, Null_Rd, Null_Wr, Null_Wr, 0);
  SYS_registerIO  (0, 0x3FF, Null_Rd, Null_Rd, Null_Wr, Null_Wr, 0);

  SYS_State.NullHandlers.Read8  = Null_Rd;
  SYS_State.NullHandlers.Read16 = Null_Rd;
  SYS_State.NullHandlers.Write8 = Null_Wr;
  SYS_State.NullHandlers.Write16= Null_Wr;

  SYS_State.CPU_Interrupt = CPUS_CauseInt;
  SYS_State.IntsAvailable = CPUS_IntsAvailable();
  SYS_State.SetA20gate    = DummyA20handler;

  CPUS_IOArray  = &(SYS_State.IOhandlers[0]);
  CPUS_MemArray = &(SYS_State.Memhandlers[0]);

  SYS_registerEvent ( SYS_SetConfig, CPU_SetConfig, 0 );
  SYS_registerEvent ( SYS_HardReset, CPU_Reset, 0 );

  HPC_Init();
  ROM_Init();
  DMA_Init();

  /* Set up callbacks / timer */

  R.r[0] = (int) SYS_State.CPU_Interrupt;
  R.r[1] = (int) SYS_State.DMA_Request;

  if ( _kernel_swi ( PCSupport_CallbackSetup, &R, &R ) != NULL )
    return false;
  SYS_InitDoCallbacks();

  CPUS_pStopFlag = (int *) (R.r[0]);
  CPU_pTimerWord = (int *) (R.r[1]);

  return true;
}


