diff --git a/atmel-samd/asf_conf/conf_dma.h b/atmel-samd/asf_conf/conf_dma.h index 82e59025dc..afe790a644 100644 --- a/atmel-samd/asf_conf/conf_dma.h +++ b/atmel-samd/asf_conf/conf_dma.h @@ -46,6 +46,6 @@ #ifndef CONF_DMA_H_INCLUDED #define CONF_DMA_H_INCLUDED -# define CONF_MAX_USED_CHANNEL_NUM 2 +# define CONF_MAX_USED_CHANNEL_NUM 3 #endif diff --git a/atmel-samd/boards/circuitplayground_express/mpconfigboard.h b/atmel-samd/boards/circuitplayground_express/mpconfigboard.h index 375c28dddb..5eee39574c 100644 --- a/atmel-samd/boards/circuitplayground_express/mpconfigboard.h +++ b/atmel-samd/boards/circuitplayground_express/mpconfigboard.h @@ -3,9 +3,8 @@ #define MICROPY_HW_BOARD_NAME "Adafruit CircuitPlayground Express" #define MICROPY_HW_MCU_NAME "samd21g18" -//#define MICROPY_HW_LED_MSC PIN_PA17 - -#define SPI_FLASH_BAUDRATE (1000000) +// Salae reads 12mhz which is the limit even though we set it to the safer 8mhz. +#define SPI_FLASH_BAUDRATE (8000000) // On-board flash #define SPI_FLASH_MUX_SETTING SPI_SIGNAL_MUX_SETTING_E diff --git a/atmel-samd/boards/feather_m0_express/mpconfigboard.h b/atmel-samd/boards/feather_m0_express/mpconfigboard.h index 04b836b4ee..b18de9544e 100644 --- a/atmel-samd/boards/feather_m0_express/mpconfigboard.h +++ b/atmel-samd/boards/feather_m0_express/mpconfigboard.h @@ -5,7 +5,8 @@ #define MICROPY_HW_NEOPIXEL (&pin_PA06) -#define SPI_FLASH_BAUDRATE (1000000) +// Salae reads 12mhz which is the limit even though we set it to the safer 8mhz. +#define SPI_FLASH_BAUDRATE (8000000) #define SPI_FLASH_MUX_SETTING SPI_SIGNAL_MUX_SETTING_C #define SPI_FLASH_PAD0_PINMUX PINMUX_PA08D_SERCOM2_PAD0 // MOSI diff --git a/atmel-samd/boards/metro_m0_express/mpconfigboard.h b/atmel-samd/boards/metro_m0_express/mpconfigboard.h index c7a53bcdb8..ddee4ff70b 100644 --- a/atmel-samd/boards/metro_m0_express/mpconfigboard.h +++ b/atmel-samd/boards/metro_m0_express/mpconfigboard.h @@ -8,7 +8,8 @@ #define MICROPY_HW_NEOPIXEL (&pin_PA30) -#define SPI_FLASH_BAUDRATE (1000000) +// Salae reads 12mhz which is the limit even though we set it to the safer 8mhz. +#define SPI_FLASH_BAUDRATE (8000000) #define SPI_FLASH_MUX_SETTING SPI_SIGNAL_MUX_SETTING_F #define SPI_FLASH_PAD0_PINMUX PINMUX_DEFAULT // CS diff --git a/atmel-samd/flash_api.h b/atmel-samd/flash_api.h new file mode 100644 index 0000000000..2847023d6c --- /dev/null +++ b/atmel-samd/flash_api.h @@ -0,0 +1,32 @@ +/* + * This file is part of the Micro Python 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_FLASH_API_H__ +#define __MICROPY_INCLUDED_ATMEL_SAMD_FLASH_API_H__ + +extern void flash_init_vfs(fs_user_mount_t *vfs); +extern void flash_flush(void); + +#endif // __MICROPY_INCLUDED_ATMEL_SAMD_FLASH_API_H__ diff --git a/atmel-samd/internal_flash.c b/atmel-samd/internal_flash.c index 1ce38f0b0a..9046edc4cf 100644 --- a/atmel-samd/internal_flash.c +++ b/atmel-samd/internal_flash.c @@ -68,6 +68,10 @@ uint32_t internal_flash_get_block_count(void) { void internal_flash_flush(void) { } +void flash_flush(void) { + internal_flash_flush(); +} + static void build_partition(uint8_t *buf, int boot, int type, uint32_t start_block, uint32_t num_blocks) { buf[0] = boot; diff --git a/atmel-samd/main.c b/atmel-samd/main.c index bc29798981..9ed1c3cd1b 100644 --- a/atmel-samd/main.c +++ b/atmel-samd/main.c @@ -39,6 +39,7 @@ #endif #include "autoreset.h" +#include "flash_api.h" #include "mpconfigboard.h" #include "rgb_led_status.h" #include "shared_dma.h" @@ -66,8 +67,6 @@ void do_str(const char *src, mp_parse_input_kind_t input_kind) { } } -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(void) { @@ -93,6 +92,8 @@ void init_flash_fs(void) { vfs->flags &= ~FSUSER_USB_WRITEABLE; res = f_mkfs("/flash", 0, 0); + // Flush the new file system to make sure its repaired immediately. + flash_flush(); if (res != FR_OK) { MP_STATE_PORT(fs_user_mount)[0] = NULL; return; diff --git a/atmel-samd/shared_dma.c b/atmel-samd/shared_dma.c index b4bbdd07ac..d7063fb6ba 100644 --- a/atmel-samd/shared_dma.c +++ b/atmel-samd/shared_dma.c @@ -23,14 +23,16 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ - #include "shared_dma.h" +#include "asf/sam0/drivers/system/interrupt/system_interrupt.h" + // We allocate two DMA resources for the entire lifecycle of the board (not the // vm) because the general_dma resource will be shared between the REPL and SPI // flash. Both uses must block each other in order to prevent conflict. struct dma_resource audio_dma; -struct dma_resource general_dma; +struct dma_resource general_dma_tx; +struct dma_resource general_dma_rx; void init_shared_dma(void) { struct dma_resource_config config; @@ -45,10 +47,118 @@ void init_shared_dma(void) { // Turn on the transfer complete interrupt so that the job_status changes to done. g_chan_interrupt_flag[audio_dma.channel_id] |= (1UL << DMA_CALLBACK_TRANSFER_DONE); + // Prioritize the RX channel over the TX channel because TX can cause an RX + // overflow. dma_get_config_defaults(&config); - dma_allocate(&general_dma, &config); + config.trigger_action = DMA_TRIGGER_ACTION_BEAT; + config.event_config.input_action = DMA_EVENT_INPUT_TRIG; + dma_allocate(&general_dma_rx, &config); + g_chan_interrupt_flag[general_dma_rx.channel_id] |= (1UL << DMA_CALLBACK_TRANSFER_DONE); + + dma_get_config_defaults(&config); + config.trigger_action = DMA_TRIGGER_ACTION_BEAT; + config.event_config.input_action = DMA_EVENT_INPUT_TRIG; + dma_allocate(&general_dma_tx, &config); + g_chan_interrupt_flag[general_dma_tx.channel_id] |= (1UL << DMA_CALLBACK_TRANSFER_DONE); // Be sneaky and reuse the active descriptor memory. audio_dma.descriptor = &descriptor_section[audio_dma.channel_id]; - general_dma.descriptor = &descriptor_section[general_dma.channel_id]; + general_dma_rx.descriptor = &descriptor_section[general_dma_rx.channel_id]; + general_dma_tx.descriptor = &descriptor_section[general_dma_tx.channel_id]; +} + +static uint8_t sercom_index(Sercom* sercom) { + return ((uint32_t) sercom - (uint32_t) SERCOM0) / 0x400; +} + +static void dma_configure(uint8_t channel, uint8_t trigsrc) { + system_interrupt_enter_critical_section(); + /** Select the DMA channel and clear software trigger */ + DMAC->CHID.reg = DMAC_CHID_ID(channel); + DMAC->CHCTRLA.reg &= ~DMAC_CHCTRLA_ENABLE; + DMAC->CHCTRLA.reg = DMAC_CHCTRLA_SWRST; + DMAC->SWTRIGCTRL.reg &= (uint32_t)(~(1 << channel)); + DMAC->CHCTRLB.reg = DMAC_CHCTRLB_LVL(DMA_PRIORITY_LEVEL_0) | \ + DMAC_CHCTRLB_TRIGSRC(trigsrc) | \ + DMAC_CHCTRLB_TRIGACT(DMA_TRIGGER_ACTION_BEAT); + system_interrupt_leave_critical_section(); +} + +enum status_code shared_dma_write(Sercom* sercom, const uint8_t* buffer, uint32_t length) { + if (general_dma_tx.job_status != STATUS_OK) { + return general_dma_tx.job_status; + } + dma_configure(general_dma_tx.channel_id, sercom_index(sercom) * 2 + 2); + + // Set up TX second. + struct dma_descriptor_config descriptor_config; + dma_descriptor_get_config_defaults(&descriptor_config); + descriptor_config.beat_size = DMA_BEAT_SIZE_BYTE; + descriptor_config.dst_increment_enable = false; + descriptor_config.block_transfer_count = length; + descriptor_config.source_address = ((uint32_t)buffer + length); + // DATA register is consistently addressed across all SERCOM modes. + descriptor_config.destination_address = ((uint32_t)&sercom->SPI.DATA.reg); + + dma_descriptor_create(general_dma_tx.descriptor, &descriptor_config); + enum status_code status = dma_start_transfer_job(&general_dma_tx); + if (status != STATUS_OK) { + return status; + } + + // Wait for the transfer to finish. + while (general_dma_tx.job_status == STATUS_BUSY) {} + + // This transmit will cause the RX buffer overflow but we're OK with that. + // So, read the garbage data and clear the overflow flag. + sercom->SPI.DATA.reg; + sercom->SPI.DATA.reg; + sercom->SPI.STATUS.bit.BUFOVF = 1; + sercom->SPI.DATA.reg; + + return general_dma_tx.job_status; +} + +enum status_code shared_dma_read(Sercom* sercom, uint8_t* buffer, uint32_t length, uint8_t tx) { + if (general_dma_tx.job_status != STATUS_OK) { + return general_dma_tx.job_status; + } + + dma_configure(general_dma_tx.channel_id, sercom_index(sercom) * 2 + 2); + dma_configure(general_dma_rx.channel_id, sercom_index(sercom) * 2 + 1); + + // Set up RX first. + struct dma_descriptor_config descriptor_config; + dma_descriptor_get_config_defaults(&descriptor_config); + descriptor_config.beat_size = DMA_BEAT_SIZE_BYTE; + descriptor_config.src_increment_enable = false; + descriptor_config.block_transfer_count = length; + // DATA register is consistently addressed across all SERCOM modes. + descriptor_config.source_address = ((uint32_t)&sercom->SPI.DATA.reg); + descriptor_config.destination_address = ((uint32_t)buffer + length); + + dma_descriptor_create(general_dma_rx.descriptor, &descriptor_config); + + // Set up TX to retransmit the same byte over and over. + dma_descriptor_get_config_defaults(&descriptor_config); + descriptor_config.beat_size = DMA_BEAT_SIZE_BYTE; + descriptor_config.src_increment_enable = false; + descriptor_config.dst_increment_enable = false; + descriptor_config.block_transfer_count = length; + descriptor_config.source_address = ((uint32_t)&tx); + // DATA register is consistently addressed across all SERCOM modes. + descriptor_config.destination_address = ((uint32_t)&sercom->SPI.DATA.reg); + + dma_descriptor_create(general_dma_tx.descriptor, &descriptor_config); + + // Start the RX job first so we don't miss the first byte. The TX job clocks + // the output. + general_dma_rx.transfered_size = 0; + dma_start_transfer_job(&general_dma_rx); + general_dma_tx.transfered_size = 0; + dma_start_transfer_job(&general_dma_tx); + + // Wait for the transfer to finish. + while (general_dma_rx.job_status == STATUS_BUSY) {} + return general_dma_rx.job_status; } diff --git a/atmel-samd/shared_dma.h b/atmel-samd/shared_dma.h index 409b06bac8..190001b6b5 100644 --- a/atmel-samd/shared_dma.h +++ b/atmel-samd/shared_dma.h @@ -30,8 +30,12 @@ #include "asf/sam0/drivers/dma/dma.h" extern struct dma_resource audio_dma; -extern struct dma_resource general_dma; +extern struct dma_resource general_dma_tx; +extern struct dma_resource general_dma_rx; void init_shared_dma(void); +enum status_code shared_dma_write(Sercom* sercom, const uint8_t* buffer, uint32_t length); +enum status_code shared_dma_read(Sercom* sercom, uint8_t* buffer, uint32_t length, uint8_t tx); + #endif // __MICROPY_INCLUDED_ATMEL_SAMD_SHARED_DMA_H__ diff --git a/atmel-samd/spi_flash.c b/atmel-samd/spi_flash.c index 6ed8e0cf81..812e4359e2 100644 --- a/atmel-samd/spi_flash.c +++ b/atmel-samd/spi_flash.c @@ -3,7 +3,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2013, 2014 Damien P. George + * 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 @@ -37,6 +37,7 @@ #include "extmod/fsusermount.h" #include "rgb_led_status.h" +#include "shared_dma.h" #define SPI_FLASH_PART1_START_BLOCK (0x1) @@ -46,6 +47,7 @@ #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 @@ -124,7 +126,7 @@ static bool read_flash(uint32_t address, uint8_t* data, uint32_t data_length) { 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); + status = shared_dma_read(spi_flash_instance.hw, data, data_length, 0x00); } flash_disable(); return status == STATUS_OK; @@ -149,7 +151,7 @@ static bool write_flash(uint32_t address, const uint8_t* data, uint32_t data_len 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); + status = shared_dma_write(spi_flash_instance.hw, data + bytes_written, page_size); } flash_disable(); if (status != STATUS_OK) { @@ -222,9 +224,8 @@ void spi_flash_init(void) { 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); + 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 @@ -234,6 +235,15 @@ void spi_flash_init(void) { flash_size = 0; } + // Turn off writes in case this is a microcontroller only reset. + uint8_t disable_write_request[1] = {CMD_DISABLE_WRITE}; + uint8_t disable_response[1] = {0x00}; + flash_enable(); + spi_transceive_buffer_wait(&spi_flash_instance, disable_write_request, disable_response, 1); + flash_disable(); + + wait_for_flash_ready(); + current_sector = NO_SECTOR_LOADED; dirty_mask = 0; MP_STATE_VM(flash_ram_cache) = NULL; @@ -404,6 +414,10 @@ 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) {