/****************************************************************************
 * drivers/mtd/s25fl1.c
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

/* Driver for QuadSPI-based S25FL116K, S25FL132K, and S25L164K */

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>

#include <sys/types.h>

#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>

#include <nuttx/kmalloc.h>
#include <nuttx/signal.h>
#include <nuttx/fs/ioctl.h>
#include <nuttx/spi/qspi.h>
#include <nuttx/mtd/mtd.h>

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

/* Configuration ************************************************************/

/* QuadSPI Mode.  Per data sheet, either Mode 0 or Mode 3 may be used. */

#ifndef CONFIG_S25FL1_QSPIMODE
#define CONFIG_S25FL1_QSPIMODE QSPIDEV_MODE0
#endif

/* QuadSPI Frequency per data sheet::
 *
 * – Normal Read (Serial):
 *   50 MHz clock rate (-40°C to +85°C/105°C)
 *   45 MHz clock rate (-40°C to +125°C)
 * – Fast Read (Serial):
 *   108 MHz clock rate (-40°C to +85°C/105°C)
 *   97 MHz clock rate (-40°C to +125°C)
 * – Dual Read:
 *   108 MHz clock rate (-40°C to +85°C/105°C)
 *   97 MHz clock rate (-40°C to +125°C)
 * – Quad Read:
 *   108 MHz clock rate (-40°C to +85°C/105°C)
 *   97 MHz clock rate for S25FL164K (-40°C to +125°C)
 *
 * Table 5.8:
 * - Clock frequency for all SPI commands except for Read Data
 *   command (0x03) and Fast Read command (0x0b): 108 MHz
 * - Clock frequency for Read Data command (0x03): 50 MHz
 * - Clock frequency for all Fast Read commands SIO and MIO: 108 MHz
 *
 * In this implementation, only "Quad" reads are performed.
 */

#ifndef CONFIG_S25FL1_QSPI_FREQUENCY
#define CONFIG_S25FL1_QSPI_FREQUENCY 108000000
#endif

/* S25FL1 Commands **********************************************************/

/* Configuration, Status, Erase, Program Commands ***************************/

/*      Command                    Value    Description:                    */

/*                                          Data sequence                   */

#define S25FL1_READ_STATUS1        0x05  /* Read status register 1:         *
                                          * 0x05 | SR1                      */
#define S25FL1_READ_STATUS2        0x35  /* Read status register 2:         *
                                          * 0x35 | SR2                      */
#define S25FL1_READ_STATUS3        0x33  /* Read status register 3:         *
                                          * 0x33 | SR3                      */
#define S25FL1_WRITE_ENABLE        0x06  /* Write enable:                   *
                                          * 0x06                            */
#define S25FL1_VWRITE_ENABLE       0x50  /* Write enable for volatile status: *
                                          * 0x50                            */
#define S25FL1_WRITE_DISABLE       0x04  /* Write disable command code:     *
                                          * 0x04                            */
#define S25FL1_WRITE_STATUS        0x01  /* Write status register:          *
                                          * 0x01 | SR1 | SR2 | SR3          */
#define S25FL1_WRAP_ENABLE         0x77  /* Set Burst with Wrap:            *
                                          * 0x77 | xx | xx | xx | SR3       */
#define S25FL1_UNPROTECT_SECTOR    0x39  /* Set Block / Pointer Protection: *
                                          * 0x39 | ADDR(MS) | ADDR(MID) | xx */
#define S25FL1_PAGE_PROGRAM        0x02  /* Page Program:                   *
                                          * 0x02 | ADDR(MS) | ADDR(MID) |   *
                                          * ADDR(LS) | data                 */
#define S25FL1_SECTOR_ERASE        0x20  /* Sector Erase (4 kB)             *
                                          * 0x02 | ADDR(MS) | ADDR(MID) |   *
                                          * ADDR(LS)                        */
#define S25FL1_BLOCK_ERASE         0xd8  /* Block Erase (64 kB):            *
                                          * 0x02 | ADDR(MS) | ADDR(MID) |   *
                                          * ADDR(LS)                        */
#define S25FL1_CHIP_ERASE_1        0x60  /* Chip Erase 1:                   *
                                          * 0x60                            */
#define S25FL1_CHIP_ERASE_2        0xc7  /* Chip Erase 2:                   *
                                          * 0xc7                            */
#define S25FL1_ERASE_PROG_SUSPEND  0x75  /* Erase / Program Suspend:        *
                                          * 0x75                            */
#define S25FL1_ERASE_PROG_RESUME   0x7a  /* Erase / Program Resume:         *
                                          * 0x7a                            */

/* Read Commands ************************************************************/

/*      Command                    Value    Description:                    */

/*                                            Data sequence                 */

#define S25FL1_READ_DATA           0x03  /* Read Data:                      *
                                          * 0x03 | ADDR(MS) | ADDR(MID) |   *
                                          * ADDR(LS) | data...              */
#define S25FL1_FAST_READ           0x0b  /* Fast Read:                      *
                                          * 0x0b | ADDR(MS) | ADDR(MID) |   *
                                          * ADDR(LS) | dummy | data...      */
#define S25FL1_FAST_READ_DUAL      0x3b  /* Fast Read Dual Output:          *
                                          * 0x3b | ADDR(MS) | ADDR(MID) |   *
                                          * ADDR(LS) | dummy | data...      */
#define S25FL1_FAST_READ_QUAD      0x6b  /* Fast Read Dual Output:          *
                                          * 0x6b | ADDR(MS) | ADDR(MID) |   *
                                          * ADDR(LS) | dummy | data...      */
#define S25FL1_FAST_READ_DUALIO    0xbb  /* Fast Read Dual I/O:             *
                                          * 0xbb | ADDR(MS) | ADDR(LS) | data... */
#define S25FL1_FAST_READ_QUADIO    0xeb  /* Fast Read Quad I/O:             *
                                          * 0xeb | ADDR | data...           */
#define S25FL1_CONT_READ_RESET     0xff  /* Continuous Read Mode Reset:     *
                                          * 0xff | 0xff                     */

/* Reset Commands ***********************************************************/

