1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2025-12-25 14:33:52 +01:00

cpu/sam0_common: Make SPI peripheral DMA compatible

This commit is contained in:
Koen Zandberg 2020-06-11 11:07:06 +02:00
parent 7bb3aa6560
commit 60f4502e6c
No known key found for this signature in database
GPG Key ID: 0895A893E6D2985B
2 changed files with 140 additions and 9 deletions

View File

@ -39,8 +39,10 @@ extern "C" {
*/
#define PERIPH_SPI_NEEDS_INIT_CS
#define PERIPH_SPI_NEEDS_TRANSFER_BYTE
#ifndef MODULE_PERIPH_DMA
#define PERIPH_SPI_NEEDS_TRANSFER_REG
#define PERIPH_SPI_NEEDS_TRANSFER_REGS
#endif
/** @} */
/**
@ -275,6 +277,10 @@ typedef struct {
spi_misopad_t miso_pad; /**< pad to use for MISO line */
spi_mosipad_t mosi_pad; /**< pad to use for MOSI and CLK line */
uint8_t gclk_src; /**< GCLK source which supplys SERCOM */
#ifdef MODULE_PERIPH_DMA
uint8_t tx_trigger; /**< DMA trigger */
uint8_t rx_trigger; /**< DMA trigger */
#endif
} spi_conf_t;
/** @} */

View File

