327 lines
7.7 KiB
C
327 lines
7.7 KiB
C
/*
|
|
* libtilemcore - Graphing calculator emulation library
|
|
*
|
|
* Copyright (C) 2001 Solignac Julien
|
|
* Copyright (C) 2004-2012 Benjamin Moody
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public License
|
|
* as published by the Free Software Foundation; either version 2.1 of
|
|
* the License, or (at your option) any later version.
|
|
*
|
|
* This library 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
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, see
|
|
* <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include <config.h>
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include "tilem.h"
|
|
|
|
#define FLASH_READ 0
|
|
#define FLASH_AA 1
|
|
#define FLASH_55 2
|
|
#define FLASH_PROG 3
|
|
#define FLASH_ERASE 4
|
|
#define FLASH_ERAA 5
|
|
#define FLASH_ER55 6
|
|
#define FLASH_ERROR 7
|
|
#define FLASH_FASTMODE 8
|
|
#define FLASH_FASTPROG 9
|
|
#define FLASH_FASTEXIT 10
|
|
|
|
#define FLASH_BUSY_PROGRAM 1
|
|
#define FLASH_BUSY_ERASE_WAIT 2
|
|
#define FLASH_BUSY_ERASE 3
|
|
|
|
/* Still to do:
|
|
- autoselect
|
|
- erase suspend
|
|
- fast program
|
|
- CFI
|
|
*/
|
|
|
|
#define WARN(xxx) \
|
|
tilem_warning(calc, "Flash error (" xxx ")")
|
|
#define WARN2(xxx, yyy, zzz) \
|
|
tilem_warning(calc, "Flash error (" xxx ")", (yyy), (zzz))
|
|
|
|
void tilem_flash_reset(TilemCalc* calc)
|
|
{
|
|
calc->flash.unlock = 0;
|
|
calc->flash.state = FLASH_READ;
|
|
calc->flash.busy = 0;
|
|
}
|
|
|
|
void tilem_flash_delay_timer(TilemCalc* calc, void* data TILEM_ATTR_UNUSED)
|
|
{
|
|
if (calc->flash.busy == FLASH_BUSY_ERASE_WAIT) {
|
|
calc->flash.busy = FLASH_BUSY_ERASE;
|
|
tilem_z80_set_timer(calc, TILEM_TIMER_FLASH_DELAY,
|
|
200000, 0, 1);
|
|
}
|
|
else {
|
|
calc->flash.busy = 0;
|
|
}
|
|
}
|
|
|
|
#ifdef DISABLE_FLASH_DELAY
|
|
# define set_busy(fl, bm, t)
|
|
#else
|
|
static inline void set_busy(TilemCalc* calc, int busymode, int time)
|
|
{
|
|
if (!(calc->flash.emuflags & TILEM_FLASH_REQUIRE_DELAY))
|
|
return;
|
|
|
|
calc->flash.busy = busymode;
|
|
tilem_z80_set_timer(calc, TILEM_TIMER_FLASH_DELAY,
|
|
time, 0, 1);
|
|
}
|
|
#endif
|
|
|
|
static inline void program_byte(TilemCalc* calc, dword a, byte v)
|
|
{
|
|
calc->mem[a] &= v;
|
|
calc->flash.progaddr = a;
|
|
calc->flash.progbyte = v;
|
|
|
|
if (calc->mem[a] != v) {
|
|
WARN2("bad program %02x over %02x", v, calc->mem[a]);
|
|
calc->flash.state = FLASH_ERROR;
|
|
}
|
|
else {
|
|
calc->flash.state = FLASH_READ;
|
|
}
|
|
|
|
set_busy(calc, FLASH_BUSY_PROGRAM, 7);
|
|
}
|
|
|
|
static inline void erase_sector(TilemCalc* calc, dword a, dword l)
|
|
{
|
|
dword i;
|
|
|
|
calc->flash.progaddr = a;
|
|
for (i = 0; i < l; i++)
|
|
calc->mem[a + i]=0xFF;
|
|
calc->flash.state = FLASH_READ;
|
|
|
|
set_busy(calc, FLASH_BUSY_ERASE_WAIT, 50);
|
|
}
|
|
|
|
static const TilemFlashSector* get_sector(TilemCalc* calc, dword pa)
|
|
{
|
|
int i;
|
|
const TilemFlashSector* sec;
|
|
|
|
for (i = 0; i < calc->hw.nflashsectors; i++) {
|
|
sec = &calc->hw.flashsectors[i];
|
|
if (pa >= sec->start && pa < sec->start + sec->size)
|
|
return sec;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int sector_writable(TilemCalc* calc, const TilemFlashSector* sec)
|
|
{
|
|
return !(sec->protectgroup & ~calc->flash.overridegroup);
|
|
}
|
|
|
|
byte tilem_flash_read_byte(TilemCalc* calc, dword pa)
|
|
{
|
|
byte value;
|
|
|
|
if (calc->flash.busy == FLASH_BUSY_PROGRAM) {
|
|
if (pa != calc->flash.progaddr)
|
|
WARN("reading from Flash while programming");
|
|
value = (~calc->flash.progbyte & 0x80);
|
|
value |= calc->flash.toggles;
|
|
calc->flash.toggles ^= 0x40;
|
|
return (value);
|
|
}
|
|
else if (calc->flash.busy == FLASH_BUSY_ERASE) {
|
|
if ((pa >> 16) != (calc->flash.progaddr >> 16))
|
|
WARN("reading from Flash while erasing");
|
|
value = calc->flash.toggles | 0x08;
|
|
calc->flash.toggles ^= 0x44;
|
|
return (value);
|
|
}
|
|
else if (calc->flash.busy == FLASH_BUSY_ERASE_WAIT) {
|
|
if ((pa >> 16) != (calc->flash.progaddr >> 16))
|
|
WARN("reading from Flash while erasing");
|
|
value = calc->flash.toggles;
|
|
calc->flash.toggles ^= 0x44;
|
|
return (value);
|
|
}
|
|
|
|
if (calc->flash.state == FLASH_ERROR) {
|
|
value = ((~calc->flash.progbyte & 0x80) | 0x20);
|
|
value |= calc->flash.toggles;
|
|
calc->flash.toggles ^= 0x40;
|
|
return (value);
|
|
}
|
|
else if (calc->flash.state == FLASH_FASTMODE) {
|
|
return (calc->mem[pa]);
|
|
}
|
|
else if (calc->flash.state == FLASH_READ) {
|
|
return (calc->mem[pa]);
|
|
}
|
|
else {
|
|
WARN("reading during program/erase sequence");
|
|
calc->flash.state = FLASH_READ;
|
|
return (calc->mem[pa]);
|
|
}
|
|
}
|
|
|
|
void tilem_flash_erase_address(TilemCalc* calc, dword pa)
|
|
{
|
|
const TilemFlashSector* sec = get_sector(calc, pa);
|
|
|
|
if (sector_writable(calc, sec)) {
|
|
tilem_message(calc, "Erasing Flash sector at %06x", pa);
|
|
erase_sector(calc, sec->start, sec->size);
|
|
}
|
|
else {
|
|
WARN("erasing protected sector");
|
|
}
|
|
}
|
|
|
|
void tilem_flash_write_byte(TilemCalc* calc, dword pa, byte v)
|
|
{
|
|
int oldstate;
|
|
int i;
|
|
const TilemFlashSector* sec;
|
|
|
|
if (!calc->flash.unlock)
|
|
return;
|
|
|
|
#ifndef DISABLE_FLASH_DELAY
|
|
if (calc->flash.busy == FLASH_BUSY_PROGRAM
|
|
|| calc->flash.busy == FLASH_BUSY_ERASE)
|
|
return;
|
|
#endif
|
|
|
|
oldstate = calc->flash.state;
|
|
calc->flash.state = FLASH_READ;
|
|
|
|
switch (oldstate) {
|
|
case FLASH_READ:
|
|
if (((pa&0xFFF) == 0xAAA) && (v == 0xAA))
|
|
calc->flash.state = FLASH_AA;
|
|
return;
|
|
|
|
case FLASH_AA:
|
|
if (((pa&0xFFF) == 0x555) && (v == 0x55))
|
|
calc->flash.state = FLASH_55;
|
|
else if (v != 0xF0) {
|
|
WARN2("undefined command %02x->%06x after AA", v, pa);
|
|
}
|
|
return;
|
|
|
|
case FLASH_55:
|
|
if ((pa&0xFFF) == 0xAAA) {
|
|
switch (v) {
|
|
case 0x10:
|
|
case 0x30:
|
|
WARN("attempt to erase without pre-erase");
|
|
return;
|
|
case 0x20:
|
|
//WARN("entering fast mode");
|
|
calc->flash.state = FLASH_FASTMODE;
|
|
return;
|
|
case 0x80:
|
|
calc->flash.state = FLASH_ERASE;
|
|
return;
|
|
case 0x90:
|
|
WARN("autoselect is not implemented");
|
|
return;
|
|
case 0xA0:
|
|
calc->flash.state = FLASH_PROG;
|
|
return;
|
|
}
|
|
}
|
|
if (v != 0xF0)
|
|
WARN2("undefined command %02x->%06x after AA,55", v, pa);
|
|
return;
|
|
|
|
case FLASH_PROG:
|
|
sec = get_sector(calc, pa);
|
|
if (!sector_writable(calc, sec))
|
|
WARN("programming protected sector");
|
|
else
|
|
program_byte(calc, pa, v);
|
|
return;
|
|
|
|
case FLASH_FASTMODE:
|
|
//WARN2("fast mode cmd %02x->%06x", v, pa);
|
|
if ( v == 0x90 )
|
|
calc->flash.state = FLASH_FASTEXIT;
|
|
else if ( v == 0xA0 )
|
|
calc->flash.state = FLASH_FASTPROG;
|
|
else
|
|
// TODO : figure out whether mixing is allowed on real HW
|
|
WARN2("mixing fast programming with regular programming : %02x->%06x", v, pa);
|
|
return;
|
|
|
|
case FLASH_FASTPROG:
|
|
//WARN2("fast prog %02x->%06x", v, pa);
|
|
sec = get_sector(calc, pa);
|
|
if (!sector_writable(calc, sec))
|
|
WARN("programming protected sector");
|
|
else
|
|
program_byte(calc, pa, v);
|
|
calc->flash.state = FLASH_FASTMODE;
|
|
return;
|
|
|
|
case FLASH_FASTEXIT:
|
|
//WARN("leaving fast mode");
|
|
if ( v != 0xF0 )
|
|
{
|
|
WARN2("undefined command %02x->%06x after fast mode pre-exit 90", v, pa);
|
|
// TODO : figure out whether fast mode remains in such a case
|
|
calc->flash.state = FLASH_FASTMODE;
|
|
}
|
|
return;
|
|
|
|
case FLASH_ERASE:
|
|
if (((pa&0xFFF) == 0xAAA) && (v == 0xAA))
|
|
calc->flash.state = FLASH_ERAA;
|
|
else if (v != 0xF0)
|
|
WARN2("undefined command %02x->%06x after pre-erase", v, pa);
|
|
return;
|
|
|
|
case FLASH_ERAA:
|
|
if (((pa&0xFFF) == 0x555) && (v == 0x55))
|
|
calc->flash.state = FLASH_ER55;
|
|
else if (v != 0xF0)
|
|
WARN2("undefined command %02x->%06x after pre-erase AA", v, pa);
|
|
return;
|
|
|
|
case FLASH_ER55:
|
|
if (((pa&0xFFF) == 0xAAA) && v==0x10) {
|
|
tilem_message(calc, "Erasing entire Flash chip");
|
|
|
|
for (i = 0; i < calc->hw.nflashsectors; i++) {
|
|
sec = &calc->hw.flashsectors[i];
|
|
if (sector_writable(calc, sec))
|
|
erase_sector(calc, sec->start,
|
|
sec->size);
|
|
}
|
|
}
|
|
else if (v == 0x30) {
|
|
tilem_flash_erase_address(calc, pa);
|
|
}
|
|
else if (v != 0xF0)
|
|
WARN2("undefined command %02x->%06x after pre-erase AA,55", v, pa);
|
|
return;
|
|
}
|
|
}
|