/* 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 "emu.h" #include "machine/i2cmemdev.h" #define VERBOSE_LEVEL ( 0 ) INLINE void ATTR_PRINTF(3,4) verboselog( running_machine *machine, 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 ); logerror( "%s: %s", cpuexec_describe_context(machine), buf ); } } #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 ) typedef struct _i2cmem_state i2cmem_state; struct _i2cmem_state { 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; int readmode; }; /*------------------------------------------------- get_safe_token - makes sure that the passed in device is, in fact, an I2C memory -------------------------------------------------*/ INLINE i2cmem_state *get_safe_token( const device_config *device ) { assert( device != NULL ); assert( device->token != NULL ); assert( device->type == I2CMEM ); return (i2cmem_state *)device->token; } static int select_device( i2cmem_state *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( i2cmem_state *c ) { return ( ( ( c->devsel << 7 ) & 0xff00 ) | ( c->byteaddr & 0xff ) ) & ( c->data_size - 1 ); } void i2cmemdev_write( const device_config *device, int line, int data ) { i2cmem_state *c = get_safe_token( device ); switch( line ) { case I2CMEM_E0: if( c->e0 != data ) { c->e0 = data; verboselog( device->machine, 2, "i2cmem_write( I2CMEM_E0, %d )\n", c->e0 ); } break; case I2CMEM_E1: if( c->e1 != data ) { c->e1 = data; verboselog( device->machine, 2, "i2cmem_write( I2CMEM_E1, %d )\n", c->e1 ); } break; case I2CMEM_E2: if( c->e2 != data ) { c->e2 = data; verboselog( device->machine, 2, "i2cmem_write( I2CMEM_E2, %d )\n", c->e2 ); } break; case I2CMEM_SDA: if( c->sdaw != data ) { c->sdaw = data; verboselog( device->machine, 2, "i2cmem_write( I2CMEM_SDA, %d )\n", c->sdaw ); if( c->scl ) { if( c->sdaw ) { verboselog( device->machine, 1, "i2cmem stop\n"); c->state = STATE_IDLE; c->byteaddr = 0; } else { verboselog( device->machine, 2, "i2cmem start\n"); c->state = STATE_DEVSEL; c->bits = 0; } c->sdar = 1; } } break; case I2CMEM_SCL: if( c->scl != data ) { c->scl = data; verboselog( device->machine, 2, "i2cmem_write( I2CMEM_SCL, %d )\n", 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( device->machine, 1, "i2cmem devsel %02x: not this device\n", c->devsel ); c->state = STATE_IDLE; } else if( ( c->devsel & DEVSEL_RW ) == 0 ) { verboselog( device->machine, 1, "i2cmem devsel %02x: write\n", c->devsel ); c->state = STATE_BYTEADDR; } else { verboselog( device->machine, 1, "i2cmem devsel %02x: read\n", c->devsel ); c->state = STATE_DATAOUT; } break; case STATE_BYTEADDR: c->byteaddr = c->shift; c->page_offset = 0; verboselog( device->machine, 1, "i2cmem byteaddr %02x\n", c->byteaddr ); c->state = STATE_DATAIN; break; case STATE_DATAIN: if( c->wc ) { verboselog( device->machine, 0, "i2cmem write not enabled\n"); c->state = STATE_IDLE; } else if( c->page_size > 0 ) { c->page[ c->page_offset ] = c->shift; verboselog( device->machine, 1, "i2cmem page[ %04x ] <- %02x\n", 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( device->machine, 1, "i2cmem data[ %04x to %04x ] = page\n", offset, offset + c->page_size - 1 ); c->page_offset = 0; } } else { int offset = data_offset( c ); c->data[ offset ] = c->shift; verboselog( device->machine, 1, "i2cmem data[ %04x ] <- %02x\n", 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( device->machine, 1, "i2cmem data[ %04x ] -> %02x\n", 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( device->machine, 1, "i2cmem sleep\n"); 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( device->machine, 2, "i2cmem_write( I2CMEM_WC, %d )\n", c->wc ); } break; default: verboselog( device->machine, 0, "i2cmem_write( %d, %d ) invalid line\n", line, data ); break; } } int i2cmemdev_read( const device_config *device, int line ) { i2cmem_state *c = get_safe_token( device ); switch( line ) { case I2CMEM_SDA: if (c->readmode == 0) { verboselog( device->machine, 2, "i2cmem_read( I2CMEM_SDA ) %d\n", c->sdar & c->sdaw ); return c->sdar & c->sdaw; } else { verboselog( device->machine, 2, "i2cmem_read( I2CMEM_SDA ) %d\n", c->sdar ); return c->sdar; } default: verboselog( device->machine, 0, "i2cmem_read( %d ) invalid line\n", line ); break; } return 0; } void i2cmemdev_set_read_mode( const device_config *device, int mode ) { i2cmem_state *c = get_safe_token( device ); c->readmode = mode; } /*------------------------------------------------- device start callback -------------------------------------------------*/ static DEVICE_START( i2cmem ) { i2cmem_state *c = get_safe_token( device ); const i2cmem_config *config; unsigned char *page = NULL; /* validate some basic stuff */ assert( device != NULL ); assert( device->inline_config != NULL ); assert( device->machine != NULL ); assert( device->machine->config != NULL ); config = (const i2cmem_config *)device->inline_config; 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->readmode = 0; if( config != NULL ) { c->data = auto_alloc_array( device->machine, UINT8, config->data_size ); memcpy(c->data, config->data, config->data_size); if( config->page_size > 0 ) page = auto_alloc_array( device->machine, UINT8, config->page_size ); c->slave_address = config->slave_address; c->data_size = config->data_size; c->page_size = config->page_size; c->page = page; } state_save_register_device_item( device, 0, c->scl ); state_save_register_device_item( device, 0, c->sdaw ); state_save_register_device_item( device, 0, c->e0 ); state_save_register_device_item( device, 0, c->e1 ); state_save_register_device_item( device, 0, c->e2 ); state_save_register_device_item( device, 0, c->wc ); state_save_register_device_item( device, 0, c->sdar ); state_save_register_device_item( device, 0, c->state ); state_save_register_device_item( device, 0, c->bits ); state_save_register_device_item( device, 0, c->shift ); state_save_register_device_item( device, 0, c->devsel ); state_save_register_device_item( device, 0, c->byteaddr ); state_save_register_device_item_pointer( device, 0, c->data, c->data_size ); state_save_register_device_item( device, 0, c->readmode ); } /*------------------------------------------------- device reset callback -------------------------------------------------*/ static DEVICE_RESET( i2cmem ) { } static DEVICE_NVRAM( i2cmem ) { const i2cmem_config *config = (const i2cmem_config *)device->inline_config; i2cmem_state *c = get_safe_token( device ); if( read_or_write ) { mame_fwrite( file, c->data, c->data_size ); } else if( file ) { mame_fread( file, c->data, c->data_size ); } else { if( config->data != NULL ) memcpy( c->data, config->data, config->data_size ); } } /*------------------------------------------------- device get info callback -------------------------------------------------*/ DEVICE_GET_INFO( i2cmem ) { switch ( state ) { /* --- the following bits of info are returned as 64-bit signed integers --- */ case DEVINFO_INT_TOKEN_BYTES: info->i = sizeof( i2cmem_state ); break; case DEVINFO_INT_INLINE_CONFIG_BYTES: info->i = sizeof( i2cmem_config ); break; case DEVINFO_INT_CLASS: info->i = DEVICE_CLASS_PERIPHERAL; break; /* --- the following bits of info are returned as pointers to data or functions --- */ case DEVINFO_FCT_START: info->start = DEVICE_START_NAME( i2cmem ); break; case DEVINFO_FCT_STOP: /* nothing */ break; case DEVINFO_FCT_RESET: info->reset = DEVICE_RESET_NAME( i2cmem ); break; case DEVINFO_FCT_NVRAM: info->nvram = DEVICE_NVRAM_NAME( i2cmem ); break; /* --- the following bits of info are returned as NULL-terminated strings --- */ case DEVINFO_STR_NAME: strcpy( info->s, "I2CMEM" ); break; case DEVINFO_STR_FAMILY: strcpy( info->s, "EEPROM" ); break; case DEVINFO_STR_VERSION: strcpy( info->s, "1.0" ); break; case DEVINFO_STR_SOURCE_FILE: strcpy( info->s, __FILE__ ); break; case DEVINFO_STR_CREDITS: strcpy( info->s, "Copyright Nicola Salmoria and the MAME Team" ); break; } }