diff --git a/boards/same54-xpro/Makefile.dep b/boards/same54-xpro/Makefile.dep index c6c3769fc3..3c35960fac 100644 --- a/boards/same54-xpro/Makefile.dep +++ b/boards/same54-xpro/Makefile.dep @@ -5,3 +5,8 @@ endif ifneq (,$(filter eui_provider,$(USEMODULE))) USEMODULE += at24mac endif + +ifneq (,$(filter mtd,$(USEMODULE))) + FEATURES_REQUIRED += periph_spi_on_qspi + USEMODULE += mtd_spi_nor +endif diff --git a/boards/same54-xpro/board.c b/boards/same54-xpro/board.c index 7d5eff3896..5845ec5422 100644 --- a/boards/same54-xpro/board.c +++ b/boards/same54-xpro/board.c @@ -20,6 +20,38 @@ #include "board.h" #include "periph/gpio.h" +#include "mtd_spi_nor.h" +#include "timex.h" + +#ifdef MODULE_MTD +/* N25Q256A */ +static const mtd_spi_nor_params_t _same54_nor_params = { + .opcode = &mtd_spi_nor_opcode_default, + .wait_chip_erase = 240 * US_PER_SEC, + .wait_64k_erase = 700 * US_PER_MS, + .wait_sector_erase = 250 * US_PER_MS, + .wait_chip_wake_up = 1 * US_PER_MS, + .clk = MHZ(54), + .flag = SPI_NOR_F_SECT_4K | SPI_NOR_F_SECT_64K, + .spi = SPI_DEV(2), + .mode = SPI_MODE_0, + .cs = SAM0_QSPI_PIN_CS, + .wp = SAM0_QSPI_PIN_DATA_2, + .hold = SAM0_QSPI_PIN_DATA_3, + .addr_width = 4, +}; + +static mtd_spi_nor_t same54_nor_dev = { + .base = { + .driver = &mtd_spi_nor_driver, + .page_size = 256, + .pages_per_sector = 16, + }, + .params = &_same54_nor_params, +}; + +mtd_dev_t *mtd0 = (mtd_dev_t *)&same54_nor_dev; +#endif /* MODULE_MTD */ void board_init(void) { diff --git a/boards/same54-xpro/include/board.h b/boards/same54-xpro/include/board.h index f484210448..5618f18e63 100644 --- a/boards/same54-xpro/include/board.h +++ b/boards/same54-xpro/include/board.h @@ -22,6 +22,7 @@ #include "cpu.h" #include "at24mac.h" +#include "mtd.h" #ifdef __cplusplus extern "C" { @@ -83,6 +84,14 @@ static inline int _at24mac_get_eui48(const void *arg, eui48_t *addr) #define BTN0_MODE GPIO_IN_PU /** @} */ +/** + * @name MTD configuration + * @{ + */ +extern mtd_dev_t *mtd0; +#define MTD_0 mtd0 +/** @} */ + /** * @name Xtimer configuration * @{ diff --git a/boards/same54-xpro/include/periph_conf.h b/boards/same54-xpro/include/periph_conf.h index 4d9c75d781..301b1da5e8 100644 --- a/boards/same54-xpro/include/periph_conf.h +++ b/boards/same54-xpro/include/periph_conf.h @@ -251,7 +251,25 @@ static const spi_conf_t spi_config[] = { .tx_trigger = SERCOM6_DMAC_ID_TX, .rx_trigger = SERCOM6_DMAC_ID_RX, #endif - } + }, +#ifdef MODULE_PERIPH_SPI_ON_QSPI + { /* QSPI in SPI mode */ + .dev = QSPI, + .miso_pin = SAM0_QSPI_PIN_DATA_1, + .mosi_pin = SAM0_QSPI_PIN_DATA_0, + .clk_pin = SAM0_QSPI_PIN_CLK, + .miso_mux = SAM0_QSPI_MUX, + .mosi_mux = SAM0_QSPI_MUX, + .clk_mux = SAM0_QSPI_MUX, + .miso_pad = SPI_PAD_MISO_0, /* unused */ + .mosi_pad = SPI_PAD_MOSI_0_SCK_1, /* unused */ + .gclk_src = SAM0_GCLK_MAIN, /* unused */ +#ifdef MODULE_PERIPH_DMA + .tx_trigger = QSPI_DMAC_ID_TX, + .rx_trigger = QSPI_DMAC_ID_RX, +#endif + }, +#endif }; #define SPI_NUMOF ARRAY_SIZE(spi_config) 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 569d30f533..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,17 +57,79 @@ 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 inline void _reset(SercomSpi *dev) +{ + dev->CTRLA.reg |= SERCOM_SPI_CTRLA_SWRST; + while (dev->CTRLA.reg & SERCOM_SPI_CTRLA_SWRST) {} + +#ifdef SERCOM_SPI_STATUS_SYNCBUSY + while (dev->STATUS.bit.SYNCBUSY) {} +#else + while (dev->SYNCBUSY.bit.SWRST) {} +#endif +} + +static inline void _disable(SercomSpi *dev) +{ + dev->CTRLA.reg = 0; + +#ifdef SERCOM_SPI_STATUS_SYNCBUSY + while (dev->STATUS.bit.SYNCBUSY) {} +#else + while (dev->SYNCBUSY.reg) {} +#endif +} + +static inline void _enable(SercomSpi *dev) +{ + dev->CTRLA.bit.ENABLE = 1; + +#ifdef SERCOM_SPI_STATUS_SYNCBUSY + while (dev->STATUS.bit.SYNCBUSY) {} +#else + while (dev->SYNCBUSY.reg) {} +#endif } static inline bool _use_dma(spi_t bus) @@ -80,6 +143,186 @@ static inline bool _use_dma(spi_t bus) #endif } +static inline void _init_dma(spi_t bus, const volatile void *reg_rx, volatile void *reg_tx) +{ + if (!_use_dma(bus)) { + return; + } + +#ifdef MODULE_PERIPH_DMA + _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*)reg_rx, NULL, 1, 0); + dma_prepare(_dma_state[bus].tx_dma, DMAC_BTCTRL_BEATSIZE_BYTE_Val, + 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 */ @@ -94,35 +337,14 @@ void spi_init(spi_t bus) /* wake up device */ poweron(bus); - /* reset all device configuration */ - dev(bus)->CTRLA.reg |= SERCOM_SPI_CTRLA_SWRST; - while ((dev(bus)->CTRLA.reg & SERCOM_SPI_CTRLA_SWRST) || - (dev(bus)->SYNCBUSY.reg & SERCOM_SPI_SYNCBUSY_SWRST)) {} - - /* configure base clock: using GLK GEN 0 */ - 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); - -#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); + if (_is_qspi(bus)) { + _init_qspi(bus); + } else { + _init_spi(bus, dev(bus)); } -#endif + /* put device back to sleep */ poweroff(bus); - } void spi_init_pins(spi_t bus) @@ -150,46 +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 */ - dev(bus)->CTRLA.reg &= ~(SERCOM_SPI_CTRLA_ENABLE); - while (dev(bus)->SYNCBUSY.reg & SERCOM_SPI_SYNCBUSY_ENABLE) {} - - 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 */ - dev(bus)->CTRLA.reg |= SERCOM_SPI_CTRLA_ENABLE; - while (dev(bus)->SYNCBUSY.reg & SERCOM_SPI_SYNCBUSY_ENABLE) {} - /* mux clk_pin to SPI peripheral */ gpio_init_mux(spi_config[bus].clk_pin, spi_config[bus].clk_mux); @@ -202,9 +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 */ - dev(bus)->CTRLA.reg &= ~(SERCOM_SPI_CTRLA_ENABLE); - while (dev(bus)->SYNCBUSY.reg & SERCOM_SPI_SYNCBUSY_ENABLE) {} + if (_is_qspi(bus)) { + _qspi_release(); + } else { + _spi_release(bus); + } /* power off the device */ poweroff(bus); @@ -215,17 +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 (int i = 0; i < (int)len; i++) { - uint8_t tmp = (out_buf) ? out_buf[i] : 0; - dev(bus)->DATA.reg = tmp; - while (!(dev(bus)->INTFLAG.reg & SERCOM_SPI_INTFLAG_RXC)) {} - tmp = (uint8_t)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); } } @@ -273,6 +462,7 @@ static void _dma_transfer_regs(spi_t bus, uint8_t reg, const uint8_t *out, _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) { @@ -303,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) { @@ -312,6 +501,7 @@ void spi_transfer_bytes(spi_t bus, spi_cs_t cs, bool cont, 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 */ 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/drivers/mtd_spi_nor/mtd_spi_nor.c b/drivers/mtd_spi_nor/mtd_spi_nor.c index c8c0b680a5..1e5b73a132 100644 --- a/drivers/mtd_spi_nor/mtd_spi_nor.c +++ b/drivers/mtd_spi_nor/mtd_spi_nor.c @@ -400,6 +400,7 @@ static int mtd_spi_nor_power(mtd_dev_t *mtd, enum mtd_power_state power) #endif /* enable 32 bit address mode */ if (dev->params->addr_width == 4) { + mtd_spi_cmd(dev, dev->params->opcode->wren); mtd_spi_cmd(dev, SFLASH_CMD_4_BYTE_ADDR); } 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 diff --git a/tests/periph_spi/Makefile b/tests/periph_spi/Makefile index 6578bf7b95..b3b522f839 100644 --- a/tests/periph_spi/Makefile +++ b/tests/periph_spi/Makefile @@ -3,10 +3,12 @@ include ../Makefile.tests_common LOW_MEMORY_BOARDS := samd10-xmini +FEATURES_REQUIRED += periph_spi +FEATURES_OPTIONAL += periph_spi_on_qspi + ifeq (,$(filter $(BOARD),$(LOW_MEMORY_BOARDS))) FEATURES_OPTIONAL += periph_spi_reconfigure endif -FEATURES_REQUIRED += periph_spi USEMODULE += xtimer USEMODULE += shell