Skip to content

Commit 24a5752

Browse files
committed
atmel-samd: Use DMA for SPI flash block transfers.
Fixes micropython#99
1 parent a2c463d commit 24a5752

File tree

10 files changed

+184
-18
lines changed

10 files changed

+184
-18
lines changed

atmel-samd/asf_conf/conf_dma.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,6 @@
4646
#ifndef CONF_DMA_H_INCLUDED
4747
#define CONF_DMA_H_INCLUDED
4848

49-
# define CONF_MAX_USED_CHANNEL_NUM 2
49+
# define CONF_MAX_USED_CHANNEL_NUM 3
5050

5151
#endif

atmel-samd/boards/circuitplayground_express/mpconfigboard.h

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,8 @@
33
#define MICROPY_HW_BOARD_NAME "Adafruit CircuitPlayground Express"
44
#define MICROPY_HW_MCU_NAME "samd21g18"
55

6-
//#define MICROPY_HW_LED_MSC PIN_PA17
7-
8-
#define SPI_FLASH_BAUDRATE (1000000)
6+
// Salae reads 12mhz which is the limit even though we set it to the safer 8mhz.
7+
#define SPI_FLASH_BAUDRATE (8000000)
98

109
// On-board flash
1110
#define SPI_FLASH_MUX_SETTING SPI_SIGNAL_MUX_SETTING_E

atmel-samd/boards/feather_m0_express/mpconfigboard.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55

66
#define MICROPY_HW_NEOPIXEL (&pin_PA06)
77

8-
#define SPI_FLASH_BAUDRATE (1000000)
8+
// Salae reads 12mhz which is the limit even though we set it to the safer 8mhz.
9+
#define SPI_FLASH_BAUDRATE (8000000)
910

1011
#define SPI_FLASH_MUX_SETTING SPI_SIGNAL_MUX_SETTING_C
1112
#define SPI_FLASH_PAD0_PINMUX PINMUX_PA08D_SERCOM2_PAD0 // MOSI

atmel-samd/boards/metro_m0_express/mpconfigboard.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88

99
#define MICROPY_HW_NEOPIXEL (&pin_PA30)
1010

11-
#define SPI_FLASH_BAUDRATE (1000000)
11+
// Salae reads 12mhz which is the limit even though we set it to the safer 8mhz.
12+
#define SPI_FLASH_BAUDRATE (8000000)
1213

1314
#define SPI_FLASH_MUX_SETTING SPI_SIGNAL_MUX_SETTING_F
1415
#define SPI_FLASH_PAD0_PINMUX PINMUX_DEFAULT // CS

atmel-samd/flash_api.h

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* This file is part of the Micro Python project, http://micropython.org/
3+
*
4+
* The MIT License (MIT)
5+
*
6+
* Copyright (c) 2017 Scott Shawcroft for Adafruit Industries
7+
*
8+
* Permission is hereby granted, free of charge, to any person obtaining a copy
9+
* of this software and associated documentation files (the "Software"), to deal
10+
* in the Software without restriction, including without limitation the rights
11+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
* copies of the Software, and to permit persons to whom the Software is
13+
* furnished to do so, subject to the following conditions:
14+
*
15+
* The above copyright notice and this permission notice shall be included in
16+
* all copies or substantial portions of the Software.
17+
*
18+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+
* THE SOFTWARE.
25+
*/
26+
#ifndef __MICROPY_INCLUDED_ATMEL_SAMD_FLASH_API_H__
27+
#define __MICROPY_INCLUDED_ATMEL_SAMD_FLASH_API_H__
28+
29+
extern void flash_init_vfs(fs_user_mount_t *vfs);
30+
extern void flash_flush(void);
31+
32+
#endif // __MICROPY_INCLUDED_ATMEL_SAMD_FLASH_API_H__

