///////////////////////////////////////////////////////////////////////
// $Id$
/////////////////////////////////////////////////////////////////////////
//
//  Copyright (C) 2001-2021  The Bochs Project
//
//  This library is free software; you can redistribute it and/or
//  modify it under the terms of the GNU Lesser General Public
//  License as published by the Free Software Foundation; either
//  version 2 of the License, or (at your option) any later version.
//
//  This library is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//  Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public
//  License along with this library; if not, write to the Free Software
//  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA

// Define BX_PLUGGABLE in files that can be compiled into plugins.  For
// platforms that require a special tag on exported symbols, BX_PLUGGABLE
// is used to know when we are exporting symbols and when we are importing.
#define BX_PLUGGABLE

#include "iodev.h"
#include "pit.h"
#include "virt_timer.h"
#include "speaker.h"


#define LOG_THIS thePit->

bx_pit_c *thePit = NULL;

PLUGIN_ENTRY_FOR_MODULE(pit)
{
  if (mode == PLUGIN_INIT) {
    thePit = new bx_pit_c();
    bx_devices.pluginPitDevice = thePit;
    BX_REGISTER_DEVICE_DEVMODEL(plugin, type, thePit, BX_PLUGIN_PIT);
  } else if (mode == PLUGIN_FINI) {
    delete thePit;
  } else if (mode == PLUGIN_PROBE) {
    return (int)PLUGTYPE_CORE;
  }
  return 0; // Success
}

//Important constant #defines:
#define USEC_PER_SECOND (1000000)
//1.193181MHz Clock
#define TICKS_PER_SECOND (1193181)


// define a macro to convert floating point numbers into 64-bit integers.
// In MSVC++ you can convert a 64-bit float into a 64-bit signed integer,
// but it will not convert a 64-bit float into a 64-bit unsigned integer.
// This macro works around that.
#define F2I(x)  ((Bit64u)(Bit64s) (x))
#define I2F(x)  ((double)(Bit64s) (x))


//USEC_ALPHA is multiplier for the past.
//USEC_ALPHA_B is 1-USEC_ALPHA, or multiplier for the present.
#define USEC_ALPHA ((double)(.8))
#define USEC_ALPHA_B ((double)(((double)1)-USEC_ALPHA))
#define USEC_ALPHA2 ((double)(.5))
#define USEC_ALPHA2_B ((double)(((double)1)-USEC_ALPHA2))
#define ALPHA_LOWER(old,new) ((Bit64u)((old<new)?((USEC_ALPHA*(I2F(old)))+(USEC_ALPHA_B*(I2F(new)))):((USEC_ALPHA2*(I2F(old)))+(USEC_ALPHA2_B*(I2F(new))))))


//PIT tick to usec conversion functions:
//Direct conversions:
#define TICKS_TO_USEC(a) (((a)*USEC_PER_SECOND)/TICKS_PER_SECOND)
#define USEC_TO_TICKS(a) (((a)*TICKS_PER_SECOND)/USEC_PER_SECOND)

bx_pit_c::bx_pit_c()
{
  put("PIT");

  /* 8254 PIT (Programmable Interval Timer) */

  s.timer_handle[1] = BX_NULL_TIMER_HANDLE;
  s.timer_handle[2] = BX_NULL_TIMER_HANDLE;
  s.timer_handle[0] = BX_NULL_TIMER_HANDLE;
}

bx_pit_c::~bx_pit_c()
{
  SIM->get_bochs_root()->remove("pit");
  BX_DEBUG(("Exit"));
}

