/*
   Copyright (C) 1998 Scott Dattalo

This file is part of gpsim.

gpsim is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.

gpsim 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 General Public License for more details.

You should have received a copy of the GNU General Public License
along with gpsim; see the file COPYING.  If not, write to
the Free Software Foundation, 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.  */


#include <stdio.h>
#include <iostream>
#include <iomanip>


#include "../config.h"
#include "14bit-processors.h"
#include "interface.h"

#include <string>
#include "stimuli.h"

#include "xref.h"

//#define DEBUG
#if defined(DEBUG)
#define Dprintf(arg) {printf("%s:%d-%s() ",__FILE__,__LINE__,__FUNCTION__); printf arg; }
#else
#define Dprintf(arg) {}
#endif


//--------------------------------------------------
// member functions for the TMR0 base class
//--------------------------------------------------
TMR0::TMR0(void)
{
  value.put(0);
  synchronized_cycle=0;
  future_cycle=0;
  last_cycle=0;
  state=0;      // start disabled (will change to enabled by other init code)
  prescale=1;
  m_bLastClockedState=false;
  new_name("tmr0");
}

//------------------------------------------------------------------------
// setSinkState
//
// Called when the I/O pin driving TMR0 changes states.

void TMR0::setSinkState(char new3State)
{
  bool bNewState = new3State == '1';

  if (m_bLastClockedState != bNewState) {
    m_bLastClockedState = bNewState;
    if ( verbose & 2 )
      printf("TMR0::setSinkState:%d cs:%d se:%d\n",bNewState,get_t0cs(),get_t0se());
    if (get_t0cs() && bNewState != get_t0se())
      increment();
  }
}

void TMR0::set_cpu(Processor *new_cpu, PortRegister *reg, unsigned int pin)
{
  cpu = new_cpu;
  reg->addSink(this,pin);
}

// RCP - add an alternate way to connect to a CPU
void TMR0::set_cpu(Processor *new_cpu, PinModule *pin)
{
  cpu = new_cpu;
  pin->addSink(this);
}

//------------------------------------------------------------
// Stop the tmr.
//
void TMR0::stop(void)
{

  Dprintf(("\n"));

  // If tmr0 is running, then stop it:
  if (state & 1) {

    // refresh the current value.
    get_value();

    state &= (~1);      // the timer is disabled.
    clear_trigger();

  }
}
  
void TMR0::start(int restart_value, int sync)
{

  Dprintf(("restart_value=%d sync=%d\n",restart_value, sync));

  state |= 1;          // the timer is on

  value.put(restart_value&0xff);

  old_option = cpu_pic->option_reg.value.get();

  prescale = 1 << get_prescale();
  prescale_counter = prescale;

  if(get_t0cs()) {
    Dprintf(("External clock\n"));

  } else {

    synchronized_cycle = get_cycles().value + sync;
  
    last_cycle = (restart_value % max_counts()) * prescale;
    last_cycle = synchronized_cycle - last_cycle;

    guint64 fc = last_cycle + max_counts() * prescale;


    if(future_cycle)
      get_cycles().reassign_break(future_cycle, fc, this);
    else
      get_cycles().set_break(fc, this);

    future_cycle = fc;

    Dprintf(("last_cycle:0x%llx future_cycle:0xllx\n",last_cycle,future_cycle));

  }



}

void TMR0::clear_trigger()
{
  Dprintf(("\n"));

  if (future_cycle) {
    future_cycle = 0;
    get_cycles().clear_break(this);
  }
  last_cycle = 0;

}

unsigned int TMR0::get_prescale(void)
{
  Dprintf(("\n"));

  return (cpu_pic->option_reg.get_psa() ? 0 : (1+cpu_pic->option_reg.get_prescale()));
}

void TMR0::increment(void)
{
  Dprintf(("\n"));

  if((state & 1) == 0)
    return;

  if(--prescale_counter == 0)
    {
      trace.raw(write_trace.get() | value.get());
      prescale_counter = prescale;
      if(value.get() == 255)
	{
	  //cout << "TMR0 rollover because of external clock ";
	  value.put(0);
	  set_t0if();

	}
      else
	value.put(value.get() + 1);
    }
  //  cout << "TMR0 value ="<<value.get() << '\n';
}


void TMR0::put_value(unsigned int new_value)
{
  Dprintf(("\n"));

  value.put(new_value & 0xff);

  // If tmr0 is enabled, then start it up.
  if(state & 1)
    start(new_value,2);
}

void TMR0::put(unsigned int new_value)
{
  Dprintf(("\n"));

  trace.raw(write_trace.get() | value.get());
  put_value(new_value);
}

