Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add async transfer #46

Merged
merged 5 commits into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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