void bx_pit_c::init(void)
{
  int clock_mode = SIM->get_param_enum(BXPN_CLOCK_SYNC)->get();
  BX_PIT_THIS is_realtime = (clock_mode == BX_CLOCK_SYNC_REALTIME) ||
      (clock_mode == BX_CLOCK_SYNC_BOTH);

  DEV_register_irq(0, "8254 PIT");
  BX_PIT_THIS s.irq_enabled = 1;
  DEV_register_ioread_handler(this, read_handler, 0x0040, "8254 PIT", 1);
  DEV_register_ioread_handler(this, read_handler, 0x0041, "8254 PIT", 1);
  DEV_register_ioread_handler(this, read_handler, 0x0042, "8254 PIT", 1);
  DEV_register_ioread_handler(this, read_handler, 0x0043, "8254 PIT", 1);
  DEV_register_ioread_handler(this, read_handler, 0x0061, "8254 PIT", 1);

  DEV_register_iowrite_handler(this, write_handler, 0x0040, "8254 PIT", 1);
  DEV_register_iowrite_handler(this, write_handler, 0x0041, "8254 PIT", 1);
  DEV_register_iowrite_handler(this, write_handler, 0x0042, "8254 PIT", 1);
  DEV_register_iowrite_handler(this, write_handler, 0x0043, "8254 PIT", 1);
  DEV_register_iowrite_handler(this, write_handler, 0x0061, "8254 PIT", 1);

  BX_DEBUG(("starting init"));

  BX_PIT_THIS s.speaker_data_on = 0;
  BX_PIT_THIS s.speaker_active = 0;
  BX_PIT_THIS s.speaker_level = 0;

  BX_PIT_THIS s.timer.init();
  BX_PIT_THIS s.timer.set_OUT_handler(0, irq_handler);
  BX_PIT_THIS s.timer.set_OUT_handler(2, speaker_handler);

  Bit64u my_time_usec = bx_virt_timer.time_usec(BX_PIT_THIS is_realtime);

  if (BX_PIT_THIS s.timer_handle[0] == BX_NULL_TIMER_HANDLE) {
    BX_PIT_THIS s.timer_handle[0] = bx_virt_timer.register_timer(this, timer_handler,
                                                                 (unsigned) 100 , 1, 1, BX_PIT_THIS is_realtime, "pit");
    if (BX_PIT_THIS is_realtime) {
      BX_INFO(("PIT using realtime synchronisation method"));
    }
  }
  BX_DEBUG(("RESETting timer."));
  bx_virt_timer.deactivate_timer(BX_PIT_THIS s.timer_handle[0]);
  BX_DEBUG(("deactivated timer."));
  if (BX_PIT_THIS s.timer.get_next_event_time()) {
    bx_virt_timer.activate_timer(BX_PIT_THIS s.timer_handle[0],
                                 (Bit32u)BX_MAX(1,TICKS_TO_USEC(BX_PIT_THIS s.timer.get_next_event_time())),
                                 0);
    BX_DEBUG(("activated timer."));
  }
  BX_PIT_THIS s.last_next_event_time = BX_PIT_THIS s.timer.get_next_event_time();
  BX_PIT_THIS s.last_usec = my_time_usec;

  BX_PIT_THIS s.total_ticks = 0;
  BX_PIT_THIS s.total_usec = 0;

  BX_DEBUG(("finished init"));

  BX_DEBUG(("s.last_usec=" FMT_LL "d",BX_PIT_THIS s.last_usec));
  BX_DEBUG(("s.timer_id=%d",BX_PIT_THIS s.timer_handle[0]));
  BX_DEBUG(("s.timer.get_next_event_time=%d", BX_PIT_THIS s.timer.get_next_event_time()));
  BX_DEBUG(("s.last_next_event_time=%d", BX_PIT_THIS s.last_next_event_time));

#if BX_DEBUGGER
  // register device for the 'info device' command (calls debug_dump())
  bx_dbg_register_debug_info("pit", this);
#endif
}

void bx_pit_c::reset(unsigned type)
{
  BX_PIT_THIS s.timer.reset(type);
}

void bx_pit_c::register_state(void)
{
  bx_list_c *list = new bx_list_c(SIM->get_bochs_root(), "pit", "8254 PIT State");
  BXRS_PARAM_BOOL(list, speaker_data_on, BX_PIT_THIS s.speaker_data_on);
  BXRS_PARAM_BOOL(list, speaker_active, BX_PIT_THIS s.speaker_active);
  BXRS_PARAM_BOOL(list, speaker_level, BX_PIT_THIS s.speaker_level);
  new bx_shadow_num_c(list, "last_usec", &BX_PIT_THIS s.last_usec);
  new bx_shadow_num_c(list, "last_next_event_time", &BX_PIT_THIS s.last_next_event_time);
  new bx_shadow_num_c(list, "total_ticks", &BX_PIT_THIS s.total_ticks);
  new bx_shadow_num_c(list, "total_usec", &BX_PIT_THIS s.total_usec);
  BXRS_PARAM_BOOL(list, irq_enabled, BX_PIT_THIS s.irq_enabled);
  bx_list_c *counter = new bx_list_c(list, "counter");
  BX_PIT_THIS s.timer.register_state(counter);
}

