/*
   Copyright (C) 1998-2003 Scott Dattalo
                 2003 Mike Durian
		 2006 Roy 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 gpsim; see the file COPYING.  If not, write to
the Free Software Foundation, 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.  */

#include <assert.h>

#include <iostream>
#include <iomanip>
using namespace std;

#include <glib.h>

#include "trace.h"
#include "pic-processor.h"
#include "eeprom.h"
#include "pir.h"
#include "intcon.h"


// EEPROM - Peripheral
//
//  This object emulates the 14-bit core's EEPROM/FLASH peripheral
//  (such as the 16c84).
//
//  It's main purpose is to provide a means by which the control
//  registers may communicate.
// 

//------------------------------------------------------------------------
//
// EEPROM related registers


void EECON1::put(unsigned int new_value)
{


  trace.raw(write_trace.get() | value.get());

  new_value &= valid_bits;
  
  //cout << "EECON1::put new_value " << new_value << "  valid_bits " << valid_bits << '\n';
  if(new_value & WREN)
    {
      if(eeprom->get_reg_eecon2()->is_unarmed())
	{
          eeprom->get_reg_eecon2()->unready();
	  value.put(value.get() | WREN);

	}

      // WREN is true and EECON2 is armed (which means that we've passed through here
      // once before with WREN true). Initiate an eeprom write only if WR is true and
      // RD is false AND EECON2 is ready

      else if( (new_value & WR) && !(new_value & RD) &&
        (eeprom->get_reg_eecon2()->is_ready_for_write()))
	{
	  value.put(value.get() | WR);
	  eeprom->start_write();
	}

      //    else cout << "EECON1: write ignored " << new_value << "  (WREN is probably already set)\n";

    }
  else
    {
      // WREN is low so inhibit further eeprom writes:

      if ( ! eeprom->get_reg_eecon2()->is_writing() )
        {
          eeprom->get_reg_eecon2()->unarm();
        }
      //cout << "EECON1: write is disabled\n";

    }

  value.put((value.get() & (RD | WR)) | new_value);

  if ( (value.get() & RD) && !( value.get() & WR) )
    {
      if(value.get() & EEPGD) {
        eeprom->get_reg_eecon2()->read();
	eeprom->start_program_memory_read();
	//cout << "eestate " << eeprom->eecon2->eestate << '\n';
	// read program memory
      } else {
	//eeprom->eedata->value = eeprom->rom[eeprom->eeadr->value]->get();
        eeprom->get_reg_eecon2()->read();
	eeprom->callback();
	value.put(value.get() & ~RD);
      }
    }
  
}

unsigned int EECON1::get(void)
{

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

  return(value.get());
}

EECON1::EECON1(void)
{
  new_name("eecon1");
  valid_bits = EECON1_VALID_BITS;
}

void EECON2::put(unsigned int new_value)
{


  trace.raw(write_trace.get() | value.get());
  value.put(new_value);

  if( (eestate == EENOT_READY) && (0x55 == new_value))
    {
      eestate = EEHAVE_0x55;
    }
  else if ( (eestate == EEHAVE_0x55) && (0xaa == new_value))
    {
      eestate = EEREADY_FOR_WRITE;
    }
  else if ((eestate == EEHAVE_0x55) || (eestate == EEREADY_FOR_WRITE))
    {
      eestate = EENOT_READY;
    }


}

unsigned int EECON2::get(void)
{

  trace.raw(read_trace.get() | value.get());

  return(0);
}

EECON2::EECON2(void)
{
  new_name("eecon2");
  ee_reset();
}





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

void EEDATA::put(unsigned int new_value)
{

  trace.raw(write_trace.get() | value.get());
  value.put(new_value);

}

EEDATA::EEDATA(void)
{
  new_name("eedata");
}




unsigned int EEADR::get(void)
{

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

  return(value.get());
}

void EEADR::put(unsigned int new_value)
{

  trace.raw(write_trace.get() | value.get());
  value.put(new_value);

}


EEADR::EEADR(void)
{
  new_name("eeadr");
}


//------------------------------------------------------------------------
EEPROM_PIR::EEPROM_PIR(PIR *pPir)
  : m_pir(pPir)
{
  
}

