Initial implementation of simulated flash chips
There is still a lot of work to do in order to make it pretty, but this is a great start! Chip ID function works.
This commit is contained in:
parent
2df20a9fa7
commit
af171135a3
|
@ -1,3 +1,4 @@
|
|||
/Debug
|
||||
/Release
|
||||
.settings/org.eclipse.cdt.core.prefs
|
||||
CMakeLists.txt.user
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
*/
|
||||
|
||||
#include "board_hw.h"
|
||||
#include "flash_4mbit.h"
|
||||
|
||||
/** Initializes any board hardware-specific stuff
|
||||
*
|
||||
|
@ -30,6 +31,28 @@
|
|||
void Board_Init(void)
|
||||
{
|
||||
// Nothing to do on PC
|
||||
|
||||
// TODO: Figure out a way to make this dynamically configurable. But for now...
|
||||
GPIOPin cs = {GPIOMISC, 4};
|
||||
GPIOPin oe = {GPIOMISC, 5};
|
||||
GPIOPin we = {GPIOMISC, 6};
|
||||
static Flash4MBit ics[4];
|
||||
for (uint8_t i = 0; i < 4; i++)
|
||||
{
|
||||
Flash4MBit_Init(&ics[i], Flash_SST39SF040);
|
||||
GPIOSim_AddDevice(&ics[i].base);
|
||||
Flash4MBit_SetControlPins(&ics[i], cs, oe, we);
|
||||
for (uint8_t j = 0; j < FLASH_4MBIT_DATA_PINS; j++)
|
||||
{
|
||||
GPIOPin dataPin = {GPIODATA, i * 8 + j};
|
||||
Flash4MBit_SetDataPin(&ics[i], j, dataPin);
|
||||
}
|
||||
for (uint8_t j = 0; j < FLASH_4MBIT_ADDR_PINS; j++)
|
||||
{
|
||||
GPIOPin addrPin = {GPIOADDR, j};
|
||||
Flash4MBit_SetAddressPin(&ics[i], j, addrPin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Determines if a brownout was detected at startup
|
||||
|
|
|
@ -0,0 +1,496 @@
|
|||
/*
|
||||
* 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);
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* flash_4mbit.h
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef FLASH_4MBIT_H
|
||||
#define FLASH_4MBIT_H
|
||||
|
||||
#include "gpio_sim.h"
|
||||
|
||||
#define FLASH_4MBIT_DATA_PINS 8
|
||||
#define FLASH_4MBIT_ADDR_PINS 19
|
||||
|
||||
/// Enum representing the type of 4-megabit flash chip being emulated
|
||||
typedef enum Flash4MBitDeviceType
|
||||
{
|
||||
/// SST/Microchip SST39SF040 4-megabit flash chip
|
||||
Flash_SST39SF040,
|
||||
/// AMD/Infineon AM29F040B 4-megabit flash chips
|
||||
Flash_AM29F040B,
|
||||
} Flash4MBitDeviceType;
|
||||
|
||||
/// Implementation of GPIOSimDevice for a 4-megabit flash chip
|
||||
typedef struct Flash4MBit
|
||||
{
|
||||
/// Base class
|
||||
GPIOSimDevice base;
|
||||
/// The type of flash being emulated
|
||||
Flash4MBitDeviceType type;
|
||||
/// Current data input state
|
||||
uint8_t dataInput;
|
||||
/// Current address
|
||||
uint32_t address;
|
||||
/// The write address that has been latched
|
||||
uint32_t latchedWriteAddress;
|
||||
/// True if CS is asserted
|
||||
bool csAsserted;
|
||||
/// True if OE is asserted
|
||||
bool oeAsserted;
|
||||
/// True if WE is asserted
|
||||
bool weAsserted;
|
||||
/// True if we've activated software ID mode
|
||||
bool softwareIDActivated;
|
||||
/// All of our pins
|
||||
GPIOPin csPin;
|
||||
GPIOPin oePin;
|
||||
GPIOPin wePin;
|
||||
GPIOPin dataPins[FLASH_4MBIT_DATA_PINS];
|
||||
GPIOPin addressPins[FLASH_4MBIT_ADDR_PINS];
|
||||
/// The state -- private so we store as an 8-bit int rather than the enum
|
||||
uint8_t state;
|
||||
uint8_t *content;
|
||||
} Flash4MBit;
|
||||
|
||||
void Flash4MBit_Init(Flash4MBit *f, Flash4MBitDeviceType t);
|
||||
void Flash4MBit_SetControlPins(Flash4MBit *f, GPIOPin cs, GPIOPin oe, GPIOPin we);
|
||||
void Flash4MBit_SetDataPin(Flash4MBit *f, uint8_t index, GPIOPin pin);
|
||||
void Flash4MBit_SetAddressPin(Flash4MBit *f, uint8_t index, GPIOPin pin);
|
||||
|
||||
#endif // FLASH_4MBIT_H
|
137
hal/pc/gpio.c
137
hal/pc/gpio.c
|
@ -23,6 +23,12 @@
|
|||
*/
|
||||
|
||||
#include "../gpio.h"
|
||||
#include "gpio_hw.h"
|
||||
#include "gpio_sim.h"
|
||||
|
||||
static uint32_t directionReg[NUM_SIM_GPIO_PORTS];
|
||||
static uint32_t pullupReg[NUM_SIM_GPIO_PORTS];
|
||||
static uint32_t outputReg[NUM_SIM_GPIO_PORTS];
|
||||
|
||||
/** Sets the direction of a GPIO pin.
|
||||
*
|
||||
|
@ -31,8 +37,26 @@
|
|||
*/
|
||||
void GPIO_SetDirection(GPIOPin pin, bool output)
|
||||
{
|
||||
// TODO: Modify direction
|
||||
(void)pin; (void)output;
|
||||
if (pin.port < NUM_SIM_GPIO_PORTS)
|
||||
{
|
||||
// Figure out if any state is actually changing, and if so, modify it
|
||||
bool alreadyOutput = directionReg[pin.port] & (1UL << pin.pin);
|
||||
if (output != alreadyOutput)
|
||||
{
|
||||
if (output)
|
||||
{
|
||||
directionReg[pin.port] |= (1UL << pin.pin);
|
||||
// We just became an output, so make sure any sim devices are updated with
|
||||
// the new driven value
|
||||
GPIOSim_WritePin(pin, outputReg[pin.port] & (1UL << pin.pin));
|
||||
}
|
||||
else
|
||||
{
|
||||
directionReg[pin.port] &= ~(1UL << pin.pin);
|
||||
// We just became an input, there's no state to update really
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Sets whether an input GPIO pin is pulled up
|
||||
|
@ -42,8 +66,19 @@ void GPIO_SetDirection(GPIOPin pin, bool output)
|
|||
*/
|
||||
void GPIO_SetPullup(GPIOPin pin, bool pullup)
|
||||
{
|
||||
// TODO: Modify pullup
|
||||
(void)pin; (void)pullup;
|
||||
if (pin.port < NUM_SIM_GPIO_PORTS)
|
||||
{
|
||||
// Update the pullup register. No need to update any other state,
|
||||
// it simply affects the readback simulation
|
||||
if (pullup)
|
||||
{
|
||||
pullupReg[pin.port] |= (1UL << pin.pin);
|
||||
}
|
||||
else
|
||||
{
|
||||
pullupReg[pin.port] &= ~(1UL << pin.pin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Turns a GPIO pin on (sets it high)
|
||||
|
@ -52,8 +87,19 @@ void GPIO_SetPullup(GPIOPin pin, bool pullup)
|
|||
*/
|
||||
void GPIO_SetOn(GPIOPin pin)
|
||||
{
|
||||
// TODO: Turn on
|
||||
(void)pin;
|
||||
if (pin.port < NUM_SIM_GPIO_PORTS)
|
||||
{
|
||||
// Determine if we're actually changing the current state, which would
|
||||
// require us to update simulated devices
|
||||
bool isOutput = directionReg[pin.port] & (1UL << pin.pin);
|
||||
bool curOutputValue = outputReg[pin.port] & (1UL << pin.pin);
|
||||
bool needsNotification = isOutput && !curOutputValue;
|
||||
outputReg[pin.port] |= (1UL << pin.pin);
|
||||
if (needsNotification)
|
||||
{
|
||||
GPIOSim_WritePin(pin, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Turns a GPIO pin off (sets it low)
|
||||
|
@ -62,8 +108,19 @@ void GPIO_SetOn(GPIOPin pin)
|
|||
*/
|
||||
void GPIO_SetOff(GPIOPin pin)
|
||||
{
|
||||
// TODO: Turn off
|
||||
(void)pin;
|
||||
if (pin.port < NUM_SIM_GPIO_PORTS)
|
||||
{
|
||||
// Determine if we're actually changing the current state, which would
|
||||
// require us to update simulated devices
|
||||
bool isOutput = directionReg[pin.port] & (1UL << pin.pin);
|
||||
bool curOutputValue = outputReg[pin.port] & (1UL << pin.pin);
|
||||
bool needsNotification = isOutput && curOutputValue;
|
||||
outputReg[pin.port] &= ~(1UL << pin.pin);
|
||||
if (needsNotification)
|
||||
{
|
||||
GPIOSim_WritePin(pin, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Toggles a GPIO pin
|
||||
|
@ -72,8 +129,18 @@ void GPIO_SetOff(GPIOPin pin)
|
|||
*/
|
||||
void GPIO_Toggle(GPIOPin pin)
|
||||
{
|
||||
// TODO: Toggle
|
||||
(void)pin;
|
||||
if (pin.port < NUM_SIM_GPIO_PORTS)
|
||||
{
|
||||
// Figure out whether we are turning it on or off, and forward on
|
||||
if (outputReg[pin.port] & (1UL << pin.pin))
|
||||
{
|
||||
GPIO_SetOff(pin);
|
||||
}
|
||||
else
|
||||
{
|
||||
GPIO_SetOn(pin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Reads the input status of a GPIO pin
|
||||
|
@ -83,7 +150,51 @@ void GPIO_Toggle(GPIOPin pin)
|
|||
*/
|
||||
bool GPIO_Read(GPIOPin pin)
|
||||
{
|
||||
// TODO: Read
|
||||
(void)pin;
|
||||
return false;
|
||||
if (pin.port < NUM_SIM_GPIO_PORTS)
|
||||
{
|
||||
// If we are currently configured as an output, just read back the output value.
|
||||
// We'll pretend that's what our "simulated" hardware does.
|
||||
if (directionReg[pin.port] & (1UL << pin.pin))
|
||||
{
|
||||
return outputReg[pin.port] & (1UL << pin.pin);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we're configured as an input, read back the value from any simulators
|
||||
GPIOSimValue readback = GPIOSim_ReadPin(pin);
|
||||
switch (readback)
|
||||
{
|
||||
case GPIOSimNotDriving:
|
||||
default:
|
||||
if (pullupReg[pin.port] & (1UL << pin.pin))
|
||||
{
|
||||
// If the pull-up is active and nothing is driving the pin,
|
||||
// read back as high
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the pull-up is not active and nothing is driving the pin,
|
||||
// it's floating. For the purposes of our simulation, let's return low.
|
||||
// We could return random values if we wanted...
|
||||
// TODO: assertion with current GPIO state, stack trace?
|
||||
return false;
|
||||
}
|
||||
case GPIOSimDrivingLow:
|
||||
return false;
|
||||
case GPIOSimDrivingHigh:
|
||||
return true;
|
||||
case GPIOSimDrivingConflict:
|
||||
// If it's being driven both high and low, bad things will happen.
|
||||
// For the purposes of our simulation, read back as high.
|
||||
// TODO: assertion with current GPIO state, stack trace?
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Read values as low if an invalid port is passed in
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,13 +29,15 @@
|
|||
/// Used with the GPIOPin struct.
|
||||
enum {
|
||||
/// The PC simulated hardware is laid out in a special way so that pins that
|
||||
/// belong together are grouped onto virtual "ports". All of the address pins
|
||||
/// belong together are grouped onto virtual 32-bit "ports". All of the address pins
|
||||
/// comprise one port, all of the data pins comprise another port, and other
|
||||
/// miscellaneous pins like LEDs and parallel bus control pins comprise one
|
||||
/// additional port.
|
||||
GPIOMISC,
|
||||
GPIOADDR,
|
||||
GPIODATA,
|
||||
/// We also have a define to know how many actual ports we have
|
||||
NUM_SIM_GPIO_PORTS
|
||||
};
|
||||
|
||||
#endif /* HAL_PC_GPIO_HW_H_ */
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* gpio_sim.c
|
||||
*
|
||||
* Created on: Jul 31, 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 "gpio_sim.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
static GPIOSimDevice *gpioSims = NULL;
|
||||
|
||||
void GPIOSim_AddDevice(GPIOSimDevice *device)
|
||||
{
|
||||
// We keep a super simple linked list, ordering doesn't matter.
|
||||
// For simplicity, prepend this device to the linked list.
|
||||
device->next = gpioSims;
|
||||
gpioSims = device;
|
||||
}
|
||||
|
||||
void GPIOSim_WritePin(GPIOPin pin, bool high)
|
||||
{
|
||||
// Give each simulated device a chance to handle this event
|
||||
GPIOSimDevice *device = gpioSims;
|
||||
while (device)
|
||||
{
|
||||
if (device->functions && device->functions->drivePin)
|
||||
{
|
||||
device->functions->drivePin(device, pin, high);
|
||||
}
|
||||
device = device->next;
|
||||
}
|
||||
}
|
||||
|
||||
GPIOSimValue GPIOSim_ReadPin(GPIOPin pin)
|
||||
{
|
||||
GPIOSimValue retval = GPIOSimNotDriving;
|
||||
|
||||
// Give each simulated device a chance to handle this read
|
||||
GPIOSimDevice *device = gpioSims;
|
||||
while (device)
|
||||
{
|
||||
if (device->functions && device->functions->readPin)
|
||||
{
|
||||
GPIOSimValue val = device->functions->readPin(device, pin);
|
||||
switch (val)
|
||||
{
|
||||
case GPIOSimNotDriving:
|
||||
default:
|
||||
// Do nothing if we're not driving this pin
|
||||
break;
|
||||
case GPIOSimDrivingLow:
|
||||
// If we haven't found anything driving it yet, we can be the driver
|
||||
if (retval == GPIOSimNotDriving)
|
||||
{
|
||||
retval = GPIOSimDrivingLow;
|
||||
}
|
||||
// If we already found something driving it high, mark the error condition
|
||||
else if (retval == GPIOSimDrivingHigh)
|
||||
{
|
||||
retval = GPIOSimDrivingConflict;
|
||||
}
|
||||
break;
|
||||
case GPIOSimDrivingHigh:
|
||||
// If we haven't found anything driving it yet, we can be the driver
|
||||
if (retval == GPIOSimNotDriving)
|
||||
{
|
||||
retval = GPIOSimDrivingHigh;
|
||||
}
|
||||
// If we already found something driving it low, mark the error condition
|
||||
else if (retval == GPIOSimDrivingLow)
|
||||
{
|
||||
retval = GPIOSimDrivingConflict;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
device = device->next;
|
||||
}
|
||||
|
||||
// All done, return retval
|
||||
return retval;
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* gpio_sim.h
|
||||
*
|
||||
* Created on: Jul 31, 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef GPIO_SIM_H
|
||||
#define GPIO_SIM_H
|
||||
|
||||
#include "../gpio.h"
|
||||
|
||||
/// Enum describing the various ways a simulated GPIO device can be driving a GPIO pin
|
||||
typedef enum GPIOSimValue
|
||||
{
|
||||
/// The simulated GPIO is not driving this pin
|
||||
GPIOSimNotDriving,
|
||||
/// The simulated GPIO is driven low
|
||||
GPIOSimDrivingLow,
|
||||
/// The simulated GPIO is driven high
|
||||
GPIOSimDrivingHigh,
|
||||
/// It's being driven both high and low, uh oh.
|
||||
/// Actual devices don't return this value, but it's used as a return value for GPIOSim_ReadPin
|
||||
GPIOSimDrivingConflict
|
||||
} GPIOSimValue;
|
||||
|
||||
typedef struct GPIOSimDevice GPIOSimDevice;
|
||||
|
||||
typedef struct GPIOSimDeviceFunctions
|
||||
{
|
||||
void (*drivePin)(GPIOSimDevice *device, GPIOPin pin, bool high);
|
||||
GPIOSimValue (*readPin)(GPIOSimDevice *device, GPIOPin pin);
|
||||
} GPIOSimDeviceFunctions;
|
||||
|
||||
struct GPIOSimDevice
|
||||
{
|
||||
GPIOSimDeviceFunctions const *functions;
|
||||
GPIOSimDevice *next;
|
||||
};
|
||||
|
||||
void GPIOSim_AddDevice(GPIOSimDevice *device);
|
||||
void GPIOSim_WritePin(GPIOPin pin, bool high);
|
||||
GPIOSimValue GPIOSim_ReadPin(GPIOPin pin);
|
||||
|
||||
#endif // GPIO_SIM_H
|
|
@ -76,8 +76,11 @@ void ParallelBus_Init(void)
|
|||
*/
|
||||
void ParallelBus_SetAddress(uint32_t address)
|
||||
{
|
||||
// TODO: Fill out entire address in GPIOADDR
|
||||
(void)address;
|
||||
for (int i = 0; i <= PARALLEL_BUS_HIGHEST_ADDRESS_LINE; i++)
|
||||
{
|
||||
GPIOPin addrPin = {GPIOADDR, i};
|
||||
GPIO_Set(addrPin, address & (1UL << i));
|
||||
}
|
||||
}
|
||||
|
||||
/** Sets the output data on the 32-bit data bus
|
||||
|
@ -86,8 +89,11 @@ void ParallelBus_SetAddress(uint32_t address)
|
|||
*/
|
||||
void ParallelBus_SetData(uint32_t data)
|
||||
{
|
||||
// TODO: Fill out entire data in GPIODATA
|
||||
(void)data;
|
||||
for (int i = 0; i < 32; i++)
|
||||
{
|
||||
GPIOPin dataPin = {GPIODATA, i};
|
||||
GPIO_Set(dataPin, data & (1UL << i));
|
||||
}
|
||||
}
|
||||
|
||||
/** Sets the output value of the CS pin
|
||||
|
@ -126,8 +132,11 @@ void ParallelBus_SetWE(bool high)
|
|||
*/
|
||||
void ParallelBus_SetAddressDir(uint32_t outputs)
|
||||
{
|
||||
// TODO: Set direction of GPIOADDR
|
||||
(void)outputs;
|
||||
for (int i = 0; i <= PARALLEL_BUS_HIGHEST_ADDRESS_LINE; i++)
|
||||
{
|
||||
GPIOPin addrPin = {GPIOADDR, i};
|
||||
GPIO_SetDirection(addrPin, outputs & (1UL << i));
|
||||
}
|
||||
}
|
||||
|
||||
/** Sets which pins on the 32-bit data bus should be outputs
|
||||
|
@ -140,8 +149,11 @@ void ParallelBus_SetAddressDir(uint32_t outputs)
|
|||
*/
|
||||
void ParallelBus_SetDataDir(uint32_t outputs)
|
||||
{
|
||||
// TODO: Set direction of GPIODATA
|
||||
(void)outputs;
|
||||
for (int i = 0; i < 32; i++)
|
||||
{
|
||||
GPIOPin dataPin = {GPIODATA, i};
|
||||
GPIO_SetDirection(dataPin, outputs & (1UL << i));
|
||||
}
|
||||
}
|
||||
|
||||
/** Sets the direction of the CS pin
|
||||
|
@ -189,8 +201,11 @@ void ParallelBus_SetWEDir(bool output)
|
|||
*/
|
||||
void ParallelBus_SetAddressPullups(uint32_t pullups)
|
||||
{
|
||||
// TODO: Set pullups on GPIOADDR
|
||||
(void)pullups;
|
||||
for (int i = 0; i <= PARALLEL_BUS_HIGHEST_ADDRESS_LINE; i++)
|
||||
{
|
||||
GPIOPin addrPin = {GPIOADDR, i};
|
||||
GPIO_SetPullup(addrPin, pullups & (1UL << i));
|
||||
}
|
||||
}
|
||||
|
||||
/** Sets which pins on the 32-bit data bus should be pulled up (if inputs)
|
||||
|
@ -203,8 +218,11 @@ void ParallelBus_SetAddressPullups(uint32_t pullups)
|
|||
*/
|
||||
void ParallelBus_SetDataPullups(uint32_t pullups)
|
||||
{
|
||||
// TODO: Set pullups on GPIODATA
|
||||
(void)pullups;
|
||||
for (int i = 0; i < 32; i++)
|
||||
{
|
||||
GPIOPin dataPin = {GPIODATA, i};
|
||||
GPIO_SetPullup(dataPin, pullups & (1UL << i));
|
||||
}
|
||||
}
|
||||
|
||||
/** Sets whether the CS pin is pulled up, if it's an input.
|
||||
|
@ -252,8 +270,16 @@ void ParallelBus_SetWEPullup(bool pullup)
|
|||
*/
|
||||
uint32_t ParallelBus_ReadAddress(void)
|
||||
{
|
||||
// TODO: Read GPIOADDR
|
||||
return 0;
|
||||
uint32_t readback = 0;
|
||||
for (int i = 0; i <= PARALLEL_BUS_HIGHEST_ADDRESS_LINE; i++)
|
||||
{
|
||||
GPIOPin addrPin = {GPIOADDR, i};
|
||||
if (GPIO_Read(addrPin))
|
||||
{
|
||||
readback |= (1UL << i);
|
||||
}
|
||||
}
|
||||
return readback;
|
||||
}
|
||||
|
||||
/** Reads the current data on the 32-bit data bus.
|
||||
|
@ -262,8 +288,16 @@ uint32_t ParallelBus_ReadAddress(void)
|
|||
*/
|
||||
uint32_t ParallelBus_ReadData(void)
|
||||
{
|
||||
// TODO: Read GPIODATA
|
||||
return 0;
|
||||
uint32_t readback = 0;
|
||||
for (int i = 0; i < 32; i++)
|
||||
{
|
||||
GPIOPin dataPin = {GPIODATA, i};
|
||||
if (GPIO_Read(dataPin))
|
||||
{
|
||||
readback |= (1UL << i);
|
||||
}
|
||||
}
|
||||
return readback;
|
||||
}
|
||||
|
||||
/** Reads the status of the CS pin, if it's set as an input.
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
# Create a list of all source files specific to the PC
|
||||
set(HWSOURCES
|
||||
hal/pc/board.c
|
||||
hal/pc/flash_4mbit.c
|
||||
hal/pc/gpio.c
|
||||
hal/pc/gpio_sim.c
|
||||
hal/pc/main.cpp
|
||||
hal/pc/mainwindow.cpp
|
||||
hal/pc/mainwindow.ui
|
||||
|
|
Loading…
Reference in New Issue