/*
 * DANG_BEGIN_MODULE
 *
 * Description: 8042 Keyboard controller chip emulation for DOSEMU.
 *
 * Maintainers: Scott Buchholz
 * 
 * This code provides truly rudimentary 8042 controller emulation.
 * Not having any documentation on the 8042 makes it hard to improve. :)
 *
 * DANG_END_MODULE
 *
 * $Date: $
 * $Source: $
 * $Revision: $
 * $State: $
 *
 * $Log: $
 */

#include "dosemu.h"
#include "base/iodev.h"
#include "base/int.h"
#include "base/port.h"
#include "base/memory.h"
#include "base/keyb.h"
#include "base/bios_entry.h"

#define OLD_CODE       /* until the new stuff is working */

static u_short microsoft_port_check = 0;
static int     keyb_ctrl_port60     = -1;
static int     keyb_ctrl_mode60     = 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 inline void keyb_ctrl_clearbuf(void)
{
/* this probably ought to do something :) */
}

static void keyb_ctrl_write0(Bit8u value)
{
  switch (keyb_ctrl_mode60) {
    case 0x00:
      switch (value) {
        case 0xee:    /* port check */
	  h_printf("8042: write port 0x60 test mode 0xee\n");
	  keyb_ctrl_port60 = 0xee;
	  keyb_ctrl_isdata = 0;
	  break;
	case 0xed:    /* set mode indicators */
	  h_printf("8042: write port 0x60 set mode indicators\n");
	  keyb_ctrl_port60 = 0xaa;
	  keyb_ctrl_mode60 = 0xed;
	  keyb_ctrl_isdata = 1;
	  break;
	case 0xf0:    /* set keyb scan byte */
	  h_printf("8042: write port 0x60 set keyb scan type\n");
	  keyb_ctrl_port60 = 0xaa;
	  keyb_ctrl_mode60 = 0xf0;
	  keyb_ctrl_isdata = 1;
	  break;
	case 0xf2:    /* get keyb type */
	  h_printf("8042: write port 0x60 get keyb type\n");
	  keyb_ctrl_port60 = 0xaa;
	  keyb_ctrl_mode60 = 0xf2;
	  keyb_ctrl_isdata = 1;
	  break;
	case 0xf3:    /* set typematic speed */
	  h_printf("8042: write port 0x60 set typematic speed\n");
	  keyb_ctrl_port60 = 0xaa;
	  keyb_ctrl_mode60 = 0xf3;
	  keyb_ctrl_isdata = 1;
	  break;
	case 0xf4:    /* clear buffer */
	  h_printf("8042: write port 0x60 clear buffer\n");
	  keyb_ctrl_clearbuf();
	  keyb_ctrl_port60 = 0xaa;
	  keyb_ctrl_isdata = 1;
	  break;
	case 0xf5:    /* default, w/disable */
	  h_printf("8042: write port 0x60 set default, w/disable\n");
	  keyb_reset();
	  keyb_ctrl_enable = 0;
	  keyb_ctrl_port60 = 0xaa;
	  keyb_ctrl_isdata = 1;
	  break;
	case 0xf6:    /* set default */
	  h_printf("8042: write port 0x60 set default\n");
	  keyb_reset();
	  keyb_ctrl_port60 = 0xaa;
	  keyb_ctrl_isdata = 1;
	  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();
	  /* set mode ??? */
	  keyb_ctrl_port60 = 0xaa;
	  keyb_ctrl_isdata = 1;
	  break;
	case 0xfb:    /* set all keys to typematic & wait */
	case 0xfc:    /* set all keys to make/break & wait */
	case 0xfd:    /* set all keys to make & wait */
	  h_printf("8042: write port 0x60 set mod (0x%02x)\n", value);
	  keyb_ctrl_clearbuf();
	  /* set mode & wait... */
	  keyb_ctrl_port60 = 0xaa;
	  keyb_ctrl_isdata = 1;
	  break;
	case 0xfe:    /* resend */
	  h_printf("8042: write port 0x60 resend\n");
	  keyb_ctrl_mode60 = 0x00;
	  keyb_ctrl_port60 = -1;
	  keyb_ctrl_isdata = 1;
	  break;
	case 0xff:    /* reset */
	  h_printf("8042: write port 0x60 resent\n");
	  keyb_ctrl_mode60 = 0xff;
	  keyb_ctrl_port60 = 0xaa;
	  keyb_ctrl_isdata = 1;
	  break;
	default:
	  h_printf("8042: write port 0x60 ERROR, value = 0x%02x\n", value);
	  keyb_ctrl_port60 = 0xfe;
	  keyb_ctrl_isdata = 1;
	  break;
      }
      break;
    case 0xed:        /* set LED mode */
      h_printf("8042: write port 0x60 set LED mode to 0x%02x\n", value);
      keyb_ctrl_port60 = 0xaa;
      keyb_ctrl_isdata = 1;
      keyb_ctrl_mode60 = -1;
      break;
    case 0xf0:        /* get/set keyboard scan map */
      h_printf("8042: write port 0x60 set keyboard scan map 0x%02x\n", value);
      if (value == 0)
	keyb_ctrl_port60 = keyb_ctrl_scanmap;
      else
	keyb_ctrl_port60 = 0xaa;
      keyb_ctrl_isdata = 1;
      keyb_ctrl_mode60 = 0x00;
      break;
    case 0xf3:        /* set typematic rate */
      h_printf("8042: write port 0x60 set typematic rate 0x%02x\n", value);
      keyb_ctrl_mode60 = 0x00;
      if (value >= 0xf0)
	keyb_ctrl_write0(value);
      else
	keyb_ctrl_typematic = value;
      break;
    default:
      h_printf("8042: write port 0x60 illegal mode (0x%02x), resending\n", 
	       keyb_ctrl_mode60);
      keyb_ctrl_mode60 = 0x00;
      keyb_ctrl_write0(value);
  }
}

