Compare commits

..

38 Commits

Author SHA1 Message Date
Doug Brown da1a7b3438 Don't assert OE and CS simultaneously during electrical test
If you do, and a SIMM is installed in the socket, you will get an
erroneous short detected for any data pins that are currently outputting
a 0. Avoid this by testing OE and CS for 5V shorts separately.
2023-08-26 21:11:18 -07:00
Doug Brown 5c868f0454 Mark the M258KE board as having pulldown support 2023-08-26 21:11:18 -07:00
Doug Brown d25492c308 Add pulldown (5V short detection) to SIMM electrical test 2023-08-26 21:11:18 -07:00
Doug Brown 24cfe22feb Hook up pulldowns in M258KE ParallelBus implementation 2023-08-26 21:11:18 -07:00
Doug Brown c0ae82f7de Add dummy AVR pulldown functions for ParallelBus 2023-08-26 21:11:18 -07:00
Doug Brown 4fa533a119 Add prototypes for pulldown control in ParallelBus 2023-08-26 21:11:18 -07:00
Doug Brown 850e5bc202 Add support for GPIO pulldown control
Note that it does nothing on the AVR, which doesn't support it.
2023-08-26 21:11:18 -07:00
Doug Brown 28bab58bed Exclude M258KE code from Eclipse AVR builds 2023-08-26 21:11:18 -07:00
Doug Brown e9ab40c527 Hook up the M258KE port
Note that ASM has to be enabled as a project language because the M258KE
startup code is an assembly file.
2023-08-26 21:11:18 -07:00
Doug Brown c3701f9137 Add cmake source/options includes for M258KE port 2023-08-26 21:11:18 -07:00
Doug Brown 9476de85b4 Disable watchdog timer when the firmware boots
If it gets this far, the firmware is at least sort of running, so the
watchdog has served its purpose of resetting to the bootloader if
there's invalid firmware flashed to the device.
2023-08-26 21:11:18 -07:00
Doug Brown 1d78da958c Enable timer, use for delay functions 2023-08-26 21:11:18 -07:00
Doug Brown 3ad3012e1e Implement M258KE board functions 2023-08-26 21:11:18 -07:00
Doug Brown daec9e74eb Add initial hardware.h for M258KE
Delay functions aren't implemented yet; they will be coming later.
2023-08-26 21:11:18 -07:00
Doug Brown e55bc45279 Add implementation of ParallelBus for M258KE 2023-08-26 21:11:18 -07:00
Doug Brown 5c14d2a149 Add ability for status LED to be inverted 2023-08-26 21:11:18 -07:00
Doug Brown 952fdeacc7 Add GPIO driver
Provide the basic functionality for setting direction, turning on and
off, toggling, reading inputs, and enabling/disabling pullups.

This chip also provides pulldowns, so in the future I will also
implement pulldown control so we can detect shorts to 5V.
2023-08-26 21:11:18 -07:00
Doug Brown 74761e5a24 Implement stubbed-out SPI driver
SPI isn't needed on this platform because we don't need an I/O expander.
So this can be a bunch of stub functions that do nothing. They will be
optimized out during the linking process anyway.
2023-08-26 21:11:18 -07:00
Doug Brown a43ede8f71 Add cmake toolchain file for M258KE build 2023-08-26 21:11:18 -07:00
Doug Brown 9aa6748f3a Add USB CDC serial port code
This implements a USB CDC serial port using the Nuvoton USBD driver. The
USB handling is based on Nuvoton's BSP sample code, especially the IRQ
handlers and descriptor buffer configuration. The descriptors have been
adapted to be similar to the AVR version, and RX/TX functions have been
written to implement an API closer to LUFA, which is what the SIMM
programmer common code needs.
2023-08-26 21:11:18 -07:00
Doug Brown 8fa9699adb Include usbd.h
Since I stripped the peripheral header includes from M251.h, I have to
include it manually instead.
2023-08-26 21:11:18 -07:00
Doug Brown 94af4cf2a6 Fix const correctness of S_USBD_INFO_T struct 2023-08-26 21:11:18 -07:00
Doug Brown aa44b44dab Disable unnecessary interrupt
I don't really need to bother with VBUS or "no-event-wake-up"
interrupts. This allows me to strip out more code in the IRQ handler.
2023-08-26 21:11:18 -07:00
Doug Brown a0d80a010f Change USBD_MemCopy to not be static inline
I'm sure it's slightly more efficient as a static inline function, but
it results in less flash usage as a separate function. This is important
for the bootloader where every byte matters.
2023-08-26 21:11:17 -07:00
Doug Brown c39700a66d Strip out unnecessary callbacks and code in USBD driver
This is important for the bootloader. There's a bunch of stuff here we
don't need that unnecessarily bloats the code.
2023-08-26 21:11:17 -07:00
Doug Brown a25132c96a Bypass GCC built-in startup code
Nuvoton's sample startup_M251.S file handles enough initialization for
my purposes, so I can completely bypass _start and jump directly to
main. Note that I also had to add a define to enable clearing of BSS.
2023-08-26 21:11:17 -07:00
Doug Brown 7ad248e5aa Strip out unnecessary clock and UART code
The default values for SystemCoreClock, CyclesPerUs, and PllClock work
fine for my purposes of running from the 48 MHz HIRC. Remove unnecessary
initialization code. This is especially useful for the bootloader where
flash space is at a premium.

