497 lines
12 KiB
C
497 lines
12 KiB
C
/*
|
|
* flash_4mbit.c
|
|
*
|
|
* Created on: Jul 27, 2021
|
|
* Author: Doug
|
|
*
|
|
* Copyright (C) 2011-2021 Doug Brown
|
|
*
|
|
* 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 2
|
|
* 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, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
*/
|
|
|
|
#include "flash_4mbit.h"
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
// TODO: Can we use a timer after an erase operation begins?
|
|
// I'd rather not base the emulation on the number of confirm read cycles
|
|
// we get...
|
|
|
|
static void UpdateGPIOPin(GPIOSimDevice *device, GPIOPin pin, bool high);
|
|
static GPIOSimValue ReadGPIOPin(GPIOSimDevice *device, GPIOPin pin);
|
|
static void HandleWriteCycle(Flash4MBit *f, uint32_t address, uint8_t data);
|
|
static void HandleReadCycle(Flash4MBit *f);
|
|
static int8_t DataPinIndex(Flash4MBit *f, GPIOPin pin);
|
|
static int8_t AddressPinIndex(Flash4MBit *f, GPIOPin pin);
|
|
static bool IsCSPin(Flash4MBit *f, GPIOPin pin);
|
|
static bool IsOEPin(Flash4MBit *f, GPIOPin pin);
|
|
static bool IsWEPin(Flash4MBit *f, GPIOPin pin);
|
|
static uint32_t UnlockAddressMask(Flash4MBit *f);
|
|
static uint8_t ManufacturerID(Flash4MBit *f);
|
|
static uint8_t DeviceID(Flash4MBit *f);
|
|
static void EraseChip(Flash4MBit *f);
|
|
static void EraseSector(Flash4MBit *f, uint32_t address);
|
|
|
|
static const GPIOSimDeviceFunctions functions = {
|
|
.drivePin = UpdateGPIOPin,
|
|
.readPin = ReadGPIOPin,
|
|
};
|
|
|
|
enum FlashState
|
|
{
|
|
FlashReading,
|
|
FlashUnlocking,
|
|
FlashWaitingForCommand,
|
|
FlashSoftwareID,
|
|
FlashProgrammingByte,
|
|
FlashEraseUnlocking1,
|
|
FlashEraseUnlocking2,
|
|
FlashEraseWaitingForCommand,
|
|
FlashReadingWriteOrEraseStatus,
|
|
};
|
|
|
|
/**
|
|
* @brief Initializes emulation for a single 4-megabit flash chip
|
|
* @param f The flash chip object
|
|
* @param t The type of flash chip being emulated
|
|
*/
|
|
void Flash4MBit_Init(Flash4MBit *f, Flash4MBitDeviceType t)
|
|
{
|
|
f->base.functions = &functions;
|
|
f->type = t;
|
|
f->dataInput = 0;
|
|
f->address = 0;
|
|
f->latchedWriteAddress = 0;
|
|
f->csAsserted = false;
|
|
f->oeAsserted = false;
|
|
f->weAsserted = false;
|
|
f->softwareIDActivated = false;
|
|
f->state = FlashReading;
|
|
f->content = malloc(512*1024);
|
|
|
|
// TODO: Dealloc content
|
|
}
|
|
|
|
void Flash4MBit_SetControlPins(Flash4MBit *f, GPIOPin cs, GPIOPin oe, GPIOPin we)
|
|
{
|
|
f->csPin = cs;
|
|
f->oePin = oe;
|
|
f->wePin = we;
|
|
}
|
|
|
|
void Flash4MBit_SetDataPin(Flash4MBit *f, uint8_t index, GPIOPin pin)
|
|
{
|
|
if (index < FLASH_4MBIT_DATA_PINS)
|
|
{
|
|
f->dataPins[index] = pin;
|
|
}
|
|
}
|
|
|
|
void Flash4MBit_SetAddressPin(Flash4MBit *f, uint8_t index, GPIOPin pin)
|
|
{
|
|
if (index < FLASH_4MBIT_ADDR_PINS)
|
|
{
|
|
f->addressPins[index] = pin;
|
|
}
|
|
}
|
|
|
|
static void UpdateGPIOPin(GPIOSimDevice *device, GPIOPin pin, bool high)
|
|
{
|
|
Flash4MBit *f = (Flash4MBit *)device;
|
|
int8_t pinIndex;
|
|
|
|
if (IsCSPin(f, pin))
|
|
{
|
|
if (f->csAsserted != !high)
|
|
{
|
|
f->csAsserted = !high;
|
|
|
|
// If WE is asserted, latch stuff...
|
|
if (f->weAsserted)
|
|
{
|
|
// Latch the address on the falling edge of CS
|
|
if (f->csAsserted)
|
|
{
|
|
f->latchedWriteAddress = f->address;
|
|
}
|
|
// Latch the data (and update state) on the rising edge of CS
|
|
else
|
|
{
|
|
HandleWriteCycle(f, f->latchedWriteAddress, f->dataInput);
|
|
}
|
|
}
|
|
else if (f->oeAsserted)
|
|
{
|
|
if (!f->csAsserted)
|
|
{
|
|
// Handle a read cycle when CS is deasserted while OE is asserted.
|
|
// The data isn't really important; it's more of a state machine update
|
|
HandleReadCycle(f);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (IsOEPin(f, pin))
|
|
{
|
|
if (f->oeAsserted != !high)
|
|
{
|
|
f->oeAsserted = !high;
|
|
|
|
// If CS is asserted, latch stuff...
|
|
if (f->csAsserted)
|
|
{
|
|
if (!f->oeAsserted)
|
|
{
|
|
// Handle a read cycle when OE is deasserted while CS is asserted.
|
|
// The data isn't really important; it's more of a state machine update
|
|
HandleReadCycle(f);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (IsWEPin(f, pin))
|
|
{
|
|
if (f->weAsserted != !high)
|
|
{
|
|
f->weAsserted = !high;
|
|
|
|
// If CS is asserted, latch stuff...
|
|
if (f->csAsserted)
|
|
{
|
|
// Latch the address on the falling edge of WE
|
|
if (f->weAsserted)
|
|
{
|
|
f->latchedWriteAddress = f->address;
|
|
}
|
|
// Latch the data (and update state) on the rising edge of WE
|
|
else
|
|
{
|
|
HandleWriteCycle(f, f->latchedWriteAddress, f->dataInput);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if ((pinIndex = DataPinIndex(f, pin)) >= 0)
|
|
{
|
|
// Update the current data input state
|
|
if (high)
|
|
{
|
|
f->dataInput |= (1UL << pinIndex);
|
|
}
|
|
else
|
|
{
|
|
f->dataInput &= ~(1UL << pinIndex);
|
|
}
|
|
}
|
|
else if ((pinIndex = AddressPinIndex(f, pin)) >= 0)
|
|
{
|
|
// Update the current address
|
|
if (high)
|
|
{
|
|
f->address |= (1UL << pinIndex);
|
|
}
|
|
else
|
|
{
|
|
f->address &= ~(1UL << pinIndex);
|
|
}
|
|
}
|
|
}
|
|
|
|
static GPIOSimValue ReadGPIOPin(GPIOSimDevice *device, GPIOPin pin)
|
|
{
|
|
Flash4MBit *f = (Flash4MBit *)device;
|
|
|
|
// We only read when allowed
|
|
if (f->csAsserted && f->oeAsserted)
|
|
{
|
|
// Figure out if this is a data pin, that's the only pin we ever drive
|
|
int8_t dataPinIndex = DataPinIndex(f, pin);
|
|
if (dataPinIndex >= 0)
|
|
{
|
|
uint8_t dataToRead = 0;
|
|
|
|
if (f->state == FlashSoftwareID)
|
|
{
|
|
// This is kind of janky, chips might have other internal registers to read. The different chip
|
|
// types are differently picky about what bits need to be zeroed for this operation. For the purposes
|
|
// of this emulation, just look at the bottom bit to decide manufacturer or device.
|
|
if (f->address & 1)
|
|
{
|
|
dataToRead = DeviceID(f);
|
|
}
|
|
else
|
|
{
|
|
dataToRead = ManufacturerID(f);
|
|
}
|
|
}
|
|
// TODO: programming toggle bit state?
|
|
else // For example, state is FlashReading, or we're interrupting an unlock sequence
|
|
{
|
|
// Grab the content from the current address
|
|
dataToRead = f->content[f->address];
|
|
}
|
|
|
|
// Drive high or low based on the data we're reading
|
|
if (dataToRead & (1 << dataPinIndex))
|
|
{
|
|
return GPIOSimDrivingHigh;
|
|
}
|
|
else
|
|
{
|
|
return GPIOSimDrivingLow;
|
|
}
|
|
}
|
|
}
|
|
|
|
return GPIOSimNotDriving;
|
|
}
|
|
|
|
static void HandleWriteCycle(Flash4MBit *f, uint32_t address, uint8_t data)
|
|
{
|
|
uint32_t unlockMask = UnlockAddressMask(f);
|
|
switch (f->state)
|
|
{
|
|
case FlashReadingWriteOrEraseStatus:
|
|
// Don't allow any changes to state while a write/erase is active
|
|
break;
|
|
case FlashReading:
|
|
case FlashSoftwareID: // TODO: Does this state even need to exist? Or is the softwareIDActivated bool enough?
|
|
default:
|
|
if (((address & unlockMask) == (0x55555555UL & unlockMask)) && (data == 0xAA))
|
|
{
|
|
f->state = FlashUnlocking;
|
|
}
|
|
else if (data == 0xF0)
|
|
{
|
|
// Force a reset back to software ID mode
|
|
f->softwareIDActivated = false;
|
|
f->state = FlashReading;
|
|
}
|
|
break;
|
|
case FlashUnlocking:
|
|
if (((address & unlockMask) == (0xAAAAAAAAUL & unlockMask)) && (data == 0x55))
|
|
{
|
|
f->state = FlashWaitingForCommand;
|
|
}
|
|
else
|
|
{
|
|
// Invalid unlock sequence
|
|
f->state = f->softwareIDActivated ? FlashSoftwareID : FlashReading;
|
|
}
|
|
break;
|
|
case FlashWaitingForCommand:
|
|
if ((address & unlockMask) == (0x55555555UL & unlockMask))
|
|
{
|
|
switch (data)
|
|
{
|
|
case 0xA0:
|
|
f->state = FlashProgrammingByte;
|
|
break;
|
|
case 0x80:
|
|
f->state = FlashEraseUnlocking1;
|
|
break;
|
|
case 0x90:
|
|
f->softwareIDActivated = true;
|
|
f->state = FlashSoftwareID;
|
|
break;
|
|
case 0xF0:
|
|
f->softwareIDActivated = false;
|
|
f->state = FlashReading;
|
|
break;
|
|
default:
|
|
// Invalid command, go back to the read state we should be in
|
|
f->state = f->softwareIDActivated ? FlashSoftwareID : FlashReading;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Invalid unlock sequence
|
|
f->state = f->softwareIDActivated ? FlashSoftwareID : FlashReading;
|
|
}
|
|
break;
|
|
case FlashProgrammingByte:
|
|
// Write the data, then begin reading back the status
|
|
// Programming is only capable of turning 1 bits into zero bits.
|
|
// In other words, programming takes the current byte stored in the flash
|
|
// and ANDs it with the value you're trying to program to that byte.
|
|
// The resulting value is what gets programmed to the chip.
|
|
f->content[address & 0x7FFFF] &= data;
|
|
/// TODO: Set up timer for erase?
|
|
f->state = FlashReadingWriteOrEraseStatus;
|
|
break;
|
|
case FlashEraseUnlocking1:
|
|
if (((address & unlockMask) == (0x55555555UL & unlockMask)) && (data == 0xAA))
|
|
{
|
|
f->state = FlashEraseUnlocking2;
|
|
}
|
|
else
|
|
{
|
|
f->state = f->softwareIDActivated ? FlashSoftwareID : FlashReading;
|
|
}
|
|
break;
|
|
case FlashEraseUnlocking2:
|
|
if (((address & unlockMask) == (0xAAAAAAAAUL & unlockMask)) && (data == 0x55))
|
|
{
|
|
f->state = FlashEraseWaitingForCommand;
|
|
}
|
|
else
|
|
{
|
|
// Invalid unlock sequence
|
|
f->state = f->softwareIDActivated ? FlashSoftwareID : FlashReading;
|
|
}
|
|
break;
|
|
case FlashEraseWaitingForCommand:
|
|
if (((address & unlockMask) == (0x55555555UL & unlockMask)) && data == 0x10)
|
|
{
|
|
// Erase the entire chip
|
|
EraseChip(f);
|
|
/// TODO: Set up timer for erase?
|
|
f->state = FlashReadingWriteOrEraseStatus;
|
|
}
|
|
else if (data == 0x30)
|
|
{
|
|
EraseSector(f, address);
|
|
/// TODO: Set up timer for erase?
|
|
f->state = FlashReadingWriteOrEraseStatus;
|
|
}
|
|
else
|
|
{
|
|
// Invalid command/address supplied during erase
|
|
f->state = f->softwareIDActivated ? FlashSoftwareID : FlashReading;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void HandleReadCycle(Flash4MBit *f)
|
|
{
|
|
// We don't care about the address here because most of the output logic is handled in the ReadGPIOPin handler.
|
|
// This just makes sure the state machine is up to date after a read cycle.
|
|
}
|
|
|
|
static int8_t DataPinIndex(Flash4MBit *f, GPIOPin pin)
|
|
{
|
|
for (int i = 0; i < FLASH_4MBIT_DATA_PINS; i++)
|
|
{
|
|
if (f->dataPins[i].port == pin.port &&
|
|
f->dataPins[i].pin == pin.pin)
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static int8_t AddressPinIndex(Flash4MBit *f, GPIOPin pin)
|
|
{
|
|
for (int i = 0; i < FLASH_4MBIT_ADDR_PINS; i++)
|
|
{
|
|
if (f->addressPins[i].port == pin.port &&
|
|
f->addressPins[i].pin == pin.pin)
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static bool IsCSPin(Flash4MBit *f, GPIOPin pin)
|
|
{
|
|
return f->csPin.port == pin.port && f->csPin.pin == pin.pin;
|
|
}
|
|
|
|
static bool IsOEPin(Flash4MBit *f, GPIOPin pin)
|
|
{
|
|
return f->oePin.port == pin.port && f->oePin.pin == pin.pin;
|
|
}
|
|
|
|
static bool IsWEPin(Flash4MBit *f, GPIOPin pin)
|
|
{
|
|
return f->wePin.port == pin.port && f->wePin.pin == pin.pin;
|
|
}
|
|
|
|
static uint32_t UnlockAddressMask(Flash4MBit *f)
|
|
{
|
|
switch (f->type)
|
|
{
|
|
case Flash_SST39SF040:
|
|
// SST39SF040 only cares about A0 through A14 (low 15 bits)
|
|
return 0x7FFFUL;
|
|
case Flash_AM29F040B:
|
|
// AM329F040B only cares about A0 through A10 (low 11 bits)
|
|
return 0x7FFUL;
|
|
default:
|
|
// Default behavior is we care about all 19 address bits during unlock operations
|
|
return 0x7FFFFUL;
|
|
}
|
|
}
|
|
|
|
static uint8_t ManufacturerID(Flash4MBit *f)
|
|
{
|
|
switch (f->type)
|
|
{
|
|
case Flash_SST39SF040:
|
|
return 0xBF;
|
|
case Flash_AM29F040B:
|
|
return 0x01;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static uint8_t DeviceID(Flash4MBit *f)
|
|
{
|
|
switch (f->type)
|
|
{
|
|
case Flash_SST39SF040:
|
|
return 0xB7;
|
|
case Flash_AM29F040B:
|
|
return 0xA4;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
static void EraseChip(Flash4MBit *f)
|
|
{
|
|
memset(f->content, 0xFF, 512*1024);
|
|
}
|
|
|
|
static void EraseSector(Flash4MBit *f, uint32_t address)
|
|
{
|
|
uint32_t sectorSize;
|
|
|
|
switch (f->type)
|
|
{
|
|
case Flash_SST39SF040:
|
|
default:
|
|
// 4 KB sectors
|
|
sectorSize = 4 * 1024;
|
|
break;
|
|
case Flash_AM29F040B:
|
|
// 64 KB sectors
|
|
sectorSize = 64 * 1024;
|
|
break;
|
|
}
|
|
|
|
// Mask out the low bits of the address so we have the sector start address.
|
|
// We're guaranteed that sectorSize is a power of 2, so subtracting 1 will
|
|
// give us a mask we can use to turn off address bits to get the start addr.
|
|
address &= ~(sectorSize - 1);
|
|
// Clear those bytes
|
|
memset(f->content + address, 0xFF, sectorSize);
|
|
}
|