/*
   Copyright (C) 1998 T. Scott Dattalo
   Copyright (C) 2006 Roy R Rankin

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 gpasm; 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 <string>

#include "../config.h"
#include "14bit-tmrs.h"
#include "stimuli.h"
#include "p16x7x.h"

//
// 14bit-tmrs.cc
//
// Timer 1&2  modules for the 14bit core pic processors.
//
//#define DEBUG

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

//--------------------------------------------------
// CCPRL
//--------------------------------------------------

CCPRL::CCPRL()
{

  ccprh = 0;
  tmrl = 0;

}

void CCPRL::put(unsigned int new_value)
{

  trace.raw(write_trace.get() | value.get());
  //trace.register_write(address,value.get());

  value.put(new_value);

  if(tmrl && tmrl->compare_mode)
    start_compare_mode();   // Actually, re-start with new capture value.

}

void CCPRL::capture_tmr()
{

  tmrl->get_low_and_high();

  trace.raw(write_trace.get() | value.get());
  //trace.register_write(address,value.get());
  value.put(tmrl->value.get());

  trace.raw(ccprh->write_trace.get() | ccprh->value.get());
  //trace.register_write(ccprh->address,ccprh->value.get());
  ccprh->value.put(tmrl->tmrh->value.get());

  int c = value.get() + 256*ccprh->value.get();
  if(verbose & 4)
    cout << "CCPRL captured: " << c << '\n';
}

void CCPRL::start_compare_mode()
{

  tmrl->compare_mode = 1;

  int capture_value = value.get() + 256*ccprh->value.get();
  //cout << "start compare mode with capture value = " << capture_value << '\n';
  tmrl->compare_value = capture_value;
  tmrl->update();
}

void CCPRL::stop_compare_mode()
{
  // If tmr1 is in the compare mode, then change to non-compare and update
  // the tmr breakpoint.

  if(tmrl && tmrl->compare_mode)
    {
      tmrl->compare_mode = 0;
      tmrl->update();
    }
}

void CCPRL::start_pwm_mode()
{
  //cout << "CCPRL: starting pwm mode\n";

  ccprh->pwm_mode = 1;
}
void CCPRL::stop_pwm_mode()
{
  //cout << "CCPRL: stopping pwm mode\n"; 

  ccprh->pwm_mode = 0;
}

//--------------------------------------------------
// assign_tmr - assign a new timer to the capture compare module
//
// This was created for the 18f family where it's possible to dynamically
// choose which clock is captured during an event.
//
void CCPRL::assign_tmr(TMRL *ptmr)
{
  if(ptmr) {
    cout << "Reassigning CCPRL clock source\n";

    tmrl = ptmr;
  }

}

//--------------------------------------------------
// CCPRH
//--------------------------------------------------

CCPRH::CCPRH()
{

  ccprl = 0;
  pwm_mode = 0;
  pwm_value = 0;
}

// put_value allows PWM code to put data
void CCPRH::put_value(unsigned int new_value)
{
      trace.raw(write_trace.get() | value.get());
      //trace.register_write(address,value.get());

      value.put(new_value);
}
void CCPRH::put(unsigned int new_value)
{

  //cout << "CCPRH put \n";

  if(pwm_mode == 0)   // In pwm_mode, CCPRH is a read-only register.
    {
      put_value(new_value);

      if(ccprl && ccprl->tmrl && ccprl->tmrl->compare_mode)
	ccprl->start_compare_mode();   // Actually, re-start with new capture value.

    }
}

unsigned int CCPRH::get()
{
  //cout << "CCPRH get\n";

  trace.raw(read_trace.get() | value.get());
  //trace.register_read(address, read_value);
  return value.get();
}

//--------------------------------------------------
// 
//--------------------------------------------------
class CCPSignalSource : public SignalControl
{
public:
  CCPSignalSource(CCPCON *_ccp)
    : m_ccp(_ccp)
  {
    assert(m_ccp);
  }
  char getState()
  {
    return m_ccp->getState();
  }
private:
  CCPCON *m_ccp;
};

//--------------------------------------------------
//
//--------------------------------------------------

class CCPSignalSink : public SignalSink
{
public:
  CCPSignalSink(CCPCON *_ccp)
    : m_ccp(_ccp)
  {
    assert(_ccp);
  }

  void setSinkState(char new3State)
  {
    m_ccp->new_edge( new3State=='1' || new3State=='W');
  }
private:
  CCPCON *m_ccp;
};

//--------------------------------------------------
// CCPCON
//--------------------------------------------------
CCPCON::CCPCON()
  : m_PinModule(0),
    m_source(0),
    m_bInputEnabled(false),
    m_bOutputEnabled(false),
    m_cOutputState('?'),
    edges(0),
    ccprl(0), pir_set(0), tmr2(0), adcon0(0)

{
}
void CCPCON::setIOpin(PinModule *new_PinModule)
{
  Dprintf(("CCPCON::setIOpin\n"));

  m_PinModule = new_PinModule;

  m_sink = new CCPSignalSink(this);
  m_PinModule->addSink(m_sink);

  m_source = new CCPSignalSource(this);

}
void CCPCON::setCrosslinks(CCPRL *_ccprl, PIR_SET *_pir_set, TMR2 *_tmr2)
{
  ccprl = _ccprl;
  pir_set = _pir_set;
  tmr2 = _tmr2;
}
void CCPCON::setADCON(ADCON0 *_adcon0)
{
  adcon0 = _adcon0;
}
char CCPCON::getState()
{
  return m_bOutputEnabled ?  m_cOutputState : '?';
}

void CCPCON::new_edge(unsigned int level)
{
  Dprintf(("CCPCON::new_edge() level=%d\n",level));

  switch(value.get() & (CCPM3 | CCPM2 | CCPM1 | CCPM0))
    {
    case ALL_OFF0:
    case ALL_OFF1:
    case ALL_OFF2:
    case ALL_OFF3:
      Dprintf(("--CCPCON not enabled\n"));
      return;

    case CAP_FALLING_EDGE:
      if (level == 0 && ccprl) {
	ccprl->capture_tmr();
	pir_set->set_ccpif();
	Dprintf(("--CCPCON caught falling edge\n"));
      }
      break;

    case CAP_RISING_EDGE:
      if (level && ccprl) {
	ccprl->capture_tmr();
	pir_set->set_ccpif();
	Dprintf(("--CCPCON caught rising edge\n"));
      }
      break;

    case CAP_RISING_EDGE4:
      if (level && --edges <= 0) {
	ccprl->capture_tmr();
	pir_set->set_ccpif();
	edges = 4;
	Dprintf(("--CCPCON caught 4th rising edge\n"));
      }
	//else cout << "Saw rising edge, but skipped\n";
      break;


    case CAP_RISING_EDGE16:
      if (level && --edges <= 0) {
	ccprl->capture_tmr();
	pir_set->set_ccpif();
	edges = 16;
	Dprintf(("--CCPCON caught 4th rising edge\n"));
      }
      //else cout << "Saw rising edge, but skipped\n";
      break;

    case COM_SET_OUT:
    case COM_CLEAR_OUT:
    case COM_INTERRUPT:
    case COM_TRIGGER:
    case PWM0:
    case PWM1:
    case PWM2:
    case PWM3:
      //cout << "CCPCON is set up as an output\n";
      return;
    }
}

void CCPCON::compare_match()
{

  Dprintf(("CCPCON::compare_match()\n"));

  switch(value.get() & (CCPM3 | CCPM2 | CCPM1 | CCPM0))
    {
    case ALL_OFF0:
    case ALL_OFF1:
    case ALL_OFF2:
    case ALL_OFF3:
      Dprintf(("-- CCPCON not enabled\n"));
      return;

    case CAP_FALLING_EDGE:
    case CAP_RISING_EDGE:
    case CAP_RISING_EDGE4:
    case CAP_RISING_EDGE16:
      Dprintf(("-- CCPCON is programmed for capture. bug?\n"));
      break;

    case COM_SET_OUT:
      m_cOutputState = '1';
      m_PinModule->updatePinModule();
      if (pir_set)
	pir_set->set_ccpif();
      Dprintf(("-- CCPCON setting compare output to 1\n"));
      break;

    case COM_CLEAR_OUT:
      m_cOutputState = '0';
      m_PinModule->updatePinModule();
      if (pir_set)
	pir_set->set_ccpif();
      Dprintf(("-- CCPCON setting compare output to 0\n"));
      break;

    case COM_INTERRUPT:
      if (pir_set)
	pir_set->set_ccpif();
      Dprintf(("-- CCPCON setting interrupt\n"));
      break;

    case COM_TRIGGER:
      if (ccprl)
	ccprl->tmrl->clear_timer();
      if (pir_set)
	pir_set->set_ccpif();
      if(adcon0)
	adcon0->start_conversion();

      Dprintf(("-- CCPCON triggering an A/D conversion\n"));
      break;

    case PWM0:
    case PWM1:
    case PWM2:
    case PWM3:
      //cout << "CCPCON is set up as an output\n";
      return;
    }
}

void CCPCON::pwm_match(int level)
{
  Dprintf(("CCPCON::pwm_match()\n"));

  if( (value.get() & PWM0) == PWM0) {

    m_cOutputState = level ? '1' : '0';

    // if the level is 'high', then tmr2 == pr2 and the pwm cycle
    // is starting over. In which case, we need to update the duty
    // cycle by reading ccprl and the ccp X & Y and caching them
    // in ccprh's pwm slave register.

    if(level) {
      ccprl->ccprh->pwm_value = ((value.get()>>4) & 3) | 4*ccprl->value.get();
      tmr2->pwm_dc(ccprl->ccprh->pwm_value, address);
      ccprl->ccprh->put_value(ccprl->value.get());
      if (!ccprl->ccprh->pwm_value)  // if duty cycle == 0 output stays low
	m_cOutputState = '0';
    }
    m_PinModule->updatePinModule();

    //cout << "iopin should change\n";
  }  else
    cout << "not pwm mode. bug?\n";
}

void CCPCON::put(unsigned int new_value)
{

  Dprintf(("CCPCON::put() new_value=0x%x\n",new_value));
  trace.raw(write_trace.get() | value.get());

  value.put(new_value);
  if (!ccprl || !tmr2)
    return;

  bool oldbInEn  = m_bInputEnabled;
  bool oldbOutEn = m_bOutputEnabled;

  switch(value.get() & (CCPM3 | CCPM2 | CCPM1 | CCPM0))
    {
    case ALL_OFF0:
    case ALL_OFF1:
    case ALL_OFF2:
    case ALL_OFF3:
      if (ccprl)
      {
	ccprl->stop_compare_mode();
	ccprl->stop_pwm_mode();
      }
      if (tmr2)
	tmr2->stop_pwm(address);
      m_bInputEnabled = false;
      m_bOutputEnabled = false;
      break;
    case CAP_FALLING_EDGE:
    case CAP_RISING_EDGE:
      edges = 0;
      ccprl->stop_compare_mode();
      ccprl->stop_pwm_mode();
      tmr2->stop_pwm(address);
      m_bInputEnabled = true;
      m_bOutputEnabled = false;
      break;

    case CAP_RISING_EDGE4:
      edges &= 3;
      ccprl->stop_compare_mode();
      ccprl->stop_pwm_mode();
      tmr2->stop_pwm(address);
      m_bInputEnabled = true;
      m_bOutputEnabled = false;
      break;

    case CAP_RISING_EDGE16:
      ccprl->stop_compare_mode();
      ccprl->stop_pwm_mode();
      tmr2->stop_pwm(address);
      m_bInputEnabled = true;
      m_bOutputEnabled = false;
      break;

    case COM_SET_OUT:
    case COM_CLEAR_OUT:
    case COM_INTERRUPT:
    case COM_TRIGGER:
      ccprl->tmrl->ccpcon = this;
      ccprl->start_compare_mode();
      ccprl->stop_pwm_mode();
      tmr2->stop_pwm(address);

      if(adcon0)
	adcon0->start_conversion();

      m_bInputEnabled = true;
      m_bOutputEnabled = false;
      //if(adcon0) cout << "CCP triggering an A/D\n";

      break;
    case PWM0:
    case PWM1:
    case PWM2:
    case PWM3:
      ccprl->stop_compare_mode();
/* do this when TMR2 == PR2
      ccprl->start_pwm_mode();
      tmr2->pwm_dc( ccprl->ccprh->pwm_value, address);
*/
      m_bInputEnabled = false;
      m_bOutputEnabled = true;
      m_cOutputState = '0';
      break;

    }

  if (oldbOutEn != m_bOutputEnabled && m_PinModule) 
    m_PinModule->setSource(m_bOutputEnabled ? m_source : 0);

  if ((oldbInEn  != m_bInputEnabled  || 
       oldbOutEn != m_bOutputEnabled)
      && m_PinModule)
    m_PinModule->updatePinModule();

}

