Skip to content

Commit

Permalink
Merge pull request #46 from Bruin-Spacecraft-Group/main
Browse files Browse the repository at this point in the history
add async transfer
  • Loading branch information
tannewt authored Nov 25, 2024
2 parents d31fca2 + fd82c8b commit d321022
Show file tree
Hide file tree
Showing 2 changed files with 132 additions and 32 deletions.
136 changes: 108 additions & 28 deletions samd/dma.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand All @@ -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;
}

Expand All @@ -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);
Expand All @@ -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;

Expand All @@ -155,23 +184,25 @@ 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;
}

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

Expand All @@ -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);
Expand All @@ -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;
}
Expand All @@ -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) {
Expand Down
28 changes: 24 additions & 4 deletions samd/dma.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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);
Expand Down

0 comments on commit d321022

Please sign in to comment.