/*
*    DivaPC ARM C source
*
*    DEV.C.KBD - Keyboard handling routines
*
*    24-12-91 INH Original
*                 Keyboard LEDs added
*                 Repeats added
*                 FE switching added
*    06-02-92     AT-type keboard controller added
*    16-02-92     New FE logic
*    19-03-92     Uses module
*    20-03-92     Break logic changed
*    13-04-92     Repeat is now timed
*    07-05-92     Keyboard controller emulation
*    01-07-92     Pause/Break key
*    15-07-92     Pound sign
*    04-01-93     Tracing for Focus Bug (removed)
*    24-01-93     Tracing for Kbd ID removed
*    05-07-94     \| key on RiscPC fixed
*    05-07-95     Overhaul for DOS 6.3 Keyb UK
*  1997.08.05     Add win95 keyboard codes
*/


#include "kernel.h"
#include "swis.h"

#include "sys.h.stdtypes"
#include "sys.h.sys"
#include "sys.h.hrdstate"  /* For A20gate function */
#include "sys.h.FEstate"
#include "sys.h.config"

#include "module.h.pcsupport"

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

static int  KBD_LED_state;
static bool KBD_EnableLEDChange;

static bool AltKeyDown;
static bool CtrlKeyDown;

static BYTE KBD_PCscanbuf[0x40];
            /* Buffer for PC scan codes *********** */

static int KBD_PCInPtr, KBD_PCOutPtr;
            /* Indexes for inserting and removing codes */

static int repeat_rawkey;
static int repeat_nexttime;

/* Module interface routines ***************************************** */

static void KBDM_Install( void )
{
  _kernel_swi_regs regs;

  _kernel_swi ( PCSupport_KeyInstall, &regs, &regs );

}

static void KBDM_Remove( void )
{
  _kernel_swi_regs regs;
  _kernel_swi ( PCSupport_KeyRelease, &regs, &regs );

}


static bool KBDM_CodeWaiting( void )
{
  _kernel_swi_regs regs;

  _kernel_swi ( PCSupport_KeyCount, &regs, &regs );

  return (regs.r[0] != 0);
}

static int  KBDM_GetCode( void )
{
  _kernel_swi_regs regs;

  _kernel_swi ( PCSupport_KeyGet, &regs, &regs );

  return regs.r[0];
}

static void KBDM_FlushBuffer( void )
{
  _kernel_swi_regs regs;

  _kernel_swi ( PCSupport_KeyFlush, &regs, &regs );

}


static void KBDM_UpdateLEDs ( void )
{
  _kernel_swi_regs regs;

  regs.r[0] = KBD_LED_state;

  _kernel_swi ( PCSupport_KeySetLEDs, &regs, &regs );
}

/* PC Scancode buffer routines *************************************** */

static bool KBD_PCCodeWaiting()
{
  return (KBD_PCInPtr != KBD_PCOutPtr);
}

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


static int KBD_GetPCCode()
{
  int tmp = KBD_PCscanbuf[KBD_PCOutPtr++];
  KBD_PCOutPtr &= 0x3F;
  return tmp;
}

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

static void KBD_InsertPCScanCode( int code )
{
   KBD_PCscanbuf[KBD_PCInPtr++] = code;
   KBD_PCInPtr &= 0x3F;
}


/* Raw key code routines ***************************************** */


#define LAST_RAW_KEY 0x6A

static int KBD_translate[] =
     /* Translation of ARM raw keys into PC scan codes
       6-12-93: Added raw 0x4D->PC 0x2B mapping
        5-06-93: Changed raw 0x4D->0x56 
     1997.08.05: Added win95 keys 0x68-0x6A */
{

0x01,     0x3B,     0x3C,     0x3D,     0x3E,     0x3F,     0x40,     0x41,
0x42,     0x43,     0x44,     0x57,     0x58,   0x37E02AE0, 0x46,   0x451DE1,

0x29,     0x02,     0x03,     0x04,     0x05,     0x06,     0x07,     0x08,
0x09,     0x0A,     0x0B,     0x0C,     0x0D,   0x4D4C4F38, 0x0E,     0x52E0,

0x47E0,   0x49E0,   0x45,     0x35E0,   0x37,     0x0,      0x0F,     0x10,
0x11,     0x12,     0x13,     0x14,     0x15,     0x16,     0x17,     0x18,

0x19,     0x1A,     0x1B,     0x2B,     0x53E0,   0x4FE0,   0x51E0,   0x47,
0x48,     0x49,     0x4A,     0x1D,     0x1E,     0x1F,     0x20,     0x21,

0x22,     0x23,     0x24,     0x25,     0x26,     0x27,     0x28,     0x1C,
0x4B,     0x4C,     0x4D,     0x4E,     0x2A,     0x56,     0x2C,     0x2D,

0x2E,     0x2F,     0x30,     0x31,     0x32,     0x33,     0x34,     0x35,
0x36,     0x48E0,   0x4F,     0x50,     0x51,     0x3A,     0x38,     0x39,

0x38E0,   0x1DE0,   0x4BE0,   0x50E0,   0x4DE0,   0x52,     0x53,     0x1CE0,
0x5BE0,   0x5CE0,   0x5DE0

};


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