//--------------------------------------------------
// T1CON
//--------------------------------------------------
T1CON::T1CON()
  : tmrl(0)
{

  new_name("T1CON");

}

void T1CON::put(unsigned int new_value)
{

  trace.raw(write_trace.get() | value.get());
  //trace.register_write(address,value.get());

  unsigned int diff = value.get() ^ new_value;
  value.put(new_value);
  if (!tmrl)
    return;
  // First, check the tmr1 clock source bit to see if we are  changing from
  // internal to external (or vice versa) clocks.
  if( diff & TMR1CS)
    tmrl->new_clock_source();

  if( diff & TMR1ON)
    tmrl->on_or_off(value.get() & TMR1ON);
  else  if( diff & (T1CKPS0 | T1CKPS1))
    tmrl->update();

}

unsigned int T1CON::get()
{
  trace.raw(read_trace.get() | value.get());
  //trace.register_read(address,value.get());
  return(value.get());
}

unsigned int T1CON::get_prescale()
{
  return( ((value.get() &(T1CKPS0 | T1CKPS1)) >> 4) + cpu_pic->pll_factor);
}



//--------------------------------------------------
// member functions for the TMRH base class
//--------------------------------------------------
TMRH::TMRH()
  : tmrl(0)
{

  value.put(0);
  new_name("TMRH");

}

