From e7fc0b6aa7eafb1977f3a8a70e46364d0a8f393c Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Fri, 9 Mar 2018 12:05:12 -0800 Subject: [PATCH] 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. --- ports/atmel-samd/Makefile | 4 +- .../itsybitsy_m0_express/mpconfigboard.h | 2 +- .../metro_m4_express_revb/mpconfigboard.h | 2 +- ports/atmel-samd/common-hal/busio/SPI.c | 40 +- .../external_flash/external_flash.c | 2 - ports/atmel-samd/external_flash/qspi_flash.c | 12 +- ports/atmel-samd/external_flash/spi_flash.c | 19 +- ports/atmel-samd/shared_dma.c | 523 ++++++++++++------ ports/atmel-samd/shared_dma.h | 18 +- ports/atmel-samd/supervisor/port.c | 5 +- 10 files changed, 411 insertions(+), 216 deletions(-) diff --git a/ports/atmel-samd/Makefile b/ports/atmel-samd/Makefile index 4660d575ec..73799e5f28 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 ca39792a92..675b9a30db 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 70c92ea444..7af9767caa 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 43288cc8bb..eca73be78a 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 ca8121bc93..304bf6462b 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 54f1af0dce..46e3cb92a3 100644 --- a/ports/atmel-samd/external_flash/qspi_flash.c +++ b/ports/atmel-samd/external_flash/qspi_flash.c @@ -30,6 +30,7 @@ #include #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 4b63132eea..fcaedcaaca 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 59fca30c81..8a6a606bbd 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 #include "shared_dma.h" +#include + #include "py/gc.h" #include "py/mpstate.h" -#undef ENABLE +#include "hal/utils/include/utils.h" -// We allocate two DMA resources for the entire lifecycle of the board (not the +#include "shared-bindings/microcontroller/__init__.h" + +// 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); + // Turn on the clocks + #ifdef SAMD51 + MCLK->AHBMASK.reg |= MCLK_AHBMASK_DMAC; + #endif - // 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; + #ifdef SAMD21 + PM->AHBMASK.reg |= PM_AHBMASK_DMAC; + PM->APBBMASK.reg |= PM_APBBMASK_DMAC; + #endif - // 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); + DMAC->CTRL.reg = DMAC_CTRL_SWRST; - // 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); + DMAC->BASEADDR.reg = (uint32_t) dma_descriptors; + DMAC->WRBADDR.reg = (uint32_t) write_back_descriptors; - // 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); + DMAC->CTRL.reg = DMAC_CTRL_DMAENABLE | DMAC_CTRL_LVLEN0; - // 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]; + // 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 + + #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 } -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); +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 - // 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; - } - - // 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; - - return general_dma_tx.job_status; + #ifdef SAMD51 + DmacChannel* channel = &DMAC->Channel[channel_number]; + channel->CHCTRLA.bit.ENABLE = true; + #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 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 +} + +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); + if (rx_active) { + enable_channel(SHARED_RX_CHANNEL); + } + if (tx_active) { + enable_channel(SHARED_TX_CHANNEL); + } - // Wait for the transfer to finish. - while (general_dma_rx.job_status == STATUS_BUSY) {} - while (sercom->SPI.INTFLAG.bit.RXC == 1) {} - return general_dma_rx.job_status; -} + 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 + } -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; + // 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) {} + } + if (tx_active) { + while ((transfer_status(SHARED_TX_CHANNEL) & 0x3) == 0) {} + } + + 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; } } - 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; + + 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; +} - // 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); +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); +} - // 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); +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); +} - 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; +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 -#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)); +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 2ddd81c4e4..536a95af33 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 +#include + +#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 fe65915106..7935c87d37 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);