void bx_pit_c::after_restore_state(void)
{
  if (BX_PIT_THIS s.speaker_active && (BX_PIT_THIS s.timer.get_mode(2) == 3)) {
    Bit32u value32 = BX_PIT_THIS get_timer(2);
    if (value32 == 0) value32 = 0x10000;
    DEV_speaker_beep_on((float)(1193180.0 / value32));
  }
}

void bx_pit_c::timer_handler(void *this_ptr)
{
  bx_pit_c * class_ptr = (bx_pit_c *) this_ptr;
  class_ptr->handle_timer();
}

void bx_pit_c::handle_timer()
{
  Bit64u my_time_usec = bx_virt_timer.time_usec(BX_PIT_THIS is_realtime);
  Bit64u time_passed = my_time_usec-BX_PIT_THIS s.last_usec;
  Bit32u time_passed32 = (Bit32u)time_passed;

  BX_DEBUG(("entering timer handler"));

  if(time_passed32) {
    periodic(time_passed32);
  }
  BX_PIT_THIS s.last_usec = BX_PIT_THIS s.last_usec + time_passed;
  if (time_passed || (BX_PIT_THIS s.last_next_event_time != BX_PIT_THIS s.timer.get_next_event_time())) {
    BX_DEBUG(("RESETting timer"));
    bx_virt_timer.deactivate_timer(BX_PIT_THIS s.timer_handle[0]);
    BX_DEBUG(("deactivated timer"));
    if(BX_PIT_THIS s.timer.get_next_event_time()) {
      bx_virt_timer.activate_timer(BX_PIT_THIS s.timer_handle[0],
                                   (Bit32u)BX_MAX(1,TICKS_TO_USEC(BX_PIT_THIS s.timer.get_next_event_time())),
                                   0);
      BX_DEBUG(("activated timer"));
    }
    BX_PIT_THIS s.last_next_event_time = BX_PIT_THIS s.timer.get_next_event_time();
  }
  BX_DEBUG(("s.last_usec=" FMT_LL "d", BX_PIT_THIS s.last_usec));
  BX_DEBUG(("s.timer_id=%d", BX_PIT_THIS s.timer_handle[0]));
  BX_DEBUG(("s.timer.get_next_event_time=%x", BX_PIT_THIS s.timer.get_next_event_time()));
  BX_DEBUG(("s.last_next_event_time=%d", BX_PIT_THIS s.last_next_event_time));
}

// static IO port read callback handler
// redirects to non-static class handler to avoid virtual functions

Bit32u bx_pit_c::read_handler(void *this_ptr, Bit32u address, unsigned io_len)
{
#if !BX_USE_PIT_SMF
  bx_pit_c *class_ptr = (bx_pit_c *) this_ptr;
  return class_ptr->read(address, io_len);
}

Bit32u bx_pit_c::read(Bit32u address, unsigned io_len)
{
#else
  UNUSED(this_ptr);
#endif  // !BX_USE_PIT_SMF
  bool refresh_clock_div2;
  Bit8u value = 0;

  handle_timer();

  switch (address) {

    case 0x40: /* timer 0 - system ticks */
      value = BX_PIT_THIS s.timer.read(0);
      break;
    case 0x41: /* timer 1 read */
      value = BX_PIT_THIS s.timer.read(1);
      break;
    case 0x42: /* timer 2 read */
      value = BX_PIT_THIS s.timer.read(2);
      break;
    case 0x43: /* timer 1 read */
      value = BX_PIT_THIS s.timer.read(3);
      break;

    case 0x61:
      /* AT, port 61h */
      refresh_clock_div2 = (bool)((bx_virt_timer.time_usec(BX_PIT_THIS is_realtime) / 15) & 1);
      value = (BX_PIT_THIS s.timer.read_OUT(2)  << 5) |
              (refresh_clock_div2               << 4) |
              (BX_PIT_THIS s.speaker_data_on    << 1) |
              (BX_PIT_THIS s.timer.read_GATE(2) ? 1 : 0);
      break;

    default:
      BX_PANIC(("unsupported io read from port 0x%04x", address));
  }

  BX_DEBUG(("read from port 0x%04x, value = 0x%02x", address, value));
  return value;
}