void TMRH::put(unsigned int new_value)
{

  trace.raw(write_trace.get() | value.get());
  //trace.register_write(address,value.get());
  value.put(new_value & 0xff);
  if(!tmrl)
    return;
  tmrl->synchronized_cycle = get_cycles().value;
  tmrl->last_cycle = tmrl->synchronized_cycle - (tmrl->value.get() + (value.get()<<8))*tmrl->prescale;

  if(tmrl->t1con->get_tmr1on())
    tmrl->update();

}

unsigned int TMRH::get()
{

  trace.raw(read_trace.get() | value.get());
  return get_value();
}

// For the gui and CLI
unsigned int TMRH::get_value()
{
  // If the TMR1 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 <= tmrl->synchronized_cycle)
    return value.get();

  // If he TMR is not running then return.
  if(!tmrl->t1con->get_tmr1on())
    return value.get();

  tmrl->current_value();

  value.put(((tmrl->value_16bit)>>8) & 0xff);

  return(value.get());
  
}


//--------------------------------------------------
// member functions for the TMRL base class
//--------------------------------------------------
TMRL::TMRL()
  : m_cState('?'), m_bExtClkEnabled(false), m_Interrupt(0)
{

  value.put(0);
  synchronized_cycle=0;
  prescale_counter=prescale=1;
  break_value = 0x10000;
  compare_value = 0;
  compare_mode = 0;
  last_cycle = 0;

  tmrh    = 0;
  t1con   = 0;
  ccpcon  = 0;
  new_name("TMRL");

}

