summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/machine/eeprom28.ipp
blob: ae7736e489e86d9aa9b3678ddf002165c2cf930d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
// license:BSD-3-Clause
// copyright-holders:Christian Brunschen
/***************************************************************************

  28-series Parallel EEPROM sich as Xicor X28, Atmel AT28 etc.
  Caters for different speeds such as X28C256, X28HC256, etc.
  Caters for different storage sizes such as X28C64, X28C256, etc.

***************************************************************************/

#ifndef MAME_MACHINE_EE28_IPP
#define MAME_MACHINE_EE28_IPP

// Included here to provide context for editors; include guards in eeprom28.h
// prevent this from actually doing anything.
#include "eeprom28.h"

#pragma once

template<EEPROM28_PARAMS>
void eeprom28_device<EEPROM28_ARGS>::write(uint32_t offset, uint8_t data)
{
	m_toggle_bit = TOGGLE_BIT;

	EE28LOGMASKED(EE28_LOG_DETAIL, "%s: eeprom28.write(%04x, %02x) in state %d\n", machine().describe_context(), offset, data, m_state);
	if (m_state == STATE_PROGRAMMING) {
		// An attempt to write during a programming cycle does nothing.
		EE28LOG("IN PROGRAMMING CYCLE: writing %02x @ %04x\n", data, offset);
		return;
	}

	if (m_t_blc_usec > 0) {
		// Adjust the time remaining for more writes to the same page.
		m_start_programming_timer->adjust(attotime::from_usec(m_t_blc_usec));
	}

	if (offset >= TOTAL_SIZE_BYTES) {
		// Attempting to write outside the range of this device does nothing.
		return;
	}

	// Command sequence processing:
	if (m_state == STATE_IDLE) {
		// Detect if this is the initiation of a command sequence, but only if the current state is IDLE
		if ((offset == (0x5555 & ADDRESS_MASK)) && (data == 0xaa)){
			change_to_command_state(COMMAND_STATE_1);
		}
	} else if (m_state == STATE_BUFFERING) {
		// Detect if this is the second write in a command sequence
		if (m_command_state == COMMAND_STATE_1) {
			if ((offset == (0x2aaa & ADDRESS_MASK)) && (data == 0x55)) {
				// We're firmly within a protection command sequence.
				// Inhibit the actual writing of data during the subsequence programming cycle.
				m_program_buffer_to_eeprom = false;
				EE28LOGMASKED(EE28_LOG_DETAIL, "m_program_buffer_to_eeprom -> %d\r\n", m_program_buffer_to_eeprom);
				change_to_command_state(COMMAND_STATE_2);
			} else {
				// Not, after all in a command sequence.
				change_to_command_state(COMMAND_STATE_NONE);
			}
		} else if (m_command_state == COMMAND_STATE_2) {
			if ((offset == (0x5555 & ADDRESS_MASK)) && (data == 0xa0)) {
				// We've received a complete "enable write protection" command, so we:
				// - Enable write protection, i.e., disable writes;
				m_software_data_protection_enabled = true;
				// - Note that we are no longer in a command sequence;
				change_to_command_state(COMMAND_STATE_NONE);
				// - Also enter the overall "protected write" state to potentially accept some writes.
				change_to_state(STATE_PROTECTED_WRITE);
				// - Note that this was the last written offset
				m_last_written_offset = offset;
				// - And that concludes what we do in this write cycle.
				return;
			} else if ((offset == (0x5555 & ADDRESS_MASK)) && (data == 0x80)) {
				change_to_command_state(COMMAND_STATE_PROTECION_DISABLE_3);
			} else  {
				command_state_machine_error();
			}
		} else if (m_command_state == COMMAND_STATE_PROTECION_DISABLE_3) {
			if ((offset == (0x5555 & ADDRESS_MASK)) && (data == 0xaa)) {
				change_to_command_state(COMMAND_STATE_PROTECION_DISABLE_4);
			} else {
				command_state_machine_error();
			}
		} else if (m_command_state == COMMAND_STATE_PROTECION_DISABLE_4) {
			if ((offset == (0x2aaa & ADDRESS_MASK)) && (data == 0x55)) {
				change_to_command_state(COMMAND_STATE_PROTECION_DISABLE_5);
			} else {
				command_state_machine_error();
			}
		} else if (m_command_state == COMMAND_STATE_PROTECION_DISABLE_5) {
			if (offset == (0x5555 & ADDRESS_MASK)) {
				if constexpr (HAS_SOFTWARE_CHIP_ERASE) {
					if (data == 0x10) {
						// We have received a complete "Software Chip Erase command. So we:
						// - Leave write protection as it is!
						// - Note that we're no longer in a command sequence.
						change_to_command_state(COMMAND_STATE_NONE);
						// - The preceding writes were just part of that command sequence.
						m_program_buffer_to_eeprom = false;

						// - Start the chip erase cycle
						start_erase_cycle();
						// - and now we're done with this write.
						return;
					} else if (data == 0x20) {
						disable_software_data_protection();
						// - and now we're done with this write.
						return;
					} else {
						command_state_machine_error();
					}
				} else {
					if (data == 0x20) {
						disable_software_data_protection();
						// - and now we're done with this write.
						return;
					} else {
						command_state_machine_error();
					}
				}
			} else {
				command_state_machine_error();
			}
		}
	}

	if (m_state == STATE_IDLE || m_state == STATE_PROTECTED_WRITE) {
		// This is the first write that we will buffer.
		// At this point, the beginning of buffering data, we expect to program the data
		// we are about to buffer into EEPROM storage - unless of course we're write protected,
		// in which case we already know _not_ to program the buffer into the EEPROM.
		// If later on we detect a protection command sequence we will set this to 'false'
		// so that the command sequence (which will end up in the buffer)
		// does not get written to storage.
		m_program_buffer_to_eeprom = (!m_software_data_protection_enabled) || (m_state == STATE_PROTECTED_WRITE);
		EE28LOGMASKED(EE28_LOG_DETAIL, "m_program_buffer_to_eeprom -> %d\r\n", m_program_buffer_to_eeprom);

		// We start to buffer a set of writes.
		// We do this even if we're write protected - m_program_buffer_to_eeprom protects us.
		change_to_state(STATE_BUFFERING);

		// We note which page we're starting to buffer, copy its current contents into the buffer
		// so the buffer can be written into on a byte by byte basis, before being written back
		// to storage during the programming cycle.
		m_buffering_page = offset & PAGE_MASK;
		std::memcpy(&m_page_buffer[0], &m_storage[storage_page(m_buffering_page)], PAGE_SIZE_BYTES);
		EE28LOGMASKED(EE28_LOG_DETAIL, "%s: buffering page %04x\n", machine().describe_context(), m_buffering_page);
	}

	// Deliberately falling through after detecting the first write and changing state
	// from (IDLE or PROTECTED_WRITE) to BUFFERING

	if (m_state == STATE_BUFFERING) {
		// The datasheet for the X28C256 says that
		//   "the page address (A6 through A14) for each subsequent
		//   valid write cycle to the part during this operation must be
		//   the same as the initial page address."
		// but does not say anything about the chip verifying this, or the consequences
		// if this is no the case.
		// A valid interpretation is that the chip simply accepts the write into the buffer anyway,
		// at the appropriate offset within the page;
		// Another valid interpretation is to reject the write.
		// Here, I choose the latter: any writes to an address within a different page
		// are ignored, only those within the same page are accepted.
		if ((offset & PAGE_MASK) == m_buffering_page) {
			m_page_buffer[offset & PAGE_OFFSET_MASK] = data;
			EE28LOGMASKED(EE28_LOG_DETAIL, "%s: buffer[%02x] = %02x\n", machine().describe_context(), offset & PAGE_OFFSET_MASK, data);
		}

		// Note where the last write occurred.
		m_last_written_offset = offset;
	}

	if (m_software_data_protection_enabled) {
		EE28LOGMASKED(EE28_LOG_DETAIL, "X28C: write %02x to %x while write protected\n", data, offset);
	}
}

