From b046a01f86d45a9464c80c4afcbf6a00fd909db6 Mon Sep 17 00:00:00 2001 From: Benjamin Valentin Date: Sun, 25 Oct 2020 15:38:46 +0100 Subject: [PATCH 01/13] cpu/sam0_common: add function to enable & disable CPU cache --- cpu/sam0_common/include/periph_cpu_common.h | 22 +++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/cpu/sam0_common/include/periph_cpu_common.h b/cpu/sam0_common/include/periph_cpu_common.h index 96e77e3d0292..2524dd1e0185 100644 --- a/cpu/sam0_common/include/periph_cpu_common.h +++ b/cpu/sam0_common/include/periph_cpu_common.h @@ -536,6 +536,28 @@ static inline void sam0_cortexm_sleep(int deep) #endif } +/** + * @brief Enable CPU cache + */ +static inline void samd0_cache_enable(void) +{ +#ifdef CMCC + CMCC->CTRL.bit.CEN = 1; +#endif +} + +/** + * @brief Disable and clear CPU cache + */ +static inline void samd0_cache_disable(void) +{ +#ifdef CMCC + CMCC->CTRL.bit.CEN = 0; + while (CMCC->SR.bit.CSTS) {} + CMCC->MAINT0.bit.INVALL = 1; +#endif +} + /** * @brief Disable alternate function (PMUX setting) for a PORT pin * From 2a3a56dc4987ee9bda5fe8abf7e79b0e4a9176dd Mon Sep 17 00:00:00 2001 From: Benjamin Valentin Date: Sun, 25 Oct 2020 15:57:34 +0100 Subject: [PATCH 02/13] drivers/periph: add interface for QSPI --- drivers/include/periph/qspi.h | 207 ++++++++++++++++++++++++++++++++++ drivers/periph_common/init.c | 10 ++ kconfigs/Kconfig.features | 5 + 3 files changed, 222 insertions(+) create mode 100644 drivers/include/periph/qspi.h diff --git a/drivers/include/periph/qspi.h b/drivers/include/periph/qspi.h new file mode 100644 index 000000000000..6722d414130d --- /dev/null +++ b/drivers/include/periph/qspi.h @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2020 ML!PA Consulting GmbH + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @defgroup drivers_periph_qspi Quad SPI peripheral + * @ingroup drivers_periph + * @brief Low-level QSPI peripheral driver + */ + +#ifndef PERIPH_QSPI_H +#define PERIPH_QSPI_H + +#include +#include +#include +#include + +#include "periph_cpu.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Serial Flash JEDEC commands + */ +typedef enum { + SFLASH_CMD_READ = 0x03, /**< Single Read */ + SFLASH_CMD_FAST_READ = 0x0B, /**< Fast Read */ + SFLASH_CMD_QUAD_READ = 0x6B, /**< 1 line address, 4 line data */ + + SFLASH_CMD_READ_JEDEC_ID = 0x9f, /**< Read Chip ID */ + + SFLASH_CMD_PAGE_PROGRAM = 0x02, /**< 1 line address, 1 line data */ + SFLASH_CMD_QUAD_PAGE_PROGRAM = 0x32, /**< 1 line address, 4 line data */ + + SFLASH_CMD_READ_STATUS = 0x05, /**< Read first status register */ + SFLASH_CMD_READ_STATUS2 = 0x35, /**< Read second status register */ + + SFLASH_CMD_WRITE_STATUS = 0x01, /**< Write first status register */ + SFLASH_CMD_WRITE_STATUS2 = 0x31, /**< Write second status register */ + + SFLASH_CMD_ENABLE_RESET = 0x66, /**< Must be issued before reset command */ + SFLASH_CMD_RESET = 0x99, /**< Reset device state */ + + SFLASH_CMD_WRITE_ENABLE = 0x06, /**< Must be issued before write command */ + SFLASH_CMD_WRITE_DISABLE = 0x04, /**< Revoke Write Enable */ + + SFLASH_CMD_ERASE_SECTOR = 0x20, /**< sector (4k) erase */ + SFLASH_CMD_ERASE_BLOCK = 0xD8, /**< block (64k) erase */ + SFLASH_CMD_ERASE_CHIP = 0xC7, /**< whole chip erase */ + + SFLASH_CMD_4_BYTE_ADDR = 0xB7, /**< enable 32 bit addressing */ + SFLASH_CMD_3_BYTE_ADDR = 0xE9, /**< enable 24 bit addressing */ +} spi_flash_cmd_t; + +/** + * @brief Default SPI device access macro + */ +#ifndef QSPI_DEV +#define QSPI_DEV(x) (x) +#endif + +/** + * @brief Define global value for undefined QSPI device + */ +#ifndef QSPI_UNDEF +#define QSPI_UNDEF (UINT_MAX) +#endif + +/** + * @brief Default type for QSPI devices + */ +#ifndef HAVE_QSPI_T +typedef unsigned int qspi_t; +#endif + +/** + * @brief Available QSPI modes, defining the configuration of clock polarity + * and clock phase + * + * RIOT is using the mode numbers as commonly defined by most vendors + * (https://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus#Mode_numbers): + * + * - MODE_0: CPOL=0, CPHA=0 - The first data bit is sampled by the receiver on + * the first SCK rising SCK edge (this mode is used most often). + * - MODE_1: CPOL=0, CPHA=1 - The first data bit is sampled by the receiver on + * the second rising SCK edge. + * - MODE_2: CPOL=1, CPHA=0 - The first data bit is sampled by the receiver on + * the first falling SCK edge. + * - MODE_3: CPOL=1, CPHA=1 - The first data bit is sampled by the receiver on + * the second falling SCK edge. + */ +#ifndef HAVE_QSPI_MODE_T +typedef enum { + QSPI_MODE_0 = 0, /**< CPOL=0, CPHA=0 */ + QSPI_MODE_1, /**< CPOL=0, CPHA=1 */ + QSPI_MODE_2, /**< CPOL=1, CPHA=0 */ + QSPI_MODE_3 /**< CPOL=1, CPHA=1 */ +} qspi_mode_t; +#endif + +/** + * @brief Basic initialization of the given QSPI bus + * + * @note This function MUST not be called more than once per bus! + * + * @param[in] bus QSPI device to initialize + */ +void qspi_init(qspi_t bus); + +/** + * @brief Configure the QSPI peripheral settings + * + * @param[in] bus QSPI device to configure + * @param[in] mode QSPI mode @see qspi_mode_t + * @param[in] addrlen Addressing mode (bytes) + * 3 for 24 bit addressing, 4 for 32 bit + * @param[in] clk_hz QSPI frequency in Hz + */ +void qspi_configure(qspi_t bus, qspi_mode_t mode, uint8_t addrlen, uint32_t clk_hz); + +/** + * @brief Start a new QSPI transaction + * + * Starting a new QSPI transaction will get exclusive access to the QSPI bus + * and configure it according to the given values. If another QSPI transaction + * is active when this function is called, this function will block until the + * other transaction is complete (qspi_relase was called). + * + * @param[in] bus QSPI device to access + */ +void qspi_acquire(qspi_t bus); + +/** + * @brief Finish an ongoing QSPI transaction by releasing the given QSPI bus + * + * After release, the given SPI bus should be fully powered down until acquired + * again. + * + * @param[in] bus QSPI device to release + */ +void qspi_release(qspi_t bus); + +/** + * @brief Execute a command with response data e.g Read Status, Read JEDEC + * + * @param[in] bus QSPI device + * @param[in] command The JEDEC command ID + * @param[out] response Command response data, may be NULL + * @param[in] len Size of the command response buffer + */ +void qspi_cmd_read(qspi_t bus, uint8_t command, void *response, size_t len); + +/** + * @brief Execute a command with data e.g Write Status, Write Enable + * + * @param[in] bus QSPI device + * @param[in] command The JEDEC command ID + * @param[in] data Command parameter data, may be NULL + * @param[in] len Size of the command response buffer + */ +void qspi_cmd_write(qspi_t bus, uint8_t command, const void *data, size_t len); + +/** + * @brief Erase external memory by address + * + * @param[in] bus QSPI device + * @param[in] command The JEDEC command ID (sector erase, block erase) + * @param[in] address The address of the block that will be reased + */ +void qspi_erase(qspi_t bus, uint8_t command, uint32_t address); + +/** + * @brief Read data from external memory. + * Typically it is implemented by quad read command (`0x6B`). + * + * @param[in] bus QSPI device + * @param[in] address Address to read from + * @param[out] data Buffer to store the data + * @param[in] len Nmber of byte to read + */ +void qspi_read(qspi_t bus, uint32_t addr, void *data, size_t len); + +/** + * @brief Write data to external memory. + * Can only write 1 -> 0, so target region should erased first. + * Typically it uses quad write command (`0x32`). + * + * @param[in] bus QSPI device + * @param[in] address Address to write to + * @param[in] data Buffer to write + * @param[in] len Nmber of byte to write + */ +void qspi_write(qspi_t bus, uint32_t addr, const void *data, size_t len); + +#ifdef __cplusplus +} +#endif + +#endif /* PERIPH_QSPI_H */ +/** @} */ diff --git a/drivers/periph_common/init.c b/drivers/periph_common/init.c index c3f6bd446388..8e72851d6e64 100644 --- a/drivers/periph_common/init.c +++ b/drivers/periph_common/init.c @@ -31,6 +31,9 @@ #ifdef MODULE_PERIPH_INIT_SPI #include "periph/spi.h" #endif +#ifdef MODULE_PERIPH_INIT_QSPI +#include "periph/qspi.h" +#endif #ifdef MODULE_PERIPH_INIT_RTC #include "periph/rtc.h" #endif @@ -65,6 +68,13 @@ void periph_init(void) } #endif + /* initialize configured QSPI devices */ +#ifdef MODULE_PERIPH_INIT_QSPI + for (unsigned i = 0; i < QSPI_NUMOF; i++) { + qspi_init(QSPI_DEV(i)); + } +#endif + /* Initialize RTT before RTC to allow for RTT based RTC implementations */ #ifdef MODULE_PERIPH_INIT_RTT rtt_init(); diff --git a/kconfigs/Kconfig.features b/kconfigs/Kconfig.features index 26bfd998ec2e..f138787698ec 100644 --- a/kconfigs/Kconfig.features +++ b/kconfigs/Kconfig.features @@ -240,6 +240,11 @@ config HAS_PERIPH_SPI_GPIO_MODE help Indicates that the SPI peripheral supports configuring the GPIOs modes. +config HAS_PERIPH_QSPI + bool + help + Indicates that a Quad-SPI peripheral is present. + config HAS_PERIPH_TEMPERATURE bool help From 54abc50a44fc9b7dab82e96c9b4ad7a1a8513967 Mon Sep 17 00:00:00 2001 From: Benjamin Valentin Date: Sun, 25 Oct 2020 15:58:41 +0100 Subject: [PATCH 03/13] cpu/sam0_common: implement QSPI peripheral --- cpu/sam0_common/include/periph_cpu_common.h | 13 ++ cpu/sam0_common/periph/qspi.c | 236 ++++++++++++++++++++ cpu/samd5x/include/periph_cpu.h | 5 + 3 files changed, 254 insertions(+) create mode 100644 cpu/sam0_common/periph/qspi.c diff --git a/cpu/sam0_common/include/periph_cpu_common.h b/cpu/sam0_common/include/periph_cpu_common.h index 2524dd1e0185..848026ef49f7 100644 --- a/cpu/sam0_common/include/periph_cpu_common.h +++ b/cpu/sam0_common/include/periph_cpu_common.h @@ -377,6 +377,19 @@ typedef enum { #define spi_pin_clk(dev) spi_config[dev].clk_pin /** @} */ +/** + * @brief Override QSPI modes + * @{ + */ +#define HAVE_QSPI_MODE_T +typedef enum { + QSPI_MODE_0 = 0x0, /**< CPOL=0, CPHA=0 */ + QSPI_MODE_1 = 0x2, /**< CPOL=0, CPHA=1 */ + QSPI_MODE_2 = 0x1, /**< CPOL=1, CPHA=0 */ + QSPI_MODE_3 = 0x3 /**< CPOL=1, CPHA=1 */ +} qspi_mode_t; +/** @} */ + #endif /* ndef DOXYGEN */ /** diff --git a/cpu/sam0_common/periph/qspi.c b/cpu/sam0_common/periph/qspi.c new file mode 100644 index 000000000000..99409594b52c --- /dev/null +++ b/cpu/sam0_common/periph/qspi.c @@ -0,0 +1,236 @@ +/* + * Copyright (c) 2020 ML!PA Consulting GmbH + * Copyright (c) 2019 Ha Thach and Dean Miller for Adafruit Industries LLC + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup cpu_sam0_common + * @{ + * + * @file qspi.c + * @brief Low-level QSPI peripheral implementation + * based on Adafruit_QSPI.cpp + * + * @author Benjamin Valentin + * + * @} + */ + +#include + +#include "cpu.h" +#include "periph/gpio.h" +#include "periph/qspi.h" +#include "macros/units.h" +#include "mutex.h" + +/* QSPI peripheral pins are fixed */ +#ifdef CPU_COMMON_SAMD5X +#define QSPI_PIN_CLK GPIO_PIN(PB, 10) +#define QSPI_PIN_CS GPIO_PIN(PB, 11) +#define QSPI_PIN_DATA_0 GPIO_PIN(PA, 8) +#define QSPI_PIN_DATA_1 GPIO_PIN(PA, 9) +#define QSPI_PIN_DATA_2 GPIO_PIN(PA, 10) +#define QSPI_PIN_DATA_3 GPIO_PIN(PA, 11) +#define QSPI_MUX GPIO_MUX_H +#endif + +static mutex_t _qspi_mutex = MUTEX_INIT; +static uint32_t _qspi_addrlen = QSPI_INSTRFRAME_ADDRLEN_24BITS; + +/** + * @brief Run a single QSPI instruction. + * + * @param command instruction code + * @param iframe iframe register value (configured by caller according to command + * code) + * @param addr The address to read or write from. If the instruction doesn't + * require an address, this parameter is meaningless. + * @param buffer pointer to the data to be written or stored depending on the type + * is Read or Write + * @param size the number of bytes to read or write. + */ +static void _run_instruction(uint8_t command, uint32_t iframe, uint32_t addr, + uint8_t *buffer, uint32_t size) +{ + QSPI->INSTRCTRL.bit.INSTR = command; + QSPI->INSTRFRAME.reg = iframe; + + /* Dummy read of INSTRFRAME needed to synchronize. + * See Instruction Transmission Flow Diagram, figure 37.9, page 995 + * and Example 4, page 998, section 37.6.8.5. + */ + QSPI->INSTRFRAME.reg; + + if (buffer && size) { + void *qspi_mem = (uint8_t *)(QSPI_AHB + addr); + const uint32_t tfr_type = iframe & QSPI_INSTRFRAME_TFRTYPE_Msk; + + if ((tfr_type == QSPI_INSTRFRAME_TFRTYPE_READ) || + (tfr_type == QSPI_INSTRFRAME_TFRTYPE_READMEMORY)) { + memcpy(buffer, qspi_mem, size); + } else { + memcpy(qspi_mem, buffer, size); + } + } + + QSPI->CTRLA.reg = QSPI_CTRLA_ENABLE | QSPI_CTRLA_LASTXFER; + + while (!QSPI->INTFLAG.bit.INSTREND) { + thread_yield(); + } + + QSPI->INTFLAG.reg = QSPI_INTFLAG_INSTREND; +} + +void qspi_cmd_read(qspi_t bus, uint8_t command, void *response, size_t len) +{ + (void)bus; + + uint32_t iframe = QSPI_INSTRFRAME_WIDTH_SINGLE_BIT_SPI + | _qspi_addrlen + | QSPI_INSTRFRAME_TFRTYPE_READ + | QSPI_INSTRFRAME_INSTREN + | (response != NULL ? QSPI_INSTRFRAME_DATAEN : 0); + + samd0_cache_disable(); + _run_instruction(command, iframe, 0, response, len); + samd0_cache_enable(); +} + +void qspi_cmd_write(qspi_t bus, uint8_t command, const void *data, size_t len) +{ + (void)bus; + + uint32_t iframe = QSPI_INSTRFRAME_WIDTH_SINGLE_BIT_SPI + | _qspi_addrlen + | QSPI_INSTRFRAME_TFRTYPE_WRITE + | QSPI_INSTRFRAME_INSTREN + | (data != NULL ? QSPI_INSTRFRAME_DATAEN : 0); + + samd0_cache_disable(); + _run_instruction(command, iframe, 0, (void *)data, len); + samd0_cache_enable(); +} + +void qspi_read(qspi_t bus, uint32_t addr, void *data, size_t len) +{ + (void)bus; + + /* Command 0x6B 1 line address, 4 line Data + * Quad output mode, read memory type + */ + uint32_t iframe = QSPI_INSTRFRAME_WIDTH_QUAD_OUTPUT + | _qspi_addrlen + | QSPI_INSTRFRAME_TFRTYPE_READMEMORY + | QSPI_INSTRFRAME_INSTREN | QSPI_INSTRFRAME_ADDREN + | QSPI_INSTRFRAME_DATAEN | QSPI_INSTRFRAME_DUMMYLEN(8); + + samd0_cache_disable(); + _run_instruction(SFLASH_CMD_QUAD_READ, iframe, addr, data, len); + samd0_cache_enable(); +} + +void qspi_erase(qspi_t bus, uint8_t command, uint32_t address) +{ + (void)bus; + + uint32_t iframe = QSPI_INSTRFRAME_WIDTH_SINGLE_BIT_SPI + | _qspi_addrlen + | QSPI_INSTRFRAME_TFRTYPE_WRITE + | QSPI_INSTRFRAME_INSTREN | QSPI_INSTRFRAME_ADDREN; + + QSPI->INSTRADDR.reg = address; + _run_instruction(command, iframe, address, NULL, 0); +} + +void qspi_write(qspi_t bus, uint32_t addr, const void *data, size_t len) +{ + (void)bus; + + uint32_t iframe = QSPI_INSTRFRAME_WIDTH_QUAD_OUTPUT + | _qspi_addrlen + | QSPI_INSTRFRAME_TFRTYPE_WRITEMEMORY + | QSPI_INSTRFRAME_INSTREN | QSPI_INSTRFRAME_ADDREN + | QSPI_INSTRFRAME_DATAEN; + + samd0_cache_disable(); + _run_instruction(SFLASH_CMD_QUAD_PAGE_PROGRAM, iframe, addr, (void *)data, len); + samd0_cache_enable(); +} + +void qspi_acquire(qspi_t bus) +{ + (void)bus; + + mutex_lock(&_qspi_mutex); + + QSPI->CTRLA.bit.ENABLE = 1; +} + +void qspi_release(qspi_t bus) +{ + (void)bus; + + mutex_unlock(&_qspi_mutex); + + QSPI->CTRLA.bit.ENABLE = 0; +} + +void qspi_init(qspi_t bus) +{ + (void)bus; + + /* enable QSPI clock */ + MCLK->AHBMASK.reg |= MCLK_AHBMASK_QSPI; + MCLK->APBCMASK.reg |= MCLK_APBCMASK_QSPI; + + /* reset the peripheral */ + QSPI->CTRLA.bit.SWRST = 1; + + /* configure QSPI pins */ + gpio_init_mux(QSPI_PIN_CLK, QSPI_MUX); + gpio_init_mux(QSPI_PIN_CS, QSPI_MUX); + gpio_init_mux(QSPI_PIN_DATA_0, QSPI_MUX); + gpio_init_mux(QSPI_PIN_DATA_1, QSPI_MUX); + gpio_init_mux(QSPI_PIN_DATA_2, QSPI_MUX); + gpio_init_mux(QSPI_PIN_DATA_3, QSPI_MUX); + + /* start with low 4 MHz, Mode 0 */ + QSPI->BAUD.bit.BAUD = CLOCK_CORECLOCK / MHZ(4); + QSPI->CTRLB.reg = QSPI_CTRLB_MODE_MEMORY | QSPI_CTRLB_CSMODE_NORELOAD + | QSPI_CTRLB_DATALEN_8BITS | QSPI_CTRLB_CSMODE_LASTXFER; +} + +void qspi_configure(qspi_t bus, qspi_mode_t mode, uint8_t addrlen, uint32_t clk_hz) +{ + (void)bus; + + assert(addrlen == 3 || addrlen == 4); + assert(clk_hz <= CLOCK_CORECLOCK); + + qspi_acquire(bus); + + QSPI->BAUD.reg = QSPI_BAUD_BAUD((CLOCK_CORECLOCK + clk_hz - 1) / clk_hz) + | mode; + + /* make sure to keep the memory peripheral in sync */ + /* set addr len needs write enable */ + qspi_cmd_write(bus, SFLASH_CMD_WRITE_ENABLE, NULL, 0); + + if (addrlen == 3) { + qspi_cmd_write(bus, SFLASH_CMD_3_BYTE_ADDR, NULL, 0); + _qspi_addrlen = QSPI_INSTRFRAME_ADDRLEN_24BITS; + } + + if (addrlen == 4) { + qspi_cmd_write(bus, SFLASH_CMD_4_BYTE_ADDR, NULL, 0); + _qspi_addrlen = QSPI_INSTRFRAME_ADDRLEN_32BITS; + } + + qspi_release(bus); +} diff --git a/cpu/samd5x/include/periph_cpu.h b/cpu/samd5x/include/periph_cpu.h index 21b80c7cdf5d..4c3435b4ea63 100644 --- a/cpu/samd5x/include/periph_cpu.h +++ b/cpu/samd5x/include/periph_cpu.h @@ -106,6 +106,11 @@ typedef enum { */ #define DAC_NUMOF (2) +/** + * @brief The MCU has one QSPI interface. + */ +#define QSPI_NUMOF (1) + /** * @name Real time counter configuration * @{ From 9d124d6667b6074e14abbe6a810d9daf2cc8a0f4 Mon Sep 17 00:00:00 2001 From: Benjamin Valentin Date: Sun, 25 Oct 2020 15:59:48 +0100 Subject: [PATCH 04/13] boards/same54-xpro: enable QSPI flash --- boards/same54-xpro/Kconfig | 1 + boards/same54-xpro/Makefile.dep | 4 ++++ boards/same54-xpro/Makefile.features | 1 + boards/same54-xpro/board.c | 29 ++++++++++++++++++++++++++++ boards/same54-xpro/include/board.h | 9 +++++++++ 5 files changed, 44 insertions(+) diff --git a/boards/same54-xpro/Kconfig b/boards/same54-xpro/Kconfig index e6342dfdc17e..84699b18866f 100644 --- a/boards/same54-xpro/Kconfig +++ b/boards/same54-xpro/Kconfig @@ -18,6 +18,7 @@ config BOARD_SAME54_XPRO select HAS_PERIPH_RTT select HAS_PERIPH_PWM select HAS_PERIPH_SPI + select HAS_PERIPH_QSPI select HAS_PERIPH_TIMER select HAS_PERIPH_UART select HAS_PERIPH_USBDEV diff --git a/boards/same54-xpro/Makefile.dep b/boards/same54-xpro/Makefile.dep index c6c3769fc385..16d13b0257a9 100644 --- a/boards/same54-xpro/Makefile.dep +++ b/boards/same54-xpro/Makefile.dep @@ -5,3 +5,7 @@ endif ifneq (,$(filter eui_provider,$(USEMODULE))) USEMODULE += at24mac endif + +ifneq (,$(filter mtd,$(USEMODULE))) + USEMODULE += mtd_spi_nor_qspi +endif diff --git a/boards/same54-xpro/Makefile.features b/boards/same54-xpro/Makefile.features index 7bf9dac809b3..46b01f54151f 100644 --- a/boards/same54-xpro/Makefile.features +++ b/boards/same54-xpro/Makefile.features @@ -7,6 +7,7 @@ FEATURES_PROVIDED += periph_i2c FEATURES_PROVIDED += periph_rtc FEATURES_PROVIDED += periph_rtt FEATURES_PROVIDED += periph_pwm +FEATURES_PROVIDED += periph_qspi FEATURES_PROVIDED += periph_spi FEATURES_PROVIDED += periph_timer FEATURES_PROVIDED += periph_uart diff --git a/boards/same54-xpro/board.c b/boards/same54-xpro/board.c index 7d5eff3896fd..893534939743 100644 --- a/boards/same54-xpro/board.c +++ b/boards/same54-xpro/board.c @@ -20,6 +20,35 @@ #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_qspi, + .wait_chip_erase = 240 * US_PER_SEC, + .wait_32k_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_NOR_F_QSPI), + .spi.qspi = QSPI_DEV(0), + .mode = QSPI_MODE_0, + .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 f484210448b1..5618f18e638d 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 * @{ From ecc4b0272882476507e99f6b2967f762f9dc8382 Mon Sep 17 00:00:00 2001 From: Benjamin Valentin Date: Sun, 25 Oct 2020 16:41:08 +0100 Subject: [PATCH 05/13] tests/periph_qspi: add QSPI test application --- tests/periph_qspi/Makefile | 11 ++ tests/periph_qspi/main.c | 236 +++++++++++++++++++++++++++++++++++++ 2 files changed, 247 insertions(+) create mode 100644 tests/periph_qspi/Makefile create mode 100644 tests/periph_qspi/main.c diff --git a/tests/periph_qspi/Makefile b/tests/periph_qspi/Makefile new file mode 100644 index 000000000000..02936a2fbc56 --- /dev/null +++ b/tests/periph_qspi/Makefile @@ -0,0 +1,11 @@ +BOARD ?= same54-xpro +include ../Makefile.tests_common + +FEATURES_REQUIRED += periph_qspi + +USEMODULE += shell +USEMODULE += shell_commands + +USEMODULE += od + +include $(RIOTBASE)/Makefile.include diff --git a/tests/periph_qspi/main.c b/tests/periph_qspi/main.c new file mode 100644 index 000000000000..73e354b3d398 --- /dev/null +++ b/tests/periph_qspi/main.c @@ -0,0 +1,236 @@ +/* + * Copyright (c) 2020 ML!PA Consulting GmbH + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup tests + * @{ + * + * @file + * @brief Application for testing low-level QSPI driver implementations + * + * @author Benjamin Valentin + * + * @} + */ + +#include +#include +#include + +#include "od.h" +#include "periph/qspi.h" +#include "shell.h" + +static qspi_t _get_dev(int argc, char **argv, bool *oob) +{ + qspi_t dev = QSPI_DEV(0); + *oob = false; + + if (argc > 1) { + unsigned idx = atoi(argv[1]); + if (idx >= QSPI_NUMOF) { + printf("%s: invalid device: %s\n", argv[0], argv[1]); + *oob = true; + } + + dev = QSPI_DEV(idx); + } + + return dev; +} + +static int cmd_read(int argc, char **argv) +{ + bool oob; + qspi_t dev = _get_dev(argc, argv, &oob); + uint32_t addr, len; + + if (oob) { + return -1; + } + + if (argc < 4) { + printf("usage: %s \n", argv[0]); + return -1; + } + + addr = atoi(argv[2]); + len = atoi(argv[3]); + + void *buffer = malloc(len); + if (buffer == NULL) { + puts("out of memory"); + return -1; + } + + qspi_acquire(dev); + qspi_read(dev, addr, buffer, len); + qspi_release(dev); + + od_hex_dump(buffer, len, 0); + + free(buffer); + + return 0; +} + +static int cmd_erase(int argc, char **argv) +{ + bool oob; + qspi_t dev = _get_dev(argc, argv, &oob); + uint32_t addr; + uint8_t mode; + + if (oob) { + return -1; + } + + if (argc < 4) { + printf("usage: %s \n", argv[0]); + printf("\tmode 0: sector erase\n"); + printf("\tmode 1: block erase\n"); + printf("\tmode 2: chip erase\n"); + return -1; + } + + addr = atoi(argv[2]); + mode = atoi(argv[3]); + + const spi_flash_cmd_t cmds[] = { + SFLASH_CMD_ERASE_SECTOR, + SFLASH_CMD_ERASE_BLOCK, + SFLASH_CMD_ERASE_CHIP, + }; + + if (mode > ARRAY_SIZE(cmds)) { + printf("%s: invalid mode\n", argv[0]); + return -1; + } + + qspi_acquire(dev); + qspi_cmd_write(dev, SFLASH_CMD_WRITE_ENABLE, NULL, 0); + qspi_erase(dev, cmds[mode], addr); + qspi_release(dev); + + return 0; +} +static int cmd_write(int argc, char **argv) +{ + bool oob; + qspi_t dev = _get_dev(argc, argv, &oob); + uint32_t addr, len; + + if (oob) { + return -1; + } + + if (argc < 4) { + printf("usage: %s \n", argv[0]); + return -1; + } + + addr = atoi(argv[2]); + len = strlen(argv[3]); + + qspi_acquire(dev); + qspi_cmd_write(dev, SFLASH_CMD_WRITE_ENABLE, NULL, 0); + qspi_write(dev, addr, argv[3], len); + qspi_release(dev); + + return 0; +} + +static int cmd_jedec(int argc, char **argv) +{ + bool oob; + qspi_t dev = _get_dev(argc, argv, &oob); + + if (oob) { + return -1; + } + + struct __attribute__ ((packed)) { + uint8_t manuf; + uint16_t device; + uint8_t unique[17]; + } jedec_id; + + qspi_acquire(dev); + qspi_cmd_read(dev, SFLASH_CMD_READ_JEDEC_ID, &jedec_id, sizeof(jedec_id)); + qspi_release(dev); + + printf("Manufacturer ID: %x\n", jedec_id.manuf); + printf("Device ID: %x\n", jedec_id.device); + printf("Unique ID:"); + for (unsigned i = 0; i < sizeof(jedec_id.unique); ++i) { + printf(" %02x", jedec_id.unique[i]); + } + puts(""); + + return 0; +} + +static qspi_mode_t _qspi_mode(unsigned mode) +{ + switch (mode) { + case 3: return QSPI_MODE_3; + case 2: return QSPI_MODE_2; + case 1: return QSPI_MODE_1; + default: + case 0: return QSPI_MODE_0; + } +} + +static int cmd_cfg(int argc, char **argv) +{ + bool oob; + qspi_t dev = _get_dev(argc, argv, &oob); + + if (oob) { + return -1; + } + + if (argc < 4) { + printf("usage: %s [mode]\n", argv[0]); + return -1; + } + + qspi_mode_t mode = QSPI_MODE_0; + unsigned addr_len = atoi(argv[2]); + unsigned freq_mhz = atoi(argv[3]); + + if (argc > 3) { + mode = _qspi_mode(atoi(argv[4])); + } + + printf("QSPI%s: %u-bit, %u MHz\n", argv[1], 8 * addr_len, freq_mhz); + + qspi_configure(dev, mode, addr_len, MHZ(freq_mhz)); + + return 0; +} +static const shell_command_t shell_commands[] = { + { "jedec", "Read JEDEC ID of the QSPI flash", cmd_jedec }, + { "init", "Configure the QSPI peripheral", cmd_cfg }, + { "read", "Read a region of memory on the QSPI flash", cmd_read }, + { "erase", "Erase a region of memory on the QSPI flash", cmd_erase }, + { "write", "Write a region of memory on the QSPI flash", cmd_write }, + { NULL, NULL, NULL } +}; + +int main(void) +{ + puts("Manual QSPI peripheral driver test"); + printf("There are %u QSPI peripherals.\n", QSPI_NUMOF); + + /* run the shell */ + char line_buf[SHELL_DEFAULT_BUFSIZE]; + shell_run(shell_commands, line_buf, SHELL_DEFAULT_BUFSIZE); + + return 0; +} From 95d1fd4bab21327e36ea28b93e1546ef82fca063 Mon Sep 17 00:00:00 2001 From: Benjamin Valentin Date: Sun, 25 Oct 2020 22:42:45 +0100 Subject: [PATCH 06/13] drivers/mtd_spi_nor: add support for QSPI backend --- boards/ikea-tradfri/board.c | 2 +- boards/mulle/board.c | 4 +- boards/nrf52840dk/mtd.c | 2 +- boards/pinetime/board.c | 2 +- boards/serpente/board.c | 2 +- boards/weact-f411ce/board.c | 2 +- drivers/Makefile.dep | 4 + drivers/include/mtd_spi_nor.h | 18 +++- drivers/mtd_spi_nor/Makefile.dep | 13 ++- drivers/mtd_spi_nor/mtd_spi_nor.c | 108 +++++++++++++++++++--- drivers/mtd_spi_nor/mtd_spi_nor_configs.c | 16 ++++ makefiles/pseudomodules.inc.mk | 1 + 12 files changed, 152 insertions(+), 22 deletions(-) diff --git a/boards/ikea-tradfri/board.c b/boards/ikea-tradfri/board.c index 810462715aa9..1cd1265f8ba1 100644 --- a/boards/ikea-tradfri/board.c +++ b/boards/ikea-tradfri/board.c @@ -33,7 +33,7 @@ static const mtd_spi_nor_params_t _ikea_tradfri_nor_params = { .wait_chip_wake_up = 1LU * US_PER_MS, .clk = IKEA_TRADFRI_NOR_SPI_CLK, .flag = IKEA_TRADFRI_NOR_FLAGS, - .spi = IKEA_TRADFRI_NOR_SPI_DEV, + .spi.spi = IKEA_TRADFRI_NOR_SPI_DEV, .mode = IKEA_TRADFRI_NOR_SPI_MODE, .cs = IKEA_TRADFRI_NOR_SPI_CS, .addr_width = 3, diff --git a/boards/mulle/board.c b/boards/mulle/board.c index d402ee0b167f..73858fd3b7ed 100644 --- a/boards/mulle/board.c +++ b/boards/mulle/board.c @@ -36,7 +36,7 @@ static nvram_t mulle_nvram_dev; nvram_t *mulle_nvram = &mulle_nvram_dev; static nvram_spi_params_t nvram_spi_params = { - .spi = MULLE_NVRAM_SPI_DEV, + .spi.spi = MULLE_NVRAM_SPI_DEV, .clk = MULLE_NVRAM_SPI_CLK, .cs = MULLE_NVRAM_SPI_CS, .address_count = MULLE_NVRAM_SPI_ADDRESS_COUNT, @@ -54,7 +54,7 @@ static const mtd_spi_nor_params_t mulle_nor_params = { .wait_sector_erase = 10LU * US_PER_MS, .wait_32k_erase = 20LU * US_PER_MS, .wait_chip_wake_up = 1LU * US_PER_MS, - .spi = MULLE_NOR_SPI_DEV, + .spi.spi = MULLE_NOR_SPI_DEV, .cs = MULLE_NOR_SPI_CS, .addr_width = 3, .mode = SPI_MODE_3, diff --git a/boards/nrf52840dk/mtd.c b/boards/nrf52840dk/mtd.c index fa84ec44a7ce..6bed7bfaa123 100644 --- a/boards/nrf52840dk/mtd.c +++ b/boards/nrf52840dk/mtd.c @@ -35,7 +35,7 @@ static const mtd_spi_nor_params_t _nrf52840dk_nor_params = { .wait_chip_wake_up = 35LU * US_PER_MS, .clk = NRF52840DK_NOR_SPI_CLK, .flag = NRF52840DK_NOR_FLAGS, - .spi = NRF52840DK_NOR_SPI_DEV, + .spi.spi = NRF52840DK_NOR_SPI_DEV, .mode = NRF52840DK_NOR_SPI_MODE, .cs = NRF52840DK_NOR_SPI_CS, .addr_width = 3, diff --git a/boards/pinetime/board.c b/boards/pinetime/board.c index 0b01f858185b..58e41e1a577f 100644 --- a/boards/pinetime/board.c +++ b/boards/pinetime/board.c @@ -38,7 +38,7 @@ static const mtd_spi_nor_params_t _pinetime_nor_params = { .wait_chip_wake_up = 1LU * US_PER_MS, .clk = PINETIME_NOR_SPI_CLK, .flag = PINETIME_NOR_FLAGS, - .spi = PINETIME_NOR_SPI_DEV, + .spi.spi = PINETIME_NOR_SPI_DEV, .mode = PINETIME_NOR_SPI_MODE, .cs = PINETIME_NOR_SPI_CS, .addr_width = 3, diff --git a/boards/serpente/board.c b/boards/serpente/board.c index 4bd0c1af3d35..75b79da58982 100644 --- a/boards/serpente/board.c +++ b/boards/serpente/board.c @@ -36,7 +36,7 @@ static const mtd_spi_nor_params_t _serpente_nor_params = { .wait_chip_wake_up = 1LU * US_PER_MS, .clk = SERPENTE_NOR_SPI_CLK, .flag = SERPENTE_NOR_FLAGS, - .spi = SERPENTE_NOR_SPI_DEV, + .spi.spi = SERPENTE_NOR_SPI_DEV, .mode = SERPENTE_NOR_SPI_MODE, .cs = SERPENTE_NOR_SPI_CS, .addr_width = 3, diff --git a/boards/weact-f411ce/board.c b/boards/weact-f411ce/board.c index bff15f3154ff..dcdb1df47582 100644 --- a/boards/weact-f411ce/board.c +++ b/boards/weact-f411ce/board.c @@ -35,7 +35,7 @@ static const mtd_spi_nor_params_t _weact_nor_params = { .wait_chip_wake_up = 1LU * US_PER_MS, .clk = WEACT_411CE_NOR_SPI_CLK, .flag = WEACT_411CE_NOR_FLAGS, - .spi = WEACT_411CE_NOR_SPI_DEV, + .spi.spi = WEACT_411CE_NOR_SPI_DEV, .mode = WEACT_411CE_NOR_SPI_MODE, .cs = WEACT_411CE_NOR_SPI_CS, .addr_width = 3, diff --git a/drivers/Makefile.dep b/drivers/Makefile.dep index 0d8da2b51dfb..011bb35f6092 100644 --- a/drivers/Makefile.dep +++ b/drivers/Makefile.dep @@ -93,6 +93,10 @@ ifneq (,$(filter mtd_%,$(USEMODULE))) USEMODULE += mtd endif +ifneq (,$(filter mtd_spi_nor_%,$(USEMODULE))) + USEMODULE += mtd_spi_nor +endif + # nrfmin is a concrete module but comes from cpu/nrf5x_common. Due to limitations # in the dependency resolution mechanism it's not possible to move its # dependency resolution at cpu level. diff --git a/drivers/include/mtd_spi_nor.h b/drivers/include/mtd_spi_nor.h index ad9103610e90..3e91811ae455 100644 --- a/drivers/include/mtd_spi_nor.h +++ b/drivers/include/mtd_spi_nor.h @@ -28,6 +28,7 @@ #include "periph_conf.h" #include "periph/spi.h" +#include "periph/qspi.h" #include "periph/gpio.h" #include "mtd.h" @@ -96,6 +97,11 @@ typedef struct __attribute__((packed)) { */ #define SPI_NOR_F_SECT_64K (4) +/** + * @brief Flag to set when the device is connected via QSPI + */ +#define SPI_NOR_F_QSPI (8) + /** * @brief Compile-time parameters for a serial flash device */ @@ -108,7 +114,10 @@ typedef struct { uint32_t wait_chip_wake_up; /**< Chip wake up time in µs */ spi_clk_t clk; /**< SPI clock */ uint16_t flag; /**< Config flags */ - spi_t spi; /**< SPI bus the device is connected to */ + union { + spi_t spi; /**< SPI bus the device is connected to */ + qspi_t qspi; /**< QSPI bus the device is connected to */ + } spi; /**< SPI/QSPI bus to use */ spi_mode_t mode; /**< SPI mode */ gpio_t cs; /**< CS pin GPIO handle */ uint8_t addr_width; /**< Number of bytes in addresses, usually 3 for small devices */ @@ -173,6 +182,13 @@ extern const mtd_spi_nor_opcode_t mtd_spi_nor_opcode_default; */ extern const mtd_spi_nor_opcode_t mtd_spi_nor_opcode_default_4bytes; +/** + * @brief Default QSPI command opcodes + * + * Uses Quad Read for read & Quad Write for write + */ +extern const mtd_spi_nor_opcode_t mtd_spi_nor_opcode_qspi; + #ifdef __cplusplus } #endif diff --git a/drivers/mtd_spi_nor/Makefile.dep b/drivers/mtd_spi_nor/Makefile.dep index 6f0dd3d6c3c4..e2849d42ee75 100644 --- a/drivers/mtd_spi_nor/Makefile.dep +++ b/drivers/mtd_spi_nor/Makefile.dep @@ -1 +1,12 @@ -FEATURES_REQUIRED += periph_spi +ifneq (,$(filter mtd_spi_nor_spi,$(USEMODULE))) + FEATURES_REQUIRED += periph_spi +endif + +ifneq (,$(filter mtd_spi_nor_qspi,$(USEMODULE))) + FEATURES_REQUIRED += periph_qspi +endif + +# default to mtd_spi_nor_spi +ifeq (,$(filter mtd_spi_nor_%,$(USEMODULE))) + USEMODULE += mtd_spi_nor_spi +endif diff --git a/drivers/mtd_spi_nor/mtd_spi_nor.c b/drivers/mtd_spi_nor/mtd_spi_nor.c index f7ff1b72ef7d..ca0f7c341caa 100644 --- a/drivers/mtd_spi_nor/mtd_spi_nor.c +++ b/drivers/mtd_spi_nor/mtd_spi_nor.c @@ -70,20 +70,36 @@ typedef enum { } jedec_manuf_t; /** @} */ -static inline spi_t _get_spi(const mtd_spi_nor_t *dev) +static inline bool _is_qspi(const mtd_spi_nor_t *dev) { - return dev->params->spi; + if (!IS_ACTIVE(MODULE_MTD_SPI_NOR_QSPI)) { + return false; + } + + return dev->params->flag & SPI_NOR_F_QSPI; } -static void mtd_spi_acquire(const mtd_spi_nor_t *dev) +static inline bool _is_spi(const mtd_spi_nor_t *dev) { - spi_acquire(_get_spi(dev), dev->params->cs, - dev->params->mode, dev->params->clk); + if (!IS_ACTIVE(MODULE_MTD_SPI_NOR_SPI)) { + return false; + } + + if (IS_ACTIVE(MODULE_MTD_SPI_NOR_QSPI)) { + return (dev->params->flag & SPI_NOR_F_QSPI) == 0; + } + + return true; } -static void mtd_spi_release(const mtd_spi_nor_t *dev) +static inline spi_t _get_spi(const mtd_spi_nor_t *dev) { - spi_release(_get_spi(dev)); + return dev->params->spi.spi; +} + +static inline qspi_t _get_qspi(const mtd_spi_nor_t *dev) +{ + return dev->params->spi.qspi; } static inline uint8_t* _be_addr(const mtd_spi_nor_t *dev, uint32_t *addr) @@ -92,6 +108,27 @@ static inline uint8_t* _be_addr(const mtd_spi_nor_t *dev, uint32_t *addr) return &((uint8_t*)addr)[4 - dev->params->addr_width]; } +static void mtd_spi_acquire(const mtd_spi_nor_t *dev) +{ + if (_is_qspi(dev)) { + qspi_acquire(_get_qspi(dev)); + } else if (_is_spi(dev)) + { + spi_acquire(_get_spi(dev), dev->params->cs, + dev->params->mode, dev->params->clk); + } +} + +static void mtd_spi_release(const mtd_spi_nor_t *dev) +{ + if (_is_qspi(dev)) { + qspi_release(_get_qspi(dev)); + } else if (_is_spi(dev)) + { + spi_release(_get_spi(dev)); + } +} + /** * @internal * @brief Send command opcode followed by address, followed by a read to buffer @@ -108,6 +145,13 @@ static void mtd_spi_cmd_addr_read(const mtd_spi_nor_t *dev, uint8_t opcode, TRACE("mtd_spi_cmd_addr_read: %p, %02x, (%06"PRIx32"), %p, %" PRIu32 "\n", (void *)dev, (unsigned int)opcode, addr, dest, count); + if (_is_qspi(dev)) { + qspi_read(_get_qspi(dev), addr, dest, count); + return; + } else if (!_is_spi(dev)) { + return; + } + uint8_t *addr_buf = _be_addr(dev, &addr); if (ENABLE_TRACE) { @@ -144,6 +188,17 @@ static void mtd_spi_cmd_addr_write(const mtd_spi_nor_t *dev, uint8_t opcode, TRACE("mtd_spi_cmd_addr_write: %p, %02x, (%06"PRIx32"), %p, %" PRIu32 "\n", (void *)dev, (unsigned int)opcode, addr, src, count); + if (_is_qspi(dev)) { + if (opcode == SFLASH_CMD_QUAD_PAGE_PROGRAM) { + qspi_write(_get_qspi(dev), addr, src, count); + } else { + qspi_erase(_get_qspi(dev), opcode, addr); + } + return; + } else if (!_is_spi(dev)) { + return; + } + uint8_t *addr_buf = _be_addr(dev, &addr); if (ENABLE_TRACE) { @@ -183,7 +238,11 @@ static void mtd_spi_cmd_read(const mtd_spi_nor_t *dev, uint8_t opcode, void *des TRACE("mtd_spi_cmd_read: %p, %02x, %p, %" PRIu32 "\n", (void *)dev, (unsigned int)opcode, dest, count); - spi_transfer_regs(_get_spi(dev), dev->params->cs, opcode, NULL, dest, count); + if (_is_qspi(dev)) { + qspi_cmd_read(_get_qspi(dev), opcode, dest, count); + } else if (_is_spi(dev)) { + spi_transfer_regs(_get_spi(dev), dev->params->cs, opcode, NULL, dest, count); + } } /** @@ -200,8 +259,12 @@ static void __attribute__((unused)) mtd_spi_cmd_write(const mtd_spi_nor_t *dev, TRACE("mtd_spi_cmd_write: %p, %02x, %p, %" PRIu32 "\n", (void *)dev, (unsigned int)opcode, src, count); - spi_transfer_regs(_get_spi(dev), dev->params->cs, opcode, - (void *)src, NULL, count); + if (_is_qspi(dev)) { + qspi_cmd_write(_get_qspi(dev), opcode, src, count); + } else if (_is_spi(dev)) { + spi_transfer_regs(_get_spi(dev), dev->params->cs, opcode, + (void *)src, NULL, count); + } } /** @@ -216,7 +279,11 @@ static void mtd_spi_cmd(const mtd_spi_nor_t *dev, uint8_t opcode) TRACE("mtd_spi_cmd: %p, %02x\n", (void *)dev, (unsigned int)opcode); - spi_transfer_byte(_get_spi(dev), dev->params->cs, false, opcode); + if (_is_qspi(dev)) { + qspi_cmd_write(_get_qspi(dev), opcode, NULL, 0); + } else if (_is_spi(dev)) { + spi_transfer_byte(_get_spi(dev), dev->params->cs, false, opcode); + } } static bool mtd_spi_manuf_match(const mtd_jedec_id_t *id, jedec_manuf_t manuf) @@ -363,6 +430,7 @@ static int mtd_spi_nor_power(mtd_dev_t *mtd, enum mtd_power_state power) mtd_spi_acquire(dev); switch (power) { case MTD_POWER_UP: + mtd_spi_cmd(dev, dev->params->opcode->wake); #if defined(MODULE_XTIMER) /* No sense in trying multiple times if no xtimer to wait between @@ -378,6 +446,12 @@ static int mtd_spi_nor_power(mtd_dev_t *mtd, enum mtd_power_state power) return -EIO; } #endif + /* configure QSPI */ + if (_is_qspi(dev)) { + qspi_configure(_get_qspi(dev), dev->params->mode, + dev->params->addr_width, dev->params->clk); + } + break; case MTD_POWER_DOWN: mtd_spi_cmd(dev, dev->params->opcode->sleep); @@ -401,8 +475,16 @@ static int mtd_spi_nor_init(mtd_dev_t *mtd) } /* CS */ - DEBUG("mtd_spi_nor_init: CS init\n"); - spi_init_cs(_get_spi(dev), dev->params->cs); + if (_is_spi(dev)) { + DEBUG("mtd_spi_nor_init: CS init\n"); + spi_init_cs(_get_spi(dev), dev->params->cs); + } + + /* configure QSPI */ + if (_is_qspi(dev)) { + qspi_configure(_get_qspi(dev), dev->params->mode, + dev->params->addr_width, dev->params->clk); + } /* power up the MTD device*/ DEBUG("mtd_spi_nor_init: power up MTD device"); diff --git a/drivers/mtd_spi_nor/mtd_spi_nor_configs.c b/drivers/mtd_spi_nor/mtd_spi_nor_configs.c index 65d674b0226d..7453e09b021e 100644 --- a/drivers/mtd_spi_nor/mtd_spi_nor_configs.c +++ b/drivers/mtd_spi_nor/mtd_spi_nor_configs.c @@ -42,6 +42,22 @@ const mtd_spi_nor_opcode_t mtd_spi_nor_opcode_default = { .wake = 0xab, }; +const mtd_spi_nor_opcode_t mtd_spi_nor_opcode_qspi = { + .rdid = 0x9f, + .wren = 0x06, + .rdsr = 0x05, + .wrsr = 0x01, + .read = SFLASH_CMD_QUAD_READ, + .read_fast = 0x0b, + .page_program = SFLASH_CMD_QUAD_PAGE_PROGRAM, + .sector_erase = 0x20, + .block_erase_32k = 0x52, + .block_erase_64k = 0xd8, + .chip_erase = 0xc7, + .sleep = 0xb9, + .wake = 0xab, +}; + const mtd_spi_nor_opcode_t mtd_spi_nor_opcode_default_4bytes = { .rdid = 0x9f, .wren = 0x06, diff --git a/makefiles/pseudomodules.inc.mk b/makefiles/pseudomodules.inc.mk index 388dcf4dc48c..bbb0c7b3d6b1 100644 --- a/makefiles/pseudomodules.inc.mk +++ b/makefiles/pseudomodules.inc.mk @@ -74,6 +74,7 @@ PSEUDOMODULES += log_color PSEUDOMODULES += lora PSEUDOMODULES += mpu_stack_guard PSEUDOMODULES += mpu_noexec_ram +PSEUDOMODULES += mtd_spi_nor_% PSEUDOMODULES += nanocoap_% PSEUDOMODULES += netdev_default PSEUDOMODULES += netdev_ieee802154_% From 6b93d0f28567560b66cde2a0a2d93a2217fbafd6 Mon Sep 17 00:00:00 2001 From: Benjamin Valentin Date: Wed, 28 Oct 2020 12:12:27 +0100 Subject: [PATCH 07/13] fixup! drivers/periph: add interface for QSPI --- drivers/include/periph/qspi.h | 51 ++++++++++++++++++++++++++--------- 1 file changed, 39 insertions(+), 12 deletions(-) diff --git a/drivers/include/periph/qspi.h b/drivers/include/periph/qspi.h index 6722d414130d..9ceab39931d8 100644 --- a/drivers/include/periph/qspi.h +++ b/drivers/include/periph/qspi.h @@ -105,6 +105,34 @@ typedef enum { } qspi_mode_t; #endif +/** + * @brief Standard QSPI erase block sizes + */ +#ifndef HAVE_QSPI_ERASE_SIZE_T +typedef enum { + QSPI_ERASE_4K = SFLASH_CMD_ERASE_SECTOR, + QSPI_ERASE_64K = SFLASH_CMD_ERASE_BLOCK, + QSPI_ERASE_CHIP = SFLASH_CMD_ERASE_CHIP, +} qspi_erase_size_t; +#endif + +/** + * @brief Configuration Options for QSPI + * @{ + */ +#define QSPI_FLAG_1BIT (0x0) /**< single data line */ +#define QSPI_FLAG_2BIT (0x1) /**< two parallel data lines */ +#define QSPI_FLAG_4BIT (0x2) /**< four parallel data lines */ +#define QSPI_FLAG_8BIT (0x3) /**< eight parallel data lines */ + +#define QSPI_FLAG_BIT_MASK (0x3) /**< mask to get data lines from flags */ +#define QSPI_FLAG_BIT(flags) ((flags) & QSPI_FLAG_BIT_MASK) + +#define QSPI_FLAG_DDR (0x4) /**< use Double Data Rate mode */ + +#define QSPI_FLAG_ADDR_32BIT (0x8) /**< use 32 bit addressing */ +/** @} */ + /** * @brief Basic initialization of the given QSPI bus * @@ -119,11 +147,10 @@ void qspi_init(qspi_t bus); * * @param[in] bus QSPI device to configure * @param[in] mode QSPI mode @see qspi_mode_t - * @param[in] addrlen Addressing mode (bytes) - * 3 for 24 bit addressing, 4 for 32 bit + * @param[in] flags QSPI Configuration Options * @param[in] clk_hz QSPI frequency in Hz */ -void qspi_configure(qspi_t bus, qspi_mode_t mode, uint8_t addrlen, uint32_t clk_hz); +void qspi_configure(qspi_t bus, qspi_mode_t mode, uint32_t flags, uint32_t clk_hz); /** * @brief Start a new QSPI transaction @@ -167,15 +194,6 @@ void qspi_cmd_read(qspi_t bus, uint8_t command, void *response, size_t len); */ void qspi_cmd_write(qspi_t bus, uint8_t command, const void *data, size_t len); -/** - * @brief Erase external memory by address - * - * @param[in] bus QSPI device - * @param[in] command The JEDEC command ID (sector erase, block erase) - * @param[in] address The address of the block that will be reased - */ -void qspi_erase(qspi_t bus, uint8_t command, uint32_t address); - /** * @brief Read data from external memory. * Typically it is implemented by quad read command (`0x6B`). @@ -199,6 +217,15 @@ void qspi_read(qspi_t bus, uint32_t addr, void *data, size_t len); */ void qspi_write(qspi_t bus, uint32_t addr, const void *data, size_t len); +/** + * @brief Erase external memory by address + * + * @param[in] bus QSPI device + * @param[in] address The address of the block that will be reased + * @param[in] size The erase block size to use + */ +void qspi_erase(qspi_t bus, uint32_t address, qspi_erase_size_t size); + #ifdef __cplusplus } #endif From a2284281f69e2574c267ed5ddc7e2c21cc5ff113 Mon Sep 17 00:00:00 2001 From: Benjamin Valentin Date: Wed, 28 Oct 2020 12:12:54 +0100 Subject: [PATCH 08/13] fixup! tests/periph_qspi: add QSPI test application --- tests/periph_qspi/main.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/tests/periph_qspi/main.c b/tests/periph_qspi/main.c index 73e354b3d398..f4e200a22401 100644 --- a/tests/periph_qspi/main.c +++ b/tests/periph_qspi/main.c @@ -101,10 +101,10 @@ static int cmd_erase(int argc, char **argv) addr = atoi(argv[2]); mode = atoi(argv[3]); - const spi_flash_cmd_t cmds[] = { - SFLASH_CMD_ERASE_SECTOR, - SFLASH_CMD_ERASE_BLOCK, - SFLASH_CMD_ERASE_CHIP, + const qspi_erase_size_t cmds[] = { + QSPI_ERASE_4K, + QSPI_ERASE_64K, + QSPI_ERASE_CHIP, }; if (mode > ARRAY_SIZE(cmds)) { @@ -113,8 +113,7 @@ static int cmd_erase(int argc, char **argv) } qspi_acquire(dev); - qspi_cmd_write(dev, SFLASH_CMD_WRITE_ENABLE, NULL, 0); - qspi_erase(dev, cmds[mode], addr); + qspi_erase(dev, addr, cmds[mode]); qspi_release(dev); return 0; @@ -138,7 +137,6 @@ static int cmd_write(int argc, char **argv) len = strlen(argv[3]); qspi_acquire(dev); - qspi_cmd_write(dev, SFLASH_CMD_WRITE_ENABLE, NULL, 0); qspi_write(dev, addr, argv[3], len); qspi_release(dev); @@ -204,13 +202,16 @@ static int cmd_cfg(int argc, char **argv) unsigned addr_len = atoi(argv[2]); unsigned freq_mhz = atoi(argv[3]); + uint32_t flags = QSPI_FLAG_4BIT + | (addr_len == 4 ? QSPI_FLAG_ADDR_32BIT : 0); + if (argc > 3) { mode = _qspi_mode(atoi(argv[4])); } printf("QSPI%s: %u-bit, %u MHz\n", argv[1], 8 * addr_len, freq_mhz); - qspi_configure(dev, mode, addr_len, MHZ(freq_mhz)); + qspi_configure(dev, mode, flags, MHZ(freq_mhz)); return 0; } From 5671190bfaf71093e40bf4794eb00906155e3e63 Mon Sep 17 00:00:00 2001 From: Benjamin Valentin Date: Wed, 28 Oct 2020 12:13:17 +0100 Subject: [PATCH 09/13] fixup! cpu/sam0_common: implement QSPI peripheral --- cpu/sam0_common/periph/qspi.c | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/cpu/sam0_common/periph/qspi.c b/cpu/sam0_common/periph/qspi.c index 99409594b52c..b31175042023 100644 --- a/cpu/sam0_common/periph/qspi.c +++ b/cpu/sam0_common/periph/qspi.c @@ -135,9 +135,9 @@ void qspi_read(qspi_t bus, uint32_t addr, void *data, size_t len) samd0_cache_enable(); } -void qspi_erase(qspi_t bus, uint8_t command, uint32_t address) +void qspi_erase(qspi_t bus, uint32_t address, qspi_erase_size_t size) { - (void)bus; + qspi_cmd_write(bus, SFLASH_CMD_WRITE_ENABLE, NULL, 0); uint32_t iframe = QSPI_INSTRFRAME_WIDTH_SINGLE_BIT_SPI | _qspi_addrlen @@ -145,12 +145,12 @@ void qspi_erase(qspi_t bus, uint8_t command, uint32_t address) | QSPI_INSTRFRAME_INSTREN | QSPI_INSTRFRAME_ADDREN; QSPI->INSTRADDR.reg = address; - _run_instruction(command, iframe, address, NULL, 0); + _run_instruction(size, iframe, address, NULL, 0); } void qspi_write(qspi_t bus, uint32_t addr, const void *data, size_t len) { - (void)bus; + qspi_cmd_write(bus, SFLASH_CMD_WRITE_ENABLE, NULL, 0); uint32_t iframe = QSPI_INSTRFRAME_WIDTH_QUAD_OUTPUT | _qspi_addrlen @@ -206,12 +206,13 @@ void qspi_init(qspi_t bus) | QSPI_CTRLB_DATALEN_8BITS | QSPI_CTRLB_CSMODE_LASTXFER; } -void qspi_configure(qspi_t bus, qspi_mode_t mode, uint8_t addrlen, uint32_t clk_hz) +void qspi_configure(qspi_t bus, qspi_mode_t mode, uint32_t flags, uint32_t clk_hz) { (void)bus; - assert(addrlen == 3 || addrlen == 4); assert(clk_hz <= CLOCK_CORECLOCK); + /* TODO: support other modes */ + assert(QSPI_FLAG_BIT(flags) == QSPI_FLAG_4BIT); qspi_acquire(bus); @@ -222,14 +223,12 @@ void qspi_configure(qspi_t bus, qspi_mode_t mode, uint8_t addrlen, uint32_t clk_ /* set addr len needs write enable */ qspi_cmd_write(bus, SFLASH_CMD_WRITE_ENABLE, NULL, 0); - if (addrlen == 3) { - qspi_cmd_write(bus, SFLASH_CMD_3_BYTE_ADDR, NULL, 0); - _qspi_addrlen = QSPI_INSTRFRAME_ADDRLEN_24BITS; - } - - if (addrlen == 4) { + if (flags & QSPI_FLAG_ADDR_32BIT) { qspi_cmd_write(bus, SFLASH_CMD_4_BYTE_ADDR, NULL, 0); _qspi_addrlen = QSPI_INSTRFRAME_ADDRLEN_32BITS; + } else { + qspi_cmd_write(bus, SFLASH_CMD_3_BYTE_ADDR, NULL, 0); + _qspi_addrlen = QSPI_INSTRFRAME_ADDRLEN_24BITS; } qspi_release(bus); From fc053b09416395e1c8945f9900270135cc0064b4 Mon Sep 17 00:00:00 2001 From: Benjamin Valentin Date: Wed, 28 Oct 2020 17:43:12 +0100 Subject: [PATCH 10/13] fixup! drivers/mtd_spi_nor: add support for QSPI backend --- drivers/mtd_spi_nor/mtd_spi_nor.c | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/drivers/mtd_spi_nor/mtd_spi_nor.c b/drivers/mtd_spi_nor/mtd_spi_nor.c index ca0f7c341caa..951c4eb6c1e8 100644 --- a/drivers/mtd_spi_nor/mtd_spi_nor.c +++ b/drivers/mtd_spi_nor/mtd_spi_nor.c @@ -286,6 +286,16 @@ static void mtd_spi_cmd(const mtd_spi_nor_t *dev, uint8_t opcode) } } +static void _write_enable(const mtd_spi_nor_t *dev) +{ + /* QSPI handles write enable internally */ + if (_is_qspi(dev)) { + return; + } + + mtd_spi_cmd(dev, dev->params->opcode->wren); +} + static bool mtd_spi_manuf_match(const mtd_jedec_id_t *id, jedec_manuf_t manuf) { return manuf == ((id->bank << 8) | id->manuf); @@ -448,8 +458,12 @@ static int mtd_spi_nor_power(mtd_dev_t *mtd, enum mtd_power_state power) #endif /* configure QSPI */ if (_is_qspi(dev)) { + + uint32_t flags = QSPI_FLAG_4BIT + | (dev->params->addr_width == 4 ? QSPI_FLAG_ADDR_32BIT : 0); + qspi_configure(_get_qspi(dev), dev->params->mode, - dev->params->addr_width, dev->params->clk); + flags, dev->params->clk); } break; @@ -480,12 +494,6 @@ static int mtd_spi_nor_init(mtd_dev_t *mtd) spi_init_cs(_get_spi(dev), dev->params->cs); } - /* configure QSPI */ - if (_is_qspi(dev)) { - qspi_configure(_get_qspi(dev), dev->params->mode, - dev->params->addr_width, dev->params->clk); - } - /* power up the MTD device*/ DEBUG("mtd_spi_nor_init: power up MTD device"); if (mtd_spi_nor_power(mtd, MTD_POWER_UP)) { @@ -611,7 +619,7 @@ static int mtd_spi_nor_write(mtd_dev_t *mtd, const void *src, uint32_t addr, uin mtd_spi_acquire(dev); /* write enable */ - mtd_spi_cmd(dev, dev->params->opcode->wren); + _write_enable(dev); /* Page program */ mtd_spi_cmd_addr_write(dev, dev->params->opcode->page_program, addr, src, size); @@ -640,7 +648,7 @@ static int mtd_spi_nor_write_page(mtd_dev_t *mtd, const void *src, uint32_t page mtd_spi_acquire(dev); /* write enable */ - mtd_spi_cmd(dev, dev->params->opcode->wren); + _write_enable(dev); /* Page program */ mtd_spi_cmd_addr_write(dev, dev->params->opcode->page_program, addr, src, size); @@ -682,7 +690,7 @@ static int mtd_spi_nor_erase(mtd_dev_t *mtd, uint32_t addr, uint32_t size) uint32_t us; /* write enable */ - mtd_spi_cmd(dev, dev->params->opcode->wren); + _write_enable(dev); if (size == total_size) { mtd_spi_cmd(dev, dev->params->opcode->chip_erase); From 35972c005ccb0914b93fe29986b4794353380574 Mon Sep 17 00:00:00 2001 From: Benjamin Valentin Date: Wed, 28 Oct 2020 17:43:43 +0100 Subject: [PATCH 11/13] fixup! fixup! cpu/sam0_common: implement QSPI peripheral --- cpu/sam0_common/periph/qspi.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/cpu/sam0_common/periph/qspi.c b/cpu/sam0_common/periph/qspi.c index b31175042023..0ea06cc0ef98 100644 --- a/cpu/sam0_common/periph/qspi.c +++ b/cpu/sam0_common/periph/qspi.c @@ -214,8 +214,6 @@ void qspi_configure(qspi_t bus, qspi_mode_t mode, uint32_t flags, uint32_t clk_h /* TODO: support other modes */ assert(QSPI_FLAG_BIT(flags) == QSPI_FLAG_4BIT); - qspi_acquire(bus); - QSPI->BAUD.reg = QSPI_BAUD_BAUD((CLOCK_CORECLOCK + clk_hz - 1) / clk_hz) | mode; @@ -230,6 +228,4 @@ void qspi_configure(qspi_t bus, qspi_mode_t mode, uint32_t flags, uint32_t clk_h qspi_cmd_write(bus, SFLASH_CMD_3_BYTE_ADDR, NULL, 0); _qspi_addrlen = QSPI_INSTRFRAME_ADDRLEN_24BITS; } - - qspi_release(bus); } From a05e4e806913ee1c288b14918bb29acdc45eb277 Mon Sep 17 00:00:00 2001 From: Benjamin Valentin Date: Sat, 31 Oct 2020 18:39:20 +0100 Subject: [PATCH 12/13] fixup! fixup! drivers/periph: add interface for QSPI --- drivers/include/periph/qspi.h | 81 ++++++++++++++++++++++++----------- 1 file changed, 57 insertions(+), 24 deletions(-) diff --git a/drivers/include/periph/qspi.h b/drivers/include/periph/qspi.h index 9ceab39931d8..79f020abc930 100644 --- a/drivers/include/periph/qspi.h +++ b/drivers/include/periph/qspi.h @@ -145,6 +145,8 @@ void qspi_init(qspi_t bus); /** * @brief Configure the QSPI peripheral settings * + * @pre The @p bus has been acquired with @ref qspi_acquire + * * @param[in] bus QSPI device to configure * @param[in] mode QSPI mode @see qspi_mode_t * @param[in] flags QSPI Configuration Options @@ -167,62 +169,93 @@ void qspi_acquire(qspi_t bus); /** * @brief Finish an ongoing QSPI transaction by releasing the given QSPI bus * + * @pre The @p bus has been acquired with @ref qspi_acquire + * * After release, the given SPI bus should be fully powered down until acquired * again. * - * @param[in] bus QSPI device to release + * @param[in] bus QSPI device to release */ void qspi_release(qspi_t bus); /** * @brief Execute a command with response data e.g Read Status, Read JEDEC * - * @param[in] bus QSPI device - * @param[in] command The JEDEC command ID - * @param[out] response Command response data, may be NULL - * @param[in] len Size of the command response buffer + * @pre The @p bus has been acquired with @ref qspi_acquire + * + * @param[in] bus QSPI device + * @param[in] command The JEDEC command ID + * @param[out] response Buffer for command response + * @param[in] len Size of the command response buffer + * + * @return length of the response + * negative error */ -void qspi_cmd_read(qspi_t bus, uint8_t command, void *response, size_t len); +ssize_t qspi_cmd_read(qspi_t bus, uint8_t command, void *response, size_t len); /** * @brief Execute a command with data e.g Write Status, Write Enable * - * @param[in] bus QSPI device - * @param[in] command The JEDEC command ID - * @param[in] data Command parameter data, may be NULL - * @param[in] len Size of the command response buffer + * @pre The @p bus has been acquired with @ref qspi_acquire + * + * @param[in] bus QSPI device + * @param[in] command The JEDEC command ID + * @param[in] data Command parameter data, may be NULL + * @param[in] len Size of the command response buffer + * + * @return number of bytes written + * negative error */ -void qspi_cmd_write(qspi_t bus, uint8_t command, const void *data, size_t len); +ssize_t qspi_cmd_write(qspi_t bus, uint8_t command, const void *data, size_t len); /** * @brief Read data from external memory. * Typically it is implemented by quad read command (`0x6B`). * - * @param[in] bus QSPI device - * @param[in] address Address to read from - * @param[out] data Buffer to store the data - * @param[in] len Nmber of byte to read + * @pre The @p bus has been acquired with @ref qspi_acquire + * + * @param[in] bus QSPI device + * @param[in] address Address to read from + * @param[out] data Buffer to store the data + * @param[in] len Number of byte to read + * + * @return length of bytes read, may be smaller than @p len if the + * driver has a maximal transfer size. + * negative error */ -void qspi_read(qspi_t bus, uint32_t addr, void *data, size_t len); +ssize_t qspi_read(qspi_t bus, uint32_t addr, void *data, size_t len); /** * @brief Write data to external memory. * Can only write 1 -> 0, so target region should erased first. * Typically it uses quad write command (`0x32`). * - * @param[in] bus QSPI device - * @param[in] address Address to write to - * @param[in] data Buffer to write - * @param[in] len Nmber of byte to write + * The QSPI driver will take care of setting the Write Enable + * on the external flash. + * + * @pre The @p bus has been acquired with @ref qspi_acquire + * + * @param[in] bus QSPI device + * @param[in] address Address to write to + * @param[in] data Buffer to write + * @param[in] len Number of byte to write + * + * @return number of bytes written, may be smaller than @p len + * negative error */ -void qspi_write(qspi_t bus, uint32_t addr, const void *data, size_t len); +ssize_t qspi_write(qspi_t bus, uint32_t addr, const void *data, size_t len); /** * @brief Erase external memory by address * - * @param[in] bus QSPI device - * @param[in] address The address of the block that will be reased - * @param[in] size The erase block size to use + * The QSPI driver will take care of setting the Write Enable + * on the external flash. + * + * @pre The @p bus has been acquired with @ref qspi_acquire + * + * @param[in] bus QSPI device + * @param[in] address The address of the block that will be erased + * @param[in] size The erase block size to use */ void qspi_erase(qspi_t bus, uint32_t address, qspi_erase_size_t size); From 2836218a8a7297baa7fa8db67599cd38451cc5f1 Mon Sep 17 00:00:00 2001 From: Benjamin Valentin Date: Sat, 31 Oct 2020 18:41:08 +0100 Subject: [PATCH 13/13] fixup! fixup! fixup! cpu/sam0_common: implement QSPI peripheral --- cpu/sam0_common/periph/qspi.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/cpu/sam0_common/periph/qspi.c b/cpu/sam0_common/periph/qspi.c index 0ea06cc0ef98..e230ff1e655b 100644 --- a/cpu/sam0_common/periph/qspi.c +++ b/cpu/sam0_common/periph/qspi.c @@ -87,7 +87,7 @@ static void _run_instruction(uint8_t command, uint32_t iframe, uint32_t addr, QSPI->INTFLAG.reg = QSPI_INTFLAG_INSTREND; } -void qspi_cmd_read(qspi_t bus, uint8_t command, void *response, size_t len) +ssize_t qspi_cmd_read(qspi_t bus, uint8_t command, void *response, size_t len) { (void)bus; @@ -100,9 +100,11 @@ void qspi_cmd_read(qspi_t bus, uint8_t command, void *response, size_t len) samd0_cache_disable(); _run_instruction(command, iframe, 0, response, len); samd0_cache_enable(); + + return len; } -void qspi_cmd_write(qspi_t bus, uint8_t command, const void *data, size_t len) +ssize_t qspi_cmd_write(qspi_t bus, uint8_t command, const void *data, size_t len) { (void)bus; @@ -115,9 +117,11 @@ void qspi_cmd_write(qspi_t bus, uint8_t command, const void *data, size_t len) samd0_cache_disable(); _run_instruction(command, iframe, 0, (void *)data, len); samd0_cache_enable(); + + return len; } -void qspi_read(qspi_t bus, uint32_t addr, void *data, size_t len) +ssize_t qspi_read(qspi_t bus, uint32_t addr, void *data, size_t len) { (void)bus; @@ -133,6 +137,8 @@ void qspi_read(qspi_t bus, uint32_t addr, void *data, size_t len) samd0_cache_disable(); _run_instruction(SFLASH_CMD_QUAD_READ, iframe, addr, data, len); samd0_cache_enable(); + + return len; } void qspi_erase(qspi_t bus, uint32_t address, qspi_erase_size_t size) @@ -148,7 +154,7 @@ void qspi_erase(qspi_t bus, uint32_t address, qspi_erase_size_t size) _run_instruction(size, iframe, address, NULL, 0); } -void qspi_write(qspi_t bus, uint32_t addr, const void *data, size_t len) +ssize_t qspi_write(qspi_t bus, uint32_t addr, const void *data, size_t len) { qspi_cmd_write(bus, SFLASH_CMD_WRITE_ENABLE, NULL, 0); @@ -161,6 +167,8 @@ void qspi_write(qspi_t bus, uint32_t addr, const void *data, size_t len) samd0_cache_disable(); _run_instruction(SFLASH_CMD_QUAD_PAGE_PROGRAM, iframe, addr, (void *)data, len); samd0_cache_enable(); + + return len; } void qspi_acquire(qspi_t bus)