diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig index 5f04bccd606..b3a4dd7c062 100644 --- a/drivers/mtd/Kconfig +++ b/drivers/mtd/Kconfig @@ -16,7 +16,7 @@ config MTD_PARTITION accesses do not extend outside of the partition. A FLASH device may be broken up into several partitions managed, - each managed by a separate MTD driver. The MTD parition interface + each managed by a separate MTD driver. The MTD partition interface is described in: include/nuttx/mtd/mtd.h @@ -43,7 +43,7 @@ config MTD_SECT512 If enabled, a MTD driver will be created that will convert the sector size of any other MTD driver to a 512 byte "apparent" sector size. The managed MTD driver in this case must have an erase block - size that is greater than 512B and an event multiple of 512B. + size that is greater than 512B and an even multiple of 512B. if MTD_SECT512 @@ -142,7 +142,7 @@ config MTD_CONFIG_RAM_CONSOLIDATE (For configurations that have only a single erase block assigned to the config device, RAM consolidation is the ONLY option.) - Another apporach is to allow the driver to use the entire MTD + Another approach is to allow the driver to use the entire MTD device (or partition) to save config data, and then allocate a RAM buffer (the size of one erase block) to perform the consolidation. Enabling this feature basically trades off RAM @@ -305,7 +305,7 @@ config AT24XX_MULTI default n ---help--- Build in additional support for multiple AT24XX devices, each with - dynamically allocated device structures wiath a separate I2C + dynamically allocated device structures with a separate I2C addresses (but otherwise identical -- support for multiple, different AT24xx, devices not yet supported). @@ -334,7 +334,7 @@ config AT24XX_EXTENDED bool "Extended memory" default n ---help--- - If the device supports extended memory, then this operion may be set + If the device supports extended memory, then this operation may be set to enabled the MTDIOC_EXTENDED ioctl() operation. When the extended operation is selected, calls to the driver read method will return data from the extended memory region. @@ -482,7 +482,7 @@ config S25FL1_SCRAMBLE bool "Scramble data" default n ---help--- - Requires drviver support for data scrambling/descrambling. + Requires driver support for data scrambling/descrambling. config S25FL1_SCRAMBLE_KEY hex "Scramble key" @@ -491,6 +491,36 @@ config S25FL1_SCRAMBLE_KEY endif +config MTD_N25QXXX + bool "QuadSPI-based Micron N25QXXX family FLASH" + default n + ---help--- + Support the N25Q016A, N25Q032A, N25Q064A, N25Q128A, N25Q256A + + +if MTD_N25QXXX + +config N25QXXX_QSPIMODE + int "N25QXXX QuadSPI Mode" + default 0 + ---help--- + This device can operate in SPI mode 0 or 3. + +config N25QXXX_QSPI_FREQUENCY + int "N25QXXX QuadSPI Frequency" + default 108000000 + ---help--- + - Clock frequency for all SPI commands except for Read Data + command (0x03) + - Clock frequency for Read Data command (0x03): 54 MHz + In this implementation, only "Quad" reads are performed. + +config N25QXXX_SECTOR512 + bool "Simulate 512 byte Erase Blocks" + default n + +endif + config MTD_SMART bool "Sector Mapped Allocation for Really Tiny (SMART) Flash support" default n diff --git a/drivers/mtd/Make.defs b/drivers/mtd/Make.defs index fdd77d3610b..4bbd1eb1ffe 100644 --- a/drivers/mtd/Make.defs +++ b/drivers/mtd/Make.defs @@ -104,6 +104,10 @@ ifeq ($(CONFIG_MTD_S25FL1),y) CSRCS += s25fl1.c endif +ifeq ($(CONFIG_MTD_N25QXXX),y) +CSRCS += n25qxxx.c +endif + ifeq ($(CONFIG_MTD_SMART),y) ifeq ($(CONFIG_FS_SMARTFS),y) CSRCS += smart.c diff --git a/drivers/mtd/n25qxxx.c b/drivers/mtd/n25qxxx.c new file mode 100644 index 00000000000..31bab580d27 --- /dev/null +++ b/drivers/mtd/n25qxxx.c @@ -0,0 +1,1503 @@ +/************************************************************************************ + * drivers/mtd/n25qxxx.c + * Driver for QuadSPI-based N25QxxxA + * + * Copyright (C) 2015 Gregory Nutt. All rights reserved. + * Author: dev@ziggurat29.com + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ************************************************************************************/ + +/************************************************************************************ + * Included Files + ************************************************************************************/ + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/************************************************************************************ + * Pre-processor Definitions + ************************************************************************************/ +/* Configuration ********************************************************************/ +/* QuadSPI Mode. Per data sheet, either Mode 0 or Mode 3 may be used. */ + +#ifndef CONFIG_N25QXXX_QSPIMODE +# define CONFIG_N25QXXX_QSPIMODE QSPIDEV_MODE0 +#endif + +/* QuadSPI Frequency per data sheet:: + * + * + * In this implementation, only "Quad" reads are performed. + */ + +#ifndef CONFIG_N25QXXX_QSPI_FREQUENCY +/* if you haven't specified frequency, default to 40 MHz which will work with all + commands. + */ +# define CONFIG_N25QXXX_QSPI_FREQUENCY 40000000 +#endif + +#ifndef CONFIG_N25QXXX_DUMMIES +/* if you haven't specified the number of dummy cycles for quad reads, provide a + reasonable default. The actual number of dummies needed is clock and IO command + dependent. + */ +# define CONFIG_N25QXXX_DUMMIES 6 +#endif + + +/* N25QXXX Commands *****************************************************************/ +/* Configuration, Status, Erase, Program Commands ***********************************/ +/* Command Value Description: */ +/* Data sequence */ +#define N25QXXX_READ_STATUS 0x05 /* Read status register: * + * 0x05 | SR */ +#define N25QXXX_WRITE_STATUS 0x01 /* Write status register: * + * 0x01 | SR */ +#define N25QXXX_READ_VOLCFG 0x85 /* Read volatile configuration register: * + * 0x85 | VCR */ +#define N25QXXX_WRITE_VOLCFG 0x81 /* Write status register: * + * 0x81 | VCR */ +#define N25QXXX_WRITE_ENABLE 0x06 /* Write enable: * + * 0x06 */ +#define N25QXXX_WRITE_DISABLE 0x04 /* Write disable command code: * + * 0x04 */ +#define N25QXXX_PAGE_PROGRAM 0x02 /* Page Program: * + * 0x02 | ADDR(MS) | ADDR(MID) | * + * ADDR(LS) | data */ +#define N25QXXX_SUBSECTOR_ERASE 0x20 /* Sub-sector Erase (4 kB) * + * 0x20 | ADDR(MS) | ADDR(MID) | * + * ADDR(LS) */ +#define N25QXXX_BULK_ERASE 0xc7 /* Bulk erase: * + * 0xc7 */ + +/* Read Commands ********************************************************************/ +/* Command Value Description: */ +/* Data sequence */ +#define N25QXXX_FAST_READ_QUADIO 0xeb /* Fast Read Quad I/O: * + * 0xeb | ADDR | data... */ + +/* Reset Commands *******************************************************************/ +/* Command Value Description: */ +/* Data sequence */ + +/* ID/Security Commands *************************&***********************************/ +/* Command Value Description: */ +/* Data sequence */ +#define N25QXXX_JEDEC_ID 0x9f /* JEDEC ID: * + * 0x9f | Manufacturer | MemoryType | * + * Capacity */ + +/* Flash Manufacturer JEDEC IDs */ + +#define N25QXXX_JEDEC_ID_SPANSION 0x01 +#define N25QXXX_JEDEC_ID_ATMEL 0x1f +#define N25QXXX_JEDEC_ID_ST 0x20 +#define N25QXXX_JEDEC_ID_SST 0xbf +#define N25QXXX_JEDEC_ID_MACRONIX 0xc2 +#define N25QXXX_JEDEC_ID_WINBOND 0xef + +/* N25QXXX JEDIC IDs */ + +#define N25QXXX3V_JEDEC_DEVICE_TYPE 0xba /* 3v memory device type */ +#define N25QXXX2V_JEDEC_DEVICE_TYPE 0xbb /* 2v memory device type */ + +#define N25Q016_JEDEC_CAPACITY 0x15 /* N25Q016 (2 MB) memory capacity */ +#define N25Q032_JEDEC_CAPACITY 0x16 /* N25Q032 (4 MB) memory capacity */ +#define N25Q064_JEDEC_CAPACITY 0x17 /* N25Q064 (8 MB) memory capacity */ +#define N25Q128_JEDEC_CAPACITY 0x18 /* N25Q128 (16 MB) memory capacity */ +#define N25Q256_JEDEC_CAPACITY 0x19 /* N25Q256 (32 MB) memory capacity */ +#define N25Q512_JEDEC_CAPACITY 0x20 /* N25Q512 (64 MB) memory capacity */ +#define N25Q00_JEDEC_CAPACITY 0x21 /* N25Q00 (128 MB) memory capacity */ + +/* N25QXXX Registers ****************************************************************/ +/* Status register bit definitions */ + +#define STATUS_BUSY_MASK (1 << 0) /* Bit 0: Device ready/busy status */ +# define STATUS_READY (0 << 0) /* 0 = Not Busy */ +# define STATUS_BUSY (1 << 0) /* 1 = Busy */ +#define STATUS_WEL_MASK (1 << 1) /* Bit 1: Write enable latch status */ +# define STATUS_WEL_DISABLED (0 << 1) /* 0 = Not Write Enabled */ +# define STATUS_WEL_ENABLED (1 << 1) /* 1 = Write Enabled */ +#define STATUS_BP_SHIFT (2) /* Bits 2-4: Block protect bits */ +#define STATUS_BP_MASK (7 << STATUS_BP_SHIFT) +# define STATUS_BP_NONE (0 << STATUS_BP_SHIFT) +# define STATUS_BP_ALL (7 << STATUS_BP_SHIFT) +#define STATUS_TB_MASK (1 << 5) /* Bit 5: Top / Bottom Protect */ +# define STATUS_TB_TOP (0 << 5) /* 0 = BP2-BP0 protect Top down */ +# define STATUS_TB_BOTTOM (1 << 5) /* 1 = BP2-BP0 protect Bottom up */ +#define STATUS_BP3_MASK (1 << 5) /* Bit 6: BP3 */ +#define STATUS_SRP0_MASK (1 << 7) /* Bit 7: Status register protect 0 */ +# define STATUS_SRP0_UNLOCKED (0 << 7) /* 0 = WP# no effect / PS Lock Down */ +# define STATUS_SRP0_LOCKED (1 << 7) /* 1 = WP# protect / OTP Lock Down */ + +/* Chip Geometries ******************************************************************/ +/* All members of the family support uniform 4K-byte 'sub sectors'; they also support + 64k (and sometimes 32k) 'sectors' proper, but we won't be using those here. + */ + +/* N25Q016 (2 MB) memory capacity */ +#define N25Q016_SECTOR_SIZE (4*1024) +#define N25Q016_SECTOR_SHIFT (12) +#define N25Q016_SECTOR_COUNT (512) +#define N25Q016_PAGE_SIZE (256) +#define N25Q016_PAGE_SHIFT (8) + +/* N25Q032 (4 MB) memory capacity */ +#define N25Q032_SECTOR_SIZE (4*1024) +#define N25Q032_SECTOR_SHIFT (12) +#define N25Q032_SECTOR_COUNT (1024) +#define N25Q032_PAGE_SIZE (256) +#define N25Q032_PAGE_SHIFT (8) + +/* N25Q064 (8 MB) memory capacity */ +#define N25Q064_SECTOR_SIZE (4*1024) +#define N25Q064_SECTOR_SHIFT (12) +#define N25Q064_SECTOR_COUNT (2048) +#define N25Q064_PAGE_SIZE (256) +#define N25Q064_PAGE_SHIFT (8) + +/* N25Q128 (16 MB) memory capacity */ +#define N25Q128_SECTOR_SIZE (4*1024) +#define N25Q128_SECTOR_SHIFT (12) +#define N25Q128_SECTOR_COUNT (4096) +#define N25Q128_PAGE_SIZE (256) +#define N25Q128_PAGE_SHIFT (8) + +/* N25Q256 (32 MB) memory capacity */ +#define N25Q256_SECTOR_SIZE (4*1024) +#define N25Q256_SECTOR_SHIFT (12) +#define N25Q256_SECTOR_COUNT (8196) +#define N25Q256_PAGE_SIZE (256) +#define N25Q256_PAGE_SHIFT (8) + +/* N25Q512 (64 MB) memory capacity */ +#define N25Q512_SECTOR_SIZE (4*1024) +#define N25Q512_SECTOR_SHIFT (12) +#define N25Q512_SECTOR_COUNT (16384) +#define N25Q512_PAGE_SIZE (256) +#define N25Q512_PAGE_SHIFT (8) + +/* N25Q00 (128 MB) memory capacity */ +#define N25Q00_SECTOR_SIZE (4*1024) +#define N25Q00_SECTOR_SHIFT (12) +#define N25Q00_SECTOR_COUNT (32768) +#define N25Q00_PAGE_SIZE (256) +#define N25Q00_PAGE_SHIFT (8) + +/* Cache flags **********************************************************************/ + +#define N25QXXX_CACHE_VALID (1 << 0) /* 1=Cache has valid data */ +#define N25QXXX_CACHE_DIRTY (1 << 1) /* 1=Cache is dirty */ +#define N25QXXX_CACHE_ERASED (1 << 2) /* 1=Backing FLASH is erased */ + +#define IS_VALID(p) ((((p)->flags) & N25QXXX_CACHE_VALID) != 0) +#define IS_DIRTY(p) ((((p)->flags) & N25QXXX_CACHE_DIRTY) != 0) +#define IS_ERASED(p) ((((p)->flags) & N25QXXX_CACHE_DIRTY) != 0) + +#define SET_VALID(p) do { (p)->flags |= N25QXXX_CACHE_VALID; } while (0) +#define SET_DIRTY(p) do { (p)->flags |= N25QXXX_CACHE_DIRTY; } while (0) +#define SET_ERASED(p) do { (p)->flags |= N25QXXX_CACHE_DIRTY; } while (0) + +#define CLR_VALID(p) do { (p)->flags &= ~N25QXXX_CACHE_VALID; } while (0) +#define CLR_DIRTY(p) do { (p)->flags &= ~N25QXXX_CACHE_DIRTY; } while (0) +#define CLR_ERASED(p) do { (p)->flags &= ~N25QXXX_CACHE_DIRTY; } while (0) + +/* 512 byte sector support **********************************************************/ + +#define N25QXXX_SECTOR512_SHIFT 9 +#define N25QXXX_SECTOR512_SIZE (1 << 9) +#define N25QXXX_ERASED_STATE 0xff + +/************************************************************************************ + * Private Types + ************************************************************************************/ + +/* This type represents the state of the MTD device. The struct mtd_dev_s must + * appear at the beginning of the definition so that you can freely cast between + * pointers to struct mtd_dev_s and struct n25qxxx_dev_s. + */ + +struct n25qxxx_dev_s +{ + struct mtd_dev_s mtd; /* MTD interface */ + FAR struct qspi_dev_s *qspi; /* Saved QuadSPI interface instance */ + uint16_t nsectors; /* Number of erase sectors */ + uint8_t sectorshift; /* Log2 of sector size */ + uint8_t pageshift; /* Log2 of page size */ + FAR uint8_t *cmdbuf; /* Allocated command buffer */ + FAR uint8_t *readbuf; /* Allocated status read buffer */ + +#ifdef CONFIG_N25QXXX_SECTOR512 + uint8_t flags; /* Buffered sector flags */ + uint16_t esectno; /* Erase sector number in the cache */ + FAR uint8_t *sector; /* Allocated sector data */ +#endif +}; + +/************************************************************************************ + * Private Function Prototypes + ************************************************************************************/ + +/* Locking */ + +static void n25qxxx_lock(FAR struct qspi_dev_s *qspi); +static inline void n25qxxx_unlock(FAR struct qspi_dev_s *qspi); + +/* Low-level message helpers */ + +static int n25qxxx_command(FAR struct qspi_dev_s *qspi, uint8_t cmd); +static int n25qxxx_command_address(FAR struct qspi_dev_s *qspi, uint8_t cmd, + off_t addr, uint8_t addrlen); +static int n25qxxx_command_read(FAR struct qspi_dev_s *qspi, uint8_t cmd, + FAR void *buffer, size_t buflen); +static int n25qxxx_command_write(FAR struct qspi_dev_s *qspi, uint8_t cmd, + FAR const void *buffer, size_t buflen); +static uint8_t n25qxxx_read_status(FAR struct n25qxxx_dev_s *priv); +static void n25qxxx_write_status(FAR struct n25qxxx_dev_s *priv); +static uint8_t n25qxxx_read_volcfg(FAR struct n25qxxx_dev_s *priv); +static void n25qxxx_write_volcfg(FAR struct n25qxxx_dev_s *priv); +static void n25qxxx_write_enable(FAR struct n25qxxx_dev_s *priv); +static void n25qxxx_write_disable(FAR struct n25qxxx_dev_s *priv); + +static int n25qxxx_readid(FAR struct n25qxxx_dev_s *priv); +static int n25qxxx_protect(FAR struct n25qxxx_dev_s *priv, + off_t startblock, size_t nblocks); +static int n25qxxx_unprotect(FAR struct n25qxxx_dev_s *priv, + off_t startblock, size_t nblocks); +static bool n25qxxx_isprotected(FAR struct n25qxxx_dev_s *priv, + uint8_t status, off_t address); +static int n25qxxx_erase_sector(FAR struct n25qxxx_dev_s *priv, off_t offset); +static int n25qxxx_erase_chip(FAR struct n25qxxx_dev_s *priv); +static int n25qxxx_read_byte(FAR struct n25qxxx_dev_s *priv, FAR uint8_t *buffer, + off_t address, size_t nbytes); +static int n25qxxx_write_page(FAR struct n25qxxx_dev_s *priv, + FAR const uint8_t *buffer, off_t address, size_t nbytes); +#ifdef CONFIG_N25QXXX_SECTOR512 +static int n25qxxx_flush_cache(struct n25qxxx_dev_s *priv); +static FAR uint8_t *n25qxxx_read_cache(struct n25qxxx_dev_s *priv, off_t sector); +static void n25qxxx_erase_cache(struct n25qxxx_dev_s *priv, off_t sector); +static int n25qxxx_write_cache(FAR struct n25qxxx_dev_s *priv, + FAR const uint8_t *buffer, off_t sector, size_t nsectors); +#endif + +/* MTD driver methods */ + +static int n25qxxx_erase(FAR struct mtd_dev_s *dev, off_t startblock, + size_t nblocks); +static ssize_t n25qxxx_bread(FAR struct mtd_dev_s *dev, off_t startblock, + size_t nblocks, FAR uint8_t *buf); +static ssize_t n25qxxx_bwrite(FAR struct mtd_dev_s *dev, off_t startblock, + size_t nblocks, FAR const uint8_t *buf); +static ssize_t n25qxxx_read(FAR struct mtd_dev_s *dev, off_t offset, size_t nbytes, + FAR uint8_t *buffer); +static int n25qxxx_ioctl(FAR struct mtd_dev_s *dev, int cmd, unsigned long arg); + +/************************************************************************************ + * Private Data + ************************************************************************************/ + +/************************************************************************************ + * Private Functions + ************************************************************************************/ + +/************************************************************************************ + * Name: n25qxxx_lock + ************************************************************************************/ + +static void n25qxxx_lock(FAR struct qspi_dev_s *qspi) +{ + /* On QuadSPI buses where there are multiple devices, it will be necessary to + * lock QuadSPI to have exclusive access to the buses for a sequence of + * transfers. The bus should be locked before the chip is selected. + * + * This is a blocking call and will not return until we have exclusive access to + * the QuadSPI buss. We will retain that exclusive access until the bus is unlocked. + */ + + (void)QSPI_LOCK(qspi, true); + + /* After locking the QuadSPI bus, the we also need call the setfrequency, setbits, and + * setmode methods to make sure that the QuadSPI is properly configured for the device. + * If the QuadSPI buss is being shared, then it may have been left in an incompatible + * state. + */ + + QSPI_SETMODE(qspi, CONFIG_N25QXXX_QSPIMODE); + QSPI_SETBITS(qspi, 8); + (void)QSPI_SETFREQUENCY(qspi, CONFIG_N25QXXX_QSPI_FREQUENCY); +} + +/************************************************************************************ + * Name: n25qxxx_unlock + ************************************************************************************/ + +static inline void n25qxxx_unlock(FAR struct qspi_dev_s *qspi) +{ + (void)QSPI_LOCK(qspi, false); +} + +/************************************************************************************ + * Name: n25qxxx_command + ************************************************************************************/ + +static int n25qxxx_command(FAR struct qspi_dev_s *qspi, uint8_t cmd) +{ + struct qspi_cmdinfo_s cmdinfo; + + fvdbg("CMD: %02x\n", cmd); + + cmdinfo.flags = 0; + cmdinfo.addrlen = 0; + cmdinfo.cmd = cmd; + cmdinfo.buflen = 0; + cmdinfo.addr = 0; + cmdinfo.buffer = NULL; + + return QSPI_COMMAND(qspi, &cmdinfo); +} + +/************************************************************************************ + * Name: n25qxxx_command_address + ************************************************************************************/ + +static int n25qxxx_command_address(FAR struct qspi_dev_s *qspi, uint8_t cmd, + off_t addr, uint8_t addrlen) +{ + struct qspi_cmdinfo_s cmdinfo; + + fvdbg("CMD: %02x Address: %04lx addrlen=%d\n", cmd, (unsigned long)addr, addrlen); + + cmdinfo.flags = QSPICMD_ADDRESS; + cmdinfo.addrlen = addrlen; + cmdinfo.cmd = cmd; + cmdinfo.buflen = 0; + cmdinfo.addr = addr; + cmdinfo.buffer = NULL; + + return QSPI_COMMAND(qspi, &cmdinfo); +} + +/************************************************************************************ + * Name: n25qxxx_command_read + ************************************************************************************/ + +static int n25qxxx_command_read(FAR struct qspi_dev_s *qspi, uint8_t cmd, + FAR void *buffer, size_t buflen) +{ + struct qspi_cmdinfo_s cmdinfo; + + fvdbg("CMD: %02x buflen: %lu\n", cmd, (unsigned long)buflen); + + cmdinfo.flags = QSPICMD_READDATA; + cmdinfo.addrlen = 0; + cmdinfo.cmd = cmd; + cmdinfo.buflen = buflen; + cmdinfo.addr = 0; + cmdinfo.buffer = buffer; + + return QSPI_COMMAND(qspi, &cmdinfo); +} + +/************************************************************************************ + * Name: n25qxxx_command_write + ************************************************************************************/ + +static int n25qxxx_command_write(FAR struct qspi_dev_s *qspi, uint8_t cmd, + FAR const void *buffer, size_t buflen) +{ + struct qspi_cmdinfo_s cmdinfo; + + fvdbg("CMD: %02x buflen: %lu\n", cmd, (unsigned long)buflen); + + cmdinfo.flags = QSPICMD_WRITEDATA; + cmdinfo.addrlen = 0; + cmdinfo.cmd = cmd; + cmdinfo.buflen = buflen; + cmdinfo.addr = 0; + cmdinfo.buffer = (FAR void *)buffer; + + return QSPI_COMMAND(qspi, &cmdinfo); +} + +/************************************************************************************ + * Name: n25qxxx_read_status + ************************************************************************************/ + +static uint8_t n25qxxx_read_status(FAR struct n25qxxx_dev_s *priv) +{ + DEBUGVERIFY(n25qxxx_command_read(priv->qspi, N25QXXX_READ_STATUS, + (FAR void *)&priv->readbuf[0], 1)); + return priv->readbuf[0]; +} + +/************************************************************************************ + * Name: n25qxxx_write_status + ************************************************************************************/ + +static void n25qxxx_write_status(FAR struct n25qxxx_dev_s *priv) +{ + n25qxxx_write_enable(priv); + + /* take care to mask of the SRP bit; it is one-time-programmable */ + + priv->cmdbuf[0] &= ~STATUS_SRP0_MASK; + + n25qxxx_command_write(priv->qspi, N25QXXX_WRITE_STATUS, + (FAR const void *)priv->cmdbuf, 1); + n25qxxx_write_disable(priv); +} + +/************************************************************************************ + * Name: n25qxxx_read_volcfg + ************************************************************************************/ + +static uint8_t n25qxxx_read_volcfg(FAR struct n25qxxx_dev_s *priv) +{ + DEBUGVERIFY(n25qxxx_command_read(priv->qspi, N25QXXX_READ_VOLCFG, + (FAR void *)&priv->readbuf[0], 1)); + return priv->readbuf[0]; +} + +/************************************************************************************ + * Name: n25qxxx_write_volcfg + ************************************************************************************/ + +static void n25qxxx_write_volcfg(FAR struct n25qxxx_dev_s *priv) +{ + n25qxxx_write_enable(priv); + n25qxxx_command_write(priv->qspi, N25QXXX_WRITE_VOLCFG, + (FAR const void *)priv->cmdbuf, 1); + n25qxxx_write_disable(priv); +} + +/************************************************************************************ + * Name: n25qxxx_write_enable + ************************************************************************************/ + +static void n25qxxx_write_enable(FAR struct n25qxxx_dev_s *priv) +{ + uint8_t status; + + do + { + n25qxxx_command(priv->qspi, N25QXXX_WRITE_ENABLE); + status = n25qxxx_read_status(priv); + } + while ((status & STATUS_WEL_MASK) != STATUS_WEL_ENABLED); +} + +/************************************************************************************ + * Name: n25qxxx_write_disable + ************************************************************************************/ + +static void n25qxxx_write_disable(FAR struct n25qxxx_dev_s *priv) +{ + uint8_t status; + + do + { + n25qxxx_command(priv->qspi, N25QXXX_WRITE_DISABLE); + status = n25qxxx_read_status(priv); + } + while ((status & STATUS_WEL_MASK) != STATUS_WEL_DISABLED); +} + +/************************************************************************************ + * Name: n25qxxx_readid + ************************************************************************************/ + +static inline int n25qxxx_readid(struct n25qxxx_dev_s *priv) +{ + /* Lock the QuadSPI bus and configure the bus. */ + + n25qxxx_lock(priv->qspi); + + /* Read the JEDEC ID */ + + n25qxxx_command_read(priv->qspi, N25QXXX_JEDEC_ID, priv->cmdbuf, 3); + + /* Unlock the bus */ + + n25qxxx_unlock(priv->qspi); + + fvdbg("Manufacturer: %02x Device Type %02x, Capacity: %02x\n", + priv->cmdbuf[0], priv->cmdbuf[1], priv->cmdbuf[2]); + + /* Check for a recognized memory device type */ + + if (priv->cmdbuf[1] != N25QXXX3V_JEDEC_DEVICE_TYPE && + priv->cmdbuf[1] != N25QXXX2V_JEDEC_DEVICE_TYPE) + { + fdbg("ERROR: Unrecognized device type: 0x%02x\n", priv->cmdbuf[1]); + return -ENODEV; + } + + /* Check for a supported capacity */ + + switch (priv->cmdbuf[2]) + { + case N25Q016_JEDEC_CAPACITY: + priv->sectorshift = N25Q016_SECTOR_SHIFT; + priv->pageshift = N25Q016_PAGE_SHIFT; + priv->nsectors = N25Q016_SECTOR_COUNT; + break; + case N25Q032_JEDEC_CAPACITY: + priv->sectorshift = N25Q032_SECTOR_SHIFT; + priv->pageshift = N25Q032_PAGE_SHIFT; + priv->nsectors = N25Q032_SECTOR_COUNT; + break; + case N25Q064_JEDEC_CAPACITY: + priv->sectorshift = N25Q064_SECTOR_SHIFT; + priv->pageshift = N25Q064_PAGE_SHIFT; + priv->nsectors = N25Q064_SECTOR_COUNT; + break; + case N25Q128_JEDEC_CAPACITY: + priv->sectorshift = N25Q128_SECTOR_SHIFT; + priv->pageshift = N25Q128_PAGE_SHIFT; + priv->nsectors = N25Q128_SECTOR_COUNT; + break; + case N25Q256_JEDEC_CAPACITY: + priv->sectorshift = N25Q256_SECTOR_SHIFT; + priv->pageshift = N25Q256_PAGE_SHIFT; + priv->nsectors = N25Q256_SECTOR_COUNT; + break; + case N25Q512_JEDEC_CAPACITY: + priv->sectorshift = N25Q512_SECTOR_SHIFT; + priv->pageshift = N25Q512_PAGE_SHIFT; + priv->nsectors = N25Q512_SECTOR_COUNT; + break; + case N25Q00_JEDEC_CAPACITY: + priv->sectorshift = N25Q00_SECTOR_SHIFT; + priv->pageshift = N25Q00_PAGE_SHIFT; + priv->nsectors = N25Q00_SECTOR_COUNT; + break; + + /* Support for this part is not implemented yet */ + + default: + fdbg("ERROR: Unsupported memory capacity: %02x\n", priv->cmdbuf[2]); + return -ENODEV; + } + + return OK; +} + +/************************************************************************************ + * Name: n25qxxx_protect + ************************************************************************************/ + +static int n25qxxx_protect(FAR struct n25qxxx_dev_s *priv, + off_t startblock, size_t nblocks) +{ + /* Get the status register value to check the current protection */ + + priv->cmdbuf[0] = n25qxxx_read_status(priv); + + if ((priv->cmdbuf[0] & STATUS_BP_MASK) == STATUS_BP_NONE) + { + /* Protection already disabled */ + + return 0; + } + + /* Check if sector protection registers are locked */ + + if ((priv->cmdbuf[0] & STATUS_SRP0_MASK) == STATUS_SRP0_LOCKED) + { + /* Yes.. unprotect section protection registers */ + + priv->cmdbuf[0] &= ~STATUS_SRP0_MASK; + n25qxxx_write_status(priv); + } + + /* Set the protection mask to zero. + * REVISIT: This logic should really just set the BP bits as + * necessary to protect the range of sectors. + */ + + priv->cmdbuf[0] |= (STATUS_BP3_MASK|STATUS_BP_MASK); + n25qxxx_write_status(priv); + + /* Check the new status */ + + priv->cmdbuf[0] = n25qxxx_read_status(priv); + if ((priv->cmdbuf[0] & (STATUS_BP3_MASK|STATUS_BP_MASK)) != (STATUS_BP3_MASK|STATUS_BP_MASK)) + { + return -EACCES; + } + + return OK; +} + +/************************************************************************************ + * Name: n25qxxx_unprotect + ************************************************************************************/ + +static int n25qxxx_unprotect(FAR struct n25qxxx_dev_s *priv, + off_t startblock, size_t nblocks) +{ + /* Get the status register value to check the current protection */ + + priv->cmdbuf[0] = n25qxxx_read_status(priv); + + if ((priv->cmdbuf[0] & (STATUS_BP3_MASK|STATUS_BP_MASK)) == 0 ) + { + /* Protection already disabled */ + + return 0; + } + + /* Check if sector protection registers are locked */ + + if ((priv->cmdbuf[0] & STATUS_SRP0_MASK) == STATUS_SRP0_LOCKED) + { + /* the SRP bit is one time programmable; if it's set, there's nothing that + you can do to unset it. + */ + return -EACCES; + } + + /* Set the protection mask to zero (and not complemented). + * REVISIT: This logic should really just re-write the BP bits as + * necessary to unprotect the range of sectors. + */ + + priv->cmdbuf[0] &= ~(STATUS_BP3_MASK|STATUS_BP_MASK); + n25qxxx_write_status(priv); + + /* Check the new status */ + + priv->cmdbuf[0] = n25qxxx_read_status(priv); + if ((priv->cmdbuf[0] & (STATUS_SRP0_MASK | STATUS_BP3_MASK | STATUS_BP_MASK)) != 0) + { + return -EACCES; + } + + return OK; +} + +/************************************************************************************ + * Name: n25qxxx_isprotected + ************************************************************************************/ + +static bool n25qxxx_isprotected(FAR struct n25qxxx_dev_s *priv, uint8_t status, + off_t address) +{ + off_t protstart; + off_t protend; + off_t protsize; + unsigned int bp; + + /* The BP field is spread across non-contiguous bits */ + + bp = (status & STATUS_BP_MASK) >> STATUS_BP_SHIFT; + if (status & STATUS_BP3_MASK) + { + bp |= 8; + } + + /* the BP field is essentially the power-of-two of the number of 64k sectors, + saturated to the device size. + */ + + if ( 0 == bp ) + { + return false; + } + + protsize = 0x00010000; + protsize <<= (protsize << (bp - 1)); + protend = (1 << priv->sectorshift) * priv->nsectors; + if ( protsize > protend ) + { + protsize = protend; + } + + /* The final protection range then depends on if the protection region is + * configured top-down or bottom up (assuming CMP=0). + */ + + if ((status & STATUS_TB_MASK) != 0) + { + protstart = 0x00000000; + protend = protstart + protsize; + } + else + { + protstart = protend - protsize; + /* protend already computed above */ + } + + return (address >= protstart && address < protend); +} + +/************************************************************************************ + * Name: n25qxxx_erase_sector + ************************************************************************************/ + +static int n25qxxx_erase_sector(struct n25qxxx_dev_s *priv, off_t sector) +{ + off_t address; + uint8_t status; + + fvdbg("sector: %08lx\n", (unsigned long)sector); + + /* Check that the flash is ready and unprotected */ + + status = n25qxxx_read_status(priv); + if ((status & STATUS_BUSY_MASK) != STATUS_READY) + { + fdbg("ERROR: Flash busy: %02x", status); + return -EBUSY; + } + + /* Get the address associated with the sector */ + + address = (off_t)sector << priv->sectorshift; + + if ((status & (STATUS_BP3_MASK|STATUS_BP_MASK)) != 0 && + n25qxxx_isprotected(priv, status, address)) + { + fdbg("ERROR: Flash protected: %02x", status); + return -EACCES; + } + + /* Send the sector erase command */ + + n25qxxx_write_enable(priv); + n25qxxx_command_address(priv->qspi, N25QXXX_SUBSECTOR_ERASE, address, 3); + + /* Wait for erasure to finish */ + + while ((n25qxxx_read_status(priv) & STATUS_BUSY_MASK) != 0); + + return OK; +} + +/************************************************************************************ + * Name: n25qxxx_erase_chip + ************************************************************************************/ + +static int n25qxxx_erase_chip(struct n25qxxx_dev_s *priv) +{ + uint8_t status; + + /* Check if the FLASH is protected */ + + status = n25qxxx_read_status(priv); + if ((status & (STATUS_BP3_MASK|STATUS_BP_MASK)) != 0) + { + fdbg("ERROR: FLASH is Protected: %02x", status); + return -EACCES; + } + + /* Erase the whole chip */ + + n25qxxx_write_enable(priv); + n25qxxx_command(priv->qspi, N25QXXX_BULK_ERASE); + + /* Wait for the erasure to complete */ + + status = n25qxxx_read_status(priv); + while ((status & STATUS_BUSY_MASK) != 0) + { + usleep(200*1000); + status = n25qxxx_read_status(priv); + } + + return OK; +} + +/************************************************************************************ + * Name: n25qxxx_read_byte + ************************************************************************************/ + +static int n25qxxx_read_byte(FAR struct n25qxxx_dev_s *priv, FAR uint8_t *buffer, + off_t address, size_t buflen) +{ + struct qspi_meminfo_s meminfo; + + fvdbg("address: %08lx nbytes: %d\n", (long)address, (int)buflen); + + meminfo.flags = QSPIMEM_READ | QSPIMEM_QUADIO; + meminfo.addrlen = 3; + meminfo.dummies = CONFIG_N25QXXX_DUMMIES; + meminfo.buflen = buflen; + meminfo.cmd = N25QXXX_FAST_READ_QUADIO; + meminfo.addr = address; + meminfo.buffer = buffer; + + return QSPI_MEMORY(priv->qspi, &meminfo); +} + +/************************************************************************************ + * Name: n25qxxx_write_page + ************************************************************************************/ + +static int n25qxxx_write_page(struct n25qxxx_dev_s *priv, FAR const uint8_t *buffer, + off_t address, size_t buflen) +{ + struct qspi_meminfo_s meminfo; + unsigned int pagesize; + unsigned int npages; + int ret; + int i; + + fvdbg("address: %08lx buflen: %u\n", (unsigned long)address, (unsigned)buflen); + + npages = (buflen >> priv->pageshift); + pagesize = (1 << priv->pageshift); + + /* Set up non-varying parts of transfer description */ + + meminfo.flags = QSPIMEM_WRITE; + meminfo.cmd = N25QXXX_PAGE_PROGRAM; + meminfo.addrlen = 3; + meminfo.buflen = pagesize; + meminfo.dummies = 0; + + /* Then write each page */ + + for (i = 0; i < npages; i++) + { + /* Set up varying parts of the transfer description */ + + meminfo.addr = address; + meminfo.buffer = (void *)buffer; + + /* Write one page */ + + n25qxxx_write_enable(priv); + ret = QSPI_MEMORY(priv->qspi, &meminfo); + n25qxxx_write_disable(priv); + + if (ret < 0) + { + fdbg("ERROR: QSPI_MEMORY failed writing address=%06x\n", + address); + return ret; + } + + /* Update for the next time through the loop */ + + buffer += pagesize; + address += pagesize; + buflen -= pagesize; + } + + /* The transfer should always be an even number of sectors and hence also + * pages. There should be no remainder. + */ + + DEBUGASSERT(buflen == 0); + + return OK; +} + +/************************************************************************************ + * Name: n25qxxx_flush_cache + ************************************************************************************/ + +#ifdef CONFIG_N25QXXX_SECTOR512 +static int n25qxxx_flush_cache(struct n25qxxx_dev_s *priv) +{ + int ret = OK; + + /* If the cache is dirty (meaning that it no longer matches the old FLASH contents) + * or was erased (with the cache containing the correct FLASH contents), then write + * the cached erase block to FLASH. + */ + + if (IS_DIRTY(priv) || IS_ERASED(priv)) + { + off_t address; + + /* Convert the erase sector number into a FLASH address */ + + address = (off_t)priv->esectno << priv->sectorshift; + + /* Write entire erase block to FLASH */ + + ret = n25qxxx_write_page(priv, priv->sector, address, 1 << priv->sectorshift); + if (ret < 0) + { + fdbg("ERROR: n25qxxx_write_page failed: %d\n", ret); + } + + /* The case is no long dirty and the FLASH is no longer erased */ + + CLR_DIRTY(priv); + CLR_ERASED(priv); + } + + return ret; +} +#endif + +/************************************************************************************ + * Name: n25qxxx_read_cache + ************************************************************************************/ + +#ifdef CONFIG_N25QXXX_SECTOR512 +static FAR uint8_t *n25qxxx_read_cache(struct n25qxxx_dev_s *priv, off_t sector) +{ + off_t esectno; + int shift; + int index; + int ret; + + /* Convert from the 512 byte sector to the erase sector size of the device. For + * exmample, if the actual erase sector size if 4Kb (1 << 12), then we first + * shift to the right by 3 to get the sector number in 4096 increments. + */ + + shift = priv->sectorshift - N25QXXX_SECTOR512_SHIFT; + esectno = sector >> shift; + fvdbg("sector: %ld esectno: %d shift=%d\n", sector, esectno, shift); + + /* Check if the requested erase block is already in the cache */ + + if (!IS_VALID(priv) || esectno != priv->esectno) + { + /* No.. Flush any dirty erase block currently in the cache */ + + ret = n25qxxx_flush_cache(priv); + if (ret < 0) + { + fdbg("ERROR: n25qxxx_flush_cache failed: %d\n", ret); + return NULL; + } + + /* Read the erase block into the cache */ + + ret = n25qxxx_read_byte(priv, priv->sector, + (esectno << priv->sectorshift), + (1 << priv->sectorshift)); + if (ret < 0) + { + fdbg("ERROR: n25qxxx_read_byte failed: %d\n", ret); + return NULL; + } + + /* Mark the sector as cached */ + + priv->esectno = esectno; + + SET_VALID(priv); /* The data in the cache is valid */ + CLR_DIRTY(priv); /* It should match the FLASH contents */ + CLR_ERASED(priv); /* The underlying FLASH has not been erased */ + } + + /* Get the index to the 512 sector in the erase block that holds the argument */ + + index = sector & ((1 << shift) - 1); + + /* Return the address in the cache that holds this sector */ + + return &priv->sector[index << N25QXXX_SECTOR512_SHIFT]; +} +#endif + +/************************************************************************************ + * Name: n25qxxx_erase_cache + ************************************************************************************/ + +#ifdef CONFIG_N25QXXX_SECTOR512 +static void n25qxxx_erase_cache(struct n25qxxx_dev_s *priv, off_t sector) +{ + FAR uint8_t *dest; + + /* First, make sure that the erase block containing the 512 byte sector is in + * the cache. + */ + + dest = n25qxxx_read_cache(priv, sector); + + /* Erase the block containing this sector if it is not already erased. + * The erased indicated will be cleared when the data from the erase sector + * is read into the cache and set here when we erase the block. + */ + + if (!IS_ERASED(priv)) + { + off_t esectno = sector >> (priv->sectorshift - N25QXXX_SECTOR512_SHIFT); + fvdbg("sector: %ld esectno: %d\n", sector, esectno); + + DEBUGVERIFY(n25qxxx_erase_sector(priv, esectno)); + SET_ERASED(priv); + } + + /* Put the cached sector data into the erase state and mark the cache as dirty + * (but don't update the FLASH yet. The caller will do that at a more optimal + * time). + */ + + memset(dest, N25QXXX_ERASED_STATE, N25QXXX_SECTOR512_SIZE); + SET_DIRTY(priv); +} +#endif + +/************************************************************************************ + * Name: n25qxxx_write_cache + ************************************************************************************/ + +#ifdef CONFIG_N25QXXX_SECTOR512 +static int n25qxxx_write_cache(FAR struct n25qxxx_dev_s *priv, + FAR const uint8_t *buffer, off_t sector, + size_t nsectors) +{ + FAR uint8_t *dest; + int ret; + + for (; nsectors > 0; nsectors--) + { + /* First, make sure that the erase block containing 512 byte sector is in + * memory. + */ + + dest = n25qxxx_read_cache(priv, sector); + + /* Erase the block containing this sector if it is not already erased. + * The erased indicated will be cleared when the data from the erase sector + * is read into the cache and set here when we erase the sector. + */ + + if (!IS_ERASED(priv)) + { + off_t esectno = sector >> (priv->sectorshift - N25QXXX_SECTOR512_SHIFT); + fvdbg("sector: %ld esectno: %d\n", sector, esectno); + + ret = n25qxxx_erase_sector(priv, esectno); + if (ret < 0) + { + fdbg("ERROR: n25qxxx_erase_sector failed: %d\n", ret); + return ret; + } + + SET_ERASED(priv); + } + + /* Copy the new sector data into cached erase block */ + + memcpy(dest, buffer, N25QXXX_SECTOR512_SIZE); + SET_DIRTY(priv); + + /* Set up for the next 512 byte sector */ + + buffer += N25QXXX_SECTOR512_SIZE; + sector++; + } + + /* Flush the last erase block left in the cache */ + + return n25qxxx_flush_cache(priv); +} +#endif + +/************************************************************************************ + * Name: n25qxxx_erase + ************************************************************************************/ + +static int n25qxxx_erase(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks) +{ + FAR struct n25qxxx_dev_s *priv = (FAR struct n25qxxx_dev_s *)dev; + size_t blocksleft = nblocks; +#ifdef CONFIG_N25QXXX_SECTOR512 + int ret; +#endif + + fvdbg("startblock: %08lx nblocks: %d\n", (long)startblock, (int)nblocks); + + /* Lock access to the SPI bus until we complete the erase */ + + n25qxxx_lock(priv->qspi); + + while (blocksleft-- > 0) + { + /* Erase each sector */ + +#ifdef CONFIG_N25QXXX_SECTOR512 + n25qxxx_erase_cache(priv, startblock); +#else + n25qxxx_erase_sector(priv, startblock); +#endif + startblock++; + } + +#ifdef CONFIG_N25QXXX_SECTOR512 + /* Flush the last erase block left in the cache */ + + ret = n25qxxx_flush_cache(priv); + if (ret < 0) + { + nblocks = ret; + } +#endif + + n25qxxx_unlock(priv->qspi); + + return (int)nblocks; +} + +/************************************************************************************ + * Name: n25qxxx_bread + ************************************************************************************/ + +static ssize_t n25qxxx_bread(FAR struct mtd_dev_s *dev, off_t startblock, + size_t nblocks, FAR uint8_t *buffer) +{ +#ifndef CONFIG_N25QXXX_SECTOR512 + FAR struct n25qxxx_dev_s *priv = (FAR struct n25qxxx_dev_s *)dev; +#endif + ssize_t nbytes; + + fvdbg("startblock: %08lx nblocks: %d\n", (long)startblock, (int)nblocks); + + /* On this device, we can handle the block read just like the byte-oriented read */ + +#ifdef CONFIG_N25QXXX_SECTOR512 + nbytes = n25qxxx_read(dev, startblock << N25QXXX_SECTOR512_SHIFT, + nblocks << N25QXXX_SECTOR512_SHIFT, buffer); + if (nbytes > 0) + { + nbytes >>= N25QXXX_SECTOR512_SHIFT; + } +#else + nbytes = n25qxxx_read(dev, startblock << priv->sectorshift, + nblocks << priv->sectorshift, buffer); + if (nbytes > 0) + { + nbytes >>= priv->sectorshift; + } +#endif + + return nbytes; + +return 0; +} + +/************************************************************************************ + * Name: n25qxxx_bwrite + ************************************************************************************/ + +static ssize_t n25qxxx_bwrite(FAR struct mtd_dev_s *dev, off_t startblock, + size_t nblocks, FAR const uint8_t *buffer) +{ + FAR struct n25qxxx_dev_s *priv = (FAR struct n25qxxx_dev_s *)dev; + int ret = (int)nblocks; + + fvdbg("startblock: %08lx nblocks: %d\n", (long)startblock, (int)nblocks); + + /* Lock the QuadSPI bus and write all of the pages to FLASH */ + + n25qxxx_lock(priv->qspi); + +#if defined(CONFIG_N25QXXX_SECTOR512) + ret = n25qxxx_write_cache(priv, buffer, startblock, nblocks); + if (ret < 0) + { + fdbg("ERROR: n25qxxx_write_cache failed: %d\n", ret); + } + +#else + ret = n25qxxx_write_page(priv, buffer, startblock << priv->sectorshift, + nblocks << priv->sectorshift); + if (ret < 0) + { + fdbg("ERROR: n25qxxx_write_page failed: %d\n", ret); + } +#endif + + n25qxxx_unlock(priv->qspi); + + return ret < 0 ? ret : nblocks; + +return 0; +} + +/************************************************************************************ + * Name: n25qxxx_read + ************************************************************************************/ + +static ssize_t n25qxxx_read(FAR struct mtd_dev_s *dev, off_t offset, size_t nbytes, + FAR uint8_t *buffer) +{ + FAR struct n25qxxx_dev_s *priv = (FAR struct n25qxxx_dev_s *)dev; + int ret; + + fvdbg("offset: %08lx nbytes: %d\n", (long)offset, (int)nbytes); + + /* Lock the QuadSPI bus and select this FLASH part */ + + n25qxxx_lock(priv->qspi); + ret = n25qxxx_read_byte(priv, buffer, offset, nbytes); + n25qxxx_unlock(priv->qspi); + + if (ret < 0) + { + fdbg("ERROR: n25qxxx_read_byte returned: %d\n", ret); + return (ssize_t)ret; + } + + fvdbg("return nbytes: %d\n", (int)nbytes); + return (ssize_t)nbytes; + +return 0; +} + +/************************************************************************************ + * Name: n25qxxx_ioctl + ************************************************************************************/ + +static int n25qxxx_ioctl(FAR struct mtd_dev_s *dev, int cmd, unsigned long arg) +{ + FAR struct n25qxxx_dev_s *priv = (FAR struct n25qxxx_dev_s *)dev; + int ret = -EINVAL; /* Assume good command with bad parameters */ + + fvdbg("cmd: %d \n", cmd); + + switch (cmd) + { + case MTDIOC_GEOMETRY: + { + FAR struct mtd_geometry_s *geo = + (FAR struct mtd_geometry_s *)((uintptr_t)arg); + + if (geo) + { + /* Populate the geometry structure with information need to know + * the capacity and how to access the device. + * + * NOTE: that the device is treated as though it where just an array + * of fixed size blocks. That is most likely not true, but the client + * will expect the device logic to do whatever is necessary to make it + * appear so. + */ + +#ifdef CONFIG_N25QXXX_SECTOR512 + geo->blocksize = (1 << N25QXXX_SECTOR512_SHIFT); + geo->erasesize = (1 << N25QXXX_SECTOR512_SHIFT); + geo->neraseblocks = priv->nsectors << (priv->sectorshift - N25QXXX_SECTOR512_SHIFT); +#else + geo->blocksize = (1 << priv->sectorshift); + geo->erasesize = (1 << priv->sectorshift); + geo->neraseblocks = priv->nsectors; +#endif + ret = OK; + + fvdbg("blocksize: %d erasesize: %d neraseblocks: %d\n", + geo->blocksize, geo->erasesize, geo->neraseblocks); + } + } + break; + + case MTDIOC_BULKERASE: + { + /* Erase the entire device */ + + n25qxxx_lock(priv->qspi); + ret = n25qxxx_erase_chip(priv); + n25qxxx_unlock(priv->qspi); + } + break; + + case MTDIOC_PROTECT: + { + FAR const struct mtd_protect_s *prot = + (FAR const struct mtd_protect_s *)((uintptr_t)arg); + + DEBUGASSERT(prot); + ret = n25qxxx_protect(priv, prot->startblock, prot->nblocks); + } + break; + + case MTDIOC_UNPROTECT: + { + FAR const struct mtd_protect_s *prot = + (FAR const struct mtd_protect_s *)((uintptr_t)arg); + + DEBUGASSERT(prot); + ret = n25qxxx_unprotect(priv, prot->startblock, prot->nblocks); + } + break; + + default: + ret = -ENOTTY; /* Bad/unsupported command */ + break; + } + + fvdbg("return %d\n", ret); + return ret; +} + +/************************************************************************************ + * Public Functions + ************************************************************************************/ + +/************************************************************************************ + * Name: n25qxxx_initialize + * + * Description: + * Create an initialize MTD device instance for the QuadSPI-based n25Qxxx + * FLASH part. + * + * MTD devices are not registered in the file system, but are created as instances + * that can be bound to other functions (such as a block or character driver front + * end). + * + ************************************************************************************/ + +FAR struct mtd_dev_s *n25qxxx_initialize(FAR struct qspi_dev_s *qspi, bool unprotect) +{ + FAR struct n25qxxx_dev_s *priv; + int ret; + + fvdbg("qspi: %p\n", qspi); + DEBUGASSERT(qspi != NULL); + + /* Allocate a state structure (we allocate the structure instead of using + * a fixed, static allocation so that we can handle multiple FLASH devices. + * The current implementation would handle only one FLASH part per QuadSPI + * device (only because of the QSPIDEV_FLASH definition) and so would have + * to be extended to handle multiple FLASH parts on the same QuadSPI bus. + */ + + priv = (FAR struct n25qxxx_dev_s *)kmm_zalloc(sizeof(struct n25qxxx_dev_s)); + if (priv) + { + /* Initialize the allocated structure (unsupported methods were + * nullified by kmm_zalloc). + */ + + priv->mtd.erase = n25qxxx_erase; + priv->mtd.bread = n25qxxx_bread; + priv->mtd.bwrite = n25qxxx_bwrite; + priv->mtd.read = n25qxxx_read; + priv->mtd.ioctl = n25qxxx_ioctl; + priv->qspi = qspi; + + /* Allocate a 4-byte buffer to support DMA-able command data */ + + priv->cmdbuf = (FAR uint8_t *)QSPI_ALLOC(qspi, 4); + if (priv->cmdbuf == NULL) + { + fdbg("ERROR Failed to allocate command buffer\n"); + goto errout_with_priv; + } + + /* Allocate a one-byte buffer to support DMA-able status read data */ + + priv->readbuf = (FAR uint8_t *)QSPI_ALLOC(qspi, 1); + if (priv->readbuf == NULL) + { + fdbg("ERROR Failed to allocate read buffer\n"); + goto errout_with_cmdbuf; + } + + /* Identify the FLASH chip and get its capacity */ + + ret = n25qxxx_readid(priv); + if (ret != OK) + { + /* Unrecognized! Discard all of that work we just did and return NULL */ + + fdbg("ERROR Unrecognized QSPI device\n"); + goto errout_with_readbuf; + } + + /* specify the number of dummy cycles via the 'volatile configuration register' */ + + priv->cmdbuf[0] = n25qxxx_read_volcfg(priv); + priv->cmdbuf[0] &= 0x0f; + priv->cmdbuf[0] |= (CONFIG_N25QXXX_DUMMIES<<4); + n25qxxx_write_volcfg(priv); + + /* Unprotect FLASH sectors if so requested. */ + + if (unprotect) + { + ret = n25qxxx_unprotect(priv, 0, priv->nsectors - 1); + if (ret < 0) + { + fdbg("ERROR: Sector unprotect failed\n"); + } + } + +#ifdef CONFIG_N25QXXX_SECTOR512 /* Simulate a 512 byte sector */ + /* Allocate a buffer for the erase block cache */ + + priv->sector = (FAR uint8_t *)QSPI_ALLOC(qspi, 1 << priv->sectorshift); + if (priv->sector == NULL) + { + /* Allocation failed! Discard all of that work we just did and return NULL */ + + fdbg("ERROR: Sector allocation failed\n"); + goto errout_with_readbuf; + } +#endif + } + +#ifdef CONFIG_MTD_REGISTRATION + /* Register the MTD with the procfs system if enabled */ + + mtd_register(&priv->mtd, "n25qxxx"); +#endif + + /* Return the implementation-specific state structure as the MTD device */ + + fvdbg("Return %p\n", priv); + return (FAR struct mtd_dev_s *)priv; + +errout_with_readbuf: + QSPI_FREE(qspi, priv->readbuf); + +errout_with_cmdbuf: + QSPI_FREE(qspi, priv->cmdbuf); + +errout_with_priv: + kmm_free(priv); + return NULL; +} diff --git a/include/nuttx/mtd/mtd.h b/include/nuttx/mtd/mtd.h index c8a34052c8a..bd41688c358 100644 --- a/include/nuttx/mtd/mtd.h +++ b/include/nuttx/mtd/mtd.h @@ -513,6 +513,19 @@ struct qspi_dev_s; /* Forward reference */ FAR struct mtd_dev_s *s25fl1_initialize(FAR struct qspi_dev_s *qspi, bool unprotect); +/**************************************************************************** + * Name: n25qxxx_initialize + * + * Description: + * Create an initialize MTD device instance for the QuadSPI-based N25Qxxx + * FLASH part from Micron. + * + ****************************************************************************/ + +struct qspi_dev_s; /* Forward reference */ +FAR struct mtd_dev_s *n25qxxx_initialize(FAR struct qspi_dev_s *qspi, + bool unprotect); + /**************************************************************************** * Name: up_flashinitialize *