Also strip out unneeded UART setup code.
2023-08-26 21:11:17 -07:00
Doug Brown 7e1e46e1ea Don't include peripheral header files in M251.h
I don't want to include all of Nuvoton's peripheral drivers, but I do
want to use this header file. Remove the unnecessary peripheral
includes.
2023-08-26 21:11:17 -07:00
Doug Brown 4447ae2fb1 Reserve 4 bytes at end of RAM for magic number
This will be used during firmware updates so that the main firmware can
communicate to the bootloader that it should stay in the bootloader for
a firmware update rather than run the main firmware again.
2023-08-26 21:11:17 -07:00
Doug Brown 420b01cf31 Fix issue with linker script allowing data section to overflow flash 2023-08-26 21:11:17 -07:00
Doug Brown 5bdf232841 Update linker scripts with correct RAM/flash sizes
Change code style so that it's easy to see the number of kilobytes too.
2023-08-26 21:11:17 -07:00
Doug Brown 7a18e5ff4d Add README explaining the Nuvoton directory 2023-08-26 21:11:17 -07:00
Doug Brown 8ae7768569 Initial commit of Nuvoton USBD driver 2023-08-26 21:11:17 -07:00
Doug Brown 23779a4de3 Initial commit of register defines, CMSIS code from Nuvoton BSP 2023-08-26 21:11:17 -07:00
Doug Brown a1bc572791 Change license to GPLv3
I can't use GPLv2 as soon as I need to start using the Nuvoton sample
code which is licensed with an Apache 2.0 license.
2023-08-26 21:11:17 -07:00
Doug Brown 81b3d28ffb Add get firmware version command 2023-08-26 21:01:09 -07:00
Doug Brown 1e63f3aea4 Use erase sector layout from programmer software when erasing portion
This should result in much better erase performance on larger SIMMs.
2023-08-26 21:01:09 -07:00
Doug Brown 2c88900d11 Read sector layout from programmer software 2023-08-26 21:01:09 -07:00
4 changed files with 237 additions and 69 deletions

View File

