/*
 * DANG_BEGIN_MODULE
 *
 * Description: Programmable Interrupt Timer (PIT) emulation for DOSEMU.
 * 
 * Maintainers: J. Lawrence Stephan
 *              Scott Buchholz
 *
 * This is the timer emulation for DOSEMU.  It emulates the Programmable
 * Interval Timer (PIT), and also handles IRQ0 interrupt events.
 * A lot of animation and video game software are dependant on this module
 * for high frequency timer interrupts (IRQ0).
 *
 * 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/timers.h"
#include "base/bios.h"
#include "base/pic.h"

static emu_timer_t  timer_handle = 0;

/*
 * Compute how many usecs corresponds to this pit0_latch request
 */
static inline u_long pit_timer_usecs(Bit32u pit0_latch)
{
  double hertz = (double)PIT_TICK_RATE / (double)pit0_latch;
  h_printf("PIT: Requesting %.2g hertz\n", hertz);
  return (u_long)((double)1000000 / hertz);
}

/*
 * This function is called on every timer pop
 */
static void pit_timer_func(void)
{
  if (config.timers) {
    i_printf("PIT: periodic timer function -- pic_request PIC_IRQ0\n");
    pic_request(PIC_IRQ0);
  }
  else {
    Bit32u cur_ticks = READ_DWORD(bios_timer) + 1;
    if (cur_ticks >= 0x001b00b0) {
      cur_ticks -= 0x001b00b0;
      WRITE_BYTE(bios_timer_overflow, READ_BYTE(bios_timer_overflow)+1);
    }
    WRITE_DWORD(bios_timer, cur_ticks);
  }
}

static void pit_latch(Bit32u latch)
{
  struct timeval cur_time;
  u_long         ticks;

  /* check for special 'read latch status' mode */
  if (pit[latch].mode & 0x80) {
    pit[latch].mode = pit[latch].mode & 0x07;
    /*
     * Latch status:
     *   bit 7   = state of OUT pin (uncomputed here -- always 0)
     *   bit 6   = null count flag (1 == no cntr set, 0 == cntr available)
     *   bit 4-5 = read latch format
     *   bit 1-3 = read latch mode
     *   bit 0   = BCD flag (1 == BCD, 0 == 16-bit -- always 0)
     */
    pit[latch].read_latch = (pit[latch].read_state << 4) |
                            (pit[latch].mode << 1);
    if (pit[latch].cntr == 0xffffffff)
      pit[latch].read_latch |= 0x40;
    return;
  }

  switch (pit[latch].mode) {
    default:    /* just in case... */
    case 0x00:  /* mode 0   -- countdown, interrupt, wait*/
    case 0x01:  /* mode 1   -- countdown, wait */
    case 0x04:  /* mode 4   -- countdown, wait */
    case 0x05:  /* mode 5   -- countdown, wait */
      if (pit[latch].cntr != 0xffffffff) {
	gettimeofday(&cur_time, NULL);
	ticks = (cur_time.tv_sec - pit[latch].time.tv_sec) * PIT_TICK_RATE +
	        ((cur_time.tv_usec - pit[latch].time.tv_usec) * 1193) / 1000;

	if (ticks > pit[latch].cntr) {
	  pit[latch].cntr = 0xffffffff;
	  pit[latch].read_latch = pit[latch].write_latch;
	} else {
	  pit[latch].read_latch = pit[latch].cntr - ticks;
	}
      } else {
	pit[latch].read_latch = pit[latch].write_latch;
      }
      break;

    case 0x02:  /* mode 2,6 -- countdown, reload */
    case 0x06:
#if 1
      gettimeofday(&cur_time, NULL);
      /* fancy calculations to avoid overflow */
      ticks = (cur_time.tv_sec - pit[latch].time.tv_sec) % pit[latch].cntr;
      ticks = ticks * (PIT_TICK_RATE % pit[latch].cntr) +
	      ((cur_time.tv_usec - pit[latch].time.tv_usec) * 1193) / 1000;
#else
      ticks = pit[latch].cntr - (pic_dos_time % pit[latch].cntr);
#endif
      pit[latch].read_latch = pit[latch].cntr - ticks % pit[latch].cntr;
      break;

    case 0x03:  /* mode 3,7 -- countdown by 2, interrupt, reload */
    case 0x07:
      gettimeofday(&cur_time, NULL);
      /* fancy calculations to avoid overflow */
      ticks = (cur_time.tv_sec - pit[latch].time.tv_sec) % pit[latch].cntr;
      ticks = ticks * (PIT_TICK_RATE % pit[latch].cntr) +
	      ((cur_time.tv_usec - pit[latch].time.tv_usec) * 1193) / 1000;
      pit[latch].read_latch = pit[latch].cntr - (2*ticks) % pit[latch].cntr;
      break;
  }
#if 0 /* for debugging */
  h_printf("PIT: latched value 0x%4.4x for timer %d\n",
	   pit[latch].read_latch, latch);
#endif
}