/*      Command                    Value    Description:                    */

/*                                            Data sequence                 */

#define S25FL1_SOFT_RESET_ENABLE   0x66  /* Software Reset Enable:          *
                                          *   0x66                          */
#define S25FL1_SOFT_RESET          0x99  /* Software Reset:                 *
                                          *   0x99                          */
                                         /* Continuous Read Mode Reset:     *
                                          *   0xff | 0xff                   */

/* ID/Security Commands *************************&***************************/

/*      Command                    Value    Description:                    */

/*                                            Data sequence                 */

#define S25FL1_DEEP_PWRDOWN        0xb9  /* Deep Power-down:                *
                                          *   0xb9                          */
#define S25FL1_RELEASE_PWRDOWN     0xab  /* Release Power down / Device ID: *
                                          *   0xab | dummy | dummy | dummy | *
                                          *   DeviceID  */
#define S25FL1_MANUFACTURER        0x90  /* Manufacturer / Device ID:       *
                                          *   0x90 | dummy | dummy | 0x00 | *
                                          *   Manufacturer | DeviceID       */
#define S25FL1_JEDEC_ID            0x9f  /* JEDEC ID:                       *
                                          *   0x9f | Manufacturer | MemoryType | *
                                          *   Capacity                      */
#define S25FL1_READ_SFDP           0x5a  /* Read SFDP Register / Read Unique ID  *
                                          * Number:                          *
                                          *   0x5a | 0x00 | 0x00 | ADDR | dummy | *
                                          *   data...                        */
#define S25FL1_READ_SECURITY       0x48  /* Read Security Registers:         *
                                          *   0x48 | ADDR(MS) | ADDR(MID) |  *
                                          *   ADDR(LS) | dummy | data...     */
#define S25FL1_ERASE_SECURITY      0x44  /* Erase Security Registers:        *
                                          *   0x48 | ADDR(MS) | ADDR(MID) |  *
                                          *   ADDR(LS)                       */
#define S25FL1_PROgRAM_SECURITY    0x42  /* Program Security Registers:      *
                                          *   0x42 | ADDR(MS) | ADDR(MID) |  *
                                          *   ADDR(LS) | data...             */

/* Flash Manufacturer JEDEC IDs */

#define S25FL1_JEDEC_ID_SPANSION   0x01
#define S25FL1_JEDEC_ID_ATMEL      0x1f
#define S25FL1_JEDEC_ID_ST         0x20
#define S25FL1_JEDEC_ID_SST        0xbf
#define S25FL1_JEDEC_ID_MACRONIX   0xc2
#define S25FL1_JEDEC_ID_WINBOND    0xef

/* S25FL1 JEDIC IDs */

#define S25FL1_JEDEC_DEVICE_TYPE   0x40  /* S25FL1 memory device type */
#define S25FL116K_JEDEC_CAPACITY   0x15  /* S25FL116K memory capacity */
#define S25FL132K_JEDEC_CAPACITY   0x16  /* S25FL132K memory capacity */
#define S25FL164K_JEDEC_CAPACITY   0x17  /* S25FL164K memory capacity */

/* S25FL1 Registers *********************************************************/

/* Status register bit definitions                                          */

#define STATUS1_BUSY_MASK        (1 << 0) /* Bit 0: Device ready/busy status      */
#define STATUS1_READY            (0 << 0) /*   0 = Not Busy                       */
#define STATUS1_BUSY             (1 << 0) /*   1 = Busy                           */
#define STATUS1_WEL_MASK         (1 << 1) /* Bit 1: Write enable latch status     */
#define STATUS1_WEL_DISABLED     (0 << 1) /*   0 = Not Write Enabled              */
#define STATUS1_WEL_ENABLED      (1 << 1) /*   1 = Write Enabled                  */
#define STATUS1_BP_SHIFT         (2)      /* Bits 2-4: Block protect bits         */
#define STATUS1_BP_MASK          (7 << STATUS1_BP_SHIFT)
#define STATUS1_BP_NONE          (0 << STATUS1_BP_SHIFT)
#define STATUS1_BP_ALL           (7 << STATUS1_BP_SHIFT)
#define STATUS1_TB_MASK          (1 << 5) /* Bit 5: Top / Bottom Protect          */
#define STATUS1_TB_TOP           (0 << 5) /*   0 = BP2-BP0 protect Top down       */
#define STATUS1_TB_BOTTOM        (1 << 5) /*   1 = BP2-BP0 protect Bottom up      */
#define STATUS1_SEC_MASK         (1 << 6) /* Bit 6: Sector / Block Protect        */
#define STATUS1_SEC_BLOCK        (0 << 6) /*   0 = BP2-BP0 protect 64-kB blocks   */
#define STATUS1_SEC_SECTOR       (1 << 6) /*   1 = BP2-BP0 protect 4-kB sectors   */
#define STATUS1_SRP0_MASK        (1 << 7) /* Bit 7: Status register protect 0     */
#define STATUS1_SRP0_UNLOCKED    (0 << 7) /*   0 = WP# no effect / PS Lock Down   */
#define STATUS1_SRP0_LOCKED      (1 << 7) /*   1 = WP# protect / OTP Lock Down    */

#define STATUS2_SRP1_MASK        (1 << 0) /* Bit 0: Status register protect 1     */
#define STATUS2_SRP1_UNLOCKED    (0 << 0) /*   0 = WP# no effect / PS Lock Down   */
#define STATUS2_SRP1_LOCKED      (1 << 0) /*   1 = WP# protect / OTP Lock Down    */
#define STATUS2_QUAD_ENABLE_MASK (1 << 1) /* Bit 1: Quad Enable                   */
#define STATUS2_QUAD_DISABLE     (0 << 1) /*   0 = Quad Mode Not Enabled          */
#define STATUS2_QUAD_ENABLE      (1 << 1) /*   1 = Quad Mode Enabled              */
#define STATUS2_LB_SHIFT         (2)      /* Bits 2-5: Security Register Lock     */
#define STATUS2_LB_MASK          (15 << STATUS2_LB_SHIFT)
#define STATUS2_LB_NONE          (0 << STATUS2_LB_SHIFT)
#define STATUS2_LB_ALL           (15 << STATUS2_LB_SHIFT)
#define STATUS2_CMP_MASK         (1 << 6) /* Bit 6: Complement Protect            */
#define STATUS2_CMP_NORMAL       (0 << 6) /*   0 = Normal Protection Map          */
#define STATUS2_CMP_INVERTED     (1 << 6) /*   1 = Inverted Protection Map        */
#define STATUS2_SUS_MASK         (1 << 7) /* Bit 7: Suspend Status                */
#define STATUS2_SUS_NONE         (0 << 7) /*   0 = Erase / Program not suspended  */
#define STATUS2_SUS_SUSPENDED    (1 << 7) /*   1 = Erase / Program suspended      */

