summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorScott Shawcroft <scott@tannewt.org>2018-03-09 12:05:12 -0800
committerScott Shawcroft <scott.shawcroft@gmail.com>2018-03-09 12:45:46 -0800
commite7fc0b6aa7eafb1977f3a8a70e46364d0a8f393c (patch)
tree97f2f65473cbc825eb825fba8e2e53e6e40aabfc
parentf00d2814b78e092656a05cb16674c37f6587b062 (diff)
Use DMA for long SPI transactions including those to the SPI Flash.
QSPI is not currently working so its commented out. This is progress on #652.
-rw-r--r--ports/atmel-samd/Makefile4
-rw-r--r--ports/atmel-samd/boards/itsybitsy_m0_express/mpconfigboard.h2
-rw-r--r--ports/atmel-samd/boards/metro_m4_express_revb/mpconfigboard.h2
-rw-r--r--ports/atmel-samd/common-hal/busio/SPI.c40
-rw-r--r--ports/atmel-samd/external_flash/external_flash.c2
-rw-r--r--ports/atmel-samd/external_flash/qspi_flash.c12
-rw-r--r--ports/atmel-samd/external_flash/spi_flash.c19
-rw-r--r--ports/atmel-samd/shared_dma.c535
-rw-r--r--ports/atmel-samd/shared_dma.h18
-rw-r--r--ports/atmel-samd/supervisor/port.c5
10 files changed, 417 insertions, 222 deletions
diff --git a/ports/atmel-samd/Makefile b/ports/atmel-samd/Makefile
index 4660d575e..73799e5f2 100644
--- a/ports/atmel-samd/Makefile
+++ b/ports/atmel-samd/Makefile
@@ -217,9 +217,6 @@ endif
SRC_ASF := $(addprefix asf4/$(CHIP_FAMILY)/, $(SRC_ASF))
-# Skip this source for now.
-# shared_dma.c \
-
SRC_C = \
background.c \
fatfs_port.c \
@@ -229,6 +226,7 @@ SRC_C = \
$(CHIP_FAMILY)_peripherals.c \
peripherals.c \
$(CHIP_FAMILY)_pins.c \
+ shared_dma.c \
tick.c \
timers.c \
usb.c \
diff --git a/ports/atmel-samd/boards/itsybitsy_m0_express/mpconfigboard.h b/ports/atmel-samd/boards/itsybitsy_m0_express/mpconfigboard.h
index ca39792a9..675b9a30d 100644
--- a/ports/atmel-samd/boards/itsybitsy_m0_express/mpconfigboard.h
+++ b/ports/atmel-samd/boards/itsybitsy_m0_express/mpconfigboard.h
@@ -5,7 +5,7 @@
#define MICROPY_HW_APA102_MOSI (&pin_PA01)
#define MICROPY_HW_APA102_SCK (&pin_PA00)
-// Salae reads 12mhz which is the limit even though we set it to the safer 8mhz.
+// Saleae reads 12mhz which is the limit even though we set it to the safer 8mhz.
#define SPI_FLASH_BAUDRATE (8000000)
#define SPI_FLASH_MOSI_PIN PIN_PB22
diff --git a/ports/atmel-samd/boards/metro_m4_express_revb/mpconfigboard.h b/ports/atmel-samd/boards/metro_m4_express_revb/mpconfigboard.h
index 70c92ea44..7af9767ca 100644
--- a/ports/atmel-samd/boards/metro_m4_express_revb/mpconfigboard.h
+++ b/ports/atmel-samd/boards/metro_m4_express_revb/mpconfigboard.h
@@ -11,7 +11,7 @@
#define MICROPY_HW_NEOPIXEL (&pin_PB17)
-#define SPI_FLASH_BAUDRATE (8000000)
+#define SPI_FLASH_BAUDRATE (60000000)
// Rev B: single channel SPI
// Rev C will be QSPI
diff --git a/ports/atmel-samd/common-hal/busio/SPI.c b/ports/atmel-samd/common-hal/busio/SPI.c
index 43288cc8b..eca73be78 100644
--- a/ports/atmel-samd/common-hal/busio/SPI.c
+++ b/ports/atmel-samd/common-hal/busio/SPI.c
@@ -38,7 +38,7 @@
#include "peripherals.h"
#include "pins.h"
-//#include "shared_dma.h"
+#include "shared_dma.h"
void common_hal_busio_spi_construct(busio_spi_obj_t *self,
const mcu_pin_obj_t * clock, const mcu_pin_obj_t * mosi,
@@ -113,7 +113,7 @@ void common_hal_busio_spi_construct(busio_spi_obj_t *self,
// Set up SPI clocks on SERCOM.
samd_peripherals_sercom_clock_init(sercom, sercom_index);
-
+
#if defined(MICROPY_HW_APA102_SCK) && defined(MICROPY_HW_APA102_MOSI) && !defined(CIRCUITPY_BITBANG_APA102)
// if we're re-using the dotstar sercom, make sure it is disabled or the init will fail out
hri_sercomspi_clear_CTRLA_ENABLE_bit(sercom);
@@ -121,7 +121,7 @@ void common_hal_busio_spi_construct(busio_spi_obj_t *self,
if (spi_m_sync_init(&self->spi_desc, sercom) != ERR_NONE) {
mp_raise_OSError(MP_EIO);
}
-
+
// Pads must be set after spi_m_sync_init(), which uses default values from
// the prototypical SERCOM.
hri_sercomspi_write_CTRLA_DOPO_bf(sercom, dopo);
@@ -135,7 +135,7 @@ void common_hal_busio_spi_construct(busio_spi_obj_t *self,
// busy or not
mp_raise_OSError(MP_EIO);
}
-
+
gpio_set_pin_direction(clock->pin, GPIO_DIRECTION_OUT);
gpio_set_pin_pull_mode(clock->pin, GPIO_PULL_OFF);
gpio_set_pin_function(clock->pin, clock_pinmux);
@@ -194,7 +194,7 @@ bool common_hal_busio_spi_configure(busio_spi_obj_t *self,
return true;
}
- // Disable, set values (most or all are enable-protected), and re-enable.
+ // Disable, set values (most or all are enable-protected), and re-enable.
spi_m_sync_disable(&self->spi_desc);
hri_sercomspi_wait_for_sync(hw, SERCOM_SPI_SYNCBUSY_MASK);
@@ -235,14 +235,14 @@ bool common_hal_busio_spi_write(busio_spi_obj_t *self,
return true;
}
int32_t status;
-// if (len >= 16) {
-// status = shared_dma_write(self->spi_desc.dev.prvt, data, len);
-// } else {
+ if (len >= 16) {
+ status = sercom_dma_write(self->spi_desc.dev.prvt, data, len);
+ } else {
struct io_descriptor *spi_io;
spi_m_sync_get_io_descriptor(&self->spi_desc, &spi_io);
status = spi_io->write(spi_io, data, len);
-// }
- return status >= 0; // Status is number of chars read or an error code < 0.
+ }
+ return status >= 0; // Status is number of chars read or an error code < 0.
}
bool common_hal_busio_spi_read(busio_spi_obj_t *self,
@@ -251,17 +251,17 @@ bool common_hal_busio_spi_read(busio_spi_obj_t *self,
return true;
}
int32_t status;
-// if (len >= 16) {
-// status = shared_dma_read(self->spi_desc.dev.prvt, data, len, write_value);
-// } else {
+ if (len >= 16) {
+ status = sercom_dma_read(self->spi_desc.dev.prvt, data, len, write_value);
+ } else {
self->spi_desc.dev.dummy_byte = write_value;
struct io_descriptor *spi_io;
spi_m_sync_get_io_descriptor(&self->spi_desc, &spi_io);
status = spi_io->read(spi_io, data, len);
-// }
- return status >= 0; // Status is number of chars read or an error code < 0.
+ }
+ return status >= 0; // Status is number of chars read or an error code < 0.
}
bool common_hal_busio_spi_transfer(busio_spi_obj_t *self, uint8_t *data_out, uint8_t *data_in, size_t len) {
@@ -269,16 +269,16 @@ bool common_hal_busio_spi_transfer(busio_spi_obj_t *self, uint8_t *data_out, uin
return true;
}
int32_t status;
-// if (len >= 16) {
-// status = shared_dma_transfer(self->spi_master_instance.hw, data_out, data_in, len, 0 /*ignored*/);
-// } else {
+ if (len >= 16) {
+ status = sercom_dma_transfer(self->spi_desc.dev.prvt, data_out, data_in, len);
+ } else {
struct spi_xfer xfer;
xfer.txbuf = data_out;
xfer.rxbuf = data_in;
xfer.size = len;
status = spi_m_sync_transfer(&self->spi_desc, &xfer);
-// }
- return status >= 0; // Status is number of chars read or an error code < 0.
+ }
+ return status >= 0; // Status is number of chars read or an error code < 0.
}
uint32_t common_hal_busio_spi_get_frequency(busio_spi_obj_t* self) {
diff --git a/ports/atmel-samd/external_flash/external_flash.c b/ports/atmel-samd/external_flash/external_flash.c
index ca8121bc9..304bf6462 100644
--- a/ports/atmel-samd/external_flash/external_flash.c
+++ b/ports/atmel-samd/external_flash/external_flash.c
@@ -199,7 +199,6 @@ void external_flash_init(void) {
spi_flash_init();
-
for (uint8_t i = 0; i < num_possible_devices; i++) {
const external_flash_device* possible_device = &possible_devices[i];
uint8_t jedec_id_response[3] = {0x00, 0x00, 0x00};
@@ -213,7 +212,6 @@ void external_flash_init(void) {
}
if (flash_device == NULL) {
- asm("bkpt");
return;
}
diff --git a/ports/atmel-samd/external_flash/qspi_flash.c b/ports/atmel-samd/external_flash/qspi_flash.c
index 54f1af0dc..46e3cb92a 100644
--- a/ports/atmel-samd/external_flash/qspi_flash.c
+++ b/ports/atmel-samd/external_flash/qspi_flash.c
@@ -30,6 +30,7 @@
#include <string.h>
#include "external_flash/common_commands.h"
+#include "shared_dma.h"
#include "atmel_start_pins.h"
#include "hal_gpio.h"
@@ -125,6 +126,8 @@ bool spi_flash_write_data(uint32_t address, uint8_t* data, uint32_t length) {
QSPI_INSTRFRAME_DATAEN;
memcpy(((uint8_t *) QSPI_AHB) + address, data, length);
+ // TODO(tannewt): Fix DMA and enable it.
+ // qspi_dma_write(address, data, length);
QSPI->CTRLA.reg = QSPI_CTRLA_ENABLE | QSPI_CTRLA_LASTXFER;
@@ -148,6 +151,8 @@ bool spi_flash_read_data(uint32_t address, uint8_t* data, uint32_t length) {
QSPI_INSTRFRAME_DUMMYLEN(8);
memcpy(data, ((uint8_t *) QSPI_AHB) + address, length);
+ // TODO(tannewt): Fix DMA and enable it.
+ // qspi_dma_read(address, data, length);
QSPI->CTRLA.reg = QSPI_CTRLA_ENABLE | QSPI_CTRLA_LASTXFER;
@@ -167,12 +172,15 @@ void spi_flash_init(void) {
QSPI->CTRLA.reg = QSPI_CTRLA_SWRST;
// We don't need to wait because we're running as fast as the CPU.
- QSPI->BAUD.bit.BAUD = 1;
+ // Slow, good for debugging with Saleae
+ // QSPI->BAUD.bit.BAUD = 32;
+ // Super fast
+ QSPI->BAUD.bit.BAUD = 2;
QSPI->CTRLB.reg = QSPI_CTRLB_MODE_MEMORY |
QSPI_CTRLB_DATALEN_8BITS |
QSPI_CTRLB_CSMODE_LASTXFER;
- QSPI->CTRLA.bit.ENABLE = 1;
+ QSPI->CTRLA.reg = QSPI_CTRLA_ENABLE;
// The QSPI is only connected to one set of pins in the SAMD51 so we can hard code it.
uint32_t pins[6] = {PIN_PA08, PIN_PA09, PIN_PA10, PIN_PA11, PIN_PB10, PIN_PB11};
diff --git a/ports/atmel-samd/external_flash/spi_flash.c b/ports/atmel-samd/external_flash/spi_flash.c
index 4b63132ee..fcaedcaac 100644
--- a/ports/atmel-samd/external_flash/spi_flash.c
+++ b/ports/atmel-samd/external_flash/spi_flash.c
@@ -30,6 +30,7 @@
#include "external_flash/common_commands.h"
#include "peripherals.h"
+#include "shared_dma.h"
#include "hal_gpio.h"
#include "hal_spi_m_sync.h"
@@ -91,14 +92,28 @@ bool spi_flash_write_data(uint32_t address, uint8_t* data, uint32_t data_length)
uint8_t request[4] = {CMD_PAGE_PROGRAM, 0x00, 0x00, 0x00};
// Write the SPI flash write address into the bytes following the command byte.
address_to_bytes(address, request + 1);
- return transfer(request, 4, data, NULL, data_length);
+ struct spi_xfer xfer = { request, NULL, 4 };
+ flash_enable();
+ int32_t status = spi_m_sync_transfer(&spi_flash_desc, &xfer);
+ if (status >= 0) {
+ status = sercom_dma_write(spi_flash_desc.dev.prvt, data, data_length);
+ }
+ flash_disable();
+ return status >= 0;
}
bool spi_flash_read_data(uint32_t address, uint8_t* data, uint32_t data_length) {
uint8_t request[4] = {CMD_READ_DATA, 0x00, 0x00, 0x00};
// Write the SPI flash write address into the bytes following the command byte.
address_to_bytes(address, request + 1);
- return transfer(request, 4, NULL, data, data_length);
+ struct spi_xfer xfer = { request, NULL, 4 };
+ flash_enable();
+ int32_t status = spi_m_sync_transfer(&spi_flash_desc, &xfer);
+ if (status >= 0) {
+ status = sercom_dma_read(spi_flash_desc.dev.prvt, data, data_length, 0xff);
+ }
+ flash_disable();
+ return status >= 0;
}
void spi_flash_init(void) {
diff --git a/ports/atmel-samd/shared_dma.c b/ports/atmel-samd/shared_dma.c
index 59fca30c8..8a6a606bb 100644
--- a/ports/atmel-samd/shared_dma.c
+++ b/ports/atmel-samd/shared_dma.c
@@ -23,245 +23,414 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
-
-#include <stdbool.h>
#include "shared_dma.h"
+#include <string.h>
+
#include "py/gc.h"
#include "py/mpstate.h"
-#undef ENABLE
+#include "hal/utils/include/utils.h"
+
+#include "shared-bindings/microcontroller/__init__.h"
-// We allocate two DMA resources for the entire lifecycle of the board (not the
+// We allocate three 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_tx;
-struct dma_resource general_dma_rx;
+COMPILER_ALIGNED(16) static DmacDescriptor dma_descriptors[3];
+
+// Don't use these directly. They are used by the DMA engine itself.
+COMPILER_ALIGNED(16) static DmacDescriptor write_back_descriptors[3];
+
+#define AUDIO_DMA_CHANNEL 0
+#define SHARED_TX_CHANNEL 1
+#define SHARED_RX_CHANNEL 2
+
+
+#ifdef SAMD21
+#define FIRST_SERCOM_RX_TRIGSRC 0x01
+#define FIRST_SERCOM_TX_TRIGSRC 0x02
+#endif
+#ifdef SAMD51
+#define FIRST_SERCOM_RX_TRIGSRC 0x04
+#define FIRST_SERCOM_TX_TRIGSRC 0x05
+#endif
+
+// static void dma_configure_audio(uint8_t channel) {
+// 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));
+// uint32_t event_output_enable = 0;
+// if (output_event) {
+// event_output_enable = DMAC_CHCTRLB_EVOE;
+// }
+// DMAC->CHCTRLB.reg = DMAC_CHCTRLB_LVL(DMA_PRIORITY_LEVEL_0) |
+// DMAC_CHCTRLB_TRIGSRC(trigsrc) |
+// DMAC_CHCTRLB_TRIGACT(DMA_TRIGGER_ACTION_BEAT) |
+// event_output_enable;
+// // config.peripheral_trigger = DAC_DMAC_ID_EMPTY;
+// // config.trigger_action = DMA_TRIGGER_ACTION_BEAT;
+// // config.event_config.input_action = DMA_EVENT_INPUT_TRIG;
+// // config.event_config.event_output_enable = true;
+// system_interrupt_leave_critical_section();
+// }
void init_shared_dma(void) {
- struct dma_resource_config config;
- dma_get_config_defaults(&config);
-
- // See asf4_conf/hpl_dmac_config.h for initial settings for DMA channels
- // DMA Channel 0: audio, highest priority,
- // normal transfer on input, DAC 0 empty is trigger source, trigger on each beat, beat is one byte
- // output enable true.
- // asf3 settings:
- //config.peripheral_trigger = DAC_DMAC_ID_EMPTY;
- //config.trigger_action = DMA_TRIGGER_ACTION_BEAT;
- //config.event_config.input_action = DMA_EVENT_INPUT_TRIG;
- //config.event_config.event_output_enable = true;
-
- // 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 Channel 1: rx channel,
- // normal transfer on input, trigger on each beat, beat is one byte
- //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 Channel 1: rx channel,
- // normal transfer on input, trigger on each beat, beat is one byte
- //config.trigger_action = DMA_TRIGGER_ACTION_BEAT;
- //config.event_config.input_action = DMA_EVENT_INPUT_TRIG;
- 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_rx.descriptor = &descriptor_section[general_dma_rx.channel_id];
- general_dma_tx.descriptor = &descriptor_section[general_dma_tx.channel_id];
+ // Turn on the clocks
+ #ifdef SAMD51
+ MCLK->AHBMASK.reg |= MCLK_AHBMASK_DMAC;
+ #endif
+
+ #ifdef SAMD21
+ PM->AHBMASK.reg |= PM_AHBMASK_DMAC;
+ PM->APBBMASK.reg |= PM_APBBMASK_DMAC;
+ #endif
+
+ DMAC->CTRL.reg = DMAC_CTRL_SWRST;
+
+ DMAC->BASEADDR.reg = (uint32_t) dma_descriptors;
+ DMAC->WRBADDR.reg = (uint32_t) write_back_descriptors;
+
+ DMAC->CTRL.reg = DMAC_CTRL_DMAENABLE | DMAC_CTRL_LVLEN0;
+
+ // This allocates the lowest channel first so make sure the audio is first
+ // so it gets the highest priority.
+ // dma_configure_audio(0);
}
static uint8_t sercom_index(Sercom* sercom) {
+ #ifdef SAMD21
return ((uint32_t) sercom - (uint32_t) SERCOM0) / 0x400;
+ #else
+ const Sercom* sercoms[SERCOM_INST_NUM] = SERCOM_INSTS;
+ for (uint8_t i = 0; i < SERCOM_INST_NUM; i++) {
+ if (sercoms[i] == sercom) {
+ return i;
+ }
+ }
+ return 0;
+ #endif
}
-static void dma_configure(uint8_t channel, uint8_t trigsrc, bool output_event) {
- system_interrupt_enter_critical_section();
+static void dma_configure(uint8_t channel_number, uint8_t trigsrc, bool output_event) {
+ #ifdef SAMD21
+ common_hal_mcu_disable_interrupts();
/** Select the DMA channel and clear software trigger */
- DMAC->CHID.reg = DMAC_CHID_ID(channel);
+ DMAC->CHID.reg = DMAC_CHID_ID(channel_number);
DMAC->CHCTRLA.reg &= ~DMAC_CHCTRLA_ENABLE;
DMAC->CHCTRLA.reg = DMAC_CHCTRLA_SWRST;
- DMAC->SWTRIGCTRL.reg &= (uint32_t)(~(1 << channel));
+ DMAC->SWTRIGCTRL.reg &= (uint32_t)(~(1 << channel_number));
uint32_t event_output_enable = 0;
if (output_event) {
event_output_enable = DMAC_CHCTRLB_EVOE;
}
- DMAC->CHCTRLB.reg = DMAC_CHCTRLB_LVL(DMA_PRIORITY_LEVEL_0) |
+ DMAC->CHCTRLB.reg = DMAC_CHCTRLB_LVL_LVL0 |
DMAC_CHCTRLB_TRIGSRC(trigsrc) |
- DMAC_CHCTRLB_TRIGACT(DMA_TRIGGER_ACTION_BEAT) |
+ DMAC_CHCTRLB_TRIGACT_BEAT |
event_output_enable;
- system_interrupt_leave_critical_section();
-}
+ common_hal_mcu_enable_interrupts();
+ #endif
-int32_t 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, false);
-
- // Set up TX. There is no RX job.
- 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 != ERR_NONE) {
- return status;
+ #ifdef SAMD51
+ DmacChannel* channel = &DMAC->Channel[channel_number];
+ channel->CHCTRLA.reg &= ~DMAC_CHCTRLA_ENABLE;
+ channel->CHCTRLA.reg = DMAC_CHCTRLA_SWRST;
+ if (output_event) {
+ channel->CHEVCTRL.reg = DMAC_CHEVCTRL_EVOE;
}
+ channel->CHCTRLA.reg = DMAC_CHCTRLA_TRIGSRC(trigsrc) |
+ DMAC_CHCTRLA_TRIGACT_BURST |
+ DMAC_CHCTRLA_BURSTLEN_SINGLE;
+ #endif
+}
- // Wait for the dma transfer to finish.
- while (general_dma_tx.job_status == STATUS_BUSY) {}
-
- // Wait for the SPI transfer to complete.
- while (sercom->SPI.INTFLAG.bit.TXC == 0) {}
-
- // This transmit will cause the RX buffer overflow but we're OK with that.
- // So, read the garbage and clear the overflow flag.
- while (sercom->SPI.INTFLAG.bit.RXC == 1) {
- sercom->SPI.DATA.reg;
- }
- sercom->SPI.STATUS.bit.BUFOVF = 1;
- sercom->SPI.INTFLAG.reg = SERCOM_SPI_INTFLAG_ERROR;
+static void enable_channel(uint8_t channel_number) {
+ #ifdef SAMD21
+ common_hal_mcu_disable_interrupts();
+ /** Select the DMA channel and clear software trigger */
+ DMAC->CHID.reg = DMAC_CHID_ID(channel_number);
+ DMAC->CHCTRLA.bit.ENABLE = true;
+ common_hal_mcu_enable_interrupts();
+ #endif
+
+ #ifdef SAMD51
+ DmacChannel* channel = &DMAC->Channel[channel_number];
+ channel->CHCTRLA.bit.ENABLE = true;
+ #endif
+}
- return general_dma_tx.job_status;
+static uint8_t transfer_status(uint8_t channel_number) {
+ #ifdef SAMD21
+ common_hal_mcu_disable_interrupts();
+ /** Select the DMA channel and clear software trigger */
+ DMAC->CHID.reg = DMAC_CHID_ID(channel_number);
+ uint8_t status = DMAC->CHINTFLAG.reg;
+ common_hal_mcu_enable_interrupts();
+ return status;
+ #endif
+
+ #ifdef SAMD51
+ DmacChannel* channel = &DMAC->Channel[channel_number];
+ return channel->CHINTFLAG.reg;
+ #endif
}
-int32_t shared_dma_read(Sercom* sercom, uint8_t* buffer, uint32_t length, uint8_t tx) {
- if (general_dma_tx.job_status != ERR_NONE) {
+static bool channel_free(uint8_t channel_number) {
+ #ifdef SAMD21
+ common_hal_mcu_disable_interrupts();
+ /** Select the DMA channel and clear software trigger */
+ DMAC->CHID.reg = DMAC_CHID_ID(channel_number);
+ bool channel_free = DMAC->CHSTATUS.reg == 0;
+ common_hal_mcu_enable_interrupts();
+ return channel_free;
+ #endif
+
+ #ifdef SAMD51
+ DmacChannel* channel = &DMAC->Channel[channel_number];
+ return channel->CHSTATUS.reg == 0;
+ #endif
}
// Do write and read simultaneously. If buffer_out is NULL, write the tx byte over and over.
// If buffer_out is a real buffer, ignore tx.
-enum status_code shared_dma_transfer(Sercom* sercom, uint8_t* buffer_out, uint8_t* buffer_in, uint32_t length, uint8_t tx) {
- return general_dma_tx.job_status;
+// DMAs buffer_out -> dest
+// DMAs src -> buffer_in
+static int32_t shared_dma_transfer(void* peripheral,
+ const uint8_t* buffer_out, volatile uint32_t* dest,
+ volatile uint32_t* src, uint8_t* buffer_in,
+ uint32_t length, uint8_t tx) {
+ if (!channel_free(SHARED_TX_CHANNEL) ||
+ (buffer_in != NULL && !channel_free(SHARED_RX_CHANNEL))) {
+ return -1;
}
- dma_configure(general_dma_tx.channel_id, sercom_index(sercom) * 2 + 2, false);
- dma_configure(general_dma_rx.channel_id, sercom_index(sercom) * 2 + 1, false);
+ uint32_t beat_size = DMAC_BTCTRL_BEATSIZE_BYTE;
+ bool sercom = true;
+ bool tx_active = false;
+ bool rx_active = false;
+ uint16_t beat_length = length;
+ #ifdef SAMD51
+ if (peripheral == QSPI) {
+ // Check input alignment on word boundaries.
+ if ((((uint32_t) buffer_in) & 0x3) != 0 ||
+ (((uint32_t) buffer_out) & 0x3) != 0) {
+ return -3;
+ }
+ beat_size = DMAC_BTCTRL_BEATSIZE_WORD | DMAC_BTCTRL_SRCINC | DMAC_BTCTRL_DSTINC;
+ beat_length /= 4;
+ sercom = false;
+ if (buffer_out != NULL) {
+ dma_configure(SHARED_TX_CHANNEL, QSPI_DMAC_ID_TX, false);
+ tx_active = true;
+ } else {
+ dma_configure(SHARED_RX_CHANNEL, QSPI_DMAC_ID_RX, false);
+ rx_active = true;
+ }
+
+ } else {
+ #endif
+
+ // sercom index is incorrect for SAMD51
+ dma_configure(SHARED_TX_CHANNEL, sercom_index(peripheral) * 2 + FIRST_SERCOM_TX_TRIGSRC, false);
+ tx_active = true;
+ if (buffer_in != NULL) {
+ dma_configure(SHARED_RX_CHANNEL, sercom_index(peripheral) * 2 + FIRST_SERCOM_RX_TRIGSRC, false);
+ rx_active = true;
+ }
+
+ #ifdef SAMD51
+ }
+ #endif
// 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.dst_increment_enable = true;
- 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_in + length);
-
- dma_descriptor_create(general_dma_rx.descriptor, &descriptor_config);
+ if (rx_active) {
+ DmacDescriptor* rx_descriptor = &dma_descriptors[SHARED_RX_CHANNEL];
+ rx_descriptor->BTCTRL.reg = beat_size | DMAC_BTCTRL_DSTINC;
+ rx_descriptor->BTCNT.reg = beat_length;
+ rx_descriptor->SRCADDR.reg = ((uint32_t) src);
+ #ifdef SAMD51
+ if (peripheral == QSPI) {
+ rx_descriptor->SRCADDR.reg = ((uint32_t) src + length);
+ }
+ #endif
+ rx_descriptor->DSTADDR.reg = ((uint32_t)buffer_in + length);
+ rx_descriptor->BTCTRL.bit.VALID = true;
+ }
// Set up TX second.
- dma_descriptor_get_config_defaults(&descriptor_config);
- descriptor_config.beat_size = DMA_BEAT_SIZE_BYTE;
- // Increment write address only if we have a real buffer.
- descriptor_config.src_increment_enable = buffer_out != NULL;
- descriptor_config.dst_increment_enable = false;
- descriptor_config.block_transfer_count = length;
- //
- descriptor_config.source_address = ((uint32_t) (buffer_out != NULL ? buffer_out + length : &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);
-
+ if (tx_active) {
+ DmacDescriptor* tx_descriptor = &dma_descriptors[SHARED_TX_CHANNEL];
+ tx_descriptor->BTCTRL.reg = beat_size;
+ tx_descriptor->BTCNT.reg = beat_length;
+
+ if (buffer_out != NULL) {
+ tx_descriptor->SRCADDR.reg = ((uint32_t)buffer_out + length);
+ tx_descriptor->BTCTRL.reg |= DMAC_BTCTRL_SRCINC;
+ } else {
+ tx_descriptor->SRCADDR.reg = ((uint32_t) &tx);
+ }
+ tx_descriptor->DSTADDR.reg = ((uint32_t) dest);
+ tx_descriptor->BTCTRL.bit.VALID = true;
+ }
+ if (sercom) {
+ SercomSpi *s = &((Sercom*) peripheral)->SPI;
+ s->INTFLAG.reg = SERCOM_SPI_INTFLAG_RXC | SERCOM_SPI_INTFLAG_DRE;
+ } else {
+ //QSPI->INTFLAG.reg = QSPI_INTFLAG_RXC | QSPI_INTFLAG_DRE;
+ }
// 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) {}
+ if (rx_active) {
+ enable_channel(SHARED_RX_CHANNEL);
+ }
+ if (tx_active) {
+ enable_channel(SHARED_TX_CHANNEL);
+ }
- while (sercom->SPI.INTFLAG.bit.RXC == 1) {}
- return general_dma_rx.job_status;
-}
-bool allocate_block_counter() {
- // Find a timer to count DMA block completions.
- Tc *t = NULL;
- Tc *tcs[TC_INST_NUM] = TC_INSTS;
- for (uint8_t i = TC_INST_NUM; i > 0; i--) {
- if (tcs[i - 1]->COUNT16.CTRLA.bit.ENABLE == 0) {
- t = tcs[i - 1];
- break;
+ if (sercom) {
+ //DMAC->SWTRIGCTRL.reg |= (1 << SHARED_TX_CHANNEL);
+ } else {
+ // Do a manual copy to trigger then DMA. We do 32-bit accesses to match the DMA.
+ #pragma GCC diagnostic push
+ #pragma GCC diagnostic ignored "-Wcast-align"
+ if (rx_active) {
+ //buffer_in[0] = *src;
+ DMAC->SWTRIGCTRL.reg |= (1 << SHARED_RX_CHANNEL);
+ } else {
+ //*(uint32_t*)dest = ((uint32_t*) buffer_out)[0];
}
+ #pragma GCC diagnostic pop
}
- if (t == NULL) {
- return false;
+
+ // Channels cycle between Suspend -> Pending -> Busy and back while transfering. So, we check
+ // the channels transfer status for an error or completion.
+ if (rx_active) {
+ while ((transfer_status(SHARED_RX_CHANNEL) & 0x3) == 0) {}
}
- MP_STATE_VM(audiodma_block_counter) = gc_alloc(sizeof(struct tc_module), false);
- if (MP_STATE_VM(audiodma_block_counter) == NULL) {
- return false;
+ if (tx_active) {
+ while ((transfer_status(SHARED_TX_CHANNEL) & 0x3) == 0) {}
}
- // Don't bother setting the period. We set it before you playback anything.
- struct tc_config config_tc;
- tc_get_config_defaults(&config_tc);
- config_tc.counter_size = TC_COUNTER_SIZE_16BIT;
- config_tc.clock_prescaler = TC_CLOCK_PRESCALER_DIV1;
- if (tc_init(MP_STATE_VM(audiodma_block_counter), t, &config_tc) != STATUS_OK) {
- return false;
- };
-
- struct tc_events events_tc;
- events_tc.generate_event_on_overflow = false;
- events_tc.on_event_perform_action = true;
- events_tc.event_action = TC_EVENT_ACTION_INCREMENT_COUNTER;
- tc_enable_events(MP_STATE_VM(audiodma_block_counter), &events_tc);
-
- // Connect the timer overflow event, which happens at the target frequency,
- // to the DAC conversion trigger.
- MP_STATE_VM(audiodma_block_event) = gc_alloc(sizeof(struct events_resource), false);
- if (MP_STATE_VM(audiodma_block_event) == NULL) {
- return false;
- }
- struct events_config config;
- events_get_config_defaults(&config);
-
- uint8_t user = EVSYS_ID_USER_TC3_EVU;
- if (t == TC4) {
- user = EVSYS_ID_USER_TC4_EVU;
- } else if (t == TC5) {
- user = EVSYS_ID_USER_TC5_EVU;
-#ifdef TC6
- } else if (t == TC6) {
- user = EVSYS_ID_USER_TC6_EVU;
-#endif
-#ifdef TC7
- } else if (t == TC7) {
- user = EVSYS_ID_USER_TC7_EVU;
-#endif
+ if (sercom) {
+ Sercom* s = (Sercom*) peripheral;
+ // Wait for the SPI transfer to complete.
+ while (s->SPI.INTFLAG.bit.TXC == 0) {}
+
+ // This transmit will cause the RX buffer overflow but we're OK with that.
+ // So, read the garbage and clear the overflow flag.
+ if (!rx_active) {
+ while (s->SPI.INTFLAG.bit.RXC == 1) {
+ s->SPI.DATA.reg;
+ }
+ s->SPI.STATUS.bit.BUFOVF = 1;
+ s->SPI.INTFLAG.reg = SERCOM_SPI_INTFLAG_ERROR;
+ }
}
- config.generator = EVSYS_ID_GEN_DMAC_CH_0;
- config.path = EVENTS_PATH_ASYNCHRONOUS;
- if (events_allocate(MP_STATE_VM(audiodma_block_event), &config) != STATUS_OK ||
- events_attach_user(MP_STATE_VM(audiodma_block_event), user) != STATUS_OK) {
- return false;
+ if ((!rx_active || transfer_status(SHARED_RX_CHANNEL) == DMAC_CHINTFLAG_TCMPL) &&
+ (!tx_active || transfer_status(SHARED_TX_CHANNEL) == DMAC_CHINTFLAG_TCMPL)) {
+ return length;
}
+ return -2;
+}
+
+
+int32_t sercom_dma_transfer(Sercom* sercom, const uint8_t* buffer_out, uint8_t* buffer_in,
+ uint32_t length) {
+ return shared_dma_transfer(sercom, buffer_out, &sercom->SPI.DATA.reg, &sercom->SPI.DATA.reg, buffer_in, length, 0);
+}
+
+int32_t sercom_dma_write(Sercom* sercom, const uint8_t* buffer, uint32_t length) {
+ return shared_dma_transfer(sercom, buffer, &sercom->SPI.DATA.reg, NULL, NULL, length, 0);
+}
- tc_enable(MP_STATE_VM(audiodma_block_counter));
- tc_stop_counter(MP_STATE_VM(audiodma_block_counter));
+int32_t sercom_dma_read(Sercom* sercom, uint8_t* buffer, uint32_t length, uint8_t tx) {
+ return shared_dma_transfer(sercom, NULL, &sercom->SPI.DATA.reg, &sercom->SPI.DATA.reg, buffer, length, tx);
+}
+
+#ifdef SAMD51
+int32_t qspi_dma_write(uint32_t address, const uint8_t* buffer, uint32_t length) {
+ return shared_dma_transfer(QSPI, buffer, (uint32_t*) (QSPI_AHB + address), NULL, NULL, length, 0);
+}
+
+int32_t qspi_dma_read(uint32_t address, uint8_t* buffer, uint32_t length) {
+ return shared_dma_transfer(QSPI, NULL, NULL, (uint32_t*) (QSPI_AHB + address), buffer, length, 0);
+}
+#endif
+
+bool allocate_block_counter() {
+// // Find a timer to count DMA block completions.
+// Tc *t = NULL;
+// Tc *tcs[TC_INST_NUM] = TC_INSTS;
+// for (uint8_t i = TC_INST_NUM; i > 0; i--) {
+// if (tcs[i - 1]->COUNT16.CTRLA.bit.ENABLE == 0) {
+// t = tcs[i - 1];
+// break;
+// }
+// }
+// if (t == NULL) {
+// return false;
+// }
+// MP_STATE_VM(audiodma_block_counter) = gc_alloc(sizeof(struct tc_module), false);
+// if (MP_STATE_VM(audiodma_block_counter) == NULL) {
+// return false;
+// }
+//
+// // Don't bother setting the period. We set it before you playback anything.
+// struct tc_config config_tc;
+// tc_get_config_defaults(&config_tc);
+// config_tc.counter_size = TC_COUNTER_SIZE_16BIT;
+// config_tc.clock_prescaler = TC_CLOCK_PRESCALER_DIV1;
+// if (tc_init(MP_STATE_VM(audiodma_block_counter), t, &config_tc) != STATUS_OK) {
+// return false;
+// };
+//
+// struct tc_events events_tc;
+// events_tc.generate_event_on_overflow = false;
+// events_tc.on_event_perform_action = true;
+// events_tc.event_action = TC_EVENT_ACTION_INCREMENT_COUNTER;
+// tc_enable_events(MP_STATE_VM(audiodma_block_counter), &events_tc);
+//
+// // Connect the timer overflow event, which happens at the target frequency,
+// // to the DAC conversion trigger.
+// MP_STATE_VM(audiodma_block_event) = gc_alloc(sizeof(struct events_resource), false);
+// if (MP_STATE_VM(audiodma_block_event) == NULL) {
+// return false;
+// }
+// struct events_config config;
+// events_get_config_defaults(&config);
+//
+// uint8_t user = EVSYS_ID_USER_TC3_EVU;
+// if (t == TC4) {
+// user = EVSYS_ID_USER_TC4_EVU;
+// } else if (t == TC5) {
+// user = EVSYS_ID_USER_TC5_EVU;
+// #ifdef TC6
+// } else if (t == TC6) {
+// user = EVSYS_ID_USER_TC6_EVU;
+// #endif
+// #ifdef TC7
+// } else if (t == TC7) {
+// user = EVSYS_ID_USER_TC7_EVU;
+// #endif
+// }
+//
+// config.generator = EVSYS_ID_GEN_DMAC_CH_0;
+// config.path = EVENTS_PATH_ASYNCHRONOUS;
+// if (events_allocate(MP_STATE_VM(audiodma_block_event), &config) != STATUS_OK ||
+// events_attach_user(MP_STATE_VM(audiodma_block_event), user) != STATUS_OK) {
+// return false;
+// }
+//
+// tc_enable(MP_STATE_VM(audiodma_block_counter));
+// tc_stop_counter(MP_STATE_VM(audiodma_block_counter));
return true;
}
void switch_audiodma_trigger(uint8_t trigger_dmac_id) {
- dma_configure(audio_dma.channel_id, trigger_dmac_id, true);
+ //dma_configure(audio_dma.channel_id, trigger_dmac_id, true);
}
diff --git a/ports/atmel-samd/shared_dma.h b/ports/atmel-samd/shared_dma.h
index 2ddd81c4e..536a95af3 100644
--- a/ports/atmel-samd/shared_dma.h
+++ b/ports/atmel-samd/shared_dma.h
@@ -27,17 +27,23 @@
#ifndef MICROPY_INCLUDED_ATMEL_SAMD_SHARED_DMA_H
#define MICROPY_INCLUDED_ATMEL_SAMD_SHARED_DMA_H
-extern struct dma_resource audio_dma;
-extern struct dma_resource general_dma_tx;
-extern struct dma_resource general_dma_rx;
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "include/sam.h"
volatile bool audio_dma_in_use;
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);
-enum status_code shared_dma_transfer(Sercom* sercom, uint8_t* buffer_out, uint8_t* buffer_in, uint32_t length, uint8_t tx);
+#ifdef SAMD51
+int32_t qspi_dma_write(uint32_t address, const uint8_t* buffer, uint32_t length);
+int32_t qspi_dma_read(uint32_t address, uint8_t* buffer, uint32_t length);
+#endif
+
+int32_t sercom_dma_write(Sercom* sercom, const uint8_t* buffer, uint32_t length);
+int32_t sercom_dma_read(Sercom* sercom, uint8_t* buffer, uint32_t length, uint8_t tx);
+int32_t sercom_dma_transfer(Sercom* sercom, const uint8_t* buffer_out, uint8_t* buffer_in, uint32_t length);
// Allocate a counter to track how far along we are in a DMA double buffer.
bool allocate_block_counter(void);
diff --git a/ports/atmel-samd/supervisor/port.c b/ports/atmel-samd/supervisor/port.c
index fe6591510..7935c87d3 100644
--- a/ports/atmel-samd/supervisor/port.c
+++ b/ports/atmel-samd/supervisor/port.c
@@ -49,6 +49,7 @@
#include "common-hal/pulseio/PulseIn.h"
#include "common-hal/pulseio/PulseOut.h"
#include "common-hal/pulseio/PWMOut.h"
+#include "shared_dma.h"
#include "tick.h"
extern volatile bool mp_msc_enabled;
@@ -120,7 +121,7 @@ safe_mode_t port_init(void) {
// config_nvm.manual_page_write = false;
// nvm_set_config(&config_nvm);
- // init_shared_dma();
+ init_shared_dma();
#ifdef CIRCUITPY_CANARY_WORD
// Run in safe mode if the canary is corrupt.
if (_ezero != CIRCUITPY_CANARY_WORD) {
@@ -206,7 +207,7 @@ void reset_port(void) {
reset_all_pins();
// Set up debugging pins after reset_all_pins().
-
+
// Uncomment to init PIN_PA17 for debugging.
// struct port_config pin_conf;
// port_get_config_defaults(&pin_conf);