static Bit8u keyb_ctrl_read0(void)
{
  Bit8u r = (keyb_ctrl_port60 == -1) ? 0 : keyb_ctrl_port60;

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

  switch (keyb_ctrl_mode60) {
    case 0xf2:        /* get keyboard type, MSB */
      h_printf("8042: read port 0x60, getting keyboard type (MSB)\n");
      keyb_ctrl_port60 = 0x83;
      keyb_ctrl_mode60 = 0x72;
      keyb_ctrl_isdata = 1;
      break;
    case 0x72:        /* get keyboard type, LSB */
      h_printf("8042: read port 0x60, getting keyboard type (LSB)\n");
      keyb_ctrl_port60 = 0xab;
      keyb_ctrl_mode60 = 0x00;
      keyb_ctrl_isdata = 1;
      break;
    case 0xff:        /* reset keyboard */
      h_printf("8042: read port 0x60, resetting\n");
      keyb_ctrl_port60 = 0xaa;
      keyb_ctrl_mode60 = 0x00;
      keyb_ctrl_isdata = 1;
      break;
    default:
      keyb_ctrl_port60 = -1;
      keyb_ctrl_isdata = 0;
      break;
  }
  return r;
}



static Bit8u keyb_io_read(Bit32u port)
{
  Bit8u r = 0;

  switch (port) {
  case 0x60:
#ifdef OLD_CODE
    if (keys_ready)
      microsoft_port_check = 0;
    k_printf("8042: write port 0x60 read=0x%02x, microsoft=%d\n",
	     *LASTSCAN_ADD, microsoft_port_check);
    if (microsoft_port_check)
      r = microsoft_port_check;
    else
      r = *LASTSCAN_ADD;
    keys_ready = 0;
#else
    r = keyb_ctrl_read0();
#endif
    break;

  case 0x61:
    if (config.speaker == SPKR_NATIVE)
      r = port_safe_inb(0x61);
    else
      r = keyb_ctrl_port61;
    break;

  case 0x64:
#ifdef OLD_CODE
    r = 0x1c | (keys_ready || microsoft_port_check ? 1 : 0);
#else
    /* this is a BS construction, by the way... */
    r = (keyb_ctrl_enable) ? 0x10 : 0x00;
    if (keyb_ctrl_isdata)
      r |= 0x02;
    if (keyb_ctrl_mode60)
      r |= 0x08;
    if (!keyb_ctrl_isdata || keys_ready || microsoft_port_check)
      r |= 0x01;
#endif
    k_printf("8042: read port 0x64 status check=0x%02x, keys_ready=%d, msft=%d\n",
	     r, keys_ready, microsoft_port_check);
    break;
  }
  return r;
}

static void keyb_io_write(Bit32u port, Bit8u value)
{
  switch (port) {
  case 0x60:
#ifdef OLD_CODE
    k_printf("8042: write port 0x60 outb = 0x%x\n", value);
    microsoft_port_check = 1;
    if (value < 0xf0) {
      microsoft_port_check = 0xfe;
    }
    else {
      microsoft_port_check = 0xfa;
    }
#else
    keyb_ctrl_write0(value);
#endif
    break;

  case 0x61:
    switch (config.speaker) {
    case SPKR_NATIVE:
      port_safe_outb(0x61, value & 0x03);
      break;
      
    case SPKR_EMULATED:
      if (((value & 3) == 3) && (pit[2].mode == 2 || pit[2].mode == 3)) {
	k_printf("8042: emulated beep!\n");
	putchar('\007');
      }
      /* nobreak; */
    case SPKR_OFF:
      keyb_ctrl_port61 = value & 0x0f;
      break;
    }
    break;

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

void keyb_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.handler_name = "8042 Keyboard controller";
  io_device.start_addr   = 0x0060;
  io_device.end_addr     = 0x0064;
  io_device.irq          = 1;
  port_register_handler(io_device);
}

void keyb_reset(void)
{
  microsoft_port_check = 0;
  keyb_ctrl_port61     = 0x0e;
  keyb_ctrl_port60     = -1;
  keyb_ctrl_mode60     = 0;
  keyb_ctrl_scanmap    = 1;
  keyb_ctrl_typematic  = 0x23;
  keyb_ctrl_enable     = 1;
  keyb_ctrl_isdata     = 0;
  keyb_ctrl_clearbuf();
}