void TMRL::setIOpin(PinModule *extClkSource)
{
  Dprintf(("TMRL::setIOpin\n"));

  if (extClkSource)
    extClkSource->addSink(this);
}

void TMRL::setSinkState(char new3State)
{
  if (new3State != m_cState) {
    m_cState = new3State;

    if (m_bExtClkEnabled && (m_cState == '1' || m_cState == 'W'))
      increment();
  }
}

//------------------------------------------------------------
// setInterruptSource()
//
// This Timer can be an interrupt source. When the interrupt
// needs to be generated, then the InterruptSource object will
// direct the interrupt to where it needs to go (usually this
// is the Peripheral Interrupt Register).

void TMRL::setInterruptSource(InterruptSource *_int)
{
  m_Interrupt = _int;
}

void TMRL::increment()
{
  Dprintf(("TMRL increment because of external clock\n"));

  if(--prescale_counter == 0) {
    prescale_counter = prescale;

    // If TMRH/TMRL have been manually changed, we'll want to 
    // get the up-to-date values;

    trace.raw(write_trace.get() | value.get());
    //trace.register_write(address,value.get());
    current_value();

    value_16bit = 0xffff & ( value_16bit + 1);

    tmrh->value.put((value_16bit >> 8) & 0xff);
    value.put(value_16bit & 0xff);
    if(value_16bit == 0 && m_Interrupt)
      m_Interrupt->Trigger();
  }

}

void TMRL::on_or_off(int new_state)
{

  if(new_state) {

    Dprintf(("TMR1 is being turned on\n"));

    // turn on the timer

    // Effective last cycle
    // Compute the "effective last cycle", i.e. the cycle
    // at which TMR1 was last 0 had it always been counting.

    last_cycle = get_cycles().value - value_16bit*prescale;
    update();
  }
  else {

    Dprintf(("TMR1 is being turned off\n"));

    // turn off the timer and save the current value
    current_value();
    value.put(value_16bit & 0xff);
    tmrh->value.put((value_16bit>>8) & 0xff);
  }

}
//
// If anything has changed to affect when the next TMR1 break point
// will occur, this routine will make sure the break point is moved
// correctly.
//

void TMRL::update()
{

  Dprintf(("TMR1 update 0x%llx\n",get_cycles().value));

  if(t1con->get_tmr1on()) {

    if(t1con->get_tmr1cs()) {
      cout << "TMRl::update - external clock is not fully implemented\n";
    } else {
	
      if(verbose & 0x4)
	cout << "Internal clock\n";

      current_value();


      // Note, unlike TMR0, anytime something is written to TMRL, the 
      // prescaler is unaffected.

      prescale = 1 << t1con->get_prescale();
      prescale_counter = prescale;

      //cout << "TMRL: Current prescale " << prescale << '\n';
      //  synchronized_cycle = cycles.value + 2;
      synchronized_cycle = get_cycles().value;

      last_cycle = synchronized_cycle - value_16bit * prescale;

      break_value = 0x10000;  // Assume that a rollover will happen first.

      if(compare_mode)
	{
	  //cout << "compare mode. compare_value = " << compare_value << '\n';
	  if(compare_value > value_16bit)
	    {
	      // A compare interrupt is going to happen before the timer
	      // will rollover.
	      break_value = compare_value - value_16bit;
	    }
	}

      guint64 fc = get_cycles().value + (break_value - value_16bit) * prescale;

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

      //cout << "TMR1: update; new break cycle = " << fc << '\n';
      future_cycle = fc;
    }
    }
  else
    {
      //cout << "TMR1: not running\n";
    }
}

void TMRL::put(unsigned int new_value)
{

  trace.raw(write_trace.get() | value.get());
  //trace.register_write(address,value.get());
  value.put(new_value & 0xff);

  if (!tmrh || !t1con)
    return;

  synchronized_cycle = get_cycles().value;
  last_cycle = synchronized_cycle - ( value.get() + (tmrh->value.get()<<8)) * prescale;

  if(t1con->get_tmr1on())
    update();

}