@ -29,6 +29,7 @@
#include "mutex.h"
#include "assert.h"
#include "periph/spi.h"
#include "pm_layered.h"
#define ENABLE_DEBUG (0)
#include "debug.h"
@ -38,6 +39,18 @@
*/
static mutex_t locks[SPI_NUMOF];
#ifdef MODULE_PERIPH_DMA
struct dma_state {
dma_t tx_dma;
dma_t rx_dma;
};
static struct dma_state _dma_state[SPI_NUMOF];
static DmacDescriptor DMA_DESCRIPTOR_ATTRS tx_desc[SPI_NUMOF];
static DmacDescriptor DMA_DESCRIPTOR_ATTRS rx_desc[SPI_NUMOF];
#endif
/**
* @brief Shortcut for accessing the used SPI SERCOM device
*/
@ -56,6 +69,17 @@ static inline void poweroff(spi_t bus)
sercom_clk_dis(dev(bus));
}
static inline bool _use_dma(spi_t bus)
{
#ifdef MODULE_PERIPH_DMA
return (spi_config[bus].tx_trigger != DMA_TRIGGER_DISABLED) &&
(spi_config[bus].rx_trigger != DMA_TRIGGER_DISABLED);
#else
(void)bus;
return false;
#endif
}
void spi_init(spi_t bus)
{
/* make sure given bus is good */
@ -82,8 +106,23 @@ void spi_init(spi_t bus)
* no synchronization needed, as SERCOM device is not enabled */
dev(bus)->CTRLB.reg = (SERCOM_SPI_CTRLB_CHSIZE(0) | SERCOM_SPI_CTRLB_RXEN);
#ifdef MODULE_PERIPH_DMA
if (_use_dma(bus)) {
_dma_state[bus].rx_dma = dma_acquire_channel();
_dma_state[bus].tx_dma = dma_acquire_channel();
dma_setup(_dma_state[bus].tx_dma,
spi_config[bus].tx_trigger, 0, false);
dma_setup(_dma_state[bus].rx_dma,
spi_config[bus].rx_trigger, 1, true);
dma_prepare(_dma_state[bus].rx_dma, DMAC_BTCTRL_BEATSIZE_BYTE_Val,
(void*)&dev(bus)->DATA.reg, NULL, 1, 0);
dma_prepare(_dma_state[bus].tx_dma, DMAC_BTCTRL_BEATSIZE_BYTE_Val,
NULL, (void*)&dev(bus)->DATA.reg, 0, 0);
}
#endif
/* put device back to sleep */
poweroff(bus);
}
void spi_init_pins(spi_t bus)
@ -164,21 +203,13 @@ void spi_release(spi_t bus)
mutex_unlock(&locks[bus]);
}
void spi_transfer_bytes(spi_t bus, spi_cs_t cs, bool cont,
const void *out, void *in, size_t len)
static void _blocking_transfer(spi_t bus, const void *out, void *in, size_t len)
{
const uint8_t *out_buf = out;
uint8_t *in_buf = in;
assert(out || in);
if (cs != SPI_CS_UNDEF) {
gpio_clear((gpio_t)cs);
}
for (int i = 0; i < (int)len; i++) {
uint8_t tmp = (out_buf) ? out_buf[i] : 0;
while (!(dev(bus)->INTFLAG.reg & SERCOM_SPI_INTFLAG_DRE)) {}
dev(bus)->DATA.reg = tmp;
while (!(dev(bus)->INTFLAG.reg & SERCOM_SPI_INTFLAG_RXC)) {}
tmp = (uint8_t)dev(bus)->DATA.reg;
@ -186,6 +217,100 @@ void spi_transfer_bytes(spi_t bus, spi_cs_t cs, bool cont,
in_buf[i] = tmp;
}
}
}
#ifdef MODULE_PERIPH_DMA
static void _dma_execute(spi_t bus)
{
#if defined(CPU_FAM_SAMD21)
pm_block(SAMD21_PM_IDLE_1);
#endif
dma_start(_dma_state[bus].rx_dma);
dma_start(_dma_state[bus].tx_dma);
dma_wait(_dma_state[bus].rx_dma);
#if defined(CPU_FAM_SAMD21)
pm_unblock(SAMD21_PM_IDLE_1);
#endif
}
static void _dma_transfer(spi_t bus, const uint8_t *out, uint8_t *in,
size_t len)
{
uint8_t tmp = 0;
const uint8_t *out_addr = out ? out + len : &tmp;
uint8_t *in_addr = in ? in + len : &tmp;
dma_prepare_dst(_dma_state[bus].rx_dma, in_addr, len, in ? true : false);
dma_prepare_src(_dma_state[bus].tx_dma, out_addr, len, out ? true : false);
_dma_execute(bus);
}
static void _dma_transfer_regs(spi_t bus, uint8_t reg, const uint8_t *out,
uint8_t *in, size_t len)
{
uint8_t tmp;
const uint8_t *out_addr = out ? out + len : &tmp;
uint8_t *in_addr = in ? in + len : &tmp;
dma_prepare_dst(_dma_state[bus].rx_dma, &tmp, 1, false);
dma_prepare_src(_dma_state[bus].tx_dma, &reg, 1, false);
dma_append_dst(_dma_state[bus].rx_dma, &rx_desc[bus], in_addr,
len, in ? true : false);
dma_append_src(_dma_state[bus].tx_dma, &tx_desc[bus], out_addr,
len, out ? true : false);
_dma_execute(bus);
}
void spi_transfer_regs(spi_t bus, spi_cs_t cs,
uint8_t reg, const void *out, void *in, size_t len)
{
if (cs != SPI_CS_UNDEF) {
gpio_clear((gpio_t)cs);
}
if (_use_dma(bus)) {
/* The DMA promises not to modify the const out data */
_dma_transfer_regs(bus, reg, out, in, len);
}
else {
_blocking_transfer(bus, &reg, NULL, 1);
_blocking_transfer(bus, out, in, len);
}
if (cs != SPI_CS_UNDEF) {
gpio_set((gpio_t)cs);
}
}
uint8_t spi_transfer_reg(spi_t bus, spi_cs_t cs, uint8_t reg, uint8_t out)
{
uint8_t res;
spi_transfer_regs(bus, cs, reg, &out, &res, 1);
return res;
}
#endif /* MODULE_PERIPH_DMA */
void spi_transfer_bytes(spi_t bus, spi_cs_t cs, bool cont,
const void *out, void *in, size_t len)
{
assert(out || in);
if (cs != SPI_CS_UNDEF) {
gpio_clear((gpio_t)cs);
}
if (_use_dma(bus)) {
#ifdef MODULE_PERIPH_DMA
/* The DMA promises not to modify the const out data */
_dma_transfer(bus, out, in, len);
#endif
}
else {
_blocking_transfer(bus, out, in, len);
}
if ((!cont) && (cs != SPI_CS_UNDEF)) {
gpio_set((gpio_t)cs);