#define STATUS3_LC_SHIFT         (0)      /* Bits 0-3: Latency control            */
#define STATUS3_LC_MASK          (15 << STATUS3_LC_SHIFT)
#define STATUS3_W4_MASK          (1 << 4) /* Bit 4: Burst Wrap Enable             */
#define STATUS3_W4_DISABLED      (0 << 4) /*   0 = Wrap Enabled                   */
#define STATUS3_W4_ENABLED       (1 << 4) /*   1 = Wrap Disabled                  */
#define STATUS3_W56_SHIFT        (5)      /* Bits 5-6: Burst Wrap Length          */
#define STATUS3_W56_MASK         (3 << STATUS3_W56_SHIFT)
#define STATUS3_W56_8BYTE        (0 << STATUS3_W56_SHIFT)
#define STATUS3_W56_16BYTE       (1 << STATUS3_W56_SHIFT)
#define STATUS3_W56_32BYTE       (2 << STATUS3_W56_SHIFT)
#define STATUS3_W56_63BYTE       (3 << STATUS3_W56_SHIFT)
                                            /* Bit 7: Reserved                      */

/* Chip Geometries **********************************************************/

/* All members of the family support uniform 4K-byte sectors  */

#define S25FL116K_SECTOR_SIZE    (4*1024)
#define S25FL116K_SECTOR_SHIFT   (12)
#define S25FL116K_SECTOR_COUNT   (512)
#define S25FL116K_PAGE_SIZE      (256)
#define S25FL116K_PAGE_SHIFT     (8)

#define S25FL132K_SECTOR_SIZE    (4*1024)
#define S25FL132K_SECTOR_SHIFT   (12)
#define S25FL132K_SECTOR_COUNT   (1024)
#define S25FL132K_PAGE_SIZE      (256)
#define S25FL132K_PAGE_SHIFT     (8)

#define S25FL164K_SECTOR_SIZE    (4*1024)
#define S25FL164K_SECTOR_SHIFT   (12)
#define S25FL164K_SECTOR_COUNT   (2048)
#define S25FL164K_PAGE_SIZE      (256)
#define S25FL164K_PAGE_SHIFT     (8)

/* Cache flags **************************************************************/

#define S25FL1_CACHE_VALID   (1 << 0)  /* 1=Cache has valid data */
#define S25FL1_CACHE_DIRTY   (1 << 1)  /* 1=Cache is dirty */
#define S25FL1_CACHE_ERASED  (1 << 2)  /* 1=Backing FLASH is erased */

#define IS_VALID(p)      ((((p)->flags) & S25FL1_CACHE_VALID) != 0)
#define IS_DIRTY(p)      ((((p)->flags) & S25FL1_CACHE_DIRTY) != 0)
#define IS_ERASED(p)     ((((p)->flags) & S25FL1_CACHE_ERASED) != 0)

#define SET_VALID(p)     do { (p)->flags |= S25FL1_CACHE_VALID; } while (0)
#define SET_DIRTY(p)     do { (p)->flags |= S25FL1_CACHE_DIRTY; } while (0)
#define SET_ERASED(p)    do { (p)->flags |= S25FL1_CACHE_ERASED; } while (0)

#define CLR_VALID(p)     do { (p)->flags &= ~S25FL1_CACHE_VALID; } while (0)
#define CLR_DIRTY(p)     do { (p)->flags &= ~S25FL1_CACHE_DIRTY; } while (0)
#define CLR_ERASED(p)    do { (p)->flags &= ~S25FL1_CACHE_ERASED; } while (0)

/* 512 byte sector support **************************************************/

#define S25FL1_SECTOR512_SHIFT     9
#define S25FL1_SECTOR512_SIZE      (1 << 9)
#define S25FL1_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
 * s25fl1_dev_s.
 */

struct s25fl1_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_S25FL1_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 s25fl1_lock(FAR struct qspi_dev_s *qspi);
static inline void s25fl1_unlock(FAR struct qspi_dev_s *qspi);

/* Low-level message helpers */

static int  s25fl1_command(FAR struct qspi_dev_s *qspi,
                           uint8_t cmd);
static int  s25fl1_command_address(FAR struct qspi_dev_s *qspi,
                                   uint8_t cmd,
                                   off_t addr,
                                   uint8_t addrlen);
static int  s25fl1_command_read(FAR struct qspi_dev_s *qspi,
                                uint8_t cmd,
                                FAR void *buffer,
                                size_t buflen);
static int  s25fl1_command_write(FAR struct qspi_dev_s *qspi,
                                 uint8_t cmd,
                                 FAR const void *buffer,
                                 size_t buflen);
static uint8_t sf25fl1_read_status1(FAR struct s25fl1_dev_s *priv);
static uint8_t sf25fl1_read_status2(FAR struct s25fl1_dev_s *priv);
static uint8_t sf25fl1_read_status3(FAR struct s25fl1_dev_s *priv);
static void s25fl1_write_enable(FAR struct s25fl1_dev_s *priv);
static void s25fl1_write_disable(FAR struct s25fl1_dev_s *priv);

static int  s25fl1_readid(FAR struct s25fl1_dev_s *priv);
static int  s25fl1_protect(FAR struct s25fl1_dev_s *priv,
                           off_t startblock,
                           size_t nblocks);
static int  s25fl1_unprotect(FAR struct s25fl1_dev_s *priv,
                              off_t startblock,
                             size_t nblocks);