@ -140,32 +140,112 @@ void ParallelFlash_EraseChips(uint8_t chipsMask)
* @param address The start address to erase (must be aligned to a sector boundary)
* @param length The number of bytes to erase (must be aligned to a sector boundary)
* @param chipsMask The mask of which chips to erase
* @param numEraseSectorGroups The number of erase sector groups we know about
* @param eraseSectorGroups The erase sector groups
* @return True on success, false on failure
*/
bool ParallelFlash_EraseSectors(uint32_t address, uint32_t length, uint8_t chipsMask)
bool ParallelFlash_EraseSectors(uint32_t address, uint32_t length, uint8_t chipsMask, uint8_t numEraseSectorGroups, ParallelFlashEraseSectorGroup const *eraseSectorGroups)
{
bool result = false;
// Choose a default sector group if we don't have the info
static const ParallelFlashEraseSectorGroup defaultSST39SF040Sectors[] = {
{0xFFFFFFFFUL, SECTOR_SIZE_SST39SF040}
};
// Figure out our sector size
uint32_t sectorSize;
switch (curChipType)
static const ParallelFlashEraseSectorGroup defaultM29F160FBSectors[] = {
{1, 0x4000},
{2, 0x2000},
{1, 0x8000},
{0xFFFFFFFFUL, SECTOR_SIZE_M29F160FB5AN6E2_8}
};
// If we don't know the sector info (older programmer or unknown chips)
// then fall back to the previous hardcoded sector maps.
// Note that "chip type" isn't really accurate anymore; this is more about
// whether or not it has shifted unlock addresses. But these are the hardcoded
// defaults that seemed to work okay for people previously.
if (numEraseSectorGroups == 0)
{
case ParallelFlash_SST39SF040_x4:
default:
sectorSize = SECTOR_SIZE_SST39SF040;
break;
case ParallelFlash_M29F160FB5AN6E2_x4:
sectorSize = SECTOR_SIZE_M29F160FB5AN6E2_8;
break;
switch (curChipType)
{
case ParallelFlash_SST39SF040_x4:
default:
eraseSectorGroups = defaultSST39SF040Sectors;
numEraseSectorGroups = sizeof(defaultSST39SF040Sectors)/sizeof(defaultSST39SF040Sectors[0]);
break;
case ParallelFlash_M29F160FB5AN6E2_x4:
eraseSectorGroups = defaultM29F160FBSectors;
numEraseSectorGroups = sizeof(defaultM29F160FBSectors)/sizeof(defaultM29F160FBSectors[0]);
break;
}
}
// Make sure the area requested to be erased is on good boundaries
if ((address % sectorSize) ||
(length % sectorSize))
bool result = false;
// The first sector group and index in that group to erase
uint32_t firstSectorGroup = 0;
uint32_t firstSectorInGroup = 0;
// Temporary counters for matching up sector locations
uint32_t curSectorGroup = 0;
uint32_t curSectorInGroup = 0;
// Find the first sector we need to erase. Keep searching until we've
// 1) found it or gone past it, or
// 2) exhausted our list of erase sector groups
uint32_t curAddress = 0;
while (curAddress < address &&
curSectorGroup < numEraseSectorGroups)
{
curAddress += eraseSectorGroups[curSectorGroup].size;
curSectorInGroup++;
if (curSectorInGroup >= eraseSectorGroups[curSectorGroup].count)
{
curSectorGroup++;
curSectorInGroup = 0;
}
}
// If the start address wasn't on a sector boundary, bail
if (curAddress != address)
{
return false;
}
// OK, we've found our first sector to erase.
firstSectorGroup = curSectorGroup;
firstSectorInGroup = curSectorInGroup;
// Now, locate our last sector to erase.
uint32_t curLength = 0;
while (curLength < length &&
curSectorGroup < numEraseSectorGroups)
{
curLength += eraseSectorGroups[curSectorGroup].size;
// If we still haven't handled the entire requested space,
// go to the next sector
if (curLength < length)
{
curSectorInGroup++;
if (curSectorInGroup >= eraseSectorGroups[curSectorGroup].count)
{
curSectorGroup++;
curSectorInGroup = 0;
}
}
}
// If the length wasn't on a sector boundary, bail
if (curLength != length)
{
return false;
}
// We've now verified that everything is on a sector boundary, so we can
// go ahead with the erase operation!
curSectorGroup = firstSectorGroup;
curSectorInGroup = firstSectorInGroup;
// We're good to go. Let's do it. The process varies based on the chip type
if (curChipType == ParallelFlash_SST39SF040_x4)
{
@ -183,8 +263,15 @@ bool ParallelFlash_EraseSectors(uint32_t address, uint32_t length, uint8_t chips
// unlock sequence has to be done again after this sector is done.
ParallelBus_WriteCycle(address, 0x30303030UL);
address += sectorSize;
length -= sectorSize;
// Move our counters in preparation for the next sector
address += eraseSectorGroups[curSectorGroup].size;
length -= eraseSectorGroups[curSectorGroup].size;
curSectorInGroup++;
if (curSectorInGroup >= eraseSectorGroups[curSectorGroup].count)
{
curSectorGroup++;
curSectorInGroup = 0;
}
// Wait for completion of this individual erase operation before
// we can start a new erase operation.
@ -202,25 +289,19 @@ bool ParallelFlash_EraseSectors(uint32_t address, uint32_t length, uint8_t chips
ParallelBus_WriteCycle(ParallelFlash_UnlockAddress1(), 0x80808080UL);
ParallelFlash_UnlockChips(chipsMask);
// Now provide as many sector addresses as needed to erase.
// The first address is a bit of a special case because the boot sector
// actually has finer granularity for sector sizes.
if (address == 0)
{
ParallelBus_WriteCycle(0x00000000UL, 0x30303030UL);
ParallelBus_WriteCycle(0x00004000UL, 0x30303030UL);
ParallelBus_WriteCycle(0x00006000UL, 0x30303030UL);
ParallelBus_WriteCycle(0x00008000UL, 0x30303030UL);
address += sectorSize;
length -= sectorSize;
}
// The remaining sectors can use a more generic algorithm
while (length)
{
ParallelBus_WriteCycle(address, 0x30303030UL);
address += sectorSize;
length -= sectorSize;
// Move our counters in preparation for the next sector
address += eraseSectorGroups[curSectorGroup].size;
length -= eraseSectorGroups[curSectorGroup].size;
curSectorInGroup++;
if (curSectorInGroup >= eraseSectorGroups[curSectorGroup].count)
{
curSectorGroup++;
curSectorInGroup = 0;
}
}
// Wait for completion of the entire erase operation
@ -230,7 +311,6 @@ bool ParallelFlash_EraseSectors(uint32_t address, uint32_t length, uint8_t chips
}
return result;
}
/** Writes a buffer of data to all 4 chips simultaneously

View File

@ -54,6 +54,13 @@ typedef enum ParallelFlashChipType
ParallelFlash_M29F160FB5AN6E2_x4,
} ParallelFlashChipType;
/// Struct representing a group of identical erase sectors
typedef struct ParallelFlashEraseSectorGroup
{
uint32_t count;
uint32_t size;
} ParallelFlashEraseSectorGroup;
// Tells which type of flash chip we are communicating with
void ParallelFlash_SetChipType(ParallelFlashChipType type);
ParallelFlashChipType ParallelFlash_ChipType(void);
@ -69,7 +76,7 @@ void ParallelFlash_IdentifyChips(ParallelFlashChipID *chips);
// Erases the chips/sectors requested
void ParallelFlash_EraseChips(uint8_t chipsMask);
bool ParallelFlash_EraseSectors(uint32_t address, uint32_t length, uint8_t chipsMask);
bool ParallelFlash_EraseSectors(uint32_t address, uint32_t length, uint8_t chipsMask, uint8_t numEraseSectorGroups, ParallelFlashEraseSectorGroup const *eraseSectorGroups);
// Writes a buffer to all 4 chips simultaneously (each uint32_t contains an 8-bit portion for each chip).
// Optimized variant of this function if we know we're writing to all 4 chips simultaneously.

View File

@ -46,7 +46,9 @@ typedef enum ProgrammerCommand
ErasePortion,
WriteChipsAt,
ReadChipsAt,
SetChipsMask
SetChipsMask,
SetSectorLayout,
GetFirmwareVersion
} ProgrammerCommand;
// After a command is sent, the programmer will always respond with
@ -184,4 +186,15 @@ typedef enum ProgrammerErasePortionOfChipReply
ProgrammerErasePortionFinished
} ProgrammerErasePortionOfChipReply;
// ------------------------- GET FIRMWARE VERSION PROTOCOL -------------------------
// If the command is GetFirmwareVersion, the programmer will reply CommandReplyOK.
// Next, it will return 4 bytes: major version, minor version, revision, and a final
// byte where 0 means it's a normal version and 1 means it's a prerelease version.
// Other values are reserved.
// Finally, it will finish the response with ProgrammerGetFWVersionDone.
typedef enum ProgrammerGetFWVersionReply
{
ProgrammerGetFWVersionDone
} ProgrammerGetFWVersionReply;
#endif /* PROGRAMMER_PROTOCOL_H_ */

View File

@ -29,6 +29,7 @@
#include "led.h"
#include "hardware.h"
#include <stdbool.h>
#include <string.h>
/// Maximum size of an individual chip on a SIMM we read
#define MAX_CHIP_SIZE (2UL * 1024UL * 1024UL)
@ -38,8 +39,20 @@
#if ((READ_WRITE_CHUNK_SIZE_BYTES % 4) != 0)
#error Read/write chunk size should be a multiple of 4 bytes
#endif
/// The smallest granularity for sector erase that we support
#define ERASE_SECTOR_SIZE_BYTES (256UL * 1024UL)
/// The maximum number of erase groups we deal with
#define MAX_ERASE_SECTOR_GROUPS 10
/// Version info to respond with
#define VERSION_MAJOR 1
#define VERSION_MINOR 5
#define VERSION_REVISION 0
/// The number of erase sector groups we know about currently.
/// If it's zero, we don't know, so fall back to defaults.
static uint8_t numEraseSectorGroups = 0;
/// The erase sector groups that we will pass to the programmer
static ParallelFlashEraseSectorGroup eraseSectorGroups[MAX_ERASE_SECTOR_GROUPS];
/// Internal state so we know how to interpret the next-received byte
typedef enum ProgrammerCommandState
@ -52,6 +65,7 @@ typedef enum ProgrammerCommandState
ReadingChipsReadStartPos, //!< Reading the start position for reading data from the SIMM
WritingChipsReadingStartPos, //!< Reading the start position for writing data to the SIMM
ReadingChipsMask, //!< Reading the bitmask of which chips should be programmed
ReadingSectorLayout, //!< Reading the erase sector layout
} ProgrammerCommandState;
static ProgrammerCommandState curCommandState = WaitingForCommand;
@ -84,6 +98,7 @@ static void SIMMProgrammer_HandleErasePortionReadPosLengthByte(uint8_t byte);
static void SIMMProgrammer_HandleReadingChipsReadStartPosByte(uint8_t byte);
static void SIMMProgrammer_HandleWritingChipsReadingStartPosByte(uint8_t byte);
static void SIMMProgrammer_HandleReadingChipsMaskByte(uint8_t byte);
static void SIMMProgrammer_HandleReadingSectorLayoutByte(uint8_t byte);
/** Initializes the SIMM programmer and prepares it for USB communication.
*
@ -132,6 +147,9 @@ void SIMMProgrammer_Check(void)
case ReadingChipsMask:
SIMMProgrammer_HandleReadingChipsMaskByte(recvByte);
break;
case ReadingSectorLayout:
SIMMProgrammer_HandleReadingSectorLayoutByte(recvByte);
break;
}
}
@ -264,6 +282,18 @@ static void SIMMProgrammer_HandleWaitingForCommandByte(uint8_t byte)
curCommandState = ReadingChipsMask;
USBCDC_SendByte(CommandReplyOK);
break;
case SetSectorLayout:
curCommandState = ReadingSectorLayout;
USBCDC_SendByte(CommandReplyOK);
break;
case GetFirmwareVersion:
USBCDC_SendByte(CommandReplyOK);
USBCDC_SendByte(VERSION_MAJOR);
USBCDC_SendByte(VERSION_MINOR);
USBCDC_SendByte(VERSION_REVISION);
USBCDC_SendByte(0);
USBCDC_SendByte(ProgrammerGetFWVersionDone);
break;
// We don't know what this command is, so reply that it was invalid.
default:
USBCDC_SendByte(CommandReplyInvalid);
@ -523,44 +553,28 @@ static void SIMMProgrammer_HandleErasePortionReadPosLengthByte(uint8_t byte)
if (++readLengthByteIndex >= 8)
{
ParallelFlashChipType chipType = ParallelFlash_ChipType();
bool eraseSuccess = false;
// Ensure they are both within limits of sector size erasure
if (((erasePosition % ERASE_SECTOR_SIZE_BYTES) == 0) &&
((eraseLength % ERASE_SECTOR_SIZE_BYTES) == 0))
// Ensure the position and length are a multiple of 4 so that the division by 4
// won't confuse anything.
if (((erasePosition % 4) == 0) &&
((eraseLength % 4) == 0))
{
uint32_t boundary = eraseLength + erasePosition;
// Ensure they are within the limits of the chip size too
if (chipType == ParallelFlash_SST39SF040_x4)
// Ensure they are within the limits of our addressable length too.
// We can't address more than 8 MB of data at a time.
if (boundary <= (8 * 1024UL * 1024UL))
{
if (boundary <= (8 * 1024UL * 1024UL))
// OK! We're erasing certain sectors of a SIMM.
USBCDC_SendByte(ProgrammerErasePortionOK);
// Send the response immediately, it could take a while.
USBCDC_Flush();
if (ParallelFlash_EraseSectors(erasePosition/PARALLEL_FLASH_NUM_CHIPS,
eraseLength/PARALLEL_FLASH_NUM_CHIPS, chipsMask,
numEraseSectorGroups, eraseSectorGroups))
{
// OK! We're erasing certain sectors of a SIMM.
USBCDC_SendByte(ProgrammerErasePortionOK);
// Send the response immediately, it could take a while.
USBCDC_Flush();
if (ParallelFlash_EraseSectors(erasePosition/PARALLEL_FLASH_NUM_CHIPS,
eraseLength/PARALLEL_FLASH_NUM_CHIPS, chipsMask))
{
eraseSuccess = true;
}
}
}
else if (chipType == ParallelFlash_M29F160FB5AN6E2_x4)
{
if (boundary <= (8 * 1024UL * 1024UL))
{
// OK! We're erasing certain sectors of a SIMM.
USBCDC_SendByte(ProgrammerErasePortionOK);
// Send the response immediately, it could take a while.
USBCDC_Flush();
if (ParallelFlash_EraseSectors(erasePosition/PARALLEL_FLASH_NUM_CHIPS,
eraseLength/PARALLEL_FLASH_NUM_CHIPS, chipsMask))
{
eraseSuccess = true;
}
eraseSuccess = true;
}
}
}
@ -646,3 +660,57 @@ static void SIMMProgrammer_HandleReadingChipsMaskByte(uint8_t byte)
// Done either way; now we're waiting for a command to arrive
curCommandState = WaitingForCommand;
}
/** Handles a received byte when we are reading in the sector layout
*
* @param byte The received byte, which is the first sector layout byte
*/
static void SIMMProgrammer_HandleReadingSectorLayoutByte(uint8_t byte)
{
numEraseSectorGroups = 0;
uint32_t sectorCount = byte;
uint32_t sectorSize = 0;
int byteIndex = 1;
while (1)
{
// Read in the sector size
for (int i = byteIndex; i < 4; i++)
{
uint32_t nextByte = (uint32_t)USBCDC_ReadByteBlocking();
sectorCount |= nextByte << (i * 8);
}
// From now on, we loop over 4 bytes, not 3
byteIndex = 0;
// If we read in a count of 0, we're done!
if (sectorCount == 0)
{
break;
}
// We have a nonzero count, so read in the size now
for (int i = 0; i < 4; i++)
{
uint32_t nextByte = (uint32_t)USBCDC_ReadByteBlocking();
sectorSize |= nextByte << (i * 8);
}
// If we have room to store it in the array, do it
if (numEraseSectorGroups < MAX_ERASE_SECTOR_GROUPS)
{
eraseSectorGroups[numEraseSectorGroups].count = sectorCount;
eraseSectorGroups[numEraseSectorGroups].size = sectorSize;
numEraseSectorGroups++;
}
// Now read in the next chunk of data
sectorCount = 0;
sectorSize = 0;
}
// We got the list. Done!
USBCDC_SendByte(CommandReplyOK);
curCommandState = WaitingForCommand;
}