static void KBD_SetShiftsUp()
{
    KBD_InsertPCScanCode(0x80 | 0x2A);  /* L shift released */
    KBD_InsertPCScanCode(0x80 | 0x36);  /* R shift released */
    KBD_InsertPCScanCode(0x80 | 0x1D);  /* Ctrl released */
    KBD_InsertPCScanCode(0x80 | 0x38);  /* Alt released */

}

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

static void KBD_InsertCode ( unsigned int code, bool keyup )
{
  while (code != 0)
  {
    KBD_InsertPCScanCode ( (code & 0xFF) | (keyup ? 0x80:0) );
    code >>= 8;
  }

}

/* LEDs bodge *************************** */

/* For some reason, RISCOS does not get round to toggling the state
   of the Caps/Num/Scroll lock keys for a long time after the key
   itself has been pressed; this allows the PC to do it first,
   resulting in confusion. Thus the LEDs bodge routine, which re-sets
   the LED state when the caps/num/scroll lock keys are released.

   27/10/92 - Added EnableLEDChange variable, to stop LED state being
   changed at all by us when a scroll/num/caps lock key is currently
   pressed.
*/


static void KBD_bodgeLEDs ( int rawkey, bool keyup )
{
  if ( rawkey == 0x0E || rawkey == 0x22 || rawkey == 0x5D )
       /* Scroll lock      Num lock          Caps lock */
  {
    if ( keyup )
    {
      KBD_EnableLEDChange = true;
      KBDM_UpdateLEDs ();
    }
    else
      KBD_EnableLEDChange = false;
  }
}


/* CheckRawKeycodes & repeat routines **************************** */


/* Repeat definitions ************************** */

#define KBD_RepeatDelay    70
#define KBD_RepeatPeriod    4

/* All keys repeat apart from shift keys, escape, insert, function keys,
     print, scroll-lock, break */

static char isrepeat[] =

  {
      0, 0, 0, 0,   0, 0, 0, 0,   0, 0, 0, 0,   0, 0, 0, 0,
      1, 1, 1, 1,   1, 1, 1, 1,   1, 1, 1, 1,   1, 0, 1, 0,
      1, 1, 0, 1,   1, 1, 1, 1,   1, 1, 1, 1,   1, 1, 1, 1,
      1, 1, 1, 1,   1, 1, 1, 1,   1, 1, 1, 0,   1, 1, 1, 1,
      1, 1, 1, 1,   1, 1, 1, 1,   1, 1, 1, 1,   0, 0, 1, 1,
      1, 1, 1, 1,   1, 1, 1, 1,   0, 1, 1, 1,   1, 0, 0, 1,
      0, 0, 1, 1,   1, 1, 1, 1
  };



static void KBD_CheckRawKeycodes()
{
  int rawkey;
  bool keyup;

  /* Check if any new key data is present */

  if ( KBDM_CodeWaiting() )
  {
    rawkey = KBDM_GetCode(); /* bit 8 set for key down */
    keyup  = ((rawkey & 0x100) == 0);
    rawkey &= 0xFF;
    if ( rawkey > LAST_RAW_KEY )
      return;

    if ( !keyup && isrepeat[rawkey] )
    {
      repeat_nexttime = SYS_GetTime() + KBD_RepeatDelay;
      repeat_rawkey = rawkey;
    }
    else
      repeat_rawkey = -1;

  }

  /* Check to see if key is repeating */

  else if (  repeat_rawkey >= 0  &&
            (SYS_GetTime() - repeat_nexttime) > 0
          )
  {
    keyup  = false;
    rawkey = repeat_rawkey;
    repeat_nexttime = repeat_nexttime + KBD_RepeatPeriod;
  }
  else
    return;

  /* Process raw key -------------- */

  KBD_bodgeLEDs(rawkey, keyup);

  if (rawkey == 0x5E || rawkey == 0x60 )  /* Alt Key */
    AltKeyDown =  !keyup;

  if (rawkey == 0x3B || rawkey == 0x61 )  /* Ctrl Key */
    CtrlKeyDown = !keyup;

  if (rawkey == 0xF)                      /* Break key */
  {
    if ( AltKeyDown )                 /* Alt-break = suspend */
    {
      if ( !keyup  &&
           (CFG.SuspendOption & SO_ALTBREAK) != 0 )
        SYS_FEState.SuspendRequest = true;
      return;
    }

    if ( CtrlKeyDown )                      /* Ctrl-break */
      KBD_InsertCode( 0x46E01DE0, keyup );  /* Simulate Ctrl-ScrollLock */
    else                                    /* Pause */
      KBD_InsertCode( 0x451DE1, keyup );    /* Simulate Ctrl-NumLock */

    return;
  }

  KBD_InsertCode (KBD_translate [rawkey], keyup);

}