static bool s25fl1_isprotected(FAR struct s25fl1_dev_s *priv,
                               uint8_t status,
                               off_t address);
static int  s25fl1_erase_sector(FAR struct s25fl1_dev_s *priv,
                                off_t offset);
static int  s25fl1_erase_chip(FAR struct s25fl1_dev_s *priv);
static int  s25fl1_read_byte(FAR struct s25fl1_dev_s *priv,
                             FAR uint8_t *buffer,
              off_t address, size_t nbytes);
static int  s25fl1_write_page(FAR struct s25fl1_dev_s *priv,
                              FAR const uint8_t *buffer,
                              off_t address,
                              size_t nbytes);
#ifdef CONFIG_S25FL1_SECTOR512
static int  s25fl1_flush_cache(struct s25fl1_dev_s *priv);
static FAR uint8_t *s25fl1_read_cache(struct s25fl1_dev_s *priv,
                                      off_t sector);
static void s25fl1_erase_cache(struct s25fl1_dev_s *priv,
                               off_t sector);
static int  s25fl1_write_cache(FAR struct s25fl1_dev_s *priv,
                               FAR const uint8_t *buffer,
                               off_t sector,
                               size_t nsectors);
#endif

/* MTD driver methods */

static int  s25fl1_erase(FAR struct mtd_dev_s *dev,
                         off_t startblock,
                         size_t nblocks);
static ssize_t s25fl1_bread(FAR struct mtd_dev_s *dev,
                            off_t startblock,
                            size_t nblocks,
                            FAR uint8_t *buf);
static ssize_t s25fl1_bwrite(FAR struct mtd_dev_s *dev,
                             off_t startblock,
                             size_t nblocks,
                             FAR const uint8_t *buf);
static ssize_t s25fl1_read(FAR struct mtd_dev_s *dev,
                           off_t offset,
                           size_t nbytes,
                           FAR uint8_t *buffer);
static int  s25fl1_ioctl(FAR struct mtd_dev_s *dev,
                         int cmd,
                         unsigned long arg);

/****************************************************************************
 * Private Data
 ****************************************************************************/

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name: s25fl1_lock
 ****************************************************************************/

static void s25fl1_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 bus.
   * We will retain that exclusive access until the bus is unlocked.
   */

  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 bus is being shared, then it may have been left in an
   * incompatible state.
   */

  QSPI_SETMODE(qspi, CONFIG_S25FL1_QSPIMODE);
  QSPI_SETBITS(qspi, 8);
  QSPI_SETFREQUENCY(qspi, CONFIG_S25FL1_QSPI_FREQUENCY);
}

/****************************************************************************
 * Name: s25fl1_unlock
 ****************************************************************************/

static inline void s25fl1_unlock(FAR struct qspi_dev_s *qspi)
{
  QSPI_LOCK(qspi, false);
}

/****************************************************************************
 * Name: s25fl1_command
 ****************************************************************************/