template <EEPROM28_PARAMS>
uint8_t eeprom28_device<EEPROM28_ARGS>::read(uint32_t offset)
{
	if (m_command_state != COMMAND_STATE_NONE) {
		// Per the X28C256 datasheet regarding the command sequence,
		//   "Note: Once initiated, the sequence of write operations
		//   should not be interrupted."
		// A read operation would seem to interrupt that sequence, and thus end that sequence.
		change_to_state(STATE_BUFFERING);
	}

	if (m_program_on_read) {
		if (m_state == STATE_BUFFERING || m_state == STATE_PROTECTED_WRITE) {
			// We have some buffered data or a change to enable write protection;
			// immediately write any buffered data to storage:
			// First, cancel any existing programming cycle timer
			if (m_t_blc_usec > 0) {
				m_start_programming_timer->enable(false);
			}
			// Then, start the programming cycle. If T_WC is 0, this will in turn also change the
			// state to STATE_IDLE.
			start_programming_cycle();
		}
	}

	// If we're currently buffering a page, then reads from the page in question
	// should be sourced from the page buffer.
	bool read_from_buffer = m_buffering_page >= 0 && (offset & PAGE_MASK) == m_buffering_page;
	uint8_t data = read_from_buffer ? m_page_buffer[offset & PAGE_OFFSET_MASK] : m_storage[storage_offset(offset)];

	if (m_program_buffer_to_eeprom || (m_state == STATE_PROGRAMMING)) {
		// "/DATA Polling"
		// While programming or preparing to progam, if the client reads back the
		// last written byte, the returned bit 7 is the inverse of what was written.
		// But according to at least one datasheet:
		//   "If the X28C64 is in the protected state and an illegal write
		//    operation is attempted /DATA Polling will not operate."
		// so we only perform this if we're actually going to be writing to storage.
		if (m_program_buffer_to_eeprom && (offset == m_last_written_offset)) {
			data = data ^ INVERSE_DATA_BIT;
		}

		// While programming or preparing to progam, bit 6 of what is returned
		// alternates between 0 and 1.
		data = data ^ m_toggle_bit;
		m_toggle_bit ^= TOGGLE_BIT;
	}

	EE28LOGMASKED(EE28_LOG_DETAIL, "%s: eeprom28.read(%04x), have %02x in %s, returning %02x\n",
		machine().describe_context(), offset,
		read_from_buffer ? m_page_buffer[offset & PAGE_OFFSET_MASK] : m_storage[storage_offset(offset)],
		read_from_buffer ? "buffer" : "storage",
		data);

	return data;
}

#endif // MAME_MACHINE_EE28_IPP