#include "project.h"


#define KBCLK		(1UL << 0)
#define KBCLK_PORT	GPIOA
#define KBCLK_IRQ	NVIC_EXTI0_IRQ

#define KBDAT		(1UL << 1)
#define KBDAT_PORT	GPIOA

/*Cope with lost sync */
/*If we've had no bits for this long, it's definately the start of a new word,  72MHz units */
#define RESYNC_GAP 	(7200000)   /* 100ms */

#define ATKBD_TIMEOUT	1000      /*how long to wait for the keyboard to respond to a command in ms */

#define ATKBD_CLOCK_SEIZE_DELAY 200 /* how many us from pulling clock low to pulling data low, when we xmit */
#define ATKBD_BIT_DELAY	10      /* how many us we allow from the KBD to pull clock back up */


typedef enum
{
  STATE_START = 0,
  STATE_BIT0,
  STATE_BIT1,
  STATE_BIT2,
  STATE_BIT3,
  STATE_BIT4,
  STATE_BIT5,
  STATE_BIT6,
  STATE_BIT7,
  STATE_PARITY,
  STATE_STOP,
} atkbd_state;


#define ATKBD_CMD_SETLEDS       0xed
#define ATKBD_CMD_GSCANSET      0xf0
#define ATKBD_CMD_SSCANSET      0xf0
#define ATKBD_CMD_GETID         0xf2
#define ATKBD_CMD_SETREP        0xf3
#define ATKBD_CMD_ENABLE        0xf4
#define ATKBD_CMD_RESET_DIS     0xf5
#define ATKBD_CMD_RESET_DEF     0xf6
#define ATKBD_CMD_SETALL_MB     0xf8
#define ATKBD_CMD_SETALL_MBR    0xfa
#define ATKBD_CMD_RESET_BAT     0xff
#define ATKBD_CMD_RESEND        0xfe
#define ATKBD_CMD_EX_ENABLE     0xea
#define ATKBD_CMD_EX_SETLEDS    0xeb
#define ATKBD_CMD_OK_GETID      0xe8
#define ATKBD_CMD_ECHO          0xee

#define ATKBD_RET_ECHO          0xee
#define ATKBD_RET_ACK           0xfa
#define ATKBD_RET_NAK           0xfe
#define ATKBD_RET_BAT           0xaa
#define ATKBD_RET_EMUL0         0xe0
#define ATKBD_RET_EMUL1         0xe1
#define ATKBD_RET_RELEASE       0xf0
#define ATKBD_RET_HANJA         0xf1
#define ATKBD_RET_HANGEUL       0xf2
#define ATKBD_RET_ERR           0xff

#define ATKBD_KEY_UNKNOWN       0
#define ATKBD_KEY_NULL          255



static int ready;
static uint8_t saved_leds;

static int atkbd_ack;
static int atkbd_nack;
static int atkbd_bat;
static int atkbd_echo;

static void
atkbd_mask_irq (void)
{
  nvic_disable_irq (KBCLK_IRQ);
}

static void
atkbd_unmask_flush_irq (void)
{
  exti_reset_request (KBCLK);
  nvic_enable_irq (KBCLK_IRQ);
}



static uint32_t
cycle_diff (uint32_t a, uint32_t b)
{
  return b - a;
}

static void
atkbd_dispatch (int key, int updown)
{
/* the logic here is batshit, consult scancode.doc in the DOCS dir */
  static int fake_ctrl, pause_down;

  switch (key)
    {
    case AT_SC_LEFTCTRL | AT_BS_EMUL1:
      fake_ctrl = updown;
      break;
    }


  if (key == (AT_BS_EMUL0 | AT_SC_KPASTERISK))
    key = AT_SC_SYSRQ;

  if (key == AT_SC_NUMLOCK)
    {
      /*Grr broken MS state machine in docs */
      if ((!updown) && (pause_down))
        {
          key = AT_SC_PAUSE;
          pause_down = 0;
        }

      if (fake_ctrl && updown)
        {
          key = AT_SC_PAUSE;
          pause_down = 1;
        }
    }


/* Filter fakes */
  switch (key)
    {
    case AT_SC_LEFTCTRL | AT_BS_EMUL1:
    case AT_SC_LEFTSHIFT | AT_BS_EMUL0:
    case AT_SC_RIGHTSHIFT | AT_BS_EMUL0:
      break;
    default:
      scancode_dispatch (key, updown);
    }

}

