From 8f72212eb097cb65449531f92b1e3ce6aa5867e2 Mon Sep 17 00:00:00 2001 From: Benjamin Valentin Date: Sun, 1 Nov 2020 22:50:50 +0100 Subject: [PATCH] cpu/sam0_common: SPI: add support for QSPI in SPI mode We can use the QSPI peripheral as an additional (non-Quad) SPI peripheral. --- cpu/sam0_common/include/periph_cpu_common.h | 2 +- cpu/sam0_common/periph/spi.c | 280 +++++++++++++++----- cpu/samd5x/Kconfig | 1 + cpu/samd5x/Makefile.features | 1 + cpu/samd5x/include/periph_cpu.h | 12 + kconfigs/Kconfig.features | 5 + 6 files changed, 229 insertions(+), 72 deletions(-) diff --git a/cpu/sam0_common/include/periph_cpu_common.h b/cpu/sam0_common/include/periph_cpu_common.h index e40513e5ae..d6310e6123 100644 --- a/cpu/sam0_common/include/periph_cpu_common.h +++ b/cpu/sam0_common/include/periph_cpu_common.h @@ -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 */ diff --git a/cpu/sam0_common/periph/spi.c b/cpu/sam0_common/periph/spi.c index aae491437c..7cf29e46a5 100644 --- a/cpu/sam0_common/periph/spi.c +++ b/cpu/sam0_common/periph/spi.c @@ -21,6 +21,7 @@ * @author Hauke Petersen * @author Joakim NohlgÄrd * @author Kaspar Schleiser + * @author Benjamin Valentin * * @} */ @@ -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) { diff --git a/cpu/samd5x/Kconfig b/cpu/samd5x/Kconfig index bf9005ef5a..5ebb1052dd 100644 --- a/cpu/samd5x/Kconfig +++ b/cpu/samd5x/Kconfig @@ -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 diff --git a/cpu/samd5x/Makefile.features b/cpu/samd5x/Makefile.features index ff90cc6e8b..ff059dc677 100644 --- a/cpu/samd5x/Makefile.features +++ b/cpu/samd5x/Makefile.features @@ -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 diff --git a/cpu/samd5x/include/periph_cpu.h b/cpu/samd5x/include/periph_cpu.h index 1d6e002147..6cc5000db3 100644 --- a/cpu/samd5x/include/periph_cpu.h +++ b/cpu/samd5x/include/periph_cpu.h @@ -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 diff --git a/kconfigs/Kconfig.features b/kconfigs/Kconfig.features index 598d539f06..2bcc8288ff 100644 --- a/kconfigs/Kconfig.features +++ b/kconfigs/Kconfig.features @@ -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