summaryrefslogtreecommitdiffstatshomepage
path: root/src/mess/machine/at29040a.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/mess/machine/at29040a.c')
-rw-r--r--src/mess/machine/at29040a.c444
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>;