diff --git a/drivers/Makefile.dep b/drivers/Makefile.dep index 322ab0d32f..3e891a1e8a 100644 --- a/drivers/Makefile.dep +++ b/drivers/Makefile.dep @@ -218,3 +218,8 @@ endif ifneq (,$(filter feetech,$(USEMODULE))) USEMODULE += uart_half_duplex endif + +ifneq (,$(filter mtd_spi_nor,$(USEMODULE))) + USEMODULE += mtd + FEATURES_REQUIRED += periph_spi +endif diff --git a/drivers/include/mtd_spi_nor.h b/drivers/include/mtd_spi_nor.h new file mode 100644 index 0000000000..a8cb949c9b --- /dev/null +++ b/drivers/include/mtd_spi_nor.h @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2016 Eistec AB + * 2017 OTA keys S.A. + * + * 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_mtd_spi_nor Serial NOR flash + * @ingroup drivers_storage + * @brief Driver for serial NOR flash memory technology devices attached via SPI + * + * @{ + * + * @file + * @brief Interface definition for the serial flash memory driver + * + * @author Joakim Nohlgård + * @author Vincent Dupont + */ + +#ifndef MTD_SPI_NOR_H_ +#define MTD_SPI_NOR_H_ + +#include + +#include "periph_conf.h" +#include "periph/spi.h" +#include "periph/gpio.h" +#include "mtd.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @brief SPI NOR flash opcode table + */ +typedef struct { + uint8_t rdid; /**< Read identification (JEDEC ID) */ + uint8_t wren; /**< Write enable */ + uint8_t rdsr; /**< Read status register */ + uint8_t wrsr; /**< Write status register */ + uint8_t read; /**< Read data bytes, 3 byte address */ + uint8_t read_fast; /**< Read data bytes, 3 byte address, at higher speed */ + uint8_t page_program; /**< Page program */ + uint8_t sector_erase; /**< Block erase 4 KiB */ + uint8_t block_erase_32k; /**< 32KiB block erase */ + uint8_t block_erase; /**< Block erase (usually 64 KiB) */ + uint8_t chip_erase; /**< Chip erase */ + uint8_t sleep; /**< Deep power down */ + uint8_t wake; /**< Release from deep power down */ + /* TODO: enter 4 byte address mode for large memories */ +} mtd_spi_nor_opcode_t; + +/** + * @brief Internal representation of JEDEC memory ID codes. + * + * @see http://www.jedec.org/standards-documents/results/jep106 + */ +typedef struct __attribute__((packed)) { + uint8_t bank; /**< Manufacturer ID bank number, 1 through 10, see JEP106 */ + uint8_t manuf; /**< Manufacturer ID, 1 byte */ + uint8_t device[2]; /**< Device ID, 2 bytes */ +} mtd_jedec_id_t; + +/** + * @brief Byte to signal increment bank number when reading manufacturer ID + * + * @see http://www.jedec.org/standards-documents/results/jep106 + */ +#define JEDEC_NEXT_BANK (0x7f) + +/** + * @brief Flag to set when the device support 4KiB sector erase (sector_erase opcode) + */ +#define SPI_NOR_F_SECT_4K (1) +/** + * @brief Flag to set when the device support 32KiB block erase (block_erase_32k opcode) + */ +#define SPI_NOR_F_SECT_32K (2) + +/** + * @brief Device descriptor for serial flash memory devices + * + * This is an extension of the @c mtd_dev_t struct + */ +typedef struct { + mtd_dev_t base; /**< inherit from mtd_dev_t object */ + const mtd_spi_nor_opcode_t *opcode; /**< Opcode table for the device */ + spi_t spi; /**< SPI bus the device is connected to */ + gpio_t cs; /**< CS pin GPIO handle */ + spi_mode_t mode; /**< SPI mode */ + spi_clk_t clk; /**< SPI clock */ + uint16_t flag; /**< Config flags */ + mtd_jedec_id_t jedec_id; /**< JEDEC ID of the chip */ + /** + * @brief bitmask to corresponding to the page address + * + * Computed by mtd_spi_nor_init, no need to touch outside the driver. + */ + uint32_t page_addr_mask; + /** + * @brief bitmask to corresponding to the sector address + * + * Computed by mtd_spi_nor_init, no need to touch outside the driver. + */ + uint32_t sec_addr_mask; + uint8_t addr_width; /**< Number of bytes in addresses, usually 3 for small devices */ + /** + * @brief number of right shifts to get the address to the start of the page + * + * Computed by mtd_spi_nor_init, no need to touch outside the driver. + */ + uint8_t page_addr_shift; + /** + * @brief number of right shifts to get the address to the start of the sector + * + * Computed by mtd_spi_nor_init, no need to touch outside the driver. + */ + uint8_t sec_addr_shift; +} mtd_spi_nor_t; + +/** + * @brief NOR flash SPI MTD device operations table + */ +extern const mtd_desc_t mtd_spi_nor_driver; + +/* Available opcode tables for known devices */ +/* Defined in mtd_spi_nor_configs.c */ +/** + * @brief Default command opcodes + * + * The numbers were taken from Micron M25P16, but the same opcodes can + * be found in Macronix MX25L25735E, and multiple other data sheets for + * different devices, as well as in the Linux kernel, so they seem quite + * sensible for default values. */ +extern const mtd_spi_nor_opcode_t mtd_spi_nor_opcode_default; + +#ifdef __cplusplus +} +#endif + +#endif /* MTD_SPI_NOR_H_ */ +/** @} */ diff --git a/drivers/mtd_spi_nor/Makefile b/drivers/mtd_spi_nor/Makefile new file mode 100644 index 0000000000..48422e909a --- /dev/null +++ b/drivers/mtd_spi_nor/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/drivers/mtd_spi_nor/mtd_spi_nor.c b/drivers/mtd_spi_nor/mtd_spi_nor.c new file mode 100644 index 0000000000..ed1c0d40f0 --- /dev/null +++ b/drivers/mtd_spi_nor/mtd_spi_nor.c @@ -0,0 +1,505 @@ +/* + * Copyright (C) 2016 Eistec AB + * 2017 OTA keys S.A. + * + * 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 drivers_mtd_spi_nor + * @{ + * + * @file + * @brief Driver for serial flash memory attached to SPI + * + * @author Joakim Nohlgård + * @author Vincent Dupont + * + * @} + */ + +#include +#include + +#include "mtd.h" +#if MODULE_XTIMER +#include "xtimer.h" +#include "timex.h" +#else +#include "thread.h" +#endif +#include "byteorder.h" +#include "mtd_spi_nor.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" +#define ENABLE_TRACE (0) + +#if ENABLE_TRACE +#define TRACE(...) DEBUG(__VA_ARGS__) +#else +#define TRACE(...) +#endif + +#ifndef MTD_SPI_NOR_WRITE_WAIT_US +#define MTD_SPI_NOR_WRITE_WAIT_US (50 * US_PER_MS) +#endif + +static int mtd_spi_nor_init(mtd_dev_t *mtd); +static int mtd_spi_nor_read(mtd_dev_t *mtd, void *dest, uint32_t addr, uint32_t size); +static int mtd_spi_nor_write(mtd_dev_t *mtd, const void *src, uint32_t addr, uint32_t size); +static int mtd_spi_nor_erase(mtd_dev_t *mtd, uint32_t addr, uint32_t size); +static int mtd_spi_nor_power(mtd_dev_t *mtd, enum mtd_power_state power); + +const mtd_desc_t mtd_spi_nor_driver = { + .init = mtd_spi_nor_init, + .read = mtd_spi_nor_read, + .write = mtd_spi_nor_write, + .erase = mtd_spi_nor_erase, + .power = mtd_spi_nor_power, +}; + +/** + * @internal + * @brief Send command opcode followed by address, followed by a read to buffer + * + * @param[in] dev pointer to device descriptor + * @param[in] opcode command opcode + * @param[in] addr address (big endian) + * @param[out] dest read buffer + * @param[in] count number of bytes to read after the address has been sent + */ +static void mtd_spi_cmd_addr_read(mtd_spi_nor_t *dev, uint8_t opcode, + be_uint32_t addr, void* dest, uint32_t count) +{ + TRACE("mtd_spi_cmd_addr_read: %p, %02x, (%02x %02x %02x %02x), %p, %" PRIu32 "\n", + (void *)dev, (unsigned int)opcode, addr.u8[0], addr.u8[1], addr.u8[2], + addr.u8[3], dest, count); + + uint8_t *addr_buf = &addr.u8[4 - dev->addr_width]; + if (ENABLE_TRACE) { + TRACE("mtd_spi_cmd_addr_read: addr:"); + for (unsigned int i = 0; i < dev->addr_width; ++i) + { + TRACE(" %02x", addr_buf[i]); + } + TRACE("\n"); + } + /* Acquire exclusive access to the bus. */ + spi_acquire(dev->spi, dev->cs, dev->mode, dev->clk); + + do { + /* Send opcode followed by address */ + spi_transfer_byte(dev->spi, dev->cs, true, opcode); + spi_transfer_bytes(dev->spi, dev->cs, true, (char *)addr_buf, NULL, dev->addr_width); + + /* Read data */ + spi_transfer_bytes(dev->spi, dev->cs, false, NULL, dest, count); + } while(0); + + /* Release the bus for other threads. */ + spi_release(dev->spi); +} + +/** + * @internal + * @brief Send command opcode followed by address, followed by a write from buffer + * + * @param[in] dev pointer to device descriptor + * @param[in] opcode command opcode + * @param[in] addr address (big endian) + * @param[out] src write buffer + * @param[in] count number of bytes to write after the opcode has been sent + */ +static void mtd_spi_cmd_addr_write(mtd_spi_nor_t *dev, uint8_t opcode, + be_uint32_t addr, const void* src, uint32_t count) +{ + TRACE("mtd_spi_cmd_addr_write: %p, %02x, (%02x %02x %02x %02x), %p, %" PRIu32 "\n", + (void *)dev, (unsigned int)opcode, addr.u8[0], addr.u8[1], addr.u8[2], + addr.u8[3], src, count); + + uint8_t *addr_buf = &addr.u8[4 - dev->addr_width]; + if (ENABLE_TRACE) { + TRACE("mtd_spi_cmd_addr_write: addr:"); + for (unsigned int i = 0; i < dev->addr_width; ++i) + { + TRACE(" %02x", addr_buf[i]); + } + TRACE("\n"); + } + /* Acquire exclusive access to the bus. */ + spi_acquire(dev->spi, dev->cs, dev->mode, dev->clk); + + do { + /* Send opcode followed by address */ + spi_transfer_byte(dev->spi, dev->cs, true, opcode); + bool cont = (count > 0); /* only keep CS asserted when there is data that follows */ + spi_transfer_bytes(dev->spi, dev->cs, cont, (char *)addr_buf, NULL, dev->addr_width); + /* Write data */ + if (cont) { + spi_transfer_bytes(dev->spi, dev->cs, false, (void *)src, NULL, count); + } + } while(0); + + /* Release the bus for other threads. */ + spi_release(dev->spi); +} + +/** + * @internal + * @brief Send command opcode followed by a read to buffer + * + * @param[in] dev pointer to device descriptor + * @param[in] opcode command opcode + * @param[out] dest read buffer + * @param[in] count number of bytes to write after the opcode has been sent + */ +static void mtd_spi_cmd_read(mtd_spi_nor_t *dev, uint8_t opcode, void* dest, uint32_t count) +{ + TRACE("mtd_spi_cmd_read: %p, %02x, %p, %" PRIu32 "\n", + (void *)dev, (unsigned int)opcode, dest, count); + /* Acquire exclusive access to the bus. */ + spi_acquire(dev->spi, dev->cs, dev->mode, dev->clk); + + spi_transfer_regs(dev->spi, dev->cs, opcode, NULL, dest, count); + + /* Release the bus for other threads. */ + spi_release(dev->spi); +} + +/** + * @internal + * @brief Send command opcode followed by a write from buffer + * + * @param[in] dev pointer to device descriptor + * @param[in] opcode command opcode + * @param[out] src write buffer + * @param[in] count number of bytes to write after the opcode has been sent + */ +static void __attribute__((unused)) mtd_spi_cmd_write(mtd_spi_nor_t *dev, uint8_t opcode, const void* src, uint32_t count) +{ + TRACE("mtd_spi_cmd_write: %p, %02x, %p, %" PRIu32 "\n", + (void *)dev, (unsigned int)opcode, src, count); + /* Acquire exclusive access to the bus. */ + spi_acquire(dev->spi, dev->cs, dev->mode, dev->clk); + + spi_transfer_regs(dev->spi, dev->cs, opcode, (void *)src, NULL, count); + + /* Release the bus for other threads. */ + spi_release(dev->spi); +} + +/** + * @internal + * @brief Send command opcode + * + * @param[in] dev pointer to device descriptor + * @param[in] opcode command opcode + */ +static void mtd_spi_cmd(mtd_spi_nor_t *dev, uint8_t opcode) +{ + TRACE("mtd_spi_cmd: %p, %02x\n", + (void *)dev, (unsigned int)opcode); + /* Acquire exclusive access to the bus. */ + spi_acquire(dev->spi, dev->cs, dev->mode, dev->clk); + + spi_transfer_byte(dev->spi, dev->cs, false, opcode); + + /* Release the bus for other threads. */ + spi_release(dev->spi); +} + +/** + * @internal + * @brief Compute 8 bit parity + */ +static inline uint8_t parity8(uint8_t x) +{ + /* Taken from http://stackoverflow.com/a/21618038/1805713 */ + x ^= x >> 4; + x ^= x >> 2; + x ^= x >> 1; + return (x & 1); +} + +/** + * @internal + * @brief Read JEDEC ID + */ +static int mtd_spi_read_jedec_id(mtd_spi_nor_t *dev, mtd_jedec_id_t *out) +{ + /* not using above read functions because of variable length rdid response */ + int status = 0; + mtd_jedec_id_t jedec; + + DEBUG("mtd_spi_read_jedec_id: rdid=0x%02x\n", (unsigned int)dev->opcode->rdid); + + /* Acquire exclusive access to the bus. */ + spi_acquire(dev->spi, dev->cs, dev->mode, dev->clk); + + /* Send opcode */ + spi_transfer_byte(dev->spi, dev->cs, true, dev->opcode->rdid); + + /* Read manufacturer ID */ + jedec.bank = 1; + while (status == 0) { + jedec.manuf = spi_transfer_byte(dev->spi, dev->cs, true, 0); + if (jedec.manuf == JEDEC_NEXT_BANK) { + /* next bank, see JEP106 */ + DEBUG("mtd_spi_read_jedec_id: manuf bank incr\n"); + ++jedec.bank; + continue; + } + if (parity8(jedec.manuf) == 0) { + /* saw even parity, we expected odd parity => parity error */ + DEBUG("mtd_spi_read_jedec_id: Parity error (0x%02x)\n", (unsigned int)jedec.manuf); + status = -2; + break; + } + else { + /* all OK! */ + break; + } + } + DEBUG("mtd_spi_read_jedec_id: bank=%u manuf=0x%02x\n", (unsigned int)jedec.bank, + (unsigned int)jedec.manuf); + + /* Read device ID */ + if (status == 0) { + spi_transfer_bytes(dev->spi, dev->cs, false, NULL, (char *)&jedec.device[0], sizeof(jedec.device)); + } + DEBUG("mtd_spi_read_jedec_id: device=0x%02x, 0x%02x\n", + (unsigned int)jedec.device[0], (unsigned int)jedec.device[1]); + + if (status == 0) { + *out = jedec; + } + + /* Release the bus for other threads. */ + spi_release(dev->spi); + return status; +} + +static inline void wait_for_write_complete(mtd_spi_nor_t *dev) +{ + do { + uint8_t status; + mtd_spi_cmd_read(dev, dev->opcode->rdsr, &status, sizeof(status)); + + TRACE("mtd_spi_nor: wait device status = 0x%02x\n", (unsigned int)status); + if ((status & 1) == 0) { /* TODO magic number */ + break; + } +#if MODULE_XTIMER + xtimer_usleep(MTD_SPI_NOR_WRITE_WAIT_US); +#else + thread_yield(); +#endif + } while (1); +} + +static int mtd_spi_nor_init(mtd_dev_t *mtd) +{ + DEBUG("mtd_spi_nor_init: %p\n", (void *)mtd); + mtd_spi_nor_t *dev = (mtd_spi_nor_t *)mtd; + + DEBUG("mtd_spi_nor_init: -> spi: %lx, cs: %lx, opcodes: %p\n", + (unsigned long)dev->spi, (unsigned long)dev->cs, (void *)dev->opcode); + + DEBUG("mtd_spi_nor_init: %" PRIu32 " bytes " + "(%" PRIu32 " sectors, %" PRIu32 " bytes/sector, " + "%" PRIu32 " pages, " + "%" PRIu32 " pages/sector, %" PRIu32 " bytes/page)\n", + mtd->pages_per_sector * mtd->sector_count * mtd->page_size, + mtd->sector_count, mtd->pages_per_sector * mtd->page_size, + mtd->pages_per_sector * mtd->sector_count, + mtd->pages_per_sector, mtd->page_size); + DEBUG("mtd_spi_nor_init: Using %u byte addresses\n", dev->addr_width); + + if (dev->addr_width == 0) { + return -EINVAL; + } + + /* CS */ + DEBUG("mtd_spi_nor_init: CS init\n"); + spi_init_cs(dev->spi, dev->cs); + + int res = mtd_spi_read_jedec_id(dev, &dev->jedec_id); + if (res < 0) { + return -EIO; + } + DEBUG("mtd_spi_nor_init: Found chip with ID: (%d, 0x%02x, 0x%02x, 0x%02x)\n", + dev->jedec_id.bank, dev->jedec_id.manuf, dev->jedec_id.device[0], dev->jedec_id.device[1]); + + uint8_t status; + mtd_spi_cmd_read(dev, dev->opcode->rdsr, &status, sizeof(status)); + DEBUG("mtd_spi_nor_init: device status = 0x%02x\n", (unsigned int)status); + + /* check whether page size and sector size are powers of two (most chips' are) + * and compute the number of shifts needed to get the page and sector addresses + * from a byte address */ + uint8_t shift = 0; + uint32_t page_size = mtd->page_size; + uint32_t mask = 0; + if ((page_size & (page_size - 1)) == 0) { + while ((page_size >> shift) > 1) { + ++shift; + } + mask = (UINT32_MAX << shift); + } + dev->page_addr_mask = mask; + dev->page_addr_shift = shift; + DEBUG("mtd_spi_nor_init: page_addr_mask = 0x%08" PRIx32 ", page_addr_shift = %u\n", + mask, (unsigned int)shift); + + mask = 0; + shift = 0; + uint32_t sector_size = mtd->page_size * mtd->pages_per_sector; + if ((sector_size & (sector_size - 1)) == 0) { + while ((sector_size >> shift) > 1) { + ++shift; + } + mask = (UINT32_MAX << shift); + } + dev->sec_addr_mask = mask; + dev->sec_addr_shift = shift; + + DEBUG("mtd_spi_nor_init: sec_addr_mask = 0x%08" PRIx32 ", sec_addr_shift = %u\n", + mask, (unsigned int)shift); + + return 0; +} + +static int mtd_spi_nor_read(mtd_dev_t *mtd, void *dest, uint32_t addr, uint32_t size) +{ + DEBUG("mtd_spi_nor_read: %p, %p, 0x%" PRIx32 ", 0x%" PRIx32 "\n", + (void *)mtd, dest, addr, size); + mtd_spi_nor_t *dev = (mtd_spi_nor_t *)mtd; + size_t chipsize = mtd->page_size * mtd->pages_per_sector * mtd->sector_count; + if (addr > chipsize) { + return -EOVERFLOW; + } + if (size > mtd->page_size) { + size = mtd->page_size; + } + if ((addr + size) > chipsize) { + size = chipsize - addr; + } + uint32_t page_addr_mask = dev->page_addr_mask; + if ((addr & page_addr_mask) != ((addr + size - 1) & page_addr_mask)) { + /* Reads across page boundaries must be split */ + size = mtd->page_size - (addr & ~(page_addr_mask)); + } + if (size == 0) { + return 0; + } + be_uint32_t addr_be = byteorder_htonl(addr); + mtd_spi_cmd_addr_read(dev, dev->opcode->read, addr_be, dest, size); + + return size; +} + +static int mtd_spi_nor_write(mtd_dev_t *mtd, const void *src, uint32_t addr, uint32_t size) +{ + uint32_t total_size = mtd->page_size * mtd->pages_per_sector * mtd->sector_count; + DEBUG("mtd_spi_nor_write: %p, %p, 0x%" PRIx32 ", 0x%" PRIx32 "\n", + (void *)mtd, src, addr, size); + if (size == 0) { + return 0; + } + mtd_spi_nor_t *dev = (mtd_spi_nor_t *)mtd; + if (size > mtd->page_size) { + DEBUG("mtd_spi_nor_write: ERR: page program >1 page (%" PRIu32 ")!\n", mtd->page_size); + return -EOVERFLOW; + } + if (dev->page_addr_mask && + ((addr & dev->page_addr_mask) != ((addr + size - 1) & dev->page_addr_mask))) { + DEBUG("mtd_spi_nor_write: ERR: page program spans page boundary!\n"); + return -EOVERFLOW; + } + if (addr + size > total_size) { + return -EOVERFLOW; + } + be_uint32_t addr_be = byteorder_htonl(addr); + + /* write enable */ + mtd_spi_cmd(dev, dev->opcode->wren); + + /* Page program */ + mtd_spi_cmd_addr_write(dev, dev->opcode->page_program, addr_be, src, size); + + /* waiting for the command to complete before returning */ + wait_for_write_complete(dev); + return size; +} + +static int mtd_spi_nor_erase(mtd_dev_t *mtd, uint32_t addr, uint32_t size) +{ + DEBUG("mtd_spi_nor_erase: %p, 0x%" PRIx32 ", 0x%" PRIx32 "\n", + (void *)mtd, addr, size); + mtd_spi_nor_t *dev = (mtd_spi_nor_t *)mtd; + uint32_t sector_size = mtd->page_size * mtd->pages_per_sector; + uint32_t total_size = sector_size * mtd->sector_count; + + if (dev->sec_addr_mask && + ((addr & ~dev->sec_addr_mask) != 0)) { + /* This is not a requirement in hardware, but it helps in catching + * software bugs (the erase-all-your-files kind) */ + DEBUG("addr = %" PRIx32 " ~dev->erase_addr_mask = %" PRIx32 "", addr, ~dev->sec_addr_mask); + DEBUG("mtd_spi_nor_erase: ERR: erase addr not aligned on %" PRIu32 " byte boundary.\n", + sector_size); + return -EOVERFLOW; + } + if (addr + size > total_size) { + return -EOVERFLOW; + } + be_uint32_t addr_be = byteorder_htonl(addr); + + /* write enable */ + mtd_spi_cmd(dev, dev->opcode->wren); + + if (size == total_size) { + mtd_spi_cmd_addr_write(dev, dev->opcode->chip_erase, addr_be, NULL, 0); + } + else if ((dev->flag & SPI_NOR_F_SECT_4K) && size == 4096) { + /* 4 KiO sectors can be erased with sector erase command */ + mtd_spi_cmd_addr_write(dev, dev->opcode->sector_erase, addr_be, NULL, 0); + } + else if ((dev->flag & SPI_NOR_F_SECT_32K) && size == 32768) { + /* 32 KiO sectors can be erased with sector erase command */ + mtd_spi_cmd_addr_write(dev, dev->opcode->block_erase_32k, addr_be, NULL, 0); + } + else if (size % sector_size != 0) { + return -EOVERFLOW; + } + else { + for (size_t i = 0; i < size / sector_size; i++) { + mtd_spi_cmd_addr_write(dev, dev->opcode->block_erase, addr_be, NULL, 0); + addr += sector_size; + addr_be = byteorder_htonl(addr); + } + } + + /* waiting for the command to complete before returning */ + wait_for_write_complete(dev); + return 0; +} + +static int mtd_spi_nor_power(mtd_dev_t *mtd, enum mtd_power_state power) +{ + mtd_spi_nor_t *dev = (mtd_spi_nor_t *)mtd; + + switch (power) { + case MTD_POWER_UP: + mtd_spi_cmd(dev, dev->opcode->wake); + break; + case MTD_POWER_DOWN: + mtd_spi_cmd(dev, dev->opcode->sleep); + break; + } + + return 0; +} diff --git a/drivers/mtd_spi_nor/mtd_spi_nor_configs.c b/drivers/mtd_spi_nor/mtd_spi_nor_configs.c new file mode 100644 index 0000000000..9d31ff9510 --- /dev/null +++ b/drivers/mtd_spi_nor/mtd_spi_nor_configs.c @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2016 Eistec AB + * + * 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 drivers_mtd_spi_nor + * @{ + * + * @file + * @brief Configurations for some known serial flash memory devices + * + * @author Joakim Nohlgård + */ + +#include +#include "mtd_spi_nor.h" + +/* Define opcode tables for SPI NOR flash memory devices here. */ + +/* Linker garbage collection (gcc -fdata-sections -Wl,--gc-sections) should ensure + * that only the tables that are actually used by the application will take up + * space in the .rodata section in program ROM. */ + +const mtd_spi_nor_opcode_t mtd_spi_nor_opcode_default = { + .rdid = 0x9f, + .wren = 0x06, + .rdsr = 0x05, + .wrsr = 0x01, + .read = 0x03, + .read_fast = 0x0b, + .page_program = 0x02, + .sector_erase = 0x20, + .block_erase_32k = 0x52, + .block_erase = 0xd8, + .chip_erase = 0xc7, + .sleep = 0xb9, + .wake = 0xab, +}; + +/** @} */