1999-04-16 03:35:26 +02:00
|
|
|
/* This file is part of the program psim.
|
|
|
|
|
|
|
|
Copyright (C) 1994-1996, Andrew Cagney <cagney@highland.com.au>
|
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
2012-12-19 08:12:02 +01:00
|
|
|
the Free Software Foundation; either version 3 of the License, or
|
1999-04-16 03:35:26 +02:00
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
2012-12-19 08:18:22 +01:00
|
|
|
along with this program; if not, see <http://www.gnu.org/licenses/>.
|
1999-04-16 03:35:26 +02:00
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
#ifndef _HW_EEPROM_C_
|
|
|
|
#define _HW_EEPROM_C_
|
|
|
|
|
|
|
|
#include "device_table.h"
|
|
|
|
|
|
|
|
#ifdef HAVE_STRING_H
|
|
|
|
#include <string.h>
|
|
|
|
#else
|
|
|
|
#ifdef HAVE_STRINGS_H
|
|
|
|
#include <strings.h>
|
|
|
|
#endif
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
/* DEVICE
|
|
|
|
|
|
|
|
|
|
|
|
eeprom - JEDEC? compatible electricaly erasable programable device
|
|
|
|
|
|
|
|
|
|
|
|
DESCRIPTION
|
|
|
|
|
|
|
|
|
|
|
|
This device implements a small byte addressable EEPROM.
|
|
|
|
Programming is performed using the same write sequences as used by
|
|
|
|
standard modern EEPROM components. Writes occure in real time, the
|
|
|
|
device returning a progress value until the programing has been
|
|
|
|
completed.
|
|
|
|
|
|
|
|
It is based on the AMD 29F040 component.
|
|
|
|
|
|
|
|
|
|
|
|
PROPERTIES
|
|
|
|
|
|
|
|
|
|
|
|
reg = <address> <size> (required)
|
|
|
|
|
|
|
|
Determine where the device lives in the parents address space.
|
|
|
|
|
|
|
|
|
|
|
|
nr-sectors = <integer> (required)
|
|
|
|
|
|
|
|
When erasing an entire sector is cleared at a time. This specifies
|
|
|
|
the number of sectors in the EEPROM component.
|
|
|
|
|
|
|
|
|
|
|
|
sector-size = <integer> (required)
|
|
|
|
|
|
|
|
The number of bytes in a sector. When erasing, memory chunks of
|
|
|
|
this size are cleared.
|
|
|
|
|
|
|
|
NOTE: The product nr-sectors * sector-size does not need to map the
|
|
|
|
size specified in the reg property. If the specified size is
|
|
|
|
smaller part of the eeprom will not be accessible while if it is
|
|
|
|
larger the addresses will wrap.
|
|
|
|
|
|
|
|
|
|
|
|
byte-write-delay = <integer> (required)
|
|
|
|
|
|
|
|
Number of clock ticks before the programming of a single byte
|
|
|
|
completes.
|
|
|
|
|
|
|
|
|
|
|
|
sector-start-delay = <integer> (required)
|
|
|
|
|
|
|
|
When erasing sectors, the number of clock ticks after the sector
|
|
|
|
has been specified that the actual erase process commences.
|
|
|
|
|
|
|
|
|
|
|
|
erase-delay = <intger> (required)
|
|
|
|
|
|
|
|
Number of clock ticks before an erase program completes
|
|
|
|
|
|
|
|
|
|
|
|
manufacture-code = <integer> (required)
|
|
|
|
|
|
|
|
The one byte value returned when the auto-select manufacturer code
|
|
|
|
is read.
|
|
|
|
|
|
|
|
|
|
|
|
device-code = <integer> (required)
|
|
|
|
|
|
|
|
The one byte value returned when the auto-select device code is
|
|
|
|
read.
|
|
|
|
|
|
|
|
|
|
|
|
input-file = <file-name> (optional)
|
|
|
|
|
|
|
|
Initialize the eeprom using the specified binary file.
|
|
|
|
|
|
|
|
|
|
|
|
output-file = <file-name> (optional)
|
|
|
|
|
|
|
|
When ever the eeprom is updated, save the modified image into the
|
|
|
|
specified file.
|
|
|
|
|
|
|
|
|
|
|
|
EXAMPLES
|
|
|
|
|
|
|
|
|
|
|
|
Enable tracing of the eeprom:
|
|
|
|
|
|
|
|
| bash$ psim -t eeprom-device \
|
|
|
|
|
|
|
|
|
|
|
|
Configure something very like the Amd Am29F040 - 512byte EEPROM
|
|
|
|
(but a bit faster):
|
|
|
|
|
|
|
|
| -o '/eeprom@0xfff00000/reg 0xfff00000 0x80000' \
|
|
|
|
| -o '/eeprom@0xfff00000/nr-sectors 8' \
|
|
|
|
| -o '/eeprom@0xfff00000/sector-size 0x10000' \
|
|
|
|
| -o '/eeprom@0xfff00000/byte-write-delay 1000' \
|
|
|
|
| -o '/eeprom@0xfff00000/sector-start-delay 100' \
|
|
|
|
| -o '/eeprom@0xfff00000/erase-delay 1000' \
|
|
|
|
| -o '/eeprom@0xfff00000/manufacture-code 0x01' \
|
|
|
|
| -o '/eeprom@0xfff00000/device-code 0xa4' \
|
|
|
|
|
|
|
|
|
|
|
|
Initialize the eeprom from the file <</dev/zero>>:
|
|
|
|
|
|
|
|
| -o '/eeprom@0xfff00000/input-file /dev/zero'
|
|
|
|
|
|
|
|
|
|
|
|
BUGS
|
|
|
|
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
typedef enum {
|
|
|
|
read_reset,
|
|
|
|
write_nr_2,
|
|
|
|
write_nr_3,
|
|
|
|
write_nr_4,
|
|
|
|
write_nr_5,
|
|
|
|
write_nr_6,
|
|
|
|
byte_program,
|
|
|
|
byte_programming,
|
|
|
|
chip_erase,
|
|
|
|
sector_erase,
|
|
|
|
sector_erase_suspend,
|
|
|
|
autoselect,
|
|
|
|
} hw_eeprom_states;
|
|
|
|
|
|
|
|
static const char *
|
|
|
|
state2a(hw_eeprom_states state)
|
|
|
|
{
|
|
|
|
switch (state) {
|
|
|
|
case read_reset: return "read_reset";
|
|
|
|
case write_nr_2: return "write_nr_2";
|
|
|
|
case write_nr_3: return "write_nr_3";
|
|
|
|
case write_nr_4: return "write_nr_4";
|
|
|
|
case write_nr_5: return "write_nr_5";
|
|
|
|
case write_nr_6: return "write_nr_6";
|
|
|
|
case byte_program: return "byte_program";
|
|
|
|
case byte_programming: return "byte_programming";
|
|
|
|
case chip_erase: return "chip_erase";
|
|
|
|
case sector_erase: return "sector_erase";
|
|
|
|
case sector_erase_suspend: return "sector_erase_suspend";
|
|
|
|
case autoselect: return "autoselect";
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
typedef struct _hw_eeprom_device {
|
|
|
|
/* general */
|
|
|
|
hw_eeprom_states state;
|
|
|
|
unsigned8 *memory;
|
|
|
|
unsigned sizeof_memory;
|
|
|
|
unsigned erase_delay;
|
|
|
|
signed64 program_start_time;
|
|
|
|
signed64 program_finish_time;
|
|
|
|
unsigned8 manufacture_code;
|
|
|
|
unsigned8 device_code;
|
|
|
|
unsigned8 toggle_bit;
|
|
|
|
/* initialization */
|
|
|
|
const char *input_file_name;
|
|
|
|
const char *output_file_name;
|
|
|
|
/* for sector and sector programming */
|
|
|
|
hw_eeprom_states sector_state;
|
|
|
|
unsigned8 *sectors;
|
|
|
|
unsigned nr_sectors;
|
|
|
|
unsigned sizeof_sector;
|
|
|
|
unsigned sector_start_delay;
|
|
|
|
unsigned sector_start_time;
|
|
|
|
/* byte and byte programming */
|
|
|
|
unsigned byte_write_delay;
|
|
|
|
unsigned_word byte_program_address;
|
|
|
|
unsigned8 byte_program_byte;
|
|
|
|
} hw_eeprom_device;
|
|
|
|
|
|
|
|
typedef struct _hw_eeprom_reg_spec {
|
|
|
|
unsigned32 base;
|
|
|
|
unsigned32 size;
|
|
|
|
} hw_eeprom_reg_spec;
|
|
|
|
|
|
|
|
static void
|
|
|
|
hw_eeprom_init_data(device *me)
|
|
|
|
{
|
|
|
|
hw_eeprom_device *eeprom = (hw_eeprom_device*)device_data(me);
|
|
|
|
|
|
|
|
/* have we any input or output files */
|
|
|
|
if (device_find_property(me, "input-file") != NULL)
|
|
|
|
eeprom->input_file_name = device_find_string_property(me, "input-file");
|
|
|
|
if (device_find_property(me, "output-file") != NULL)
|
|
|
|
eeprom->input_file_name = device_find_string_property(me, "output-file");
|
|
|
|
|
|
|
|
/* figure out the sectors in the eeprom */
|
|
|
|
if (eeprom->sectors == NULL) {
|
|
|
|
eeprom->nr_sectors = device_find_integer_property(me, "nr-sectors");
|
|
|
|
eeprom->sizeof_sector = device_find_integer_property(me, "sector-size");
|
|
|
|
eeprom->sectors = zalloc(eeprom->nr_sectors);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
memset(eeprom->sectors, 0, eeprom->nr_sectors);
|
|
|
|
|
|
|
|
/* initialize the eeprom */
|
|
|
|
if (eeprom->memory == NULL) {
|
|
|
|
eeprom->sizeof_memory = eeprom->sizeof_sector * eeprom->nr_sectors;
|
|
|
|
eeprom->memory = zalloc(eeprom->sizeof_memory);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
memset(eeprom->memory, 0, eeprom->sizeof_memory);
|
|
|
|
if (eeprom->input_file_name != NULL) {
|
|
|
|
int i;
|
|
|
|
FILE *input_file = fopen(eeprom->input_file_name, "r");
|
|
|
|
if (input_file == NULL) {
|
|
|
|
perror("eeprom");
|
|
|
|
device_error(me, "Failed to open input file %s\n", eeprom->input_file_name);
|
|
|
|
}
|
|
|
|
for (i = 0; i < eeprom->sizeof_memory; i++) {
|
|
|
|
if (fread(&eeprom->memory[i], 1, 1, input_file) != 1)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
fclose(input_file);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* timing */
|
|
|
|
eeprom->byte_write_delay = device_find_integer_property(me, "byte-write-delay");
|
|
|
|
eeprom->sector_start_delay = device_find_integer_property(me, "sector-start-delay");
|
|
|
|
eeprom->erase_delay = device_find_integer_property(me, "erase-delay");
|
|
|
|
|
|
|
|
/* misc */
|
|
|
|
eeprom->manufacture_code = device_find_integer_property(me, "manufacture-code");
|
|
|
|
eeprom->device_code = device_find_integer_property(me, "device-code");
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
invalid_read(device *me,
|
|
|
|
hw_eeprom_states state,
|
|
|
|
unsigned_word address,
|
|
|
|
const char *reason)
|
|
|
|
{
|
|
|
|
DTRACE(eeprom, ("Invalid read to 0x%lx while in state %s (%s)\n",
|
|
|
|
(unsigned long)address,
|
|
|
|
state2a(state),
|
|
|
|
reason));
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
invalid_write(device *me,
|
|
|
|
hw_eeprom_states state,
|
|
|
|
unsigned_word address,
|
|
|
|
unsigned8 data,
|
|
|
|
const char *reason)
|
|
|
|
{
|
|
|
|
DTRACE(eeprom, ("Invalid write of 0x%lx to 0x%lx while in state %s (%s)\n",
|
|
|
|
(unsigned long)data,
|
|
|
|
(unsigned long)address,
|
|
|
|
state2a(state),
|
|
|
|
reason));
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
dump_eeprom(device *me,
|
|
|
|
hw_eeprom_device *eeprom)
|
|
|
|
{
|
|
|
|
if (eeprom->output_file_name != NULL) {
|
|
|
|
int i;
|
|
|
|
FILE *output_file = fopen(eeprom->output_file_name, "w");
|
|
|
|
if (output_file == NULL) {
|
|
|
|
perror("eeprom");
|
|
|
|
device_error(me, "Failed to open output file %s\n",
|
|
|
|
eeprom->output_file_name);
|
|
|
|
}
|
|
|
|
for (i = 0; i < eeprom->sizeof_memory; i++) {
|
|
|
|
if (fwrite(&eeprom->memory[i], 1, 1, output_file) != 1)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
fclose(output_file);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* program a single byte of eeprom */
|
|
|
|
|
|
|
|
static void
|
|
|
|
start_programming_byte(device *me,
|
|
|
|
hw_eeprom_device *eeprom,
|
|
|
|
unsigned_word address,
|
|
|
|
unsigned8 new_byte)
|
|
|
|
{
|
|
|
|
unsigned8 old_byte = eeprom->memory[address];
|
|
|
|
DTRACE(eeprom, ("start-programing-byte - address 0x%lx, new 0x%lx, old 0x%lx\n",
|
|
|
|
(unsigned long)address,
|
|
|
|
(unsigned long)new_byte,
|
|
|
|
(unsigned long)old_byte));
|
|
|
|
eeprom->byte_program_address = address;
|
|
|
|
/* : old new : ~old : new&~old
|
|
|
|
: 0 0 : 1 : 0
|
|
|
|
: 0 1 : 1 : 1 -- can not set a bit
|
|
|
|
: 1 0 : 0 : 0
|
|
|
|
: 1 1 : 0 : 0 */
|
|
|
|
if (~old_byte & new_byte)
|
|
|
|
invalid_write(me, eeprom->state, address, new_byte, "setting cleared bit");
|
|
|
|
/* : old new : old&new
|
|
|
|
: 0 0 : 0
|
|
|
|
: 0 1 : 0
|
|
|
|
: 1 0 : 0
|
|
|
|
: 1 1 : 1 */
|
|
|
|
eeprom->byte_program_byte = new_byte & old_byte;
|
|
|
|
eeprom->memory[address] = ~new_byte & ~0x24; /* LE-bits 5:3 zero */
|
|
|
|
eeprom->program_start_time = device_event_queue_time(me);
|
|
|
|
eeprom->program_finish_time = (eeprom->program_start_time
|
|
|
|
+ eeprom->byte_write_delay);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
finish_programming_byte(device *me,
|
|
|
|
hw_eeprom_device *eeprom)
|
|
|
|
{
|
|
|
|
DTRACE(eeprom, ("finish-programming-byte - address 0x%lx, byte 0x%lx\n",
|
|
|
|
(unsigned long)eeprom->byte_program_address,
|
|
|
|
(unsigned long)eeprom->byte_program_byte));
|
|
|
|
eeprom->memory[eeprom->byte_program_address] = eeprom->byte_program_byte;
|
|
|
|
dump_eeprom(me, eeprom);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* erase the eeprom completly */
|
|
|
|
|
|
|
|
static void
|
|
|
|
start_erasing_chip(device *me,
|
|
|
|
hw_eeprom_device *eeprom)
|
|
|
|
{
|
|
|
|
DTRACE(eeprom, ("start-erasing-chip\n"));
|
|
|
|
memset(eeprom->memory, 0, eeprom->sizeof_memory);
|
|
|
|
eeprom->program_start_time = device_event_queue_time(me);
|
|
|
|
eeprom->program_finish_time = (eeprom->program_start_time
|
|
|
|
+ eeprom->erase_delay);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
finish_erasing_chip(device *me,
|
|
|
|
hw_eeprom_device *eeprom)
|
|
|
|
{
|
|
|
|
DTRACE(eeprom, ("finish-erasing-chip\n"));
|
|
|
|
memset(eeprom->memory, 0xff, eeprom->sizeof_memory);
|
|
|
|
dump_eeprom(me, eeprom);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* erase a single sector of the eeprom */
|
|
|
|
|
|
|
|
static void
|
|
|
|
start_erasing_sector(device *me,
|
|
|
|
hw_eeprom_device *eeprom,
|
|
|
|
unsigned_word address)
|
|
|
|
{
|
|
|
|
int sector = address / eeprom->sizeof_sector;
|
|
|
|
DTRACE(eeprom, ("start-erasing-sector - address 0x%lx, sector %d\n",
|
|
|
|
(unsigned long)address, sector));
|
|
|
|
ASSERT(sector < eeprom->nr_sectors);
|
|
|
|
eeprom->sectors[sector] = 1;
|
|
|
|
memset(eeprom->memory + sector * eeprom->sizeof_sector,
|
|
|
|
0x4, eeprom->sizeof_sector);
|
|
|
|
eeprom->program_start_time = device_event_queue_time(me);
|
|
|
|
eeprom->sector_start_time = (eeprom->program_start_time
|
|
|
|
+ eeprom->sector_start_delay);
|
|
|
|
eeprom->program_finish_time = (eeprom->sector_start_time
|
|
|
|
+ eeprom->erase_delay);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
finish_erasing_sector(device *me,
|
|
|
|
hw_eeprom_device *eeprom)
|
|
|
|
{
|
|
|
|
int sector;
|
|
|
|
DTRACE(eeprom, ("finish-erasing-sector\n"));
|
|
|
|
for (sector = 0; sector < eeprom->nr_sectors; sector++) {
|
|
|
|
if (eeprom->sectors[sector]) {
|
|
|
|
eeprom->sectors[sector] = 0;
|
|
|
|
memset(eeprom->memory + sector * eeprom->sizeof_sector,
|
|
|
|
0xff, eeprom->sizeof_sector);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
dump_eeprom(me, eeprom);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* eeprom reads */
|
|
|
|
|
|
|
|
static unsigned8
|
|
|
|
toggle(hw_eeprom_device *eeprom,
|
|
|
|
unsigned8 byte)
|
|
|
|
{
|
|
|
|
eeprom->toggle_bit = eeprom->toggle_bit ^ 0x40; /* le-bit 6 */
|
|
|
|
return eeprom->toggle_bit ^ byte;
|
|
|
|
}
|
|
|
|
|
|
|
|
static unsigned8
|
|
|
|
read_byte(device *me,
|
|
|
|
hw_eeprom_device *eeprom,
|
|
|
|
unsigned_word address)
|
|
|
|
{
|
|
|
|
/* may need multiple iterations of this */
|
|
|
|
while (1) {
|
|
|
|
switch (eeprom->state) {
|
|
|
|
|
|
|
|
case read_reset:
|
|
|
|
return eeprom->memory[address];
|
|
|
|
|
|
|
|
case autoselect:
|
|
|
|
if ((address & 0xff) == 0x00)
|
|
|
|
return eeprom->manufacture_code;
|
|
|
|
else if ((address & 0xff) == 0x01)
|
|
|
|
return eeprom->device_code;
|
|
|
|
else
|
|
|
|
return 0; /* not certain about this */
|
|
|
|
|
|
|
|
case byte_programming:
|
|
|
|
if (device_event_queue_time(me) > eeprom->program_finish_time) {
|
|
|
|
finish_programming_byte(me, eeprom);
|
|
|
|
eeprom->state = read_reset;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
else if (address == eeprom->byte_program_address) {
|
|
|
|
return toggle(eeprom, eeprom->memory[address]);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
/* trash that memory location */
|
|
|
|
invalid_read(me, eeprom->state, address, "not byte program address");
|
|
|
|
eeprom->memory[address] = (eeprom->memory[address]
|
|
|
|
& eeprom->byte_program_byte);
|
|
|
|
return toggle(eeprom, eeprom->memory[eeprom->byte_program_address]);
|
|
|
|
}
|
|
|
|
|
|
|
|
case chip_erase:
|
|
|
|
if (device_event_queue_time(me) > eeprom->program_finish_time) {
|
|
|
|
finish_erasing_chip(me, eeprom);
|
|
|
|
eeprom->state = read_reset;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return toggle(eeprom, eeprom->memory[address]);
|
|
|
|
}
|
|
|
|
|
|
|
|
case sector_erase:
|
|
|
|
if (device_event_queue_time(me) > eeprom->program_finish_time) {
|
|
|
|
finish_erasing_sector(me, eeprom);
|
|
|
|
eeprom->state = read_reset;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
else if (!eeprom->sectors[address / eeprom->sizeof_sector]) {
|
|
|
|
/* read to wrong sector */
|
|
|
|
invalid_read(me, eeprom->state, address, "sector not being erased");
|
|
|
|
return toggle(eeprom, eeprom->memory[address]) & ~0x8;
|
|
|
|
}
|
|
|
|
else if (device_event_queue_time(me) > eeprom->sector_start_time) {
|
|
|
|
return toggle(eeprom, eeprom->memory[address]) | 0x8;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return toggle(eeprom, eeprom->memory[address]) & ~0x8;
|
|
|
|
}
|
|
|
|
|
|
|
|
case sector_erase_suspend:
|
|
|
|
if (!eeprom->sectors[address / eeprom->sizeof_sector]) {
|
|
|
|
return eeprom->memory[address];
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
invalid_read(me, eeprom->state, address, "sector being erased");
|
|
|
|
return eeprom->memory[address];
|
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
|
|
|
invalid_read(me, eeprom->state, address, "invalid state");
|
|
|
|
return eeprom->memory[address];
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static unsigned
|
|
|
|
hw_eeprom_io_read_buffer(device *me,
|
|
|
|
void *dest,
|
|
|
|
int space,
|
|
|
|
unsigned_word addr,
|
|
|
|
unsigned nr_bytes,
|
|
|
|
cpu *processor,
|
|
|
|
unsigned_word cia)
|
|
|
|
{
|
|
|
|
hw_eeprom_device *eeprom = (hw_eeprom_device*)device_data(me);
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < nr_bytes; i++) {
|
|
|
|
unsigned_word address = (addr + i) % eeprom->sizeof_memory;
|
|
|
|
unsigned8 byte = read_byte(me, eeprom, address);
|
|
|
|
((unsigned8*)dest)[i] = byte;
|
|
|
|
}
|
|
|
|
return nr_bytes;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* eeprom writes */
|
|
|
|
|
|
|
|
static void
|
|
|
|
write_byte(device *me,
|
|
|
|
hw_eeprom_device *eeprom,
|
|
|
|
unsigned_word address,
|
|
|
|
unsigned8 data)
|
|
|
|
{
|
|
|
|
/* may need multiple transitions to process a write */
|
|
|
|
while (1) {
|
|
|
|
switch (eeprom->state) {
|
|
|
|
|
|
|
|
case read_reset:
|
|
|
|
if (address == 0x5555 && data == 0xaa)
|
|
|
|
eeprom->state = write_nr_2;
|
|
|
|
else if (data == 0xf0)
|
|
|
|
eeprom->state = read_reset;
|
|
|
|
else {
|
|
|
|
invalid_write(me, eeprom->state, address, data, "unexpected");
|
|
|
|
eeprom->state = read_reset;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
|
|
|
|
case write_nr_2:
|
|
|
|
if (address == 0x2aaa && data == 0x55)
|
|
|
|
eeprom->state = write_nr_3;
|
|
|
|
else {
|
|
|
|
invalid_write(me, eeprom->state, address, data, "unexpected");
|
|
|
|
eeprom->state = read_reset;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
|
|
|
|
case write_nr_3:
|
|
|
|
if (address == 0x5555 && data == 0xf0)
|
|
|
|
eeprom->state = read_reset;
|
|
|
|
else if (address == 0x5555 && data == 0x90)
|
|
|
|
eeprom->state = autoselect;
|
|
|
|
else if (address == 0x5555 && data == 0xa0) {
|
|
|
|
eeprom->state = byte_program;
|
|
|
|
}
|
|
|
|
else if (address == 0x5555 && data == 0x80)
|
|
|
|
eeprom->state = write_nr_4;
|
|
|
|
else {
|
|
|
|
invalid_write(me, eeprom->state, address, data, "unexpected");
|
|
|
|
eeprom->state = read_reset;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
|
|
|
|
case write_nr_4:
|
|
|
|
if (address == 0x5555 && data == 0xaa)
|
|
|
|
eeprom->state = write_nr_5;
|
|
|
|
else {
|
|
|
|
invalid_write(me, eeprom->state, address, data, "unexpected");
|
|
|
|
eeprom->state = read_reset;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
|
|
|
|
case write_nr_5:
|
|
|
|
if (address == 0x2aaa && data == 0x55)
|
|
|
|
eeprom->state = write_nr_6;
|
|
|
|
else {
|
|
|
|
invalid_write(me, eeprom->state, address, data, "unexpected");
|
|
|
|
eeprom->state = read_reset;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
|
|
|
|
case write_nr_6:
|
|
|
|
if (address == 0x5555 && data == 0x10) {
|
|
|
|
start_erasing_chip(me, eeprom);
|
|
|
|
eeprom->state = chip_erase;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
start_erasing_sector(me, eeprom, address);
|
|
|
|
eeprom->sector_state = read_reset;
|
|
|
|
eeprom->state = sector_erase;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
|
|
|
|
case autoselect:
|
|
|
|
if (data == 0xf0)
|
|
|
|
eeprom->state = read_reset;
|
|
|
|
else if (address == 0x5555 && data == 0xaa)
|
|
|
|
eeprom->state = write_nr_2;
|
|
|
|
else {
|
|
|
|
invalid_write(me, eeprom->state, address, data, "unsupported address");
|
|
|
|
eeprom->state = read_reset;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
|
|
|
|
case byte_program:
|
|
|
|
start_programming_byte(me, eeprom, address, data);
|
|
|
|
eeprom->state = byte_programming;
|
|
|
|
return;
|
|
|
|
|
|
|
|
case byte_programming:
|
|
|
|
if (device_event_queue_time(me) > eeprom->program_finish_time) {
|
|
|
|
finish_programming_byte(me, eeprom);
|
|
|
|
eeprom->state = read_reset;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
/* ignore it */
|
|
|
|
return;
|
|
|
|
|
|
|
|
case chip_erase:
|
|
|
|
if (device_event_queue_time(me) > eeprom->program_finish_time) {
|
|
|
|
finish_erasing_chip(me, eeprom);
|
|
|
|
eeprom->state = read_reset;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
/* ignore it */
|
|
|
|
return;
|
|
|
|
|
|
|
|
case sector_erase:
|
|
|
|
if (device_event_queue_time(me) > eeprom->program_finish_time) {
|
|
|
|
finish_erasing_sector(me, eeprom);
|
|
|
|
eeprom->state = eeprom->sector_state;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
else if (device_event_queue_time(me) > eeprom->sector_start_time
|
|
|
|
&& data == 0xb0) {
|
|
|
|
eeprom->sector_state = read_reset;
|
|
|
|
eeprom->state = sector_erase_suspend;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (eeprom->sector_state == read_reset
|
|
|
|
&& address == 0x5555 && data == 0xaa)
|
|
|
|
eeprom->sector_state = write_nr_2;
|
|
|
|
else if (eeprom->sector_state == write_nr_2
|
|
|
|
&& address == 0x2aaa && data == 0x55)
|
|
|
|
eeprom->sector_state = write_nr_3;
|
|
|
|
else if (eeprom->sector_state == write_nr_3
|
|
|
|
&& address == 0x5555 && data == 0x80)
|
|
|
|
eeprom->sector_state = write_nr_4;
|
|
|
|
else if (eeprom->sector_state == write_nr_4
|
|
|
|
&& address == 0x5555 && data == 0xaa)
|
|
|
|
eeprom->sector_state = write_nr_5;
|
|
|
|
else if (eeprom->sector_state == write_nr_5
|
|
|
|
&& address == 0x2aaa && data == 0x55)
|
|
|
|
eeprom->sector_state = write_nr_6;
|
|
|
|
else if (eeprom->sector_state == write_nr_6
|
|
|
|
&& address != 0x5555 && data == 0x30) {
|
|
|
|
if (device_event_queue_time(me) > eeprom->sector_start_time) {
|
|
|
|
DTRACE(eeprom, ("sector erase command after window closed\n"));
|
|
|
|
eeprom->sector_state = read_reset;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
start_erasing_sector(me, eeprom, address);
|
|
|
|
eeprom->sector_state = read_reset;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
invalid_write(me, eeprom->state, address, data, state2a(eeprom->sector_state));
|
|
|
|
eeprom->state = read_reset;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
|
|
|
|
case sector_erase_suspend:
|
|
|
|
if (data == 0x30)
|
|
|
|
eeprom->state = sector_erase;
|
|
|
|
else {
|
|
|
|
invalid_write(me, eeprom->state, address, data, "not resume command");
|
|
|
|
eeprom->state = read_reset;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static unsigned
|
|
|
|
hw_eeprom_io_write_buffer(device *me,
|
|
|
|
const void *source,
|
|
|
|
int space,
|
|
|
|
unsigned_word addr,
|
|
|
|
unsigned nr_bytes,
|
|
|
|
cpu *processor,
|
|
|
|
unsigned_word cia)
|
|
|
|
{
|
|
|
|
hw_eeprom_device *eeprom = (hw_eeprom_device*)device_data(me);
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < nr_bytes; i++) {
|
|
|
|
unsigned_word address = (addr + i) % eeprom->sizeof_memory;
|
|
|
|
unsigned8 byte = ((unsigned8*)source)[i];
|
|
|
|
write_byte(me, eeprom, address, byte);
|
|
|
|
}
|
|
|
|
return nr_bytes;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* An instance of the eeprom */
|
|
|
|
|
|
|
|
typedef struct _hw_eeprom_instance {
|
|
|
|
unsigned_word pos;
|
|
|
|
hw_eeprom_device *eeprom;
|
|
|
|
device *me;
|
|
|
|
} hw_eeprom_instance;
|
|
|
|
|
|
|
|
static void
|
|
|
|
hw_eeprom_instance_delete(device_instance *instance)
|
|
|
|
{
|
|
|
|
hw_eeprom_instance *data = device_instance_data(instance);
|
2011-02-14 06:14:28 +01:00
|
|
|
free(data);
|
1999-04-16 03:35:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
hw_eeprom_instance_read(device_instance *instance,
|
|
|
|
void *buf,
|
|
|
|
unsigned_word len)
|
|
|
|
{
|
|
|
|
hw_eeprom_instance *data = device_instance_data(instance);
|
|
|
|
int i;
|
|
|
|
if (data->eeprom->state != read_reset)
|
|
|
|
DITRACE(eeprom, ("eeprom not idle during instance read\n"));
|
|
|
|
for (i = 0; i < len; i++) {
|
|
|
|
((unsigned8*)buf)[i] = data->eeprom->memory[data->pos];
|
|
|
|
data->pos = (data->pos + 1) % data->eeprom->sizeof_memory;
|
|
|
|
}
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
hw_eeprom_instance_write(device_instance *instance,
|
|
|
|
const void *buf,
|
|
|
|
unsigned_word len)
|
|
|
|
{
|
|
|
|
hw_eeprom_instance *data = device_instance_data(instance);
|
|
|
|
int i;
|
|
|
|
if (data->eeprom->state != read_reset)
|
|
|
|
DITRACE(eeprom, ("eeprom not idle during instance write\n"));
|
|
|
|
for (i = 0; i < len; i++) {
|
|
|
|
data->eeprom->memory[data->pos] = ((unsigned8*)buf)[i];
|
|
|
|
data->pos = (data->pos + 1) % data->eeprom->sizeof_memory;
|
|
|
|
}
|
|
|
|
dump_eeprom(data->me, data->eeprom);
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
hw_eeprom_instance_seek(device_instance *instance,
|
|
|
|
unsigned_word pos_hi,
|
|
|
|
unsigned_word pos_lo)
|
|
|
|
{
|
|
|
|
hw_eeprom_instance *data = device_instance_data(instance);
|
|
|
|
if (pos_lo >= data->eeprom->sizeof_memory)
|
|
|
|
device_error(data->me, "seek value 0x%lx out of range\n",
|
|
|
|
(unsigned long)pos_lo);
|
|
|
|
data->pos = pos_lo;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const device_instance_callbacks hw_eeprom_instance_callbacks = {
|
|
|
|
hw_eeprom_instance_delete,
|
|
|
|
hw_eeprom_instance_read,
|
|
|
|
hw_eeprom_instance_write,
|
|
|
|
hw_eeprom_instance_seek,
|
|
|
|
};
|
|
|
|
|
|
|
|
static device_instance *
|
|
|
|
hw_eeprom_create_instance(device *me,
|
|
|
|
const char *path,
|
|
|
|
const char *args)
|
|
|
|
{
|
|
|
|
hw_eeprom_device *eeprom = device_data(me);
|
|
|
|
hw_eeprom_instance *data = ZALLOC(hw_eeprom_instance);
|
|
|
|
data->eeprom = eeprom;
|
|
|
|
data->me = me;
|
|
|
|
return device_create_instance_from(me, NULL,
|
|
|
|
data,
|
|
|
|
path, args,
|
|
|
|
&hw_eeprom_instance_callbacks);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static device_callbacks const hw_eeprom_callbacks = {
|
|
|
|
{ generic_device_init_address,
|
|
|
|
hw_eeprom_init_data },
|
|
|
|
{ NULL, }, /* address */
|
|
|
|
{ hw_eeprom_io_read_buffer,
|
|
|
|
hw_eeprom_io_write_buffer }, /* IO */
|
|
|
|
{ NULL, }, /* DMA */
|
|
|
|
{ NULL, }, /* interrupt */
|
|
|
|
{ NULL, }, /* unit */
|
|
|
|
hw_eeprom_create_instance,
|
|
|
|
};
|
|
|
|
|
|
|
|
static void *
|
|
|
|
hw_eeprom_create(const char *name,
|
|
|
|
const device_unit *unit_address,
|
|
|
|
const char *args)
|
|
|
|
{
|
|
|
|
hw_eeprom_device *eeprom = ZALLOC(hw_eeprom_device);
|
|
|
|
return eeprom;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const device_descriptor hw_eeprom_device_descriptor[] = {
|
|
|
|
{ "eeprom", hw_eeprom_create, &hw_eeprom_callbacks },
|
|
|
|
{ NULL },
|
|
|
|
};
|
|
|
|
|
|
|
|
#endif /* _HW_EEPROM_C_ */
|