/* 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 #include #include using namespace std; #include #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="<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'; } }