/* I/O handlers ****************************************************** */

/* These are to emulate the keyboard on a PC-AT, which is capable of
   being given commands. There are 2 sorts of commands - those which
   are given to the motherboard's 8042 microcontroller (typically
   system functions such as the A20 gate line, speed change, etc). and
   those passed on to the controller in the keyboard (typically setting
   LEDs). On the 8042 is a mode register and a 'Port 2' register, both
   capable of being read or written. The mode register has an
   interrupt-enable bit and a 'keyboard enable' bit. When keyboard-enable
   is clear, the keyboard is prevented from sending scan-code data, but
   can send ACKs or other responses to commands. (Added 1/Nov/94 to fix
   KEYB UK's keyboard lights bug).
*/



     /* Command definitions */
     /* MakeBeep has been invented by me! */

#define No_CMD  (-1)

#define CMD_8042writeMode   0x60
#define CMD_8042writePort2  0xD1
#define CMD_8042writeBuffer 0xD2
#define CMD_8042makeBeep    0xDF

#define KBD_ACK             0xFA
#define KBD_NAK             0xFE

static BYTE KBD_ControllerRAM [0x20];

static int  KBD_8042Command;
static int  KBD_OutputBuffer;
static int  KBD_StatusReg;
#define SR_PAR_ERR    0x80  /* True if parity error talking to kbd */
#define SR_TIMEOUT    0x40  /* True if timeout error talking to kbd */
#define SR_AUXDATA    0x20  /* True if data from aux device is valid */
#define SR_KEYLOCK    0x10  /* True if keyboard is unlocked */
#define SR_CMDnotDATA 0x08  /* True if 0x64 has been written last */
#define SR_SELFTEST   0x04  /* True if self-test passed */
#define SR_IP_BUSY    0x02  /* True if busy processing PC written data */
#define SR_OP_READY   0x01  /* True if data is available */


static int  KBD_ModeReg;
#define MR_XLATE_FLAG  0x40
#define MR_MOU_DISABLE 0x20
#define MR_KB_DISABLE  0x10
#define MR_SYSFLAG     0x04
#define MR_MOU_IRQEN   0x02
#define MR_KB_IRQEN    0x01

static int  KBD_Port2Reg;

static int  KBD_KbdCommand;

#define MAX_RESP_BYTES 4

  /* This holds responses for keyboard-controller commands: up to
     4 bytes are allowed, the LSB of KBD_ResponseBytes being the
     */
static int  KBD_ResponseBytes;
static int  KBD_ResponseByteCount;

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

/* SetOutputBuffer is used to place the given byte into the
    output buffer for sending to the PC */

static void SetOutputBuffer ( int val )
{
  int oldstat;

  KBD_OutputBuffer = val;  /* Value is always overwritten */

  oldstat = KBD_StatusReg;
  KBD_StatusReg |= SR_OP_READY; /* If this sends data available from
                                   0->1, and interrupts are enabled,
                                   generate an interrupt */

  if ( ( oldstat & SR_OP_READY )  == 0  &&
       ( KBD_ModeReg & MR_KB_IRQEN ) != 0
     )
  {
#ifdef DEBUG
    SYS_trace("Kbd IRQ, code %X", val );
#endif
    SYS_Interrupt(1);
  }
}

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