static void
atkbd_data_dispatch (uint8_t byte)
{
  static int release;
  static int emul;

#ifdef DEBUG
  printf ("ATKBD < 0x%02x\r\n", byte);
#endif

  switch (byte)
    {
    case ATKBD_RET_ACK:
      atkbd_ack++;
      break;
    case ATKBD_RET_NAK:
      atkbd_nack++;
      break;
    case ATKBD_RET_BAT:
      atkbd_bat++;
      break;
    case ATKBD_RET_ECHO:
      atkbd_echo++;
      break;
    case ATKBD_RET_ERR:
    case ATKBD_KEY_UNKNOWN:
      /*All these need no action */
      break;
    case AT_SC_EMUL0:
      emul = AT_BS_EMUL0;
      break;
    case AT_SC_EMUL1:
      emul = AT_BS_EMUL1;
      break;
    case ATKBD_RET_RELEASE:
      release = 1;
      break;
    default:
      atkbd_dispatch (emul | byte, !release);
      emul = 0;
      release = 0;
    }

}


void
exti0_isr (void)
{
  static uint32_t last_interrupt = 0;
  static atkbd_state state = STATE_START;
  static uint8_t atkbd_byte;
  static int parity;

  uint32_t now, diff;
  int d;

  d = ! !GET (KBDAT);

  exti_reset_request (KBCLK);

  now = dwt_read_cycle_counter ();
  diff = cycle_diff (last_interrupt, now);
  last_interrupt = now;


  if (diff > RESYNC_GAP)
    state = STATE_START;


  switch (state)
    {
    case STATE_START:
      atkbd_byte = 0;
      parity = 0;
      if (!d)
        state++;
      break;
    case STATE_BIT0:
    case STATE_BIT1:
    case STATE_BIT2:
    case STATE_BIT3:
    case STATE_BIT4:
    case STATE_BIT5:
    case STATE_BIT6:
    case STATE_BIT7:
      atkbd_byte |= d << (state - STATE_BIT0);
      /* fall through */
    case STATE_PARITY:
      parity ^= d;
      state++;
      break;
    case STATE_STOP:
      if (d && parity)
        atkbd_data_dispatch (atkbd_byte);
      state = STATE_START;
      break;
    }
}



void
atkbd_set (int clk, int dat)
{
  if (clk)
    {
      MAP_INPUT_PU (KBCLK);
    }
  else
    {
      CLEAR (KBCLK);
      MAP_OUTPUT_PP (KBCLK);
    }


  if (dat)
    {
      MAP_INPUT_PU (KBDAT);
    }
  else
    {
      CLEAR (KBDAT);
      MAP_OUTPUT_PP (KBDAT);
    }

}


int
atkbd_send (uint8_t d)
{
  uint32_t then = ticks;
  int parity = 1;
  uint32_t c;

  atkbd_mask_irq ();

  atkbd_set (0, 1);
  delay_us (ATKBD_CLOCK_SEIZE_DELAY);
  atkbd_set (0, 0);
  delay_us (ATKBD_BIT_DELAY);
  atkbd_set (1, 0);
  delay_us (ATKBD_BIT_DELAY);


  /* 8 data bits */
  for (c = 1; c < 0x100; c <<= 1)
    {
      while (GET (KBCLK))
        if (timed_out (then, ATKBD_TIMEOUT))
          goto err;
      atkbd_set (1, c & d);
      parity ^= ! !(c & d);
      while (!GET (KBCLK))
        if (timed_out (then, ATKBD_TIMEOUT))
          goto err;
    }

  /* A parity bit */
  while (GET (KBCLK))
    if (timed_out (then, ATKBD_TIMEOUT))
      goto err;
  atkbd_set (1, parity);
  while (!GET (KBCLK))
    if (timed_out (then, ATKBD_TIMEOUT))
      goto err;

  /*  two stop bits */
  while (GET (KBCLK))
    if (timed_out (then, ATKBD_TIMEOUT))
      goto err;
  atkbd_set (1, 1);
  while (!GET (KBCLK))
    if (timed_out (then, ATKBD_TIMEOUT))
      goto err;

  while (GET (KBCLK))
    if (timed_out (then, ATKBD_TIMEOUT))
      goto err;
  atkbd_set (1, 1);
  while (!GET (KBCLK))
    if (timed_out (then, ATKBD_TIMEOUT))
      goto err;

  while (!GET (KBDAT))
    if (timed_out (then, ATKBD_TIMEOUT))
      goto err;
  while (!GET (KBCLK))
    if (timed_out (then, ATKBD_TIMEOUT))
      goto err;

  atkbd_unmask_flush_irq ();

#ifdef DEBUG
  printf ("ATKBD > 0x%02x\r\n", d);
#endif

  return 0;

err:
  atkbd_set (1, 1);
  while (!GET (KBDAT))
    if (timed_out (then, ATKBD_TIMEOUT))
      goto err;
  while (!GET (KBCLK))
    if (timed_out (then, ATKBD_TIMEOUT))
      goto err;

  atkbd_unmask_flush_irq ();

#ifdef DEBUG
  printf ("ATKBD >! 0x%02x\r\n", d);
#endif
  return -1;
}