unsigned int TMRL::get()
{
  trace.raw(read_trace.get() | value.get());
  return get_value();
}

// For the gui and CLI
unsigned int TMRL::get_value()
{
  // If the TMRL 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 TMRL is not on, then return the current value
  if(!t1con->get_tmr1on())
    return value.get();

  current_value();

  value.put(value_16bit & 0xff);

  return(value.get());
}

//%%%FIXME%%% inline this
void TMRL::current_value()
{
  if(t1con->get_tmr1cs())
    value_16bit = tmrh->value.get() * 256 + value.get();
  else
    value_16bit = (unsigned int)((get_cycles().value - last_cycle)/ prescale) & 0xffff;
}

unsigned int TMRL::get_low_and_high()
{

  // If the TMRL 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();

  //  value_16bit = (cycles.value.lo - last_cycle)/ prescale;

  current_value();

  value.put(value_16bit & 0xff);
  trace.raw(read_trace.get() | value.get());
  //trace.register_read(address, value.get());

  tmrh->value.put((value_16bit>>8) & 0xff);
  trace.raw(tmrh->read_trace.get() | tmrh->value.get());
  //trace.register_read(tmrh->address, tmrh->value.get());

  return(value_16bit);
  
}

void TMRL::new_clock_source()
{

  if(t1con->get_tmr1cs())
    m_bExtClkEnabled = true;
  else {
    m_bExtClkEnabled = false;
    put(value.get());    // let TMRL::put() set a cycle counter break point
  }
}

//
// clear_timer - This is called by either the CCP or PWM modules to 
// reset the timer to zero. This is rather easy since the current TMR
// value is always referenced to the cpu cycle counter. 
//

void TMRL::clear_timer()
{

  last_cycle = get_cycles().value;
  //cout << "TMR1 has been cleared\n";
}

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

void TMRL::callback()
{

  if(verbose & 4)
    cout << "TMRL::callback\n";

  // If TMRL 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(t1con->get_tmr1cs())
    {
      future_cycle = 0;  // indicates that TMRL no longer has a break point
      return;
    }

  future_cycle = 0;     // indicate that there's no break currently set
  //cout << "in tmrl callback break_value = " << break_value << '\n';

  if(break_value < 0x10000)
    {

      // The break was due to a "compare"

      //cout << "TMR1 break due to compare "  << hex << cycles.value.lo << '\n';
      ccpcon->compare_match();

    }
  else
    {

      // The break was due to a roll-over

      //cout<<"TMRL rollover: " << hex << cycles.value.lo << '\n';
      if (m_Interrupt)
	m_Interrupt->Trigger();

      // Reset the timer to 0.

      synchronized_cycle = get_cycles().value;
      last_cycle = synchronized_cycle;

    }

  update();

}

//---------------------------

void TMRL::callback_print()
{
  cout << "TMRL " << name() << " CallBack ID " << CallBackID << '\n';

}


//--------------------------------------------------
// member functions for the PR2 base class
//--------------------------------------------------

PR2::PR2()
  :  tmr2(0)
{

  new_name("PR2");

}

void PR2::put(unsigned int new_value)
{

  trace.raw(write_trace.get() | value.get());
  //trace.register_write(address,value.get());

  if(value.get() != new_value)
    {
      if (tmr2)
	tmr2->new_pr2(new_value);
      value.put(new_value);
    }
  else
    value.put(new_value);

}

//--------------------------------------------------
// member functions for the T2CON base class
//--------------------------------------------------

T2CON::T2CON()
  : tmr2(0)
{

  new_name("T2CON");

}

void T2CON::put(unsigned int new_value)
{
  trace.raw(write_trace.get() | value.get());

  unsigned int diff = value.get() ^ new_value;
  value.put(new_value);


  if (tmr2) {
    
    tmr2->new_pre_post_scale();

    if( diff & TMR2ON)
      tmr2->on_or_off(value.get() & TMR2ON);

  }
}



//--------------------------------------------------
// member functions for the TMR2 base class
//--------------------------------------------------
TMR2::TMR2()
  : pr2(0), pir_set(0), t2con(0), ccp1con(0), ccp2con(0)
{
  update_state = TMR2_PWM1_UPDATE | TMR2_PWM2_UPDATE | TMR2_PR2_UPDATE;
  pwm_mode = 0;
  value.put(0);
  future_cycle = 0;
  prescale=1;
  new_name("TMR2");
}

void TMR2::callback_print() 
{
  cout << "TMR2 " << name() << " CallBack ID " << CallBackID << '\n';
}

void TMR2::start()
{

  value.put(0);
  prescale = 0;
  last_cycle = 0;
  future_cycle = 0;

}


void TMR2::on_or_off(int new_state)
{

  if(new_state) {

    Dprintf(("TMR2 is being turned on\n"));

    // turn on the timer

    // Effective last cycle
    // Compute the "effective last cycle", i.e. the cycle
    // at which TMR2 was last 0 had it always been counting.

    last_cycle = get_cycles().value - value.get()*prescale;
    update();
  }
  else {

    Dprintf(("TMR2 is being turned off\n"));

    // turn off the timer and save the current value
    current_value();
  }

}

