diff options
Diffstat (limited to 'src/mess/machine/at29040a.c')
-rw-r--r-- | src/mess/machine/at29040a.c | 444 |
1 files changed, 444 insertions, 0 deletions
diff --git a/src/mess/machine/at29040a.c b/src/mess/machine/at29040a.c new file mode 100644 index 00000000000..76b9a32bf22 --- /dev/null +++ b/src/mess/machine/at29040a.c @@ -0,0 +1,444 @@ +/* + Atmel at29c040a flash EEPROM + + 512k*8 FEEPROM, organized in pages of 256 bytes. + + References: + Datasheets were found on Atmel's site (www.atmel.com) + + Raphael Nabet 2003 + + September 2010: Rewritten as device + February 2012: Rewritten as class +*/ + +#include "at29040a.h" + +#define VERBOSE 2 +#define LOG logerror + +#define FEEPROM_SIZE 0x80000 +#define SECTOR_SIZE 0x00100 +#define BOOT_BLOCK_SIZE 0x04000 + +#define ADDRESS_MASK 0x7ffff +#define SECTOR_ADDRESS_MASK 0x7ff00 +#define BYTE_ADDRESS_MASK 0x000ff + +#define PRG_TIMER 1 + +#define VERSION 0 + +/* + Constructor. +*/ +at29040a_device::at29040a_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) +: device_t(mconfig, AT29040A, "ATMEL 29040A 512K*8 FEEPROM", tag, owner, clock), + device_nvram_interface(mconfig, *this) +{ +} + +//------------------------------------------------- +// nvram_default - called to initialize NVRAM to +// its default state +//------------------------------------------------- + +void at29040a_device::nvram_default() +{ + memset(m_eememory, 0, FEEPROM_SIZE+2); +} + +//------------------------------------------------- +// nvram_read - called to read NVRAM from the +// .nv file +//------------------------------------------------- + +void at29040a_device::nvram_read(emu_file &file) +{ + file.read(m_eememory, FEEPROM_SIZE+2); +} + +//------------------------------------------------- +// nvram_write - called to write NVRAM to the +// .nv file +//------------------------------------------------- + +void at29040a_device::nvram_write(emu_file &file) +{ + m_eememory[0] = VERSION; + file.write(m_eememory, FEEPROM_SIZE+2); +} + +/* + programming timer callback +*/ +void at29040a_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) +{ + switch (m_pgm) + { + case PGM_1: + /* programming cycle timeout */ + if (VERBOSE>7) LOG("at29040a: Programming cycle timeout\n"); + m_pgm = PGM_0; + break; + + case PGM_2: + /* programming cycle start */ + if (VERBOSE>7) LOG("at29040a: Sector write start\n"); + m_pgm = PGM_3; + /* max delay 10ms, typical delay 5 to 7 ms */ + m_programming_timer->adjust(attotime::from_msec(5)); + break; + + case PGM_3: + /* programming cycle end */ + memcpy(m_eememory + 2 + (m_programming_last_offset & ~0xff), m_programming_buffer, SECTOR_SIZE); + if (VERBOSE>7) LOG("at29040a: Sector write completed at location %04x + 2\n", (m_programming_last_offset & ~0xff)); + if (m_enabling_sdb) + { + m_sdp = true; + } + if (m_disabling_sdb) + { + m_sdp = false; + } + if (VERBOSE>7) LOG("at29040a: Software data protection = %d\n", m_sdp); + + m_pgm = PGM_0; + m_enabling_sdb = false; + m_disabling_sdb = false; + + break; + + default: + if (VERBOSE>1) LOG("internal error in %s %d\n", __FILE__, __LINE__); + break; + } +} + +void at29040a_device::sync_flags() +{ + if (m_lower_bbl) m_eememory[1] |= 0x04; + else m_eememory[1] &= ~0x04; + + if (m_higher_bbl) m_eememory[1] |= 0x02; + else m_eememory[1] &= ~0x02; + + if (m_sdp) m_eememory[1] |= 0x01; + else m_eememory[1] &= ~0x01; +} + +/* + read a byte from FEEPROM +*/ +READ8_MEMBER( at29040a_device::read ) +{ + int reply; + + offset &= ADDRESS_MASK; + + /* reading in the midst of any command sequence cancels it (right???) */ + m_cmd = CMD_0; + m_long_sequence = false; + // m_higher_bbl = true; // who says that? + + sync_flags(); + + /* reading before the start of a programming cycle cancels it (right???) */ + if (m_pgm == PGM_1) + { + // attempt to access a locked out boot block: cancel programming + // command if necessary + m_pgm = PGM_0; + m_enabling_sdb = false; + m_disabling_sdb = false; + m_programming_timer->adjust(attotime::never); + } + + if (m_id_mode) + { + switch (offset) + { + case 0x00000: + reply = 0x1f; // Manufacturer code + break; + + case 0x00001: + reply = 0xa4; // Device code + break; + + case 0x00002: + reply = m_lower_bbl? 0xff : 0xfe; + break; + + case 0x7fff2: + reply = m_higher_bbl? 0xff : 0xfe; + break; + + default: + reply = 0; + break; + } + } + else if ((m_pgm == PGM_2) || (m_pgm == PGM_3)) + { + if (m_pgm == PGM_2) + { // DATA* polling starts the programming cycle (right???) + m_pgm = PGM_3; + /* max delay 10ms, typical delay 5 to 7 ms */ + m_programming_timer->adjust(attotime::from_msec(5)); + } + + reply = m_toggle_bit? 0x02 : 0x00; + m_toggle_bit = !m_toggle_bit; + + if ((offset == m_programming_last_offset) && (! (m_programming_buffer[m_programming_last_offset & 0xff] & 0x01))) + reply |= 0x01; + } + else + reply = m_eememory[offset+2]; + + if (VERBOSE>7) LOG("at29040a: %05x -> %02x\n", offset, reply); + + return reply; +} + +/* + Write a byte to FEEPROM +*/ +WRITE8_MEMBER( at29040a_device::write ) +{ + offset &= ADDRESS_MASK; + if (VERBOSE>7) LOG("at29040a: %05x <- %02x\n", offset, data); + + /* The special CFI commands assume a smaller address space according */ + /* to the specification ("address format A14-A0") */ + offs_t cfi_offset = offset & 0x7fff; + + if (m_enabling_bbl) + { + if (VERBOSE>7) LOG("at29040a: Enabling boot block lockout\n"); + m_enabling_bbl = false; + + if ((offset == 0x00000) && (data == 0x00)) + { + if (VERBOSE>7) LOG("at29040a: Enabling lower boot block lockout\n"); + m_lower_bbl = true; + sync_flags(); + return; + } + else + { + if ((offset == 0x7ffff) && (data == 0xff)) + { + if (VERBOSE>7) LOG("at29040a: Enabling higher boot block lockout\n"); + m_higher_bbl = true; + sync_flags(); + return; + } + else + { + if (VERBOSE>1) LOG("at29040a: Invalid boot block specification: %05x/%02x\n", offset, data); + } + } + } + + switch (m_cmd) + { + case CMD_0: + if ((cfi_offset == 0x5555) && (data == 0xaa)) + { + if (VERBOSE>7) LOG("at29040a: Command sequence started\n"); + m_cmd = CMD_1; + } + else + { + m_cmd = CMD_0; + m_long_sequence = false; + } + break; + + case CMD_1: + if ((cfi_offset == 0x2aaa) && (data == 0x55)) + { + m_cmd = CMD_2; + } + else + { + m_cmd = CMD_0; + m_long_sequence = false; + if (VERBOSE>7) LOG("at29040a: Command sequence aborted\n"); + } + break; + + case CMD_2: + if (cfi_offset == 0x5555) + { + if (!m_long_sequence) + if (VERBOSE>7) LOG("at29040a: Command sequence completed\n"); + + m_pgm = PGM_0; + m_enabling_sdb = false; + m_disabling_sdb = false; + m_programming_timer->adjust(attotime::never); + + /* process command */ + switch (data) + { + case 0x10: + /* Software chip erase */ + if (m_long_sequence) + { + if (m_lower_bbl || m_higher_bbl) + { + if (VERBOSE>1) LOG("at29040a: Chip erase sequence deactivated due to previous boot block lockout.\n"); + } + else + { + if (VERBOSE>7) LOG("at29040a: Erase chip\n"); + memset(m_eememory+2, 0xff, FEEPROM_SIZE); + } + } + break; + + case 0x20: + /* Software data protection disable */ + if (VERBOSE>7) LOG("at29040a: Software data protection disable\n"); + // The complete sequence is aa-55-80-aa-55-20 + // so we need a 80 before, else the sequence is invalid + if (m_long_sequence) + { + m_pgm = PGM_1; + m_disabling_sdb = true; + /* set command timeout (right???) */ + //m_programming_timer->adjust(attotime::from_usec(150), id, 0.); + } + break; + + case 0x40: + /* Boot block lockout enable */ + // Complete sequence is aa-55-80-aa-55-40 + if (VERBOSE>7) LOG("at29040a: Boot block lockout enable\n"); + if (m_long_sequence) m_enabling_bbl = true; + break; + + case 0x80: + m_long_sequence = true; + break; + + case 0x90: + /* Software product identification entry */ + if (VERBOSE>7) LOG("at29040a: Identification mode (start)\n"); + m_id_mode = true; + break; + + case 0xa0: + /* Software data protection enable */ + if (VERBOSE>7) LOG("at29040a: Software data protection enable\n"); + m_pgm = PGM_1; + m_enabling_sdb = true; + /* set command timeout (right???) */ + //m_programming_timer->adjust(attotime::from_usec(150), id, 0.); + break; + + case 0xf0: + /* Software product identification exit */ + if (VERBOSE>7) LOG("at29040a: Identification mode (end)\n"); + m_id_mode = false; + break; + } + m_cmd = CMD_0; + if (data != 0x80) m_long_sequence = false; + + /* return, because we don't want to write the EEPROM with the command byte */ + return; + } + else + { + m_cmd = CMD_0; + m_long_sequence = false; + } + } + if ((m_pgm == PGM_2) + && ((offset & ~0xff) != (m_programming_last_offset & ~0xff))) + { + /* cancel current programming cycle */ + if (VERBOSE>7) LOG("at29040a: invalid sector change (from %05x to %05x); cancel programming cycle\n",(offset & ~0xff), (m_programming_last_offset & ~0xff)); + m_pgm = PGM_0; + m_enabling_sdb = false; + m_disabling_sdb = false; + m_programming_timer->adjust(attotime::never); + } + + if (((m_pgm == PGM_0) && !m_sdp) // write directly + || (m_pgm == PGM_1)) // write after unlocking + { + if (((offset < BOOT_BLOCK_SIZE) && m_lower_bbl) + || ((offset >= FEEPROM_SIZE-BOOT_BLOCK_SIZE) && m_higher_bbl)) + { + // attempt to access a locked out boot block: cancel programming + // command if necessary + if (VERBOSE>7) LOG("at29040a: attempt to access a locked out boot block: offset = %05x, lowblock=%d, highblock=%d\n", offset, m_lower_bbl, m_higher_bbl); + + m_pgm = PGM_0; + m_enabling_sdb = false; + m_disabling_sdb = false; + } + else + { /* enter programming mode */ + if (VERBOSE>7) LOG("at29040a: enter programming mode (m_pgm=%d)\n", m_pgm); + memset(m_programming_buffer, 0xff, SECTOR_SIZE); + m_pgm = PGM_2; + } + } + if (m_pgm == PGM_2) + { + /* write data to programming buffer */ + if (VERBOSE>7) LOG("at29040a: Write data to programming buffer\n"); + m_programming_buffer[offset & 0xff] = data; + m_programming_last_offset = offset; + m_programming_timer->adjust(attotime::from_usec(150)); // next byte must be written before the timer expires + } +} + +void at29040a_device::device_start(void) +{ + m_programming_buffer = (UINT8*)malloc(SECTOR_SIZE); + m_programming_timer = timer_alloc(PRG_TIMER); + + m_eememory = (UINT8*)malloc(FEEPROM_SIZE+2); +} + +void at29040a_device::device_stop(void) +{ + free(m_programming_buffer); + free(m_eememory); +} + +void at29040a_device::device_reset(void) +{ + if (m_eememory[0] != VERSION) + { + if (VERBOSE>1) LOG("AT29040A: Warning: Version mismatch; expected %d but found %d for %s. Resetting.\n", VERSION, m_eememory[0], tag()); + m_eememory[0] = 0; + m_eememory[1] = 0; + } + + m_lower_bbl = ((m_eememory[1] & 0x04)!=0); + m_higher_bbl = ((m_eememory[1] & 0x02)!=0); + m_sdp = ((m_eememory[1] & 0x01)!=0); + + if (VERBOSE>7) LOG("at29040a (%s): LowerBBL = %d, HigherBBL = %d, SoftDataProt = %d\n", tag(), m_lower_bbl, m_higher_bbl, m_sdp); + + m_id_mode = false; + m_cmd = CMD_0; + m_enabling_bbl = false; + m_long_sequence = false; + m_pgm = PGM_0; + m_enabling_sdb = false; + m_disabling_sdb = false; + m_toggle_bit = false; + m_programming_last_offset = 0; +} + +const device_type AT29040A = &device_creator<at29040a_device>; |