src/emu/machine/i2cmemdev.c
| r8473 | r8474 | |
| 1 | | /* |
| 2 | | I2C Memory |
| 3 | | |
| 4 | | Generic ram/rom/eeprom/flash on an i2c bus. Supports specifying the slave address, |
| 5 | | the data size & the page size for writing. |
| 6 | | |
| 7 | | inputs: |
| 8 | | e0,e1,e2 lower 3 bits of the slave address |
| 9 | | sda serial data |
| 10 | | scl serial clock |
| 11 | | wc write protect |
| 12 | | |
| 13 | | outputs: |
| 14 | | sda serial data |
| 15 | | |
| 16 | | The memory address is only 8 bits, devices larger than this have multiple slave addresses. |
| 17 | | The top five address bits are set at manufacture time, two values are standard. |
| 18 | | Up to 4096 bytes can be addressed. |
| 19 | | |
| 20 | | */ |
| 21 | | |
| 22 | | #include "emu.h" |
| 23 | | #include "machine/i2cmemdev.h" |
| 24 | | |
| 25 | | #define VERBOSE_LEVEL ( 0 ) |
| 26 | | |
| 27 | | INLINE void ATTR_PRINTF(3,4) verboselog( running_machine *machine, int n_level, const char *s_fmt, ... ) |
| 28 | | { |
| 29 | | if( VERBOSE_LEVEL >= n_level ) |
| 30 | | { |
| 31 | | va_list v; |
| 32 | | char buf[ 32768 ]; |
| 33 | | va_start( v, s_fmt ); |
| 34 | | vsprintf( buf, s_fmt, v ); |
| 35 | | va_end( v ); |
| 36 | | logerror( "%s: %s", cpuexec_describe_context(machine), buf ); |
| 37 | | } |
| 38 | | } |
| 39 | | |
| 40 | | #define STATE_IDLE ( 0 ) |
| 41 | | #define STATE_DEVSEL ( 1 ) |
| 42 | | #define STATE_BYTEADDR ( 2 ) |
| 43 | | #define STATE_DATAIN ( 3 ) |
| 44 | | #define STATE_DATAOUT ( 4 ) |
| 45 | | |
| 46 | | #define DEVSEL_RW ( 1 ) |
| 47 | | #define DEVSEL_ADDRESS ( 0xfe ) |
| 48 | | |
| 49 | | typedef struct _i2cmem_state i2cmem_state; |
| 50 | | struct _i2cmem_state |
| 51 | | { |
| 52 | | int slave_address; |
| 53 | | int scl; |
| 54 | | int sdaw; |
| 55 | | int e0; |
| 56 | | int e1; |
| 57 | | int e2; |
| 58 | | int wc; |
| 59 | | int sdar; |
| 60 | | int state; |
| 61 | | int bits; |
| 62 | | int shift; |
| 63 | | int devsel; |
| 64 | | int byteaddr; |
| 65 | | unsigned char *data; |
| 66 | | int data_size; |
| 67 | | unsigned char *page; |
| 68 | | int page_offset; |
| 69 | | int page_size; |
| 70 | | int readmode; |
| 71 | | }; |
| 72 | | |
| 73 | | /*------------------------------------------------- |
| 74 | | get_safe_token - makes sure that the passed |
| 75 | | in device is, in fact, an I2C memory |
| 76 | | -------------------------------------------------*/ |
| 77 | | |
| 78 | | INLINE i2cmem_state *get_safe_token( running_device *device ) |
| 79 | | { |
| 80 | | assert( device != NULL ); |
| 81 | | assert( device->type() == I2CMEM ); |
| 82 | | |
| 83 | | return (i2cmem_state *)downcast<legacy_device_base *>(device)->token(); |
| 84 | | } |
| 85 | | |
| 86 | | static int select_device( i2cmem_state *c ) |
| 87 | | { |
| 88 | | int device = ( c->slave_address & 0xf0 ) | ( c->e2 << 3 ) | ( c->e1 << 2 ) | ( c->e0 << 1 ); |
| 89 | | int mask = DEVSEL_ADDRESS & ~( ( c->data_size - 1 ) >> 7 ); |
| 90 | | |
| 91 | | if( ( c->devsel & mask ) == ( device & mask ) ) |
| 92 | | { |
| 93 | | return 1; |
| 94 | | } |
| 95 | | |
| 96 | | return 0; |
| 97 | | } |
| 98 | | |
| 99 | | static int data_offset( i2cmem_state *c ) |
| 100 | | { |
| 101 | | return ( ( ( c->devsel << 7 ) & 0xff00 ) | ( c->byteaddr & 0xff ) ) & ( c->data_size - 1 ); |
| 102 | | } |
| 103 | | |
| 104 | | void i2cmemdev_write( running_device *device, int line, int data ) |
| 105 | | { |
| 106 | | i2cmem_state *c = get_safe_token( device ); |
| 107 | | |
| 108 | | switch( line ) |
| 109 | | { |
| 110 | | case I2CMEM_E0: |
| 111 | | if( c->e0 != data ) |
| 112 | | { |
| 113 | | c->e0 = data; |
| 114 | | verboselog( device->machine, 2, "i2cmem_write( I2CMEM_E0, %d )\n", c->e0 ); |
| 115 | | } |
| 116 | | break; |
| 117 | | |
| 118 | | case I2CMEM_E1: |
| 119 | | if( c->e1 != data ) |
| 120 | | { |
| 121 | | c->e1 = data; |
| 122 | | verboselog( device->machine, 2, "i2cmem_write( I2CMEM_E1, %d )\n", c->e1 ); |
| 123 | | } |
| 124 | | break; |
| 125 | | |
| 126 | | case I2CMEM_E2: |
| 127 | | if( c->e2 != data ) |
| 128 | | { |
| 129 | | c->e2 = data; |
| 130 | | verboselog( device->machine, 2, "i2cmem_write( I2CMEM_E2, %d )\n", c->e2 ); |
| 131 | | } |
| 132 | | break; |
| 133 | | |
| 134 | | case I2CMEM_SDA: |
| 135 | | if( c->sdaw != data ) |
| 136 | | { |
| 137 | | c->sdaw = data; |
| 138 | | verboselog( device->machine, 2, "i2cmem_write( I2CMEM_SDA, %d )\n", c->sdaw ); |
| 139 | | |
| 140 | | if( c->scl ) |
| 141 | | { |
| 142 | | if( c->sdaw ) |
| 143 | | { |
| 144 | | verboselog( device->machine, 1, "i2cmem stop\n"); |
| 145 | | c->state = STATE_IDLE; |
| 146 | | c->byteaddr = 0; |
| 147 | | } |
| 148 | | else |
| 149 | | { |
| 150 | | verboselog( device->machine, 2, "i2cmem start\n"); |
| 151 | | c->state = STATE_DEVSEL; |
| 152 | | c->bits = 0; |
| 153 | | } |
| 154 | | |
| 155 | | c->sdar = 1; |
| 156 | | } |
| 157 | | } |
| 158 | | break; |
| 159 | | |
| 160 | | case I2CMEM_SCL: |
| 161 | | if( c->scl != data ) |
| 162 | | { |
| 163 | | c->scl = data; |
| 164 | | verboselog( device->machine, 2, "i2cmem_write( I2CMEM_SCL, %d )\n", c->scl ); |
| 165 | | |
| 166 | | switch( c->state ) |
| 167 | | { |
| 168 | | case STATE_DEVSEL: |
| 169 | | case STATE_BYTEADDR: |
| 170 | | case STATE_DATAIN: |
| 171 | | if( c->bits < 8 ) |
| 172 | | { |
| 173 | | if( c->scl ) |
| 174 | | { |
| 175 | | c->shift = ( ( c->shift << 1 ) | c->sdaw ) & 0xff; |
| 176 | | c->bits++; |
| 177 | | } |
| 178 | | } |
| 179 | | else |
| 180 | | { |
| 181 | | if( c->scl ) |
| 182 | | { |
| 183 | | switch( c->state ) |
| 184 | | { |
| 185 | | case STATE_DEVSEL: |
| 186 | | c->devsel = c->shift; |
| 187 | | |
| 188 | | if( !select_device( c ) ) |
| 189 | | { |
| 190 | | verboselog( device->machine, 1, "i2cmem devsel %02x: not this device\n", c->devsel ); |
| 191 | | c->state = STATE_IDLE; |
| 192 | | } |
| 193 | | else if( ( c->devsel & DEVSEL_RW ) == 0 ) |
| 194 | | { |
| 195 | | verboselog( device->machine, 1, "i2cmem devsel %02x: write\n", c->devsel ); |
| 196 | | c->state = STATE_BYTEADDR; |
| 197 | | } |
| 198 | | else |
| 199 | | { |
| 200 | | verboselog( device->machine, 1, "i2cmem devsel %02x: read\n", c->devsel ); |
| 201 | | c->state = STATE_DATAOUT; |
| 202 | | } |
| 203 | | break; |
| 204 | | |
| 205 | | case STATE_BYTEADDR: |
| 206 | | c->byteaddr = c->shift; |
| 207 | | c->page_offset = 0; |
| 208 | | |
| 209 | | verboselog( device->machine, 1, "i2cmem byteaddr %02x\n", c->byteaddr ); |
| 210 | | |
| 211 | | c->state = STATE_DATAIN; |
| 212 | | break; |
| 213 | | |
| 214 | | case STATE_DATAIN: |
| 215 | | if( c->wc ) |
| 216 | | { |
| 217 | | verboselog( device->machine, 0, "i2cmem write not enabled\n"); |
| 218 | | c->state = STATE_IDLE; |
| 219 | | } |
| 220 | | else if( c->page_size > 0 ) |
| 221 | | { |
| 222 | | c->page[ c->page_offset ] = c->shift; |
| 223 | | verboselog( device->machine, 1, "i2cmem page[ %04x ] <- %02x\n", c->page_offset, c->page[ c->page_offset ] ); |
| 224 | | |
| 225 | | c->page_offset++; |
| 226 | | if( c->page_offset == c->page_size ) |
| 227 | | { |
| 228 | | int offset = data_offset( c ) & ~( c->page_size - 1 ); |
| 229 | | |
| 230 | | memcpy( &c->data[ offset ], c->page, c->page_size ); |
| 231 | | verboselog( device->machine, 1, "i2cmem data[ %04x to %04x ] = page\n", offset, offset + c->page_size - 1 ); |
| 232 | | |
| 233 | | c->page_offset = 0; |
| 234 | | } |
| 235 | | } |
| 236 | | else |
| 237 | | { |
| 238 | | int offset = data_offset( c ); |
| 239 | | |
| 240 | | c->data[ offset ] = c->shift; |
| 241 | | verboselog( device->machine, 1, "i2cmem data[ %04x ] <- %02x\n", offset, c->data[ offset ] ); |
| 242 | | |
| 243 | | c->byteaddr++; |
| 244 | | } |
| 245 | | break; |
| 246 | | } |
| 247 | | |
| 248 | | c->bits++; |
| 249 | | } |
| 250 | | else |
| 251 | | { |
| 252 | | if( c->bits == 8 ) |
| 253 | | { |
| 254 | | c->sdar = 0; |
| 255 | | } |
| 256 | | else |
| 257 | | { |
| 258 | | c->bits = 0; |
| 259 | | c->sdar = 1; |
| 260 | | } |
| 261 | | } |
| 262 | | } |
| 263 | | break; |
| 264 | | |
| 265 | | case STATE_DATAOUT: |
| 266 | | if( c->bits < 8 ) |
| 267 | | { |
| 268 | | if( c->scl ) |
| 269 | | { |
| 270 | | if( c->bits == 0 ) |
| 271 | | { |
| 272 | | int offset = data_offset( c ); |
| 273 | | |
| 274 | | c->shift = c->data[ offset ]; |
| 275 | | verboselog( device->machine, 1, "i2cmem data[ %04x ] -> %02x\n", offset, c->data[ offset ] ); |
| 276 | | c->byteaddr++; |
| 277 | | } |
| 278 | | |
| 279 | | c->sdar = ( c->shift >> 7 ) & 1; |
| 280 | | |
| 281 | | c->shift = ( c->shift << 1 ) & 0xff; |
| 282 | | c->bits++; |
| 283 | | } |
| 284 | | } |
| 285 | | else |
| 286 | | { |
| 287 | | if( c->scl ) |
| 288 | | { |
| 289 | | if( c->sdaw ) |
| 290 | | { |
| 291 | | verboselog( device->machine, 1, "i2cmem sleep\n"); |
| 292 | | c->state = STATE_IDLE; |
| 293 | | } |
| 294 | | |
| 295 | | c->bits++; |
| 296 | | } |
| 297 | | else |
| 298 | | { |
| 299 | | if( c->bits == 8 ) |
| 300 | | { |
| 301 | | c->sdar = 1; |
| 302 | | } |
| 303 | | else |
| 304 | | { |
| 305 | | c->bits = 0; |
| 306 | | } |
| 307 | | } |
| 308 | | } |
| 309 | | break; |
| 310 | | } |
| 311 | | } |
| 312 | | break; |
| 313 | | |
| 314 | | case I2CMEM_WC: |
| 315 | | if( c->wc != data ) |
| 316 | | { |
| 317 | | c->wc = data; |
| 318 | | verboselog( device->machine, 2, "i2cmem_write( I2CMEM_WC, %d )\n", c->wc ); |
| 319 | | } |
| 320 | | break; |
| 321 | | |
| 322 | | default: |
| 323 | | verboselog( device->machine, 0, "i2cmem_write( %d, %d ) invalid line\n", line, data ); |
| 324 | | break; |
| 325 | | } |
| 326 | | } |
| 327 | | |
| 328 | | int i2cmemdev_read( running_device *device, int line ) |
| 329 | | { |
| 330 | | i2cmem_state *c = get_safe_token( device ); |
| 331 | | |
| 332 | | switch( line ) |
| 333 | | { |
| 334 | | case I2CMEM_SDA: |
| 335 | | if (c->readmode == 0) |
| 336 | | { |
| 337 | | verboselog( device->machine, 2, "i2cmem_read( I2CMEM_SDA ) %d\n", c->sdar & c->sdaw ); |
| 338 | | return c->sdar & c->sdaw; |
| 339 | | } |
| 340 | | else |
| 341 | | { |
| 342 | | verboselog( device->machine, 2, "i2cmem_read( I2CMEM_SDA ) %d\n", c->sdar ); |
| 343 | | return c->sdar; |
| 344 | | } |
| 345 | | |
| 346 | | default: |
| 347 | | verboselog( device->machine, 0, "i2cmem_read( %d ) invalid line\n", line ); |
| 348 | | break; |
| 349 | | } |
| 350 | | |
| 351 | | return 0; |
| 352 | | } |
| 353 | | |
| 354 | | void i2cmemdev_set_read_mode( running_device *device, int mode ) |
| 355 | | { |
| 356 | | i2cmem_state *c = get_safe_token( device ); |
| 357 | | |
| 358 | | c->readmode = mode; |
| 359 | | } |
| 360 | | |
| 361 | | /*------------------------------------------------- |
| 362 | | device start callback |
| 363 | | -------------------------------------------------*/ |
| 364 | | |
| 365 | | static DEVICE_START( i2cmem ) |
| 366 | | { |
| 367 | | i2cmem_state *c = get_safe_token( device ); |
| 368 | | const i2cmem_config *config; |
| 369 | | unsigned char *page = NULL; |
| 370 | | |
| 371 | | /* validate some basic stuff */ |
| 372 | | assert( device != NULL ); |
| 373 | | assert( downcast<const legacy_device_config_base &>(device->baseconfig()).inline_config() != NULL ); |
| 374 | | assert( device->machine != NULL ); |
| 375 | | assert( device->machine->config != NULL ); |
| 376 | | |
| 377 | | config = (const i2cmem_config *)downcast<const legacy_device_config_base &>(device->baseconfig()).inline_config(); |
| 378 | | |
| 379 | | c->scl = 0; |
| 380 | | c->sdaw = 0; |
| 381 | | c->e0 = 0; |
| 382 | | c->e1 = 0; |
| 383 | | c->e2 = 0; |
| 384 | | c->wc = 0; |
| 385 | | c->sdar = 1; |
| 386 | | c->state = STATE_IDLE; |
| 387 | | c->bits = 0; |
| 388 | | c->shift = 0; |
| 389 | | c->devsel = 0; |
| 390 | | c->byteaddr = 0; |
| 391 | | c->readmode = 0; |
| 392 | | |
| 393 | | if( config != NULL ) |
| 394 | | { |
| 395 | | c->data = auto_alloc_array( device->machine, UINT8, config->data_size ); |
| 396 | | memcpy(c->data, config->data, config->data_size); |
| 397 | | |
| 398 | | if( config->page_size > 0 ) |
| 399 | | page = auto_alloc_array( device->machine, UINT8, config->page_size ); |
| 400 | | |
| 401 | | c->slave_address = config->slave_address; |
| 402 | | c->data_size = config->data_size; |
| 403 | | c->page_size = config->page_size; |
| 404 | | c->page = page; |
| 405 | | } |
| 406 | | |
| 407 | | state_save_register_device_item( device, 0, c->scl ); |
| 408 | | state_save_register_device_item( device, 0, c->sdaw ); |
| 409 | | state_save_register_device_item( device, 0, c->e0 ); |
| 410 | | state_save_register_device_item( device, 0, c->e1 ); |
| 411 | | state_save_register_device_item( device, 0, c->e2 ); |
| 412 | | state_save_register_device_item( device, 0, c->wc ); |
| 413 | | state_save_register_device_item( device, 0, c->sdar ); |
| 414 | | state_save_register_device_item( device, 0, c->state ); |
| 415 | | state_save_register_device_item( device, 0, c->bits ); |
| 416 | | state_save_register_device_item( device, 0, c->shift ); |
| 417 | | state_save_register_device_item( device, 0, c->devsel ); |
| 418 | | state_save_register_device_item( device, 0, c->byteaddr ); |
| 419 | | state_save_register_device_item_pointer( device, 0, c->data, c->data_size ); |
| 420 | | state_save_register_device_item( device, 0, c->readmode ); |
| 421 | | } |
| 422 | | |
| 423 | | /*------------------------------------------------- |
| 424 | | device reset callback |
| 425 | | -------------------------------------------------*/ |
| 426 | | |
| 427 | | static DEVICE_RESET( i2cmem ) |
| 428 | | { |
| 429 | | } |
| 430 | | |
| 431 | | static DEVICE_NVRAM( i2cmem ) |
| 432 | | { |
| 433 | | const i2cmem_config *config = (const i2cmem_config *)downcast<const legacy_device_config_base &>(device->baseconfig()).inline_config(); |
| 434 | | i2cmem_state *c = get_safe_token( device ); |
| 435 | | |
| 436 | | if( read_or_write ) |
| 437 | | { |
| 438 | | mame_fwrite( file, c->data, c->data_size ); |
| 439 | | } |
| 440 | | else if( file ) |
| 441 | | { |
| 442 | | mame_fread( file, c->data, c->data_size ); |
| 443 | | } |
| 444 | | else |
| 445 | | { |
| 446 | | if( config->data != NULL ) |
| 447 | | memcpy( c->data, config->data, config->data_size ); |
| 448 | | } |
| 449 | | } |
| 450 | | |
| 451 | | /*------------------------------------------------- |
| 452 | | device get info callback |
| 453 | | -------------------------------------------------*/ |
| 454 | | |
| 455 | | DEVICE_GET_INFO( i2cmem ) |
| 456 | | { |
| 457 | | switch ( state ) |
| 458 | | { |
| 459 | | /* --- the following bits of info are returned as 64-bit signed integers --- */ |
| 460 | | case DEVINFO_INT_TOKEN_BYTES: info->i = sizeof( i2cmem_state ); break; |
| 461 | | case DEVINFO_INT_INLINE_CONFIG_BYTES: info->i = sizeof( i2cmem_config ); break; |
| 462 | | |
| 463 | | /* --- the following bits of info are returned as pointers to data or functions --- */ |
| 464 | | case DEVINFO_FCT_START: info->start = DEVICE_START_NAME( i2cmem ); break; |
| 465 | | case DEVINFO_FCT_STOP: /* nothing */ break; |
| 466 | | case DEVINFO_FCT_RESET: info->reset = DEVICE_RESET_NAME( i2cmem ); break; |
| 467 | | case DEVINFO_FCT_NVRAM: info->nvram = DEVICE_NVRAM_NAME( i2cmem ); break; |
| 468 | | |
| 469 | | /* --- the following bits of info are returned as NULL-terminated strings --- */ |
| 470 | | case DEVINFO_STR_NAME: strcpy( info->s, "I2CMEM" ); break; |
| 471 | | case DEVINFO_STR_FAMILY: strcpy( info->s, "EEPROM" ); break; |
| 472 | | case DEVINFO_STR_VERSION: strcpy( info->s, "1.0" ); break; |
| 473 | | case DEVINFO_STR_SOURCE_FILE: strcpy( info->s, __FILE__ ); break; |
| 474 | | case DEVINFO_STR_CREDITS: strcpy( info->s, "Copyright Nicola Salmoria and the MAME Team" ); break; |
| 475 | | } |
| 476 | | } |
| 477 | | |
| 478 | | |
| 479 | | DEFINE_LEGACY_NVRAM_DEVICE(I2CMEM, i2cmem); |