/*
Copyright (C) 1998-2003 Scott Dattalo
2004 Rob Pearce
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 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 "stimuli.h"
#include "i2c-ee.h"
#include "registers.h"
//#define DEBUG
#if defined(DEBUG)
#include "../config.h"
#define Dprintf(arg) {printf("%s:%d ",__FILE__,__LINE__); printf arg; }
#else
#define Dprintf(arg) {}
#endif
#define Vprintf(arg) { if (verbose) {printf("%s:%d ",__FILE__,__LINE__); printf arg;} }
// I2C EEPROM Peripheral
//
// This object emulates the I2C EEPROM peripheral on the 12CE51x
//
// It's main purpose is to provide a means by which the port pins
// may communicate.
//
//--------------------------------------------------------------
//
//
class I2C_EE_PIN : public IO_open_collector
{
public:
I2C_EE *eeprom;
I2C_EE_PIN (I2C_EE *_eeprom, char *_name)
: IO_open_collector(_name)
{
eeprom = _eeprom;
bDrivingState = true;
bDrivenState = true;
// Make the pin an output.
update_direction(IO_bi_directional::DIR_OUTPUT,true);
};
//
void setDrivingState(bool new_state) {
bDrivingState = new_state;
bDrivenState = new_state;
if(snode)
snode->update();
}
};
class I2C_EE_SDA : public I2C_EE_PIN
{
public:
I2C_EE_SDA (I2C_EE *_eeprom, char *_name)
: I2C_EE_PIN (_eeprom,_name)
{
}
void setDrivenState(bool new_dstate) {
bool diff = new_dstate ^ bDrivenState;
Dprintf(("eeprom sda setDrivenState %d\n", new_dstate));
if( eeprom && diff ) {
bDrivenState = new_dstate;
eeprom->new_sda_edge(new_dstate);
}
}
};
class I2C_EE_SCL : public I2C_EE_PIN
{
public:
I2C_EE_SCL (I2C_EE *_eeprom, char *_name)
: I2C_EE_PIN (_eeprom,_name)
{
}
void setDrivenState(bool new_state) {
bool diff = new_state ^ bDrivenState;
Dprintf(("eeprom scl setDrivenState %d\n", new_state));
if( eeprom && diff ) {
bDrivenState = new_state;
eeprom->new_scl_edge(bDrivenState);
}
}
};
//----------------------------------------------------------
//
// I2C EE PROM
//
// There are many conditions that need to be verified against a real part:
// 1) what happens if
// > the simulator
// 2) what happens if a RD is initiated while data is being written?
// > the simulator ignores the read
// 3) what happens if
// > the simulator
I2C_EE::I2C_EE(unsigned int _rom_size, unsigned int _write_page_size,
unsigned int _addr_bytes, unsigned int _CSmask,
unsigned int _BSmask, unsigned int _BSshift)
: rom(0),
rom_size(_rom_size), // size of eeprom in bytes
xfr_addr(0), xfr_data(0),
write_page_off(0),
write_page_size(_write_page_size), // Page size for writes
bit_count(0), m_command(0),
m_chipselect(0),
m_CSmask(_CSmask), // mask for chip select in command
m_BSmask(_BSmask), // mask for bank select in command
m_BSshift(_BSshift), // right shift bank select to bit 0
m_addr_bytes(_addr_bytes), // number of address bytes
m_write_protect(false),
ee_busy(false),
bus_state(IDLE)
{
// 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, "ee0x%02x", i);
rom[i]->new_name(str);
}
scl = new I2C_EE_SCL ( this, "SCL" );
sda = new I2C_EE_SDA ( this, "SDA" );
}
I2C_EE::~I2C_EE()
{
delete rom;
}
// Bit 0 is write protect, 1-3 is A0 - A2
void I2C_EE::set_chipselect(unsigned int _cs)
{
m_write_protect = (_cs & 1) == 1;
m_chipselect = (_cs & m_CSmask);
}
void I2C_EE::debug()
{
if (!scl || !sda || !rom)
return;
const char *cPBusState=0;
switch (bus_state) {
case IDLE:
cPBusState = "IDLE";
break;
case START:
cPBusState = "START";
break;
case RX_CMD:
cPBusState = "RX_CMD";
break;
case ACK_CMD:
cPBusState = "ACK_CMD";
break;
case RX_ADDR:
cPBusState = "RX_ADDR";
break;
case ACK_ADDR:
cPBusState = "ACK_ADDR";
break;
case RX_DATA:
cPBusState = "RX_DATA";
break;
case ACK_WR:
cPBusState = "ACK_WR";
break;
case WRPEND:
cPBusState = "WRPEND";
break;
case ACK_RD:
cPBusState = "ACK_RD";
break;
case TX_DATA:
cPBusState = "TX_DATA";
break;
}
cout << "I2C EEPROM: current state="<<cPBusState<<endl;
cout << " t=0x"<< hex <<get_cycles().value << endl;
cout << " scl drivenState=" << scl->getDrivenState()
<< " drivingState=" << scl->getDrivingState()
<< " direction=" << ((scl->get_direction()==IOPIN::DIR_INPUT) ?"IN":"OUT")
<< endl;
cout << " sda drivenState=" << sda->getDrivenState()
<< " drivingState=" << sda->getDrivingState()
<< " direction=" << ((sda->get_direction()==IOPIN::DIR_INPUT) ?"IN":"OUT")
<< endl;
cout << " bit_count:"<<bit_count
<< " ee_busy:"<<ee_busy
<< " xfr_addr:0x"<<hex<<xfr_addr
<< " xfr_data:0x"<<hex<<xfr_data
<< endl;
}
Register * I2C_EE::get_register(unsigned int address)
{
if ( address < rom_size )
return rom[address];
return 0;
}
void I2C_EE::change_rom(unsigned int offset, unsigned int val)
{
assert(offset < rom_size);
rom[offset]->value.put(val);
}
// write data to eeprom unles write protect is active
void I2C_EE::start_write()
{
unsigned int addr = xfr_addr + write_page_off;
if (! m_write_protect)
{
rom[addr]->put ( xfr_data );
}
else
cout << "I2c_EE start_write- write protect\n";
}
// allow 5 msec after last write
void I2C_EE::write_busy()
{
guint64 fc;
if (! ee_busy && ! m_write_protect)
{
fc = (guint64)(get_cycles().instruction_cps() * 0.005);
get_cycles().set_break(get_cycles().value + fc, this);
ee_busy = true;
}
}
void I2C_EE::write_is_complete()
{
}
void I2C_EE::callback()
{
ee_busy = false;
Vprintf(("I2C_EE::callback() - write cycle is complete\n"));
}
void I2C_EE::callback_print()
{
cout << "Internal I2C-EEPROM\n";
}
bool I2C_EE::shift_read_bit ( bool x )
{
xfr_data = ( xfr_data << 1 ) | ( x != 0 );
bit_count++;
if ( bit_count == 8 )
return true;
else
return false;
}
bool I2C_EE::shift_write_bit ()
{
bool bit;
bit_count--;
bit = ( xfr_data >> bit_count ) & 1;
Dprintf(("I2C_EE : send bit %d = %d\n",bit_count, bit));
return bit;
}
bool I2C_EE::processCommand(unsigned int command)
{
if ((command & 0xf0) == 0xa0 && ((command & m_CSmask) == m_chipselect)) {
m_command = command;
return true;
}
return false;
}
void I2C_EE::new_scl_edge ( bool direction )
{
int curBusState = bus_state;
if (verbose) {
Vprintf(("I2C_EE::new_scl_edge: %d\n",direction));
debug();
}
if ( direction )
{
// Rising edge
nxtbit = sda->getDrivenState();
Dprintf(("I2C_EE SCL : Rising edge, data=%d\n",nxtbit));
}
else
{
// Falling edge
//cout << "I2C_EE SCL : Falling edge\n";
switch ( bus_state )
{
case IDLE :
sda->setDrivingState ( true );
break;
case START :
sda->setDrivingState ( true );
bus_state = RX_CMD;
break;
case RX_CMD :
if ( shift_read_bit ( sda->getDrivenState() ) )
{
Vprintf(("I2C_EE : got command:0x%x\n", xfr_data));
//if ( ( xfr_data & 0xf0 ) == 0xA0 )
if (processCommand(xfr_data))
{
bus_state = ACK_CMD;
Vprintf((" - OK\n"));
// Acknowledge the command by pulling SDA low.
sda->setDrivingState ( false );
}
else
{
// not for us
bus_state = IDLE;
Vprintf((" - not for us\n"));
}
}
break;
case ACK_CMD :
sda->setDrivingState ( true );
// Check the R/W bit of the command
if ( m_command & 0x01 )
{
// it's a read command
bus_state = TX_DATA;
bit_count = 8;
xfr_addr += write_page_off;
write_page_off = 0;
xfr_data = rom[xfr_addr]->get();
sda->setDrivingState ( shift_write_bit() );
}
else
{
// it's a write command
bus_state = RX_ADDR;
bit_count = 0;
xfr_data = 0;
xfr_addr = (m_command & m_BSmask) >> m_BSshift;
m_addr_cnt = m_addr_bytes;
}
break;
case RX_ADDR :
if ( shift_read_bit ( sda->getDrivenState() ) )
{
sda->setDrivingState ( false );
// convert xfr_data to base and page offset to allow
// sequencel writes to wrap around page
xfr_addr = ((xfr_addr << 8) | xfr_data ) % rom_size;
bus_state = ACK_ADDR;
Vprintf(("I2C_EE : address set to 0x%x data:0x%x\n", xfr_addr,xfr_data));
}
break;
case ACK_ADDR :
sda->setDrivingState ( true );
if (--m_addr_cnt)
bus_state = RX_ADDR;
else
{
write_page_off = xfr_addr % write_page_size;
xfr_addr -= write_page_off;
Vprintf(("I2C_EE : address set to 0x%x page offset 0x%x data:0x%x\n",
xfr_addr, write_page_off ,xfr_data));
bus_state = RX_DATA;
}
bit_count = 0;
xfr_data = 0;
break;
case RX_DATA :
if ( shift_read_bit ( sda->getDrivenState() ) )
{
start_write();
Vprintf(("I2C_EE : data set to 0x%x\n", xfr_data));
sda->setDrivingState ( false );
bus_state = ACK_WR;
write_page_off = ++write_page_off % write_page_size;
}
break;
case ACK_WR :
sda->setDrivingState ( true );
bus_state = WRPEND;
break;
case WRPEND :
// We were about to do the write but got more data instead
// of the expected stop bit
xfr_data = sda->getDrivenState();
bit_count = 1;
bus_state = RX_DATA;
Vprintf(("I2C_EE : write postponed by extra data\n"));
break;
case TX_DATA :
if ( bit_count == 0 )
{
sda->setDrivingState ( true ); // Release the bus
xfr_addr++;
xfr_addr %= rom_size;
bus_state = ACK_RD;
}
else
{
sda->setDrivingState ( shift_write_bit() );
}
break;
case ACK_RD :
if ( sda->getDrivenState() == false )
{
// The master has asserted ACK, so we send another byte
bus_state = TX_DATA;
bit_count = 8;
xfr_data = rom[xfr_addr]->get();
sda->setDrivingState ( shift_write_bit() );
}
else
{
bus_state = IDLE; // Actually a limbo state
}
break;
default :
sda->setDrivingState ( true ); // Release the bus
break;
}
}
if ((bool)verbose && bus_state != curBusState) {
Vprintf(("I2C_EE::new_scl_edge() new bus state = %d\n",bus_state));
debug();
}
}
void I2C_EE::new_sda_edge ( bool direction )
{
if (verbose) {
Vprintf(("I2C_EE::new_sda_edge: direction:%d\n",direction));
debug();
}
if ( scl->getDrivenState() )
{
int curBusState = bus_state;
if ( direction )
{
// stop bit
Vprintf(("I2C_EE SDA : Rising edge in SCL high => stop bit\n"));
if ( bus_state == WRPEND )
{
Vprintf(("I2C_EE : write is pending - commence...\n"));
write_busy();
bus_state = IDLE; // Should be busy
}
else
bus_state = IDLE;
}
else
{
// start bit
Vprintf(("I2C_EE SDA : Falling edge in SCL high => start bit\n"));
if ( ee_busy )
{
Vprintf((" Device is busy - ignoring start bit\n"));
}
else
{
bus_state = START;
bit_count = 0;
xfr_data = 0;
}
}
if ((bool)verbose && bus_state != curBusState) {
Vprintf(("I2C_EE::new_sda_edge() new bus state = %d\n",bus_state));
debug();
}
}
}
void I2C_EE::reset(RESET_TYPE by)
{
switch(by)
{
case POR_RESET:
bus_state = IDLE;
ee_busy = false;
break;
default:
break;
}
}
void I2C_EE::attach ( Stimulus_Node *_scl, Stimulus_Node *_sda )
{
_scl->attach_stimulus ( scl );
_sda->attach_stimulus ( sda );
}
void I2C_EE::dump()
{
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';
}
}
syntax highlighted by Code2HTML, v. 0.9.1