//
// pwm_dc - 
//
//  

void TMR2::pwm_dc(unsigned int dc, unsigned int ccp_address)
{

  //cout << "TMR2_PWM constants pwm1 " << TMR2_PWM1_UPDATE << " pwm2 " << TMR2_PWM2_UPDATE << '\n';
  if(ccp_address == ccp1con->address)
    {
      //cout << "TMR2:  pwm mode with ccp1. duty cycle = " << hex << dc << '\n';

      duty_cycle1 = dc;

      // Update the cycle break if this is the first time to go into pwm mode
      if( (pwm_mode & TMR2_PWM1_UPDATE) == 0)
	{
	  pwm_mode |= TMR2_PWM1_UPDATE;
//wait for next TMR2 update	  update();	
	}
    }
  else if(ccp_address == ccp2con->address)
    {
      //cout << "TMR2: starting pwm mode with ccp2. duty cycle = " << hex << dc << '\n';

      duty_cycle2 = dc;

      // Update the cycle break if this is the first time to go into pwm mode
      if( (pwm_mode & TMR2_PWM2_UPDATE) == 0)
	{
	  pwm_mode |= TMR2_PWM2_UPDATE;

//wait for next TMR2 update	  update();	
	}
    }
  else
    {
      cout << "TMR2: error bad ccpxcon address while in pwm_dc()\n";
      cout << "ccp_address = " << ccp_address << " expected 1con " << 
	ccp1con->address << " or 2con " << ccp2con->address << '\n';
    }

}

//
// stop_pwm
//

void TMR2::stop_pwm(unsigned int ccp_address)
{

  int old_pwm = pwm_mode;

  if(ccp_address == ccp1con->address)
    {
      // cout << "TMR2:  stopping pwm mode with ccp1.\n";
      pwm_mode &= ~TMR2_PWM1_UPDATE;
      if(last_update & TMR2_PWM1_UPDATE)
         update_state &= (~TMR2_PWM1_UPDATE);

    }
  else if(ccp_address == ccp2con->address)
    {
      // cout << "TMR2:  stopping pwm mode with ccp2.\n";
      pwm_mode &= ~TMR2_PWM2_UPDATE;
      if(last_update & TMR2_PWM2_UPDATE)
         update_state &= (~TMR2_PWM2_UPDATE);
    }

  if((pwm_mode ^ old_pwm) && future_cycle && t2con->get_tmr2on())
    update(update_state);

}

//
// update 
//  This member function will determine if/when there is a TMR2 break point
// that needs to be set and will set/move it if so.
//  There are three different break sources: 1) TMR2 matching PR2 2) TMR2 matching
// the ccp1 registers in pwm mode and 3) TMR2 matching the ccp2 registers in pwm
// mode.
//

