/* I2C Memory Generic ram/rom/eeprom/flash on an i2c bus. Supports specifying the slave address, the data size & the page size for writing. inputs: e0,e1,e2 lower 3 bits of the slave address sda serial data scl serial clock wc write protect outputs: sda serial data The memory address is only 8 bits, devices larger than this have multiple slave addresses. The top five address bits are set at manufacture time, two values are standard. Up to 4096 bytes can be addressed. */ #include #include "machine/i2cmem.h" #define VERBOSE_LEVEL ( 0 ) INLINE void ATTR_PRINTF(2,3) verboselog( int n_level, const char *s_fmt, ... ) { if( VERBOSE_LEVEL >= n_level ) { va_list v; char buf[ 32768 ]; va_start( v, s_fmt ); vsprintf( buf, s_fmt, v ); va_end( v ); if( cpu_getactivecpu() != -1 ) { logerror( "%08x: %s", activecpu_get_pc(), buf ); } else { logerror( "(timer) : %s", buf ); } } } struct i2cmem_chip { int slave_address; int scl; int sdaw; int e0; int e1; int e2; int wc; int sdar; int state; int bits; int shift; int devsel; int byteaddr; unsigned char *data; int data_size; unsigned char *page; int page_offset; int page_size; }; #define STATE_IDLE ( 0 ) #define STATE_DEVSEL ( 1 ) #define STATE_BYTEADDR ( 2 ) #define STATE_DATAIN ( 3 ) #define STATE_DATAOUT ( 4 ) #define DEVSEL_RW ( 1 ) #define DEVSEL_ADDRESS ( 0xfe ) static struct i2cmem_chip i2cmem[ I2CMEM_MAXCHIP ]; void i2cmem_init( int chip, int slave_address, int page_size, int data_size, unsigned char *data ) { struct i2cmem_chip *c; unsigned char *page = NULL; if( chip >= I2CMEM_MAXCHIP ) { verboselog( 0, "i2cmem_init( %d ) invalid chip\n", chip ); return; } c = &i2cmem[ chip ]; if( data == NULL ) { data = auto_malloc( data_size ); } if( page_size > 0 ) { page = auto_malloc( page_size ); } c->slave_address = slave_address; c->data_size = data_size; c->page_size = page_size; c->scl = 0; c->sdaw = 0; c->e0 = 0; c->e1 = 0; c->e2 = 0; c->wc = 0; c->sdar = 1; c->state = STATE_IDLE; c->bits = 0; c->shift = 0; c->devsel = 0; c->byteaddr = 0; c->data = data; c->page = page; state_save_register_item( "i2cmem", chip, c->scl ); state_save_register_item( "i2cmem", chip, c->sdaw ); state_save_register_item( "i2cmem", chip, c->e0 ); state_save_register_item( "i2cmem", chip, c->e1 ); state_save_register_item( "i2cmem", chip, c->e2 ); state_save_register_item( "i2cmem", chip, c->wc ); state_save_register_item( "i2cmem", chip, c->sdar ); state_save_register_item( "i2cmem", chip, c->state ); state_save_register_item( "i2cmem", chip, c->bits ); state_save_register_item( "i2cmem", chip, c->shift ); state_save_register_item( "i2cmem", chip, c->devsel ); state_save_register_item( "i2cmem", chip, c->byteaddr ); state_save_register_item_pointer( "i2cmem", chip, c->data, c->data_size ); } static int select_device( struct i2cmem_chip *c ) { int device = ( c->slave_address & 0xf0 ) | ( c->e2 << 3 ) | ( c->e1 << 2 ) | ( c->e0 << 1 ); int mask = DEVSEL_ADDRESS & ~( ( c->data_size - 1 ) >> 7 ); if( ( c->devsel & mask ) == ( device & mask ) ) { return 1; } return 0; } static int data_offset( struct i2cmem_chip *c ) { return ( ( ( c->devsel << 7 ) & 0xff00 ) | ( c->byteaddr & 0xff ) ) & ( c->data_size - 1 ); } void i2cmem_write( int chip, int line, int data ) { struct i2cmem_chip *c; if( chip >= I2CMEM_MAXCHIP ) { verboselog( 0, "i2cmem_write( %d, %d, %d ) invalid chip\n", chip, line, data ); return; } c = &i2cmem[ chip ]; data &= 1; switch( line ) { case I2CMEM_E0: if( c->e0 != data ) { c->e0 = data; verboselog( 2, "i2cmem_write( %d, I2CMEM_E0, %d )\n", chip, c->e0 ); } break; case I2CMEM_E1: if( c->e1 != data ) { c->e1 = data; verboselog( 2, "i2cmem_write( %d, I2CMEM_E1, %d )\n", chip, c->e1 ); } break; case I2CMEM_E2: if( c->e2 != data ) { c->e2 = data; verboselog( 2, "i2cmem_write( %d, I2CMEM_E2, %d )\n", chip, c->e2 ); } break; case I2CMEM_SDA: if( c->sdaw != data ) { c->sdaw = data; verboselog( 2, "i2cmem_write( %d, I2CMEM_SDA, %d )\n", chip, c->sdaw ); if( c->scl ) { if( c->sdaw ) { verboselog( 1, "i2cmem(%d) stop\n", chip ); c->state = STATE_IDLE; c->byteaddr = 0; } else { verboselog( 2, "i2cmem(%d) start\n", chip ); c->state = STATE_DEVSEL; c->bits = 0; } c->sdar = 1; } } break; case I2CMEM_SCL: if( c->scl != data ) { c->scl = data; verboselog( 2, "i2cmem_write( %d, I2CMEM_SCL, %d )\n", chip, c->scl ); switch( c->state ) { case STATE_DEVSEL: case STATE_BYTEADDR: case STATE_DATAIN: if( c->bits < 8 ) { if( c->scl ) { c->shift = ( ( c->shift << 1 ) | c->sdaw ) & 0xff; c->bits++; } } else { if( c->scl ) { switch( c->state ) { case STATE_DEVSEL: c->devsel = c->shift; if( !select_device( c ) ) { verboselog( 1, "i2cmem(%d) devsel %02x: not this device\n", chip, c->devsel ); c->state = STATE_IDLE; } else if( ( c->devsel & DEVSEL_RW ) == 0 ) { verboselog( 1, "i2cmem(%d) devsel %02x: write\n", chip, c->devsel ); c->state = STATE_BYTEADDR; } else { verboselog( 1, "i2cmem(%d) devsel %02x: read\n", chip, c->devsel ); c->state = STATE_DATAOUT; } break; case STATE_BYTEADDR: c->byteaddr = c->shift; c->page_offset = 0; verboselog( 1, "i2cmem(%d) byteaddr %02x\n", chip, c->byteaddr ); c->state = STATE_DATAIN; break; case STATE_DATAIN: if( c->wc ) { verboselog( 0, "i2cmem(%d) write not enabled\n", chip ); c->state = STATE_IDLE; } else if( c->page_size > 0 ) { c->page[ c->page_offset ] = c->shift; verboselog( 1, "i2cmem(%d) page[ %04x ] <- %02x\n", chip, c->page_offset, c->page[ c->page_offset ] ); c->page_offset++; if( c->page_offset == c->page_size ) { int offset = data_offset( c ) & ~( c->page_size - 1 ); memcpy( &c->data[ offset ], c->page, c->page_size ); verboselog( 1, "i2cmem(%d) data[ %04x to %04x ] = page\n", chip, offset, offset + c->page_size - 1 ); c->page_offset = 0; } } else { int offset = data_offset( c ); c->data[ offset ] = c->shift; verboselog( 1, "i2cmem(%d) data[ %04x ] <- %02x\n", chip, offset, c->data[ offset ] ); c->byteaddr++; } break; } c->bits++; } else { if( c->bits == 8 ) { c->sdar = 0; } else { c->bits = 0; c->sdar = 1; } } } break; case STATE_DATAOUT: if( c->bits < 8 ) { if( c->scl ) { if( c->bits == 0 ) { int offset = data_offset( c ); c->shift = c->data[ offset ]; verboselog( 1, "i2cmem(%d) data[ %04x ] -> %02x\n", chip, offset, c->data[ offset ] ); c->byteaddr++; } c->sdar = ( c->shift >> 7 ) & 1; c->shift = ( c->shift << 1 ) & 0xff; c->bits++; } } else { if( c->scl ) { if( c->sdaw ) { verboselog( 1, "i2cmem(%d) sleep\n", chip ); c->state = STATE_IDLE; } c->bits++; } else { if( c->bits == 8 ) { c->sdar = 1; } else { c->bits = 0; } } } break; } } break; case I2CMEM_WC: if( c->wc != data ) { c->wc = data; verboselog( 2, "i2cmem_write( %d, I2CMEM_WC, %d )\n", chip, c->wc ); } break; default: verboselog( 0, "i2cmem_write( %d, %d, %d ) invalid line\n", chip, line, data ); break; } } int i2cmem_read( int chip, int line ) { struct i2cmem_chip *c; if( chip >= I2CMEM_MAXCHIP ) { verboselog( 0, "i2cmem_read( %d, %d ) invalid chip\n", chip, line ); return 0; } c = &i2cmem[ chip ]; switch( line ) { case I2CMEM_SDA: verboselog( 2, "i2cmem_read( %d, I2CMEM_SDA ) %d\n", chip, c->sdar & c->sdaw ); return c->sdar & c->sdaw; default: verboselog( 0, "i2cmem_read( %d, %d ) invalid line\n", chip, line ); break; } return 0; } static void nvram_handler_i2cmem( int chip, running_machine *machine, mame_file *file, int read_or_write ) { struct i2cmem_chip *c; if( chip >= I2CMEM_MAXCHIP ) { verboselog( 0, "nvram_handler_i2cmem( %d ) invalid chip\n", chip ); return; } c = &i2cmem[ chip ]; if( read_or_write ) { mame_fwrite( file, c->data, c->data_size ); } else if( file ) { mame_fread( file, c->data, c->data_size ); } } NVRAM_HANDLER( i2cmem_0 ) { nvram_handler_i2cmem( 0, machine, file, read_or_write ); }