atmel-samd/internal_flash.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ uint32_t internal_flash_get_block_count(void) {
6868
void internal_flash_flush(void) {
6969
}
7070

71+
void flash_flush(void) {
72+
internal_flash_flush();
73+
}
74+
7175
static void build_partition(uint8_t *buf, int boot, int type, uint32_t start_block, uint32_t num_blocks) {
7276
buf[0] = boot;
7377

atmel-samd/main.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
#endif
4040

4141
#include "autoreset.h"
42+
#include "flash_api.h"
4243
#include "mpconfigboard.h"
4344
#include "rgb_led_status.h"
4445
#include "shared_dma.h"
@@ -66,8 +67,6 @@ void do_str(const char *src, mp_parse_input_kind_t input_kind) {
6667
}
6768
}
6869

69-
extern void flash_init_vfs(fs_user_mount_t *vfs);
70-
7170
// we don't make this function static because it needs a lot of stack and we
7271
// want it to be executed without using stack within main() function
7372
void init_flash_fs(void) {
@@ -93,6 +92,8 @@ void init_flash_fs(void) {
9392
vfs->flags &= ~FSUSER_USB_WRITEABLE;
9493

9594
res = f_mkfs("/flash", 0, 0);
95+
// Flush the new file system to make sure its repaired immediately.
96+
flash_flush();
9697
if (res != FR_OK) {
9798
MP_STATE_PORT(fs_user_mount)[0] = NULL;
9899
return;

atmel-samd/shared_dma.c

Lines changed: 114 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,16 @@
2323
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
2424
* THE SOFTWARE.
2525
*/
26-
2726
#include "shared_dma.h"
2827

28+
#include "asf/sam0/drivers/system/interrupt/system_interrupt.h"
29+
2930
// We allocate two DMA resources for the entire lifecycle of the board (not the
3031
// vm) because the general_dma resource will be shared between the REPL and SPI
3132
// flash. Both uses must block each other in order to prevent conflict.
3233
struct dma_resource audio_dma;
33-
struct dma_resource general_dma;
34+
struct dma_resource general_dma_tx;
35+
struct dma_resource general_dma_rx;
3436

3537
void init_shared_dma(void) {
3638
struct dma_resource_config config;
@@ -45,10 +47,118 @@ void init_shared_dma(void) {
4547
// Turn on the transfer complete interrupt so that the job_status changes to done.
4648
g_chan_interrupt_flag[audio_dma.channel_id] |= (1UL << DMA_CALLBACK_TRANSFER_DONE);
4749

50+
// Prioritize the RX channel over the TX channel because TX can cause an RX
51+
// overflow.
4852
dma_get_config_defaults(&config);
49-
dma_allocate(&general_dma, &config);
53+
config.trigger_action = DMA_TRIGGER_ACTION_BEAT;
54+
config.event_config.input_action = DMA_EVENT_INPUT_TRIG;
55+
dma_allocate(&general_dma_rx, &config);
56+
g_chan_interrupt_flag[general_dma_rx.channel_id] |= (1UL << DMA_CALLBACK_TRANSFER_DONE);
57+
58+
dma_get_config_defaults(&config);
59+
config.trigger_action = DMA_TRIGGER_ACTION_BEAT;
60+
config.event_config.input_action = DMA_EVENT_INPUT_TRIG;
61+
dma_allocate(&general_dma_tx, &config);
62+
g_chan_interrupt_flag[general_dma_tx.channel_id] |= (1UL << DMA_CALLBACK_TRANSFER_DONE);
5063

5164
// Be sneaky and reuse the active descriptor memory.
5265
audio_dma.descriptor = &descriptor_section[audio_dma.channel_id];
53-
general_dma.descriptor = &descriptor_section[general_dma.channel_id];
66+
general_dma_rx.descriptor = &descriptor_section[general_dma_rx.channel_id];
67+
general_dma_tx.descriptor = &descriptor_section[general_dma_tx.channel_id];
68+
}
69+
70+
static uint8_t sercom_index(Sercom* sercom) {
71+
return ((uint32_t) sercom - (uint32_t) SERCOM0) / 0x400;
72+
}
73+
74+
static void dma_configure(uint8_t channel, uint8_t trigsrc) {
75+
system_interrupt_enter_critical_section();
76+
/** Select the DMA channel and clear software trigger */
77+
DMAC->CHID.reg = DMAC_CHID_ID(channel);
78+
DMAC->CHCTRLA.reg &= ~DMAC_CHCTRLA_ENABLE;
79+
DMAC->CHCTRLA.reg = DMAC_CHCTRLA_SWRST;
80+
DMAC->SWTRIGCTRL.reg &= (uint32_t)(~(1 << channel));
81+
DMAC->CHCTRLB.reg = DMAC_CHCTRLB_LVL(DMA_PRIORITY_LEVEL_0) | \
82+
DMAC_CHCTRLB_TRIGSRC(trigsrc) | \
83+
DMAC_CHCTRLB_TRIGACT(DMA_TRIGGER_ACTION_BEAT);
84+
system_interrupt_leave_critical_section();
85+
}
86+
87+
enum status_code shared_dma_write(Sercom* sercom, const uint8_t* buffer, uint32_t length) {
88+
if (general_dma_tx.job_status != STATUS_OK) {
89+
return general_dma_tx.job_status;
90+
}
91+
dma_configure(general_dma_tx.channel_id, sercom_index(sercom) * 2 + 2);
92+
93+
// Set up TX second.
94+
struct dma_descriptor_config descriptor_config;
95+
dma_descriptor_get_config_defaults(&descriptor_config);
96+
descriptor_config.beat_size = DMA_BEAT_SIZE_BYTE;
97+
descriptor_config.dst_increment_enable = false;
98+
descriptor_config.block_transfer_count = length;
99+
descriptor_config.source_address = ((uint32_t)buffer + length);
100+
// DATA register is consistently addressed across all SERCOM modes.
101+
descriptor_config.destination_address = ((uint32_t)&sercom->SPI.DATA.reg);
102+
103+
dma_descriptor_create(general_dma_tx.descriptor, &descriptor_config);
104+
enum status_code status = dma_start_transfer_job(&general_dma_tx);
105+
if (status != STATUS_OK) {
106+
return status;
107+
}
108+
109+
// Wait for the transfer to finish.
110+
while (general_dma_tx.job_status == STATUS_BUSY) {}
111+
112+
// This transmit will cause the RX buffer overflow but we're OK with that.
113+
// So, read the garbage data and clear the overflow flag.
114+
sercom->SPI.DATA.reg;
115+
sercom->SPI.DATA.reg;
116+
sercom->SPI.STATUS.bit.BUFOVF = 1;
117+
sercom->SPI.DATA.reg;
118+
119+
return general_dma_tx.job_status;
120+
}
121+
122+
enum status_code shared_dma_read(Sercom* sercom, uint8_t* buffer, uint32_t length, uint8_t tx) {
123+
if (general_dma_tx.job_status != STATUS_OK) {
124+
return general_dma_tx.job_status;
125+
}
126+
127+
dma_configure(general_dma_tx.channel_id, sercom_index(sercom) * 2 + 2);
128+
dma_configure(general_dma_rx.channel_id, sercom_index(sercom) * 2 + 1);
129+
130+
// Set up RX first.
131+
struct dma_descriptor_config descriptor_config;
132+
dma_descriptor_get_config_defaults(&descriptor_config);
133+
descriptor_config.beat_size = DMA_BEAT_SIZE_BYTE;
134+
descriptor_config.src_increment_enable = false;
135+
descriptor_config.block_transfer_count = length;
136+
// DATA register is consistently addressed across all SERCOM modes.
137+
descriptor_config.source_address = ((uint32_t)&sercom->SPI.DATA.reg);
138+
descriptor_config.destination_address = ((uint32_t)buffer + length);
139+
140+
dma_descriptor_create(general_dma_rx.descriptor, &descriptor_config);
141+
142+
// Set up TX to retransmit the same byte over and over.
143+
dma_descriptor_get_config_defaults(&descriptor_config);
144+
descriptor_config.beat_size = DMA_BEAT_SIZE_BYTE;
145+
descriptor_config.src_increment_enable = false;
146+
descriptor_config.dst_increment_enable = false;
147+
descriptor_config.block_transfer_count = length;
148+
descriptor_config.source_address = ((uint32_t)&tx);
149+
// DATA register is consistently addressed across all SERCOM modes.
150+
descriptor_config.destination_address = ((uint32_t)&sercom->SPI.DATA.reg);
151+
152+
dma_descriptor_create(general_dma_tx.descriptor, &descriptor_config);
153+
154+
// Start the RX job first so we don't miss the first byte. The TX job clocks
155+
// the output.
156+
general_dma_rx.transfered_size = 0;
157+
dma_start_transfer_job(&general_dma_rx);
158+
general_dma_tx.transfered_size = 0;
159+
dma_start_transfer_job(&general_dma_tx);
160+
161+
// Wait for the transfer to finish.
162+
while (general_dma_rx.job_status == STATUS_BUSY) {}
163+
return general_dma_rx.job_status;
54164
}

atmel-samd/shared_dma.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,12 @@
3030
#include "asf/sam0/drivers/dma/dma.h"
3131

3232
extern struct dma_resource audio_dma;
33-
extern struct dma_resource general_dma;
33+
extern struct dma_resource general_dma_tx;
34+
extern struct dma_resource general_dma_rx;
3435

3536
void init_shared_dma(void);
3637

38+
enum status_code shared_dma_write(Sercom* sercom, const uint8_t* buffer, uint32_t length);
39+
enum status_code shared_dma_read(Sercom* sercom, uint8_t* buffer, uint32_t length, uint8_t tx);
40+
3741
#endif // __MICROPY_INCLUDED_ATMEL_SAMD_SHARED_DMA_H__

atmel-samd/spi_flash.c

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
*
44
* The MIT License (MIT)
55
*
6-
* Copyright (c) 2013, 2014 Damien P. George
6+
* Copyright (c) 2016, 2017 Scott Shawcroft for Adafruit Industries
77
*
88
* Permission is hereby granted, free of charge, to any person obtaining a copy
99
* of this software and associated documentation files (the "Software"), to deal
@@ -37,6 +37,7 @@
3737
#include "extmod/fsusermount.h"
3838

3939
#include "rgb_led_status.h"
40+
#include "shared_dma.h"
4041

4142
#define SPI_FLASH_PART1_START_BLOCK (0x1)
4243

@@ -46,6 +47,7 @@
4647
#define CMD_READ_DATA 0x03
4748
#define CMD_SECTOR_ERASE 0x20
4849
// #define CMD_SECTOR_ERASE CMD_READ_JEDEC_ID
50+
#define CMD_DISABLE_WRITE 0x04
4951
#define CMD_ENABLE_WRITE 0x06
5052
#define CMD_PAGE_PROGRAM 0x02
5153
// #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) {
124126
flash_enable();
125127
status = spi_write_buffer_wait(&spi_flash_instance, read_request, 4);
126128
if (status == STATUS_OK) {
127-
status = spi_read_buffer_wait(&spi_flash_instance, data, data_length, 0x00);
129+
status = shared_dma_read(spi_flash_instance.hw, data, data_length, 0x00);
128130
}
129131
flash_disable();
130132
return status == STATUS_OK;
@@ -149,7 +151,7 @@ static bool write_flash(uint32_t address, const uint8_t* data, uint32_t data_len
149151
enum status_code status;
150152
status = spi_write_buffer_wait(&spi_flash_instance, command, 4);
151153
if (status == STATUS_OK) {
152-
status = spi_write_buffer_wait(&spi_flash_instance, data + bytes_written, page_size);
154+
status = shared_dma_write(spi_flash_instance.hw, data + bytes_written, page_size);
153155
}
154156
flash_disable();
155157
if (status != STATUS_OK) {
@@ -222,9 +224,8 @@ void spi_flash_init(void) {
222224
uint8_t jedec_id_request[4] = {CMD_READ_JEDEC_ID, 0x00, 0x00, 0x00};
223225
uint8_t response[4] = {0x00, 0x00, 0x00, 0x00};
224226
flash_enable();
225-
volatile enum status_code status = spi_transceive_buffer_wait(&spi_flash_instance, jedec_id_request, response, 4);
227+
spi_transceive_buffer_wait(&spi_flash_instance, jedec_id_request, response, 4);
226228
flash_disable();
227-
(void) status;
228229
if (response[1] == 0x01 && response[2] == 0x40 && response[3] == 0x15) {
229230
flash_size = 1 << 21; // 2 MiB
230231
sector_size = 1 << 12; // 4 KiB
@@ -234,6 +235,15 @@ void spi_flash_init(void) {
234235
flash_size = 0;
235236
}
236237

238+
// Turn off writes in case this is a microcontroller only reset.
239+
uint8_t disable_write_request[1] = {CMD_DISABLE_WRITE};
240+
uint8_t disable_response[1] = {0x00};
241+
flash_enable();
242+
spi_transceive_buffer_wait(&spi_flash_instance, disable_write_request, disable_response, 1);
243+
flash_disable();
244+
245+
wait_for_flash_ready();
246+
237247
current_sector = NO_SECTOR_LOADED;
238248
dirty_mask = 0;
239249
MP_STATE_VM(flash_ram_cache) = NULL;
@@ -404,6 +414,10 @@ void spi_flash_flush(void) {
404414
spi_flash_flush_keep_cache(false);
405415
}
406416

417+
void flash_flush(void) {
418+
spi_flash_flush();
419+
}
420+
407421
// Builds a partition entry for the MBR.
408422
static void build_partition(uint8_t *buf, int boot, int type,
409423
uint32_t start_block, uint32_t num_blocks) {

0 commit comments

Comments
 (0)