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