| /*  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 | 
 |     the Free Software Foundation; either version 3 of the License, or | 
 |     (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 | 
 |     along with this program; if not, see <http://www.gnu.org/licenses/>. | 
 |   | 
 |     */ | 
 |  | 
 |  | 
 | #ifndef _HW_EEPROM_C_ | 
 | #define _HW_EEPROM_C_ | 
 |  | 
 | #include "device_table.h" | 
 |  | 
 | #include <string.h> | 
 |  | 
 |  | 
 | /* 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; | 
 |   uint8_t *memory; | 
 |   unsigned sizeof_memory; | 
 |   unsigned erase_delay; | 
 |   int64_t program_start_time; | 
 |   int64_t program_finish_time; | 
 |   uint8_t manufacture_code; | 
 |   uint8_t device_code; | 
 |   uint8_t toggle_bit; | 
 |   /* initialization */ | 
 |   const char *input_file_name; | 
 |   const char *output_file_name; | 
 |   /* for sector and sector programming */ | 
 |   hw_eeprom_states sector_state; | 
 |   uint8_t *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; | 
 |   uint8_t byte_program_byte; | 
 | } hw_eeprom_device; | 
 |  | 
 | typedef struct _hw_eeprom_reg_spec { | 
 |   uint32_t base; | 
 |   uint32_t 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, | 
 | 	      uint8_t 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, | 
 | 		       uint8_t new_byte) | 
 | { | 
 |   uint8_t 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 uint8_t | 
 | toggle(hw_eeprom_device *eeprom, | 
 |        uint8_t byte) | 
 | { | 
 |   eeprom->toggle_bit = eeprom->toggle_bit ^ 0x40; /* le-bit 6 */ | 
 |   return eeprom->toggle_bit ^ byte; | 
 | } | 
 |  | 
 | static uint8_t | 
 | 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; | 
 |     uint8_t byte = read_byte(me, eeprom, address); | 
 |     ((uint8_t*)dest)[i] = byte; | 
 |   } | 
 |   return nr_bytes; | 
 | } | 
 |  | 
 |  | 
 | /* eeprom writes */ | 
 |  | 
 | static void | 
 | write_byte(device *me, | 
 | 	   hw_eeprom_device *eeprom, | 
 | 	   unsigned_word address, | 
 | 	   uint8_t 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; | 
 |     uint8_t byte = ((uint8_t*)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); | 
 |   free(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++) { | 
 |     ((uint8_t*)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] = ((uint8_t*)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_ */ |