unsigned int TMR0::get_value(void)
{
  // If the TMR0 is being read immediately after being written, then
  // it hasn't had enough time to synchronize with the PIC's clock.
  if(get_cycles().value <= synchronized_cycle)
    return value.get();

  // If we're running off the external clock or the tmr is disabled
  // then just return the register value.
  if(get_t0cs() || ((state & 1)==0))
    return(value.get());

  int new_value = (int )((get_cycles().value - last_cycle)/ prescale);

  //  if(new_value == 256) {
    // tmr0 is about to roll over. However, the user code
    // has requested the current value before the callback function
    // has been invoked. S0 just return 0.

  //    new_value = 0;

  //  }

  if (new_value > 255)
    {
      cout << "TMR0: bug TMR0 is larger than 255...\n";
      cout << "cycles.value = " << get_cycles().value <<
	"  last_cycle = " << last_cycle <<
	"  prescale = "  << prescale << 
	"  calculated value = " << new_value << '\n';

      // cop out. tmr0 has a bug. So rather than annoy
      // the user with an infinite number of messages,
      // let's just go ahead and reset the logic.
      new_value &= 0xff;
      last_cycle = new_value*prescale;
      last_cycle = get_cycles().value - last_cycle;
      synchronized_cycle = last_cycle;
    }

  value.put(new_value);
  return(value.get());
  
}

unsigned int TMR0::get(void)
{
  value.put(get_value());
  trace.raw(read_trace.get() | value.get());
  return value.get();
}
void TMR0::new_prescale(void)
{
  Dprintf(("\n"));

  unsigned int new_value;

  int option_diff = old_option ^ cpu_pic->option_reg.value.get();

  old_option ^= option_diff;   // save old option value. ( (a^b) ^b = a)

  if(option_diff & OPTION_REG::T0CS) {
    // TMR0's clock source has changed.
    if(verbose)
      cout << "T0CS has changed to ";

    if(cpu_pic->option_reg.get_t0cs()) {
      // External clock
      if(verbose)
	cout << "external clock\n";
      if (future_cycle)
      {
        future_cycle = 0;
        get_cycles().clear_break(this);
      }
    } else {
      // Internal Clock
      if(verbose)
	cout << "internal clock\n";
    }
    start(value.get());

  } else {

    if(get_t0cs() || ((state & 1)==0)) {
      prescale = 1 << get_prescale();
      prescale_counter = prescale;

    } else {

    if(last_cycle < (gint64)get_cycles().value)
	  new_value = (unsigned int)((get_cycles().value - last_cycle)/prescale);
    else
	  new_value = 0;

      if(new_value>=max_counts()) {
	cout << "TMR0 bug (new_prescale): exceeded max count"<< max_counts() <<'\n';
	cout << "   last_cycle = 0x" << hex << last_cycle << endl;
	cout << "   cpu cycle = 0x" << hex << (get_cycles().value) << endl;

	cout << "   prescale = 0x" << hex << prescale << endl;

      }


      // Get the current value of TMR0

      // cout << "cycles " << cycles.value  << " old prescale " << prescale;

      prescale = 1 << get_prescale();
      prescale_counter = prescale;

      // cout << " new prescale " << prescale;

      // Now compute the 'last_cycle' as though if TMR0 had been running on the 
      // new prescale all along. Recall, 'last_cycle' records the value of the cpu's
      // cycle counter when tmr0 last rolled over.

      last_cycle = value.get() * prescale;
      last_cycle = synchronized_cycle - last_cycle;

      // cout << " effective last_cycle " << last_cycle << '\n';

      if(get_cycles().value <= synchronized_cycle)
	last_cycle += (synchronized_cycle - get_cycles().value);

      guint64 fc = last_cycle + max_counts() * prescale;

      // cout << "moving break from " << future_cycle << " to " << fc << '\n';

      get_cycles().reassign_break(future_cycle, fc, this);

      future_cycle = fc;
    }
  }
}

bool TMR0::get_t0cs(void)
{
  return cpu_pic->option_reg.get_t0cs() != 0;
}
bool TMR0::get_t0se(void)
{
  return cpu_pic->option_reg.get_t0se() != 0;
}

void TMR0::set_t0if(void)
{
  if(cpu_pic->base_isa() == _14BIT_PROCESSOR_)
    {
      cpu14->intcon->set_t0if();
    }
}

void TMR0::new_clock_source(void)
{
  // This is handled in the new_prescale() function now
#if 0
  //  cout << "TMR0:new_clock_source changed to the ";
  if(get_t0cs())
    {
      //cout << "external\n";
      //      cycles.
    }
  else
    {
      //cout << "internal\n";
      put(value.get());    // let TMR0::put() set a cycle counter break point
    }
#endif
}

// TMR0 callback is called when the cycle counter hits the break point that
// was set in TMR0::put. The cycle counter will clear the break point, so
// we don't need to worry about it. At this point, TMR0 is rolling over.

void TMR0::callback(void)
{

  Dprintf(("0x%llx\n",cycles.value));

  if((state & 1) == 0) {

    cout << "TMR0 callback ignored because timer is disabled\n";
  }

  // If tmr0 is being clocked by the external clock, then at some point
  // the simulate code must have switched from the internal clock to
  // external clock. The cycle break point was still set, so just ignore it.
  if(get_t0cs())
    {
      future_cycle = 0;  // indicates that tmr0 no longer has a break point
      return;
    }

  value.put(0);
  synchronized_cycle = get_cycles().value;
  last_cycle = synchronized_cycle;
  future_cycle = last_cycle + max_counts()*prescale;
  get_cycles().set_break(future_cycle, this);
  set_t0if();
}

void  TMR0::reset(RESET_TYPE r)
{

  switch(r) {
  case POR_RESET:
    value = por_value;
    break;
  default:
    break;
  }


}

void TMR0::callback_print(void)
{

  cout << "TMR0\n";
}


syntax highlighted by Code2HTML, v. 0.9.1