static int s25fl1_command(FAR struct qspi_dev_s *qspi, uint8_t cmd)
{
  struct qspi_cmdinfo_s cmdinfo;

  finfo("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: s25fl1_command_address
 ****************************************************************************/

static int s25fl1_command_address(FAR struct qspi_dev_s *qspi,
                                  uint8_t cmd,
                                  off_t addr,
                                  uint8_t addrlen)
{
  struct qspi_cmdinfo_s cmdinfo;

  finfo("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: s25fl1_command_read
 ****************************************************************************/

static int s25fl1_command_read(FAR struct qspi_dev_s *qspi, uint8_t cmd,
                               FAR void *buffer, size_t buflen)
{
  struct qspi_cmdinfo_s cmdinfo;

  finfo("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: s25fl1_command_write
 ****************************************************************************/

static int s25fl1_command_write(FAR struct qspi_dev_s *qspi, uint8_t cmd,
                                FAR const void *buffer, size_t buflen)
{
  struct qspi_cmdinfo_s cmdinfo;

  finfo("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: sf25fl1_read_status1
 ****************************************************************************/

static uint8_t sf25fl1_read_status1(FAR struct s25fl1_dev_s *priv)
{
  DEBUGVERIFY(s25fl1_command_read(priv->qspi, S25FL1_READ_STATUS1,
                                  (FAR void *)&priv->readbuf[0], 1));
  return priv->readbuf[0];
}

/****************************************************************************
 * Name: sf25fl1_read_status2
 ****************************************************************************/

static uint8_t sf25fl1_read_status2(FAR struct s25fl1_dev_s *priv)
{
  DEBUGVERIFY(s25fl1_command_read(priv->qspi, S25FL1_READ_STATUS2,
                                  (FAR void *)&priv->readbuf[0], 1));
  return priv->readbuf[0];
}

/****************************************************************************
 * Name: sf25fl1_read_status3
 ****************************************************************************/

static uint8_t sf25fl1_read_status3(FAR struct s25fl1_dev_s *priv)
{
  DEBUGVERIFY(s25fl1_command_read(priv->qspi, S25FL1_READ_STATUS3,
                                  (FAR void *)&priv->readbuf[0], 1));
  return priv->readbuf[0];
}

/****************************************************************************
 * Name:  s25fl1_write_enable
 ****************************************************************************/

static void s25fl1_write_enable(FAR struct s25fl1_dev_s *priv)
{
  uint8_t status;

  do
    {
      s25fl1_command(priv->qspi, S25FL1_WRITE_ENABLE);
      status = sf25fl1_read_status1(priv);
    }
  while ((status & STATUS1_WEL_MASK) != STATUS1_WEL_ENABLED);
}

/****************************************************************************
 * Name:  s25fl1_write_disable
 ****************************************************************************/

static void s25fl1_write_disable(FAR struct s25fl1_dev_s *priv)
{
  uint8_t status;

  do
    {
      s25fl1_command(priv->qspi, S25FL1_WRITE_DISABLE);
      status = sf25fl1_read_status1(priv);
    }
  while ((status & STATUS1_WEL_MASK) != STATUS1_WEL_DISABLED);
}

/****************************************************************************
 * Name:  s25fl1_write_status
 ****************************************************************************/

static void s25fl1_write_status(FAR struct s25fl1_dev_s *priv)
{
  s25fl1_write_enable(priv);
  s25fl1_command_write(priv->qspi, S25FL1_WRITE_STATUS,
                       (FAR const void *)priv->cmdbuf, 3);
  s25fl1_write_disable(priv);
}

/****************************************************************************
 * Name: s25fl1_readid
 ****************************************************************************/

static inline int s25fl1_readid(struct s25fl1_dev_s *priv)
{
  /* Lock the QuadSPI bus and configure the bus. */

  s25fl1_lock(priv->qspi);

  /* Read the JEDEC ID */

  s25fl1_command_read(priv->qspi, S25FL1_JEDEC_ID, priv->cmdbuf, 3);

  /* Unlock the bus */

  s25fl1_unlock(priv->qspi);

  finfo("Manufacturer: %02x Device Type %02x, Capacity: %02x",
        priv->cmdbuf[0], priv->cmdbuf[1], priv->cmdbuf[2]);

  /* Check for a recognized memory device type */

  if (priv->cmdbuf[1] != S25FL1_JEDEC_DEVICE_TYPE)
    {
      ferr("ERROR: Unrecognized device type: %02x\n", priv->cmdbuf[1]);
      return -ENODEV;
    }

  /* Check for a supported capacity */

  switch (priv->cmdbuf[2])
    {
      case S25FL116K_JEDEC_CAPACITY:
        priv->sectorshift = S25FL116K_SECTOR_SHIFT;
        priv->pageshift   = S25FL116K_PAGE_SHIFT;
        priv->nsectors    = S25FL116K_SECTOR_COUNT;
        break;

      case S25FL132K_JEDEC_CAPACITY:
        priv->sectorshift = S25FL132K_SECTOR_SHIFT;
        priv->pageshift   = S25FL116K_PAGE_SHIFT;
        priv->nsectors    = S25FL132K_SECTOR_COUNT;
        break;

      case S25FL164K_JEDEC_CAPACITY:
        priv->sectorshift = S25FL164K_SECTOR_SHIFT;
        priv->pageshift   = S25FL116K_PAGE_SHIFT;
        priv->nsectors    = S25FL164K_SECTOR_COUNT;
        break;

      /* Support for this part is not implemented yet */

      default:
        ferr("ERROR: Unsupported memory capacity: %02x\n", priv->cmdbuf[2]);
        return -ENODEV;
    }

  return OK;
}

/****************************************************************************
 * Name: s25fl1_protect
 ****************************************************************************/

static int s25fl1_protect(FAR struct s25fl1_dev_s *priv,
                          off_t startblock, size_t nblocks)
{
  /* Get the status register value to check the current protection */

  priv->cmdbuf[0] = sf25fl1_read_status1(priv);
  priv->cmdbuf[1] = sf25fl1_read_status2(priv);
  priv->cmdbuf[2] = sf25fl1_read_status3(priv);

  if ((priv->cmdbuf[0] & STATUS1_BP_MASK) == STATUS1_BP_NONE)
    {
      /* Protection already disabled */

      return 0;
    }

  /* Check if sector protection registers are locked */

  if ((priv->cmdbuf[0] & STATUS1_SRP0_MASK) == STATUS1_SRP0_LOCKED)
    {
      /* Yes.. unprotect section protection registers */

      priv->cmdbuf[0] &= ~STATUS1_SRP0_MASK;
      s25fl1_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] |= STATUS1_BP_MASK;
  s25fl1_write_status(priv);

  /* Check the new status */

  priv->cmdbuf[0] = sf25fl1_read_status1(priv);
  if ((priv->cmdbuf[0] & STATUS1_BP_MASK) != STATUS1_BP_MASK)
    {
      return -EACCES;
    }

  return OK;
}

/****************************************************************************
 * Name: s25fl1_unprotect
 ****************************************************************************/

static int s25fl1_unprotect(FAR struct s25fl1_dev_s *priv,
                            off_t startblock, size_t nblocks)
{
  /* Get the status register value to check the current protection */

  priv->cmdbuf[0] = sf25fl1_read_status1(priv);
  priv->cmdbuf[1] = sf25fl1_read_status2(priv);
  priv->cmdbuf[2] = sf25fl1_read_status3(priv);

  if ((priv->cmdbuf[0] & STATUS1_BP_MASK) == STATUS1_BP_NONE &&
      (priv->cmdbuf[1] & STATUS2_CMP_MASK) == 0)
    {
      /* Protection already disabled */

      return 0;
    }

  /* Check if sector protection registers are locked */

  if ((priv->cmdbuf[0] & STATUS1_SRP0_MASK) == STATUS1_SRP0_LOCKED)
    {
      /* Yes.. unprotect section protection registers */

      priv->cmdbuf[0] &= ~STATUS1_SRP0_MASK;
      s25fl1_write_status(priv);
    }

  /* 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] &= ~STATUS1_BP_MASK;
  priv->cmdbuf[1] &= ~STATUS2_CMP_MASK;
  s25fl1_write_status(priv);

  /* Check the new status */

  priv->cmdbuf[0] = sf25fl1_read_status1(priv);
  if ((priv->cmdbuf[0] & (STATUS1_SRP0_MASK | STATUS1_BP_MASK)) != 0)
    {
      return -EACCES;
    }

  return OK;
}

/****************************************************************************
 * Name: s25fl1_isprotected
 ****************************************************************************/

static bool s25fl1_isprotected(FAR struct s25fl1_dev_s *priv, uint8_t status,
                               off_t address)
{
  off_t protstart;
  off_t protend;
  off_t protsize;
  unsigned int bp;

  /* What is protected? 64 Kb blocks?  Or 4Kb sectors? */

  if ((status & STATUS1_SEC_MASK) == STATUS1_SEC_BLOCK)
    {
      /* 64 Kb block */

      protsize = 0x00010000;
    }
  else
    {
      /* 4 Kb sector */

      protsize = 0x00001000;
    }

  /* The BP field is the essentially a multiplier on this protection size */

  bp = (status & STATUS1_BP_MASK) >> STATUS1_BP_SHIFT;
  switch (bp)
    {
      case 0:
        return false;

      case 1:
        break;

      case 6:
      case 7:
        return true;

       default:
        protsize <<= (protsize << (bp - 1));
        break;
    }

  /* The final protection range then depends on if the protection region is
   * configured top-down or bottom up  (assuming CMP=0).
   */

  if ((status & STATUS1_TB_MASK) != 0)
    {
      protstart = 0x00000000;
      protend   = protstart + protsize;
    }
  else
    {
      protend   = 0x00200000;
      protstart = protend - protsize;
    }

  return (address >= protstart && address < protend);
}

/****************************************************************************
 * Name:  s25fl1_erase_sector
 ****************************************************************************/

static int s25fl1_erase_sector(struct s25fl1_dev_s *priv, off_t sector)
{
  off_t address;
  uint8_t status;

  finfo("sector: %08lx\n", (unsigned long)sector);

  /* Check that the flash is ready and unprotected */

  status = sf25fl1_read_status1(priv);
  if ((status & STATUS1_BUSY_MASK) != STATUS1_READY)
    {
      ferr("ERROR: Flash busy: %02x", status);
      return -EBUSY;
    }

  /* Get the address associated with the sector */

  address = (off_t)sector << priv->sectorshift;

  if ((status & STATUS1_BP_MASK) != 0 &&
      s25fl1_isprotected(priv, status, address))
    {
      ferr("ERROR: Flash protected: %02x", status);
      return -EACCES;
    }

  /* Send the sector erase command */

  s25fl1_write_enable(priv);
  s25fl1_command_address(priv->qspi, S25FL1_SECTOR_ERASE, address, 3);

  /* Wait for erasure to finish */

  while ((sf25fl1_read_status1(priv) & STATUS1_BUSY_MASK) != 0);
  return OK;
}

/****************************************************************************
 * Name:  s25fl1_erase_chip
 ****************************************************************************/

static int s25fl1_erase_chip(struct s25fl1_dev_s *priv)
{
  uint8_t status;

  /* Check if the FLASH is protected */

  status = sf25fl1_read_status1(priv);
  if ((status & STATUS1_BP_MASK) != 0)
    {
      ferr("ERROR: FLASH is Protected: %02x", status);
      return -EACCES;
    }

  /* Erase the whole chip */

  s25fl1_write_enable(priv);
  s25fl1_command(priv->qspi, S25FL1_CHIP_ERASE_2);

  /* Wait for the erasure to complete */

  status = sf25fl1_read_status1(priv);
  while ((status & STATUS1_BUSY_MASK) != 0)
    {
      nxsig_usleep(200 * 1000);
      status = sf25fl1_read_status1(priv);
    }

  return OK;
}

/****************************************************************************
 * Name: s25fl1_read_byte
 ****************************************************************************/

static int s25fl1_read_byte(FAR struct s25fl1_dev_s *priv,
                            FAR uint8_t *buffer,
                            off_t address,
                            size_t buflen)
{
  struct qspi_meminfo_s meminfo;

  finfo("address: %08lx nbytes: %d\n", (long)address, (int)buflen);

#ifdef CONFIG_S25FL1_SCRAMBLE
  meminfo.flags   = QSPIMEM_READ | QSPIMEM_QUADIO | QSPIMEM_SCRAMBLE;
#else
  meminfo.flags   = QSPIMEM_READ | QSPIMEM_QUADIO;
#endif
  meminfo.addrlen = 3;
  meminfo.dummies = 6;
  meminfo.buflen  = buflen;
  meminfo.cmd     = S25FL1_FAST_READ_QUADIO;
  meminfo.addr    = address;
#ifdef CONFIG_S25FL1_SCRAMBLE
  meminfo.key     = CONFIG_S25FL1_SCRAMBLE_KEY;
#endif
  meminfo.buffer  = buffer;

  return QSPI_MEMORY(priv->qspi, &meminfo);
}

/****************************************************************************
 * Name:  s25fl1_write_page
 ****************************************************************************/

static int s25fl1_write_page(struct s25fl1_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;

  finfo("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 */

#ifdef CONFIG_S25FL1_SCRAMBLE
  meminfo.flags   = QSPIMEM_WRITE | QSPIMEM_SCRAMBLE;
#else
  meminfo.flags   = QSPIMEM_WRITE;
#endif
  meminfo.cmd     = S25FL1_PAGE_PROGRAM;
  meminfo.addrlen = 3;
  meminfo.buflen  = pagesize;
#ifdef CONFIG_S25FL1_SCRAMBLE
  meminfo.key     = CONFIG_S25FL1_SCRAMBLE_KEY;
#endif
  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 */

      s25fl1_write_enable(priv);
      ret = QSPI_MEMORY(priv->qspi, &meminfo);
      s25fl1_write_disable(priv);

      if (ret < 0)
        {
          ferr("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: s25fl1_flush_cache
 ****************************************************************************/

#ifdef CONFIG_S25FL1_SECTOR512
static int s25fl1_flush_cache(struct s25fl1_dev_s *priv)
{
  int ret = OK;

  /* If the cached 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 numuber into a FLASH address */

      address = (off_t)priv->esectno << priv->sectorshift;

      /* Write entire erase block to FLASH */

      ret = s25fl1_write_page(priv,
                              priv->sector,
                              address,
                              1 << priv->sectorshift);
      if (ret < 0)
        {
          ferr("ERROR: s25fl1_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: s25fl1_read_cache
 ****************************************************************************/

#ifdef CONFIG_S25FL1_SECTOR512
static FAR uint8_t *s25fl1_read_cache(struct s25fl1_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 - S25FL1_SECTOR512_SHIFT;
  esectno  = sector >> shift;
  finfo("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 = s25fl1_flush_cache(priv);
      if (ret < 0)
        {
          ferr("ERROR: s25fl1_flush_cache failed: %d\n", ret);
          return NULL;
        }

      /* Read the erase block into the cache */

      ret = s25fl1_read_byte(priv, priv->sector,
                             (esectno << priv->sectorshift),
                             (1 << priv->sectorshift));
      if (ret < 0)
        {
          ferr("ERROR: s25fl1_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 << S25FL1_SECTOR512_SHIFT];
}
#endif

/****************************************************************************
 * Name: s25fl1_erase_cache
 ****************************************************************************/

#ifdef CONFIG_S25FL1_SECTOR512
static void s25fl1_erase_cache(struct s25fl1_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 = s25fl1_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 - S25FL1_SECTOR512_SHIFT);
      finfo("sector: %ld esectno: %d\n", sector, esectno);

      DEBUGVERIFY(s25fl1_erase_sector(priv, esectno));
      SET_ERASED(priv);
    }

  /* Put the cached sector data into the erase state and mart the cache as
   * dirty (but don't update the FLASH yet.  The caller will do that at a
   * more optimal time).
   */

  memset(dest, S25FL1_ERASED_STATE, S25FL1_SECTOR512_SIZE);
  SET_DIRTY(priv);
}
#endif

/****************************************************************************
 * Name: s25fl1_write_cache
 ****************************************************************************/

#ifdef CONFIG_S25FL1_SECTOR512
static int s25fl1_write_cache(FAR struct s25fl1_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 = s25fl1_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 - S25FL1_SECTOR512_SHIFT);
          finfo("sector: %ld esectno: %d\n", sector, esectno);

          ret = s25fl1_erase_sector(priv, esectno);
          if (ret < 0)
            {
              ferr("ERROR: s25fl1_erase_sector failed: %d\n", ret);
              return ret;
            }

          SET_ERASED(priv);
        }

      /* Copy the new sector data into cached erase block */

      memcpy(dest, buffer, S25FL1_SECTOR512_SIZE);
      SET_DIRTY(priv);

      /* Set up for the next 512 byte sector */

      buffer += S25FL1_SECTOR512_SIZE;
      sector++;
    }

  /* Flush the last erase block left in the cache */

  return s25fl1_flush_cache(priv);
}
#endif

/****************************************************************************
 * Name: s25fl1_erase
 ****************************************************************************/

static int s25fl1_erase(FAR struct mtd_dev_s *dev,
                        off_t startblock,
                        size_t nblocks)
{
  FAR struct s25fl1_dev_s *priv = (FAR struct s25fl1_dev_s *)dev;
  size_t blocksleft = nblocks;
#ifdef CONFIG_S25FL1_SECTOR512
  int ret;
#endif

  finfo("startblock: %08lx nblocks: %d\n", (long)startblock, (int)nblocks);

  /* Lock access to the SPI bus until we complete the erase */

  s25fl1_lock(priv->qspi);

  while (blocksleft-- > 0)
    {
      /* Erase each sector */

#ifdef CONFIG_S25FL1_SECTOR512
      s25fl1_erase_cache(priv, startblock);
#else
      s25fl1_erase_sector(priv, startblock);
#endif
      startblock++;
    }

#ifdef CONFIG_S25FL1_SECTOR512
  /* Flush the last erase block left in the cache */

  ret = s25fl1_flush_cache(priv);
  if (ret < 0)
    {
      nblocks = ret;
    }
#endif

  s25fl1_unlock(priv->qspi);
  return (int)nblocks;
}

/****************************************************************************
 * Name: s25fl1_bread
 ****************************************************************************/

static ssize_t s25fl1_bread(FAR struct mtd_dev_s *dev, off_t startblock,
                            size_t nblocks, FAR uint8_t *buffer)
{
#ifndef CONFIG_S25FL1_SECTOR512
  FAR struct s25fl1_dev_s *priv = (FAR struct s25fl1_dev_s *)dev;
#endif
  ssize_t nbytes;

  finfo("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_S25FL1_SECTOR512
  nbytes = s25fl1_read(dev, startblock << S25FL1_SECTOR512_SHIFT,
                       nblocks << S25FL1_SECTOR512_SHIFT, buffer);
  if (nbytes > 0)
    {
      nbytes >>= S25FL1_SECTOR512_SHIFT;
    }
#else
  nbytes = s25fl1_read(dev, startblock << priv->pageshift,
                       nblocks << priv->pageshift, buffer);
  if (nbytes > 0)
    {
      nbytes >>= priv->pageshift;
    }
#endif

  return nbytes;
}

/****************************************************************************
 * Name: s25fl1_bwrite
 ****************************************************************************/

static ssize_t s25fl1_bwrite(FAR struct mtd_dev_s *dev, off_t startblock,
                             size_t nblocks, FAR const uint8_t *buffer)
{
  FAR struct s25fl1_dev_s *priv = (FAR struct s25fl1_dev_s *)dev;
  int ret = (int)nblocks;

  finfo("startblock: %08lx nblocks: %d\n", (long)startblock, (int)nblocks);

  /* Lock the QuadSPI bus and write all of the pages to FLASH */

  s25fl1_lock(priv->qspi);

#if defined(CONFIG_S25FL1_SECTOR512)
  ret = s25fl1_write_cache(priv, buffer, startblock, nblocks);
  if (ret < 0)
    {
      ferr("ERROR: s25fl1_write_cache failed: %d\n", ret);
    }

#else
  ret = s25fl1_write_page(priv, buffer, startblock << priv->pageshift,
                          nblocks << priv->pageshift);
  if (ret < 0)
    {
      ferr("ERROR: s25fl1_write_page failed: %d\n", ret);
    }
#endif

  s25fl1_unlock(priv->qspi);

  return ret < 0 ? ret : nblocks;
}

/****************************************************************************
 * Name: s25fl1_read
 ****************************************************************************/

static ssize_t s25fl1_read(FAR struct mtd_dev_s *dev,
                           off_t offset,
                           size_t nbytes,
                           FAR uint8_t *buffer)
{
  FAR struct s25fl1_dev_s *priv = (FAR struct s25fl1_dev_s *)dev;
  int ret;

  finfo("offset: %08lx nbytes: %d\n", (long)offset, (int)nbytes);

  /* Lock the QuadSPI bus and select this FLASH part */

  s25fl1_lock(priv->qspi);
  ret = s25fl1_read_byte(priv, buffer, offset, nbytes);
  s25fl1_unlock(priv->qspi);

  if (ret < 0)
    {
      ferr("ERROR: s25fl1_read_byte returned: %d\n", ret);
      return (ssize_t)ret;
    }

  finfo("return nbytes: %d\n", (int)nbytes);
  return (ssize_t)nbytes;
}

/****************************************************************************
 * Name: s25fl1_ioctl
 ****************************************************************************/

static int s25fl1_ioctl(FAR struct mtd_dev_s *dev,
                        int cmd,
                        unsigned long arg)
{
  FAR struct s25fl1_dev_s *priv = (FAR struct s25fl1_dev_s *)dev;
  int ret = -EINVAL; /* Assume good command with bad parameters */

  finfo("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_S25FL1_SECTOR512
              geo->blocksize    = (1 << S25FL1_SECTOR512_SHIFT);
              geo->erasesize    = (1 << S25FL1_SECTOR512_SHIFT);
              geo->neraseblocks = priv->nsectors <<
                                (priv->sectorshift - S25FL1_SECTOR512_SHIFT);
#else
              geo->blocksize    = (1 << priv->pageshift);
              geo->erasesize    = (1 << priv->sectorshift);
              geo->neraseblocks = priv->nsectors;
#endif
              ret               = OK;

              finfo("blocksize: %d erasesize: %d neraseblocks: %d\n",
                    geo->blocksize, geo->erasesize, geo->neraseblocks);
            }
        }
        break;

      case BIOC_PARTINFO:
        {
          FAR struct partition_info_s *info =
            (FAR struct partition_info_s *)arg;
          if (info != NULL)
            {
#ifdef CONFIG_S25FL1_SECTOR512
              info->numsectors  = priv->nsectors <<
                                (priv->sectorshift - S25FL1_SECTOR512_SHIFT);
              info->sectorsize  = 1 << S25FL1_SECTOR512_SHIFT;
#else
              info->numsectors  = priv->nsectors <<
                                  (priv->sectorshift - priv->pageshift);
              info->sectorsize  = 1 << priv->pageshift;
#endif
              info->startsector = 0;
              info->parent[0]   = '\0';
              ret               = OK;
            }
        }
        break;

      case MTDIOC_BULKERASE:
        {
          /* Erase the entire device */

          s25fl1_lock(priv->qspi);
          ret = s25fl1_erase_chip(priv);
          s25fl1_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 = s25fl1_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 = s25fl1_unprotect(priv, prot->startblock, prot->nblocks);
        }
        break;

      case MTDIOC_ERASESTATE:
        {
          FAR uint8_t *result = (FAR uint8_t *)arg;
          *result = S25FL1_ERASED_STATE;

          ret = OK;
        }
        break;

      default:
        ret = -ENOTTY; /* Bad/unsupported command */
        break;
    }

  finfo("return %d\n", ret);
  return ret;
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: s25fl1_initialize
 *
 * Description:
 *   Create an initialize MTD device instance for the QuadSPI-based ST24FL1
 *   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 *s25fl1_initialize(FAR struct qspi_dev_s *qspi,
                                        bool unprotect)
{
  FAR struct s25fl1_dev_s *priv;
  int ret;

  finfo("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(0) definition) and so would
   * have to be extended to handle multiple FLASH parts on the same QuadSPI
   * bus.
   */

  priv = (FAR struct s25fl1_dev_s *)kmm_zalloc(sizeof(struct s25fl1_dev_s));
  if (priv)
    {
      /* Initialize the allocated structure (unsupported methods were
       * nullified by kmm_zalloc).
       */

      priv->mtd.erase  = s25fl1_erase;
      priv->mtd.bread  = s25fl1_bread;
      priv->mtd.bwrite = s25fl1_bwrite;
      priv->mtd.read   = s25fl1_read;
      priv->mtd.ioctl  = s25fl1_ioctl;
      priv->mtd.name   = "s25fl1";
      priv->qspi       = qspi;

      /* Allocate a 4-byte buffer to support DMA command data */

      priv->cmdbuf = (FAR uint8_t *)QSPI_ALLOC(qspi, 4);
      if (priv->cmdbuf == NULL)
        {
          ferr("ERROR Failed to allocate command buffer\n");
          goto errout_with_priv;
        }

      /* Allocate a one-byte buffer to support DMA status read data */

      priv->readbuf = (FAR uint8_t *)QSPI_ALLOC(qspi, 1);
      if (priv->readbuf == NULL)
        {
          ferr("ERROR Failed to allocate read buffer\n");
          goto errout_with_cmdbuf;
        }

      /* Identify the FLASH chip and get its capacity */

      ret = s25fl1_readid(priv);
      if (ret != OK)
        {
          /* Unrecognized! Discard all of that work we just did and return
           * NULL
           */

          ferr("ERROR Unrecognized QSPI device\n");
          goto errout_with_readbuf;
        }

      /* Enable quad mode */

      priv->cmdbuf[0] = sf25fl1_read_status1(priv);
      priv->cmdbuf[1] = sf25fl1_read_status2(priv);
      priv->cmdbuf[2] = sf25fl1_read_status3(priv);

      while ((priv->cmdbuf[1] & STATUS2_QUAD_ENABLE_MASK) == 0)
        {
          priv->cmdbuf[1] |= STATUS2_QUAD_ENABLE;
          s25fl1_write_status(priv);
          priv->cmdbuf[1] = sf25fl1_read_status2(priv);
          nxsig_usleep(50 * 1000);
        }

      /* Unprotect FLASH sectors if so requested. */

      if (unprotect)
        {
          ret = s25fl1_unprotect(priv, 0, priv->nsectors - 1);
          if (ret < 0)
            {
              ferr("ERROR: Sector unprotect failed\n");
            }
        }

#ifdef CONFIG_S25FL1_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
           */

          ferr("ERROR: Sector allocation failed\n");
          goto errout_with_readbuf;
        }
#endif
    }

  /* Return the implementation-specific state structure as the MTD device */

  finfo("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;
}
