/*
 * (C) Copyright 1992, ..., 2014 the "DOSEMU-Development-Team".
 *
 * for details see file COPYING in the DOSEMU distribution
 */

/*
 * DANG_BEGIN_MODULE
 *
 * Description: 8042 Keyboard controller chip emulation for DOSEMU.
 *
 * Exports: keyb_8042_init(void), keyb_8042_reset(void)
 *
 * Maintainers: Scott Buchholz, Rainer Zimmermann
 *
 * REMARK
 * This code provides truly rudimentary 8042 controller emulation.
 * Not having any documentation on the 8042 makes it hard to improve. :)
 *
 * /REMARK
 * DANG_END_MODULE
 *
 */

#include "emu.h"
#include "iodev.h"
#include "int.h"
#include "port.h"
#include "memory.h"
#include "keyboard/keyboard.h"
#include "keyboard/keyb_server.h"
#include "keyboard/keyb_clients.h"
#include "speaker.h"
#include "hma.h"

/* bios-assisted keyboard read hack */
#define KBD_READ_HACK 0

#define RESET_LINE_MASK 1

/* accurate emulation of special 8042 and keyboard commands - currently untested...
*/
#define KEYB_CMD 1

Bit8u port60_buffer = 0;
Boolean port60_ready = 0;
#if KBD_READ_HACK
static Bit8u last_read_data;
static Boolean last_read_valid;
#endif
static Boolean kbd_disabled;

#if KEYB_CMD

/* variable indicating the command status of the keyboard/8042.
 * if non-zero, e.g. a parameter byte to a command is expected.
 */
static int wstate = 0;
static int rstate = 0;

static int     keyb_ctrl_scanmap    = 1;
static int     keyb_ctrl_typematic  = 0x23;
static int     keyb_ctrl_enable     = 1;
static int     keyb_ctrl_isdata     = 0;
static Bit8u   keyb_ctrl_command    = 0x01;

static inline void keyb_ctrl_clearbuf(void)
{
/* this probably ought to do something :) */
}

/* write byte to the 8042's output buffer */
void output_byte_8042(Bit8u value)
{
   port60_buffer=value;
   port60_ready=1;
   if (keyb_ctrl_command & 0x01) {    /* if interrupt enabled */
      k_printf("8042: scheduling IRQ1\n");
      pic_request(1);
   }
   else
      k_printf("8042: interrupt flag OFF!\n");
}

static void ack(void)
{
    write_queue(&keyb_queue, 0xfa);
}