static void Do8042Cmd ( int cmd, int data )
{
  KBD_8042Command = No_CMD;

  switch ( cmd )
  {
    case CMD_8042writeMode:
      KBD_ModeReg = data;

      if ( data & MR_SYSFLAG )
        KBD_StatusReg |= SR_SELFTEST;
      else
        KBD_StatusReg &= ~SR_SELFTEST;
      break;

    case CMD_8042writePort2:
      KBD_Port2Reg = data;
      SYS_State.SetA20gate( data & 2 );
      break;

    case CMD_8042writeBuffer:
      SetOutputBuffer(data); /* NB! This command doesn't actually work
                                on my test PC! */
      break;

    default:
      if ( cmd >= 0x21 && cmd <= 0x3F )  /* Write Controller RAM */
        KBD_ControllerRAM [ cmd & 0x1F ] = data;
      break;
  }
}


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


static void Write8042CmdReg ( int data )
{
  KBD_8042Command = No_CMD;

  switch ( data )
  {
    case 0x20: /* Read Mode reg */
      KBD_StatusReg |= SR_OP_READY; /* BODGE!! W4WG keyboard stiff fix */
      SetOutputBuffer(KBD_ModeReg);
      break;

    case 0xD0: /* Read Port 2 */
      SetOutputBuffer(KBD_Port2Reg);
      break;

    case 0xAE: /* Enable Keyboard */
      KBD_ModeReg &= ~MR_KB_DISABLE;
      break;

    case 0xAD: /* Disable keyboard */
      KBD_ModeReg |=  MR_KB_DISABLE;
      break;

    case 0xAA: /* Self-test: clears a lot of things */
      KBD_PCInPtr = KBD_PCOutPtr = KBD_ResponseByteCount = 0;
      KBD_StatusReg = SR_KEYLOCK | SR_CMDnotDATA | SR_SELFTEST;
      KBD_ModeReg   = MR_MOU_DISABLE | MR_KB_DISABLE;
      SetOutputBuffer(0x55);
      break;

    case 0xAB: /* Check keyboard - return '0' = always works */
      KBD_PCInPtr = KBD_PCOutPtr = KBD_ResponseByteCount = 0;
      SetOutputBuffer(0);
      break;

    case CMD_8042makeBeep:
      _kernel_oswrch(7);
      break;

    case CMD_8042writeMode:
    case CMD_8042writePort2:
    case CMD_8042writeBuffer:
      KBD_8042Command = data;
      break;

    default:
      if ( data >= 0x21 && data <= 0x3F )  /* Write Controller RAM */
        KBD_8042Command = data;
      else if ( data >= 0x61 && data <= 0x7F )  /* Read Controller RAM */
        SetOutputBuffer ( KBD_ControllerRAM [ data & 0x1F ] );
      break;
  }

}

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

static void KBD_CheckKbdData (void)
{
   /* Called to check to see if the keyboard should send the
      keyboard controller some data */

   if (KBD_StatusReg & SR_OP_READY) /* Already data - hold off for now */
     return;

   /* Any response bytes from keyboard controller? */

   if ( KBD_ResponseByteCount > 0 )
   {
     SetOutputBuffer ( KBD_ResponseBytes & 0xFF );
     KBD_ResponseByteCount--;
     KBD_ResponseBytes >>= 8;
     return;
   }

   /* Any keypress data? */

   if ( (KBD_ModeReg & MR_KB_DISABLE) == 0  &&
                KBD_PCCodeWaiting() )
   {
     SYS_Activity(3);
     SetOutputBuffer ( KBD_GetPCCode() );
   }
}

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

static void SendKBResponse ( int nbytes, int val )
{
  KBD_ResponseBytes = val;
  KBD_ResponseByteCount = nbytes;
  KBD_CheckKbdData();
}

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

static void DoKbdCmd ( int cmd, int data )
{
  KBD_KbdCommand = No_CMD;

  switch ( cmd )
  {
    case 0xED:  /* Set LEDS */
       KBD_LED_state =   ( data & 1  ?  2 : 0 )   /* Scroll lock state */
                       + ( data & 2  ?  4 : 0 )   /* Num Lock state */
                       + ( data & 4  ? 16 : 0 );  /* Caps lock state */

       if ( KBD_EnableLEDChange )
         KBDM_UpdateLEDs ();
       break;

    case 0xF0:  /* Set Translation table: NOT IMPLEMENTED */
       break;

    case 0xF3:  /* Set repeat rate: NOT IMPLEMENTED */
       break;

    default:   /* Shouldn't get here */
       return;

  }

  SendKBResponse ( 1, KBD_ACK );
}

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