static inline Bit8u pit_inp(Bit32u port)
{
  int ret = 0;

  if (port == 2 && config.speaker == SPKR_NATIVE) {
    return port_safe_inb(0x42);
  }

  if (port == 1)
    h_printf("PIT: someone is reading the CMOS refresh time?!?");

  if (pit[port].read_latch == 0xffffffff)
    pit_latch(port);

  switch (pit[port].read_state) {
    case 0: /* read MSB & return to state 3 */
      ret = (pit[port].read_latch >> 8) & 0xff;
      pit[port].read_state = 3;
      pit[port].read_latch = 0xffffffff;
      break;
    case 3: /* read LSB followed by MSB */
      ret = (pit[port].read_latch & 0xff);
      pit[port].read_state = 0;
      break;
    case 1: /* read MSB */
      ret = (pit[port].read_latch >> 8) & 0xff;
      pit[port].read_latch = 0xffffffff;
      break;
    case 2: /* read LSB */
      ret = (pit[port].read_latch & 0xff);
      pit[port].read_latch = 0xffffffff;
      break;
  }
#if 0
  h_printf("PIT: pit_inp(0x%x) = 0x%x\n", port+0x40, ret);
#endif
  return ret;
}

static inline void pit_outp(Bit32u port, Bit8u val)
{
  if (port == 1)
    h_printf("PIT: someone is writing the CMOS refresh time?!?");
  else if (port == 2 && config.speaker == SPKR_NATIVE) {
    port_safe_outb(0x42, val);
    return;
  }

  switch (pit[port].write_state) {
    case 0:
      pit[port].write_latch = pit[port].write_latch | ((val & 0xff) << 8);
      pit[port].write_state = 3;
      break;
    case 3:
      pit[port].write_latch = val & 0xff;
      pit[port].write_state = 0;
      break;
    case 1:
      pit[port].write_latch = val & 0xff;
      break;
    case 2:
      pit[port].write_latch = (val & 0xff) << 8;
      break;
    }
#if 0
  h_printf("PIT: pit_outp(0x%x, 0x%x)\n", port+0x40, val);
#endif

  if (pit[port].write_state != 0) {
    if (pit[port].write_latch == 0)
      pit[port].cntr = 0x10000;
    else
      pit[port].cntr = pit[port].write_latch;

    gettimeofday(&pit[port].time, NULL);

    if (port == 0) {
      timer_modify_usecs(timer_handle, pit_timer_usecs(pit[0].cntr));
    }
    else if (port == 2 && config.speaker == SPKR_EMULATED) {
      if ((keyb_ctrl_port61 & 3) == 3) {
	h_printf("PIT: Emulated beep!");
	putchar('\007');
      }
    }
  }
}

static inline void pit_ctrl_outp(Bit32u ofs, Bit8u val)
{
  int latch = (val >> 6) & 0x03;

#if 0
  h_printf("PIT: control write (0x4%d0, 0x%x)\n", (ofs)?7:3, val);
#endif

  switch (latch) {
    case 2:
      if (!ofs && config.speaker == SPKR_NATIVE) {
        port_safe_outb(0x43, val);
	break;
      }
      /* nobreak; */
    case 0:
    case 1:
      latch += ofs;
      if (latch >= PIT_TIMERS)
	break;
      if ((val & 0x30) == 0)
	pit_latch(latch);
      else {
	pit[latch].read_state  = (val >> 4) & 0x03;
	pit[latch].write_state = (val >> 4) & 0x03;
	pit[latch].mode        = (val >> 1) & 0x07;
      }
      break;
    case 3:
      /* I think this code is more or less correct */
      if ((val & 0x20) == 0) {        /* latch counts? */
	if (val & 0x02) pit_latch(ofs);
	if (!ofs) {
	  if (val & 0x04) pit_latch(1);
	  if (val & 0x08) pit_latch(2);
	}
      }
      else if ((val & 0x10) == 0) {   /* latch status words? */
	if (val & 0x02) pit[ofs].mode |= 0x80;
	if (!ofs) {
	  if (val & 0x04) pit[1].mode |= 0x80;
	  if (val & 0x08) pit[2].mode |= 0x80;
	}
      }
      break;
  }
}

static Bit8u pit_read(Bit32u port)
{
  switch (port) {
    case 0x40:
    case 0x41:
    case 0x42:
      return pit_inp(port-0x40);
    case 0x44:
      return pit_inp(3);
    case 0x43:
    case 0x47:
      return 0;
  }
  return 0;
}