static void write_port60(Bit8u value)
{
  switch (wstate) {
    case 0x00:
      switch (value) {
	case 0xed:    /* set mode indicators */
	  h_printf("8042: write port 0x60 set mode indicators\n");
	  ack();
	  wstate=0xed;
	  break;
        case 0xee:    /* port check */
	  h_printf("8042: write port 0x60 test mode 0xee\n");
	  write_queue(&keyb_queue, 0xee);
	  break;
	case 0xf0:    /* set keyb scan byte */
	  h_printf("8042: write port 0x60 set keyb scan type\n");
	  ack();
	  wstate=0xf0;
	  break;
	case 0xf2:    /* get keyb type */
	  h_printf("8042: write port 0x60 get keyb type\n");
	  ack();
	  rstate=0xf2;
	  break;
	case 0xf3:    /* set typematic speed */
	  h_printf("8042: write port 0x60 set typematic speed\n");
	  ack();
	  wstate=0xf3;
	  break;
	case 0xf4:    /* clear buffer */
	  h_printf("8042: write port 0x60 clear buffer\n");
	  keyb_ctrl_clearbuf();
 	  keyb_ctrl_enable=1;
	  ack();
	  break;
	case 0xf5:    /* default, w/disable */
	  h_printf("8042: write port 0x60 set default, w/disable\n");
	  keyb_8042_reset();
	  keyb_ctrl_enable = 0;
	  ack();
	  break;
	case 0xf6:    /* set default */
	  h_printf("8042: write port 0x60 set default\n");
	  keyb_8042_reset();
	  ack();
	  break;
	case 0xf7:    /* set all keys to typematic */
	case 0xf8:    /* set all keys to make/break */
	case 0xf9:    /* set all keys to make */
	case 0xfa:    /* set all keys to typematic make/break */
	  h_printf("8042: write port 0x60 set mode (0x%02x)\n", value);
	  keyb_ctrl_clearbuf();
	  ack();
	  /* set mode ??? */
	  break;
	case 0xfb:    /* set single key to typematic & wait */
	case 0xfc:    /* set to make/break & wait */
	case 0xfd:    /* set to make & wait */
	  h_printf("8042: write port 0x60 set mod (0x%02x)\n", value);
	  keyb_ctrl_clearbuf();
	  ack();
	  wstate=value;
	  break;
	case 0xfe:    /* resend */
	  h_printf("8042: write port 0x60 resend\n");
	  write_queue(&keyb_queue, port60_buffer);
	  break;
	case 0xff:    /* reset */
	  h_printf("8042: write port 0x60 reset\n");
	  ack();
	  rstate=0xff; /* wait for port 60h read */
	  break;
	default:
	  h_printf("8042: write port 0x60 unsupported command 0x%02x =>Error\n", value);
	  write_queue(&keyb_queue, 0xfe);
	  break;
      }
      break;
    case 0x60:
      h_printf("8042: write 8042 command byte 0x%02x\n", value);
      keyb_ctrl_command=value;
      /* no ack() */
      wstate=0;
      break;
    case 0xd1:
      h_printf("8042: drive output port lines, value=0x%02x\n", value);
      switch (value) {
        case 0xdf:	/* enable A20 */
	  h_printf("8042: enable A20 line\n");
	  set_a20(1);
	  break;
        case 0xdd:	/* disable A20) */
	  h_printf("8042: disable A20 line\n");
	  set_a20(0);
	  break;
      }
      port60_ready=0;
      wstate=0;
      break;
    case 0xed:        /* set LED mode */
    {
      t_modifiers modifiers = 0;
      h_printf("8042: write port 0x60 set LED mode to 0x%02x\n", value);
      /* TESTME this mapping is an educated guess */
      if (value & 0x01) modifiers |= MODIFIER_SCR;
      if (value & 0x02) modifiers |= MODIFIER_NUM;
      if (value & 0x04) modifiers |= MODIFIER_CAPS;
      keyb_client_set_leds(modifiers);
      ack();
      wstate=0;
      break;
    }
    case 0xf0:        /* get/set keyboard scan map */
      h_printf("8042: write port 0x60 get/set keyboard scan map 0x%02x\n", value);
      ack();
      if (value == 0)
	  write_queue(&keyb_queue, keyb_ctrl_scanmap);
      else
	  keyb_ctrl_scanmap=value;
      wstate=0;
      break;

    case 0xf3:        /* set typematic rate */
      h_printf("8042: write port 0x60 set typematic rate 0x%02x\n", value);
      keyb_ctrl_typematic = value;
      ack();
      wstate=0;
      break;
    default:
      h_printf("8042: write port 0x60 illegal state (0x%02x), resending\n",
	       wstate);
      wstate=0;
      write_port60(value);
  }
}

/* write to port 64h (8042 command register) */
static void write_port64(Bit8u value) {
   k_printf("8042: write port64h, =%02x\n",value);

       switch(value) {
	  case 0x20:       /* read 8042 command byte */
             output_byte_8042(keyb_ctrl_command);
	     break;

	  case 0x60:       /* write 8042 command byte */
	     wstate=0x60;
	     break;

#if 0 /* not sure if these are ok and/or needed. */

	  case 0xa4:       /* passwort installed test */
	     output_byte_8042(0xfa);   /* no password */
	     break;

	  case 0xa5:       /* load password */
	     /* XXX ... we should read bytes from port60 until 0 is found */
	     break;

	  case 0xa9:       /* aux interface test */
	     output_byte_8042(0x00);  /* ok */
	     break;

	  case 0xaa:       /* 8042 self test */
	     output_byte_8042(0x55);  /* ok */
	     break;

	  case 0xab:       /* keyboard interface test */
	     output_byte_8042(0x00);  /* ok */
	     break;

	  case 0xc0:       /* read 8042 input port */
	     output_byte_8042(0xff);   /* just send _something_... */
	     break;
#endif
	  case 0xad:
	     kbd_disabled = 1;
	     break;
	  case 0xae:
	     kbd_disabled = 0;
#if KBD_READ_HACK
	     last_read_valid = 0;
#endif
	     break;
	  case 0xd1:       /* next write to port 0x60 drives hardware port */
	     wstate=0xd1;
	     break;

	  case 0xf0 ... 0xff: /* produce 6ms pulse on hardware port */
	     wstate=0;
	     port60_ready=0;
	     if (!(value & RESET_LINE_MASK)) {
	         h_printf("8042: produce 6ms pulse on cpu reset line\n");
	         cpu_reset();
	     }
	     else
	         h_printf("8042: produce 6ms pulse on hardware port, ignored\n");
	     break;

	  default:
	     h_printf("8042: write port 0x64 unsupported command 0x%02x, ignored\n",
		      value);
	     /* various other commands... ignore */
	     break;
       }
}
#endif

