atmel-samd: Use DMA for SPI flash block transfers.

Fixes #99
This commit is contained in:
Scott Shawcroft 2017-05-02 15:25:06 -07:00
parent a2c463deb0
commit 24a5752f94
10 changed files with 184 additions and 18 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

32
atmel-samd/flash_api.h Normal file
View File

@ -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__

View File

@ -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;

View File

@ -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;

View File

@ -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;
}

View File

@ -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__

View File

@ -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) {