static void DoKbdByte ( int data )
{

  KBD_KbdCommand = No_CMD;

  switch ( data )
  {
    case 0xED:
    case 0xF0:
    case 0xF3:
      KBD_KbdCommand = data;
      break;

    case 0xEE:  /* Echo */
      SendKBResponse ( 1, 0xEE );
      return;

    case 0xEF:  /* No ops */
    case 0xF1:
      SendKBResponse ( 1, KBD_NAK );
      return;

    case 0xF2:
      SendKBResponse ( 3, 0x41ABFA );
      return;

    case 0xF4:  /* Enable command */
    case 0xF5:
    case 0xF6:  /* Flush buffer */
      KBD_PCInPtr = KBD_PCOutPtr = 0;
      break;


    case 0xF7: case 0xF8: case 0xF9: case 0xFA:
    case 0xFB: case 0xFC: case 0xFD:
      break;

    case 0xFE:
      SYS_trace("Kbd Resend");
      break;

    case 0xFF:  /* Reset */
      KBD_PCInPtr = KBD_PCOutPtr = 0;
      break;

    default:
      SendKBResponse(1, KBD_NAK);
      return;
  }

  SendKBResponse(1, KBD_ACK);
}

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

/*
   7/6/95

   KBD_CheckKbdData() isn't strictly necessary here, but
   DOS 6.22's KEYB UK has such a short timeout (on Geminis)
   while waiting for replies to the 'Identify' command that
   it fails to detect it (^@ on command line). This helps
   generate interrupts at a faster rate.
*/

static int KBD_Rd8 ( int addr )
{
  if (addr == 0x64)       /* Read 8042 status reg */
  {
    KBD_CheckKbdData();

#ifdef DEBUG
    SYS_trace("Rd 64h=%X", KBD_StatusReg );
#endif
    return KBD_StatusReg;
  }

  if (addr == 0x60)       /* Read keyboard data */
  {
#ifdef DEBUG
    SYS_trace("Rd 60h=%X", KBD_OutputBuffer );
#endif
    KBD_StatusReg &= ~SR_OP_READY;
    return KBD_OutputBuffer;
  }

  return 0xFF;
}

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

static void KBD_Wr8 ( int addr, int data )
{
#ifdef DEBUG
  SYS_trace("Wr8 %X=%X", addr, data);
#endif

  if (addr == 0x64) /* Command write */
  {
    KBD_StatusReg |= SR_CMDnotDATA;
    Write8042CmdReg (data);
    return;
  }

  if (addr == 0x60) /* Data write */
  {
    KBD_StatusReg &= ~SR_CMDnotDATA;

    if      (KBD_8042Command != No_CMD )
      Do8042Cmd ( KBD_8042Command, data );

    else if (KBD_KbdCommand  != No_CMD )
      DoKbdCmd  ( KBD_KbdCommand, data );

    else
      DoKbdByte ( data );

  }


}


/* Event handlers ************************ */

static bool KBD_PollFn ()
{
   /* Check for keys pressed */

   KBD_CheckRawKeycodes();

   KBD_CheckKbdData();

   return false;
}



/* Init & Shutdown routines ************** */

static bool KBD_StartKbd()
{
  KBDM_FlushBuffer();  /* Clear any scancodes in input buffer */
  KBD_PCInPtr = 0;
  KBD_PCOutPtr = 0;

  KBD_EnableLEDChange = true;
  repeat_rawkey = -1;
  AltKeyDown = false;
  CtrlKeyDown = false;

  KBDM_Install();
  KBD_SetShiftsUp();
  KBDM_UpdateLEDs();
  return false;
}

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

static bool KBD_StopKbd()
{
  KBDM_Remove();
  return false;
}

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

bool KBD_Init()
{
  KBDM_FlushBuffer();  /* Clear any scancodes in input buffer */

  KBD_Port2Reg = 0;
  KBD_8042Command = No_CMD;
  KBD_KbdCommand  = No_CMD;
  KBD_StatusReg = SR_KEYLOCK | SR_CMDnotDATA | SR_SELFTEST;
  KBD_ModeReg   = MR_MOU_DISABLE | MR_KB_DISABLE;

  SYS_registerIO (0x60, 0x67, KBD_Rd8, KBD_Rd8, KBD_Wr8, KBD_Wr8, 0);

  SYS_registerEvent (SYS_PollChain, KBD_PollFn, 0 );

  SYS_registerEvent (SYS_StartFE, KBD_StartKbd, 0 );
  SYS_registerEvent (SYS_StopFE,  KBD_StopKbd, 0  );
  SYS_registerEvent (SYS_GainFocus, KBD_StartKbd, 0 );
  SYS_registerEvent (SYS_LoseFocus, KBD_StopKbd, 0  );

  return true;
}


