From a2bd772d5ca848b53e82410966d37897e205d018 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Fri, 16 Feb 2018 09:46:34 -0800 Subject: [PATCH 1/5] Begin rework to add qspi support. --- .../boards/metro_m4_express/mpconfigboard.mk | 2 +- ports/atmel-samd/spi_flash.c | 170 ++++++++++++------ 2 files changed, 119 insertions(+), 53 deletions(-) diff --git a/ports/atmel-samd/boards/metro_m4_express/mpconfigboard.mk b/ports/atmel-samd/boards/metro_m4_express/mpconfigboard.mk index 0f96532396..1d59ed9664 100644 --- a/ports/atmel-samd/boards/metro_m4_express/mpconfigboard.mk +++ b/ports/atmel-samd/boards/metro_m4_express/mpconfigboard.mk @@ -4,7 +4,7 @@ USB_PID = 0x8021 USB_PRODUCT = "Metro M4 Express" USB_MANUFACTURER = "Adafruit Industries LLC" -SPI_FLASH_FILESYSTEM = 1 +QSPI_FLASH_FILESYSTEM = 1 CHIP_VARIANT = SAMD51J19A CHIP_FAMILY = samd51 diff --git a/ports/atmel-samd/spi_flash.c b/ports/atmel-samd/spi_flash.c index 55a00dcba3..8d6864a0c5 100644 --- a/ports/atmel-samd/spi_flash.c +++ b/ports/atmel-samd/spi_flash.c @@ -81,29 +81,84 @@ static void flash_disable(void) { gpio_set_pin_level(SPI_FLASH_CS_PIN, true); } +static void spi_flash_command(uint8_t* request, uint8_t* response, uint32_t length) { + struct spi_xfer xfer = { request, response, length }; + flash_enable(); + spi_m_sync_transfer(&spi_flash_desc, &jedec_id_xfer); + flash_disable(); +} + +static void qspi_flash_command(uint8_t* request, uint8_t* response, uint32_t length) { + +} + +static void flash_command(uint8_t* request, uint8_t* response, uint32_t length) { + #ifdef CIRCUITPY_QSPI + qspi_flash_command(request, response, length); + #else + spi_flash_command(request, response, length); + #endif +} + +static bool spi_flash_write_data(uint8_t* request, uint32_t request_length, uint8_t* data, uint32_t data_length) { + flash_enable(); + uint8_t page_program_request[4] = {CMD_PAGE_PROGRAM, 0x00, 0x00, 0x00}; + // Write the SPI flash write address into the bytes following the command byte. + address_to_bytes(address + bytes_written, page_program_request + 1); + struct spi_xfer page_program_xfer = {request, 0, request_length}; + int32_t status = spi_m_sync_transfer(&spi_flash_desc, &page_program_xfer); + if (status >= 0) { + struct spi_xfer write_data_buffer_xfer = {data, 0, data_length}; + status = spi_m_sync_transfer(&spi_flash_desc, &write_data_buffer_xfer); + } + flash_disable(); + return status >= 0; +} + +static bool qspi_flash_write_data(uint8_t* request, uint32_t request_length, uint8_t* data, uint32_t data_length) { + return true; +} + +static bool flash_write_data(uint8_t* request, uint32_t request_length, uint8_t* data, uint32_t data_length) { + #ifdef CIRCUITPY_QSPI + return qspi_flash_write_data(request, request_length, data, data_length); + #else + return spi_flash_write_data(request, request_length, data, data_length); + #endif +} + +static bool spi_flash_read_data(uint8_t* request, uint32_t request_length, uint8_t* data, uint32_t data_length) { + +} + +static bool qspi_flash_read_data(uint8_t* request, uint32_t request_length, uint8_t* data, uint32_t data_length) { + return true; +} + +static bool flash_read_data(uint8_t* request, uint32_t request_length, uint8_t* data, uint32_t data_length) { + #ifdef CIRCUITPY_QSPI + return qspi_flash_write_data(request, request_length, data, data_length); + #else + return spi_flash_write_data(request, request_length, data, data_length); + #endif +} + // Wait until both the write enable and write in progress bits have cleared. static bool wait_for_flash_ready(void) { uint8_t read_status_request[2] = {CMD_READ_STATUS, 0x00}; uint8_t read_status_response[2] = {0x00, 0x00}; - struct spi_xfer read_status_xfer = {read_status_request, read_status_response, 2}; - int32_t status; + bool ok = true; // Both the write enable and write in progress bits should be low. do { - flash_enable(); - status = spi_m_sync_transfer(&spi_flash_desc, &read_status_xfer); - flash_disable(); - } while (status >= 0 && (read_status_response[1] & 0x3) != 0); - return status >= 0; // status is number of chars read or a negative error code. + ok = flash_command(read_status_request, read_status_response, 2); + } while (ok && (read_status_response[1] & 0x3) != 0); + return ok; } // Turn on the write enable bit so we can program and erase the flash. static bool write_enable(void) { - flash_enable(); uint8_t enable_write_request[1] = {CMD_ENABLE_WRITE}; - struct spi_xfer enable_write_xfer = {enable_write_request, 0, 1}; - int32_t status = spi_m_sync_transfer(&spi_flash_desc, &enable_write_xfer); - flash_disable(); - return status >= 0; // status is number of chars read or a negative error code. + return flash_command(enable_write_request, 0, 1); } // Pack the low 24 bits of the address into a uint8_t array. @@ -123,17 +178,9 @@ static bool read_flash(uint32_t address, uint8_t* data, uint32_t data_length) { } // We can read as much as we want sequentially. uint8_t read_data_request[4] = {CMD_READ_DATA, 0x00, 0x00, 0x00}; - struct spi_xfer read_data_xfer = {read_data_request, 0, 4}; // Write the SPI flash read address into the bytes following the command byte. address_to_bytes(address, read_data_request + 1); - flash_enable(); - int32_t status = spi_m_sync_transfer(&spi_flash_desc, &read_data_xfer); - struct spi_xfer read_data_buffer_xfer = {0, data, data_length}; - if (status >= 0) { - status = spi_m_sync_transfer(&spi_flash_desc, &read_data_buffer_xfer); - } - flash_disable(); - return status >= 0; + return flash_read_data(read_data_request, 4, data, data_length); } // Writes data_length's worth of bytes starting at address from data. Assumes @@ -173,18 +220,11 @@ static bool write_flash(uint32_t address, const uint8_t* data, uint32_t data_len // flash_disable(); #endif - flash_enable(); uint8_t page_program_request[4] = {CMD_PAGE_PROGRAM, 0x00, 0x00, 0x00}; // Write the SPI flash write address into the bytes following the command byte. address_to_bytes(address + bytes_written, page_program_request + 1); - struct spi_xfer page_program_xfer = {page_program_request, 0, 4}; - status = spi_m_sync_transfer(&spi_flash_desc, &page_program_xfer); - if (status >= 0) { - struct spi_xfer write_data_buffer_xfer = {(uint8_t*) data + bytes_written, 0, SPI_FLASH_PAGE_SIZE}; - status = spi_m_sync_transfer(&spi_flash_desc, &write_data_buffer_xfer); - } - flash_disable(); - if (status < 0) { + if (!flash_write_data(page_program_request, 4, (uint8_t*) data + bytes_written, + SPI_FLASH_PAGE_SIZE)) { return false; } } @@ -230,11 +270,8 @@ static bool erase_sector(uint32_t sector_address) { uint8_t erase_request[4] = {CMD_SECTOR_ERASE, 0x00, 0x00, 0x00}; address_to_bytes(sector_address, erase_request + 1); - struct spi_xfer erase_xfer = {erase_request, 0, 4}; - flash_enable(); - int32_t status = spi_m_sync_transfer(&spi_flash_desc, &erase_xfer); - flash_disable(); - return status >= 0; + flash_command(erase_request, NULL, 4); + return true; } // Sector is really 24 bits. @@ -252,11 +289,7 @@ static bool copy_block(uint32_t src_address, uint32_t dest_address) { return true; } -void spi_flash_init(void) { - if (spi_flash_is_initialised) { - return; - } - +void init_spi_peripheral(void) { samd_peripherals_sercom_clock_init(SPI_FLASH_SERCOM, SPI_FLASH_SERCOM_INDEX); // Set up with defaults, then change. @@ -291,6 +324,47 @@ void spi_flash_init(void) { flash_disable(); spi_m_sync_enable(&spi_flash_desc); +} + +static void init_qspi_peripheral(void) { + MCLK->APBCMASK.bit.QSPI_ = true; + MCLK->AHBMASK.bit.QSPI_ = true; + MCLK->AHBMASK.bit.QSPI_2X_ = false; // Only true if we are doing DDR. + + QSPI->CTRLA.reg = QSPI_CTRLA_SWRST; + // We don't need to wait because we're running as fast as the CPU. + + QSPI->BAUD.bit.BAUD = 2; + QSPI->CTRLB.reg = QSPI_CTRLB_MODE_MEMORY | + QSPI_CTRLB_CSMODE_NORELOAD | + QSPI_CTRLB_DATALEN_8BITS | + QSPI_CTRLB_CSMODE_LASTXFER; + + QSPI->CTRLA.bit.ENABLE = 1; + + // The QSPI is only connected to one set of pins in the SAMD51 so we can hard code it. + uint32_t pins = {PIN_PA08, PIN_PA09, PIN_PA10, PIN_PA11, PIN_PB10, PIN_PB11}; + for (uint8_t i = 0; i < sizeof(pins); i++) { + gpio_set_pin_direction(SPI_FLASH_SCK_PIN, GPIO_DIRECTION_IN); + gpio_set_pin_pull_mode(SPI_FLASH_SCK_PIN, GPIO_PULL_OFF); + gpio_set_pin_function(SPI_FLASH_SCK_PIN, GPIO_PIN_FUNCTION_H); + } +} + +void init_peripherals(void) { + #ifdef CIRCUITPY_QSPI + init_qspi_peripheral(); + #else + init_spi_peripheral(); + #endif +} + +void spi_flash_init(void) { + if (spi_flash_is_initialised) { + return; + } + + init_peripherals(); // Activity LED for flash writes. #ifdef MICROPY_HW_LED_MSC @@ -302,10 +376,8 @@ void spi_flash_init(void) { uint8_t jedec_id_request[4] = {CMD_READ_JEDEC_ID, 0x00, 0x00, 0x00}; uint8_t jedec_id_response[4] = {0x00, 0x00, 0x00, 0x00}; - struct spi_xfer jedec_id_xfer = { jedec_id_request, jedec_id_response, 4 }; - flash_enable(); - spi_m_sync_transfer(&spi_flash_desc, &jedec_id_xfer); - flash_disable(); + flash_command(jedec_id_request, jedec_id_response, 4); + uint8_t manufacturer = jedec_id_response[1]; if ((jedec_id_response[1] == SPI_FLASH_JEDEC_MANUFACTURER #ifdef SPI_FLASH_JEDEC_MANUFACTURER_2 @@ -330,18 +402,12 @@ void spi_flash_init(void) { // Turn off sector protection uint8_t disable_protect_request[2] = {CMD_WRITE_STATUS_BYTE1, 0x00}; - struct spi_xfer disable_protect_xfer = { disable_protect_request, 0, 4 }; - flash_enable(); - spi_m_sync_transfer(&spi_flash_desc, &disable_protect_xfer); - flash_disable(); + flash_command(disable_protect_request, NULL, 2); } // Turn off writes in case this is a microcontroller only reset. uint8_t disable_write_request[1] = {CMD_DISABLE_WRITE}; - struct spi_xfer disable_write_xfer = { disable_write_request, 0, 1 }; - flash_enable(); - spi_m_sync_transfer(&spi_flash_desc, &disable_write_xfer); - flash_disable(); + flash_command(disable_write_request, NULL, 1); wait_for_flash_ready(); From 4710a2adba74cfaff4faf1aa8e2a490ef21a9ce5 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Fri, 16 Feb 2018 14:00:26 -0800 Subject: [PATCH 2/5] Compiles for m4. Untested on m0. This introduces a new spi_flash_api.h that works for both SPI and QSPI. The previous spi_flash functions are now called external_flash to minimize confusion. --- ports/atmel-samd/Makefile | 5 +- .../circuitplayground_express/mpconfigboard.h | 2 +- .../boards/feather_m0_express/mpconfigboard.h | 2 +- .../feather_m0_supersized/mpconfigboard.h | 2 +- .../boards/itsybitsy_m0/mpconfigboard.h | 2 +- .../boards/metro_m0_express/mpconfigboard.h | 2 +- .../boards/metro_m4_express/mpconfigboard.h | 2 +- .../trinket_m0_haxpress/mpconfigboard.h | 3 +- .../atmel-samd/boards/ugame10/mpconfigboard.h | 2 +- ports/atmel-samd/external_flash.c | 663 ++++++++++++++++ .../{spi_flash.h => external_flash.h} | 26 +- ports/atmel-samd/qspi_flash.c | 74 ++ ports/atmel-samd/spi_flash.c | 741 +----------------- ports/atmel-samd/spi_flash_api.h | 40 + 14 files changed, 820 insertions(+), 746 deletions(-) create mode 100644 ports/atmel-samd/external_flash.c rename ports/atmel-samd/{spi_flash.h => external_flash.h} (69%) create mode 100644 ports/atmel-samd/qspi_flash.c create mode 100644 ports/atmel-samd/spi_flash_api.h diff --git a/ports/atmel-samd/Makefile b/ports/atmel-samd/Makefile index 041f1d1ad7..abf5bf12ef 100644 --- a/ports/atmel-samd/Makefile +++ b/ports/atmel-samd/Makefile @@ -252,7 +252,10 @@ ifeq ($(INTERNAL_FLASH_FILESYSTEM),1) SRC_C += internal_flash.c endif ifeq ($(SPI_FLASH_FILESYSTEM),1) -SRC_C += spi_flash.c +SRC_C += external_flash.c spi_flash.c +endif +ifeq ($(QSPI_FLASH_FILESYSTEM),1) +SRC_C += external_flash.c qspi_flash.c endif SRC_COMMON_HAL = \ diff --git a/ports/atmel-samd/boards/circuitplayground_express/mpconfigboard.h b/ports/atmel-samd/boards/circuitplayground_express/mpconfigboard.h index fdb96c06c3..2b8fed6b8b 100644 --- a/ports/atmel-samd/boards/circuitplayground_express/mpconfigboard.h +++ b/ports/atmel-samd/boards/circuitplayground_express/mpconfigboard.h @@ -38,7 +38,7 @@ #define SPEAKER_ENABLE_PIN (&pin_PA30) -#include "spi_flash.h" +#include "external_flash.h" // If you change this, then make sure to update the linker scripts as well to // make sure you don't overwrite code. diff --git a/ports/atmel-samd/boards/feather_m0_express/mpconfigboard.h b/ports/atmel-samd/boards/feather_m0_express/mpconfigboard.h index 448c6c52bf..d0e70d954b 100644 --- a/ports/atmel-samd/boards/feather_m0_express/mpconfigboard.h +++ b/ports/atmel-samd/boards/feather_m0_express/mpconfigboard.h @@ -31,7 +31,7 @@ #define MICROPY_PORT_B ( 0 ) #define MICROPY_PORT_C ( 0 ) -#include "spi_flash.h" +#include "external_flash.h" // If you change this, then make sure to update the linker scripts as well to // make sure you don't overwrite code. diff --git a/ports/atmel-samd/boards/feather_m0_supersized/mpconfigboard.h b/ports/atmel-samd/boards/feather_m0_supersized/mpconfigboard.h index 02ce5e1cdc..4f9b177bec 100644 --- a/ports/atmel-samd/boards/feather_m0_supersized/mpconfigboard.h +++ b/ports/atmel-samd/boards/feather_m0_supersized/mpconfigboard.h @@ -33,7 +33,7 @@ #define MICROPY_PORT_B ( 0 ) #define MICROPY_PORT_C ( 0 ) -#include "spi_flash.h" +#include "external_flash.h" // If you change this, then make sure to update the linker scripts as well to // make sure you don't overwrite code. diff --git a/ports/atmel-samd/boards/itsybitsy_m0/mpconfigboard.h b/ports/atmel-samd/boards/itsybitsy_m0/mpconfigboard.h index f41059ee4f..116775886c 100644 --- a/ports/atmel-samd/boards/itsybitsy_m0/mpconfigboard.h +++ b/ports/atmel-samd/boards/itsybitsy_m0/mpconfigboard.h @@ -33,7 +33,7 @@ #define MICROPY_PORT_B (PORT_PB22 | PORT_PB23 | PORT_PB03 ) #define MICROPY_PORT_C (0) -#include "spi_flash.h" +#include "external_flash.h" // If you change this, then make sure to update the linker scripts as well to // make sure you don't overwrite code. diff --git a/ports/atmel-samd/boards/metro_m0_express/mpconfigboard.h b/ports/atmel-samd/boards/metro_m0_express/mpconfigboard.h index 023beb6883..be1a1a57e0 100644 --- a/ports/atmel-samd/boards/metro_m0_express/mpconfigboard.h +++ b/ports/atmel-samd/boards/metro_m0_express/mpconfigboard.h @@ -34,7 +34,7 @@ #define MICROPY_PORT_B (PORT_PB03 | PORT_PB22 | PORT_PB23) #define MICROPY_PORT_C (0) -#include "spi_flash.h" +#include "external_flash.h" // If you change this, then make sure to update the linker scripts as well to // make sure you don't overwrite code. diff --git a/ports/atmel-samd/boards/metro_m4_express/mpconfigboard.h b/ports/atmel-samd/boards/metro_m4_express/mpconfigboard.h index 1af18ecbc2..fdf98625ef 100644 --- a/ports/atmel-samd/boards/metro_m4_express/mpconfigboard.h +++ b/ports/atmel-samd/boards/metro_m4_express/mpconfigboard.h @@ -40,7 +40,7 @@ #define AUTORESET_DELAY_MS 500 -#include "spi_flash.h" +#include "external_flash.h" // If you change this, then make sure to update the linker scripts as well to // make sure you don't overwrite code diff --git a/ports/atmel-samd/boards/trinket_m0_haxpress/mpconfigboard.h b/ports/atmel-samd/boards/trinket_m0_haxpress/mpconfigboard.h index 2a5792a63b..5dcea945ac 100644 --- a/ports/atmel-samd/boards/trinket_m0_haxpress/mpconfigboard.h +++ b/ports/atmel-samd/boards/trinket_m0_haxpress/mpconfigboard.h @@ -40,7 +40,7 @@ #define CALIBRATE_CRYSTALLESS 1 -#include "spi_flash.h" +#include "external_flash.h" // If you change this, then make sure to update the linker scripts as well to // make sure you don't overwrite code. @@ -49,4 +49,3 @@ #define BOARD_FLASH_SIZE (0x00040000 - 0x2000 - CIRCUITPY_INTERNAL_NVM_SIZE) #include "flash_W25Q32BV.h" - diff --git a/ports/atmel-samd/boards/ugame10/mpconfigboard.h b/ports/atmel-samd/boards/ugame10/mpconfigboard.h index 16a792f421..3bec61ff96 100644 --- a/ports/atmel-samd/boards/ugame10/mpconfigboard.h +++ b/ports/atmel-samd/boards/ugame10/mpconfigboard.h @@ -33,7 +33,7 @@ #define CALIBRATE_CRYSTALLESS 1 -#include "spi_flash.h" +#include "external_flash.h" // If you change this, then make sure to update the linker scripts as well to // make sure you don't overwrite code. diff --git a/ports/atmel-samd/external_flash.c b/ports/atmel-samd/external_flash.c new file mode 100644 index 0000000000..45406462d6 --- /dev/null +++ b/ports/atmel-samd/external_flash.c @@ -0,0 +1,663 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2016, 2017 Scott Shawcroft for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "external_flash.h" + +#include +#include + +#include "extmod/vfs.h" +#include "extmod/vfs_fat.h" +#include "py/misc.h" +#include "py/obj.h" +#include "py/runtime.h" +#include "lib/oofatfs/ff.h" +#include "peripherals.h" +#include "spi_flash_api.h" +#include "supervisor/shared/rgb_led_status.h" + +//#include "shared_dma.h" + +#include "hal_gpio.h" +#include "hal_spi_m_sync.h" + +#define SPI_FLASH_PART1_START_BLOCK (0x1) + +#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_DISABLE_WRITE 0x04 +#define CMD_ENABLE_WRITE 0x06 +#define CMD_PAGE_PROGRAM 0x02 +// #define CMD_PAGE_PROGRAM CMD_READ_JEDEC_ID +#define CMD_READ_STATUS 0x05 +#define CMD_WRITE_STATUS_BYTE1 0x01 + +static bool spi_flash_is_initialised = false; + +struct spi_m_sync_descriptor spi_flash_desc; + +// The currently cached sector in the cache, ram or flash based. +static uint32_t current_sector; + +// Track which blocks (up to 32) in the current sector currently live in the +// cache. +static uint32_t dirty_mask; + +// Address of the scratch flash sector. +#define SCRATCH_SECTOR (SPI_FLASH_TOTAL_SIZE - SPI_FLASH_ERASE_SIZE) + +// Wait until both the write enable and write in progress bits have cleared. +static bool wait_for_flash_ready(void) { + uint8_t read_status_request[2] = {CMD_READ_STATUS, 0x00}; + uint8_t read_status_response[2] = {0x00, 0x00}; + bool ok = true; + // Both the write enable and write in progress bits should be low. + do { + ok = spi_flash_command(read_status_request, read_status_response, 2); + } while (ok && (read_status_response[1] & 0x3) != 0); + return ok; +} + +// Turn on the write enable bit so we can program and erase the flash. +static bool write_enable(void) { + uint8_t enable_write_request[1] = {CMD_ENABLE_WRITE}; + return spi_flash_command(enable_write_request, 0, 1); +} + +// Read data_length's worth of bytes starting at address into data. +static bool read_flash(uint32_t address, uint8_t* data, uint32_t data_length) { + if (!spi_flash_is_initialised) { + return false; + } + if (!wait_for_flash_ready()) { + return false; + } + return spi_flash_read_data(address, data, data_length); +} + +// Writes data_length's worth of bytes starting at address from data. Assumes +// that the sector that address resides in has already been erased. So make sure +// to run erase_sector. +static bool write_flash(uint32_t address, const uint8_t* data, uint32_t data_length) { + if (!spi_flash_is_initialised) { + return false; + } + // Don't bother writing if the data is all 1s. Thats equivalent to the flash + // state after an erase. + bool all_ones = true; + for (uint16_t i = 0; i < data_length; i++) { + if (data[i] != 0xff) { + all_ones = false; + break; + } + } + if (all_ones) { + return true; + } + + for (uint32_t bytes_written = 0; + bytes_written < data_length; + bytes_written += SPI_FLASH_PAGE_SIZE) { + if (!wait_for_flash_ready() || !write_enable()) { + return false; + } + + if (!spi_flash_write_data(address + bytes_written, (uint8_t*) data + bytes_written, + SPI_FLASH_PAGE_SIZE)) { + return false; + } + } + return true; +} + +static bool page_erased(uint32_t sector_address) { + // Check the first few bytes to catch the common case where there is data + // without using a bunch of memory. + uint8_t short_buffer[4]; + if (read_flash(sector_address, short_buffer, 4)) { + for (uint16_t i = 0; i < 4; i++) { + if (short_buffer[i] != 0xff) { + return false; + } + } + } else { + return false; + } + + // Now check the full length. + uint8_t full_buffer[FILESYSTEM_BLOCK_SIZE]; + if (read_flash(sector_address, full_buffer, FILESYSTEM_BLOCK_SIZE)) { + for (uint16_t i = 0; i < FILESYSTEM_BLOCK_SIZE; i++) { + if (short_buffer[i] != 0xff) { + return false; + } + } + } else { + return false; + } + return true; +} + +// Erases the given sector. Make sure you copied all of the data out of it you +// need! Also note, sector_address 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. + if (!wait_for_flash_ready() || !write_enable()) { + return false; + } + + spi_flash_sector_command(CMD_SECTOR_ERASE, sector_address); + return true; +} + +// 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[SPI_FLASH_PAGE_SIZE]; + for (uint32_t i = 0; i < FILESYSTEM_BLOCK_SIZE / SPI_FLASH_PAGE_SIZE; i++) { + if (!read_flash(src_address + i * SPI_FLASH_PAGE_SIZE, buffer, SPI_FLASH_PAGE_SIZE)) { + return false; + } + if (!write_flash(dest_address + i * SPI_FLASH_PAGE_SIZE, buffer, SPI_FLASH_PAGE_SIZE)) { + return false; + } + } + return true; +} + +void external_flash_init(void) { + if (spi_flash_is_initialised) { + return; + } + + spi_flash_init(); + + // Activity LED for flash writes. +#ifdef MICROPY_HW_LED_MSC + gpio_set_pin_function(SPI_FLASH_CS_PIN, GPIO_PIN_FUNCTION_OFF); + gpio_set_pin_direction(MICROPY_HW_LED_MSC, GPIO_DIRECTION_OUT); + // There's already a pull-up on the board. + gpio_set_pin_level(MICROPY_HW_LED_MSC, false); +#endif + + uint8_t jedec_id_request[4] = {CMD_READ_JEDEC_ID, 0x00, 0x00, 0x00}; + uint8_t jedec_id_response[4] = {0x00, 0x00, 0x00, 0x00}; + spi_flash_command(jedec_id_request, jedec_id_response, 4); + + uint8_t manufacturer = jedec_id_response[1]; + if ((jedec_id_response[1] == SPI_FLASH_JEDEC_MANUFACTURER +#ifdef SPI_FLASH_JEDEC_MANUFACTURER_2 + || jedec_id_response[1] == SPI_FLASH_JEDEC_MANUFACTURER_2 +#endif + ) && + jedec_id_response[2] == SPI_FLASH_JEDEC_MEMORY_TYPE && + jedec_id_response[3] == SPI_FLASH_JEDEC_CAPACITY) { + spi_flash_is_initialised = true; + } else { + // Unknown flash chip! + spi_flash_is_initialised = false; + return; + } + + if ((manufacturer == SPI_FLASH_JEDEC_MANUFACTURER && SPI_FLASH_SECTOR_PROTECTION) +#ifdef SPI_FLASH_JEDEC_MANUFACTURER_2 + || (manufacturer == SPI_FLASH_JEDEC_MANUFACTURER_2 && SPI_FLASH_SECTOR_PROTECTION_2) +#endif + ) { + write_enable(); + + // Turn off sector protection + uint8_t disable_protect_request[2] = {CMD_WRITE_STATUS_BYTE1, 0x00}; + spi_flash_command(disable_protect_request, NULL, 2); + } + + // Turn off writes in case this is a microcontroller only reset. + uint8_t disable_write_request[1] = {CMD_DISABLE_WRITE}; + spi_flash_command(disable_write_request, NULL, 1); + + wait_for_flash_ready(); + + current_sector = NO_SECTOR_LOADED; + dirty_mask = 0; + MP_STATE_VM(flash_ram_cache) = NULL; + + spi_flash_is_initialised = true; +} + +// The size of each individual block. +uint32_t external_flash_get_block_size(void) { + return FILESYSTEM_BLOCK_SIZE; +} + +// The total number of available blocks. +uint32_t external_flash_get_block_count(void) { + // We subtract one erase sector size because we may use it as a staging area + // for writes. + return SPI_FLASH_PART1_START_BLOCK + (SPI_FLASH_TOTAL_SIZE - SPI_FLASH_ERASE_SIZE) / FILESYSTEM_BLOCK_SIZE; +} + +// Flush the cache that was written to the scratch portion of flash. Only used +// when ram is tight. +static bool flush_scratch_flash(void) { + // First, copy out any blocks that we haven't touched from the sector we've + // cached. + bool copy_to_scratch_ok = true; + for (uint8_t i = 0; i < SPI_FLASH_ERASE_SIZE / FILESYSTEM_BLOCK_SIZE; i++) { + if ((dirty_mask & (1 << i)) == 0) { + copy_to_scratch_ok = copy_to_scratch_ok && + copy_block(current_sector + i * FILESYSTEM_BLOCK_SIZE, + SCRATCH_SECTOR + i * FILESYSTEM_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 false; + } + // Second, erase the current sector. + erase_sector(current_sector); + // Finally, copy the new version into it. + for (uint8_t i = 0; i < SPI_FLASH_ERASE_SIZE / FILESYSTEM_BLOCK_SIZE; i++) { + copy_block(SCRATCH_SECTOR + i * FILESYSTEM_BLOCK_SIZE, + current_sector + i * FILESYSTEM_BLOCK_SIZE); + } + return true; +} + +// Attempts to allocate a new set of page buffers for caching a full sector in +// ram. Each page is allocated separately so that the GC doesn't need to provide +// one huge block. We can free it as we write if we want to also. +static bool allocate_ram_cache(void) { + uint8_t blocks_per_sector = SPI_FLASH_ERASE_SIZE / FILESYSTEM_BLOCK_SIZE; + uint8_t pages_per_block = FILESYSTEM_BLOCK_SIZE / SPI_FLASH_PAGE_SIZE; + MP_STATE_VM(flash_ram_cache) = m_malloc_maybe(blocks_per_sector * pages_per_block * sizeof(uint32_t), false); + if (MP_STATE_VM(flash_ram_cache) == NULL) { + return false; + } + // Declare i and j outside the loops in case we fail to allocate everything + // we need. In that case we'll give it back. + uint8_t i = 0; + uint8_t j = 0; + bool success = true; + for (i = 0; i < blocks_per_sector; i++) { + for (j = 0; j < pages_per_block; j++) { + uint8_t *page_cache = m_malloc_maybe(SPI_FLASH_PAGE_SIZE, false); + if (page_cache == NULL) { + success = false; + break; + } + MP_STATE_VM(flash_ram_cache)[i * pages_per_block + j] = page_cache; + } + if (!success) { + break; + } + } + // We couldn't allocate enough so give back what we got. + if (!success) { + // We add 1 so that we delete 0 when i is 1. Going to zero (i >= 0) + // would never stop because i is unsigned. + i++; + for (; i > 0; i--) { + for (; j > 0; j--) { + m_free(MP_STATE_VM(flash_ram_cache)[(i - 1) * pages_per_block + (j - 1)]); + } + j = pages_per_block; + } + m_free(MP_STATE_VM(flash_ram_cache)); + MP_STATE_VM(flash_ram_cache) = NULL; + } + return success; +} + +// Flush the cached sector from ram onto the flash. We'll free the cache unless +// keep_cache is true. +static bool flush_ram_cache(bool keep_cache) { + // First, copy out any blocks that we haven't touched from the sector + // we've cached. If we don't do this we'll erase the data during the sector + // erase below. + bool copy_to_ram_ok = true; + uint8_t pages_per_block = FILESYSTEM_BLOCK_SIZE / SPI_FLASH_PAGE_SIZE; + for (uint8_t i = 0; i < SPI_FLASH_ERASE_SIZE / FILESYSTEM_BLOCK_SIZE; i++) { + if ((dirty_mask & (1 << i)) == 0) { + for (uint8_t j = 0; j < pages_per_block; j++) { + copy_to_ram_ok = read_flash( + current_sector + (i * pages_per_block + j) * SPI_FLASH_PAGE_SIZE, + MP_STATE_VM(flash_ram_cache)[i * pages_per_block + j], + SPI_FLASH_PAGE_SIZE); + if (!copy_to_ram_ok) { + break; + } + } + } + if (!copy_to_ram_ok) { + break; + } + } + + if (!copy_to_ram_ok) { + return false; + } + // Second, erase the current sector. + erase_sector(current_sector); + // Lastly, write all the data in ram that we've cached. + for (uint8_t i = 0; i < SPI_FLASH_ERASE_SIZE / FILESYSTEM_BLOCK_SIZE; i++) { + for (uint8_t j = 0; j < pages_per_block; j++) { + write_flash(current_sector + (i * pages_per_block + j) * SPI_FLASH_PAGE_SIZE, + MP_STATE_VM(flash_ram_cache)[i * pages_per_block + j], + SPI_FLASH_PAGE_SIZE); + if (!keep_cache) { + m_free(MP_STATE_VM(flash_ram_cache)[i * pages_per_block + j]); + } + } + } + // We're done with the cache for now so give it back. + if (!keep_cache) { + m_free(MP_STATE_VM(flash_ram_cache)); + MP_STATE_VM(flash_ram_cache) = NULL; + } + return true; +} + +// Delegates to the correct flash flush method depending on the existing cache. +static void spi_flash_flush_keep_cache(bool keep_cache) { + if (current_sector == NO_SECTOR_LOADED) { + return; + } + #ifdef MICROPY_HW_LED_MSC + port_pin_set_output_level(MICROPY_HW_LED_MSC, true); + #endif + temp_status_color(ACTIVE_WRITE); + // If we've cached to the flash itself flush from there. + if (MP_STATE_VM(flash_ram_cache) == NULL) { + flush_scratch_flash(); + } else { + flush_ram_cache(keep_cache); + } + current_sector = NO_SECTOR_LOADED; + clear_temp_status(); + #ifdef MICROPY_HW_LED_MSC + port_pin_set_output_level(MICROPY_HW_LED_MSC, false); + #endif +} + +// External flash function used. If called externally we assume we won't need +// the cache after. +void external_flash_flush(void) { + spi_flash_flush_keep_cache(false); +} + +void flash_flush(void) { + external_flash_flush(); +} + +// Builds a partition entry for the MBR. +static void build_partition(uint8_t *buf, int boot, int type, + uint32_t start_block, uint32_t num_blocks) { + buf[0] = boot; + + if (num_blocks == 0) { + buf[1] = 0; + buf[2] = 0; + buf[3] = 0; + } else { + buf[1] = 0xff; + buf[2] = 0xff; + buf[3] = 0xff; + } + + buf[4] = type; + + if (num_blocks == 0) { + buf[5] = 0; + buf[6] = 0; + buf[7] = 0; + } else { + buf[5] = 0xff; + buf[6] = 0xff; + buf[7] = 0xff; + } + + buf[8] = start_block; + buf[9] = start_block >> 8; + buf[10] = start_block >> 16; + buf[11] = start_block >> 24; + + buf[12] = num_blocks; + buf[13] = num_blocks >> 8; + buf[14] = num_blocks >> 16; + buf[15] = num_blocks >> 24; +} + +static int32_t convert_block_to_flash_addr(uint32_t block) { + if (SPI_FLASH_PART1_START_BLOCK <= block && block < external_flash_get_block_count()) { + // a block in partition 1 + block -= SPI_FLASH_PART1_START_BLOCK; + return block * FILESYSTEM_BLOCK_SIZE; + } + // bad block + return -1; +} + +bool external_flash_read_block(uint8_t *dest, uint32_t block) { + if (block == 0) { + // Fake the MBR so we can decide on our own partition table + for (int i = 0; i < 446; i++) { + dest[i] = 0; + } + + build_partition(dest + 446, 0, 0x01 /* FAT12 */, + SPI_FLASH_PART1_START_BLOCK, + external_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); + + dest[510] = 0x55; + dest[511] = 0xaa; + + return true; + } else if (block < SPI_FLASH_PART1_START_BLOCK) { + memset(dest, 0, FILESYSTEM_BLOCK_SIZE); + return true; + } else { + // Non-MBR block, get data from flash memory. + int32_t address = convert_block_to_flash_addr(block); + if (address == -1) { + // bad block number + return false; + } + + // Mask out the lower bits that designate the address within the sector. + uint32_t this_sector = address & (~(SPI_FLASH_ERASE_SIZE - 1)); + uint8_t block_index = (address / FILESYSTEM_BLOCK_SIZE) % (SPI_FLASH_ERASE_SIZE / FILESYSTEM_BLOCK_SIZE); + uint8_t mask = 1 << (block_index); + // We're reading from the currently cached sector. + if (current_sector == this_sector && (mask & dirty_mask) > 0) { + if (MP_STATE_VM(flash_ram_cache) != NULL) { + uint8_t pages_per_block = FILESYSTEM_BLOCK_SIZE / SPI_FLASH_PAGE_SIZE; + for (int i = 0; i < pages_per_block; i++) { + memcpy(dest + i * SPI_FLASH_PAGE_SIZE, + MP_STATE_VM(flash_ram_cache)[block_index * pages_per_block + i], + SPI_FLASH_PAGE_SIZE); + } + return true; + } else { + uint32_t scratch_address = SCRATCH_SECTOR + block_index * FILESYSTEM_BLOCK_SIZE; + return read_flash(scratch_address, dest, FILESYSTEM_BLOCK_SIZE); + } + } + return read_flash(address, dest, FILESYSTEM_BLOCK_SIZE); + } +} + +bool external_flash_write_block(const uint8_t *data, uint32_t block) { + if (block < SPI_FLASH_PART1_START_BLOCK) { + // Fake writing below the flash partition. + return true; + } else { + // Non-MBR block, copy to cache + int32_t address = convert_block_to_flash_addr(block); + if (address == -1) { + // bad block number + return false; + } + // Wait for any previous writes to finish. + wait_for_flash_ready(); + // Mask out the lower bits that designate the address within the sector. + uint32_t this_sector = address & (~(SPI_FLASH_ERASE_SIZE - 1)); + uint8_t block_index = (address / FILESYSTEM_BLOCK_SIZE) % (SPI_FLASH_ERASE_SIZE / FILESYSTEM_BLOCK_SIZE); + uint8_t mask = 1 << (block_index); + // Flush the cache if we're moving onto a sector or we're writing the + // same block again. + if (current_sector != this_sector || (mask & dirty_mask) > 0) { + // Check to see if we'd write to an erased page. In that case we + // can write directly. + if (page_erased(address)) { + return write_flash(address, data, FILESYSTEM_BLOCK_SIZE); + } + if (current_sector != NO_SECTOR_LOADED) { + spi_flash_flush_keep_cache(true); + } + if (MP_STATE_VM(flash_ram_cache) == NULL && !allocate_ram_cache()) { + erase_sector(SCRATCH_SECTOR); + wait_for_flash_ready(); + } + current_sector = this_sector; + dirty_mask = 0; + } + dirty_mask |= mask; + // Copy the block to the appropriate cache. + if (MP_STATE_VM(flash_ram_cache) != NULL) { + uint8_t pages_per_block = FILESYSTEM_BLOCK_SIZE / SPI_FLASH_PAGE_SIZE; + for (int i = 0; i < pages_per_block; i++) { + memcpy(MP_STATE_VM(flash_ram_cache)[block_index * pages_per_block + i], + data + i * SPI_FLASH_PAGE_SIZE, + SPI_FLASH_PAGE_SIZE); + } + return true; + } else { + uint32_t scratch_address = SCRATCH_SECTOR + block_index * FILESYSTEM_BLOCK_SIZE; + return write_flash(scratch_address, data, FILESYSTEM_BLOCK_SIZE); + } + } +} + +mp_uint_t external_flash_read_blocks(uint8_t *dest, uint32_t block_num, uint32_t num_blocks) { + for (size_t i = 0; i < num_blocks; i++) { + if (!external_flash_read_block(dest + i * FILESYSTEM_BLOCK_SIZE, block_num + i)) { + return 1; // error + } + } + return 0; // success +} + +mp_uint_t external_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 (!external_flash_write_block(src + i * FILESYSTEM_BLOCK_SIZE, block_num + i)) { + return 1; // error + } + } + return 0; // success +} + +/******************************************************************************/ +// MicroPython bindings +// +// Expose the flash as an object with the block protocol. + +// there is a singleton Flash object +STATIC const mp_obj_base_t external_flash_obj = {&external_flash_type}; + +STATIC mp_obj_t external_flash_obj_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + // check arguments + mp_arg_check_num(n_args, n_kw, 0, 0, false); + + // return singleton object + return (mp_obj_t)&external_flash_obj; +} + +STATIC mp_obj_t external_flash_obj_readblocks(mp_obj_t self, mp_obj_t block_num, mp_obj_t buf) { + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(buf, &bufinfo, MP_BUFFER_WRITE); + mp_uint_t ret = external_flash_read_blocks(bufinfo.buf, mp_obj_get_int(block_num), bufinfo.len / FILESYSTEM_BLOCK_SIZE); + return MP_OBJ_NEW_SMALL_INT(ret); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(external_flash_obj_readblocks_obj, external_flash_obj_readblocks); + +STATIC mp_obj_t external_flash_obj_writeblocks(mp_obj_t self, mp_obj_t block_num, mp_obj_t buf) { + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(buf, &bufinfo, MP_BUFFER_READ); + mp_uint_t ret = external_flash_write_blocks(bufinfo.buf, mp_obj_get_int(block_num), bufinfo.len / FILESYSTEM_BLOCK_SIZE); + return MP_OBJ_NEW_SMALL_INT(ret); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(external_flash_obj_writeblocks_obj, external_flash_obj_writeblocks); + +STATIC mp_obj_t external_flash_obj_ioctl(mp_obj_t self, mp_obj_t cmd_in, mp_obj_t arg_in) { + mp_int_t cmd = mp_obj_get_int(cmd_in); + switch (cmd) { + case BP_IOCTL_INIT: external_flash_init(); return MP_OBJ_NEW_SMALL_INT(0); + case BP_IOCTL_DEINIT: external_flash_flush(); return MP_OBJ_NEW_SMALL_INT(0); // TODO properly + case BP_IOCTL_SYNC: external_flash_flush(); return MP_OBJ_NEW_SMALL_INT(0); + case BP_IOCTL_SEC_COUNT: return MP_OBJ_NEW_SMALL_INT(external_flash_get_block_count()); + case BP_IOCTL_SEC_SIZE: return MP_OBJ_NEW_SMALL_INT(external_flash_get_block_size()); + default: return mp_const_none; + } +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(external_flash_obj_ioctl_obj, external_flash_obj_ioctl); + +STATIC const mp_rom_map_elem_t external_flash_obj_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_readblocks), MP_ROM_PTR(&external_flash_obj_readblocks_obj) }, + { MP_ROM_QSTR(MP_QSTR_writeblocks), MP_ROM_PTR(&external_flash_obj_writeblocks_obj) }, + { MP_ROM_QSTR(MP_QSTR_ioctl), MP_ROM_PTR(&external_flash_obj_ioctl_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(external_flash_obj_locals_dict, external_flash_obj_locals_dict_table); + +const mp_obj_type_t external_flash_type = { + { &mp_type_type }, + .name = MP_QSTR_SPIFlash, + .make_new = external_flash_obj_make_new, + .locals_dict = (mp_obj_t)&external_flash_obj_locals_dict, +}; + +void flash_init_vfs(fs_user_mount_t *vfs) { + vfs->base.type = &mp_fat_vfs_type; + vfs->flags |= FSUSER_NATIVE | FSUSER_HAVE_IOCTL; + vfs->fatfs.drv = vfs; + vfs->fatfs.part = 1; // flash filesystem lives on first partition + vfs->readblocks[0] = (mp_obj_t)&external_flash_obj_readblocks_obj; + vfs->readblocks[1] = (mp_obj_t)&external_flash_obj; + vfs->readblocks[2] = (mp_obj_t)external_flash_read_blocks; // native version + vfs->writeblocks[0] = (mp_obj_t)&external_flash_obj_writeblocks_obj; + vfs->writeblocks[1] = (mp_obj_t)&external_flash_obj; + vfs->writeblocks[2] = (mp_obj_t)external_flash_write_blocks; // native version + vfs->u.ioctl[0] = (mp_obj_t)&external_flash_obj_ioctl_obj; + vfs->u.ioctl[1] = (mp_obj_t)&external_flash_obj; +} diff --git a/ports/atmel-samd/spi_flash.h b/ports/atmel-samd/external_flash.h similarity index 69% rename from ports/atmel-samd/spi_flash.h rename to ports/atmel-samd/external_flash.h index e4bf8a5224..5a43b10c84 100644 --- a/ports/atmel-samd/spi_flash.h +++ b/ports/atmel-samd/external_flash.h @@ -23,8 +23,8 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -#ifndef MICROPY_INCLUDED_ATMEL_SAMD_SPI_FLASH_H -#define MICROPY_INCLUDED_ATMEL_SAMD_SPI_FLASH_H +#ifndef MICROPY_INCLUDED_ATMEL_SAMD_EXTERNAL_FLASH_H +#define MICROPY_INCLUDED_ATMEL_SAMD_EXTERNAL_FLASH_H #include @@ -40,21 +40,21 @@ #define SPI_FLASH_SYSTICK_MASK (0x1ff) // 512ms #define SPI_FLASH_IDLE_TICK(tick) (((tick) & SPI_FLASH_SYSTICK_MASK) == 2) -void spi_flash_init(void); -uint32_t spi_flash_get_block_size(void); -uint32_t spi_flash_get_block_count(void); -void spi_flash_irq_handler(void); -void spi_flash_flush(void); -bool spi_flash_read_block(uint8_t *dest, uint32_t block); -bool spi_flash_write_block(const uint8_t *src, uint32_t block); +void external_flash_init(void); +uint32_t external_flash_get_block_size(void); +uint32_t external_flash_get_block_count(void); +void external_flash_irq_handler(void); +void external_flash_flush(void); +bool external_flash_read_block(uint8_t *dest, uint32_t block); +bool external_flash_write_block(const uint8_t *src, uint32_t block); // these return 0 on success, non-zero on error -mp_uint_t spi_flash_read_blocks(uint8_t *dest, uint32_t block_num, uint32_t num_blocks); -mp_uint_t spi_flash_write_blocks(const uint8_t *src, uint32_t block_num, uint32_t num_blocks); +mp_uint_t external_flash_read_blocks(uint8_t *dest, uint32_t block_num, uint32_t num_blocks); +mp_uint_t external_flash_write_blocks(const uint8_t *src, uint32_t block_num, uint32_t num_blocks); -extern const struct _mp_obj_type_t spi_flash_type; +extern const struct _mp_obj_type_t external_flash_type; struct _fs_user_mount_t; void flash_init_vfs(struct _fs_user_mount_t *vfs); -#endif // MICROPY_INCLUDED_ATMEL_SAMD_SPI_FLASH_H +#endif // MICROPY_INCLUDED_ATMEL_SAMD_EXTERNAL_FLASH_H diff --git a/ports/atmel-samd/qspi_flash.c b/ports/atmel-samd/qspi_flash.c new file mode 100644 index 0000000000..3fa53a8989 --- /dev/null +++ b/ports/atmel-samd/qspi_flash.c @@ -0,0 +1,74 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2016, 2017 Scott Shawcroft for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "spi_flash_api.h" + +#include + +#include "atmel_start_pins.h" +#include "hal_gpio.h" + +bool spi_flash_command(uint8_t* request, uint8_t* response, uint32_t length) { + return true; +} + +bool spi_flash_sector_command(uint8_t command, uint32_t address) { + return true; +} + +bool spi_flash_write_data(uint32_t address, uint8_t* data, uint32_t data_length) { + return true; +} + +bool spi_flash_read_data(uint32_t address, uint8_t* data, uint32_t data_length) { + return true; +} + + +void spi_flash_init(void) { + MCLK->APBCMASK.bit.QSPI_ = true; + MCLK->AHBMASK.bit.QSPI_ = true; + MCLK->AHBMASK.bit.QSPI_2X_ = false; // Only true if we are doing DDR. + + QSPI->CTRLA.reg = QSPI_CTRLA_SWRST; + // We don't need to wait because we're running as fast as the CPU. + + QSPI->BAUD.bit.BAUD = 2; + QSPI->CTRLB.reg = QSPI_CTRLB_MODE_MEMORY | + QSPI_CTRLB_CSMODE_NORELOAD | + QSPI_CTRLB_DATALEN_8BITS | + QSPI_CTRLB_CSMODE_LASTXFER; + + QSPI->CTRLA.bit.ENABLE = 1; + + // The QSPI is only connected to one set of pins in the SAMD51 so we can hard code it. + uint32_t pins[6] = {PIN_PA08, PIN_PA09, PIN_PA10, PIN_PA11, PIN_PB10, PIN_PB11}; + for (uint8_t i = 0; i < 6; i++) { + gpio_set_pin_direction(pins[i], GPIO_DIRECTION_IN); + gpio_set_pin_pull_mode(pins[i], GPIO_PULL_OFF); + gpio_set_pin_function(pins[i], GPIO_PIN_FUNCTION_H); + } +} diff --git a/ports/atmel-samd/spi_flash.c b/ports/atmel-samd/spi_flash.c index 8d6864a0c5..9695067f17 100644 --- a/ports/atmel-samd/spi_flash.c +++ b/ports/atmel-samd/spi_flash.c @@ -28,49 +28,13 @@ #include #include -#include "extmod/vfs.h" -#include "extmod/vfs_fat.h" -#include "py/misc.h" -#include "py/obj.h" -#include "py/runtime.h" -#include "lib/oofatfs/ff.h" #include "peripherals.h" -#include "supervisor/shared/rgb_led_status.h" - -//#include "shared_dma.h" #include "hal_gpio.h" #include "hal_spi_m_sync.h" -#define SPI_FLASH_PART1_START_BLOCK (0x1) - -#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_DISABLE_WRITE 0x04 -#define CMD_ENABLE_WRITE 0x06 -#define CMD_PAGE_PROGRAM 0x02 -// #define CMD_PAGE_PROGRAM CMD_READ_JEDEC_ID -#define CMD_READ_STATUS 0x05 -#define CMD_WRITE_STATUS_BYTE1 0x01 - -static bool spi_flash_is_initialised = false; - struct spi_m_sync_descriptor spi_flash_desc; -// The currently cached sector in the cache, ram or flash based. -static uint32_t current_sector; - -// Track which blocks (up to 32) in the current sector currently live in the -// cache. -static uint32_t dirty_mask; - -// Address of the scratch flash sector. -#define SCRATCH_SECTOR (SPI_FLASH_TOTAL_SIZE - SPI_FLASH_ERASE_SIZE) - // Enable the flash over SPI. static void flash_enable(void) { gpio_set_pin_level(SPI_FLASH_CS_PIN, false); @@ -81,30 +45,34 @@ static void flash_disable(void) { gpio_set_pin_level(SPI_FLASH_CS_PIN, true); } -static void spi_flash_command(uint8_t* request, uint8_t* response, uint32_t length) { +void spi_flash_command(uint8_t* request, uint8_t* response, uint32_t length) { struct spi_xfer xfer = { request, response, length }; flash_enable(); - spi_m_sync_transfer(&spi_flash_desc, &jedec_id_xfer); + spi_m_sync_transfer(&spi_flash_desc, &xfer); flash_disable(); } -static void qspi_flash_command(uint8_t* request, uint8_t* response, uint32_t length) { - +// Pack the low 24 bits of the address into a uint8_t array. +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 void flash_command(uint8_t* request, uint8_t* response, uint32_t length) { - #ifdef CIRCUITPY_QSPI - qspi_flash_command(request, response, length); - #else - spi_flash_command(request, response, length); - #endif +void spi_flash_sector_command(uint8_t command, uint32_t address) { + uint8_t request[4] = {command, 0x00, 0x00, 0x00}; + address_to_bytes(address, page_program_request + 1); + struct spi_xfer xfer = { request, NULL, 4 }; + flash_enable(); + spi_m_sync_transfer(&spi_flash_desc, &xfer); + flash_disable(); } -static bool spi_flash_write_data(uint8_t* request, uint32_t request_length, uint8_t* data, uint32_t data_length) { +bool spi_flash_write_data(uint32_t address, uint8_t* data, uint32_t data_length) { flash_enable(); uint8_t page_program_request[4] = {CMD_PAGE_PROGRAM, 0x00, 0x00, 0x00}; // Write the SPI flash write address into the bytes following the command byte. - address_to_bytes(address + bytes_written, page_program_request + 1); + address_to_bytes(address, page_program_request + 1); struct spi_xfer page_program_xfer = {request, 0, request_length}; int32_t status = spi_m_sync_transfer(&spi_flash_desc, &page_program_xfer); if (status >= 0) { @@ -115,181 +83,11 @@ static bool spi_flash_write_data(uint8_t* request, uint32_t request_length, uint return status >= 0; } -static bool qspi_flash_write_data(uint8_t* request, uint32_t request_length, uint8_t* data, uint32_t data_length) { - return true; -} - -static bool flash_write_data(uint8_t* request, uint32_t request_length, uint8_t* data, uint32_t data_length) { - #ifdef CIRCUITPY_QSPI - return qspi_flash_write_data(request, request_length, data, data_length); - #else - return spi_flash_write_data(request, request_length, data, data_length); - #endif -} - -static bool spi_flash_read_data(uint8_t* request, uint32_t request_length, uint8_t* data, uint32_t data_length) { +bool spi_flash_read_data(uint32_t address, uint8_t* data, uint32_t data_length) { } -static bool qspi_flash_read_data(uint8_t* request, uint32_t request_length, uint8_t* data, uint32_t data_length) { - return true; -} - -static bool flash_read_data(uint8_t* request, uint32_t request_length, uint8_t* data, uint32_t data_length) { - #ifdef CIRCUITPY_QSPI - return qspi_flash_write_data(request, request_length, data, data_length); - #else - return spi_flash_write_data(request, request_length, data, data_length); - #endif -} - -// Wait until both the write enable and write in progress bits have cleared. -static bool wait_for_flash_ready(void) { - uint8_t read_status_request[2] = {CMD_READ_STATUS, 0x00}; - uint8_t read_status_response[2] = {0x00, 0x00}; - bool ok = true; - // Both the write enable and write in progress bits should be low. - do { - ok = flash_command(read_status_request, read_status_response, 2); - } while (ok && (read_status_response[1] & 0x3) != 0); - return ok; -} - -// Turn on the write enable bit so we can program and erase the flash. -static bool write_enable(void) { - uint8_t enable_write_request[1] = {CMD_ENABLE_WRITE}; - return flash_command(enable_write_request, 0, 1); -} - -// Pack the low 24 bits of the address into a uint8_t array. -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; -} - -// Read data_length's worth of bytes starting at address into data. -static bool read_flash(uint32_t address, uint8_t* data, uint32_t data_length) { - if (!spi_flash_is_initialised) { - return false; - } - if (!wait_for_flash_ready()) { - return false; - } - // We can read as much as we want sequentially. - uint8_t read_data_request[4] = {CMD_READ_DATA, 0x00, 0x00, 0x00}; - // Write the SPI flash read address into the bytes following the command byte. - address_to_bytes(address, read_data_request + 1); - return flash_read_data(read_data_request, 4, data, data_length); -} - -// Writes data_length's worth of bytes starting at address from data. Assumes -// that the sector that address resides in has already been erased. So make sure -// to run erase_sector. -static bool write_flash(uint32_t address, const uint8_t* data, uint32_t data_length) { - if (!spi_flash_is_initialised) { - return false; - } - // Don't bother writing if the data is all 1s. Thats equivalent to the flash - // state after an erase. - bool all_ones = true; - for (uint16_t i = 0; i < data_length; i++) { - if (data[i] != 0xff) { - all_ones = false; - break; - } - } - if (all_ones) { - return true; - } - - for (uint32_t bytes_written = 0; - bytes_written < data_length; - bytes_written += SPI_FLASH_PAGE_SIZE) { - if (!wait_for_flash_ready() || !write_enable()) { - return false; - } - int32_t status; - - #ifdef SPI_FLASH_SECTOR_PROTECTION - // Print out the protection status. - // uint8_t protect_check[5] = {0x3C, 0x00, 0x00, 0x00, 0x00}; - // address_to_bytes(address + bytes_written, protect_check + 1); - // flash_enable(); - // status = spi_write_buffer_wait(&spi_flash_desc, protect_check, 5); - // flash_disable(); - #endif - - uint8_t page_program_request[4] = {CMD_PAGE_PROGRAM, 0x00, 0x00, 0x00}; - // Write the SPI flash write address into the bytes following the command byte. - address_to_bytes(address + bytes_written, page_program_request + 1); - if (!flash_write_data(page_program_request, 4, (uint8_t*) data + bytes_written, - SPI_FLASH_PAGE_SIZE)) { - return false; - } - } - return true; -} - -static bool page_erased(uint32_t sector_address) { - // Check the first few bytes to catch the common case where there is data - // without using a bunch of memory. - uint8_t short_buffer[4]; - if (read_flash(sector_address, short_buffer, 4)) { - for (uint16_t i = 0; i < 4; i++) { - if (short_buffer[i] != 0xff) { - return false; - } - } - } else { - return false; - } - - // Now check the full length. - uint8_t full_buffer[FILESYSTEM_BLOCK_SIZE]; - if (read_flash(sector_address, full_buffer, FILESYSTEM_BLOCK_SIZE)) { - for (uint16_t i = 0; i < FILESYSTEM_BLOCK_SIZE; i++) { - if (short_buffer[i] != 0xff) { - return false; - } - } - } else { - return false; - } - return true; -} - -// Erases the given sector. Make sure you copied all of the data out of it you -// need! Also note, sector_address 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. - if (!wait_for_flash_ready() || !write_enable()) { - return false; - } - - uint8_t erase_request[4] = {CMD_SECTOR_ERASE, 0x00, 0x00, 0x00}; - address_to_bytes(sector_address, erase_request + 1); - flash_command(erase_request, NULL, 4); - return true; -} - -// 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[SPI_FLASH_PAGE_SIZE]; - for (uint32_t i = 0; i < FILESYSTEM_BLOCK_SIZE / SPI_FLASH_PAGE_SIZE; i++) { - if (!read_flash(src_address + i * SPI_FLASH_PAGE_SIZE, buffer, SPI_FLASH_PAGE_SIZE)) { - return false; - } - if (!write_flash(dest_address + i * SPI_FLASH_PAGE_SIZE, buffer, SPI_FLASH_PAGE_SIZE)) { - return false; - } - } - return true; -} - -void init_spi_peripheral(void) { +void spi_flash_init(void) { samd_peripherals_sercom_clock_init(SPI_FLASH_SERCOM, SPI_FLASH_SERCOM_INDEX); // Set up with defaults, then change. @@ -325,506 +123,3 @@ void init_spi_peripheral(void) { spi_m_sync_enable(&spi_flash_desc); } - -static void init_qspi_peripheral(void) { - MCLK->APBCMASK.bit.QSPI_ = true; - MCLK->AHBMASK.bit.QSPI_ = true; - MCLK->AHBMASK.bit.QSPI_2X_ = false; // Only true if we are doing DDR. - - QSPI->CTRLA.reg = QSPI_CTRLA_SWRST; - // We don't need to wait because we're running as fast as the CPU. - - QSPI->BAUD.bit.BAUD = 2; - QSPI->CTRLB.reg = QSPI_CTRLB_MODE_MEMORY | - QSPI_CTRLB_CSMODE_NORELOAD | - QSPI_CTRLB_DATALEN_8BITS | - QSPI_CTRLB_CSMODE_LASTXFER; - - QSPI->CTRLA.bit.ENABLE = 1; - - // The QSPI is only connected to one set of pins in the SAMD51 so we can hard code it. - uint32_t pins = {PIN_PA08, PIN_PA09, PIN_PA10, PIN_PA11, PIN_PB10, PIN_PB11}; - for (uint8_t i = 0; i < sizeof(pins); i++) { - gpio_set_pin_direction(SPI_FLASH_SCK_PIN, GPIO_DIRECTION_IN); - gpio_set_pin_pull_mode(SPI_FLASH_SCK_PIN, GPIO_PULL_OFF); - gpio_set_pin_function(SPI_FLASH_SCK_PIN, GPIO_PIN_FUNCTION_H); - } -} - -void init_peripherals(void) { - #ifdef CIRCUITPY_QSPI - init_qspi_peripheral(); - #else - init_spi_peripheral(); - #endif -} - -void spi_flash_init(void) { - if (spi_flash_is_initialised) { - return; - } - - init_peripherals(); - - // Activity LED for flash writes. -#ifdef MICROPY_HW_LED_MSC - gpio_set_pin_function(SPI_FLASH_CS_PIN, GPIO_PIN_FUNCTION_OFF); - gpio_set_pin_direction(MICROPY_HW_LED_MSC, GPIO_DIRECTION_OUT); - // There's already a pull-up on the board. - gpio_set_pin_level(MICROPY_HW_LED_MSC, false); -#endif - - uint8_t jedec_id_request[4] = {CMD_READ_JEDEC_ID, 0x00, 0x00, 0x00}; - uint8_t jedec_id_response[4] = {0x00, 0x00, 0x00, 0x00}; - flash_command(jedec_id_request, jedec_id_response, 4); - - uint8_t manufacturer = jedec_id_response[1]; - if ((jedec_id_response[1] == SPI_FLASH_JEDEC_MANUFACTURER -#ifdef SPI_FLASH_JEDEC_MANUFACTURER_2 - || jedec_id_response[1] == SPI_FLASH_JEDEC_MANUFACTURER_2 -#endif - ) && - jedec_id_response[2] == SPI_FLASH_JEDEC_MEMORY_TYPE && - jedec_id_response[3] == SPI_FLASH_JEDEC_CAPACITY) { - spi_flash_is_initialised = true; - } else { - // Unknown flash chip! - spi_flash_is_initialised = false; - return; - } - - if ((manufacturer == SPI_FLASH_JEDEC_MANUFACTURER && SPI_FLASH_SECTOR_PROTECTION) -#ifdef SPI_FLASH_JEDEC_MANUFACTURER_2 - || (manufacturer == SPI_FLASH_JEDEC_MANUFACTURER_2 && SPI_FLASH_SECTOR_PROTECTION_2) -#endif - ) { - write_enable(); - - // Turn off sector protection - uint8_t disable_protect_request[2] = {CMD_WRITE_STATUS_BYTE1, 0x00}; - flash_command(disable_protect_request, NULL, 2); - } - - // Turn off writes in case this is a microcontroller only reset. - uint8_t disable_write_request[1] = {CMD_DISABLE_WRITE}; - flash_command(disable_write_request, NULL, 1); - - wait_for_flash_ready(); - - current_sector = NO_SECTOR_LOADED; - dirty_mask = 0; - MP_STATE_VM(flash_ram_cache) = NULL; - - spi_flash_is_initialised = true; -} - -// The size of each individual block. -uint32_t spi_flash_get_block_size(void) { - return FILESYSTEM_BLOCK_SIZE; -} - -// The total number of available blocks. -uint32_t spi_flash_get_block_count(void) { - // We subtract one erase sector size because we may use it as a staging area - // for writes. - return SPI_FLASH_PART1_START_BLOCK + (SPI_FLASH_TOTAL_SIZE - SPI_FLASH_ERASE_SIZE) / FILESYSTEM_BLOCK_SIZE; -} - -// Flush the cache that was written to the scratch portion of flash. Only used -// when ram is tight. -static bool flush_scratch_flash(void) { - // First, copy out any blocks that we haven't touched from the sector we've - // cached. - bool copy_to_scratch_ok = true; - for (uint8_t i = 0; i < SPI_FLASH_ERASE_SIZE / FILESYSTEM_BLOCK_SIZE; i++) { - if ((dirty_mask & (1 << i)) == 0) { - copy_to_scratch_ok = copy_to_scratch_ok && - copy_block(current_sector + i * FILESYSTEM_BLOCK_SIZE, - SCRATCH_SECTOR + i * FILESYSTEM_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 false; - } - // Second, erase the current sector. - erase_sector(current_sector); - // Finally, copy the new version into it. - for (uint8_t i = 0; i < SPI_FLASH_ERASE_SIZE / FILESYSTEM_BLOCK_SIZE; i++) { - copy_block(SCRATCH_SECTOR + i * FILESYSTEM_BLOCK_SIZE, - current_sector + i * FILESYSTEM_BLOCK_SIZE); - } - return true; -} - -// Attempts to allocate a new set of page buffers for caching a full sector in -// ram. Each page is allocated separately so that the GC doesn't need to provide -// one huge block. We can free it as we write if we want to also. -static bool allocate_ram_cache(void) { - uint8_t blocks_per_sector = SPI_FLASH_ERASE_SIZE / FILESYSTEM_BLOCK_SIZE; - uint8_t pages_per_block = FILESYSTEM_BLOCK_SIZE / SPI_FLASH_PAGE_SIZE; - MP_STATE_VM(flash_ram_cache) = m_malloc_maybe(blocks_per_sector * pages_per_block * sizeof(uint32_t), false); - if (MP_STATE_VM(flash_ram_cache) == NULL) { - return false; - } - // Declare i and j outside the loops in case we fail to allocate everything - // we need. In that case we'll give it back. - uint8_t i = 0; - uint8_t j = 0; - bool success = true; - for (i = 0; i < blocks_per_sector; i++) { - for (j = 0; j < pages_per_block; j++) { - uint8_t *page_cache = m_malloc_maybe(SPI_FLASH_PAGE_SIZE, false); - if (page_cache == NULL) { - success = false; - break; - } - MP_STATE_VM(flash_ram_cache)[i * pages_per_block + j] = page_cache; - } - if (!success) { - break; - } - } - // We couldn't allocate enough so give back what we got. - if (!success) { - // We add 1 so that we delete 0 when i is 1. Going to zero (i >= 0) - // would never stop because i is unsigned. - i++; - for (; i > 0; i--) { - for (; j > 0; j--) { - m_free(MP_STATE_VM(flash_ram_cache)[(i - 1) * pages_per_block + (j - 1)]); - } - j = pages_per_block; - } - m_free(MP_STATE_VM(flash_ram_cache)); - MP_STATE_VM(flash_ram_cache) = NULL; - } - return success; -} - -// Flush the cached sector from ram onto the flash. We'll free the cache unless -// keep_cache is true. -static bool flush_ram_cache(bool keep_cache) { - // First, copy out any blocks that we haven't touched from the sector - // we've cached. If we don't do this we'll erase the data during the sector - // erase below. - bool copy_to_ram_ok = true; - uint8_t pages_per_block = FILESYSTEM_BLOCK_SIZE / SPI_FLASH_PAGE_SIZE; - for (uint8_t i = 0; i < SPI_FLASH_ERASE_SIZE / FILESYSTEM_BLOCK_SIZE; i++) { - if ((dirty_mask & (1 << i)) == 0) { - for (uint8_t j = 0; j < pages_per_block; j++) { - copy_to_ram_ok = read_flash( - current_sector + (i * pages_per_block + j) * SPI_FLASH_PAGE_SIZE, - MP_STATE_VM(flash_ram_cache)[i * pages_per_block + j], - SPI_FLASH_PAGE_SIZE); - if (!copy_to_ram_ok) { - break; - } - } - } - if (!copy_to_ram_ok) { - break; - } - } - - if (!copy_to_ram_ok) { - return false; - } - // Second, erase the current sector. - erase_sector(current_sector); - // Lastly, write all the data in ram that we've cached. - for (uint8_t i = 0; i < SPI_FLASH_ERASE_SIZE / FILESYSTEM_BLOCK_SIZE; i++) { - for (uint8_t j = 0; j < pages_per_block; j++) { - write_flash(current_sector + (i * pages_per_block + j) * SPI_FLASH_PAGE_SIZE, - MP_STATE_VM(flash_ram_cache)[i * pages_per_block + j], - SPI_FLASH_PAGE_SIZE); - if (!keep_cache) { - m_free(MP_STATE_VM(flash_ram_cache)[i * pages_per_block + j]); - } - } - } - // We're done with the cache for now so give it back. - if (!keep_cache) { - m_free(MP_STATE_VM(flash_ram_cache)); - MP_STATE_VM(flash_ram_cache) = NULL; - } - return true; -} - -// Delegates to the correct flash flush method depending on the existing cache. -static void spi_flash_flush_keep_cache(bool keep_cache) { - if (current_sector == NO_SECTOR_LOADED) { - return; - } - #ifdef MICROPY_HW_LED_MSC - port_pin_set_output_level(MICROPY_HW_LED_MSC, true); - #endif - temp_status_color(ACTIVE_WRITE); - // If we've cached to the flash itself flush from there. - if (MP_STATE_VM(flash_ram_cache) == NULL) { - flush_scratch_flash(); - } else { - flush_ram_cache(keep_cache); - } - current_sector = NO_SECTOR_LOADED; - clear_temp_status(); - #ifdef MICROPY_HW_LED_MSC - port_pin_set_output_level(MICROPY_HW_LED_MSC, false); - #endif -} - -// External flash function used. If called externally we assume we won't need -// the cache after. -void spi_flash_flush(void) { - spi_flash_flush_keep_cache(false); -} - -void flash_flush(void) { - spi_flash_flush(); -} - -// Builds a partition entry for the MBR. -static void build_partition(uint8_t *buf, int boot, int type, - uint32_t start_block, uint32_t num_blocks) { - buf[0] = boot; - - if (num_blocks == 0) { - buf[1] = 0; - buf[2] = 0; - buf[3] = 0; - } else { - buf[1] = 0xff; - buf[2] = 0xff; - buf[3] = 0xff; - } - - buf[4] = type; - - if (num_blocks == 0) { - buf[5] = 0; - buf[6] = 0; - buf[7] = 0; - } else { - buf[5] = 0xff; - buf[6] = 0xff; - buf[7] = 0xff; - } - - buf[8] = start_block; - buf[9] = start_block >> 8; - buf[10] = start_block >> 16; - buf[11] = start_block >> 24; - - buf[12] = num_blocks; - buf[13] = num_blocks >> 8; - buf[14] = num_blocks >> 16; - buf[15] = num_blocks >> 24; -} - -static int32_t convert_block_to_flash_addr(uint32_t block) { - 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 block * FILESYSTEM_BLOCK_SIZE; - } - // bad block - return -1; -} - -bool spi_flash_read_block(uint8_t *dest, uint32_t block) { - if (block == 0) { - // Fake the MBR so we can decide on our own partition table - for (int i = 0; i < 446; i++) { - dest[i] = 0; - } - - 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); - - dest[510] = 0x55; - dest[511] = 0xaa; - - return true; - } else if (block < SPI_FLASH_PART1_START_BLOCK) { - memset(dest, 0, FILESYSTEM_BLOCK_SIZE); - return true; - } else { - // Non-MBR block, get data from flash memory. - int32_t address = convert_block_to_flash_addr(block); - if (address == -1) { - // bad block number - return false; - } - - // Mask out the lower bits that designate the address within the sector. - uint32_t this_sector = address & (~(SPI_FLASH_ERASE_SIZE - 1)); - uint8_t block_index = (address / FILESYSTEM_BLOCK_SIZE) % (SPI_FLASH_ERASE_SIZE / FILESYSTEM_BLOCK_SIZE); - uint8_t mask = 1 << (block_index); - // We're reading from the currently cached sector. - if (current_sector == this_sector && (mask & dirty_mask) > 0) { - if (MP_STATE_VM(flash_ram_cache) != NULL) { - uint8_t pages_per_block = FILESYSTEM_BLOCK_SIZE / SPI_FLASH_PAGE_SIZE; - for (int i = 0; i < pages_per_block; i++) { - memcpy(dest + i * SPI_FLASH_PAGE_SIZE, - MP_STATE_VM(flash_ram_cache)[block_index * pages_per_block + i], - SPI_FLASH_PAGE_SIZE); - } - return true; - } else { - uint32_t scratch_address = SCRATCH_SECTOR + block_index * FILESYSTEM_BLOCK_SIZE; - return read_flash(scratch_address, dest, FILESYSTEM_BLOCK_SIZE); - } - } - return read_flash(address, dest, FILESYSTEM_BLOCK_SIZE); - } -} - -bool spi_flash_write_block(const uint8_t *data, uint32_t block) { - if (block < SPI_FLASH_PART1_START_BLOCK) { - // Fake writing below the flash partition. - return true; - } else { - // Non-MBR block, copy to cache - int32_t address = convert_block_to_flash_addr(block); - if (address == -1) { - // bad block number - return false; - } - // Wait for any previous writes to finish. - wait_for_flash_ready(); - // Mask out the lower bits that designate the address within the sector. - uint32_t this_sector = address & (~(SPI_FLASH_ERASE_SIZE - 1)); - uint8_t block_index = (address / FILESYSTEM_BLOCK_SIZE) % (SPI_FLASH_ERASE_SIZE / FILESYSTEM_BLOCK_SIZE); - uint8_t mask = 1 << (block_index); - // Flush the cache if we're moving onto a sector or we're writing the - // same block again. - if (current_sector != this_sector || (mask & dirty_mask) > 0) { - // Check to see if we'd write to an erased page. In that case we - // can write directly. - if (page_erased(address)) { - return write_flash(address, data, FILESYSTEM_BLOCK_SIZE); - } - if (current_sector != NO_SECTOR_LOADED) { - spi_flash_flush_keep_cache(true); - } - if (MP_STATE_VM(flash_ram_cache) == NULL && !allocate_ram_cache()) { - erase_sector(SCRATCH_SECTOR); - wait_for_flash_ready(); - } - current_sector = this_sector; - dirty_mask = 0; - } - dirty_mask |= mask; - // Copy the block to the appropriate cache. - if (MP_STATE_VM(flash_ram_cache) != NULL) { - uint8_t pages_per_block = FILESYSTEM_BLOCK_SIZE / SPI_FLASH_PAGE_SIZE; - for (int i = 0; i < pages_per_block; i++) { - memcpy(MP_STATE_VM(flash_ram_cache)[block_index * pages_per_block + i], - data + i * SPI_FLASH_PAGE_SIZE, - SPI_FLASH_PAGE_SIZE); - } - return true; - } else { - uint32_t scratch_address = SCRATCH_SECTOR + block_index * FILESYSTEM_BLOCK_SIZE; - return write_flash(scratch_address, data, FILESYSTEM_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 * FILESYSTEM_BLOCK_SIZE, block_num + i)) { - return 1; // error - } - } - return 0; // success -} - -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 * FILESYSTEM_BLOCK_SIZE, block_num + i)) { - return 1; // error - } - } - return 0; // success -} - -/******************************************************************************/ -// MicroPython bindings -// -// Expose the flash as an object with the block protocol. - -// there is a singleton Flash object -STATIC const mp_obj_base_t spi_flash_obj = {&spi_flash_type}; - -STATIC mp_obj_t spi_flash_obj_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { - // check arguments - mp_arg_check_num(n_args, n_kw, 0, 0, false); - - // return singleton object - return (mp_obj_t)&spi_flash_obj; -} - -STATIC mp_obj_t spi_flash_obj_readblocks(mp_obj_t self, mp_obj_t block_num, mp_obj_t buf) { - mp_buffer_info_t bufinfo; - mp_get_buffer_raise(buf, &bufinfo, MP_BUFFER_WRITE); - mp_uint_t ret = spi_flash_read_blocks(bufinfo.buf, mp_obj_get_int(block_num), bufinfo.len / FILESYSTEM_BLOCK_SIZE); - return MP_OBJ_NEW_SMALL_INT(ret); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_3(spi_flash_obj_readblocks_obj, spi_flash_obj_readblocks); - -STATIC mp_obj_t spi_flash_obj_writeblocks(mp_obj_t self, mp_obj_t block_num, mp_obj_t buf) { - mp_buffer_info_t bufinfo; - mp_get_buffer_raise(buf, &bufinfo, MP_BUFFER_READ); - mp_uint_t ret = spi_flash_write_blocks(bufinfo.buf, mp_obj_get_int(block_num), bufinfo.len / FILESYSTEM_BLOCK_SIZE); - return MP_OBJ_NEW_SMALL_INT(ret); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_3(spi_flash_obj_writeblocks_obj, spi_flash_obj_writeblocks); - -STATIC mp_obj_t spi_flash_obj_ioctl(mp_obj_t self, mp_obj_t cmd_in, mp_obj_t arg_in) { - mp_int_t cmd = mp_obj_get_int(cmd_in); - switch (cmd) { - case BP_IOCTL_INIT: spi_flash_init(); return MP_OBJ_NEW_SMALL_INT(0); - case BP_IOCTL_DEINIT: spi_flash_flush(); return MP_OBJ_NEW_SMALL_INT(0); // TODO properly - case BP_IOCTL_SYNC: spi_flash_flush(); return MP_OBJ_NEW_SMALL_INT(0); - case BP_IOCTL_SEC_COUNT: return MP_OBJ_NEW_SMALL_INT(spi_flash_get_block_count()); - case BP_IOCTL_SEC_SIZE: return MP_OBJ_NEW_SMALL_INT(spi_flash_get_block_size()); - default: return mp_const_none; - } -} -STATIC MP_DEFINE_CONST_FUN_OBJ_3(spi_flash_obj_ioctl_obj, spi_flash_obj_ioctl); - -STATIC const mp_rom_map_elem_t spi_flash_obj_locals_dict_table[] = { - { MP_ROM_QSTR(MP_QSTR_readblocks), MP_ROM_PTR(&spi_flash_obj_readblocks_obj) }, - { MP_ROM_QSTR(MP_QSTR_writeblocks), MP_ROM_PTR(&spi_flash_obj_writeblocks_obj) }, - { MP_ROM_QSTR(MP_QSTR_ioctl), MP_ROM_PTR(&spi_flash_obj_ioctl_obj) }, -}; - -STATIC MP_DEFINE_CONST_DICT(spi_flash_obj_locals_dict, spi_flash_obj_locals_dict_table); - -const mp_obj_type_t spi_flash_type = { - { &mp_type_type }, - .name = MP_QSTR_SPIFlash, - .make_new = spi_flash_obj_make_new, - .locals_dict = (mp_obj_t)&spi_flash_obj_locals_dict, -}; - -void flash_init_vfs(fs_user_mount_t *vfs) { - vfs->base.type = &mp_fat_vfs_type; - vfs->flags |= FSUSER_NATIVE | FSUSER_HAVE_IOCTL; - vfs->fatfs.drv = vfs; - vfs->fatfs.part = 1; // flash filesystem lives on first partition - vfs->readblocks[0] = (mp_obj_t)&spi_flash_obj_readblocks_obj; - vfs->readblocks[1] = (mp_obj_t)&spi_flash_obj; - vfs->readblocks[2] = (mp_obj_t)spi_flash_read_blocks; // native version - vfs->writeblocks[0] = (mp_obj_t)&spi_flash_obj_writeblocks_obj; - vfs->writeblocks[1] = (mp_obj_t)&spi_flash_obj; - vfs->writeblocks[2] = (mp_obj_t)spi_flash_write_blocks; // native version - vfs->u.ioctl[0] = (mp_obj_t)&spi_flash_obj_ioctl_obj; - vfs->u.ioctl[1] = (mp_obj_t)&spi_flash_obj; -} diff --git a/ports/atmel-samd/spi_flash_api.h b/ports/atmel-samd/spi_flash_api.h new file mode 100644 index 0000000000..d758faa774 --- /dev/null +++ b/ports/atmel-samd/spi_flash_api.h @@ -0,0 +1,40 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef MICROPY_INCLUDED_ATMEL_SAMD_SPI_FLASH_H +#define MICROPY_INCLUDED_ATMEL_SAMD_SPI_FLASH_H + +#include +#include + +// This API is implemented for both normal SPI peripherals and QSPI peripherals. + +bool spi_flash_command(uint8_t* request, uint8_t* response, uint32_t length); +bool spi_flash_sector_command(uint8_t command, uint32_t address); +bool spi_flash_write_data(uint32_t address, uint8_t* data, uint32_t data_length); +bool spi_flash_read_data(uint32_t address, uint8_t* data, uint32_t data_length); +void spi_flash_init(void); + +#endif // MICROPY_INCLUDED_ATMEL_SAMD_SPI_FLASH_H From f20d5723aab3e6b5eea316235767c5e28a2dac2d Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Fri, 16 Feb 2018 17:22:33 -0800 Subject: [PATCH 3/5] Reorganize things. Reading jdec works. --- ports/atmel-samd/Makefile | 4 +- .../boards/metro_m4_express/mpconfigboard.h | 6 +- .../external_flash/common_commands.h | 40 +++++ .../devices/AT25DF081A.h} | 0 .../devices/GD25Q16C.h} | 0 .../devices/S25FL064L.h} | 0 .../external_flash/devices/S25FL116K.h | 54 +++++++ .../devices/S25FL216K.h} | 0 .../devices/W25Q32BV.h} | 0 .../devices/W25Q80DV.h} | 0 .../{ => external_flash}/external_flash.c | 34 ++-- .../{ => external_flash}/external_flash.h | 0 ports/atmel-samd/external_flash/qspi_flash.c | 153 ++++++++++++++++++ .../{ => external_flash}/spi_flash.c | 0 .../{ => external_flash}/spi_flash_api.h | 4 +- ports/atmel-samd/qspi_flash.c | 74 --------- 16 files changed, 265 insertions(+), 104 deletions(-) create mode 100644 ports/atmel-samd/external_flash/common_commands.h rename ports/atmel-samd/{boards/flash_AT25DF081A.h => external_flash/devices/AT25DF081A.h} (100%) rename ports/atmel-samd/{boards/flash_GD25Q16C.h => external_flash/devices/GD25Q16C.h} (100%) rename ports/atmel-samd/{boards/flash_S25FL064L.h => external_flash/devices/S25FL064L.h} (100%) create mode 100644 ports/atmel-samd/external_flash/devices/S25FL116K.h rename ports/atmel-samd/{boards/flash_S25FL216K.h => external_flash/devices/S25FL216K.h} (100%) rename ports/atmel-samd/{boards/flash_W25Q32BV.h => external_flash/devices/W25Q32BV.h} (100%) rename ports/atmel-samd/{boards/flash_W25Q80DV.h => external_flash/devices/W25Q80DV.h} (100%) rename ports/atmel-samd/{ => external_flash}/external_flash.c (95%) rename ports/atmel-samd/{ => external_flash}/external_flash.h (100%) create mode 100644 ports/atmel-samd/external_flash/qspi_flash.c rename ports/atmel-samd/{ => external_flash}/spi_flash.c (100%) rename ports/atmel-samd/{ => external_flash}/spi_flash_api.h (89%) delete mode 100644 ports/atmel-samd/qspi_flash.c diff --git a/ports/atmel-samd/Makefile b/ports/atmel-samd/Makefile index abf5bf12ef..a5d1fd7c0e 100644 --- a/ports/atmel-samd/Makefile +++ b/ports/atmel-samd/Makefile @@ -252,10 +252,10 @@ ifeq ($(INTERNAL_FLASH_FILESYSTEM),1) SRC_C += internal_flash.c endif ifeq ($(SPI_FLASH_FILESYSTEM),1) -SRC_C += external_flash.c spi_flash.c +SRC_C += external_flash/external_flash.c external_flash/spi_flash.c endif ifeq ($(QSPI_FLASH_FILESYSTEM),1) -SRC_C += external_flash.c qspi_flash.c +SRC_C += external_flash/external_flash.c external_flash/qspi_flash.c endif SRC_COMMON_HAL = \ diff --git a/ports/atmel-samd/boards/metro_m4_express/mpconfigboard.h b/ports/atmel-samd/boards/metro_m4_express/mpconfigboard.h index fdf98625ef..6ced2ca706 100644 --- a/ports/atmel-samd/boards/metro_m4_express/mpconfigboard.h +++ b/ports/atmel-samd/boards/metro_m4_express/mpconfigboard.h @@ -40,7 +40,7 @@ #define AUTORESET_DELAY_MS 500 -#include "external_flash.h" +#include "external_flash/external_flash.h" // If you change this, then make sure to update the linker scripts as well to // make sure you don't overwrite code @@ -49,5 +49,5 @@ #define BOARD_FLASH_SIZE (FLASH_SIZE - 0x4000 - CIRCUITPY_INTERNAL_NVM_SIZE) -#include "flash_S25FL216K.h" -#include "flash_GD25Q16C.h" +#include "external_flash/devices/S25FL216K.h" +#include "external_flash/devices/GD25Q16C.h" diff --git a/ports/atmel-samd/external_flash/common_commands.h b/ports/atmel-samd/external_flash/common_commands.h new file mode 100644 index 0000000000..ca1e877823 --- /dev/null +++ b/ports/atmel-samd/external_flash/common_commands.h @@ -0,0 +1,40 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef MICROPY_INCLUDED_ATMEL_SAMD_EXTERNAL_FLASH_COMMON_COMMANDS_H +#define MICROPY_INCLUDED_ATMEL_SAMD_EXTERNAL_FLASH_COMMON_COMMANDS_H + +#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_DISABLE_WRITE 0x04 +#define CMD_ENABLE_WRITE 0x06 +#define CMD_PAGE_PROGRAM 0x02 +// #define CMD_PAGE_PROGRAM CMD_READ_JEDEC_ID +#define CMD_READ_STATUS 0x05 +#define CMD_WRITE_STATUS_BYTE1 0x01 + +#endif // MICROPY_INCLUDED_ATMEL_SAMD_EXTERNAL_FLASH_COMMON_COMMANDS_H diff --git a/ports/atmel-samd/boards/flash_AT25DF081A.h b/ports/atmel-samd/external_flash/devices/AT25DF081A.h similarity index 100% rename from ports/atmel-samd/boards/flash_AT25DF081A.h rename to ports/atmel-samd/external_flash/devices/AT25DF081A.h diff --git a/ports/atmel-samd/boards/flash_GD25Q16C.h b/ports/atmel-samd/external_flash/devices/GD25Q16C.h similarity index 100% rename from ports/atmel-samd/boards/flash_GD25Q16C.h rename to ports/atmel-samd/external_flash/devices/GD25Q16C.h diff --git a/ports/atmel-samd/boards/flash_S25FL064L.h b/ports/atmel-samd/external_flash/devices/S25FL064L.h similarity index 100% rename from ports/atmel-samd/boards/flash_S25FL064L.h rename to ports/atmel-samd/external_flash/devices/S25FL064L.h diff --git a/ports/atmel-samd/external_flash/devices/S25FL116K.h b/ports/atmel-samd/external_flash/devices/S25FL116K.h new file mode 100644 index 0000000000..4928bd04e2 --- /dev/null +++ b/ports/atmel-samd/external_flash/devices/S25FL116K.h @@ -0,0 +1,54 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2017 Scott Shawcroft for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef MICROPY_INCLUDED_ATMEL_SAMD_BOARD_FLASH_S25FL116K_H +#define MICROPY_INCLUDED_ATMEL_SAMD_BOARD_FLASH_S25FL116K_H + +// Settings for the Cypress (was Spansion) S25FL116K 2MiB SPI flash. +// Datasheet: http://www.cypress.com/file/196886/download + +// The total flash size in bytes. +#define SPI_FLASH_TOTAL_SIZE (1 << 21) // 2 MiB + +// The size of the smallest erase unit thats erased with command 0x20. +#define SPI_FLASH_ERASE_SIZE (1 << 12) // 4 KiB + +// The size of a page that is programmed with page program command 0x02. +#define SPI_FLASH_PAGE_SIZE (256) // 256 bytes + +// These are the first three response bytes to the JEDEC ID command 0x9f that is +// used to confirm we're talking to the flash we expect. +#ifndef SPI_FLASH_JEDEC_MANUFACTURER +#define SPI_FLASH_JEDEC_MANUFACTURER 0x01 +#define SPI_FLASH_SECTOR_PROTECTION false +#else +#define SPI_FLASH_JEDEC_MANUFACTURER_2 0x01 +#define SPI_FLASH_SECTOR_PROTECTION_2 false +#endif +#define SPI_FLASH_JEDEC_MEMORY_TYPE 0x40 +#define SPI_FLASH_JEDEC_CAPACITY 0x15 + +#endif // MICROPY_INCLUDED_ATMEL_SAMD_BOARD_FLASH_S25FL216K_H diff --git a/ports/atmel-samd/boards/flash_S25FL216K.h b/ports/atmel-samd/external_flash/devices/S25FL216K.h similarity index 100% rename from ports/atmel-samd/boards/flash_S25FL216K.h rename to ports/atmel-samd/external_flash/devices/S25FL216K.h diff --git a/ports/atmel-samd/boards/flash_W25Q32BV.h b/ports/atmel-samd/external_flash/devices/W25Q32BV.h similarity index 100% rename from ports/atmel-samd/boards/flash_W25Q32BV.h rename to ports/atmel-samd/external_flash/devices/W25Q32BV.h diff --git a/ports/atmel-samd/boards/flash_W25Q80DV.h b/ports/atmel-samd/external_flash/devices/W25Q80DV.h similarity index 100% rename from ports/atmel-samd/boards/flash_W25Q80DV.h rename to ports/atmel-samd/external_flash/devices/W25Q80DV.h diff --git a/ports/atmel-samd/external_flash.c b/ports/atmel-samd/external_flash/external_flash.c similarity index 95% rename from ports/atmel-samd/external_flash.c rename to ports/atmel-samd/external_flash/external_flash.c index 45406462d6..c046047e4f 100644 --- a/ports/atmel-samd/external_flash.c +++ b/ports/atmel-samd/external_flash/external_flash.c @@ -28,6 +28,8 @@ #include #include +#include "external_flash/spi_flash_api.h" +#include "external_flash/common_commands.h" #include "extmod/vfs.h" #include "extmod/vfs_fat.h" #include "py/misc.h" @@ -35,7 +37,6 @@ #include "py/runtime.h" #include "lib/oofatfs/ff.h" #include "peripherals.h" -#include "spi_flash_api.h" #include "supervisor/shared/rgb_led_status.h" //#include "shared_dma.h" @@ -47,17 +48,6 @@ #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_DISABLE_WRITE 0x04 -#define CMD_ENABLE_WRITE 0x06 -#define CMD_PAGE_PROGRAM 0x02 -// #define CMD_PAGE_PROGRAM CMD_READ_JEDEC_ID -#define CMD_READ_STATUS 0x05 -#define CMD_WRITE_STATUS_BYTE1 0x01 - static bool spi_flash_is_initialised = false; struct spi_m_sync_descriptor spi_flash_desc; @@ -74,20 +64,18 @@ static uint32_t dirty_mask; // Wait until both the write enable and write in progress bits have cleared. static bool wait_for_flash_ready(void) { - uint8_t read_status_request[2] = {CMD_READ_STATUS, 0x00}; - uint8_t read_status_response[2] = {0x00, 0x00}; + uint8_t read_status_response[1] = {0x00}; bool ok = true; // Both the write enable and write in progress bits should be low. do { - ok = spi_flash_command(read_status_request, read_status_response, 2); - } while (ok && (read_status_response[1] & 0x3) != 0); + ok = spi_flash_read_command(CMD_READ_STATUS, read_status_response, 1); + } while (ok && (read_status_response[0] & 0x3) != 0); return ok; } // Turn on the write enable bit so we can program and erase the flash. static bool write_enable(void) { - uint8_t enable_write_request[1] = {CMD_ENABLE_WRITE}; - return spi_flash_command(enable_write_request, 0, 1); + return spi_flash_command(CMD_ENABLE_WRITE); } // Read data_length's worth of bytes starting at address into data. @@ -207,9 +195,8 @@ void external_flash_init(void) { gpio_set_pin_level(MICROPY_HW_LED_MSC, false); #endif - uint8_t jedec_id_request[4] = {CMD_READ_JEDEC_ID, 0x00, 0x00, 0x00}; uint8_t jedec_id_response[4] = {0x00, 0x00, 0x00, 0x00}; - spi_flash_command(jedec_id_request, jedec_id_response, 4); + spi_flash_read_command(CMD_READ_JEDEC_ID, jedec_id_response, 4); uint8_t manufacturer = jedec_id_response[1]; if ((jedec_id_response[1] == SPI_FLASH_JEDEC_MANUFACTURER @@ -234,13 +221,12 @@ void external_flash_init(void) { write_enable(); // Turn off sector protection - uint8_t disable_protect_request[2] = {CMD_WRITE_STATUS_BYTE1, 0x00}; - spi_flash_command(disable_protect_request, NULL, 2); + uint8_t data[1] = {0x00}; + spi_flash_write_command(CMD_WRITE_STATUS_BYTE1, data, 1); } // Turn off writes in case this is a microcontroller only reset. - uint8_t disable_write_request[1] = {CMD_DISABLE_WRITE}; - spi_flash_command(disable_write_request, NULL, 1); + spi_flash_command(CMD_DISABLE_WRITE); wait_for_flash_ready(); diff --git a/ports/atmel-samd/external_flash.h b/ports/atmel-samd/external_flash/external_flash.h similarity index 100% rename from ports/atmel-samd/external_flash.h rename to ports/atmel-samd/external_flash/external_flash.h diff --git a/ports/atmel-samd/external_flash/qspi_flash.c b/ports/atmel-samd/external_flash/qspi_flash.c new file mode 100644 index 0000000000..42d4e5a420 --- /dev/null +++ b/ports/atmel-samd/external_flash/qspi_flash.c @@ -0,0 +1,153 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2016, 2017 Scott Shawcroft for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "spi_flash_api.h" + +#include +#include + +#include "external_flash/common_commands.h" + +#include "atmel_start_pins.h" +#include "hal_gpio.h" + +bool spi_flash_command(uint8_t command) { + QSPI->INSTRCTRL.bit.INSTR = command; + + QSPI->INSTRFRAME.reg = QSPI_INSTRFRAME_WIDTH_SINGLE_BIT_SPI | + QSPI_INSTRFRAME_ADDRLEN_24BITS | + QSPI_INSTRFRAME_TFRTYPE_READ | + QSPI_INSTRFRAME_INSTREN; + + QSPI->CTRLA.reg = QSPI_CTRLA_ENABLE | QSPI_CTRLA_LASTXFER; + + while( !QSPI->INTFLAG.bit.INSTREND ); + + QSPI->INTFLAG.reg = QSPI_INTFLAG_INSTREND; + + return true; +} + +bool spi_flash_read_command(uint8_t command, uint8_t* response, uint32_t length) { + QSPI->INSTRCTRL.bit.INSTR = command; + + QSPI->INSTRFRAME.reg = QSPI_INSTRFRAME_WIDTH_SINGLE_BIT_SPI | + QSPI_INSTRFRAME_ADDRLEN_24BITS | + QSPI_INSTRFRAME_TFRTYPE_READ | + QSPI_INSTRFRAME_INSTREN | + QSPI_INSTRFRAME_DATAEN; + + memcpy(response, (uint8_t *) QSPI_AHB, length); + + QSPI->CTRLA.reg = QSPI_CTRLA_ENABLE | QSPI_CTRLA_LASTXFER; + + while( !QSPI->INTFLAG.bit.INSTREND ); + + QSPI->INTFLAG.reg = QSPI_INTFLAG_INSTREND; + + return true; +} + +bool spi_flash_write_command(uint8_t command, uint8_t* data, uint32_t length) { + QSPI->INSTRCTRL.bit.INSTR = command; + + QSPI->INSTRFRAME.reg = QSPI_INSTRFRAME_WIDTH_SINGLE_BIT_SPI | + QSPI_INSTRFRAME_ADDRLEN_24BITS | + QSPI_INSTRFRAME_TFRTYPE_WRITE | + QSPI_INSTRFRAME_INSTREN; + + if (data != NULL) { + QSPI->INSTRFRAME.bit.DATAEN = true; + + memcpy((uint8_t *) QSPI_AHB, data, length); + } + + QSPI->CTRLA.reg = QSPI_CTRLA_ENABLE | QSPI_CTRLA_LASTXFER; + + while( !QSPI->INTFLAG.bit.INSTREND ); + + QSPI->INTFLAG.reg = QSPI_INTFLAG_INSTREND; + + return true; +} + +bool spi_flash_sector_command(uint8_t command, uint32_t address) { +// QSPI->INSTRCTRL.bit.INSTR = command; +// QSPI->INSTRADDR.reg = addr; +// uint32_t iframe = QSPI->INSTRFRAME.reg; +// +// iframe = QSPI_INSTRFRAME_WIDTH(instr->ioFormat) | instr->options | +// QSPI_INSTRFRAME_OPTCODELEN(instr->opcodeLen) | (instr->addrLen << QSPI_INSTRFRAME_ADDRLEN_Pos) | +// ( instr->continuousRead << QSPI_INSTRFRAME_CRMODE_Pos) | QSPI_INSTRFRAME_TFRTYPE(instr->type) | QSPI_INSTRFRAME_DUMMYLEN(instr->dummylen); +// +// QSPI->INSTRFRAME.reg = iframe; + return true; +} + +bool spi_flash_write_data(uint32_t address, uint8_t* data, uint32_t data_length) { + return true; +} + +bool spi_flash_read_data(uint32_t address, uint8_t* data, uint32_t data_length) { + return true; +} + + +void spi_flash_init(void) { + MCLK->APBCMASK.bit.QSPI_ = true; + MCLK->AHBMASK.bit.QSPI_ = true; + MCLK->AHBMASK.bit.QSPI_2X_ = false; // Only true if we are doing DDR. + + QSPI->CTRLA.reg = QSPI_CTRLA_SWRST; + // We don't need to wait because we're running as fast as the CPU. + + QSPI->BAUD.bit.BAUD = 10; + QSPI->CTRLB.reg = QSPI_CTRLB_MODE_MEMORY | + QSPI_CTRLB_DATALEN_8BITS | + QSPI_CTRLB_CSMODE_LASTXFER; + + QSPI->CTRLA.bit.ENABLE = 1; + + // The QSPI is only connected to one set of pins in the SAMD51 so we can hard code it. + uint32_t pins[6] = {PIN_PA08, PIN_PA09, PIN_PA10, PIN_PA11, PIN_PB10, PIN_PB11}; + for (uint8_t i = 0; i < 6; i++) { + gpio_set_pin_direction(pins[i], GPIO_DIRECTION_IN); + gpio_set_pin_pull_mode(pins[i], GPIO_PULL_OFF); + gpio_set_pin_function(pins[i], GPIO_PIN_FUNCTION_H); + } + + // Verify that QSPI mode is enabled. + uint8_t status; + spi_flash_read_command(0x35, &status, 1); + + if ((status & 0x2) == 0) { + uint8_t full_status[3] = { 0, status | 0x2, 0x70}; + spi_flash_command(CMD_ENABLE_WRITE); + spi_flash_write_command(0x01, full_status, 3); + } + + asm("nop"); +} diff --git a/ports/atmel-samd/spi_flash.c b/ports/atmel-samd/external_flash/spi_flash.c similarity index 100% rename from ports/atmel-samd/spi_flash.c rename to ports/atmel-samd/external_flash/spi_flash.c diff --git a/ports/atmel-samd/spi_flash_api.h b/ports/atmel-samd/external_flash/spi_flash_api.h similarity index 89% rename from ports/atmel-samd/spi_flash_api.h rename to ports/atmel-samd/external_flash/spi_flash_api.h index d758faa774..b858ff0ea0 100644 --- a/ports/atmel-samd/spi_flash_api.h +++ b/ports/atmel-samd/external_flash/spi_flash_api.h @@ -31,7 +31,9 @@ // This API is implemented for both normal SPI peripherals and QSPI peripherals. -bool spi_flash_command(uint8_t* request, uint8_t* response, uint32_t length); +bool spi_flash_command(uint8_t command); +bool spi_flash_read_command(uint8_t command, uint8_t* response, uint32_t length); +bool spi_flash_write_command(uint8_t command, uint8_t* data, uint32_t length); bool spi_flash_sector_command(uint8_t command, uint32_t address); bool spi_flash_write_data(uint32_t address, uint8_t* data, uint32_t data_length); bool spi_flash_read_data(uint32_t address, uint8_t* data, uint32_t data_length); diff --git a/ports/atmel-samd/qspi_flash.c b/ports/atmel-samd/qspi_flash.c deleted file mode 100644 index 3fa53a8989..0000000000 --- a/ports/atmel-samd/qspi_flash.c +++ /dev/null @@ -1,74 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * The MIT License (MIT) - * - * Copyright (c) 2016, 2017 Scott Shawcroft for Adafruit Industries - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#include "spi_flash_api.h" - -#include - -#include "atmel_start_pins.h" -#include "hal_gpio.h" - -bool spi_flash_command(uint8_t* request, uint8_t* response, uint32_t length) { - return true; -} - -bool spi_flash_sector_command(uint8_t command, uint32_t address) { - return true; -} - -bool spi_flash_write_data(uint32_t address, uint8_t* data, uint32_t data_length) { - return true; -} - -bool spi_flash_read_data(uint32_t address, uint8_t* data, uint32_t data_length) { - return true; -} - - -void spi_flash_init(void) { - MCLK->APBCMASK.bit.QSPI_ = true; - MCLK->AHBMASK.bit.QSPI_ = true; - MCLK->AHBMASK.bit.QSPI_2X_ = false; // Only true if we are doing DDR. - - QSPI->CTRLA.reg = QSPI_CTRLA_SWRST; - // We don't need to wait because we're running as fast as the CPU. - - QSPI->BAUD.bit.BAUD = 2; - QSPI->CTRLB.reg = QSPI_CTRLB_MODE_MEMORY | - QSPI_CTRLB_CSMODE_NORELOAD | - QSPI_CTRLB_DATALEN_8BITS | - QSPI_CTRLB_CSMODE_LASTXFER; - - QSPI->CTRLA.bit.ENABLE = 1; - - // The QSPI is only connected to one set of pins in the SAMD51 so we can hard code it. - uint32_t pins[6] = {PIN_PA08, PIN_PA09, PIN_PA10, PIN_PA11, PIN_PB10, PIN_PB11}; - for (uint8_t i = 0; i < 6; i++) { - gpio_set_pin_direction(pins[i], GPIO_DIRECTION_IN); - gpio_set_pin_pull_mode(pins[i], GPIO_PULL_OFF); - gpio_set_pin_function(pins[i], GPIO_PIN_FUNCTION_H); - } -} From 15f626be5869a2dc5d8bc53ad44cc678bb5462d2 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Sat, 17 Feb 2018 00:29:03 -0800 Subject: [PATCH 4/5] m4 qspi works. m0 compiles --- .../circuitplayground_express/mpconfigboard.h | 6 +- .../boards/feather_m0_express/mpconfigboard.h | 6 +- .../feather_m0_supersized/mpconfigboard.h | 4 +- .../boards/itsybitsy_m0/mpconfigboard.h | 8 +-- .../boards/metro_m0_express/mpconfigboard.h | 6 +- .../boards/metro_m4_express/mpconfigboard.h | 4 +- .../trinket_m0_haxpress/mpconfigboard.h | 4 +- .../atmel-samd/boards/ugame10/mpconfigboard.h | 6 +- .../external_flash/common_commands.h | 1 + .../external_flash/external_flash.c | 15 +++-- ports/atmel-samd/external_flash/qspi_flash.c | 65 +++++++++++++++---- ports/atmel-samd/external_flash/spi_flash.c | 58 ++++++++++------- 12 files changed, 121 insertions(+), 62 deletions(-) diff --git a/ports/atmel-samd/boards/circuitplayground_express/mpconfigboard.h b/ports/atmel-samd/boards/circuitplayground_express/mpconfigboard.h index 2b8fed6b8b..cc09d7f790 100644 --- a/ports/atmel-samd/boards/circuitplayground_express/mpconfigboard.h +++ b/ports/atmel-samd/boards/circuitplayground_express/mpconfigboard.h @@ -38,7 +38,7 @@ #define SPEAKER_ENABLE_PIN (&pin_PA30) -#include "external_flash.h" +#include "external_flash/external_flash.h" // If you change this, then make sure to update the linker scripts as well to // make sure you don't overwrite code. @@ -47,8 +47,8 @@ #define BOARD_FLASH_SIZE (0x00040000 - 0x2000 - CIRCUITPY_INTERNAL_NVM_SIZE) -#include "flash_S25FL216K.h" -#include "flash_GD25Q16C.h" +#include "external_flash/devices/S25FL216K.h" +#include "external_flash/devices/GD25Q16C.h" #define CALIBRATE_CRYSTALLESS 1 diff --git a/ports/atmel-samd/boards/feather_m0_express/mpconfigboard.h b/ports/atmel-samd/boards/feather_m0_express/mpconfigboard.h index d0e70d954b..378b0891ad 100644 --- a/ports/atmel-samd/boards/feather_m0_express/mpconfigboard.h +++ b/ports/atmel-samd/boards/feather_m0_express/mpconfigboard.h @@ -31,7 +31,7 @@ #define MICROPY_PORT_B ( 0 ) #define MICROPY_PORT_C ( 0 ) -#include "external_flash.h" +#include "external_flash/external_flash.h" // If you change this, then make sure to update the linker scripts as well to // make sure you don't overwrite code. @@ -40,5 +40,5 @@ #define BOARD_FLASH_SIZE (0x00040000 - 0x2000 - CIRCUITPY_INTERNAL_NVM_SIZE) -#include "flash_S25FL216K.h" -#include "flash_GD25Q16C.h" +#include "external_flash/devices/S25FL216K.h" +#include "external_flash/devices/GD25Q16C.h" diff --git a/ports/atmel-samd/boards/feather_m0_supersized/mpconfigboard.h b/ports/atmel-samd/boards/feather_m0_supersized/mpconfigboard.h index 4f9b177bec..b5ed682962 100644 --- a/ports/atmel-samd/boards/feather_m0_supersized/mpconfigboard.h +++ b/ports/atmel-samd/boards/feather_m0_supersized/mpconfigboard.h @@ -33,7 +33,7 @@ #define MICROPY_PORT_B ( 0 ) #define MICROPY_PORT_C ( 0 ) -#include "external_flash.h" +#include "external_flash/external_flash.h" // If you change this, then make sure to update the linker scripts as well to // make sure you don't overwrite code. @@ -42,4 +42,4 @@ #define BOARD_FLASH_SIZE (0x00040000 - 0x2000 - CIRCUITPY_INTERNAL_NVM_SIZE) -#include "flash_S25FL064L.h" +#include "external_flash/devices/S25FL064L.h" diff --git a/ports/atmel-samd/boards/itsybitsy_m0/mpconfigboard.h b/ports/atmel-samd/boards/itsybitsy_m0/mpconfigboard.h index 116775886c..15a9922211 100644 --- a/ports/atmel-samd/boards/itsybitsy_m0/mpconfigboard.h +++ b/ports/atmel-samd/boards/itsybitsy_m0/mpconfigboard.h @@ -33,7 +33,7 @@ #define MICROPY_PORT_B (PORT_PB22 | PORT_PB23 | PORT_PB03 ) #define MICROPY_PORT_C (0) -#include "external_flash.h" +#include "external_flash/external_flash.h" // If you change this, then make sure to update the linker scripts as well to // make sure you don't overwrite code. @@ -41,6 +41,6 @@ #define CIRCUITPY_INTERNAL_NVM_SIZE 0 #define BOARD_FLASH_SIZE (0x00040000 - 0x2000 - CIRCUITPY_INTERNAL_NVM_SIZE) -//#include "flash_S25FL216K.h" -#include "flash_W25Q80DV.h" -//#include "flash_GD25Q16C.h" +//#include "external_flash/devices/S25FL216K.h" +#include "external_flash/devices/W25Q80DV.h" +//#include "external_flash/devices/GD25Q16C.h" diff --git a/ports/atmel-samd/boards/metro_m0_express/mpconfigboard.h b/ports/atmel-samd/boards/metro_m0_express/mpconfigboard.h index be1a1a57e0..70e830620d 100644 --- a/ports/atmel-samd/boards/metro_m0_express/mpconfigboard.h +++ b/ports/atmel-samd/boards/metro_m0_express/mpconfigboard.h @@ -34,7 +34,7 @@ #define MICROPY_PORT_B (PORT_PB03 | PORT_PB22 | PORT_PB23) #define MICROPY_PORT_C (0) -#include "external_flash.h" +#include "external_flash/external_flash.h" // If you change this, then make sure to update the linker scripts as well to // make sure you don't overwrite code. @@ -43,5 +43,5 @@ #define BOARD_FLASH_SIZE (0x00040000 - 0x2000 - CIRCUITPY_INTERNAL_NVM_SIZE) -#include "flash_S25FL216K.h" -#include "flash_GD25Q16C.h" +#include "external_flash/devices/S25FL216K.h" +#include "external_flash/devices/GD25Q16C.h" diff --git a/ports/atmel-samd/boards/metro_m4_express/mpconfigboard.h b/ports/atmel-samd/boards/metro_m4_express/mpconfigboard.h index 6ced2ca706..b8f14e6253 100644 --- a/ports/atmel-samd/boards/metro_m4_express/mpconfigboard.h +++ b/ports/atmel-samd/boards/metro_m4_express/mpconfigboard.h @@ -33,8 +33,8 @@ #define SPI_FLASH_DIPO 3 // same as MISO pad // These are pins not to reset. -#define MICROPY_PORT_A (PORT_PA27) -#define MICROPY_PORT_B (PORT_PB06 | PORT_PB08 | PORT_PB09 | PORT_PB10 | PORT_PB11 | PORT_PB17) +#define MICROPY_PORT_A (PORT_PA08 | PORT_PA09 | PORT_PA10 | PORT_PA11 | PORT_PA27) +#define MICROPY_PORT_B (PORT_PB10 | PORT_PB11 | PORT_PB17) #define MICROPY_PORT_C (0) #define MICROPY_PORT_D (0) diff --git a/ports/atmel-samd/boards/trinket_m0_haxpress/mpconfigboard.h b/ports/atmel-samd/boards/trinket_m0_haxpress/mpconfigboard.h index 5dcea945ac..62bca9a413 100644 --- a/ports/atmel-samd/boards/trinket_m0_haxpress/mpconfigboard.h +++ b/ports/atmel-samd/boards/trinket_m0_haxpress/mpconfigboard.h @@ -40,7 +40,7 @@ #define CALIBRATE_CRYSTALLESS 1 -#include "external_flash.h" +#include "external_flash/external_flash.h" // If you change this, then make sure to update the linker scripts as well to // make sure you don't overwrite code. @@ -48,4 +48,4 @@ #define CIRCUITPY_INTERNAL_NVM_SIZE 0 #define BOARD_FLASH_SIZE (0x00040000 - 0x2000 - CIRCUITPY_INTERNAL_NVM_SIZE) -#include "flash_W25Q32BV.h" +#include "external_flash/devices/W25Q32BV.h" diff --git a/ports/atmel-samd/boards/ugame10/mpconfigboard.h b/ports/atmel-samd/boards/ugame10/mpconfigboard.h index 3bec61ff96..d0d9296a40 100644 --- a/ports/atmel-samd/boards/ugame10/mpconfigboard.h +++ b/ports/atmel-samd/boards/ugame10/mpconfigboard.h @@ -33,7 +33,7 @@ #define CALIBRATE_CRYSTALLESS 1 -#include "external_flash.h" +#include "external_flash/external_flash.h" // If you change this, then make sure to update the linker scripts as well to // make sure you don't overwrite code. @@ -41,5 +41,5 @@ #define CIRCUITPY_INTERNAL_NVM_SIZE 0 #define BOARD_FLASH_SIZE (0x00040000 - 0x2000 - CIRCUITPY_INTERNAL_NVM_SIZE) -//#include "flash_W25Q32BV.h" -#include "flash_S25FL216K.h" +//#include "external_flash/devices/W25Q32BV.h" +#include "external_flash/devices/S25FL216K.h" diff --git a/ports/atmel-samd/external_flash/common_commands.h b/ports/atmel-samd/external_flash/common_commands.h index ca1e877823..fc05e70de7 100644 --- a/ports/atmel-samd/external_flash/common_commands.h +++ b/ports/atmel-samd/external_flash/common_commands.h @@ -36,5 +36,6 @@ // #define CMD_PAGE_PROGRAM CMD_READ_JEDEC_ID #define CMD_READ_STATUS 0x05 #define CMD_WRITE_STATUS_BYTE1 0x01 +#define CMD_QUAD_READ 0x6b #endif // MICROPY_INCLUDED_ATMEL_SAMD_EXTERNAL_FLASH_COMMON_COMMANDS_H diff --git a/ports/atmel-samd/external_flash/external_flash.c b/ports/atmel-samd/external_flash/external_flash.c index c046047e4f..48872adda7 100644 --- a/ports/atmel-samd/external_flash/external_flash.c +++ b/ports/atmel-samd/external_flash/external_flash.c @@ -195,19 +195,20 @@ void external_flash_init(void) { gpio_set_pin_level(MICROPY_HW_LED_MSC, false); #endif - uint8_t jedec_id_response[4] = {0x00, 0x00, 0x00, 0x00}; - spi_flash_read_command(CMD_READ_JEDEC_ID, jedec_id_response, 4); + uint8_t jedec_id_response[3] = {0x00, 0x00, 0x00}; + spi_flash_read_command(CMD_READ_JEDEC_ID, jedec_id_response, 3); - uint8_t manufacturer = jedec_id_response[1]; - if ((jedec_id_response[1] == SPI_FLASH_JEDEC_MANUFACTURER + uint8_t manufacturer = jedec_id_response[0]; + if ((jedec_id_response[0] == SPI_FLASH_JEDEC_MANUFACTURER #ifdef SPI_FLASH_JEDEC_MANUFACTURER_2 - || jedec_id_response[1] == SPI_FLASH_JEDEC_MANUFACTURER_2 + || jedec_id_response[0] == SPI_FLASH_JEDEC_MANUFACTURER_2 #endif ) && - jedec_id_response[2] == SPI_FLASH_JEDEC_MEMORY_TYPE && - jedec_id_response[3] == SPI_FLASH_JEDEC_CAPACITY) { + jedec_id_response[1] == SPI_FLASH_JEDEC_MEMORY_TYPE && + jedec_id_response[2] == SPI_FLASH_JEDEC_CAPACITY) { spi_flash_is_initialised = true; } else { + asm("bkpt"); // Unknown flash chip! spi_flash_is_initialised = false; return; diff --git a/ports/atmel-samd/external_flash/qspi_flash.c b/ports/atmel-samd/external_flash/qspi_flash.c index 42d4e5a420..d9dc3bf01e 100644 --- a/ports/atmel-samd/external_flash/qspi_flash.c +++ b/ports/atmel-samd/external_flash/qspi_flash.c @@ -95,23 +95,66 @@ bool spi_flash_write_command(uint8_t command, uint8_t* data, uint32_t length) { } bool spi_flash_sector_command(uint8_t command, uint32_t address) { -// QSPI->INSTRCTRL.bit.INSTR = command; -// QSPI->INSTRADDR.reg = addr; -// uint32_t iframe = QSPI->INSTRFRAME.reg; -// -// iframe = QSPI_INSTRFRAME_WIDTH(instr->ioFormat) | instr->options | -// QSPI_INSTRFRAME_OPTCODELEN(instr->opcodeLen) | (instr->addrLen << QSPI_INSTRFRAME_ADDRLEN_Pos) | -// ( instr->continuousRead << QSPI_INSTRFRAME_CRMODE_Pos) | QSPI_INSTRFRAME_TFRTYPE(instr->type) | QSPI_INSTRFRAME_DUMMYLEN(instr->dummylen); -// -// QSPI->INSTRFRAME.reg = iframe; + QSPI->INSTRCTRL.bit.INSTR = command; + QSPI->INSTRADDR.bit.ADDR = address; + + QSPI->INSTRFRAME.reg = QSPI_INSTRFRAME_WIDTH_SINGLE_BIT_SPI | + QSPI_INSTRFRAME_ADDRLEN_24BITS | + QSPI_INSTRFRAME_TFRTYPE_WRITE | + QSPI_INSTRFRAME_INSTREN | + QSPI_INSTRFRAME_ADDREN; + + QSPI->CTRLA.reg = QSPI_CTRLA_ENABLE | QSPI_CTRLA_LASTXFER; + + while( !QSPI->INTFLAG.bit.INSTREND ); + + QSPI->INTFLAG.reg = QSPI_INTFLAG_INSTREND; + return true; } -bool spi_flash_write_data(uint32_t address, uint8_t* data, uint32_t data_length) { +bool spi_flash_write_data(uint32_t address, uint8_t* data, uint32_t length) { + QSPI->INSTRCTRL.bit.INSTR = CMD_PAGE_PROGRAM; + uint32_t mode = QSPI_INSTRFRAME_WIDTH_SINGLE_BIT_SPI; + + QSPI->INSTRFRAME.reg = mode | + QSPI_INSTRFRAME_ADDRLEN_24BITS | + QSPI_INSTRFRAME_TFRTYPE_WRITEMEMORY | + QSPI_INSTRFRAME_INSTREN | + QSPI_INSTRFRAME_ADDREN | + QSPI_INSTRFRAME_DATAEN; + + memcpy(((uint8_t *) QSPI_AHB) + address, data, length); + + QSPI->CTRLA.reg = QSPI_CTRLA_ENABLE | QSPI_CTRLA_LASTXFER; + + while( !QSPI->INTFLAG.bit.INSTREND ); + + QSPI->INTFLAG.reg = QSPI_INTFLAG_INSTREND; + return true; } -bool spi_flash_read_data(uint32_t address, uint8_t* data, uint32_t data_length) { +bool spi_flash_read_data(uint32_t address, uint8_t* data, uint32_t length) { + QSPI->INSTRCTRL.bit.INSTR = CMD_QUAD_READ; + uint32_t mode = QSPI_INSTRFRAME_WIDTH_QUAD_OUTPUT; + + QSPI->INSTRFRAME.reg = mode | + QSPI_INSTRFRAME_ADDRLEN_24BITS | + QSPI_INSTRFRAME_TFRTYPE_READMEMORY | + QSPI_INSTRFRAME_INSTREN | + QSPI_INSTRFRAME_ADDREN | + QSPI_INSTRFRAME_DATAEN | + QSPI_INSTRFRAME_DUMMYLEN(8); + + memcpy(data, ((uint8_t *) QSPI_AHB) + address, length); + + QSPI->CTRLA.reg = QSPI_CTRLA_ENABLE | QSPI_CTRLA_LASTXFER; + + while( !QSPI->INTFLAG.bit.INSTREND ); + + QSPI->INTFLAG.reg = QSPI_INTFLAG_INSTREND; + return true; } diff --git a/ports/atmel-samd/external_flash/spi_flash.c b/ports/atmel-samd/external_flash/spi_flash.c index 9695067f17..4b63132eea 100644 --- a/ports/atmel-samd/external_flash/spi_flash.c +++ b/ports/atmel-samd/external_flash/spi_flash.c @@ -23,11 +23,12 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -#include "spi_flash.h" +#include "spi_flash_api.h" #include #include +#include "external_flash/common_commands.h" #include "peripherals.h" #include "hal_gpio.h" @@ -45,11 +46,32 @@ static void flash_disable(void) { gpio_set_pin_level(SPI_FLASH_CS_PIN, true); } -void spi_flash_command(uint8_t* request, uint8_t* response, uint32_t length) { - struct spi_xfer xfer = { request, response, length }; +static bool transfer(uint8_t* command, uint32_t command_length, uint8_t* data_in, uint8_t* data_out, uint32_t data_length) { + struct spi_xfer xfer = { command, NULL, command_length }; flash_enable(); - spi_m_sync_transfer(&spi_flash_desc, &xfer); + int32_t status = spi_m_sync_transfer(&spi_flash_desc, &xfer); + if (status >= 0 && !(data_in == NULL && data_out == NULL)) { + struct spi_xfer data_xfer = {data_in, data_out, data_length}; + status = spi_m_sync_transfer(&spi_flash_desc, &data_xfer); + } flash_disable(); + return status >= 0; +} + +static bool transfer_command(uint8_t command, uint8_t* data_in, uint8_t* data_out, uint32_t data_length) { + return transfer(&command, 1, data_in, data_out, data_length); +} + +bool spi_flash_command(uint8_t command) { + return transfer_command(command, NULL, NULL, 0); +} + +bool spi_flash_read_command(uint8_t command, uint8_t* data, uint32_t data_length) { + return transfer_command(command, NULL, data, data_length); +} + +bool spi_flash_write_command(uint8_t command, uint8_t* data, uint32_t data_length) { + return transfer_command(command, data, NULL, data_length); } // Pack the low 24 bits of the address into a uint8_t array. @@ -59,32 +81,24 @@ static void address_to_bytes(uint32_t address, uint8_t* bytes) { bytes[2] = address & 0xff; } -void spi_flash_sector_command(uint8_t command, uint32_t address) { +bool spi_flash_sector_command(uint8_t command, uint32_t address) { uint8_t request[4] = {command, 0x00, 0x00, 0x00}; - address_to_bytes(address, page_program_request + 1); - struct spi_xfer xfer = { request, NULL, 4 }; - flash_enable(); - spi_m_sync_transfer(&spi_flash_desc, &xfer); - flash_disable(); + address_to_bytes(address, request + 1); + return transfer(request, 4, NULL, NULL, 0); } bool spi_flash_write_data(uint32_t address, uint8_t* data, uint32_t data_length) { - flash_enable(); - uint8_t page_program_request[4] = {CMD_PAGE_PROGRAM, 0x00, 0x00, 0x00}; + uint8_t request[4] = {CMD_PAGE_PROGRAM, 0x00, 0x00, 0x00}; // Write the SPI flash write address into the bytes following the command byte. - address_to_bytes(address, page_program_request + 1); - struct spi_xfer page_program_xfer = {request, 0, request_length}; - int32_t status = spi_m_sync_transfer(&spi_flash_desc, &page_program_xfer); - if (status >= 0) { - struct spi_xfer write_data_buffer_xfer = {data, 0, data_length}; - status = spi_m_sync_transfer(&spi_flash_desc, &write_data_buffer_xfer); - } - flash_disable(); - return status >= 0; + address_to_bytes(address, request + 1); + return transfer(request, 4, data, NULL, data_length); } bool spi_flash_read_data(uint32_t address, uint8_t* data, uint32_t data_length) { - + uint8_t request[4] = {CMD_READ_DATA, 0x00, 0x00, 0x00}; + // Write the SPI flash write address into the bytes following the command byte. + address_to_bytes(address, request + 1); + return transfer(request, 4, NULL, data, data_length); } void spi_flash_init(void) { From 653fc8877df4814a66aa4979bbc09be00d151c91 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Mon, 19 Feb 2018 21:38:14 -0800 Subject: [PATCH 5/5] Remove breakpoint instruction used for debugging. --- ports/atmel-samd/external_flash/external_flash.c | 1 - 1 file changed, 1 deletion(-) diff --git a/ports/atmel-samd/external_flash/external_flash.c b/ports/atmel-samd/external_flash/external_flash.c index 48872adda7..d3a2b4bae6 100644 --- a/ports/atmel-samd/external_flash/external_flash.c +++ b/ports/atmel-samd/external_flash/external_flash.c @@ -208,7 +208,6 @@ void external_flash_init(void) { jedec_id_response[2] == SPI_FLASH_JEDEC_CAPACITY) { spi_flash_is_initialised = true; } else { - asm("bkpt"); // Unknown flash chip! spi_flash_is_initialised = false; return;