static void pit_write(Bit32u port, Bit8u byte)
{
  switch (port) {
    case 0x40:
    case 0x41:
    case 0x42:
      pit_outp(port-0x40, byte);
      break;
    case 0x44:
      pit_outp(3, byte);
      break;
    case 0x43:
      pit_ctrl_outp(0, byte);
      break;
    case 0x47:
      pit_ctrl_outp(3, byte);
      break;
  }
}

/*
 * DANG_BEGIN_FUNCTION pit_init
 *
 * description:  ensure the 0x40 port timer is initially set correctly
 *
 * DANG_END_FUNCTION
 */
void pit_init(void)
{
  emu_iodev_t  io_device;

  /* 8254 PIT (Programmable Interval Timer) */
  io_device.read_portb   = pit_read;
  io_device.write_portb  = pit_write;
  io_device.read_portw   = NULL;
  io_device.write_portw  = NULL;
  io_device.handler_name = "8254 PIT";
  io_device.start_addr   = 0x0040;
  io_device.end_addr     = 0x0044;
  io_device.irq          = 0;
  port_register_handler(io_device);

  io_device.start_addr   = 0x0047;
  io_device.end_addr     = 0x0047;
  port_register_handler(io_device);
}

void pit_reset(void)
{
  struct timeval cur_time;

  gettimeofday(&cur_time, NULL);

  pit[0].mode        = 3;
  pit[0].cntr        = 0x10000;
  pit[0].time        = cur_time;
  pit[0].read_latch  = 0xffffffff;
  pit[0].write_latch = 0;
  pit[0].read_state  = 3;
  pit[0].write_state = 3;

  pit[1].mode        = 2;
  pit[1].cntr        = 18;
  pit[1].time        = cur_time;
  pit[1].read_latch  = 0xffffffff;
  pit[1].write_latch = 18;
  pit[1].read_state  = 3;
  pit[1].write_state = 3;

  pit[2].mode        = 0;
  pit[2].cntr        = 0x10000;
  pit[2].time        = cur_time;
  pit[2].read_latch  = 0xffffffff;
  pit[2].write_latch = 0;
  pit[2].read_state  = 3;
  pit[2].write_state = 3;

  pit[3].mode        = 0;
  pit[3].cntr        = 0x10000;
  pit[3].time        = cur_time;
  pit[3].read_latch  = 0xffffffff;
  pit[3].write_latch = 0;
  pit[3].read_state  = 3;
  pit[3].write_state = 3;

  timer_handle = timer_create(pit_timer_func, NULL, pit_timer_usecs(0x10000));
}

#if 0
static u_long timer_div;          /* used by timer int code */
static u_long ticks_accum;        /* For timer_tick function, 100usec ticks */

void timer_int_engine(void)
{
  static continuous = 0;

  if (ticks_accum < timer_div)
    continuous = 0;                     /* Reset overflow guard */
  else {

    /* Overflow guard so that there is no more than 50 irqs in a chain */
    continuous++;
    
    if (continuous > 50) 
      continuous = 0;                   /* Reset overflow guard */
    else {
        pic_request(PIC_IRQ0);            /* Start a timer irq */
    }

    /* Decrement the ticks accumulator */
    ticks_accum -= timer_div;
    
    /* Prevent more than 250 ms of extra timer ticks from building up.
     * This ensures that if timer_int_engine get called less frequently
     * than every 250ms, the code won't choke on an overload of timer 
     * interrupts.
     */
    if (ticks_accum > 250) ticks_accum -= 250;
  }
}
#endif
/* DANG_BEGIN_FUNCTION timer_int_engine
 *
 * This is experimental TIMER-IRQ CHAIN code!
 * This is a function to determine whether it is time to invoke a
 * new timer irq 0 event.  Normally it is 18 times a second, but
 * many video games set it to 100 times per second or more.  Since
 * the kernel cannot keep an accurate timer interrupt, the job of this
 * routine is to perform a chained timer irq 0 right after the previous
 * timer irq 0.  This routine should, ideally, be called right after
 * the end of a timer irq, if possible.
 *
 * This would speed up high frequency timer interrupts if this code
 * can be converted into an assembly macro equivalent!
 *
 * PLEASE NOTE
 *
 * This code has been replaced by interrupt scheduling code in pic.
 * The result is that we simply call pic_sched and run the dos interrupt.
 * If the new code causes no problems, I'll revise this section permanently. 
 *
 * DANG_END_FUNCTION
 */

void timer_int_engine(void)
{
  do_irq();
  pic_sched(PIC_IRQ0, pit[0].cntr);
}