//------------------------------------------------------------------------
// Set the EEIF and clear the WR bits. 

void EEPROM_PIR::write_is_complete(void) 
{

  assert(m_pir != 0);

  eecon1.value.put( eecon1.value.get()  & (~eecon1.WR));

  m_pir->set_eeif();
}


//----------------------------------------------------------
//
// EE PROM
//
// There are many conditions that need to be verified against a real part:
//    1) what happens if RD and WR are set at the same time?
//       > the simulator ignores both the read and the write.
//    2) what happens if a RD is initiated while data is being written?
//       > the simulator ignores the read
//    3) what happens if EEADR or EEDATA are changed while data is being written?
//       > the simulator will update these registers with the new values that
//         are written to them, however the write operation will be unaffected.
//    4) if WRERR is set, will this prevent a valid write sequence from being initiated?
//       > the simulator views WRERR as a status bit
//    5) if a RD is attempted after the eeprom has been prepared for a write
//       will this affect the RD or write?
//       > The simulator will proceed with the read and leave the write-enable state alone.
//    6) what happens if WREN goes low while a write is happening?
//       > The simulator will complete the write and WREN will be cleared.

EEPROM::EEPROM(void)
{

  rom_size = 0;
  name_str = 0;
  cpu = 0;
  intcon = 0;
}

Register *EEPROM::get_register(unsigned int address)
{

  if(address<rom_size)
    return rom[address];
  return 0;

}


void EEPROM::start_write(void)
{

  get_cycles().set_break(get_cycles().value + EPROM_WRITE_TIME, this);

  wr_adr = eeadr.value.get();
  wr_data = eedata.value.get();

  eecon2.start_write();
}

// Set the EEIF and clear the WR bits. 

void EEPROM::write_is_complete(void) 
{

  assert(intcon != 0);

  eecon1.value.put((eecon1.value.get()  & (~eecon1.WR)) | eecon1.EEIF);

  intcon->peripheral_interrupt();


}

void EEPROM::start_program_memory_read(void)
{

  cout << "ERROR: program memory flash should not be accessible\n";

  bp.halt();

}


void EEPROM::callback(void)
{

  switch(eecon2.get_eestate()) {
  case EECON2::EEREAD:
    //cout << "eeread\n";

    eecon2.unarm();
    eedata.value.put(rom[eeadr.value.get()]->get());
    eecon1.value.put(eecon1.value.get() & (~EECON1::RD));
    break;
  case EECON2::EEWRITE_IN_PROGRESS:
    //cout << "eewrite\n";

    if(wr_adr < rom_size)
      rom[wr_adr]->value.put(wr_data);
    else
      cout << "EEPROM wr_adr is out of range " << wr_adr << '\n';

    write_is_complete();

    if (eecon1.value.get() & eecon1.WREN)
      eecon2.unready();
    else
      eecon2.unarm();
    break;

    eecon1.value.put(eecon1.value.get() & (~EECON1::WR));
  default:
    cout << "EEPROM::callback() bad eeprom state " <<
      eecon2.get_eestate() << '\n';
  }
}


void EEPROM::reset(RESET_TYPE by)
{

  switch(by)
    {
    case POR_RESET:
      eecon1.value.put(0);          // eedata & eeadr are undefined at power up
      eecon2.unarm();
      break;
    default:
      break;
    }

}

void EEPROM::initialize(unsigned int new_rom_size)
{

  rom_size = new_rom_size;

  // Let the control registers have a pointer to the peripheral in
  // which they belong.

  eecon1.set_eeprom(this);
  eecon2.set_eeprom(this);
  eedata.set_eeprom(this);
  eeadr.set_eeprom(this);

  // Create the rom

  rom = (Register **) new char[sizeof (Register *) * rom_size];
  assert(rom != 0);
  
  // Initialize the rom

  char str[100];
  for (unsigned int i = 0; i < rom_size; i++)
    {

      rom[i] = new Register;
      rom[i]->address = i;
      rom[i]->value.put(0);
      rom[i]->alias_mask = 0;

      sprintf (str, "eereg0x%02x", i);
      rom[i]->new_name(str);

    }

  if(cpu) {
    cpu->ema.set_cpu(cpu);
    cpu->ema.set_Registers(rom, rom_size);
    m_UiAccessOfRom = new RegisterCollection(cpu,
					     "eeData",
					     rom,
					     rom_size);

  }

}

