#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
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 {
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;
const char *input_file_name;
const char *output_file_name;
hw_eeprom_states sector_state;
unsigned8 *sectors;
unsigned nr_sectors;
unsigned sizeof_sector;
unsigned sector_start_delay;
unsigned sector_start_time;
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);
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");
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);
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);
}
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");
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);
}
}
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;
if (~old_byte & new_byte)
invalid_write(me, eeprom->state, address, new_byte, "setting cleared bit");
eeprom->byte_program_byte = new_byte & old_byte;
eeprom->memory[address] = ~new_byte & ~0x24;
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);
}
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);
}
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);
}
static unsigned8
toggle(hw_eeprom_device *eeprom,
unsigned8 byte)
{
eeprom->toggle_bit = eeprom->toggle_bit ^ 0x40;
return eeprom->toggle_bit ^ byte;
}
static unsigned8
read_byte(device *me,
hw_eeprom_device *eeprom,
unsigned_word address)
{
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;
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 {
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]) {
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;
}
static void
write_byte(device *me,
hw_eeprom_device *eeprom,
unsigned_word address,
unsigned8 data)
{
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;
}
return;
case chip_erase:
if (device_event_queue_time(me) > eeprom->program_finish_time) {
finish_erasing_chip(me, eeprom);
eeprom->state = read_reset;
continue;
}
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;
}
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);
zfree(data);
}
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, },
{ hw_eeprom_io_read_buffer,
hw_eeprom_io_write_buffer },
{ NULL, },
{ NULL, },
{ NULL, },
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