// static IO port write callback handler
// redirects to non-static class handler to avoid virtual functions

void bx_pit_c::write_handler(void *this_ptr, Bit32u address, Bit32u dvalue, unsigned io_len)
{
#if !BX_USE_PIT_SMF
  bx_pit_c *class_ptr = (bx_pit_c *) this_ptr;
  class_ptr->write(address, dvalue, io_len);
}

void bx_pit_c::write(Bit32u address, Bit32u dvalue, unsigned io_len)
{
#else
  UNUSED(this_ptr);
#endif  // !BX_USE_PIT_SMF
  Bit8u   value;
  Bit64u my_time_usec = bx_virt_timer.time_usec(BX_PIT_THIS is_realtime);
  Bit64u time_passed = my_time_usec-BX_PIT_THIS s.last_usec;
  Bit32u value32, time_passed32 = (Bit32u)time_passed;
  bool new_speaker_active, new_speaker_level;

  if (time_passed32) {
    periodic(time_passed32);
  }
  BX_PIT_THIS s.last_usec = BX_PIT_THIS s.last_usec + time_passed;

  value = (Bit8u) dvalue;

  BX_DEBUG(("write to port 0x%04x, value = 0x%02x", address, value));

  switch (address) {
    case 0x40: /* timer 0: write count register */
      BX_PIT_THIS s.timer.write(0, value);
      break;

    case 0x41: /* timer 1: write count register */
      BX_PIT_THIS s.timer.write(1, value);
      break;

    case 0x42: /* timer 2: write count register */
      BX_PIT_THIS s.timer.write(2, value);
      if (BX_PIT_THIS s.speaker_active && (BX_PIT_THIS s.timer.get_mode(2) == 3) &&
          BX_PIT_THIS new_timer_count(2)) {
        value32 = BX_PIT_THIS get_timer(2);
        if (value32 == 0) value32 = 0x10000;
        DEV_speaker_beep_on((float)(1193180.0 / value32));
      }
      break;

    case 0x43: /* timer 0-2 mode control */
      BX_PIT_THIS s.timer.write(3, value);
      break;

    case 0x61:
      BX_PIT_THIS s.timer.set_GATE(2, value & 0x01);
      BX_PIT_THIS s.speaker_data_on = (value >> 1) & 0x01;
      new_speaker_active = ((value & 3) == 3);
      if (BX_PIT_THIS s.timer.get_mode(2) == 3) {
        if (BX_PIT_THIS s.speaker_active != new_speaker_active) {
          if (new_speaker_active) {
            value32 = BX_PIT_THIS get_timer(2);
            if (value32 == 0) value32 = 0x10000;
            DEV_speaker_beep_on((float)(1193180.0 / value32));
          } else {
            DEV_speaker_beep_off();
          }
          BX_PIT_THIS s.speaker_active = new_speaker_active;
        }
      } else {
        new_speaker_level = BX_PIT_THIS s.speaker_data_on & BX_PIT_THIS s.timer.read_OUT(2);
        if (BX_PIT_THIS s.speaker_level != new_speaker_level) {
          DEV_speaker_set_line(new_speaker_level);
          BX_PIT_THIS s.speaker_level = new_speaker_level;
        }
      }
      break;

    default:
      BX_PANIC(("unsupported io write to port 0x%04x = 0x%02x", address, value));
  }

  if (time_passed || (BX_PIT_THIS s.last_next_event_time != BX_PIT_THIS s.timer.get_next_event_time())) {
    BX_DEBUG(("RESETting timer"));
    bx_virt_timer.deactivate_timer(BX_PIT_THIS s.timer_handle[0]);
    BX_DEBUG(("deactivated timer"));
    if(BX_PIT_THIS s.timer.get_next_event_time()) {
      bx_virt_timer.activate_timer(BX_PIT_THIS s.timer_handle[0],
                                   (Bit32u)BX_MAX(1,TICKS_TO_USEC(BX_PIT_THIS s.timer.get_next_event_time())),
                                   0);
      BX_DEBUG(("activated timer"));
    }
    BX_PIT_THIS s.last_next_event_time = BX_PIT_THIS s.timer.get_next_event_time();
  }
  BX_DEBUG(("s.last_usec=" FMT_LL "d", BX_PIT_THIS s.last_usec));
  BX_DEBUG(("s.timer_id=%d", BX_PIT_THIS s.timer_handle[0]));
  BX_DEBUG(("s.timer.get_next_event_time=%x", BX_PIT_THIS s.timer.get_next_event_time()));
  BX_DEBUG(("s.last_next_event_time=%d", BX_PIT_THIS s.last_next_event_time));

}

