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:
parent
94c7f40b9c
commit
8f72212eb0
@ -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 */
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user