351 lines
10 KiB
C
351 lines
10 KiB
C
|
/*
|
||
|
* This file is part of the MicroPython project, http://micropython.org/
|
||
|
*
|
||
|
* The MIT License (MIT)
|
||
|
*
|
||
|
* Copyright (c) 2013, 2014 Damien P. George
|
||
|
* Copyright (c) 2019 Lucian Copeland 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 "supervisor/internal_flash.h"
|
||
|
|
||
|
#include <stdint.h>
|
||
|
#include <string.h>
|
||
|
#include <stdbool.h>
|
||
|
|
||
|
#include "extmod/vfs.h"
|
||
|
#include "extmod/vfs_fat.h"
|
||
|
#include "py/mphal.h"
|
||
|
#include "py/obj.h"
|
||
|
#include "py/runtime.h"
|
||
|
#include "lib/oofatfs/ff.h"
|
||
|
|
||
|
#include "supervisor/usb.h"
|
||
|
|
||
|
#include "csr.h"
|
||
|
#include "irq.h"
|
||
|
|
||
|
enum pin {
|
||
|
PIN_MOSI = 0,
|
||
|
PIN_CLK = 1,
|
||
|
PIN_CS = 2,
|
||
|
PIN_MISO_EN = 3,
|
||
|
PIN_MISO = 4, // Value is ignored
|
||
|
};
|
||
|
|
||
|
#define NO_CACHE 0xffffffff
|
||
|
|
||
|
static uint8_t _flash_cache[FLASH_PAGE_SIZE] __attribute__((aligned(4)));
|
||
|
static uint32_t _flash_page_addr = NO_CACHE;
|
||
|
static bool _flash_cache_dirty;
|
||
|
// -------------------------------------------------------------------------
|
||
|
// When performing SPI operations, the flash cannot be accessed. Since we
|
||
|
// normally execute directly from SPI, this can cause problems.
|
||
|
// To work around this, we execute from RAM. This is accomplished by marking
|
||
|
// functions as being in the section ".ramtext".
|
||
|
// When building under GCC with -O0 or -Od, the `inline` attribute is ignored.
|
||
|
// Therefore, we must re-implement these functions here and explicitly mark
|
||
|
// them as being in `.ramtext`, even though they really ought to be inlined.
|
||
|
__attribute__((section(".ramtext")))
|
||
|
static inline void spi_writel(uint32_t value, uint32_t addr)
|
||
|
{
|
||
|
*((volatile uint32_t *)addr) = value;
|
||
|
}
|
||
|
|
||
|
__attribute__((section(".ramtext")))
|
||
|
static inline uint32_t spi_readl(uint32_t addr)
|
||
|
{
|
||
|
return *(volatile uint32_t *)addr;
|
||
|
}
|
||
|
|
||
|
__attribute__((section(".ramtext")))
|
||
|
static inline void bb_spi_write(unsigned char value) {
|
||
|
spi_writel(value, CSR_LXSPI_BITBANG_ADDR);
|
||
|
}
|
||
|
|
||
|
__attribute__((section(".ramtext")))
|
||
|
static inline uint32_t bb_read(void) {
|
||
|
return spi_readl(CSR_LXSPI_MISO_ADDR);
|
||
|
}
|
||
|
|
||
|
__attribute__((section(".ramtext")))
|
||
|
static inline void bb_spi_en(unsigned int en) {
|
||
|
spi_writel(en, CSR_LXSPI_BITBANG_EN_ADDR);
|
||
|
}
|
||
|
|
||
|
__attribute__((section(".ramtext")))
|
||
|
static inline void bb_spi_irq_setie(unsigned int ie)
|
||
|
{
|
||
|
if(ie) csrs(mstatus,CSR_MSTATUS_MIE); else csrc(mstatus,CSR_MSTATUS_MIE);
|
||
|
}
|
||
|
|
||
|
__attribute__((section(".ramtext")))
|
||
|
static inline void bb_spi_begin(void) {
|
||
|
bb_spi_write((0 << PIN_CLK) | (0 << PIN_CS));
|
||
|
}
|
||
|
|
||
|
__attribute__((section(".ramtext")))
|
||
|
static inline void bb_spi_end(void) {
|
||
|
bb_spi_write((0 << PIN_CLK) | (1 << PIN_CS));
|
||
|
}
|
||
|
|
||
|
__attribute__((section(".ramtext")))
|
||
|
static void spi_single_tx(uint8_t out) {
|
||
|
int bit;
|
||
|
|
||
|
for (bit = 7; bit >= 0; bit--) {
|
||
|
if (out & (1 << bit)) {
|
||
|
bb_spi_write((0 << PIN_CLK) | (1 << PIN_MOSI));
|
||
|
bb_spi_write((1 << PIN_CLK) | (1 << PIN_MOSI));
|
||
|
bb_spi_write((0 << PIN_CLK) | (1 << PIN_MOSI));
|
||
|
} else {
|
||
|
bb_spi_write((0 << PIN_CLK) | (0 << PIN_MOSI));
|
||
|
bb_spi_write((1 << PIN_CLK) | (0 << PIN_MOSI));
|
||
|
bb_spi_write((0 << PIN_CLK) | (0 << PIN_MOSI));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
__attribute__((section(".ramtext")))
|
||
|
static uint8_t spi_single_rx(void) {
|
||
|
int bit = 0;
|
||
|
uint8_t in = 0;
|
||
|
|
||
|
bb_spi_write((1 << PIN_MISO_EN) | (0 << PIN_CLK));
|
||
|
|
||
|
while (bit++ < 8) {
|
||
|
bb_spi_write((1 << PIN_MISO_EN) | (1 << PIN_CLK));
|
||
|
in = (in << 1) | bb_read();
|
||
|
bb_spi_write((1 << PIN_MISO_EN) | (0 << PIN_CLK));
|
||
|
}
|
||
|
|
||
|
return in;
|
||
|
}
|
||
|
|
||
|
__attribute__((section(".ramtext")))
|
||
|
static int bb_spi_beginErase4(uint32_t erase_addr) {
|
||
|
// Enable Write-Enable Latch (WEL)
|
||
|
bb_spi_begin();
|
||
|
spi_single_tx(0x06);
|
||
|
bb_spi_end();
|
||
|
|
||
|
bb_spi_begin();
|
||
|
spi_single_tx(0x20);
|
||
|
spi_single_tx(erase_addr >> 16);
|
||
|
spi_single_tx(erase_addr >> 8);
|
||
|
spi_single_tx(erase_addr >> 0);
|
||
|
bb_spi_end();
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
__attribute__((section(".ramtext")))
|
||
|
static int bb_spi_beginWrite(uint32_t addr, const void *v_data, unsigned int count) {
|
||
|
const uint8_t write_cmd = 0x02;
|
||
|
const uint8_t *data = v_data;
|
||
|
unsigned int i;
|
||
|
|
||
|
#ifdef NDEBUG
|
||
|
if (v_data < (const void *)_flash_cache) {
|
||
|
asm("ebreak");
|
||
|
}
|
||
|
if ((v_data+count) > (const void *)&_flash_cache[4096]) {
|
||
|
asm("ebreak");
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
// Enable Write-Enable Latch (WEL)
|
||
|
bb_spi_begin();
|
||
|
spi_single_tx(0x06);
|
||
|
bb_spi_end();
|
||
|
|
||
|
bb_spi_begin();
|
||
|
spi_single_tx(write_cmd);
|
||
|
spi_single_tx(addr >> 16);
|
||
|
spi_single_tx(addr >> 8);
|
||
|
spi_single_tx(addr >> 0);
|
||
|
for (i = 0; (i < count) && (i < 256); i++)
|
||
|
spi_single_tx(*data++);
|
||
|
bb_spi_end();
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
__attribute__((section(".ramtext")))
|
||
|
static uint8_t spi_read_status(void) {
|
||
|
uint8_t val;
|
||
|
|
||
|
bb_spi_begin();
|
||
|
spi_single_tx(0x05);
|
||
|
val = spi_single_rx();
|
||
|
bb_spi_end();
|
||
|
return val;
|
||
|
}
|
||
|
|
||
|
__attribute__((section(".ramtext")))
|
||
|
static int bb_spi_is_busy(void) {
|
||
|
return spi_read_status() & (1 << 0);
|
||
|
}
|
||
|
|
||
|
__attribute__((used))
|
||
|
uint32_t page_write_log[128];
|
||
|
__attribute__((used))
|
||
|
uint32_t page_write_log_offset;
|
||
|
|
||
|
__attribute__((section(".ramtext")))
|
||
|
static void bb_spi_write_page(uint32_t flash_address, const uint8_t *data) {
|
||
|
const uint32_t flash_address_end = flash_address + FLASH_PAGE_SIZE;
|
||
|
|
||
|
// Ensure we're within the target flash address range.
|
||
|
if ((flash_address - FLASH_PARTITION_OFFSET_BYTES) > FLASH_SIZE) {
|
||
|
asm("ebreak");
|
||
|
return;
|
||
|
}
|
||
|
if (flash_address < FLASH_PARTITION_OFFSET_BYTES) {
|
||
|
asm("ebreak");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ((flash_address_end - FLASH_PARTITION_OFFSET_BYTES) > FLASH_SIZE) {
|
||
|
asm("ebreak");
|
||
|
return;
|
||
|
}
|
||
|
if (flash_address_end < FLASH_PARTITION_OFFSET_BYTES) {
|
||
|
asm("ebreak");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Ensure we're not erasing the middle of a flash bank
|
||
|
if ((flash_address & 0xfff) != 0) {
|
||
|
asm("ebreak");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
page_write_log[page_write_log_offset++] = flash_address;
|
||
|
if (page_write_log_offset > sizeof(page_write_log)/sizeof(*page_write_log)) page_write_log_offset=0;
|
||
|
|
||
|
while (bb_spi_is_busy())
|
||
|
; // relax
|
||
|
bb_spi_beginErase4(flash_address);
|
||
|
while (bb_spi_is_busy())
|
||
|
; // relax
|
||
|
while (flash_address < flash_address_end) {
|
||
|
bb_spi_beginWrite(flash_address, data, 256);
|
||
|
while (bb_spi_is_busy())
|
||
|
; // relax
|
||
|
flash_address += 256;
|
||
|
data += 256;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static inline uint32_t lba2addr(uint32_t block) {
|
||
|
return (0x20000000 + FLASH_PARTITION_OFFSET_BYTES) + (block * FILESYSTEM_BLOCK_SIZE);
|
||
|
}
|
||
|
|
||
|
void supervisor_flash_init(void) {
|
||
|
}
|
||
|
|
||
|
uint32_t supervisor_flash_get_block_size(void) {
|
||
|
return FILESYSTEM_BLOCK_SIZE;
|
||
|
}
|
||
|
|
||
|
uint32_t supervisor_flash_get_block_count(void) {
|
||
|
return FLASH_SIZE/FILESYSTEM_BLOCK_SIZE;
|
||
|
}
|
||
|
|
||
|
__attribute__((section(".ramtext")))
|
||
|
void supervisor_flash_flush(void) {
|
||
|
// Skip if data is the same, or if there is no data in the cache
|
||
|
if (_flash_page_addr == NO_CACHE)
|
||
|
return;
|
||
|
if (!_flash_cache_dirty)
|
||
|
return;
|
||
|
|
||
|
// Disable interrupts and enable bit-bang mode on the SPI flash.
|
||
|
// This function is running from RAM -- otherwise enabling bitbang mode
|
||
|
// would crash the CPU as the program suddenly became an endless stream
|
||
|
// of `0xffffffff`.
|
||
|
bb_spi_irq_setie(0);
|
||
|
bb_spi_write((0 << PIN_CLK) | (1 << PIN_CS));
|
||
|
bb_spi_en(1);
|
||
|
|
||
|
bb_spi_write_page(_flash_page_addr & 0x00ffffff, (const uint8_t *)_flash_cache);
|
||
|
|
||
|
bb_spi_en(0);
|
||
|
bb_spi_irq_setie(1);
|
||
|
|
||
|
_flash_cache_dirty = false;
|
||
|
}
|
||
|
|
||
|
mp_uint_t supervisor_flash_read_blocks(uint8_t *dest, uint32_t block, uint32_t num_blocks) {
|
||
|
// Must write out anything in cache before trying to read.
|
||
|
supervisor_flash_flush();
|
||
|
|
||
|
uint32_t src = lba2addr(block);
|
||
|
memcpy(dest, (uint8_t*) src, FILESYSTEM_BLOCK_SIZE*num_blocks);
|
||
|
|
||
|
#if USB_AVAILABLE
|
||
|
usb_background();
|
||
|
#endif
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
mp_uint_t supervisor_flash_write_blocks(const uint8_t *src, uint32_t lba, uint32_t num_blocks) {
|
||
|
while (num_blocks) {
|
||
|
uint32_t const addr = lba2addr(lba);
|
||
|
uint32_t const page_addr = addr & ~(FLASH_PAGE_SIZE - 1);
|
||
|
|
||
|
uint32_t count = 8 - (lba % 8); // up to page boundary
|
||
|
count = MIN(num_blocks, count);
|
||
|
|
||
|
if (page_addr != _flash_page_addr) {
|
||
|
// Write out anything in cache before overwriting it.
|
||
|
supervisor_flash_flush();
|
||
|
|
||
|
_flash_page_addr = page_addr;
|
||
|
_flash_cache_dirty = false;
|
||
|
|
||
|
// Copy the current contents of the entire page into the cache.
|
||
|
memcpy(_flash_cache, (void *)page_addr, FLASH_PAGE_SIZE);
|
||
|
}
|
||
|
|
||
|
// Overwrite part or all of the page cache with the src data, but only if it's changed.
|
||
|
if (_flash_cache_dirty || memcmp(_flash_cache + (addr & (FLASH_PAGE_SIZE - 1)), src, count * FILESYSTEM_BLOCK_SIZE)) {
|
||
|
memcpy(_flash_cache + (addr & (FLASH_PAGE_SIZE - 1)), src, count * FILESYSTEM_BLOCK_SIZE);
|
||
|
_flash_cache_dirty = true;
|
||
|
}
|
||
|
|
||
|
// adjust for next run
|
||
|
lba += count;
|
||
|
src += count * FILESYSTEM_BLOCK_SIZE;
|
||
|
num_blocks -= count;
|
||
|
|
||
|
#if USB_AVAILABLE
|
||
|
usb_background();
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
return 0; // success
|
||
|
}
|
||
|
|
||
|
void supervisor_flash_release_cache(void) {
|
||
|
}
|
||
|
|