void TMR2::update(int ut)
{

  //cout << "TMR2 update. cpu cycle " << hex << cycles.value <<'\n';

  if(t2con->get_tmr2on()) {

    if(future_cycle) {

      // If TMR2 is enabled (i.e. counting) then 'future_cycle' is non-zero,
      // which means there's a cycle break point set on TMR2 that needs to
      // be moved to a new cycle.

      current_value();

      // Assume that we are not in pwm mode (and hence the next break will
      // be due to tmr2 matching pr2)

      break_value = 1 + pr2->value.get();
      guint64 fc = get_cycles().value + (break_value - value.get()) * prescale;

      last_update = TMR2_PR2_UPDATE;

      if (pwm_mode)
      {
          break_value <<= 2;    // now pwm value
          fc = last_cycle + break_value * prescale;
      }

      if(pwm_mode & ut & TMR2_PWM1_UPDATE) {

	// We are in pwm mode... So let's see what happens first: a pr2 compare
	// or a duty cycle compare. (recall, the duty cycle is really 10-bits)

	if( (duty_cycle1 > (value.get()*4) ) && (duty_cycle1 < break_value))
	  {
	    //cout << "TMR2:PWM1 update\n";
	    last_update = TMR2_PWM1_UPDATE;
            fc = last_cycle + duty_cycle1 * prescale;
	  }
      }

      if(pwm_mode & ut & TMR2_PWM2_UPDATE)
	{
	  // We are in pwm mode... So let's see what happens first: a pr2 compare
	  // or a duty cycle compare. (recall, the duty cycle is really 10-bits)

	  if( (duty_cycle2 > (value.get()*4) ) && (duty_cycle2 < break_value))
	  {
	      //cout << "TMR2:PWM2 update\n";
                                                                                
            if (last_update == TMR2_PWM1_UPDATE)
            {
                // set break for first duty cycle change
                if (duty_cycle2 < duty_cycle1)
                {
                    fc = last_cycle + duty_cycle2 * prescale;
                    last_update = TMR2_PWM2_UPDATE;
                }
                else if (duty_cycle2 == duty_cycle1)
                    last_update |= TMR2_PWM2_UPDATE;
            }
            else
            {
                last_update = TMR2_PWM2_UPDATE;
                fc = last_cycle + duty_cycle2 * prescale;
            }
                                                                                
	}
      }

      if(fc < future_cycle)
        cout << "TMR2: update note: new breakpoint=" << hex << fc <<
           " before old breakpoint " << future_cycle << endl;

      if (fc != future_cycle)
      {
          // cout << "TMR2: update new break at cycle "<<hex<<fc<<'\n';
          get_cycles().reassign_break(future_cycle, fc, this);
          future_cycle = fc;
      }

    }
    else
      {
	cout << "TMR2 BUG!! tmr2 is on but has no cycle_break set on it\n";
      }

  }
  else
    {
      //cout << "TMR2 is not running (no update occurred)\n";
    }

}
void TMR2::put(unsigned int new_value)
{
  current_value();

  unsigned int old_value = value.get();


  trace.raw(write_trace.get() | value.get());
  //trace.register_write(address,value.get());

  value.put(new_value & 0xff);

  if(future_cycle)
    {
      // If TMR2 is enabled (i.e. counting) then 'future_cycle' is non-zero,
      // which means there's a cycle break point set on TMR2 that needs to
      // be moved to a new cycle.

      guint64 current_cycle = get_cycles().value;
      unsigned int delta = (future_cycle - last_cycle);
      int shift = (new_value - old_value) * prescale;

      if (pwm_mode)
	shift <<= 2;
  
      // set cycle when tmr2 would have been zero

      last_cycle = current_cycle - shift;
      unsigned int now = (current_cycle - last_cycle);
      

      guint64 fc;

      /*
	Three possible cases
	   1> TMR2 is still before the next break point.
		Adjust the breakpoint to ocurr at correct TMR2 value
	   2> TMR2 is now greater the PR2
		Assume TMR2 must count up to 0xff, roll-over and then
		we are back in business. High CCP outputs stay high.
	   3> TMR2 is now less than PR2 but greater than a CCP duty cycle point.
		The CCP output stays as the duty cycle comparator does not 
		match on this cycle.
      */

      if (now < delta) // easy case, just shift break.
      {
          fc = last_cycle + delta;
          get_cycles().reassign_break(future_cycle, fc, this);
          future_cycle = fc;
      }
      else if (now >= break_value * prescale)  // TMR2 now greater than PR2
      {
        // set break to when TMR2 will overflow
	last_update |= TMR2_WRAP;
	if (pwm_mode)
	    fc = last_cycle + (0x100 * prescale << 2);
	else
	    fc = last_cycle + 0x100 * prescale;
        get_cycles().reassign_break(future_cycle, fc, this);
        future_cycle = fc;
      }
      else	// new break < PR2 but > duty cycle break
      {
          update(update_state);
      }



     /* 
	'clear' the post scale counter. (I've actually implemented the 
	post scale counter as a count-down counter, so 'clearing it' 
	means resetting it to the starting point.
     */
      if (t2con)
	post_scale = t2con->get_post_scale();
    }
}

unsigned int TMR2::get()
{

  if(t2con->get_tmr2on())
    {
      current_value();
    }

  trace.raw(read_trace.get() | value.get());
  // trace.register_read(address, value.get());
  return(value.get());
  
}

