From 87483f3281fe6a05c9e66ae62615205f74675c49 Mon Sep 17 00:00:00 2001 From: Randall Scharpf Date: Sun, 19 May 2024 17:40:40 -0700 Subject: [PATCH 1/4] add async shared dma interface, enable using more dma channels --- samd/dma.c | 534 ++++++++++++++++++++++++++--------------------------- samd/dma.h | 134 +++++++------- 2 files changed, 334 insertions(+), 334 deletions(-) diff --git a/samd/dma.c b/samd/dma.c index 519bd9b..246adc7 100644 --- a/samd/dma.c +++ b/samd/dma.c @@ -1,267 +1,267 @@ -/* - * This file is part of the MicroPython 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. - */ -#include "samd/dma.h" - -#include - -#include "mphalport.h" -#include "py/gc.h" -#include "py/mpstate.h" - -#include "hal/utils/include/utils.h" - -#include "shared-bindings/microcontroller/__init__.h" - -COMPILER_ALIGNED(16) static DmacDescriptor dma_descriptors[DMA_CHANNEL_COUNT]; - -// Don't use these directly. They are used by the DMA engine itself. -COMPILER_ALIGNED(16) static DmacDescriptor write_back_descriptors[DMA_CHANNEL_COUNT]; - -#ifdef SAMD21 -#define FIRST_SERCOM_RX_TRIGSRC 0x01 -#define FIRST_SERCOM_TX_TRIGSRC 0x02 -#endif -#ifdef SAM_D5X_E5X -#define FIRST_SERCOM_RX_TRIGSRC 0x04 -#define FIRST_SERCOM_TX_TRIGSRC 0x05 -#endif - -void init_shared_dma(void) { - // Turn on the clocks - #ifdef SAM_D5X_E5X - 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; - - // Non-audio channels will be configured on demand. - for (uint8_t i = 0; i < AUDIO_DMA_CHANNEL_COUNT; i++) { - dma_configure(i, 0, true); - } -} - -// 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. -// 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 (!dma_channel_free(SHARED_TX_CHANNEL) || - (buffer_in != NULL && !dma_channel_free(SHARED_RX_CHANNEL))) { - return -1; - } - - 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 SAM_D5X_E5X - 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 - - 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 SAM_D5X_E5X - } - #endif - - // Set up RX first. - 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 SAM_D5X_E5X - 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. - 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; - } - - // Start the RX job first so we don't miss the first byte. The TX job clocks the output. - // Disable interrupts during startup to make sure both RX and TX start at just about the same time. - mp_hal_disable_all_interrupts(); - if (rx_active) { - dma_enable_channel(SHARED_RX_CHANNEL); - } - if (tx_active) { - dma_enable_channel(SHARED_TX_CHANNEL); - } - mp_hal_enable_all_interrupts(); - - if (!sercom) { - if (rx_active) { - DMAC->SWTRIGCTRL.reg |= (1 << SHARED_RX_CHANNEL); - } - } - - #ifdef SAM_D5X_E5X - // Sometimes (silicon bug?) this DMA transfer never starts, and another channel sits with - // CHSTATUS.reg = 0x3 (BUSY | PENDING). On the other hand, this is a - // legitimate state for a DMA channel to be in (apparently), so we can't use that alone as a check. - // Instead, let's look at the ACTIVE flag. When DMA is hung, everything in ACTIVE is zeros. - bool is_okay = false; - for (int i = 0; i < 10 && !is_okay; i++) { - bool complete = true; - if (rx_active) { - if (DMAC->Channel[SHARED_RX_CHANNEL].CHSTATUS.reg & 0x3) - complete = false; - } - if (tx_active) { - if (DMAC->Channel[SHARED_TX_CHANNEL].CHSTATUS.reg & 0x3) - complete = false; - } - is_okay = is_okay || (DMAC->ACTIVE.bit.ABUSY || complete); - } - if (!is_okay) { - for (int i = 0; i < AUDIO_DMA_CHANNEL_COUNT; i++) { - if(DMAC->Channel[i].CHCTRLA.bit.ENABLE) { - DMAC->Channel[i].CHCTRLA.bit.ENABLE = 0; - DMAC->Channel[i].CHCTRLA.bit.ENABLE = 1; - } - } - } - #endif - - // busy-wait for the RX and TX DMAs to either complete or encounter an error - if (rx_active) { - while ((dma_transfer_status(SHARED_RX_CHANNEL) & 0x3) == 0) {} - } - if (tx_active) { - while ((dma_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 ((!rx_active || dma_transfer_status(SHARED_RX_CHANNEL) == DMAC_CHINTFLAG_TCMPL) && - (!tx_active || dma_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); -} - -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 SAM_D5X_E5X -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 - -DmacDescriptor* dma_descriptor(uint8_t channel_number) { - return &dma_descriptors[channel_number]; -} - -DmacDescriptor* dma_write_back_descriptor(uint8_t channel_number) { - return &write_back_descriptors[channel_number]; -} +/* + * This file is part of the MicroPython 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. + */ +#include "samd/dma.h" + +#include + +#include "mphalport.h" +#include "py/gc.h" +#include "py/mpstate.h" + +#include "hal/utils/include/utils.h" + +#include "shared-bindings/microcontroller/__init__.h" + +COMPILER_ALIGNED(16) static DmacDescriptor dma_descriptors[DMA_CHANNEL_COUNT]; + +// Don't use these directly. They are used by the DMA engine itself. +COMPILER_ALIGNED(16) static DmacDescriptor write_back_descriptors[DMA_CHANNEL_COUNT]; + +#ifdef SAMD21 +#define FIRST_SERCOM_RX_TRIGSRC 0x01 +#define FIRST_SERCOM_TX_TRIGSRC 0x02 +#endif +#ifdef SAM_D5X_E5X +#define FIRST_SERCOM_RX_TRIGSRC 0x04 +#define FIRST_SERCOM_TX_TRIGSRC 0x05 +#endif + +void init_shared_dma(void) { + // Turn on the clocks + #ifdef SAM_D5X_E5X + 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; + + // Non-audio channels will be configured on demand. + for (uint8_t i = 0; i < AUDIO_DMA_CHANNEL_COUNT; i++) { + dma_configure(i, 0, true); + } +} + +// 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. +// 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 (!dma_channel_free(SHARED_TX_CHANNEL) || + (buffer_in != NULL && !dma_channel_free(SHARED_RX_CHANNEL))) { + return -1; + } + + 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 SAM_D5X_E5X + 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 + + 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 SAM_D5X_E5X + } + #endif + + // Set up RX first. + 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 SAM_D5X_E5X + 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. + 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; + } + + // Start the RX job first so we don't miss the first byte. The TX job clocks the output. + // Disable interrupts during startup to make sure both RX and TX start at just about the same time. + mp_hal_disable_all_interrupts(); + if (rx_active) { + dma_enable_channel(SHARED_RX_CHANNEL); + } + if (tx_active) { + dma_enable_channel(SHARED_TX_CHANNEL); + } + mp_hal_enable_all_interrupts(); + + if (!sercom) { + if (rx_active) { + DMAC->SWTRIGCTRL.reg |= (1 << SHARED_RX_CHANNEL); + } + } + + #ifdef SAM_D5X_E5X + // Sometimes (silicon bug?) this DMA transfer never starts, and another channel sits with + // CHSTATUS.reg = 0x3 (BUSY | PENDING). On the other hand, this is a + // legitimate state for a DMA channel to be in (apparently), so we can't use that alone as a check. + // Instead, let's look at the ACTIVE flag. When DMA is hung, everything in ACTIVE is zeros. + bool is_okay = false; + for (int i = 0; i < 10 && !is_okay; i++) { + bool complete = true; + if (rx_active) { + if (DMAC->Channel[SHARED_RX_CHANNEL].CHSTATUS.reg & 0x3) + complete = false; + } + if (tx_active) { + if (DMAC->Channel[SHARED_TX_CHANNEL].CHSTATUS.reg & 0x3) + complete = false; + } + is_okay = is_okay || (DMAC->ACTIVE.bit.ABUSY || complete); + } + if (!is_okay) { + for (int i = 0; i < AUDIO_DMA_CHANNEL_COUNT; i++) { + if(DMAC->Channel[i].CHCTRLA.bit.ENABLE) { + DMAC->Channel[i].CHCTRLA.bit.ENABLE = 0; + DMAC->Channel[i].CHCTRLA.bit.ENABLE = 1; + } + } + } + #endif + + // busy-wait for the RX and TX DMAs to either complete or encounter an error + if (rx_active) { + while ((dma_transfer_status(SHARED_RX_CHANNEL) & 0x3) == 0) {} + } + if (tx_active) { + while ((dma_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 ((!rx_active || dma_transfer_status(SHARED_RX_CHANNEL) == DMAC_CHINTFLAG_TCMPL) && + (!tx_active || dma_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); +} + +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 SAM_D5X_E5X +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 + +DmacDescriptor* dma_descriptor(uint8_t channel_number) { + return &dma_descriptors[channel_number]; +} + +DmacDescriptor* dma_write_back_descriptor(uint8_t channel_number) { + return &write_back_descriptors[channel_number]; +} diff --git a/samd/dma.h b/samd/dma.h index 0768dd9..cbd9acd 100644 --- a/samd/dma.h +++ b/samd/dma.h @@ -1,67 +1,67 @@ -/* - * This file is part of the MicroPython 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_PERIPHERALS_DMA_H -#define MICROPY_INCLUDED_ATMEL_SAMD_PERIPHERALS_DMA_H - -#include -#include - -#include "include/sam.h" - -// We allocate 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. -#define AUDIO_DMA_CHANNEL_COUNT 3 -#define DMA_CHANNEL_COUNT (AUDIO_DMA_CHANNEL_COUNT + 2) -#define SHARED_TX_CHANNEL (DMA_CHANNEL_COUNT - 2) -#define SHARED_RX_CHANNEL (DMA_CHANNEL_COUNT - 1) - -void init_shared_dma(void); - -#ifdef SAM_D5X_E5X -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 - -uint8_t sercom_index(Sercom* sercom); - -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); - -void dma_configure(uint8_t channel_number, uint8_t trigsrc, bool output_event); -void dma_enable_channel(uint8_t channel_number); -void dma_disable_channel(uint8_t channel_number); -void dma_suspend_channel(uint8_t channel_number); -void dma_resume_channel(uint8_t channel_number); -bool dma_channel_free(uint8_t channel_number); -bool dma_channel_enabled(uint8_t channel_number); -uint8_t dma_transfer_status(uint8_t channel_number); -DmacDescriptor* dma_descriptor(uint8_t channel_number); -DmacDescriptor* dma_write_back_descriptor(uint8_t channel_number); - -#endif // MICROPY_INCLUDED_ATMEL_SAMD_PERIPHERALS_DMA_H +/* + * This file is part of the MicroPython 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_PERIPHERALS_DMA_H +#define MICROPY_INCLUDED_ATMEL_SAMD_PERIPHERALS_DMA_H + +#include +#include + +#include "include/sam.h" + +// We allocate 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. +#define AUDIO_DMA_CHANNEL_COUNT 3 +#define DMA_CHANNEL_COUNT (AUDIO_DMA_CHANNEL_COUNT + 2) +#define SHARED_TX_CHANNEL (DMA_CHANNEL_COUNT - 2) +#define SHARED_RX_CHANNEL (DMA_CHANNEL_COUNT - 1) + +void init_shared_dma(void); + +#ifdef SAM_D5X_E5X +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 + +uint8_t sercom_index(Sercom* sercom); + +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); + +void dma_configure(uint8_t channel_number, uint8_t trigsrc, bool output_event); +void dma_enable_channel(uint8_t channel_number); +void dma_disable_channel(uint8_t channel_number); +void dma_suspend_channel(uint8_t channel_number); +void dma_resume_channel(uint8_t channel_number); +bool dma_channel_free(uint8_t channel_number); +bool dma_channel_enabled(uint8_t channel_number); +uint8_t dma_transfer_status(uint8_t channel_number); +DmacDescriptor* dma_descriptor(uint8_t channel_number); +DmacDescriptor* dma_write_back_descriptor(uint8_t channel_number); + +#endif // MICROPY_INCLUDED_ATMEL_SAMD_PERIPHERALS_DMA_H From b9f41ac76634c222c91ae937a421eb893f4f1bc0 Mon Sep 17 00:00:00 2001 From: Randall Scharpf Date: Fri, 3 May 2024 20:07:10 -0700 Subject: [PATCH 2/4] rebase changes into upstream --- samd/dma.c | 136 ++++++++++++++++++++++++++++++++++++++++++----------- samd/dma.h | 28 +++++++++-- 2 files changed, 132 insertions(+), 32 deletions(-) diff --git a/samd/dma.c b/samd/dma.c index 519bd9b..4925942 100644 --- a/samd/dma.c +++ b/samd/dma.c @@ -49,6 +49,26 @@ COMPILER_ALIGNED(16) static DmacDescriptor write_back_descriptors[DMA_CHANNEL_CO #define FIRST_SERCOM_TX_TRIGSRC 0x05 #endif +static bool dma_allocated[DMA_CHANNEL_COUNT]; + +uint8_t dma_allocate_channel(bool audio_channel) { + uint8_t channel; + uint8_t lim = audio_channel ? AUDIO_DMA_CHANNEL_COUNT : DMA_CHANNEL_COUNT; + for (channel = (audio_channel ? 0 : AUDIO_DMA_CHANNEL_COUNT); channel < lim; channel++) { + if (!dma_allocated[channel]) { + dma_allocated[channel] = true; + return channel; + } + } + return channel; // i.e., return failure +} + +void dma_free_channel(uint8_t channel) { + assert(dma_allocated[channel]); + dma_disable_channel(channel); + dma_allocated[channel] = false; +} + void init_shared_dma(void) { // Turn on the clocks #ifdef SAM_D5X_E5X @@ -77,13 +97,21 @@ void init_shared_dma(void) { // If buffer_out is a real buffer, ignore tx. // DMAs buffer_out -> dest // DMAs src -> buffer_in -static int32_t shared_dma_transfer(void* peripheral, +dma_descr_t shared_dma_transfer_start(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 (!dma_channel_free(SHARED_TX_CHANNEL) || - (buffer_in != NULL && !dma_channel_free(SHARED_RX_CHANNEL))) { - return -1; + dma_descr_t res; + res.progress = 0; + + uint8_t tx_channel = dma_allocate_channel(false); + uint8_t rx_channel = dma_allocate_channel(false); + + if ((tx_channel >= DMA_CHANNEL_COUNT) || (rx_channel >= DMA_CHANNEL_COUNT) || + !dma_channel_free(tx_channel) || + (buffer_in != NULL && !dma_channel_free(rx_channel))) { + res.failure = -1; + return res; } uint32_t beat_size = DMAC_BTCTRL_BEATSIZE_BYTE; @@ -96,26 +124,27 @@ static int32_t shared_dma_transfer(void* peripheral, // Check input alignment on word boundaries. if ((((uint32_t) buffer_in) & 0x3) != 0 || (((uint32_t) buffer_out) & 0x3) != 0) { - return -3; + res.failure = -3; + return res; } 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); + dma_configure(tx_channel, QSPI_DMAC_ID_TX, false); tx_active = true; } else { - dma_configure(SHARED_RX_CHANNEL, QSPI_DMAC_ID_RX, false); + dma_configure(rx_channel, QSPI_DMAC_ID_RX, false); rx_active = true; } } else { #endif - dma_configure(SHARED_TX_CHANNEL, sercom_index(peripheral) * 2 + FIRST_SERCOM_TX_TRIGSRC, false); + dma_configure(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); + dma_configure(rx_channel, sercom_index(peripheral) * 2 + FIRST_SERCOM_RX_TRIGSRC, false); rx_active = true; } @@ -125,7 +154,7 @@ static int32_t shared_dma_transfer(void* peripheral, // Set up RX first. if (rx_active) { - DmacDescriptor* rx_descriptor = &dma_descriptors[SHARED_RX_CHANNEL]; + DmacDescriptor* rx_descriptor = &dma_descriptors[rx_channel]; rx_descriptor->BTCTRL.reg = beat_size | DMAC_BTCTRL_DSTINC; rx_descriptor->BTCNT.reg = beat_length; rx_descriptor->SRCADDR.reg = ((uint32_t) src); @@ -140,7 +169,7 @@ static int32_t shared_dma_transfer(void* peripheral, // Set up TX second. if (tx_active) { - DmacDescriptor* tx_descriptor = &dma_descriptors[SHARED_TX_CHANNEL]; + DmacDescriptor* tx_descriptor = &dma_descriptors[tx_channel]; tx_descriptor->BTCTRL.reg = beat_size; tx_descriptor->BTCNT.reg = beat_length; @@ -155,6 +184,8 @@ static int32_t shared_dma_transfer(void* peripheral, } if (sercom) { SercomSpi *s = &((Sercom*) peripheral)->SPI; + // TODO: test if this operation is necessary or if it's just a waste of time and space + // Section 35.8.7 of the datasheet lists both of these bits as read-only, so this shouldn't do anything s->INTFLAG.reg = SERCOM_SPI_INTFLAG_RXC | SERCOM_SPI_INTFLAG_DRE; } @@ -162,16 +193,16 @@ static int32_t shared_dma_transfer(void* peripheral, // Disable interrupts during startup to make sure both RX and TX start at just about the same time. mp_hal_disable_all_interrupts(); if (rx_active) { - dma_enable_channel(SHARED_RX_CHANNEL); + dma_enable_channel(rx_channel); } if (tx_active) { - dma_enable_channel(SHARED_TX_CHANNEL); + dma_enable_channel(tx_channel); } mp_hal_enable_all_interrupts(); if (!sercom) { if (rx_active) { - DMAC->SWTRIGCTRL.reg |= (1 << SHARED_RX_CHANNEL); + DMAC->SWTRIGCTRL.reg |= (1 << rx_channel); } } @@ -184,11 +215,11 @@ static int32_t shared_dma_transfer(void* peripheral, for (int i = 0; i < 10 && !is_okay; i++) { bool complete = true; if (rx_active) { - if (DMAC->Channel[SHARED_RX_CHANNEL].CHSTATUS.reg & 0x3) + if (DMAC->Channel[rx_channel].CHSTATUS.reg & 0x3) complete = false; } if (tx_active) { - if (DMAC->Channel[SHARED_TX_CHANNEL].CHSTATUS.reg & 0x3) + if (DMAC->Channel[tx_channel].CHSTATUS.reg & 0x3) complete = false; } is_okay = is_okay || (DMAC->ACTIVE.bit.ABUSY || complete); @@ -202,23 +233,46 @@ static int32_t shared_dma_transfer(void* peripheral, } } #endif + res.peripheral = peripheral; + res.length = length; + res.rx_channel = rx_channel; + res.tx_channel = tx_channel; + res.rx_active = rx_active; + res.tx_active = tx_active; + res.sercom = sercom; + res.failure = 0; + return res; +} - // busy-wait for the RX and TX DMAs to either complete or encounter an error - if (rx_active) { - while ((dma_transfer_status(SHARED_RX_CHANNEL) & 0x3) == 0) {} +bool shared_dma_transfer_finished(dma_descr_t descr) { + if (descr.failure != 0) { + return true; } - if (tx_active) { - while ((dma_transfer_status(SHARED_TX_CHANNEL) & 0x3) == 0) {} + + if (descr.progress < 1 && descr.rx_active) { + if ((dma_transfer_status(descr.rx_channel) & 0x3) == 0) { + return false; + } + descr.progress = 1; + } + if (descr.progress < 2 && descr.tx_active) { + if ((dma_transfer_status(descr.tx_channel) & 0x3) == 0) { + return false; + } + descr.progress = 2; } - if (sercom) { - Sercom* s = (Sercom*) peripheral; + if (descr.progress < 3 && descr.sercom) { + Sercom* s = (Sercom*) descr.peripheral; // Wait for the SPI transfer to complete. - while (s->SPI.INTFLAG.bit.TXC == 0) {} + if (s->SPI.INTFLAG.bit.TXC == 0) { + return false; + } + descr.progress = 3; // 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) { + if (!descr.rx_active) { while (s->SPI.INTFLAG.bit.RXC == 1) { s->SPI.DATA.reg; } @@ -227,13 +281,39 @@ static int32_t shared_dma_transfer(void* peripheral, } } - if ((!rx_active || dma_transfer_status(SHARED_RX_CHANNEL) == DMAC_CHINTFLAG_TCMPL) && - (!tx_active || dma_transfer_status(SHARED_TX_CHANNEL) == DMAC_CHINTFLAG_TCMPL)) { - return length; + return true; +} + +int shared_dma_transfer_close(dma_descr_t descr) { + dma_free_channel(descr.tx_channel); + dma_free_channel(descr.rx_channel); + + if (descr.failure != 0) { + return descr.failure; + } + + if ((!descr.rx_active || dma_transfer_status(descr.rx_channel) == DMAC_CHINTFLAG_TCMPL) && + (!descr.tx_active || dma_transfer_status(descr.tx_channel) == DMAC_CHINTFLAG_TCMPL)) { + return descr.length; } return -2; } +// 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. +// 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) { + dma_descr_t descr = shared_dma_transfer_start(peripheral, buffer_out, dest, src, buffer_in, length, tx); + if (descr.failure != 0) { + return descr.failure; + } + while (!shared_dma_transfer_finished(descr)) {} + return shared_dma_transfer_close(descr); +} int32_t sercom_dma_transfer(Sercom* sercom, const uint8_t* buffer_out, uint8_t* buffer_in, uint32_t length) { diff --git a/samd/dma.h b/samd/dma.h index 0768dd9..883143d 100644 --- a/samd/dma.h +++ b/samd/dma.h @@ -35,10 +35,11 @@ // We allocate 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. -#define AUDIO_DMA_CHANNEL_COUNT 3 -#define DMA_CHANNEL_COUNT (AUDIO_DMA_CHANNEL_COUNT + 2) -#define SHARED_TX_CHANNEL (DMA_CHANNEL_COUNT - 2) -#define SHARED_RX_CHANNEL (DMA_CHANNEL_COUNT - 1) +#define AUDIO_DMA_CHANNEL_COUNT 4 +#define DMA_CHANNEL_COUNT 32 + +uint8_t dma_allocate_channel(bool audio_channel); +void dma_free_channel(uint8_t channel); void init_shared_dma(void); @@ -53,6 +54,25 @@ 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); +typedef struct { + void* peripheral; + uint32_t length; + uint8_t progress; + uint8_t rx_channel; + uint8_t tx_channel; + bool rx_active; + bool tx_active; + bool sercom; + int8_t failure; +} dma_descr_t; + +dma_descr_t shared_dma_transfer_start(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); +bool shared_dma_transfer_finished(dma_descr_t descr); +int shared_dma_transfer_close(dma_descr_t descr); + void dma_configure(uint8_t channel_number, uint8_t trigsrc, bool output_event); void dma_enable_channel(uint8_t channel_number); void dma_disable_channel(uint8_t channel_number); From e05a742d51608f11cb59460961dbbc19eddb8cd1 Mon Sep 17 00:00:00 2001 From: Randall Scharpf Date: Sun, 19 May 2024 22:06:16 -0700 Subject: [PATCH 3/4] rebase changes onto upstream --- samd/dma.c | 694 ++++++++++++++++++++++++++--------------------------- samd/dma.h | 174 +++++++------- 2 files changed, 434 insertions(+), 434 deletions(-) diff --git a/samd/dma.c b/samd/dma.c index 4925942..dda251a 100644 --- a/samd/dma.c +++ b/samd/dma.c @@ -1,347 +1,347 @@ -/* - * This file is part of the MicroPython 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. - */ -#include "samd/dma.h" - -#include - -#include "mphalport.h" -#include "py/gc.h" -#include "py/mpstate.h" - -#include "hal/utils/include/utils.h" - -#include "shared-bindings/microcontroller/__init__.h" - -COMPILER_ALIGNED(16) static DmacDescriptor dma_descriptors[DMA_CHANNEL_COUNT]; - -// Don't use these directly. They are used by the DMA engine itself. -COMPILER_ALIGNED(16) static DmacDescriptor write_back_descriptors[DMA_CHANNEL_COUNT]; - -#ifdef SAMD21 -#define FIRST_SERCOM_RX_TRIGSRC 0x01 -#define FIRST_SERCOM_TX_TRIGSRC 0x02 -#endif -#ifdef SAM_D5X_E5X -#define FIRST_SERCOM_RX_TRIGSRC 0x04 -#define FIRST_SERCOM_TX_TRIGSRC 0x05 -#endif - -static bool dma_allocated[DMA_CHANNEL_COUNT]; - -uint8_t dma_allocate_channel(bool audio_channel) { - uint8_t channel; - uint8_t lim = audio_channel ? AUDIO_DMA_CHANNEL_COUNT : DMA_CHANNEL_COUNT; - for (channel = (audio_channel ? 0 : AUDIO_DMA_CHANNEL_COUNT); channel < lim; channel++) { - if (!dma_allocated[channel]) { - dma_allocated[channel] = true; - return channel; - } - } - return channel; // i.e., return failure -} - -void dma_free_channel(uint8_t channel) { - assert(dma_allocated[channel]); - dma_disable_channel(channel); - dma_allocated[channel] = false; -} - -void init_shared_dma(void) { - // Turn on the clocks - #ifdef SAM_D5X_E5X - 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; - - // Non-audio channels will be configured on demand. - for (uint8_t i = 0; i < AUDIO_DMA_CHANNEL_COUNT; i++) { - dma_configure(i, 0, true); - } -} - -// 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. -// DMAs buffer_out -> dest -// DMAs src -> buffer_in -dma_descr_t shared_dma_transfer_start(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) { - dma_descr_t res; - res.progress = 0; - - uint8_t tx_channel = dma_allocate_channel(false); - uint8_t rx_channel = dma_allocate_channel(false); - - if ((tx_channel >= DMA_CHANNEL_COUNT) || (rx_channel >= DMA_CHANNEL_COUNT) || - !dma_channel_free(tx_channel) || - (buffer_in != NULL && !dma_channel_free(rx_channel))) { - res.failure = -1; - return res; - } - - 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 SAM_D5X_E5X - if (peripheral == QSPI) { - // Check input alignment on word boundaries. - if ((((uint32_t) buffer_in) & 0x3) != 0 || - (((uint32_t) buffer_out) & 0x3) != 0) { - res.failure = -3; - return res; - } - beat_size = DMAC_BTCTRL_BEATSIZE_WORD | DMAC_BTCTRL_SRCINC | DMAC_BTCTRL_DSTINC; - beat_length /= 4; - sercom = false; - if (buffer_out != NULL) { - dma_configure(tx_channel, QSPI_DMAC_ID_TX, false); - tx_active = true; - } else { - dma_configure(rx_channel, QSPI_DMAC_ID_RX, false); - rx_active = true; - } - - } else { - #endif - - dma_configure(tx_channel, sercom_index(peripheral) * 2 + FIRST_SERCOM_TX_TRIGSRC, false); - tx_active = true; - if (buffer_in != NULL) { - dma_configure(rx_channel, sercom_index(peripheral) * 2 + FIRST_SERCOM_RX_TRIGSRC, false); - rx_active = true; - } - - #ifdef SAM_D5X_E5X - } - #endif - - // Set up RX first. - if (rx_active) { - DmacDescriptor* rx_descriptor = &dma_descriptors[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 SAM_D5X_E5X - 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. - if (tx_active) { - DmacDescriptor* tx_descriptor = &dma_descriptors[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; - // TODO: test if this operation is necessary or if it's just a waste of time and space - // Section 35.8.7 of the datasheet lists both of these bits as read-only, so this shouldn't do anything - s->INTFLAG.reg = SERCOM_SPI_INTFLAG_RXC | SERCOM_SPI_INTFLAG_DRE; - } - - // Start the RX job first so we don't miss the first byte. The TX job clocks the output. - // Disable interrupts during startup to make sure both RX and TX start at just about the same time. - mp_hal_disable_all_interrupts(); - if (rx_active) { - dma_enable_channel(rx_channel); - } - if (tx_active) { - dma_enable_channel(tx_channel); - } - mp_hal_enable_all_interrupts(); - - if (!sercom) { - if (rx_active) { - DMAC->SWTRIGCTRL.reg |= (1 << rx_channel); - } - } - - #ifdef SAM_D5X_E5X - // Sometimes (silicon bug?) this DMA transfer never starts, and another channel sits with - // CHSTATUS.reg = 0x3 (BUSY | PENDING). On the other hand, this is a - // legitimate state for a DMA channel to be in (apparently), so we can't use that alone as a check. - // Instead, let's look at the ACTIVE flag. When DMA is hung, everything in ACTIVE is zeros. - bool is_okay = false; - for (int i = 0; i < 10 && !is_okay; i++) { - bool complete = true; - if (rx_active) { - if (DMAC->Channel[rx_channel].CHSTATUS.reg & 0x3) - complete = false; - } - if (tx_active) { - if (DMAC->Channel[tx_channel].CHSTATUS.reg & 0x3) - complete = false; - } - is_okay = is_okay || (DMAC->ACTIVE.bit.ABUSY || complete); - } - if (!is_okay) { - for (int i = 0; i < AUDIO_DMA_CHANNEL_COUNT; i++) { - if(DMAC->Channel[i].CHCTRLA.bit.ENABLE) { - DMAC->Channel[i].CHCTRLA.bit.ENABLE = 0; - DMAC->Channel[i].CHCTRLA.bit.ENABLE = 1; - } - } - } - #endif - res.peripheral = peripheral; - res.length = length; - res.rx_channel = rx_channel; - res.tx_channel = tx_channel; - res.rx_active = rx_active; - res.tx_active = tx_active; - res.sercom = sercom; - res.failure = 0; - return res; -} - -bool shared_dma_transfer_finished(dma_descr_t descr) { - if (descr.failure != 0) { - return true; - } - - if (descr.progress < 1 && descr.rx_active) { - if ((dma_transfer_status(descr.rx_channel) & 0x3) == 0) { - return false; - } - descr.progress = 1; - } - if (descr.progress < 2 && descr.tx_active) { - if ((dma_transfer_status(descr.tx_channel) & 0x3) == 0) { - return false; - } - descr.progress = 2; - } - - if (descr.progress < 3 && descr.sercom) { - Sercom* s = (Sercom*) descr.peripheral; - // Wait for the SPI transfer to complete. - if (s->SPI.INTFLAG.bit.TXC == 0) { - return false; - } - descr.progress = 3; - - // This transmit will cause the RX buffer overflow but we're OK with that. - // So, read the garbage and clear the overflow flag. - if (!descr.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; - } - } - - return true; -} - -int shared_dma_transfer_close(dma_descr_t descr) { - dma_free_channel(descr.tx_channel); - dma_free_channel(descr.rx_channel); - - if (descr.failure != 0) { - return descr.failure; - } - - if ((!descr.rx_active || dma_transfer_status(descr.rx_channel) == DMAC_CHINTFLAG_TCMPL) && - (!descr.tx_active || dma_transfer_status(descr.tx_channel) == DMAC_CHINTFLAG_TCMPL)) { - return descr.length; - } - return -2; -} - -// 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. -// 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) { - dma_descr_t descr = shared_dma_transfer_start(peripheral, buffer_out, dest, src, buffer_in, length, tx); - if (descr.failure != 0) { - return descr.failure; - } - while (!shared_dma_transfer_finished(descr)) {} - return shared_dma_transfer_close(descr); -} - -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); -} - -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 SAM_D5X_E5X -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 - -DmacDescriptor* dma_descriptor(uint8_t channel_number) { - return &dma_descriptors[channel_number]; -} - -DmacDescriptor* dma_write_back_descriptor(uint8_t channel_number) { - return &write_back_descriptors[channel_number]; -} +/* + * This file is part of the MicroPython 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. + */ +#include "samd/dma.h" + +#include + +#include "mphalport.h" +#include "py/gc.h" +#include "py/mpstate.h" + +#include "hal/utils/include/utils.h" + +#include "shared-bindings/microcontroller/__init__.h" + +COMPILER_ALIGNED(16) static DmacDescriptor dma_descriptors[DMA_CHANNEL_COUNT]; + +// Don't use these directly. They are used by the DMA engine itself. +COMPILER_ALIGNED(16) static DmacDescriptor write_back_descriptors[DMA_CHANNEL_COUNT]; + +#ifdef SAMD21 +#define FIRST_SERCOM_RX_TRIGSRC 0x01 +#define FIRST_SERCOM_TX_TRIGSRC 0x02 +#endif +#ifdef SAM_D5X_E5X +#define FIRST_SERCOM_RX_TRIGSRC 0x04 +#define FIRST_SERCOM_TX_TRIGSRC 0x05 +#endif + +static bool dma_allocated[DMA_CHANNEL_COUNT]; + +uint8_t dma_allocate_channel(bool audio_channel) { + uint8_t channel; + uint8_t lim = audio_channel ? AUDIO_DMA_CHANNEL_COUNT : DMA_CHANNEL_COUNT; + for (channel = (audio_channel ? 0 : AUDIO_DMA_CHANNEL_COUNT); channel < lim; channel++) { + if (!dma_allocated[channel]) { + dma_allocated[channel] = true; + return channel; + } + } + return channel; // i.e., return failure +} + +void dma_free_channel(uint8_t channel) { + assert(dma_allocated[channel]); + dma_disable_channel(channel); + dma_allocated[channel] = false; +} + +void init_shared_dma(void) { + // Turn on the clocks + #ifdef SAM_D5X_E5X + 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; + + // Non-audio channels will be configured on demand. + for (uint8_t i = 0; i < AUDIO_DMA_CHANNEL_COUNT; i++) { + dma_configure(i, 0, true); + } +} + +// 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. +// DMAs buffer_out -> dest +// DMAs src -> buffer_in +dma_descr_t shared_dma_transfer_start(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) { + dma_descr_t res; + res.progress = 0; + + uint8_t tx_channel = dma_allocate_channel(false); + uint8_t rx_channel = dma_allocate_channel(false); + + if ((tx_channel >= DMA_CHANNEL_COUNT) || (rx_channel >= DMA_CHANNEL_COUNT) || + !dma_channel_free(tx_channel) || + (buffer_in != NULL && !dma_channel_free(rx_channel))) { + res.failure = -1; + return res; + } + + 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 SAM_D5X_E5X + if (peripheral == QSPI) { + // Check input alignment on word boundaries. + if ((((uint32_t) buffer_in) & 0x3) != 0 || + (((uint32_t) buffer_out) & 0x3) != 0) { + res.failure = -3; + return res; + } + beat_size = DMAC_BTCTRL_BEATSIZE_WORD | DMAC_BTCTRL_SRCINC | DMAC_BTCTRL_DSTINC; + beat_length /= 4; + sercom = false; + if (buffer_out != NULL) { + dma_configure(tx_channel, QSPI_DMAC_ID_TX, false); + tx_active = true; + } else { + dma_configure(rx_channel, QSPI_DMAC_ID_RX, false); + rx_active = true; + } + + } else { + #endif + + dma_configure(tx_channel, sercom_index(peripheral) * 2 + FIRST_SERCOM_TX_TRIGSRC, false); + tx_active = true; + if (buffer_in != NULL) { + dma_configure(rx_channel, sercom_index(peripheral) * 2 + FIRST_SERCOM_RX_TRIGSRC, false); + rx_active = true; + } + + #ifdef SAM_D5X_E5X + } + #endif + + // Set up RX first. + if (rx_active) { + DmacDescriptor* rx_descriptor = &dma_descriptors[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 SAM_D5X_E5X + 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. + if (tx_active) { + DmacDescriptor* tx_descriptor = &dma_descriptors[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; + // TODO: test if this operation is necessary or if it's just a waste of time and space + // Section 35.8.7 of the datasheet lists both of these bits as read-only, so this shouldn't do anything + s->INTFLAG.reg = SERCOM_SPI_INTFLAG_RXC | SERCOM_SPI_INTFLAG_DRE; + } + + // Start the RX job first so we don't miss the first byte. The TX job clocks the output. + // Disable interrupts during startup to make sure both RX and TX start at just about the same time. + mp_hal_disable_all_interrupts(); + if (rx_active) { + dma_enable_channel(rx_channel); + } + if (tx_active) { + dma_enable_channel(tx_channel); + } + mp_hal_enable_all_interrupts(); + + if (!sercom) { + if (rx_active) { + DMAC->SWTRIGCTRL.reg |= (1 << rx_channel); + } + } + + #ifdef SAM_D5X_E5X + // Sometimes (silicon bug?) this DMA transfer never starts, and another channel sits with + // CHSTATUS.reg = 0x3 (BUSY | PENDING). On the other hand, this is a + // legitimate state for a DMA channel to be in (apparently), so we can't use that alone as a check. + // Instead, let's look at the ACTIVE flag. When DMA is hung, everything in ACTIVE is zeros. + bool is_okay = false; + for (int i = 0; i < 10 && !is_okay; i++) { + bool complete = true; + if (rx_active) { + if (DMAC->Channel[rx_channel].CHSTATUS.reg & 0x3) + complete = false; + } + if (tx_active) { + if (DMAC->Channel[tx_channel].CHSTATUS.reg & 0x3) + complete = false; + } + is_okay = is_okay || (DMAC->ACTIVE.bit.ABUSY || complete); + } + if (!is_okay) { + for (int i = 0; i < AUDIO_DMA_CHANNEL_COUNT; i++) { + if(DMAC->Channel[i].CHCTRLA.bit.ENABLE) { + DMAC->Channel[i].CHCTRLA.bit.ENABLE = 0; + DMAC->Channel[i].CHCTRLA.bit.ENABLE = 1; + } + } + } + #endif + res.peripheral = peripheral; + res.length = length; + res.rx_channel = rx_channel; + res.tx_channel = tx_channel; + res.rx_active = rx_active; + res.tx_active = tx_active; + res.sercom = sercom; + res.failure = 0; + return res; +} + +bool shared_dma_transfer_finished(dma_descr_t descr) { + if (descr.failure != 0) { + return true; + } + + if (descr.progress < 1 && descr.rx_active) { + if ((dma_transfer_status(descr.rx_channel) & 0x3) == 0) { + return false; + } + descr.progress = 1; + } + if (descr.progress < 2 && descr.tx_active) { + if ((dma_transfer_status(descr.tx_channel) & 0x3) == 0) { + return false; + } + descr.progress = 2; + } + + if (descr.progress < 3 && descr.sercom) { + Sercom* s = (Sercom*) descr.peripheral; + // Wait for the SPI transfer to complete. + if (s->SPI.INTFLAG.bit.TXC == 0) { + return false; + } + descr.progress = 3; + + // This transmit will cause the RX buffer overflow but we're OK with that. + // So, read the garbage and clear the overflow flag. + if (!descr.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; + } + } + + return true; +} + +int shared_dma_transfer_close(dma_descr_t descr) { + dma_free_channel(descr.tx_channel); + dma_free_channel(descr.rx_channel); + + if (descr.failure != 0) { + return descr.failure; + } + + if ((!descr.rx_active || dma_transfer_status(descr.rx_channel) == DMAC_CHINTFLAG_TCMPL) && + (!descr.tx_active || dma_transfer_status(descr.tx_channel) == DMAC_CHINTFLAG_TCMPL)) { + return descr.length; + } + return -2; +} + +// 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. +// 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) { + dma_descr_t descr = shared_dma_transfer_start(peripheral, buffer_out, dest, src, buffer_in, length, tx); + if (descr.failure != 0) { + return descr.failure; + } + while (!shared_dma_transfer_finished(descr)) {} + return shared_dma_transfer_close(descr); +} + +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); +} + +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 SAM_D5X_E5X +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 + +DmacDescriptor* dma_descriptor(uint8_t channel_number) { + return &dma_descriptors[channel_number]; +} + +DmacDescriptor* dma_write_back_descriptor(uint8_t channel_number) { + return &write_back_descriptors[channel_number]; +} diff --git a/samd/dma.h b/samd/dma.h index 883143d..ee95862 100644 --- a/samd/dma.h +++ b/samd/dma.h @@ -1,87 +1,87 @@ -/* - * This file is part of the MicroPython 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_PERIPHERALS_DMA_H -#define MICROPY_INCLUDED_ATMEL_SAMD_PERIPHERALS_DMA_H - -#include -#include - -#include "include/sam.h" - -// We allocate 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. -#define AUDIO_DMA_CHANNEL_COUNT 4 -#define DMA_CHANNEL_COUNT 32 - -uint8_t dma_allocate_channel(bool audio_channel); -void dma_free_channel(uint8_t channel); - -void init_shared_dma(void); - -#ifdef SAM_D5X_E5X -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 - -uint8_t sercom_index(Sercom* sercom); - -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); - -typedef struct { - void* peripheral; - uint32_t length; - uint8_t progress; - uint8_t rx_channel; - uint8_t tx_channel; - bool rx_active; - bool tx_active; - bool sercom; - int8_t failure; -} dma_descr_t; - -dma_descr_t shared_dma_transfer_start(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); -bool shared_dma_transfer_finished(dma_descr_t descr); -int shared_dma_transfer_close(dma_descr_t descr); - -void dma_configure(uint8_t channel_number, uint8_t trigsrc, bool output_event); -void dma_enable_channel(uint8_t channel_number); -void dma_disable_channel(uint8_t channel_number); -void dma_suspend_channel(uint8_t channel_number); -void dma_resume_channel(uint8_t channel_number); -bool dma_channel_free(uint8_t channel_number); -bool dma_channel_enabled(uint8_t channel_number); -uint8_t dma_transfer_status(uint8_t channel_number); -DmacDescriptor* dma_descriptor(uint8_t channel_number); -DmacDescriptor* dma_write_back_descriptor(uint8_t channel_number); - -#endif // MICROPY_INCLUDED_ATMEL_SAMD_PERIPHERALS_DMA_H +/* + * This file is part of the MicroPython 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_PERIPHERALS_DMA_H +#define MICROPY_INCLUDED_ATMEL_SAMD_PERIPHERALS_DMA_H + +#include +#include + +#include "include/sam.h" + +// We allocate 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. +#define AUDIO_DMA_CHANNEL_COUNT 4 +#define DMA_CHANNEL_COUNT 32 + +uint8_t dma_allocate_channel(bool audio_channel); +void dma_free_channel(uint8_t channel); + +void init_shared_dma(void); + +#ifdef SAM_D5X_E5X +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 + +uint8_t sercom_index(Sercom* sercom); + +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); + +typedef struct { + void* peripheral; + uint32_t length; + uint8_t progress; + uint8_t rx_channel; + uint8_t tx_channel; + bool rx_active; + bool tx_active; + bool sercom; + int8_t failure; +} dma_descr_t; + +dma_descr_t shared_dma_transfer_start(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); +bool shared_dma_transfer_finished(dma_descr_t descr); +int shared_dma_transfer_close(dma_descr_t descr); + +void dma_configure(uint8_t channel_number, uint8_t trigsrc, bool output_event); +void dma_enable_channel(uint8_t channel_number); +void dma_disable_channel(uint8_t channel_number); +void dma_suspend_channel(uint8_t channel_number); +void dma_resume_channel(uint8_t channel_number); +bool dma_channel_free(uint8_t channel_number); +bool dma_channel_enabled(uint8_t channel_number); +uint8_t dma_transfer_status(uint8_t channel_number); +DmacDescriptor* dma_descriptor(uint8_t channel_number); +DmacDescriptor* dma_write_back_descriptor(uint8_t channel_number); + +#endif // MICROPY_INCLUDED_ATMEL_SAMD_PERIPHERALS_DMA_H From fd82c8b829b8d72eb0e45de16ac9db43a77ac764 Mon Sep 17 00:00:00 2001 From: Randall Scharpf Date: Sun, 19 May 2024 22:54:12 -0700 Subject: [PATCH 4/4] fix line endings --- samd/dma.c | 694 ++++++++++++++++++++++++++--------------------------- samd/dma.h | 174 +++++++------- 2 files changed, 434 insertions(+), 434 deletions(-) diff --git a/samd/dma.c b/samd/dma.c index dda251a..4925942 100644 --- a/samd/dma.c +++ b/samd/dma.c @@ -1,347 +1,347 @@ -/* - * This file is part of the MicroPython 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. - */ -#include "samd/dma.h" - -#include - -#include "mphalport.h" -#include "py/gc.h" -#include "py/mpstate.h" - -#include "hal/utils/include/utils.h" - -#include "shared-bindings/microcontroller/__init__.h" - -COMPILER_ALIGNED(16) static DmacDescriptor dma_descriptors[DMA_CHANNEL_COUNT]; - -// Don't use these directly. They are used by the DMA engine itself. -COMPILER_ALIGNED(16) static DmacDescriptor write_back_descriptors[DMA_CHANNEL_COUNT]; - -#ifdef SAMD21 -#define FIRST_SERCOM_RX_TRIGSRC 0x01 -#define FIRST_SERCOM_TX_TRIGSRC 0x02 -#endif -#ifdef SAM_D5X_E5X -#define FIRST_SERCOM_RX_TRIGSRC 0x04 -#define FIRST_SERCOM_TX_TRIGSRC 0x05 -#endif - -static bool dma_allocated[DMA_CHANNEL_COUNT]; - -uint8_t dma_allocate_channel(bool audio_channel) { - uint8_t channel; - uint8_t lim = audio_channel ? AUDIO_DMA_CHANNEL_COUNT : DMA_CHANNEL_COUNT; - for (channel = (audio_channel ? 0 : AUDIO_DMA_CHANNEL_COUNT); channel < lim; channel++) { - if (!dma_allocated[channel]) { - dma_allocated[channel] = true; - return channel; - } - } - return channel; // i.e., return failure -} - -void dma_free_channel(uint8_t channel) { - assert(dma_allocated[channel]); - dma_disable_channel(channel); - dma_allocated[channel] = false; -} - -void init_shared_dma(void) { - // Turn on the clocks - #ifdef SAM_D5X_E5X - 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; - - // Non-audio channels will be configured on demand. - for (uint8_t i = 0; i < AUDIO_DMA_CHANNEL_COUNT; i++) { - dma_configure(i, 0, true); - } -} - -// 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. -// DMAs buffer_out -> dest -// DMAs src -> buffer_in -dma_descr_t shared_dma_transfer_start(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) { - dma_descr_t res; - res.progress = 0; - - uint8_t tx_channel = dma_allocate_channel(false); - uint8_t rx_channel = dma_allocate_channel(false); - - if ((tx_channel >= DMA_CHANNEL_COUNT) || (rx_channel >= DMA_CHANNEL_COUNT) || - !dma_channel_free(tx_channel) || - (buffer_in != NULL && !dma_channel_free(rx_channel))) { - res.failure = -1; - return res; - } - - 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 SAM_D5X_E5X - if (peripheral == QSPI) { - // Check input alignment on word boundaries. - if ((((uint32_t) buffer_in) & 0x3) != 0 || - (((uint32_t) buffer_out) & 0x3) != 0) { - res.failure = -3; - return res; - } - beat_size = DMAC_BTCTRL_BEATSIZE_WORD | DMAC_BTCTRL_SRCINC | DMAC_BTCTRL_DSTINC; - beat_length /= 4; - sercom = false; - if (buffer_out != NULL) { - dma_configure(tx_channel, QSPI_DMAC_ID_TX, false); - tx_active = true; - } else { - dma_configure(rx_channel, QSPI_DMAC_ID_RX, false); - rx_active = true; - } - - } else { - #endif - - dma_configure(tx_channel, sercom_index(peripheral) * 2 + FIRST_SERCOM_TX_TRIGSRC, false); - tx_active = true; - if (buffer_in != NULL) { - dma_configure(rx_channel, sercom_index(peripheral) * 2 + FIRST_SERCOM_RX_TRIGSRC, false); - rx_active = true; - } - - #ifdef SAM_D5X_E5X - } - #endif - - // Set up RX first. - if (rx_active) { - DmacDescriptor* rx_descriptor = &dma_descriptors[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 SAM_D5X_E5X - 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. - if (tx_active) { - DmacDescriptor* tx_descriptor = &dma_descriptors[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; - // TODO: test if this operation is necessary or if it's just a waste of time and space - // Section 35.8.7 of the datasheet lists both of these bits as read-only, so this shouldn't do anything - s->INTFLAG.reg = SERCOM_SPI_INTFLAG_RXC | SERCOM_SPI_INTFLAG_DRE; - } - - // Start the RX job first so we don't miss the first byte. The TX job clocks the output. - // Disable interrupts during startup to make sure both RX and TX start at just about the same time. - mp_hal_disable_all_interrupts(); - if (rx_active) { - dma_enable_channel(rx_channel); - } - if (tx_active) { - dma_enable_channel(tx_channel); - } - mp_hal_enable_all_interrupts(); - - if (!sercom) { - if (rx_active) { - DMAC->SWTRIGCTRL.reg |= (1 << rx_channel); - } - } - - #ifdef SAM_D5X_E5X - // Sometimes (silicon bug?) this DMA transfer never starts, and another channel sits with - // CHSTATUS.reg = 0x3 (BUSY | PENDING). On the other hand, this is a - // legitimate state for a DMA channel to be in (apparently), so we can't use that alone as a check. - // Instead, let's look at the ACTIVE flag. When DMA is hung, everything in ACTIVE is zeros. - bool is_okay = false; - for (int i = 0; i < 10 && !is_okay; i++) { - bool complete = true; - if (rx_active) { - if (DMAC->Channel[rx_channel].CHSTATUS.reg & 0x3) - complete = false; - } - if (tx_active) { - if (DMAC->Channel[tx_channel].CHSTATUS.reg & 0x3) - complete = false; - } - is_okay = is_okay || (DMAC->ACTIVE.bit.ABUSY || complete); - } - if (!is_okay) { - for (int i = 0; i < AUDIO_DMA_CHANNEL_COUNT; i++) { - if(DMAC->Channel[i].CHCTRLA.bit.ENABLE) { - DMAC->Channel[i].CHCTRLA.bit.ENABLE = 0; - DMAC->Channel[i].CHCTRLA.bit.ENABLE = 1; - } - } - } - #endif - res.peripheral = peripheral; - res.length = length; - res.rx_channel = rx_channel; - res.tx_channel = tx_channel; - res.rx_active = rx_active; - res.tx_active = tx_active; - res.sercom = sercom; - res.failure = 0; - return res; -} - -bool shared_dma_transfer_finished(dma_descr_t descr) { - if (descr.failure != 0) { - return true; - } - - if (descr.progress < 1 && descr.rx_active) { - if ((dma_transfer_status(descr.rx_channel) & 0x3) == 0) { - return false; - } - descr.progress = 1; - } - if (descr.progress < 2 && descr.tx_active) { - if ((dma_transfer_status(descr.tx_channel) & 0x3) == 0) { - return false; - } - descr.progress = 2; - } - - if (descr.progress < 3 && descr.sercom) { - Sercom* s = (Sercom*) descr.peripheral; - // Wait for the SPI transfer to complete. - if (s->SPI.INTFLAG.bit.TXC == 0) { - return false; - } - descr.progress = 3; - - // This transmit will cause the RX buffer overflow but we're OK with that. - // So, read the garbage and clear the overflow flag. - if (!descr.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; - } - } - - return true; -} - -int shared_dma_transfer_close(dma_descr_t descr) { - dma_free_channel(descr.tx_channel); - dma_free_channel(descr.rx_channel); - - if (descr.failure != 0) { - return descr.failure; - } - - if ((!descr.rx_active || dma_transfer_status(descr.rx_channel) == DMAC_CHINTFLAG_TCMPL) && - (!descr.tx_active || dma_transfer_status(descr.tx_channel) == DMAC_CHINTFLAG_TCMPL)) { - return descr.length; - } - return -2; -} - -// 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. -// 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) { - dma_descr_t descr = shared_dma_transfer_start(peripheral, buffer_out, dest, src, buffer_in, length, tx); - if (descr.failure != 0) { - return descr.failure; - } - while (!shared_dma_transfer_finished(descr)) {} - return shared_dma_transfer_close(descr); -} - -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); -} - -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 SAM_D5X_E5X -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 - -DmacDescriptor* dma_descriptor(uint8_t channel_number) { - return &dma_descriptors[channel_number]; -} - -DmacDescriptor* dma_write_back_descriptor(uint8_t channel_number) { - return &write_back_descriptors[channel_number]; -} +/* + * This file is part of the MicroPython 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. + */ +#include "samd/dma.h" + +#include + +#include "mphalport.h" +#include "py/gc.h" +#include "py/mpstate.h" + +#include "hal/utils/include/utils.h" + +#include "shared-bindings/microcontroller/__init__.h" + +COMPILER_ALIGNED(16) static DmacDescriptor dma_descriptors[DMA_CHANNEL_COUNT]; + +// Don't use these directly. They are used by the DMA engine itself. +COMPILER_ALIGNED(16) static DmacDescriptor write_back_descriptors[DMA_CHANNEL_COUNT]; + +#ifdef SAMD21 +#define FIRST_SERCOM_RX_TRIGSRC 0x01 +#define FIRST_SERCOM_TX_TRIGSRC 0x02 +#endif +#ifdef SAM_D5X_E5X +#define FIRST_SERCOM_RX_TRIGSRC 0x04 +#define FIRST_SERCOM_TX_TRIGSRC 0x05 +#endif + +static bool dma_allocated[DMA_CHANNEL_COUNT]; + +uint8_t dma_allocate_channel(bool audio_channel) { + uint8_t channel; + uint8_t lim = audio_channel ? AUDIO_DMA_CHANNEL_COUNT : DMA_CHANNEL_COUNT; + for (channel = (audio_channel ? 0 : AUDIO_DMA_CHANNEL_COUNT); channel < lim; channel++) { + if (!dma_allocated[channel]) { + dma_allocated[channel] = true; + return channel; + } + } + return channel; // i.e., return failure +} + +void dma_free_channel(uint8_t channel) { + assert(dma_allocated[channel]); + dma_disable_channel(channel); + dma_allocated[channel] = false; +} + +void init_shared_dma(void) { + // Turn on the clocks + #ifdef SAM_D5X_E5X + 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; + + // Non-audio channels will be configured on demand. + for (uint8_t i = 0; i < AUDIO_DMA_CHANNEL_COUNT; i++) { + dma_configure(i, 0, true); + } +} + +// 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. +// DMAs buffer_out -> dest +// DMAs src -> buffer_in +dma_descr_t shared_dma_transfer_start(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) { + dma_descr_t res; + res.progress = 0; + + uint8_t tx_channel = dma_allocate_channel(false); + uint8_t rx_channel = dma_allocate_channel(false); + + if ((tx_channel >= DMA_CHANNEL_COUNT) || (rx_channel >= DMA_CHANNEL_COUNT) || + !dma_channel_free(tx_channel) || + (buffer_in != NULL && !dma_channel_free(rx_channel))) { + res.failure = -1; + return res; + } + + 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 SAM_D5X_E5X + if (peripheral == QSPI) { + // Check input alignment on word boundaries. + if ((((uint32_t) buffer_in) & 0x3) != 0 || + (((uint32_t) buffer_out) & 0x3) != 0) { + res.failure = -3; + return res; + } + beat_size = DMAC_BTCTRL_BEATSIZE_WORD | DMAC_BTCTRL_SRCINC | DMAC_BTCTRL_DSTINC; + beat_length /= 4; + sercom = false; + if (buffer_out != NULL) { + dma_configure(tx_channel, QSPI_DMAC_ID_TX, false); + tx_active = true; + } else { + dma_configure(rx_channel, QSPI_DMAC_ID_RX, false); + rx_active = true; + } + + } else { + #endif + + dma_configure(tx_channel, sercom_index(peripheral) * 2 + FIRST_SERCOM_TX_TRIGSRC, false); + tx_active = true; + if (buffer_in != NULL) { + dma_configure(rx_channel, sercom_index(peripheral) * 2 + FIRST_SERCOM_RX_TRIGSRC, false); + rx_active = true; + } + + #ifdef SAM_D5X_E5X + } + #endif + + // Set up RX first. + if (rx_active) { + DmacDescriptor* rx_descriptor = &dma_descriptors[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 SAM_D5X_E5X + 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. + if (tx_active) { + DmacDescriptor* tx_descriptor = &dma_descriptors[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; + // TODO: test if this operation is necessary or if it's just a waste of time and space + // Section 35.8.7 of the datasheet lists both of these bits as read-only, so this shouldn't do anything + s->INTFLAG.reg = SERCOM_SPI_INTFLAG_RXC | SERCOM_SPI_INTFLAG_DRE; + } + + // Start the RX job first so we don't miss the first byte. The TX job clocks the output. + // Disable interrupts during startup to make sure both RX and TX start at just about the same time. + mp_hal_disable_all_interrupts(); + if (rx_active) { + dma_enable_channel(rx_channel); + } + if (tx_active) { + dma_enable_channel(tx_channel); + } + mp_hal_enable_all_interrupts(); + + if (!sercom) { + if (rx_active) { + DMAC->SWTRIGCTRL.reg |= (1 << rx_channel); + } + } + + #ifdef SAM_D5X_E5X + // Sometimes (silicon bug?) this DMA transfer never starts, and another channel sits with + // CHSTATUS.reg = 0x3 (BUSY | PENDING). On the other hand, this is a + // legitimate state for a DMA channel to be in (apparently), so we can't use that alone as a check. + // Instead, let's look at the ACTIVE flag. When DMA is hung, everything in ACTIVE is zeros. + bool is_okay = false; + for (int i = 0; i < 10 && !is_okay; i++) { + bool complete = true; + if (rx_active) { + if (DMAC->Channel[rx_channel].CHSTATUS.reg & 0x3) + complete = false; + } + if (tx_active) { + if (DMAC->Channel[tx_channel].CHSTATUS.reg & 0x3) + complete = false; + } + is_okay = is_okay || (DMAC->ACTIVE.bit.ABUSY || complete); + } + if (!is_okay) { + for (int i = 0; i < AUDIO_DMA_CHANNEL_COUNT; i++) { + if(DMAC->Channel[i].CHCTRLA.bit.ENABLE) { + DMAC->Channel[i].CHCTRLA.bit.ENABLE = 0; + DMAC->Channel[i].CHCTRLA.bit.ENABLE = 1; + } + } + } + #endif + res.peripheral = peripheral; + res.length = length; + res.rx_channel = rx_channel; + res.tx_channel = tx_channel; + res.rx_active = rx_active; + res.tx_active = tx_active; + res.sercom = sercom; + res.failure = 0; + return res; +} + +bool shared_dma_transfer_finished(dma_descr_t descr) { + if (descr.failure != 0) { + return true; + } + + if (descr.progress < 1 && descr.rx_active) { + if ((dma_transfer_status(descr.rx_channel) & 0x3) == 0) { + return false; + } + descr.progress = 1; + } + if (descr.progress < 2 && descr.tx_active) { + if ((dma_transfer_status(descr.tx_channel) & 0x3) == 0) { + return false; + } + descr.progress = 2; + } + + if (descr.progress < 3 && descr.sercom) { + Sercom* s = (Sercom*) descr.peripheral; + // Wait for the SPI transfer to complete. + if (s->SPI.INTFLAG.bit.TXC == 0) { + return false; + } + descr.progress = 3; + + // This transmit will cause the RX buffer overflow but we're OK with that. + // So, read the garbage and clear the overflow flag. + if (!descr.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; + } + } + + return true; +} + +int shared_dma_transfer_close(dma_descr_t descr) { + dma_free_channel(descr.tx_channel); + dma_free_channel(descr.rx_channel); + + if (descr.failure != 0) { + return descr.failure; + } + + if ((!descr.rx_active || dma_transfer_status(descr.rx_channel) == DMAC_CHINTFLAG_TCMPL) && + (!descr.tx_active || dma_transfer_status(descr.tx_channel) == DMAC_CHINTFLAG_TCMPL)) { + return descr.length; + } + return -2; +} + +// 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. +// 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) { + dma_descr_t descr = shared_dma_transfer_start(peripheral, buffer_out, dest, src, buffer_in, length, tx); + if (descr.failure != 0) { + return descr.failure; + } + while (!shared_dma_transfer_finished(descr)) {} + return shared_dma_transfer_close(descr); +} + +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); +} + +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 SAM_D5X_E5X +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 + +DmacDescriptor* dma_descriptor(uint8_t channel_number) { + return &dma_descriptors[channel_number]; +} + +DmacDescriptor* dma_write_back_descriptor(uint8_t channel_number) { + return &write_back_descriptors[channel_number]; +} diff --git a/samd/dma.h b/samd/dma.h index ee95862..883143d 100644 --- a/samd/dma.h +++ b/samd/dma.h @@ -1,87 +1,87 @@ -/* - * This file is part of the MicroPython 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_PERIPHERALS_DMA_H -#define MICROPY_INCLUDED_ATMEL_SAMD_PERIPHERALS_DMA_H - -#include -#include - -#include "include/sam.h" - -// We allocate 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. -#define AUDIO_DMA_CHANNEL_COUNT 4 -#define DMA_CHANNEL_COUNT 32 - -uint8_t dma_allocate_channel(bool audio_channel); -void dma_free_channel(uint8_t channel); - -void init_shared_dma(void); - -#ifdef SAM_D5X_E5X -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 - -uint8_t sercom_index(Sercom* sercom); - -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); - -typedef struct { - void* peripheral; - uint32_t length; - uint8_t progress; - uint8_t rx_channel; - uint8_t tx_channel; - bool rx_active; - bool tx_active; - bool sercom; - int8_t failure; -} dma_descr_t; - -dma_descr_t shared_dma_transfer_start(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); -bool shared_dma_transfer_finished(dma_descr_t descr); -int shared_dma_transfer_close(dma_descr_t descr); - -void dma_configure(uint8_t channel_number, uint8_t trigsrc, bool output_event); -void dma_enable_channel(uint8_t channel_number); -void dma_disable_channel(uint8_t channel_number); -void dma_suspend_channel(uint8_t channel_number); -void dma_resume_channel(uint8_t channel_number); -bool dma_channel_free(uint8_t channel_number); -bool dma_channel_enabled(uint8_t channel_number); -uint8_t dma_transfer_status(uint8_t channel_number); -DmacDescriptor* dma_descriptor(uint8_t channel_number); -DmacDescriptor* dma_write_back_descriptor(uint8_t channel_number); - -#endif // MICROPY_INCLUDED_ATMEL_SAMD_PERIPHERALS_DMA_H +/* + * This file is part of the MicroPython 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_PERIPHERALS_DMA_H +#define MICROPY_INCLUDED_ATMEL_SAMD_PERIPHERALS_DMA_H + +#include +#include + +#include "include/sam.h" + +// We allocate 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. +#define AUDIO_DMA_CHANNEL_COUNT 4 +#define DMA_CHANNEL_COUNT 32 + +uint8_t dma_allocate_channel(bool audio_channel); +void dma_free_channel(uint8_t channel); + +void init_shared_dma(void); + +#ifdef SAM_D5X_E5X +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 + +uint8_t sercom_index(Sercom* sercom); + +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); + +typedef struct { + void* peripheral; + uint32_t length; + uint8_t progress; + uint8_t rx_channel; + uint8_t tx_channel; + bool rx_active; + bool tx_active; + bool sercom; + int8_t failure; +} dma_descr_t; + +dma_descr_t shared_dma_transfer_start(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); +bool shared_dma_transfer_finished(dma_descr_t descr); +int shared_dma_transfer_close(dma_descr_t descr); + +void dma_configure(uint8_t channel_number, uint8_t trigsrc, bool output_event); +void dma_enable_channel(uint8_t channel_number); +void dma_disable_channel(uint8_t channel_number); +void dma_suspend_channel(uint8_t channel_number); +void dma_resume_channel(uint8_t channel_number); +bool dma_channel_free(uint8_t channel_number); +bool dma_channel_enabled(uint8_t channel_number); +uint8_t dma_transfer_status(uint8_t channel_number); +DmacDescriptor* dma_descriptor(uint8_t channel_number); +DmacDescriptor* dma_write_back_descriptor(uint8_t channel_number); + +#endif // MICROPY_INCLUDED_ATMEL_SAMD_PERIPHERALS_DMA_H