static int
atkbd_reset (void)
{
  uint32_t then = ticks;
  atkbd_bat = 0;
  atkbd_send (ATKBD_CMD_RESET_BAT);
  while (!atkbd_bat)
    if (timed_out (then, ATKBD_TIMEOUT))
      return -1;

  then = ticks;
  atkbd_ack = 0;
  atkbd_send (ATKBD_CMD_SETALL_MBR);
  while (!atkbd_ack)
    if (timed_out (then, ATKBD_TIMEOUT))
      return -1;


  return 0;
}

int
atkbd_request_echo (void)
{
  uint32_t then = ticks;
  atkbd_ack = 0;
  atkbd_send (ATKBD_CMD_ECHO);
  while (!atkbd_echo)
    if (timed_out (then, ATKBD_TIMEOUT))
      return -1;

  return 0;
}


int
atkbd_set_leds (uint8_t leds)
{
  uint32_t then;

  saved_leds = leds;

  if (!ready)
    return 0;

  then = ticks;
  atkbd_ack = 0;
  atkbd_send (ATKBD_CMD_SETLEDS);
  while (!atkbd_ack)
    if (timed_out (then, ATKBD_TIMEOUT))
      return -1;

  then = ticks;
  atkbd_ack = 0;
  atkbd_send (leds);
  while (!atkbd_ack)
    if (timed_out (then, ATKBD_TIMEOUT))
      return -1;

  return 0;
}


int
atkbd_set_scanset (uint8_t scanset)
{
  uint32_t then = ticks;
  atkbd_ack = 0;
  atkbd_send (ATKBD_CMD_SSCANSET);
  while (!atkbd_ack)
    if (timed_out (then, ATKBD_TIMEOUT))
      return -1;

  then = ticks;
  atkbd_ack = 0;
  atkbd_send (scanset);
  while (!atkbd_ack)
    if (timed_out (then, ATKBD_TIMEOUT))
      return -1;

  return 0;
}

int
atkbd_set_mb (void)
{
  uint32_t then = ticks;
  atkbd_ack = 0;
  atkbd_send (ATKBD_CMD_SETALL_MB);
  while (!atkbd_ack)
    if (timed_out (then, ATKBD_TIMEOUT))
      return -1;

  return 0;
}

int
atkbd_set_mbr (void)
{
  uint32_t then = ticks;
  atkbd_ack = 0;
  atkbd_send (ATKBD_CMD_SETALL_MBR);
  while (!atkbd_ack)
    if (timed_out (then, ATKBD_TIMEOUT))
      return -1;

  return 0;
}


void
atkbd_start (void)
{

  atkbd_reset ();
  atkbd_request_echo ();
  atkbd_set_mbr ();
  atkbd_set_scanset (2);
  atkbd_set_leds (saved_leds);

  ready++;
}

void
atkbd_init (void)
{
  atkbd_set (1, 1);

  nvic_enable_irq (NVIC_EXTI0_IRQ);


  exti_select_source (KBCLK, KBCLK_PORT);
  exti_set_trigger (KBCLK, EXTI_TRIGGER_FALLING);
  exti_enable_request (KBCLK);

  exti_reset_request (KBCLK);
  nvic_enable_irq (KBCLK_IRQ);



}