static Bit8u read_port60(void)
{
  Bit8u r;

#if KBD_READ_HACK
  if (kbd_disabled && last_read_valid) {
    r = last_read_data;
    if (port60_ready && (keyb_ctrl_command & 0x01))    /* if interrupt enabled */
      pic_request(1);
  } else {
    r = port60_buffer;
    port60_ready = 0;
    last_read_data = r;
    last_read_valid = 1;
  }
#else
  r = port60_buffer;
  port60_ready = 0;
#endif

  h_printf("8042: read port 0x60 = 0x%02x\n", r);

#if KEYB_CMD
  switch (rstate) {
    case 0xf2:        /* get keyboard type, MSB */
      h_printf("8042: read port 0x60, getting keyboard type (MSB)\n");
      output_byte_8042(0x83);
      rstate = 0x72;
      break;
    case 0x72:        /* get keyboard type, LSB */
      h_printf("8042: read port 0x60, getting keyboard type (LSB)\n");
      output_byte_8042(0xab);
      rstate = 0;
      break;
    case 0xff:        /* reset keyboard */
      h_printf("8042: read port 0x60, resetting\n");
      output_byte_8042(0xaa);  /* BAT completion code */
      rstate = 0;
      break;
    default:          /* invalid state ?! */
      rstate = 0;
      break;
  }
#endif
  return r;
}


Bit8u keyb_io_read(ioport_t port, void *arg)
{
  Bit8u r = 0;

  switch (port) {
  case 0x60:
    r = read_port60();
    if (!port60_ready)
      pic_untrigger(1);
    k_printf("8042: read port 0x60 read=0x%02x\n",r);
    break;

  case 0x61:
    /* Handle only PC-Speaker right now */
    r = spkr_io_read(port);
    break;

  case 0x64:
    r= 0x1c | (port60_ready ? 0x01 : 0x00);
    k_printf("8042: read port 0x64 status check=0x%02x, port60_ready=%d\n",
	     r, port60_ready);
  }
  return r;
}

void keyb_io_write(ioport_t port, Bit8u value, void *arg)
{
  switch (port) {
  case 0x60:
    k_printf("8042: write port 0x60 outb = 0x%x\n", value);
#if KEYB_CMD
     write_port60(value);
#endif
    break;

  case 0x61:
    if (value & 0x80) {
      k_printf("8042: IRQ ACK, %i\n", port60_ready);
      int_check_queue();   /* reschedule irq1 if appropriate */
    }
    spkr_io_write(port, value);
    break;

  case 0x64:
    k_printf("8042: write port 0x64 outb = 0x%x\n", value);
    write_port64(value);
    break;
  }
}

void keyb_8042_init(void)
{
  emu_iodev_t  io_device = {};

  /* 8042 keyboard controller */
  io_device.read_portb   = keyb_io_read;
  io_device.write_portb  = keyb_io_write;
  io_device.read_portw   = NULL;
  io_device.write_portw  = NULL;
  io_device.read_portd   = NULL;
  io_device.write_portd  = NULL;
  io_device.handler_name = "8042 Keyboard data";
  io_device.start_addr   = 0x0060;
  io_device.end_addr     = 0x0060;
  port_register_handler(io_device, 0);

  io_device.handler_name = "8042 Keyboard command";
  io_device.start_addr   = 0x0064;
  io_device.end_addr     = 0x0064;
  port_register_handler(io_device, 0);

  io_device.handler_name = "Keyboard controller port B";
  io_device.start_addr   = 0x0061;
  io_device.end_addr     = 0x0061;
  port_register_handler(io_device, 0);
}

void keyb_8042_reset(void)
{
#if KEYB_CMD
  rstate = 0;
  wstate = 0;
  keyb_ctrl_scanmap    = 1;
  keyb_ctrl_typematic  = 0x23;
  keyb_ctrl_enable     = 1;
  keyb_ctrl_isdata     = 0;
  keyb_ctrl_clearbuf();
#endif
  port60_buffer = 0;
  port60_ready = 0;
}