bool bx_pit_c::periodic(Bit32u usec_delta)
{
  Bit32u ticks_delta = 0;

  BX_PIT_THIS s.total_usec += usec_delta;
  ticks_delta = (Bit32u)((USEC_TO_TICKS((Bit64u)(BX_PIT_THIS s.total_usec)))-BX_PIT_THIS s.total_ticks);
  BX_PIT_THIS s.total_ticks += ticks_delta;

  while ((BX_PIT_THIS s.total_ticks >= TICKS_PER_SECOND) && (BX_PIT_THIS s.total_usec >= USEC_PER_SECOND)) {
    BX_PIT_THIS s.total_ticks -= TICKS_PER_SECOND;
    BX_PIT_THIS s.total_usec  -= USEC_PER_SECOND;
  }

  while(ticks_delta>0) {
    Bit32u maxchange = BX_PIT_THIS s.timer.get_next_event_time();
    Bit32u timedelta = maxchange;
    if((maxchange == 0) || (maxchange>ticks_delta)) {
      timedelta = ticks_delta;
    }
    BX_PIT_THIS s.timer.clock_all(timedelta);
    ticks_delta -= timedelta;
  }

  return 0;
}

void bx_pit_c::irq_handler(bool value)
{
  if (BX_PIT_THIS s.irq_enabled) {
    if (value == 1) {
      DEV_pic_raise_irq(0);
    } else {
      DEV_pic_lower_irq(0);
    }
  }
}

void bx_pit_c::speaker_handler(bool value)
{
  if (BX_PIT_THIS s.timer.get_mode(2) != 3) {
    DEV_speaker_set_line(value & BX_PIT_THIS s.speaker_data_on);
  }
}

Bit16u bx_pit_c::get_timer(int Timer)
{
  return BX_PIT_THIS s.timer.get_inlatch(Timer);
}

Bit16u bx_pit_c::new_timer_count(int Timer)
{
  return BX_PIT_THIS s.timer.new_count_ready(Timer);
}

#if BX_DEBUGGER
void bx_pit_c::debug_dump(int argc, char **argv)
{
  Bit32u value;
  int counter = -1;

  dbg_printf("82C54 PIT\n\n");
  dbg_printf("GATE #2 = %d\n", BX_PIT_THIS s.timer.read_GATE(2));
  dbg_printf("Speaker = %d\n\n", BX_PIT_THIS s.speaker_data_on);
  if (argc == 0) {
    for (int i = 0; i < 3; i++) {
      value = BX_PIT_THIS get_timer(i);
      if (value == 0) value = 0x10000;
      dbg_printf("counter #%d: freq=%.3f, OUT=%d\n", i, (float)(1193180.0 / value),
                 BX_PIT_THIS s.timer.read_OUT(i));
    }
    dbg_printf("\nSupported options:\n");
    dbg_printf("info device 'pit' 'counter=N' - show status of counter N\n");
  } else {
    for (int arg = 0; arg < argc; arg++) {
      if (!strncmp(argv[arg], "counter=", 8) && isdigit(argv[arg][8])) {
        counter = atoi(&argv[arg][8]);
      } else {
        dbg_printf("\nUnknown option: '%s'\n", argv[arg]);
        return;
      }
    }
    if ((counter >= 0) && (counter < 3)) {
      value = BX_PIT_THIS get_timer(counter);
      if (value == 0) value = 0x10000;
      dbg_printf("counter #%d: freq=%.3f\n", counter, (float)(1193180.0 / value));
      BX_PIT_THIS s.timer.print_cnum(counter);
    } else {
      dbg_printf("\nInvalid PIT counter number: %d\n", counter);
    }
  }
}
#endif