unsigned int TMR2::get_value()
{

  if(t2con->get_tmr2on())
    {
      current_value();
    }

  return(value.get());
  
}
void TMR2::new_pre_post_scale()
{

  //cout << "T2CON was written to, so update TMR2 " << t2con->get_tmr2on() << "\n";

  if(!t2con->get_tmr2on()) {
    // TMR2 is not on. If has just been turned off, clear the callback breakpoint.

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

  unsigned int old_prescale = prescale;
  prescale = t2con->get_pre_scale();
  post_scale = t2con->get_post_scale();

  if(future_cycle)
    {
      // If TMR2 is enabled (i.e. counting) then 'future_cycle' is non-zero,
      // which means there's a cycle break point set on TMR2 that needs to
      // be moved to a new cycle.

      // Get the current value of TMR2
      ///value = (cycles.value - last_cycle)/prescale;

      current_value();

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


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


      if (prescale != old_prescale)	// prescaler value change
      {
	// togo is number of cycles to next callback based on new prescaler.
	guint64 togo = (future_cycle - get_cycles().value) * prescale / old_prescale;

	if (!togo)	// I am not sure this can happen RRR
	    callback();
	else
	{
	   guint64 fc = togo + get_cycles().value;

          get_cycles().reassign_break(future_cycle, fc, this);
          future_cycle = fc;
	}
      }
    }
  else
    {
      //cout << "TMR2 was off, but now it's on.\n";

      if (value.get() == pr2->value.get()) // TMR2 == PR2
      {
        future_cycle = get_cycles().value;
        get_cycles().set_break(future_cycle, this);
	callback();
      }
      else if (value.get() > pr2->value.get()) // TMR2 > PR2
      {
	cout << "Warning TMR2 turned on with TMR2 greater than PR2\n";
	// this will cause TMR2 to wrap
        future_cycle  = get_cycles().value + 
		(1 + pr2->value.get() + (0x100 -  value.get())) * prescale;
        get_cycles().set_break(future_cycle, this);
      }
      else
      {
          future_cycle = get_cycles().value + 1;
          get_cycles().set_break(future_cycle, this);
	  last_cycle = get_cycles().value - value.get();
          update(update_state);
      }
  }

}

void TMR2::new_pr2(unsigned int new_value)
{

  if(t2con->get_tmr2on())
    {

      unsigned int cur_break = (future_cycle - last_cycle)/prescale;
      unsigned int new_break = (pwm_mode)? (1 + new_value) << 2 : 1 + new_value;
      unsigned int now_cycle = (get_cycles().value - last_cycle) / prescale;

      guint64 fc = last_cycle;

      /*
	PR2 change casses

	1> TMR2 greater than new PR2
		TMR2 wraps through 0xff
	2> New PR2 breakpoint less than current breakpoint or
	   current break point due to PR2
		Change breakpoint to new value based on PR2
	3> Other breakpoint < PR2 breakpoint
		No need to do anything.
     */


      if (now_cycle > new_break)	// TMR2 > PR2 do wrap
      {
        // set break to when TMR2 will overflow
	last_update |= TMR2_WRAP;
	if (pwm_mode)
	    fc += (0x100 * prescale << 2);
	else
	    fc += 0x100 * prescale;
        get_cycles().reassign_break(future_cycle, fc, this);
        future_cycle = fc;
      }
      else if (cur_break == break_value ||	// breakpoint due to pr2
	       new_break < cur_break)		// new break less than current
      {
	fc += new_break * prescale;
        get_cycles().reassign_break(future_cycle, fc, this);
        future_cycle = fc;
      }
    }
}

void TMR2::current_value()
{
  unsigned int tmr2_val = (get_cycles().value - last_cycle)/ prescale;

  if (pwm_mode)
       tmr2_val >>= 2;

  value.put(tmr2_val & 0xff);

  if(tmr2_val > 0x100)	// Can get to 0x100 during transition
   cout << "TMR2 BUG!! value = " << value.get() << " which is greater than 0xff\n";
}

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

void TMR2::callback()
{

  //cout<<"TMR2 callback cycle: " << hex << cycles.value << '\n';

  // If tmr2 is still enabled, then set up for the next break. 
  // If tmr2 was disabled then ignore this break point.
  if(t2con->get_tmr2on())
    {

      // What caused the callback: PR2 match or duty cyle match ?

      if (last_update & TMR2_WRAP) // TMR2 > PR2 
      {
	last_update &= ~TMR2_WRAP;
	// This (implicitly) resets the timer to zero:
	last_cycle = get_cycles().value;
      }
      else if( last_update & (TMR2_PWM1_UPDATE | TMR2_PWM2_UPDATE))
      {

        if(last_update & TMR2_PWM1_UPDATE)
	{
	  // duty cycle match
	  //cout << "TMR2: duty cycle match for pwm1 \n";
	  update_state &= (~TMR2_PWM1_UPDATE);
	  ccp1con->pwm_match(0);
	}
        if(last_update & TMR2_PWM2_UPDATE)
	{
	  // duty cycle match
	  //cout << "TMR2: duty cycle match for pwm2 \n";
	  update_state &= (~TMR2_PWM2_UPDATE);
	  ccp2con->pwm_match(0);
	}
      }
      else
	{
	  // matches PR2

	  //cout << "TMR2: PR2 match. pwm_mode is " << pwm_mode <<'\n';

	  // This (implicitly) resets the timer to zero:
	  last_cycle = get_cycles().value;

         if (ssp_module)
               ssp_module->tmr2_clock();

          if ((ccp1con->value.get() & CCPCON::PWM0) == CCPCON::PWM0)
               ccp1con->pwm_match(1);

          if ((ccp2con->value.get() & CCPCON::PWM0) == CCPCON::PWM0)
               ccp2con->pwm_match(1);

	  if(--post_scale < 0)
	    {
	      //cout << "setting IF\n";
	      pir_set->set_tmr2if();
	      post_scale = t2con->get_post_scale();
	    }

	  update_state = TMR2_PWM1_UPDATE | TMR2_PWM2_UPDATE | TMR2_PR2_UPDATE;

	}
      update(update_state);

    }
  else
    future_cycle = 0;
}


//------------------------------------------------------------------------
// TMR2_MODULE
//
// 

TMR2_MODULE::TMR2_MODULE()
{

  t2con = 0;
  pr2   = 0;
  tmr2  = 0;
  cpu   = 0;
  name_str = 0;

}

void TMR2_MODULE::initialize(T2CON *t2con_, PR2 *pr2_, TMR2  *tmr2_)
{

  t2con = t2con_;
  pr2   = pr2_;
  tmr2  = tmr2_;

}


syntax highlighted by Code2HTML, v. 0.9.1