Merge pull request #31 from tannewt/external_flash
Add support for external flash chips and improve mass storage.
This commit is contained in:
commit
30dc24191f
|
@ -143,6 +143,7 @@ SRC_ASF = $(addprefix asf/sam0/,\
|
|||
)
|
||||
|
||||
SRC_C = \
|
||||
access_vfs.c \
|
||||
builtin_open.c \
|
||||
fatfs_port.c \
|
||||
main.c \
|
||||
|
@ -156,9 +157,8 @@ SRC_C = \
|
|||
modutime.c \
|
||||
mphalport.c \
|
||||
pin_named_pins.c \
|
||||
rom_fs.c \
|
||||
samdneopixel.c \
|
||||
storage.c \
|
||||
$(FLASH_IMPL) \
|
||||
asf/common/services/sleepmgr/samd/sleepmgr.c \
|
||||
asf/common/services/storage/ctrl_access/ctrl_access.c \
|
||||
asf/common/services/usb/class/cdc/device/udi_cdc.c \
|
||||
|
|
|
@ -24,10 +24,18 @@
|
|||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "rom_fs.h"
|
||||
#include <string.h>
|
||||
|
||||
#include "access_vfs.h"
|
||||
|
||||
#include "asf/common/services/usb/class/msc/device/udi_msc.h"
|
||||
#include "storage.h"
|
||||
#include "extmod/fsusermount.h"
|
||||
#include "lib/fatfs/diskio.h"
|
||||
#include "py/mpconfig.h"
|
||||
#include "py/mpstate.h"
|
||||
#include "py/misc.h"
|
||||
|
||||
#define VFS_INDEX 0
|
||||
|
||||
//! This function tests memory state, and starts memory initialization
|
||||
//! @return Ctrl_status
|
||||
|
@ -35,42 +43,72 @@
|
|||
//! Memory unplug -> CTRL_NO_PRESENT
|
||||
//! Not initialized or changed -> CTRL_BUSY
|
||||
//! An error occurred -> CTRL_FAIL
|
||||
Ctrl_status rom_fs_test_unit_ready(void)
|
||||
Ctrl_status vfs_test_unit_ready(void)
|
||||
{
|
||||
return CTRL_GOOD;
|
||||
if (VFS_INDEX >= MP_ARRAY_SIZE(MP_STATE_PORT(fs_user_mount))) {
|
||||
return CTRL_FAIL;
|
||||
}
|
||||
DSTATUS status = disk_status(VFS_INDEX);
|
||||
if (status == STA_NOINIT) {
|
||||
return CTRL_NO_PRESENT;
|
||||
}
|
||||
|
||||
return CTRL_GOOD;
|
||||
}
|
||||
|
||||
//! This function returns the address of the last valid sector
|
||||
//! @param uint32_t_nb_sector Pointer to number of sectors (sector=512 bytes)
|
||||
//! @param uint32_t_nb_sector Pointer to the last valid sector (sector=512 bytes)
|
||||
//! @return Ctrl_status
|
||||
//! It is ready -> CTRL_GOOD
|
||||
//! Memory unplug -> CTRL_NO_PRESENT
|
||||
//! Not initialized or changed -> CTRL_BUSY
|
||||
//! An error occurred -> CTRL_FAIL
|
||||
Ctrl_status rom_fs_read_capacity(uint32_t *uint32_t_nb_sector)
|
||||
Ctrl_status vfs_read_capacity(uint32_t *last_valid_sector)
|
||||
{
|
||||
*uint32_t_nb_sector = storage_get_block_count();
|
||||
return CTRL_GOOD;
|
||||
if (disk_ioctl(VFS_INDEX, GET_SECTOR_COUNT, last_valid_sector) != RES_OK) {
|
||||
return CTRL_FAIL;
|
||||
}
|
||||
// Subtract one from the sector count to get the last valid sector.
|
||||
(*last_valid_sector)--;
|
||||
return CTRL_GOOD;
|
||||
}
|
||||
|
||||
//! This function returns the write-protected mode
|
||||
//!
|
||||
//! @return true if the memory is protected
|
||||
//!
|
||||
bool rom_fs_wr_protect(void)
|
||||
bool vfs_wr_protect(void)
|
||||
{
|
||||
return false;
|
||||
if (VFS_INDEX >= MP_ARRAY_SIZE(MP_STATE_PORT(fs_user_mount))) {
|
||||
return true;
|
||||
}
|
||||
fs_user_mount_t *vfs = MP_STATE_PORT(fs_user_mount)[VFS_INDEX];
|
||||
if (vfs == NULL) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// This is used to determine the writeability of the disk from USB.
|
||||
if (vfs->writeblocks[0] == MP_OBJ_NULL ||
|
||||
(vfs->flags & FSUSER_USB_WRITEABLE) == 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//! This function informs about the memory type
|
||||
//!
|
||||
//! @return true if the memory is removable
|
||||
//!
|
||||
bool rom_fs_removal(void)
|
||||
bool vfs_removal(void)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool vfs_unload(bool unload)
|
||||
{
|
||||
return unload;
|
||||
}
|
||||
|
||||
// TODO(tannewt): Transfer more than a single sector at a time if we need more
|
||||
// speed.
|
||||
//! This function transfers the memory data to the USB MSC interface
|
||||
|
@ -84,11 +122,17 @@ bool rom_fs_removal(void)
|
|||
//! Not initialized or changed -> CTRL_BUSY
|
||||
//! An error occurred -> CTRL_FAIL
|
||||
//!
|
||||
Ctrl_status rom_fs_usb_read_10(uint32_t addr, volatile uint16_t nb_sector)
|
||||
Ctrl_status vfs_usb_read_10(uint32_t addr, volatile uint16_t nb_sector)
|
||||
{
|
||||
uint8_t sector_buffer[FLASH_BLOCK_SIZE];
|
||||
for (uint16_t sector = 0; sector < nb_sector; sector++) {
|
||||
storage_read_block(sector_buffer, addr + sector);
|
||||
DRESULT result = disk_read(VFS_INDEX, sector_buffer, addr + sector, 1);
|
||||
if (result == RES_PARERR) {
|
||||
return CTRL_NO_PRESENT;
|
||||
}
|
||||
if (result == RES_ERROR) {
|
||||
return CTRL_FAIL;
|
||||
}
|
||||
if (!udi_msc_trans_block(true, sector_buffer, FLASH_BLOCK_SIZE, NULL)) {
|
||||
return CTRL_FAIL; // transfer aborted
|
||||
}
|
||||
|
@ -108,16 +152,36 @@ Ctrl_status rom_fs_usb_read_10(uint32_t addr, volatile uint16_t nb_sector)
|
|||
//! Not initialized or changed -> CTRL_BUSY
|
||||
//! An error occurred -> CTRL_FAIL
|
||||
//!
|
||||
Ctrl_status rom_fs_usb_write_10(uint32_t addr, uint16_t nb_sector)
|
||||
Ctrl_status vfs_usb_write_10(uint32_t addr, volatile uint16_t nb_sector)
|
||||
{
|
||||
uint8_t sector_buffer[FLASH_BLOCK_SIZE];
|
||||
for (uint16_t sector = 0; sector < nb_sector; sector++) {
|
||||
if (!udi_msc_trans_block(false, sector_buffer, FLASH_BLOCK_SIZE, NULL)) {
|
||||
return CTRL_FAIL; // transfer aborted
|
||||
}
|
||||
if (!storage_write_block(sector_buffer, addr + sector)) {
|
||||
uint32_t sector_address = addr + sector;
|
||||
DRESULT result = disk_write(VFS_INDEX, sector_buffer, sector_address, 1);
|
||||
if (result == RES_PARERR) {
|
||||
return CTRL_NO_PRESENT;
|
||||
}
|
||||
if (result == RES_ERROR) {
|
||||
return CTRL_FAIL;
|
||||
}
|
||||
// Since by getting here we assume the mount is read-only to MicroPython
|
||||
// lets update the cached FatFs sector if its the one we just wrote.
|
||||
fs_user_mount_t *vfs = MP_STATE_PORT(fs_user_mount)[VFS_INDEX];
|
||||
volatile uint16_t x = addr;
|
||||
(void) x;
|
||||
#if _MAX_SS != _MIN_SS
|
||||
if (vfs->ssize == FLASH_BLOCK_SIZE) {
|
||||
#else
|
||||
// The compiler can optimize this away.
|
||||
if (_MAX_SS == FLASH_BLOCK_SIZE) {
|
||||
#endif
|
||||
if (sector_address == vfs->fatfs.winsect && sector_address > 0) {
|
||||
memcpy(vfs->fatfs.win, sector_buffer, FLASH_BLOCK_SIZE);
|
||||
}
|
||||
}
|
||||
}
|
||||
return CTRL_GOOD;
|
||||
}
|
|
@ -24,17 +24,20 @@
|
|||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
// This adapts the ASF access API to MicroPython's VFS API so we can expose all
|
||||
// VFS block devices as Lun's over USB mass storage control.
|
||||
|
||||
#ifndef __MICROPY_INCLUDED_ATMEL_SAMD_ROM_FS_H__
|
||||
#define __MICROPY_INCLUDED_ATMEL_SAMD_ROM_FS_H__
|
||||
|
||||
#include "asf/common/services/storage/ctrl_access/ctrl_access.h"
|
||||
|
||||
|
||||
Ctrl_status rom_fs_test_unit_ready(void);
|
||||
Ctrl_status rom_fs_read_capacity(uint32_t *u32_nb_sector);
|
||||
bool rom_fs_wr_protect(void);
|
||||
bool rom_fs_removal(void);
|
||||
Ctrl_status rom_fs_usb_read_10(uint32_t addr, uint16_t nb_sector);
|
||||
Ctrl_status rom_fs_usb_write_10(uint32_t addr, uint16_t nb_sector);
|
||||
Ctrl_status vfs_test_unit_ready(void);
|
||||
Ctrl_status vfs_read_capacity(uint32_t *u32_nb_sector);
|
||||
bool vfs_wr_protect(void);
|
||||
bool vfs_removal(void);
|
||||
bool vfs_unload(bool);
|
||||
Ctrl_status vfs_usb_read_10(uint32_t addr, uint16_t nb_sector);
|
||||
Ctrl_status vfs_usb_write_10(uint32_t addr, uint16_t nb_sector);
|
||||
|
||||
#endif // __MICROPY_INCLUDED_ATMEL_SAMD_ROM_FS_H__
|
|
@ -68,15 +68,15 @@
|
|||
/*! \name LUN 0 Definitions
|
||||
*/
|
||||
//! @{
|
||||
#define LUN_0_INCLUDE "rom_fs.h"
|
||||
#define Lun_0_test_unit_ready rom_fs_test_unit_ready
|
||||
#define Lun_0_read_capacity rom_fs_read_capacity
|
||||
#define Lun_0_unload NULL /* Can not be unloaded */
|
||||
#define Lun_0_wr_protect rom_fs_wr_protect
|
||||
#define Lun_0_removal rom_fs_removal
|
||||
#define Lun_0_usb_read_10 rom_fs_usb_read_10
|
||||
#define Lun_0_usb_write_10 rom_fs_usb_write_10
|
||||
#define LUN_0_NAME "\"On-Chip ROM\""
|
||||
#define LUN_0_INCLUDE "access_vfs.h"
|
||||
#define Lun_0_test_unit_ready vfs_test_unit_ready
|
||||
#define Lun_0_read_capacity vfs_read_capacity
|
||||
#define Lun_0_unload NULL
|
||||
#define Lun_0_wr_protect vfs_wr_protect
|
||||
#define Lun_0_removal vfs_removal
|
||||
#define Lun_0_usb_read_10 vfs_usb_read_10
|
||||
#define Lun_0_usb_write_10 vfs_usb_write_10
|
||||
#define LUN_0_NAME "\"MicroPython VFS[0]\""
|
||||
//! @}
|
||||
|
||||
#define MEM_USB LUN_USB
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
LD_FILE = boards/samd21x18-bootloader.ld
|
||||
USB_VID = 0x2341
|
||||
USB_PID = 0x824D
|
||||
|
||||
FLASH_IMPL = internal_flash.c
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
LD_FILE = boards/samd21x18-bootloader.ld
|
||||
USB_VID = 0x239A
|
||||
USB_PID = 0x8015
|
||||
|
||||
FLASH_IMPL = internal_flash.c
|
||||
|
|
|
@ -68,15 +68,15 @@
|
|||
/*! \name LUN 0 Definitions
|
||||
*/
|
||||
//! @{
|
||||
#define LUN_0_INCLUDE "rom_fs.h"
|
||||
#define Lun_0_test_unit_ready rom_fs_test_unit_ready
|
||||
#define Lun_0_read_capacity rom_fs_read_capacity
|
||||
#define Lun_0_unload NULL /* Can not be unloaded */
|
||||
#define Lun_0_wr_protect rom_fs_wr_protect
|
||||
#define Lun_0_removal rom_fs_removal
|
||||
#define Lun_0_usb_read_10 rom_fs_usb_read_10
|
||||
#define Lun_0_usb_write_10 rom_fs_usb_write_10
|
||||
#define LUN_0_NAME "\"On-Chip ROM\""
|
||||
#define LUN_0_INCLUDE "access_vfs.h"
|
||||
#define Lun_0_test_unit_ready vfs_test_unit_ready
|
||||
#define Lun_0_read_capacity vfs_read_capacity
|
||||
#define Lun_0_unload NULL
|
||||
#define Lun_0_wr_protect vfs_wr_protect
|
||||
#define Lun_0_removal vfs_removal
|
||||
#define Lun_0_usb_read_10 vfs_usb_read_10
|
||||
#define Lun_0_usb_write_10 vfs_usb_write_10
|
||||
#define LUN_0_NAME "\"MicroPython VFS[0]\""
|
||||
//! @}
|
||||
|
||||
#define MEM_USB LUN_USB
|
||||
|
|
|
@ -1,10 +1,31 @@
|
|||
// LEDs
|
||||
#define MICROPY_HW_LED1 PIN_PA17 // red
|
||||
// #define UART_REPL
|
||||
#define USB_REPL
|
||||
|
||||
#define MICROPY_HW_BOARD_NAME "Adafruit Metro M0 with Flash (Experimental)"
|
||||
#define MICROPY_HW_MCU_NAME "samd21g18"
|
||||
|
||||
#define MICROPY_HW_LED_MSC PIN_PA17
|
||||
#define MICROPY_HW_LED_TX PIN_PA27
|
||||
#define MICROPY_HW_LED_RX PIN_PB03
|
||||
|
||||
#define SPI_FLASH_BAUDRATE (1000000)
|
||||
|
||||
// Off-board flash
|
||||
// #define SPI_FLASH_MUX_SETTING SPI_SIGNAL_MUX_SETTING_E
|
||||
// #define SPI_FLASH_PAD0_PINMUX PINMUX_PA16C_SERCOM1_PAD0 // MISO D11
|
||||
// Use default pinmux for the chip select since we manage it ourselves.
|
||||
// #define SPI_FLASH_PAD1_PINMUX PINMUX_DEFAULT
|
||||
// #define SPI_FLASH_PAD2_PINMUX PINMUX_PA18C_SERCOM1_PAD2 // MOSI D10
|
||||
// #define SPI_FLASH_PAD3_PINMUX PINMUX_PA19C_SERCOM1_PAD3 // SCK D12
|
||||
// #define SPI_FLASH_CS PIN_PA17
|
||||
// #define SPI_FLASH_SERCOM SERCOM1
|
||||
|
||||
// On-board flash
|
||||
#define SPI_FLASH_MUX_SETTING SPI_SIGNAL_MUX_SETTING_E
|
||||
#define SPI_FLASH_PAD0_PINMUX PINMUX_PA12D_SERCOM4_PAD0 // MISO
|
||||
// Use default pinmux for the chip select since we manage it ourselves.
|
||||
#define SPI_FLASH_PAD1_PINMUX PINMUX_DEFAULT // CS
|
||||
#define SPI_FLASH_PAD2_PINMUX PINMUX_PB10D_SERCOM4_PAD2 // MOSI
|
||||
#define SPI_FLASH_PAD3_PINMUX PINMUX_PB11D_SERCOM4_PAD3 // SCK
|
||||
#define SPI_FLASH_CS PIN_PA13
|
||||
#define SPI_FLASH_SERCOM SERCOM4
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
LD_FILE = boards/samd21x18-bootloader-external-flash.ld
|
||||
USB_VID = 0x239A
|
||||
USB_PID = 0x8015
|
||||
|
||||
FLASH_IMPL = spi_flash.c
|
||||
|
|
|
@ -92,7 +92,7 @@ PIN(PA19, false, NO_ADC_INPUT,
|
|||
TIMER(TC3, 0, 1, 1, PIN_PA19E_TC3_WO1, MUX_PA19E_TC3_WO1),
|
||||
TIMER(0, TCC0, 3, 3, PIN_PA19F_TCC0_WO3, MUX_PA19F_TCC0_WO3),
|
||||
SERCOM(SERCOM1, 3, PINMUX_PA19C_SERCOM1_PAD3),
|
||||
SERCOM(SERCOM3, 3, PINMUX_PA19C_SERCOM1_PAD3));
|
||||
SERCOM(SERCOM3, 3, PINMUX_PA19D_SERCOM3_PAD3));
|
||||
PIN(PA17, false, NO_ADC_INPUT,
|
||||
TIMER(0, TCC2, 1, 1, PIN_PA17E_TCC2_WO1, MUX_PA17E_TCC2_WO1),
|
||||
TIMER(0, TCC0, 3, 7, PIN_PA17F_TCC0_WO7, MUX_PA17F_TCC0_WO7),
|
||||
|
|
|
@ -64,5 +64,13 @@ SECTIONS
|
|||
_ebss = .;
|
||||
} >RAM
|
||||
|
||||
/* this just checks there is enough RAM for a minimal stack */
|
||||
.stack :
|
||||
{
|
||||
. = ALIGN(4);
|
||||
. = . + 0x800; /* Reserve a minimum of 2K for the stack. */
|
||||
. = ALIGN(4);
|
||||
} >RAM
|
||||
|
||||
.ARM.attributes 0 : { *(.ARM.attributes) }
|
||||
}
|
||||
|
|
|
@ -64,5 +64,13 @@ SECTIONS
|
|||
_ebss = .;
|
||||
} >RAM
|
||||
|
||||
/* this just checks there is enough RAM for a minimal stack */
|
||||
.stack :
|
||||
{
|
||||
. = ALIGN(4);
|
||||
. = . + 0x800; /* Reserve a minimum of 2K for the stack. */
|
||||
. = ALIGN(4);
|
||||
} >RAM
|
||||
|
||||
.ARM.attributes 0 : { *(.ARM.attributes) }
|
||||
}
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
GNU linker script for SAMD21
|
||||
*/
|
||||
|
||||
/* Specify the memory areas */
|
||||
MEMORY
|
||||
{
|
||||
FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 0x00040000 /* 256 KiB */
|
||||
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 0x008000 /* 32 KiB */
|
||||
}
|
||||
|
||||
/* top end of the stack */
|
||||
_estack = ORIGIN(RAM) + LENGTH(RAM);
|
||||
|
||||
/* define output sections */
|
||||
SECTIONS
|
||||
{
|
||||
/* The program code and other data goes into FLASH */
|
||||
.text :
|
||||
{
|
||||
. = ALIGN(4);
|
||||
_sfixed = .;
|
||||
KEEP(*(.vectors)) /* isr vector table */
|
||||
*(.text) /* .text sections (code) */
|
||||
*(.text*) /* .text* sections (code) */
|
||||
*(.rodata) /* .rodata sections (constants, strings, etc.) */
|
||||
*(.rodata*) /* .rodata* sections (constants, strings, etc.) */
|
||||
|
||||
. = ALIGN(4);
|
||||
_etext = .; /* define a global symbol at end of code */
|
||||
_sidata = _etext; /* This is used by the startup in order to initialize the .data section */
|
||||
} >FLASH
|
||||
|
||||
/* This is the initialized data section
|
||||
The program executes knowing that the data is in the RAM
|
||||
but the loader puts the initial values in the FLASH (inidata).
|
||||
It is one task of the startup to copy the initial values from FLASH to RAM. */
|
||||
.data : AT ( _sidata )
|
||||
{
|
||||
. = ALIGN(4);
|
||||
_srelocate = .; /* create a global symbol at data start; used by startup code in order to initialise the .data section in RAM */
|
||||
*(.ramfunc)
|
||||
*(.ramfunc*)
|
||||
*(.data) /* .data sections */
|
||||
*(.data*) /* .data* sections */
|
||||
|
||||
. = ALIGN(4);
|
||||
_erelocate = .; /* define a global symbol at data end; used by startup code in order to initialise the .data section in RAM */
|
||||
} >RAM
|
||||
|
||||
/* Uninitialized data section */
|
||||
.bss :
|
||||
{
|
||||
. = ALIGN(4);
|
||||
_sbss = .;
|
||||
_szero = .; /* define a global symbol at bss start; used by startup code */
|
||||
*(.bss)
|
||||
*(.bss*)
|
||||
*(COMMON)
|
||||
|
||||
. = ALIGN(4);
|
||||
_ezero = .; /* define a global symbol at bss end; used by startup code */
|
||||
_ebss = .;
|
||||
} >RAM
|
||||
|
||||
/* this just checks there is enough RAM for a minimal stack */
|
||||
.stack :
|
||||
{
|
||||
. = ALIGN(4);
|
||||
. = . + 0x800; /* Reserve a minimum of 2K for the stack. */
|
||||
. = ALIGN(4);
|
||||
} >RAM
|
||||
|
||||
.ARM.attributes 0 : { *(.ARM.attributes) }
|
||||
}
|
|
@ -63,5 +63,13 @@ SECTIONS
|
|||
_ebss = .;
|
||||
} >RAM
|
||||
|
||||
/* this just checks there is enough RAM for a minimal stack */
|
||||
.stack :
|
||||
{
|
||||
. = ALIGN(4);
|
||||
. = . + 0x800; /* Reserve a minimum of 2K for the stack. */
|
||||
. = ALIGN(4);
|
||||
} >RAM
|
||||
|
||||
.ARM.attributes 0 : { *(.ARM.attributes) }
|
||||
}
|
||||
|
|
|
@ -34,35 +34,35 @@
|
|||
|
||||
#include "asf/sam0/drivers/nvm/nvm.h"
|
||||
|
||||
#include "storage.h"
|
||||
#include "internal_flash.h"
|
||||
|
||||
#define TOTAL_FLASH_SIZE 0x010000
|
||||
#define TOTAL_INTERNAL_FLASH_SIZE 0x010000
|
||||
|
||||
#define FLASH_MEM_SEG1_START_ADDR (0x00040000 - TOTAL_FLASH_SIZE)
|
||||
#define FLASH_PART1_START_BLOCK (0x100)
|
||||
#define FLASH_PART1_NUM_BLOCKS (TOTAL_FLASH_SIZE / FLASH_BLOCK_SIZE)
|
||||
#define INTERNAL_FLASH_MEM_SEG1_START_ADDR (0x00040000 - TOTAL_INTERNAL_FLASH_SIZE)
|
||||
#define INTERNAL_FLASH_PART1_START_BLOCK (0x100)
|
||||
#define INTERNAL_FLASH_PART1_NUM_BLOCKS (TOTAL_INTERNAL_FLASH_SIZE / INTERNAL_FLASH_BLOCK_SIZE)
|
||||
|
||||
static bool flash_is_initialised = false;
|
||||
static bool internal_flash_is_initialised = false;
|
||||
|
||||
void storage_init(void) {
|
||||
if (!flash_is_initialised) {
|
||||
void internal_flash_init(void) {
|
||||
if (!internal_flash_is_initialised) {
|
||||
struct nvm_config config_nvm;
|
||||
nvm_get_config_defaults(&config_nvm);
|
||||
config_nvm.manual_page_write = false;
|
||||
nvm_set_config(&config_nvm);
|
||||
flash_is_initialised = true;
|
||||
internal_flash_is_initialised = true;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t storage_get_block_size(void) {
|
||||
return FLASH_BLOCK_SIZE;
|
||||
uint32_t internal_flash_get_block_size(void) {
|
||||
return INTERNAL_FLASH_BLOCK_SIZE;
|
||||
}
|
||||
|
||||
uint32_t storage_get_block_count(void) {
|
||||
return FLASH_PART1_START_BLOCK + FLASH_PART1_NUM_BLOCKS;
|
||||
uint32_t internal_flash_get_block_count(void) {
|
||||
return INTERNAL_FLASH_PART1_START_BLOCK + INTERNAL_FLASH_PART1_NUM_BLOCKS;
|
||||
}
|
||||
|
||||
void storage_flush(void) {
|
||||
void internal_flash_flush(void) {
|
||||
}
|
||||
|
||||
static void build_partition(uint8_t *buf, int boot, int type, uint32_t start_block, uint32_t num_blocks) {
|
||||
|
@ -102,16 +102,16 @@ static void build_partition(uint8_t *buf, int boot, int type, uint32_t start_blo
|
|||
}
|
||||
|
||||
static uint32_t convert_block_to_flash_addr(uint32_t block) {
|
||||
if (FLASH_PART1_START_BLOCK <= block && block < FLASH_PART1_START_BLOCK + FLASH_PART1_NUM_BLOCKS) {
|
||||
if (INTERNAL_FLASH_PART1_START_BLOCK <= block && block < INTERNAL_FLASH_PART1_START_BLOCK + INTERNAL_FLASH_PART1_NUM_BLOCKS) {
|
||||
// a block in partition 1
|
||||
block -= FLASH_PART1_START_BLOCK;
|
||||
return FLASH_MEM_SEG1_START_ADDR + block * FLASH_BLOCK_SIZE;
|
||||
block -= INTERNAL_FLASH_PART1_START_BLOCK;
|
||||
return INTERNAL_FLASH_MEM_SEG1_START_ADDR + block * INTERNAL_FLASH_BLOCK_SIZE;
|
||||
}
|
||||
// bad block
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool storage_read_block(uint8_t *dest, uint32_t block) {
|
||||
bool internal_flash_read_block(uint8_t *dest, uint32_t block) {
|
||||
//printf("RD %u\n", block);
|
||||
if (block == 0) {
|
||||
// fake the MBR so we can decide on our own partition table
|
||||
|
@ -120,7 +120,7 @@ bool storage_read_block(uint8_t *dest, uint32_t block) {
|
|||
dest[i] = 0;
|
||||
}
|
||||
|
||||
build_partition(dest + 446, 0, 0x01 /* FAT12 */, FLASH_PART1_START_BLOCK, FLASH_PART1_NUM_BLOCKS);
|
||||
build_partition(dest + 446, 0, 0x01 /* FAT12 */, INTERNAL_FLASH_PART1_START_BLOCK, INTERNAL_FLASH_PART1_NUM_BLOCKS);
|
||||
build_partition(dest + 462, 0, 0, 0, 0);
|
||||
build_partition(dest + 478, 0, 0, 0, 0);
|
||||
build_partition(dest + 494, 0, 0, 0, 0);
|
||||
|
@ -140,7 +140,7 @@ bool storage_read_block(uint8_t *dest, uint32_t block) {
|
|||
enum status_code error_code;
|
||||
// A block is made up of multiple pages. Read each page
|
||||
// sequentially.
|
||||
for (int i = 0; i < FLASH_BLOCK_SIZE / NVMCTRL_PAGE_SIZE; i++) {
|
||||
for (int i = 0; i < INTERNAL_FLASH_BLOCK_SIZE / NVMCTRL_PAGE_SIZE; i++) {
|
||||
do
|
||||
{
|
||||
error_code = nvm_read_buffer(src + i * NVMCTRL_PAGE_SIZE,
|
||||
|
@ -152,7 +152,7 @@ bool storage_read_block(uint8_t *dest, uint32_t block) {
|
|||
}
|
||||
}
|
||||
|
||||
bool storage_write_block(const uint8_t *src, uint32_t block) {
|
||||
bool internal_flash_write_block(const uint8_t *src, uint32_t block) {
|
||||
if (block == 0) {
|
||||
// can't write MBR, but pretend we did
|
||||
return true;
|
||||
|
@ -184,7 +184,7 @@ bool storage_write_block(const uint8_t *src, uint32_t block) {
|
|||
|
||||
// A block is made up of multiple pages. Write each page
|
||||
// sequentially.
|
||||
for (int i = 0; i < FLASH_BLOCK_SIZE / NVMCTRL_PAGE_SIZE; i++) {
|
||||
for (int i = 0; i < INTERNAL_FLASH_BLOCK_SIZE / NVMCTRL_PAGE_SIZE; i++) {
|
||||
do
|
||||
{
|
||||
error_code = nvm_write_buffer(dest + i * NVMCTRL_PAGE_SIZE,
|
||||
|
@ -199,18 +199,18 @@ bool storage_write_block(const uint8_t *src, uint32_t block) {
|
|||
}
|
||||
}
|
||||
|
||||
mp_uint_t storage_read_blocks(uint8_t *dest, uint32_t block_num, uint32_t num_blocks) {
|
||||
mp_uint_t internal_flash_read_blocks(uint8_t *dest, uint32_t block_num, uint32_t num_blocks) {
|
||||
for (size_t i = 0; i < num_blocks; i++) {
|
||||
if (!storage_read_block(dest + i * FLASH_BLOCK_SIZE, block_num + i)) {
|
||||
if (!internal_flash_read_block(dest + i * INTERNAL_FLASH_BLOCK_SIZE, block_num + i)) {
|
||||
return 1; // error
|
||||
}
|
||||
}
|
||||
return 0; // success
|
||||
}
|
||||
|
||||
mp_uint_t storage_write_blocks(const uint8_t *src, uint32_t block_num, uint32_t num_blocks) {
|
||||
mp_uint_t internal_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 (!storage_write_block(src + i * FLASH_BLOCK_SIZE, block_num + i)) {
|
||||
if (!internal_flash_write_block(src + i * INTERNAL_FLASH_BLOCK_SIZE, block_num + i)) {
|
||||
return 1; // error
|
||||
}
|
||||
}
|
||||
|
@ -223,68 +223,68 @@ mp_uint_t storage_write_blocks(const uint8_t *src, uint32_t block_num, uint32_t
|
|||
// Expose the flash as an object with the block protocol.
|
||||
|
||||
// there is a singleton Flash object
|
||||
STATIC const mp_obj_base_t flash_obj = {&flash_type};
|
||||
STATIC const mp_obj_base_t internal_flash_obj = {&internal_flash_type};
|
||||
|
||||
STATIC mp_obj_t flash_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) {
|
||||
STATIC mp_obj_t internal_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)&flash_obj;
|
||||
return (mp_obj_t)&internal_flash_obj;
|
||||
}
|
||||
|
||||
STATIC mp_obj_t flash_readblocks(mp_obj_t self, mp_obj_t block_num, mp_obj_t buf) {
|
||||
STATIC mp_obj_t internal_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 = storage_read_blocks(bufinfo.buf, mp_obj_get_int(block_num), bufinfo.len / FLASH_BLOCK_SIZE);
|
||||
mp_uint_t ret = internal_flash_read_blocks(bufinfo.buf, mp_obj_get_int(block_num), bufinfo.len / FLASH_BLOCK_SIZE);
|
||||
return MP_OBJ_NEW_SMALL_INT(ret);
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_3(flash_readblocks_obj, flash_readblocks);
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_3(internal_flash_obj_readblocks_obj, internal_flash_obj_readblocks);
|
||||
|
||||
STATIC mp_obj_t flash_writeblocks(mp_obj_t self, mp_obj_t block_num, mp_obj_t buf) {
|
||||
STATIC mp_obj_t internal_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 = storage_write_blocks(bufinfo.buf, mp_obj_get_int(block_num), bufinfo.len / FLASH_BLOCK_SIZE);
|
||||
mp_uint_t ret = internal_flash_write_blocks(bufinfo.buf, mp_obj_get_int(block_num), bufinfo.len / FLASH_BLOCK_SIZE);
|
||||
return MP_OBJ_NEW_SMALL_INT(ret);
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_3(flash_writeblocks_obj, flash_writeblocks);
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_3(internal_flash_obj_writeblocks_obj, internal_flash_obj_writeblocks);
|
||||
|
||||
STATIC mp_obj_t flash_ioctl(mp_obj_t self, mp_obj_t cmd_in, mp_obj_t arg_in) {
|
||||
STATIC mp_obj_t internal_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: storage_init(); return MP_OBJ_NEW_SMALL_INT(0);
|
||||
case BP_IOCTL_DEINIT: storage_flush(); return MP_OBJ_NEW_SMALL_INT(0); // TODO properly
|
||||
case BP_IOCTL_SYNC: storage_flush(); return MP_OBJ_NEW_SMALL_INT(0);
|
||||
case BP_IOCTL_SEC_COUNT: return MP_OBJ_NEW_SMALL_INT(storage_get_block_count());
|
||||
case BP_IOCTL_SEC_SIZE: return MP_OBJ_NEW_SMALL_INT(storage_get_block_size());
|
||||
case BP_IOCTL_INIT: internal_flash_init(); return MP_OBJ_NEW_SMALL_INT(0);
|
||||
case BP_IOCTL_DEINIT: internal_flash_flush(); return MP_OBJ_NEW_SMALL_INT(0); // TODO properly
|
||||
case BP_IOCTL_SYNC: internal_flash_flush(); return MP_OBJ_NEW_SMALL_INT(0);
|
||||
case BP_IOCTL_SEC_COUNT: return MP_OBJ_NEW_SMALL_INT(internal_flash_get_block_count());
|
||||
case BP_IOCTL_SEC_SIZE: return MP_OBJ_NEW_SMALL_INT(internal_flash_get_block_size());
|
||||
default: return mp_const_none;
|
||||
}
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_3(flash_ioctl_obj, flash_ioctl);
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_3(internal_flash_obj_ioctl_obj, internal_flash_obj_ioctl);
|
||||
|
||||
STATIC const mp_map_elem_t flash_locals_dict_table[] = {
|
||||
{ MP_OBJ_NEW_QSTR(MP_QSTR_readblocks), (mp_obj_t)&flash_readblocks_obj },
|
||||
{ MP_OBJ_NEW_QSTR(MP_QSTR_writeblocks), (mp_obj_t)&flash_writeblocks_obj },
|
||||
{ MP_OBJ_NEW_QSTR(MP_QSTR_ioctl), (mp_obj_t)&flash_ioctl_obj },
|
||||
STATIC const mp_map_elem_t internal_flash_obj_locals_dict_table[] = {
|
||||
{ MP_OBJ_NEW_QSTR(MP_QSTR_readblocks), (mp_obj_t)&internal_flash_obj_readblocks_obj },
|
||||
{ MP_OBJ_NEW_QSTR(MP_QSTR_writeblocks), (mp_obj_t)&internal_flash_obj_writeblocks_obj },
|
||||
{ MP_OBJ_NEW_QSTR(MP_QSTR_ioctl), (mp_obj_t)&internal_flash_obj_ioctl_obj },
|
||||
};
|
||||
|
||||
STATIC MP_DEFINE_CONST_DICT(flash_locals_dict, flash_locals_dict_table);
|
||||
STATIC MP_DEFINE_CONST_DICT(internal_flash_obj_locals_dict, internal_flash_obj_locals_dict_table);
|
||||
|
||||
const mp_obj_type_t flash_type = {
|
||||
const mp_obj_type_t internal_flash_type = {
|
||||
{ &mp_type_type },
|
||||
.name = MP_QSTR_Flash,
|
||||
.make_new = flash_make_new,
|
||||
.locals_dict = (mp_obj_t)&flash_locals_dict,
|
||||
.name = MP_QSTR_InternalFlash,
|
||||
.make_new = internal_flash_obj_make_new,
|
||||
.locals_dict = (mp_obj_t)&internal_flash_obj_locals_dict,
|
||||
};
|
||||
|
||||
void flash_init_vfs(fs_user_mount_t *vfs) {
|
||||
vfs->flags |= FSUSER_NATIVE | FSUSER_HAVE_IOCTL;
|
||||
vfs->readblocks[0] = (mp_obj_t)&flash_readblocks_obj;
|
||||
vfs->readblocks[1] = (mp_obj_t)&flash_obj;
|
||||
vfs->readblocks[2] = (mp_obj_t)storage_read_blocks; // native version
|
||||
vfs->writeblocks[0] = (mp_obj_t)&flash_writeblocks_obj;
|
||||
vfs->writeblocks[1] = (mp_obj_t)&flash_obj;
|
||||
vfs->writeblocks[2] = (mp_obj_t)storage_write_blocks; // native version
|
||||
vfs->u.ioctl[0] = (mp_obj_t)&flash_ioctl_obj;
|
||||
vfs->u.ioctl[1] = (mp_obj_t)&flash_obj;
|
||||
vfs->flags |= FSUSER_NATIVE | FSUSER_HAVE_IOCTL | FSUSER_USB_WRITEABLE;
|
||||
vfs->readblocks[0] = (mp_obj_t)&internal_flash_obj_readblocks_obj;
|
||||
vfs->readblocks[1] = (mp_obj_t)&internal_flash_obj;
|
||||
vfs->readblocks[2] = (mp_obj_t)internal_flash_read_blocks; // native version
|
||||
vfs->writeblocks[0] = (mp_obj_t)&internal_flash_obj_writeblocks_obj;
|
||||
vfs->writeblocks[1] = (mp_obj_t)&internal_flash_obj;
|
||||
vfs->writeblocks[2] = (mp_obj_t)internal_flash_write_blocks; // native version
|
||||
vfs->u.ioctl[0] = (mp_obj_t)&internal_flash_obj_ioctl_obj;
|
||||
vfs->u.ioctl[1] = (mp_obj_t)&internal_flash_obj;
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* This file is part of the Micro Python 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_INTERNAL_FLASH_H__
|
||||
#define __MICROPY_INCLUDED_ATMEL_SAMD_INTERNAL_FLASH_H__
|
||||
|
||||
#include "mpconfigport.h"
|
||||
|
||||
#define INTERNAL_FLASH_BLOCK_SIZE (512)
|
||||
|
||||
#define INTERNAL_FLASH_SYSTICK_MASK (0x1ff) // 512ms
|
||||
#define INTERNAL_FLASH_IDLE_TICK(tick) (((tick) & INTERNAL_FLASH_SYSTICK_MASK) == 2)
|
||||
|
||||
void internal_flash_init(void);
|
||||
uint32_t internal_flash_get_block_size(void);
|
||||
uint32_t internal_flash_get_block_count(void);
|
||||
void internal_flash_irq_handler(void);
|
||||
void internal_flash_flush(void);
|
||||
bool internal_flash_read_block(uint8_t *dest, uint32_t block);
|
||||
bool internal_flash_write_block(const uint8_t *src, uint32_t block);
|
||||
|
||||
// these return 0 on success, non-zero on error
|
||||
mp_uint_t internal_flash_read_blocks(uint8_t *dest, uint32_t block_num, uint32_t num_blocks);
|
||||
mp_uint_t internal_flash_write_blocks(const uint8_t *src, uint32_t block_num, uint32_t num_blocks);
|
||||
|
||||
extern const struct _mp_obj_type_t internal_flash_type;
|
||||
|
||||
struct _fs_user_mount_t;
|
||||
void flash_init_vfs(struct _fs_user_mount_t *vfs);
|
||||
|
||||
#endif // __MICROPY_INCLUDED_ATMEL_SAMD_INTERNAL_FLASH_H__
|
|
@ -10,6 +10,7 @@
|
|||
#include "py/gc.h"
|
||||
|
||||
#include "lib/fatfs/ff.h"
|
||||
#include "lib/fatfs/diskio.h"
|
||||
#include "lib/utils/pyexec.h"
|
||||
#include "extmod/fsusermount.h"
|
||||
|
||||
|
@ -23,7 +24,6 @@
|
|||
|
||||
#include "mpconfigboard.h"
|
||||
#include "modmachine_pin.h"
|
||||
#include "storage.h"
|
||||
|
||||
fs_user_mount_t fs_user_mount_flash;
|
||||
|
||||
|
@ -72,6 +72,8 @@ static const char fresh_readme_txt[] =
|
|||
"Please visit http://micropython.org/help/ for further help.\r\n"
|
||||
;
|
||||
|
||||
extern void flash_init_vfs(fs_user_mount_t *vfs);
|
||||
|
||||
// we don't make this function static because it needs a lot of stack and we
|
||||
// want it to be executed without using stack within main() function
|
||||
void init_flash_fs() {
|
||||
|
@ -159,6 +161,12 @@ static char *stack_top;
|
|||
static char heap[16384];
|
||||
|
||||
void reset_mp() {
|
||||
// Sync the file systems in case any used RAM from the GC to cache. As soon
|
||||
// as we re-init the GC all bets are off on the cache.
|
||||
disk_ioctl(0, CTRL_SYNC, NULL);
|
||||
disk_ioctl(1, CTRL_SYNC, NULL);
|
||||
disk_ioctl(2, CTRL_SYNC, NULL);
|
||||
|
||||
#if MICROPY_ENABLE_GC
|
||||
gc_init(heap, heap + sizeof(heap));
|
||||
#endif
|
||||
|
@ -184,9 +192,6 @@ int main(int argc, char **argv) {
|
|||
samd21_init();
|
||||
#endif
|
||||
|
||||
// Initialise the local flash filesystem.
|
||||
// Create it if needed, mount in on /flash, and set it as current dir.
|
||||
init_flash_fs();
|
||||
|
||||
int stack_dummy;
|
||||
// Store the location of stack_dummy as an approximation for the top of the
|
||||
|
@ -195,6 +200,16 @@ int main(int argc, char **argv) {
|
|||
stack_top = (char*)&stack_dummy;
|
||||
reset_mp();
|
||||
|
||||
// Initialise the local flash filesystem after the gc in case we need to
|
||||
// grab memory from it. Create it if needed, mount in on /flash, and set it
|
||||
// as current dir.
|
||||
init_flash_fs();
|
||||
|
||||
// Start USB after getting everything going.
|
||||
#ifdef USB_REPL
|
||||
udc_start();
|
||||
#endif
|
||||
|
||||
// Main script is finished, so now go into REPL mode.
|
||||
// The REPL mode can change, or it can request a soft reset.
|
||||
int exit_code = 0;
|
||||
|
@ -326,10 +341,6 @@ void samd21_init(void) {
|
|||
// pin_conf.direction = PORT_PIN_DIR_OUTPUT;
|
||||
// port_pin_set_config(MICROPY_HW_LED1, &pin_conf);
|
||||
// port_pin_set_output_level(MICROPY_HW_LED1, false);
|
||||
|
||||
#ifdef USB_REPL
|
||||
udc_start();
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -37,7 +37,6 @@
|
|||
#include "modmachine_dac.h"
|
||||
#include "modmachine_pin.h"
|
||||
#include "modmachine_pwm.h"
|
||||
#include "storage.h"
|
||||
|
||||
#if MICROPY_PY_MACHINE
|
||||
// TODO(tannewt): Add the machine_ prefix to all types so that we don't risk
|
||||
|
@ -49,7 +48,6 @@ STATIC const mp_rom_map_elem_t machine_module_globals_table[] = {
|
|||
{ MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&machine_i2c_type) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_Pin), MP_ROM_PTR(&pin_type) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_PWM), MP_ROM_PTR(&pwm_type) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_Flash), MP_ROM_PTR(&flash_type) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_SPI), MP_ROM_PTR(&machine_spi_type) },
|
||||
};
|
||||
|
||||
|
|
|
@ -70,7 +70,11 @@
|
|||
#define MICROPY_FATFS_VOLUMES (4)
|
||||
#define MICROPY_FATFS_MULTI_PARTITION (1)
|
||||
#define MICROPY_FSUSERMOUNT (1)
|
||||
#define MICROPY_FATFS_MAX_SS (4096)
|
||||
// Only enable this if you really need it. It allocates a byte cache of this
|
||||
// size.
|
||||
// #define MICROPY_FATFS_MAX_SS (4096)
|
||||
|
||||
#define FLASH_BLOCK_SIZE (512)
|
||||
|
||||
#define MICROPY_VFS_FAT (1)
|
||||
#define MICROPY_PY_MACHINE (1)
|
||||
|
|
|
@ -0,0 +1,641 @@
|
|||
/*
|
||||
* This file is part of the Micro Python 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.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "asf/sam0/drivers/sercom/spi/spi.h"
|
||||
|
||||
#include "py/gc.h"
|
||||
#include "py/obj.h"
|
||||
#include "py/runtime.h"
|
||||
#include "lib/fatfs/ff.h"
|
||||
#include "extmod/fsusermount.h"
|
||||
|
||||
#include "asf/sam0/drivers/nvm/nvm.h"
|
||||
|
||||
#include "spi_flash.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_ENABLE_WRITE 0x06
|
||||
#define CMD_PAGE_PROGRAM 0x02
|
||||
// #define CMD_PAGE_PROGRAM CMD_READ_JEDEC_ID
|
||||
#define CMD_READ_STATUS 0x05
|
||||
|
||||
static bool spi_flash_is_initialised = false;
|
||||
|
||||
struct spi_module spi_flash_instance;
|
||||
|
||||
// The total size of the flash.
|
||||
static uint32_t flash_size;
|
||||
|
||||
// The erase sector size.
|
||||
static uint32_t sector_size;
|
||||
|
||||
// The page size. Its the maximum number of bytes that can be written at once.
|
||||
static uint32_t page_size;
|
||||
|
||||
// The currently cached sector in the 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;
|
||||
|
||||
// We use this when we can allocate the whole cache in RAM.
|
||||
static uint8_t** ram_cache;
|
||||
|
||||
// Address of the scratch flash sector.
|
||||
#define SCRATCH_SECTOR (flash_size - sector_size)
|
||||
|
||||
// Enable the flash over SPI.
|
||||
static void flash_enable() {
|
||||
port_pin_set_output_level(SPI_FLASH_CS, false);
|
||||
}
|
||||
|
||||
// Disable the flash over SPI.
|
||||
static void flash_disable() {
|
||||
port_pin_set_output_level(SPI_FLASH_CS, true);
|
||||
}
|
||||
|
||||
// Wait until both the write enable and write in progress bits have cleared.
|
||||
static bool wait_for_flash_ready() {
|
||||
uint8_t status_request[2] = {CMD_READ_STATUS, 0x00};
|
||||
uint8_t response[2] = {0x00, 0x01};
|
||||
enum status_code status = STATUS_OK;
|
||||
// Both the write enable and write in progress bits should be low.
|
||||
while (status == STATUS_OK && ((response[1] & 0x1) == 1 || (response[1] & 0x2) == 2)) {
|
||||
flash_enable();
|
||||
status = spi_transceive_buffer_wait(&spi_flash_instance, status_request, response, 2);
|
||||
flash_disable();
|
||||
}
|
||||
return status == STATUS_OK;
|
||||
}
|
||||
|
||||
// Turn on the write enable bit so we can program and erase the flash.
|
||||
static bool write_enable() {
|
||||
flash_enable();
|
||||
uint8_t command = CMD_ENABLE_WRITE;
|
||||
enum status_code status = spi_write_buffer_wait(&spi_flash_instance, &command, 1);
|
||||
flash_disable();
|
||||
return status == STATUS_OK;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
wait_for_flash_ready();
|
||||
enum status_code status;
|
||||
// We can read as much as we want sequentially.
|
||||
uint8_t read_request[4] = {CMD_READ_DATA, 0x00, 0x00, 0x00};
|
||||
address_to_bytes(address, read_request + 1);
|
||||
flash_enable();
|
||||
status = spi_write_buffer_wait(&spi_flash_instance, read_request, 4);
|
||||
if (status == STATUS_OK) {
|
||||
status = spi_read_buffer_wait(&spi_flash_instance, data, data_length, 0x00);
|
||||
}
|
||||
flash_disable();
|
||||
return status == STATUS_OK;
|
||||
}
|
||||
|
||||
// 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 (page_size == 0) {
|
||||
return false;
|
||||
}
|
||||
for (uint32_t bytes_written = 0;
|
||||
bytes_written < data_length;
|
||||
bytes_written += page_size) {
|
||||
if (!wait_for_flash_ready() || !write_enable()) {
|
||||
return false;
|
||||
}
|
||||
flash_enable();
|
||||
uint8_t command[4] = {CMD_PAGE_PROGRAM, 0x00, 0x00, 0x00};
|
||||
address_to_bytes(address + bytes_written, command + 1);
|
||||
enum status_code status;
|
||||
status = spi_write_buffer_wait(&spi_flash_instance, command, 4);
|
||||
if (status == STATUS_OK) {
|
||||
status = spi_write_buffer_wait(&spi_flash_instance, data + bytes_written, page_size);
|
||||
}
|
||||
flash_disable();
|
||||
if (status != STATUS_OK) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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_enable();
|
||||
enum status_code status = spi_write_buffer_wait(&spi_flash_instance, erase_request, 4);
|
||||
flash_disable();
|
||||
return status == STATUS_OK;
|
||||
}
|
||||
|
||||
// Sector is really 24 bits.
|
||||
static bool copy_block(uint32_t src_address, uint32_t dest_address) {
|
||||
// Copy page by page to minimize RAM buffer.
|
||||
uint8_t buffer[page_size];
|
||||
for (int i = 0; i < FLASH_BLOCK_SIZE / page_size; i++) {
|
||||
if (!read_flash(src_address + i * page_size, buffer, page_size)) {
|
||||
return false;
|
||||
}
|
||||
if (!write_flash(dest_address + i * page_size, buffer, page_size)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void spi_flash_init(void) {
|
||||
if (!spi_flash_is_initialised) {
|
||||
struct spi_config config_spi_master;
|
||||
spi_get_config_defaults(&config_spi_master);
|
||||
config_spi_master.mux_setting = SPI_FLASH_MUX_SETTING;
|
||||
config_spi_master.pinmux_pad0 = SPI_FLASH_PAD0_PINMUX;
|
||||
config_spi_master.pinmux_pad1 = SPI_FLASH_PAD1_PINMUX;
|
||||
config_spi_master.pinmux_pad2 = SPI_FLASH_PAD2_PINMUX;
|
||||
config_spi_master.pinmux_pad3 = SPI_FLASH_PAD3_PINMUX;
|
||||
config_spi_master.mode_specific.master.baudrate = SPI_FLASH_BAUDRATE;
|
||||
spi_init(&spi_flash_instance, SPI_FLASH_SERCOM, &config_spi_master);
|
||||
spi_enable(&spi_flash_instance);
|
||||
|
||||
// Manage chip select ourselves.
|
||||
struct port_config pin_conf;
|
||||
port_get_config_defaults(&pin_conf);
|
||||
|
||||
pin_conf.direction = PORT_PIN_DIR_OUTPUT;
|
||||
port_pin_set_config(SPI_FLASH_CS, &pin_conf);
|
||||
flash_disable();
|
||||
|
||||
// Activity LED for flash writes.
|
||||
#ifdef MICROPY_HW_LED_MSC
|
||||
port_pin_set_config(MICROPY_HW_LED_MSC, &pin_conf);
|
||||
port_pin_set_output_level(MICROPY_HW_LED_MSC, false);
|
||||
#endif
|
||||
|
||||
uint8_t jedec_id_request[4] = {CMD_READ_JEDEC_ID, 0x00, 0x00, 0x00};
|
||||
uint8_t response[4] = {0x00, 0x00, 0x00, 0x00};
|
||||
flash_enable();
|
||||
volatile enum status_code status = spi_transceive_buffer_wait(&spi_flash_instance, jedec_id_request, response, 4);
|
||||
flash_disable();
|
||||
(void) status;
|
||||
if (response[1] == 0x01 && response[2] == 0x40 && response[3] == 0x15) {
|
||||
flash_size = 1 << 21; // 2 MiB
|
||||
sector_size = 1 << 12; // 4 KiB
|
||||
page_size = 256; // 256 bytes
|
||||
} else {
|
||||
// Unknown flash chip!
|
||||
flash_size = 0;
|
||||
}
|
||||
|
||||
current_sector = NO_SECTOR_LOADED;
|
||||
dirty_mask = 0;
|
||||
ram_cache = NULL;
|
||||
|
||||
spi_flash_is_initialised = true;
|
||||
}
|
||||
}
|
||||
|
||||
// The size of each individual block.
|
||||
uint32_t spi_flash_get_block_size(void) {
|
||||
return FLASH_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 + (flash_size - sector_size) / FLASH_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() {
|
||||
// First, copy out any blocks that we haven't touched from the sector we've
|
||||
// cached.
|
||||
bool copy_to_scratch_ok = true;
|
||||
for (int i = 0; i < sector_size / FLASH_BLOCK_SIZE; i++) {
|
||||
if ((dirty_mask & (1 << i)) == 0) {
|
||||
copy_to_scratch_ok = copy_to_scratch_ok &&
|
||||
copy_block(current_sector + i * FLASH_BLOCK_SIZE,
|
||||
SCRATCH_SECTOR + i * FLASH_BLOCK_SIZE);
|
||||
}
|
||||
}
|
||||
if (!copy_to_scratch_ok) {
|
||||
// TODO(tannewt): Do more here. We opted to not erase and copy bad data
|
||||
// in. We still risk losing the data written to the scratch sector.
|
||||
return false;
|
||||
}
|
||||
// Second, erase the current sector.
|
||||
erase_sector(current_sector);
|
||||
// Finally, copy the new version into it.
|
||||
for (int i = 0; i < sector_size / FLASH_BLOCK_SIZE; i++) {
|
||||
copy_block(SCRATCH_SECTOR + i * FLASH_BLOCK_SIZE,
|
||||
current_sector + i * FLASH_BLOCK_SIZE);
|
||||
}
|
||||
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() {
|
||||
uint8_t blocks_per_sector = sector_size / FLASH_BLOCK_SIZE;
|
||||
uint8_t pages_per_block = FLASH_BLOCK_SIZE / page_size;
|
||||
ram_cache = gc_alloc(blocks_per_sector * pages_per_block * sizeof(uint32_t), false);
|
||||
if (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.
|
||||
int i = 0;
|
||||
int j = 0;
|
||||
bool success = true;
|
||||
for (i = 0; i < sector_size / FLASH_BLOCK_SIZE; i++) {
|
||||
for (int j = 0; j < pages_per_block; j++) {
|
||||
uint8_t *page_cache = gc_alloc(page_size, false);
|
||||
if (page_cache == NULL) {
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
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) {
|
||||
for (; i >= 0; i--) {
|
||||
for (; j >= 0; j--) {
|
||||
gc_free(ram_cache[i * pages_per_block + j]);
|
||||
}
|
||||
j = pages_per_block - 1;
|
||||
}
|
||||
gc_free(ram_cache);
|
||||
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 = FLASH_BLOCK_SIZE / page_size;
|
||||
for (int i = 0; i < sector_size / FLASH_BLOCK_SIZE; i++) {
|
||||
if ((dirty_mask & (1 << i)) == 0) {
|
||||
for (int j = 0; j < pages_per_block; j++) {
|
||||
copy_to_ram_ok = read_flash(
|
||||
current_sector + (i * pages_per_block + j) * page_size,
|
||||
ram_cache[i * pages_per_block + j],
|
||||
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 (int i = 0; i < sector_size / FLASH_BLOCK_SIZE; i++) {
|
||||
for (int j = 0; j < pages_per_block; j++) {
|
||||
write_flash(current_sector + (i * pages_per_block + j) * page_size,
|
||||
ram_cache[i * pages_per_block + j],
|
||||
page_size);
|
||||
if (!keep_cache) {
|
||||
gc_free(ram_cache[i * pages_per_block + j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
// We're done with the cache for now so give it back.
|
||||
if (!keep_cache) {
|
||||
gc_free(ram_cache);
|
||||
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
|
||||
// If we've cached to the flash itself flush from there.
|
||||
if (ram_cache == NULL) {
|
||||
flush_scratch_flash();
|
||||
} else {
|
||||
flush_ram_cache(keep_cache);
|
||||
}
|
||||
current_sector = NO_SECTOR_LOADED;
|
||||
#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);
|
||||
}
|
||||
|
||||
// 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 uint32_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 * FLASH_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, FLASH_BLOCK_SIZE);
|
||||
return true;
|
||||
} else {
|
||||
// Non-MBR block, get data from flash memory.
|
||||
uint32_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 & (~(sector_size - 1));
|
||||
uint8_t block_index = (address / FLASH_BLOCK_SIZE) % (sector_size / FLASH_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 (ram_cache != NULL) {
|
||||
uint8_t pages_per_block = FLASH_BLOCK_SIZE / page_size;
|
||||
for (int i = 0; i < pages_per_block; i++) {
|
||||
memcpy(dest + i * page_size,
|
||||
ram_cache[block_index * pages_per_block + i],
|
||||
page_size);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
uint32_t scratch_address = SCRATCH_SECTOR + block_index * FLASH_BLOCK_SIZE;
|
||||
return read_flash(scratch_address, dest, FLASH_BLOCK_SIZE);
|
||||
}
|
||||
}
|
||||
return read_flash(address, dest, FLASH_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
|
||||
uint32_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 & (~(sector_size - 1));
|
||||
uint8_t block_index = (address / FLASH_BLOCK_SIZE) % (sector_size / FLASH_BLOCK_SIZE);
|
||||
uint8_t mask = 1 << (block_index);
|
||||
// Flush the cache if we're moving onto a sector our we're writing the
|
||||
// same block again.
|
||||
if (current_sector != this_sector || (mask & dirty_mask) > 0) {
|
||||
if (current_sector != NO_SECTOR_LOADED) {
|
||||
spi_flash_flush_keep_cache(true);
|
||||
}
|
||||
if (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 (ram_cache != NULL) {
|
||||
uint8_t pages_per_block = FLASH_BLOCK_SIZE / page_size;
|
||||
for (int i = 0; i < pages_per_block; i++) {
|
||||
memcpy(ram_cache[block_index * pages_per_block + i],
|
||||
data + i * page_size,
|
||||
page_size);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
uint32_t scratch_address = SCRATCH_SECTOR + block_index * FLASH_BLOCK_SIZE;
|
||||
return write_flash(scratch_address, data, FLASH_BLOCK_SIZE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mp_uint_t spi_flash_read_blocks(uint8_t *dest, uint32_t block_num, uint32_t num_blocks) {
|
||||
for (size_t i = 0; i < num_blocks; i++) {
|
||||
if (!spi_flash_read_block(dest + i * FLASH_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 * FLASH_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 / FLASH_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 / FLASH_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_map_elem_t spi_flash_obj_locals_dict_table[] = {
|
||||
{ MP_OBJ_NEW_QSTR(MP_QSTR_readblocks), (mp_obj_t)&spi_flash_obj_readblocks_obj },
|
||||
{ MP_OBJ_NEW_QSTR(MP_QSTR_writeblocks), (mp_obj_t)&spi_flash_obj_writeblocks_obj },
|
||||
{ MP_OBJ_NEW_QSTR(MP_QSTR_ioctl), (mp_obj_t)&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->flags |= FSUSER_NATIVE | FSUSER_HAVE_IOCTL | FSUSER_USB_WRITEABLE;
|
||||
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;
|
||||
}
|
|
@ -23,31 +23,32 @@
|
|||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
#ifndef __MICROPY_INCLUDED_ATMEL_SAMD_STORAGE_H__
|
||||
#define __MICROPY_INCLUDED_ATMEL_SAMD_STORAGE_H__
|
||||
#ifndef __MICROPY_INCLUDED_ATMEL_SAMD_SPI_FLASH_H__
|
||||
#define __MICROPY_INCLUDED_ATMEL_SAMD_SPI_FLASH_H__
|
||||
|
||||
#include "mpconfigport.h"
|
||||
|
||||
#define FLASH_BLOCK_SIZE (512)
|
||||
// Erase sector size.
|
||||
#define SPI_FLASH_SECTOR_SIZE (0x1000 - 100)
|
||||
|
||||
#define STORAGE_SYSTICK_MASK (0x1ff) // 512ms
|
||||
#define STORAGE_IDLE_TICK(tick) (((tick) & STORAGE_SYSTICK_MASK) == 2)
|
||||
#define SPI_FLASH_SYSTICK_MASK (0x1ff) // 512ms
|
||||
#define SPI_FLASH_IDLE_TICK(tick) (((tick) & SPI_FLASH_SYSTICK_MASK) == 2)
|
||||
|
||||
void storage_init(void);
|
||||
uint32_t storage_get_block_size(void);
|
||||
uint32_t storage_get_block_count(void);
|
||||
void storage_irq_handler(void);
|
||||
void storage_flush(void);
|
||||
bool storage_read_block(uint8_t *dest, uint32_t block);
|
||||
bool storage_write_block(const uint8_t *src, uint32_t block);
|
||||
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);
|
||||
|
||||
// these return 0 on success, non-zero on error
|
||||
mp_uint_t storage_read_blocks(uint8_t *dest, uint32_t block_num, uint32_t num_blocks);
|
||||
mp_uint_t storage_write_blocks(const uint8_t *src, uint32_t block_num, uint32_t num_blocks);
|
||||
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);
|
||||
|
||||
extern const struct _mp_obj_type_t flash_type;
|
||||
extern const struct _mp_obj_type_t spi_flash_type;
|
||||
|
||||
struct _fs_user_mount_t;
|
||||
void flash_init_vfs(struct _fs_user_mount_t *vfs);
|
||||
|
||||
#endif // __MICROPY_INCLUDED_ATMEL_SAMD_STORAGE_H__
|
||||
#endif // __MICROPY_INCLUDED_ATMEL_SAMD_SPI_FLASH_H__
|
|
@ -24,10 +24,15 @@
|
|||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "lib/fatfs/ff.h"
|
||||
#include "py/obj.h"
|
||||
|
||||
// these are the values for fs_user_mount_t.flags
|
||||
#define FSUSER_NATIVE (0x0001) // readblocks[2]/writeblocks[2] contain native func
|
||||
#define FSUSER_FREE_OBJ (0x0002) // fs_user_mount_t obj should be freed on umount
|
||||
#define FSUSER_HAVE_IOCTL (0x0004) // new protocol with ioctl
|
||||
#define FSUSER_NATIVE (0x0001) // readblocks[2]/writeblocks[2] contain native func
|
||||
#define FSUSER_FREE_OBJ (0x0002) // fs_user_mount_t obj should be freed on umount
|
||||
#define FSUSER_HAVE_IOCTL (0x0004) // new protocol with ioctl
|
||||
// Device is write-able over USB and read-only to MicroPython.
|
||||
#define FSUSER_USB_WRITEABLE (0x0008)
|
||||
|
||||
// constants for block protocol ioctl
|
||||
#define BP_IOCTL_INIT (1)
|
||||
|
|
|
@ -278,7 +278,11 @@ STATIC mp_obj_t fat_vfs_statvfs(mp_obj_t vfs_in, mp_obj_t path_in) {
|
|||
|
||||
mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(10, NULL));
|
||||
|
||||
t->items[0] = MP_OBJ_NEW_SMALL_INT(fatfs->csize * fatfs->ssize); // f_bsize
|
||||
#if _MIN_SS != _MAX_SS
|
||||
t->items[0] = MP_OBJ_NEW_SMALL_INT(fatfs->csize * fatfs->ssize); // f_bsize
|
||||
#else
|
||||
t->items[0] = MP_OBJ_NEW_SMALL_INT(fatfs->csize * _MIN_SS); // f_bsize
|
||||
#endif
|
||||
t->items[1] = t->items[0]; // f_frsize
|
||||
t->items[2] = MP_OBJ_NEW_SMALL_INT((fatfs->n_fatent - 2) * fatfs->csize); // f_blocks
|
||||
t->items[3] = MP_OBJ_NEW_SMALL_INT(nclst); // f_bfree
|
||||
|
|
|
@ -90,7 +90,7 @@ DSTATUS disk_initialize (
|
|||
/*-----------------------------------------------------------------------*/
|
||||
|
||||
DSTATUS disk_status (
|
||||
BYTE pdrv /* Physical drive nmuber (0..) */
|
||||
BYTE pdrv /* Physical drive number (0..) */
|
||||
)
|
||||
{
|
||||
fs_user_mount_t *vfs = disk_get_device(pdrv);
|
||||
|
@ -98,7 +98,10 @@ DSTATUS disk_status (
|
|||
return STA_NOINIT;
|
||||
}
|
||||
|
||||
if (vfs->writeblocks[0] == MP_OBJ_NULL) {
|
||||
// This is used to determine the writeability of the disk from MicroPython.
|
||||
// So, if its USB writeable we make it read-only from MicroPython.
|
||||
if (vfs->writeblocks[0] == MP_OBJ_NULL ||
|
||||
(vfs->flags & FSUSER_USB_WRITEABLE) != 0) {
|
||||
return STA_PROTECT;
|
||||
} else {
|
||||
return 0;
|
||||
|
@ -110,7 +113,7 @@ DSTATUS disk_status (
|
|||
/*-----------------------------------------------------------------------*/
|
||||
|
||||
DRESULT disk_read (
|
||||
BYTE pdrv, /* Physical drive nmuber (0..) */
|
||||
BYTE pdrv, /* Physical drive number (0..) */
|
||||
BYTE *buff, /* Data buffer to store read data */
|
||||
DWORD sector, /* Sector address (LBA) */
|
||||
UINT count /* Number of sectors to read (1..128) */
|
||||
|
|
Loading…
Reference in New Issue