atmel-samd: Support external flash on the Metro M0 w/Flash.
This uses a scratch flash sector to save data before writing a full sector.
This commit is contained in:
parent
46f6f9f899
commit
bb1822faea
|
@ -68,15 +68,15 @@
|
|||
/*! \name LUN 0 Definitions
|
||||
*/
|
||||
//! @{
|
||||
#define LUN_0_INCLUDE "rom_fs.h"
|
||||
#define Lun_0_test_unit_ready rom_fs_test_unit_ready
|
||||
#define Lun_0_read_capacity rom_fs_read_capacity
|
||||
#define Lun_0_unload NULL /* Can not be unloaded */
|
||||
#define Lun_0_wr_protect rom_fs_wr_protect
|
||||
#define Lun_0_removal rom_fs_removal
|
||||
#define Lun_0_usb_read_10 rom_fs_usb_read_10
|
||||
#define Lun_0_usb_write_10 rom_fs_usb_write_10
|
||||
#define LUN_0_NAME "\"On-Chip ROM\""
|
||||
#define LUN_0_INCLUDE "access_vfs.h"
|
||||
#define Lun_0_test_unit_ready vfs_test_unit_ready
|
||||
#define Lun_0_read_capacity vfs_read_capacity
|
||||
#define Lun_0_unload NULL
|
||||
#define Lun_0_wr_protect vfs_wr_protect
|
||||
#define Lun_0_removal vfs_removal
|
||||
#define Lun_0_usb_read_10 vfs_usb_read_10
|
||||
#define Lun_0_usb_write_10 vfs_usb_write_10
|
||||
#define LUN_0_NAME "\"MicroPython VFS[0]\""
|
||||
//! @}
|
||||
|
||||
#define MEM_USB LUN_USB
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// LEDs
|
||||
#define MICROPY_HW_LED1 PIN_PA17 // red
|
||||
// #define UART_REPL
|
||||
#define USB_REPL
|
||||
|
||||
|
@ -8,3 +6,25 @@
|
|||
|
||||
#define MICROPY_HW_LED_TX PIN_PA27
|
||||
#define MICROPY_HW_LED_RX PIN_PB03
|
||||
|
||||
#define SPI_FLASH_BAUDRATE (1000000)
|
||||
|
||||
// Off-board flash
|
||||
// #define SPI_FLASH_MUX_SETTING SPI_SIGNAL_MUX_SETTING_E
|
||||
// #define SPI_FLASH_PAD0_PINMUX PINMUX_PA16C_SERCOM1_PAD0 // MISO D11
|
||||
// Use default pinmux for the chip select since we manage it ourselves.
|
||||
// #define SPI_FLASH_PAD1_PINMUX PINMUX_DEFAULT
|
||||
// #define SPI_FLASH_PAD2_PINMUX PINMUX_PA18C_SERCOM1_PAD2 // MOSI D10
|
||||
// #define SPI_FLASH_PAD3_PINMUX PINMUX_PA19C_SERCOM1_PAD3 // SCK D12
|
||||
// #define SPI_FLASH_CS PIN_PA17
|
||||
// #define SPI_FLASH_SERCOM SERCOM1
|
||||
|
||||
// On-board flash
|
||||
#define SPI_FLASH_MUX_SETTING SPI_SIGNAL_MUX_SETTING_E
|
||||
#define SPI_FLASH_PAD0_PINMUX PINMUX_PA12D_SERCOM4_PAD0 // MISO
|
||||
// Use default pinmux for the chip select since we manage it ourselves.
|
||||
#define SPI_FLASH_PAD1_PINMUX PINMUX_DEFAULT // CS
|
||||
#define SPI_FLASH_PAD2_PINMUX PINMUX_PB10D_SERCOM4_PAD2 // MOSI
|
||||
#define SPI_FLASH_PAD3_PINMUX PINMUX_PB11D_SERCOM4_PAD3 // SCK
|
||||
#define SPI_FLASH_CS PIN_PA13
|
||||
#define SPI_FLASH_SERCOM SERCOM4
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
LD_FILE = boards/samd21x18-bootloader-external-flash.ld
|
||||
USB_VID = 0x239A
|
||||
USB_PID = 0x8015
|
||||
|
||||
FLASH_IMPL = spi_flash.c
|
||||
|
|
|
@ -92,7 +92,7 @@ PIN(PA19, false, NO_ADC_INPUT,
|
|||
TIMER(TC3, 0, 1, 1, PIN_PA19E_TC3_WO1, MUX_PA19E_TC3_WO1),
|
||||
TIMER(0, TCC0, 3, 3, PIN_PA19F_TCC0_WO3, MUX_PA19F_TCC0_WO3),
|
||||
SERCOM(SERCOM1, 3, PINMUX_PA19C_SERCOM1_PAD3),
|
||||
SERCOM(SERCOM3, 3, PINMUX_PA19C_SERCOM1_PAD3));
|
||||
SERCOM(SERCOM3, 3, PINMUX_PA19D_SERCOM3_PAD3));
|
||||
PIN(PA17, false, NO_ADC_INPUT,
|
||||
TIMER(0, TCC2, 1, 1, PIN_PA17E_TCC2_WO1, MUX_PA17E_TCC2_WO1),
|
||||
TIMER(0, TCC0, 3, 7, PIN_PA17F_TCC0_WO7, MUX_PA17F_TCC0_WO7),
|
||||
|
|
|
@ -64,5 +64,13 @@ SECTIONS
|
|||
_ebss = .;
|
||||
} >RAM
|
||||
|
||||
/* this just checks there is enough RAM for a minimal stack */
|
||||
.stack :
|
||||
{
|
||||
. = ALIGN(4);
|
||||
. = . + 0x800; /* Reserve a minimum of 2K for the stack. */
|
||||
. = ALIGN(4);
|
||||
} >RAM
|
||||
|
||||
.ARM.attributes 0 : { *(.ARM.attributes) }
|
||||
}
|
||||
|
|
|
@ -38,18 +38,153 @@
|
|||
|
||||
#include "spi_flash.h"
|
||||
|
||||
#define TOTAL_SPI_FLASH_SIZE 0x010000
|
||||
|
||||
#define SPI_FLASH_MEM_SEG1_START_ADDR (0x00040000 - TOTAL_SPI_FLASH_SIZE)
|
||||
#define SPI_FLASH_PART1_START_BLOCK (0x100)
|
||||
#define SPI_FLASH_PART1_NUM_BLOCKS (TOTAL_SPI_FLASH_SIZE / SPI_FLASH_BLOCK_SIZE)
|
||||
|
||||
#define NO_SECTOR_LOADED 0xFFFFFFFF
|
||||
|
||||
#define CMD_READ_JEDEC_ID 0x9f
|
||||
#define CMD_READ_DATA 0x03
|
||||
#define CMD_SECTOR_ERASE 0x20
|
||||
// #define CMD_SECTOR_ERASE CMD_READ_JEDEC_ID
|
||||
#define CMD_ENABLE_WRITE 0x06
|
||||
#define CMD_PAGE_PROGRAM 0x02
|
||||
// #define CMD_PAGE_PROGRAM CMD_READ_JEDEC_ID
|
||||
#define CMD_READ_STATUS 0x05
|
||||
|
||||
static bool spi_flash_is_initialised = false;
|
||||
|
||||
struct spi_module spi_flash_instance;
|
||||
|
||||
// The total size of the flash.
|
||||
static uint32_t flash_size;
|
||||
|
||||
// The erase sector size.
|
||||
static uint32_t sector_size;
|
||||
|
||||
// The page size. Its the maximum number of bytes that can be written at once.
|
||||
static uint32_t page_size;
|
||||
|
||||
// The currently cached sector in the scratch flash space.
|
||||
static uint32_t current_sector;
|
||||
|
||||
// A sector is made up of 8 blocks. This tracks which of those blocks in the
|
||||
// current sector current live in the scratch sector.
|
||||
static uint8_t dirty_mask;
|
||||
|
||||
#define SCRATCH_SECTOR (flash_size - sector_size)
|
||||
|
||||
static void flash_enable() {
|
||||
port_pin_set_output_level(SPI_FLASH_CS, false);
|
||||
}
|
||||
|
||||
static void flash_disable() {
|
||||
port_pin_set_output_level(SPI_FLASH_CS, true);
|
||||
}
|
||||
|
||||
static bool wait_for_flash_ready() {
|
||||
uint8_t status_request[2] = {CMD_READ_STATUS, 0x00};
|
||||
uint8_t response[2] = {0x00, 0x01};
|
||||
enum status_code status = STATUS_OK;
|
||||
while (status == STATUS_OK && (response[1] & 0x1) == 1) {
|
||||
flash_enable();
|
||||
status = spi_transceive_buffer_wait(&spi_flash_instance, status_request, response, 2);
|
||||
flash_disable();
|
||||
}
|
||||
return status == STATUS_OK;
|
||||
}
|
||||
|
||||
static bool write_enable() {
|
||||
flash_enable();
|
||||
uint8_t command = CMD_ENABLE_WRITE;
|
||||
enum status_code status = spi_write_buffer_wait(&spi_flash_instance, &command, 1);
|
||||
flash_disable();
|
||||
return status == STATUS_OK;
|
||||
}
|
||||
|
||||
static void address_to_bytes(uint32_t address, uint8_t* bytes) {
|
||||
bytes[0] = (address >> 16) & 0xff;
|
||||
bytes[1] = (address >> 8) & 0xff;
|
||||
bytes[2] = address & 0xff;
|
||||
}
|
||||
|
||||
static bool read_flash(uint32_t address, uint8_t* data, uint32_t data_length) {
|
||||
wait_for_flash_ready();
|
||||
enum status_code status;
|
||||
// We can read as much as we want sequentially.
|
||||
uint8_t read_request[4] = {CMD_READ_DATA, 0x00, 0x00, 0x00};
|
||||
address_to_bytes(address, read_request + 1);
|
||||
flash_enable();
|
||||
status = spi_write_buffer_wait(&spi_flash_instance, read_request, 4);
|
||||
if (status == STATUS_OK) {
|
||||
status = spi_read_buffer_wait(&spi_flash_instance, data, data_length, 0x00);
|
||||
}
|
||||
flash_disable();
|
||||
return status == STATUS_OK;
|
||||
}
|
||||
|
||||
// Assumes that the sector that address resides in has already been erased.
|
||||
static bool write_flash(uint32_t address, const uint8_t* data, uint32_t data_length) {
|
||||
if (page_size == 0) {
|
||||
return false;
|
||||
}
|
||||
for (uint32_t bytes_written = 0;
|
||||
bytes_written < data_length;
|
||||
bytes_written += page_size) {
|
||||
if (!wait_for_flash_ready() || !write_enable()) {
|
||||
return false;
|
||||
}
|
||||
flash_enable();
|
||||
uint8_t command[4] = {CMD_PAGE_PROGRAM, 0x00, 0x00, 0x00};
|
||||
address_to_bytes(address + bytes_written, command + 1);
|
||||
enum status_code status;
|
||||
status = spi_write_buffer_wait(&spi_flash_instance, command, 4);
|
||||
if (status == STATUS_OK) {
|
||||
status = spi_write_buffer_wait(&spi_flash_instance, data + bytes_written, page_size);
|
||||
}
|
||||
flash_disable();
|
||||
if (status != STATUS_OK) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Sector is really 24 bits.
|
||||
static bool erase_sector(uint32_t sector_address) {
|
||||
// Before we erase the sector we need to wait for any writes to finish and
|
||||
// and then enable the write again. For good measure we check that the flash
|
||||
// is ready after enabling the write too.
|
||||
if (!wait_for_flash_ready() || !write_enable() || !wait_for_flash_ready()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t erase_request[4] = {CMD_SECTOR_ERASE, 0x00, 0x00, 0x00};
|
||||
address_to_bytes(sector_address, erase_request + 1);
|
||||
|
||||
flash_enable();
|
||||
enum status_code status = spi_write_buffer_wait(&spi_flash_instance, erase_request, 4);
|
||||
flash_disable();
|
||||
return status == STATUS_OK;
|
||||
}
|
||||
|
||||
// Sector is really 24 bits.
|
||||
static bool copy_block(uint32_t src_address, uint32_t dest_address) {
|
||||
// Copy page by page to minimize RAM buffer.
|
||||
uint8_t buffer[page_size];
|
||||
for (int i = 0; i < FLASH_BLOCK_SIZE / page_size; i++) {
|
||||
if (!read_flash(src_address + i * page_size, buffer, page_size)) {
|
||||
return false;
|
||||
}
|
||||
if (!write_flash(dest_address + i * page_size, buffer, page_size)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void spi_flash_init(void) {
|
||||
if (!spi_flash_is_initialised) {
|
||||
|
||||
struct spi_config config_spi_master;
|
||||
spi_get_config_defaults(&config_spi_master);
|
||||
config_spi_master.mux_setting = SPI_FLASH_MUX_SETTING;
|
||||
|
@ -60,18 +195,74 @@ void spi_flash_init(void) {
|
|||
config_spi_master.mode_specific.master.baudrate = SPI_FLASH_BAUDRATE;
|
||||
spi_init(&spi_flash_instance, SPI_FLASH_SERCOM, &config_spi_master);
|
||||
spi_enable(&spi_flash_instance);
|
||||
|
||||
// Manage chip select ourselves.
|
||||
struct port_config pin_conf;
|
||||
port_get_config_defaults(&pin_conf);
|
||||
|
||||
pin_conf.direction = PORT_PIN_DIR_OUTPUT;
|
||||
port_pin_set_config(SPI_FLASH_CS, &pin_conf);
|
||||
flash_disable();
|
||||
|
||||
uint8_t jedec_id_request[4] = {CMD_READ_JEDEC_ID, 0x00, 0x00, 0x00};
|
||||
uint8_t response[4] = {0x00, 0x00, 0x00, 0x00};
|
||||
flash_enable();
|
||||
volatile enum status_code status = spi_transceive_buffer_wait(&spi_flash_instance, jedec_id_request, response, 4);
|
||||
flash_disable();
|
||||
(void) status;
|
||||
if (response[1] == 0x01 && response[2] == 0x40 && response[3] == 0x15) {
|
||||
flash_size = 1 << 21; // 2 MiB
|
||||
sector_size = 1 << 12; // 4 KiB
|
||||
page_size = 256; // 256 bytes
|
||||
} else {
|
||||
// Unknown flash chip!
|
||||
flash_size = 0;
|
||||
}
|
||||
|
||||
current_sector = NO_SECTOR_LOADED;
|
||||
dirty_mask = 0;
|
||||
|
||||
spi_flash_is_initialised = true;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t spi_flash_get_block_size(void) {
|
||||
return SPI_FLASH_BLOCK_SIZE;
|
||||
return FLASH_BLOCK_SIZE;
|
||||
}
|
||||
|
||||
uint32_t spi_flash_get_block_count(void) {
|
||||
return SPI_FLASH_PART1_START_BLOCK + SPI_FLASH_PART1_NUM_BLOCKS;
|
||||
// We subtract on erase sector size because we're going to use it as a
|
||||
// staging area for writes.
|
||||
return SPI_FLASH_PART1_START_BLOCK + (flash_size - sector_size) / FLASH_BLOCK_SIZE;
|
||||
}
|
||||
|
||||
void spi_flash_flush(void) {
|
||||
if (current_sector == NO_SECTOR_LOADED) {
|
||||
return;
|
||||
}
|
||||
// First, copy out any blocks that we haven't touched from the sector we've
|
||||
// cached.
|
||||
bool copy_to_scratch_ok = true;
|
||||
for (int i = 0; i < sector_size / FLASH_BLOCK_SIZE; i++) {
|
||||
if ((dirty_mask & (1 << i)) == 0) {
|
||||
copy_to_scratch_ok = copy_to_scratch_ok &&
|
||||
copy_block(current_sector + i * FLASH_BLOCK_SIZE,
|
||||
SCRATCH_SECTOR + i * FLASH_BLOCK_SIZE);
|
||||
}
|
||||
}
|
||||
if (!copy_to_scratch_ok) {
|
||||
// TODO(tannewt): Do more here. We opted to not erase and copy bad data
|
||||
// in. We still risk losing the data written to the scratch sector.
|
||||
return;
|
||||
}
|
||||
// Second, erase the current sector.
|
||||
erase_sector(current_sector);
|
||||
// Finally, copy the new version into it.
|
||||
for (int i = 0; i < sector_size / FLASH_BLOCK_SIZE; i++) {
|
||||
copy_block(SCRATCH_SECTOR + i * FLASH_BLOCK_SIZE,
|
||||
current_sector + i * FLASH_BLOCK_SIZE);
|
||||
}
|
||||
current_sector = NO_SECTOR_LOADED;
|
||||
}
|
||||
|
||||
static void build_partition(uint8_t *buf, int boot, int type, uint32_t start_block, uint32_t num_blocks) {
|
||||
|
@ -111,10 +302,10 @@ static void build_partition(uint8_t *buf, int boot, int type, uint32_t start_blo
|
|||
}
|
||||
|
||||
static uint32_t convert_block_to_flash_addr(uint32_t block) {
|
||||
if (SPI_FLASH_PART1_START_BLOCK <= block && block < SPI_FLASH_PART1_START_BLOCK + SPI_FLASH_PART1_NUM_BLOCKS) {
|
||||
if (SPI_FLASH_PART1_START_BLOCK <= block && block < spi_flash_get_block_count()) {
|
||||
// a block in partition 1
|
||||
block -= SPI_FLASH_PART1_START_BLOCK;
|
||||
return SPI_FLASH_MEM_SEG1_START_ADDR + block * SPI_FLASH_BLOCK_SIZE;
|
||||
return block * FLASH_BLOCK_SIZE;
|
||||
}
|
||||
// bad block
|
||||
return -1;
|
||||
|
@ -129,7 +320,7 @@ bool spi_flash_read_block(uint8_t *dest, uint32_t block) {
|
|||
dest[i] = 0;
|
||||
}
|
||||
|
||||
build_partition(dest + 446, 0, 0x01 /* FAT12 */, SPI_FLASH_PART1_START_BLOCK, SPI_FLASH_PART1_NUM_BLOCKS);
|
||||
build_partition(dest + 446, 0, 0x01 /* FAT12 */, SPI_FLASH_PART1_START_BLOCK, spi_flash_get_block_count() - SPI_FLASH_PART1_START_BLOCK);
|
||||
build_partition(dest + 462, 0, 0, 0, 0);
|
||||
build_partition(dest + 478, 0, 0, 0, 0);
|
||||
build_partition(dest + 494, 0, 0, 0, 0);
|
||||
|
@ -146,71 +337,45 @@ bool spi_flash_read_block(uint8_t *dest, uint32_t block) {
|
|||
// bad block number
|
||||
return false;
|
||||
}
|
||||
enum status_code error_code;
|
||||
// A block is made up of multiple pages. Read each page
|
||||
// sequentially.
|
||||
for (int i = 0; i < SPI_FLASH_BLOCK_SIZE / NVMCTRL_PAGE_SIZE; i++) {
|
||||
do
|
||||
{
|
||||
error_code = nvm_read_buffer(src + i * NVMCTRL_PAGE_SIZE,
|
||||
dest + i * NVMCTRL_PAGE_SIZE,
|
||||
NVMCTRL_PAGE_SIZE);
|
||||
} while (error_code == STATUS_BUSY);
|
||||
}
|
||||
return true;
|
||||
return read_flash(src, dest, FLASH_BLOCK_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
bool spi_flash_write_block(const uint8_t *src, uint32_t block) {
|
||||
bool spi_flash_write_block(const uint8_t *data, uint32_t block) {
|
||||
if (block == 0) {
|
||||
// can't write MBR, but pretend we did
|
||||
return true;
|
||||
|
||||
} else {
|
||||
// non-MBR block, copy to cache
|
||||
volatile uint32_t dest = convert_block_to_flash_addr(block);
|
||||
if (dest == -1) {
|
||||
volatile uint32_t address = convert_block_to_flash_addr(block);
|
||||
if (address == -1) {
|
||||
// bad block number
|
||||
return false;
|
||||
}
|
||||
enum status_code error_code;
|
||||
// A block is formed by two rows of flash. We must erase each row
|
||||
// before we write back to it.
|
||||
do
|
||||
{
|
||||
error_code = nvm_erase_row(dest);
|
||||
} while (error_code == STATUS_BUSY);
|
||||
if (error_code != STATUS_OK) {
|
||||
return false;
|
||||
// Wait for any previous writes to finish.
|
||||
wait_for_flash_ready();
|
||||
uint32_t this_sector = address & (~(sector_size - 1));
|
||||
uint8_t block_index = block % (sector_size / FLASH_BLOCK_SIZE);
|
||||
uint8_t mask = 1 << (block_index);
|
||||
if (current_sector != this_sector || (mask & dirty_mask) > 0) {
|
||||
if (current_sector != NO_SECTOR_LOADED) {
|
||||
spi_flash_flush();
|
||||
}
|
||||
erase_sector(SCRATCH_SECTOR);
|
||||
current_sector = this_sector;
|
||||
dirty_mask = 0;
|
||||
wait_for_flash_ready();
|
||||
}
|
||||
do
|
||||
{
|
||||
error_code = nvm_erase_row(dest + NVMCTRL_ROW_SIZE);
|
||||
} while (error_code == STATUS_BUSY);
|
||||
if (error_code != STATUS_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// A block is made up of multiple pages. Write each page
|
||||
// sequentially.
|
||||
for (int i = 0; i < SPI_FLASH_BLOCK_SIZE / NVMCTRL_PAGE_SIZE; i++) {
|
||||
do
|
||||
{
|
||||
error_code = nvm_write_buffer(dest + i * NVMCTRL_PAGE_SIZE,
|
||||
src + i * NVMCTRL_PAGE_SIZE,
|
||||
NVMCTRL_PAGE_SIZE);
|
||||
} while (error_code == STATUS_BUSY);
|
||||
if (error_code != STATUS_OK) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
uint32_t scratch_address = SCRATCH_SECTOR + block_index * FLASH_BLOCK_SIZE;
|
||||
dirty_mask |= mask;
|
||||
return write_flash(scratch_address, data, FLASH_BLOCK_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
mp_uint_t spi_flash_read_blocks(uint8_t *dest, uint32_t block_num, uint32_t num_blocks) {
|
||||
for (size_t i = 0; i < num_blocks; i++) {
|
||||
if (!spi_flash_read_block(dest + i * SPI_FLASH_BLOCK_SIZE, block_num + i)) {
|
||||
if (!spi_flash_read_block(dest + i * FLASH_BLOCK_SIZE, block_num + i)) {
|
||||
return 1; // error
|
||||
}
|
||||
}
|
||||
|
@ -219,7 +384,7 @@ mp_uint_t spi_flash_read_blocks(uint8_t *dest, uint32_t block_num, uint32_t num_
|
|||
|
||||
mp_uint_t spi_flash_write_blocks(const uint8_t *src, uint32_t block_num, uint32_t num_blocks) {
|
||||
for (size_t i = 0; i < num_blocks; i++) {
|
||||
if (!spi_flash_write_block(src + i * SPI_FLASH_BLOCK_SIZE, block_num + i)) {
|
||||
if (!spi_flash_write_block(src + i * FLASH_BLOCK_SIZE, block_num + i)) {
|
||||
return 1; // error
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,8 @@
|
|||
|
||||
#include "mpconfigport.h"
|
||||
|
||||
#define SPI_FLASH_BLOCK_SIZE (512)
|
||||
// Erase sector size.
|
||||
#define SPI_FLASH_SECTOR_SIZE (0x1000 - 100)
|
||||
|
||||
#define SPI_FLASH_SYSTICK_MASK (0x1ff) // 512ms
|
||||
#define SPI_FLASH_IDLE_TICK(tick) (((tick) & SPI_FLASH_SYSTICK_MASK) == 2)
|
||||
|
|
Loading…
Reference in New Issue