//----------------------------------------
// Save the current state of the eeprom. This is used to reconstitute
// the trace buffer.

void EEPROM::save_state()
{

  if(!rom || !rom_size)
    return;

  for (unsigned int i = 0; i < rom_size; i++)
    if(rom[i])
      rom[i]->put_trace_state(rom[i]->value);

}

void EEPROM::set_intcon(INTCON *ic)
{ 
  intcon = ic; 
}

void EEPROM::dump(void)
{
  unsigned int i, j, reg_num,v;

  cout << "     " << hex;

  // Column labels
  for (i = 0; i < 16; i++)
    cout << setw(2) << setfill('0') <<  i << ' ';

  cout << '\n';

  for (i = 0; i < rom_size/16; i++)
    {
      cout << setw(2) << setfill('0') <<  i << ":  ";

      for (j = 0; j < 16; j++)
	{
	  reg_num = i * 16 + j;
	  if(reg_num < rom_size)
	    {
	      v = rom[reg_num]->get_value();
	      cout << setw(2) << setfill('0') <<  v << ' ';
	    }
	  else
	    cout << "-- ";
	}
      cout << "   ";

      for (j = 0; j < 16; j++)
	{
	  reg_num = i * 16 + j;
	  if(reg_num < rom_size)
	    {
	      v = rom[reg_num]->get_value();
	      if( (v >= ' ') && (v <= 'z'))
		cout.put(v);
	      else
		cout.put('.');
	    }
	}

      cout << '\n';

    }
}




//------------------------------------------------------------------------
EEPROM_WIDE::EEPROM_WIDE(PIR *pPir)
  : EEPROM_PIR(pPir)
{
}

void EEPROM_WIDE::start_write(void)
{

  get_cycles().set_break(get_cycles().value + EPROM_WRITE_TIME, this);

  wr_adr = eeadr.value.get() + (eeadrh.value.get() << 8);
  wr_data = eedata.value.get() + (eedatah.value.get() << 8);

  eecon2.start_write();
}

void EEPROM_WIDE::start_program_memory_read(void)
{

  rd_adr = eeadr.value.get() | (eeadrh.value.get() << 8);

  get_cycles().set_break(get_cycles().value + 2, this);

}

void EEPROM_WIDE::callback(void)
{
  //cout << "eeprom call back\n";
  

  switch(eecon2.get_eestate()) {
  case EECON2::EEREAD:
    //cout << "eeread\n";

    eecon2.unarm();
    if(eecon1.value.get() & EECON1::EEPGD) {
      // read program memory
      
      int opcode = cpu->pma->get_opcode(rd_adr);
      eedata.value.put(opcode & 0xff);
      eedatah.value.put((opcode>>8) & 0xff);

    } else {
      eedata.value.put(rom[eeadr.value.get()]->get());
    }

    eecon1.value.put(eecon1.value.get() & (~EECON1::RD));
    break;

  case EECON2::EEWRITE_IN_PROGRESS:
    //cout << "eewrite\n";

    if(eecon1.value.get() & EECON1::EEPGD) // write program memory
    {
	cpu->init_program_memory_at_index(wr_adr, wr_data);
    }
    else				  // read eeprom memory
    {
        if(wr_adr < rom_size)
	{
           rom[wr_adr]->value.put(wr_data);
	}
        else
           cout << "EEPROM wr_adr is out of range " << wr_adr << '\n';
    }

    write_is_complete();

    if (eecon1.value.get() & eecon1.WREN)
      eecon2.unready();
    else
      eecon2.unarm();
    break;

  default:
    cout << "EEPROM_WIDE::callback() bad eeprom state " << eecon2.get_eestate() << '\n';
  }
}

void EEPROM_WIDE::initialize(unsigned int new_rom_size)
{

  eedatah.set_eeprom(this);
  eeadrh.set_eeprom(this);



  EEPROM::initialize(new_rom_size);


}


syntax highlighted by Code2HTML, v. 0.9.1