1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00

cpu/sam0_common: SPI: add support for QSPI in SPI mode

We can use the QSPI peripheral as an additional (non-Quad) SPI peripheral.
This commit is contained in:
Benjamin Valentin 2020-11-01 22:50:50 +01:00
parent 94c7f40b9c
commit 8f72212eb0
6 changed files with 229 additions and 72 deletions

View File

@ -383,7 +383,7 @@ typedef enum {
* @brief SPI device configuration
*/
typedef struct {
SercomSpi *dev; /**< pointer to the used SPI device */
void *dev; /**< pointer to the used SPI device */
gpio_t miso_pin; /**< used MISO pin */
gpio_t mosi_pin; /**< used MOSI pin */
gpio_t clk_pin; /**< used CLK pin */

View File

@ -21,6 +21,7 @@
* @author Hauke Petersen <hauke.petersen@fu-berlin.de>
* @author Joakim Nohlgård <joakim.nohlgard@eistec.se>
* @author Kaspar Schleiser <kaspar@schleiser.de>
* @author Benjamin Valentin <benjamin.valentin@ml-pa.com>
*
* @}
*/
@ -56,20 +57,48 @@ static DmacDescriptor DMA_DESCRIPTOR_ATTRS rx_desc[SPI_NUMOF];
*/
static inline SercomSpi *dev(spi_t bus)
{
return spi_config[bus].dev;
return (SercomSpi *)spi_config[bus].dev;
}
static inline bool _is_qspi(spi_t bus)
{
#ifdef MODULE_PERIPH_SPI_ON_QSPI
return (void*)spi_config[bus].dev == (void*)QSPI;
#else
(void)bus;
return false;
#endif
}
static inline void _qspi_clk(unsigned on)
{
#ifdef QSPI
/* enable/disable QSPI clock */
MCLK->APBCMASK.bit.QSPI_ = on;
#else
(void)on;
#endif
}
static inline void poweron(spi_t bus)
{
sercom_clk_en(dev(bus));
if (_is_qspi(bus)) {
_qspi_clk(1);
} else {
sercom_clk_en(dev(bus));
}
}
static inline void poweroff(spi_t bus)
{
sercom_clk_dis(dev(bus));
if (_is_qspi(bus)) {
_qspi_clk(0);
} else {
sercom_clk_dis(dev(bus));
}
}
static void _reset(SercomSpi *dev)
static inline void _reset(SercomSpi *dev)
{
dev->CTRLA.reg |= SERCOM_SPI_CTRLA_SWRST;
while (dev->CTRLA.reg & SERCOM_SPI_CTRLA_SWRST) {}
@ -114,7 +143,7 @@ static inline bool _use_dma(spi_t bus)
#endif
}
static inline void _init_dma(spi_t bus, volatile void *reg_rx, volatile void *reg_tx)
static inline void _init_dma(spi_t bus, const volatile void *reg_rx, volatile void *reg_tx)
{
if (!_use_dma(bus)) {
return;
@ -130,15 +159,170 @@ static inline void _init_dma(spi_t bus, volatile void *reg_rx, volatile void *re
spi_config[bus].rx_trigger, 1, true);
dma_prepare(_dma_state[bus].rx_dma, DMAC_BTCTRL_BEATSIZE_BYTE_Val,
reg_rx, NULL, 1, 0);
(void*)reg_rx, NULL, 1, 0);
dma_prepare(_dma_state[bus].tx_dma, DMAC_BTCTRL_BEATSIZE_BYTE_Val,
NULL, reg_tx, 0, 0);
NULL, (void*)reg_tx, 0, 0);
#else
(void)reg_rx;
(void)reg_tx;
#endif
}
/**
* @brief QSPI peripheral in SPI mode
* @{
*/
#ifdef QSPI
static void _init_qspi(spi_t bus)
{
/* reset the peripheral */
QSPI->CTRLA.bit.SWRST = 1;
QSPI->CTRLB.reg = QSPI_CTRLB_MODE_SPI
| QSPI_CTRLB_CSMODE_LASTXFER
| QSPI_CTRLB_DATALEN_8BITS;
/* set up DMA channels */
_init_dma(bus, &QSPI->RXDATA.reg, &QSPI->TXDATA.reg);
}
static void _qspi_acquire(spi_mode_t mode, spi_clk_t clk)
{
/* datasheet says SCK = MCK / (BAUD + 1) */
/* but BAUD = 0 does not work, assume SCK = MCK / BAUD */
uint32_t baud = CLOCK_CORECLOCK > (2 * clk)
? (CLOCK_CORECLOCK + clk - 1) / clk
: 1;
/* bit order is reversed from SERCOM SPI */
uint32_t _mode = (mode >> 1)
| (mode << 1);
_mode &= 0x3;
QSPI->CTRLA.bit.ENABLE = 1;
QSPI->BAUD.reg = QSPI_BAUD_BAUD(baud) | _mode;
}
static inline void _qspi_release(void)
{
QSPI->CTRLA.bit.ENABLE = 0;
}
static void _qspi_blocking_transfer(const void *out, void *in, size_t len)
{
const uint8_t *out_buf = out;
uint8_t *in_buf = in;
for (size_t i = 0; i < len; i++) {
uint8_t tmp = out_buf ? out_buf[i] : 0;
/* transmit byte on MOSI */
QSPI->TXDATA.reg = tmp;
/* wait until byte has been sampled on MISO */
while (QSPI->INTFLAG.bit.RXC == 0) {}
/* consume the byte */
tmp = QSPI->RXDATA.reg;
if (in_buf) {
in_buf[i] = tmp;
}
}
}
#else /* !QSPI */
void _init_qspi(spi_t bus);
void _qspi_acquire(spi_mode_t mode, spi_clk_t clk);
void _qspi_release(void);
void _qspi_blocking_transfer(const void *out, void *in, size_t len);
#endif
/** @} */
/**
* @brief SERCOM peripheral in SPI mode
* @{
*/
static void _init_spi(spi_t bus, SercomSpi *dev)
{
/* reset all device configuration */
_reset(dev);
/* configure base clock */
sercom_set_gen(dev, spi_config[bus].gclk_src);
/* enable receiver and configure character size to 8-bit
* no synchronization needed, as SERCOM device is not enabled */
dev->CTRLB.reg = SERCOM_SPI_CTRLB_CHSIZE(0) | SERCOM_SPI_CTRLB_RXEN;
/* set up DMA channels */
_init_dma(bus, &dev->DATA.reg, &dev->DATA.reg);
}
static void _spi_acquire(spi_t bus, spi_mode_t mode, spi_clk_t clk)
{
/* configure bus clock, in synchronous mode its calculated from
* BAUD.reg = (f_ref / (2 * f_bus) - 1)
* with f_ref := CLOCK_CORECLOCK as defined by the board
* to mitigate the rounding error due to integer arithmetic, the
* equation is modified to
* BAUD.reg = ((f_ref + f_bus) / (2 * f_bus) - 1) */
const uint8_t baud = ((sam0_gclk_freq(spi_config[bus].gclk_src) + clk) / (2 * clk) - 1);
/* configure device to be master and set mode and pads,
*
* NOTE: we could configure the pads already during spi_init, but for
* efficiency reason we do that here, so we can do all in one single write
* to the CTRLA register */
const uint32_t ctrla = SERCOM_SPI_CTRLA_MODE(0x3) /* 0x3 -> master */
| SERCOM_SPI_CTRLA_DOPO(spi_config[bus].mosi_pad)
| SERCOM_SPI_CTRLA_DIPO(spi_config[bus].miso_pad)
| (mode << SERCOM_SPI_CTRLA_CPHA_Pos);
/* first configuration or reconfiguration after altered device usage */
if (dev(bus)->BAUD.reg != baud || dev(bus)->CTRLA.reg != ctrla) {
/* disable the device */
_disable(dev(bus));
dev(bus)->BAUD.reg = baud;
dev(bus)->CTRLA.reg = ctrla;
/* no synchronization needed here, the enable synchronization below
* acts as a write-synchronization for both registers */
}
/* finally enable the device */
_enable(dev(bus));
}
static inline void _spi_release(spi_t bus)
{
/* disable the device */
_disable(dev(bus));
}
static void _spi_blocking_transfer(spi_t bus, const void *out, void *in, size_t len)
{
const uint8_t *out_buf = out;
uint8_t *in_buf = in;
for (size_t i = 0; i < len; i++) {
uint8_t tmp = (out_buf) ? out_buf[i] : 0;
/* transmit byte on MOSI */
dev(bus)->DATA.reg = tmp;
/* wait until byte has been sampled on MISO */
while (dev(bus)->INTFLAG.bit.RXC == 0) {}
/* consume the byte */
tmp = dev(bus)->DATA.reg;
if (in_buf) {
in_buf[i] = tmp;
}
}
}
/** @} */
void spi_init(spi_t bus)
{
/* make sure given bus is good */
@ -153,22 +337,14 @@ void spi_init(spi_t bus)
/* wake up device */
poweron(bus);
/* reset all device configuration */
_reset(dev(bus));
/* configure base clock */
sercom_set_gen(dev(bus), spi_config[bus].gclk_src);
/* enable receiver and configure character size to 8-bit
* no synchronization needed, as SERCOM device is not enabled */
dev(bus)->CTRLB.reg = (SERCOM_SPI_CTRLB_CHSIZE(0) | SERCOM_SPI_CTRLB_RXEN);
/* set up DMA channels */
_init_dma(bus, &dev(bus)->DATA.reg, &dev(bus)->DATA.reg);
if (_is_qspi(bus)) {
_init_qspi(bus);
} else {
_init_spi(bus, dev(bus));
}
/* put device back to sleep */
poweroff(bus);
}
void spi_init_pins(spi_t bus)
@ -196,44 +372,18 @@ int spi_acquire(spi_t bus, spi_cs_t cs, spi_mode_t mode, spi_clk_t clk)
{
(void)cs;
/* configure bus clock, in synchronous mode its calculated from
* BAUD.reg = (f_ref / (2 * f_bus) - 1)
* with f_ref := CLOCK_CORECLOCK as defined by the board
* to mitigate the rounding error due to integer arithmetic, the
* equation is modified to
* BAUD.reg = ((f_ref + f_bus) / (2 * f_bus) - 1) */
const uint8_t baud = ((sam0_gclk_freq(spi_config[bus].gclk_src) + clk) / (2 * clk) - 1);
/* configure device to be master and set mode and pads,
*
* NOTE: we could configure the pads already during spi_init, but for
* efficiency reason we do that here, so we can do all in one single write
* to the CTRLA register */
const uint32_t ctrla = SERCOM_SPI_CTRLA_MODE(0x3) /* 0x3 -> master */
| SERCOM_SPI_CTRLA_DOPO(spi_config[bus].mosi_pad)
| SERCOM_SPI_CTRLA_DIPO(spi_config[bus].miso_pad)
| (mode << SERCOM_SPI_CTRLA_CPHA_Pos);
/* get exclusive access to the device */
mutex_lock(&locks[bus]);
/* power on the device */
poweron(bus);
/* first configuration or reconfiguration after altered device usage */
if (dev(bus)->BAUD.reg != baud || dev(bus)->CTRLA.reg != ctrla) {
/* disable the device */
_disable(dev(bus));
dev(bus)->BAUD.reg = baud;
dev(bus)->CTRLA.reg = ctrla;
/* no synchronization needed here, the enable synchronization below
* acts as a write-synchronization for both registers */
if (_is_qspi(bus)) {
_qspi_acquire(mode, clk);
} else {
_spi_acquire(bus, mode, clk);
}
/* finally enable the device */
_enable(dev(bus));
/* mux clk_pin to SPI peripheral */
gpio_init_mux(spi_config[bus].clk_pin, spi_config[bus].clk_mux);
@ -246,8 +396,11 @@ void spi_release(spi_t bus)
* and lead to unexpected current draw by SPI salves. */
gpio_disable_mux(spi_config[bus].clk_pin);
/* disable the device */
_disable(dev(bus));
if (_is_qspi(bus)) {
_qspi_release();
} else {
_spi_release(bus);
}
/* power off the device */
poweroff(bus);
@ -258,24 +411,10 @@ void spi_release(spi_t bus)
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;
for (size_t i = 0; i < len; i++) {
uint8_t tmp = (out_buf) ? out_buf[i] : 0;
/* transmit byte on MOSI */
dev(bus)->DATA.reg = tmp;
/* wait until byte has been sampled on MISO */
while (dev(bus)->INTFLAG.bit.RXC == 0) {}
/* consume the byte */
tmp = dev(bus)->DATA.reg;
if (in_buf) {
in_buf[i] = tmp;
}
if (_is_qspi(bus)) {
_qspi_blocking_transfer(out, in, len);
} else {
_spi_blocking_transfer(bus, out, in, len);
}
}
@ -354,7 +493,6 @@ uint8_t spi_transfer_reg(spi_t bus, spi_cs_t cs, uint8_t reg, uint8_t out)
#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)
{

View File

@ -14,6 +14,7 @@ config CPU_COMMON_SAMD5X
select HAS_CPU_SAMD5X
select HAS_PERIPH_GPIO_TAMPER_WAKE
select HAS_PERIPH_HWRNG
select HAS_PERIPH_SPI_ON_QSPI
config CPU_FAM_SAMD51
bool

View File

@ -4,5 +4,6 @@ FEATURES_PROVIDED += periph_hwrng
FEATURES_PROVIDED += backup_ram
FEATURES_PROVIDED += cortexm_mpu
FEATURES_PROVIDED += periph_gpio_tamper_wake
FEATURES_PROVIDED += periph_spi_on_qspi
include $(RIOTCPU)/sam0_common/Makefile.features

View File

@ -160,6 +160,18 @@ struct sam0_aux_cfg_mapping {
/* config words 5,6,7 */
uint32_t user_pages[3]; /**< User pages */
};
/**
* @brief QSPI pins are fixed
* @{
*/
#define SAM0_QSPI_PIN_CLK GPIO_PIN(PB, 10) /**< Clock */
#define SAM0_QSPI_PIN_CS GPIO_PIN(PB, 11) /**< Chip Select */
#define SAM0_QSPI_PIN_DATA_0 GPIO_PIN(PA, 8) /**< D0 / MOSI */
#define SAM0_QSPI_PIN_DATA_1 GPIO_PIN(PA, 9) /**< D1 / MISO */
#define SAM0_QSPI_PIN_DATA_2 GPIO_PIN(PA, 10) /**< D2 / WP */
#define SAM0_QSPI_PIN_DATA_3 GPIO_PIN(PA, 11) /**< D3 / HOLD */
#define SAM0_QSPI_MUX GPIO_MUX_H /**< QSPI mux */
/** @} */
#ifdef __cplusplus

View File

@ -245,6 +245,11 @@ config HAS_PERIPH_SPI
help
Indicates that an SPI peripheral is present.
config HAS_PERIPH_SPI_ON_QSPI
bool
help
Indicates that the QSPI peripheral can be used in SPI mode.
config HAS_PERIPH_SPI_RECONFIGURE
bool
help