This is an automated email from the ASF dual-hosted git repository.

xiaoxiang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git


The following commit(s) were added to refs/heads/master by this push:
     new acd7e44cad Add GD55 QSPI NOR Flash support
acd7e44cad is described below

commit acd7e44cadc78a39ad163d1fd08c270463d55882
Author: Tim Hardisty <56726697+tim...@users.noreply.github.com>
AuthorDate: Thu Oct 17 00:01:49 2024 +0100

    Add GD55 QSPI NOR Flash support
---
 drivers/mtd/Kconfig      |   32 +
 drivers/mtd/Make.defs    |    4 +
 drivers/mtd/gd55.c       | 2079 ++++++++++++++++++++++++++++++++++++++++++++++
 include/nuttx/mtd/mtd.h  |   11 +
 include/nuttx/spi/qspi.h |   14 +-
 5 files changed, 2133 insertions(+), 7 deletions(-)

diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig
index 0dc2f38c6f..0ec2c4d98c 100644
--- a/drivers/mtd/Kconfig
+++ b/drivers/mtd/Kconfig
@@ -923,6 +923,38 @@ config MX25RXX_LXX
 
 endif # MTD_MX25RXX
 
+config MTD_GD55
+       bool "QuadSPI-based GigaDevices GD55 family FLASH"
+       default n
+       ---help---
+               Support GD55 QSPI devices
+
+if MTD_GD55
+
+config MTD_GD55_QSPIMODE
+       int "MTD_GD55 QuadSPI Mode"
+       default 3
+       ---help---
+               This device can operate in SPI mode 0 or 3.
+
+config MTD_GD55_FREQUENCY
+       int "MTD_GD55 QSPI Frequency"
+       default 133000000
+       ---help---
+               Clock frequency for all instructions except for non-QSPI read
+               commands (e.g. Read ID) and DTR read, neither of which are used 
in
+               this driver)
+
+config MTD_GD55_SECTOR512
+       bool "Emulate 512 byte Erase Blocks"
+       default n
+       ---help---
+               This is used to map the native 256 byte sectors to 512 byte 
sectors.
+               It is useful if using a file system that demands 512 byte 
sectors,
+               such as FAT
+
+endif # MTD_GD55
+
 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 0bc3cce279..2a5a7a3153 100644
--- a/drivers/mtd/Make.defs
+++ b/drivers/mtd/Make.defs
@@ -133,6 +133,10 @@ ifeq ($(CONFIG_MTD_GD25),y)
 CSRCS += gd25.c
 endif
 
+ifeq ($(CONFIG_MTD_GD55),y)
+CSRCS += gd55.c
+endif
+
 ifeq ($(CONFIG_MTD_GD5F),y)
 CSRCS += gd5f.c
 endif
diff --git a/drivers/mtd/gd55.c b/drivers/mtd/gd55.c
new file mode 100644
index 0000000000..eec99fb2d4
--- /dev/null
+++ b/drivers/mtd/gd55.c
@@ -0,0 +1,2079 @@
+/****************************************************************************
+ * drivers/mtd/gd55.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.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+#include <assert.h>
+#include <errno.h>
+#include <debug.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#ifdef CONFIG_MTD_GD55_SECTOR512
+#  include <stdlib.h>
+#  include <string.h>
+#endif
+
+#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
+ ****************************************************************************/
+
+/* 4 byte addressing is needed for addresses needing more than a 3 byte
+ * address, i.e. 16Mbyte
+ */
+
+#define MODE_3BYTE_LIMIT        ((16 * 1024 * 1024))
+
+/* GD55 Commands                                                            */
+
+#define GD55_QREAD              0x6b  /* Quad output fast read              */
+#define GD55_QREAD_DUMMIES      8
+#define GD55_QC_READ            0xeb  /* Quad output continuous fast read   */
+#define GD55_QC_READ_DUMMIES    6
+#define GD55_EQPP               0xc2  /* Extended quad page program         */
+#define GD55_EQPP_DUMMIES       0     /* No dummy clocks                    */
+#define GD55_SE                 0x20  /* 4Kb Sector erase                   */
+#define GD55_BE32               0x52  /* 32Kbit block Erase                 */
+#define GD55_BE64               0xd8  /* 64Kbit block Erase                 */
+#define GD55_CE                 0x60  /* Chip erase (alternate)             */
+#define GD55_WREN               0x06  /* Write Enable                       */
+#define GD55_WRDI               0x04  /* Write Disable                      */
+#define GD55_RDSR1              0x05  /* Read status register 1             */
+#define GD55_EN4B               0xb7  /* Enable 4 byte Addressing Mode      */
+#define GD55_DIS4B              0xe9  /* Disable 4 byte Addressing Mode     */
+#define GD55_IBSL               0x36  /* Individual block/sector lock       */
+#define GD55_IBSUL              0x39  /* Individual block/sector unlock     */
+#define GD55_RIBSL              0x3d  /* Read individual block/sector lock  */
+#define GD55_RDNVCR             0xb5  /* Read Non-Volatile config register  */
+#define GD55_RD_NVCR_DUMMIES    8
+#define GD55_RDSR2              0x35  /* Read status register 2             */
+#define GD55_WRSR1              0x01  /* Write status register 1            */
+#define GD55_SE_ALT             0x21  /* Alternate 4Kb Sector erase         */
+#define GD55_QC_READ_ALT        0xec  /* Quad output continuous fast read   */
+#define GD55_4B_QDTR_READ       0xed  /* Quad I/O DTR read                  */
+#define GD55_4B_QDTR_READ_ALT   0xee  /* Alternate quad I/O DTR read        */
+#define GD55_PP                 0x02  /* Page program (SPI, not used)       */
+#define GD55_PP_ALT             0x12  /* Aternate page program (SPI)        */
+#define GD55_BE32_ALT           0x5c  /* Alternate 32Kbit block Erase       */
+#define GD55_BE64_ALT           0xd8  /* ALternate 64Kbit block Erase       */
+#define GD55_CE_ALT             0xc7  /* Alternate chip erase               */
+#define GD55_QPP                0x32  /* Quad page program                  */
+#define GD55_QPP_ALT            0x34  /* ALternate quad page program        */
+#define GD55_QPP_DUMMIES        0     /* No dummy clocks                    */
+#define GD55_QPIEN              0x38  /* Enable QPI Operation               */
+#define GD55_QPIDIS             0xff  /* Disable QPI Operation              */
+#define GD55_DP                 0xb9  /* Deep power down                    */
+#define GD55_RDP                0xab  /* Release deep power down            */
+#define GD55_RUID               0x4b  /* Read Unique ID                     */
+#define GD55_RDID               0x9e  /* Read identification                */
+#define GD55_RDID_ALT           0x9f  /* Read identification (alternate)    */
+#define GD55_PE_SUSPEND         0x75  /* Suspends program/erase             */
+#define GD55_PE_RESUME          0x7a  /* Resume program                     */
+#define GD55_RDVCR              0x85  /* Read Volatile config register      */
+#define GD55_RD_VCR_DUMMIES     1
+#define GD55_WRSR2              0x31  /* Write status register 2            */
+#define GD55_WRNVCR             0xb1  /* Write Non-Volatile config register */
+#define GD55_WRENVSC            0x50  /* Write en. Volatile config register */
+#define GD55_WRVCR              0x91  /* Write Volatile config register     */
+#define GD55_WREAR              0xc5  /* Write Extended address register    */
+#define GD55_EARR               0xc8  /* Read extended address register     */
+#define GD55_RSFDP              0x5a  /* Read SFDP                          */
+#define GD55_RDSCUR             0x48  /* Read security register             */
+#define GD55_WRSCUR             0x42  /* Write security register            */
+#define GD55_ERSCUR             0x44  /* Erase security register            */
+#define GD55_RSTEN              0x66  /* Reset Enable                       */
+#define GD55_RST                0x99  /* Reset Memory                       */
+#define GD55_GBSL               0x7e  /* Global block/sector lock           */
+#define GD55_GBSUL              0x98  /* Global block/sector unlock         */
+
+/* Read ID (RDID) register values                                           */
+
+#define GD55_MANUFACTURER       0xc8  /* GigaSevice manufacturer ID         */
+
+/* JEDEC Read ID register values                                            */
+
+#define GD55_JEDEC_MANUFACTURER 0xc8 /* GigaDevice manufacturer ID          */
+
+#define GD55B_JEDEC_MEMORY_TYPE 0x47 /* GD55B memory type, 3V               */
+#define GD55L_JEDEC_MEMORY_TYPE 0x67 /* GD55L memory type, 1.8V             */
+#define GD55_JEDEC_1G_CAPACITY  0x1b /* 1Gbit memory capacity               */
+#define GD55_JEDEC_2G_CAPACITY  0x1c /* 2Gbit memory capacity               */
+
+/* GD55 devices all have identical sector sizes:
+ * block protection size: 64KiB
+ * sector size:           4KiB
+ * page size:             256B
+ */
+
+#define GD55_SECTOR_SHIFT       (12)
+#define GD55_SECTOR_SIZE        (1 << GD55_SECTOR_SHIFT)           /* 4KiB  */
+#define GD55_PAGE_SHIFT         (8)                                /* 256B  */
+#define GD55_PAGE_SIZE          (1 << GD55_PAGE_SHIFT)
+#define GD55_BP_SHIFT           (16)
+#define GD55_BP_SIZE            (1 << GD55_BP_SHIFT)               /* 64KiB */
+#define GD55_MIN_BP_BLKS        (GD55_BP_SIZE  >> GD55_PAGE_SHIFT)
+#define GD55_SECTORS_PER_BP_BLK (GD55_BP_SIZE / GD55_SECTOR_SIZE)
+
+/* GD55B01xx (128 MiB) memory capacity                                      */
+
+#define GD55_NSECTORS_1GBIT     (32768)
+
+/* GD55B02xx (256 MiB) memory capacity                                      */
+
+#define GD55_NSECTORS_2GBIT     (65536)
+
+/* 512 byte sector support **************************************************/
+
+#define GD55_SECTOR512_SHIFT    (9)
+#define GD55_SECTOR512_SIZE     (1 << GD55_SECTOR512_SHIFT)
+
+/* Status register 1 bit definitions                                        */
+
+#define GD55_SR_WIP             (1 << 0)  /* Bit 0: Write in progress       */
+#define GD55_SR_WEL             (1 << 1)  /* Bit 1: Write enable latch      */
+#define GD55_SR_BP_SHIFT        (2)       /* Bits 2-6: Block protect bits   */
+#define GD55_SR_BP_MASK         (31 << GD55_SR_BP_SHIFT)
+#define GD55_STATUS_BP_NONE     (0 << GD55_SR_BP_SHIFT)
+#define GD55_STATUS_BP_ALL      (7 << GD55_SR_BP_SHIFT)
+#define GD55_STATUS_TB_MASK     (1 << 6)  /* BP4 Top/Bottom Protect         */
+#define GD55_STATUS_TB_TOP      (0 << 6)  /*   = 0, BP3..0 protect Top down */
+#define GD55_STATUS_TB_BOTTOM   (1 << 6)  /*   = 1, BP3..0   "   Bottom up  */
+#define GD55_SR_BP_TOP(b)       (((b + 1) << GD55_SR_BP_SHIFT) | \
+                                 GD55_STATUS_TB_TOP)
+#define GD55_SR_BP_BOTTOM(b)    (((b + 1) << GD55_SR_BP_SHIFT) | \
+                                 GD55_STATUS_TB_BOTTOM)
+#define GD55_BP_ALL             (14 << GD55_SR_BP_SHIFT)
+                                          /* GD55B01 needs BP bits = 0xx11xx
+                                           * GD55B02 needs BP bits = 0xx111x
+                                           */
+#define GD55_SR_SRP0            (1 << 7)  /* Bit 7: SR protect bit 0        */
+
+/* Status register 2 bit definitions                                        */
+
+#define GD55_SR_ADS             (1 << 0)  /* Bit 0: Current Address Mode    */
+                                          /* Bit 1 - reserved               */
+#define GD55_SR_SUS2            (1 << 2)  /* Bit 2: Program suspend bit 2   */
+#define GD55_SR_LB              (1 << 3)  /* Bit 3: Security Register Lock  */
+#define GD55_SR_PE              (1 << 4)  /* Bit 4: Program Error Bit       */
+#define GD55_SR_EE              (1 << 5)  /* Bit 5: Erase Error Bit         */
+#define GD55_SR_SRP1            (1 << 6)  /* Bit 6: SR protection bit 1     */
+#define GD55_SR_SUS1            (1 << 7)  /* Bit 7: Program suspend bit 1   */
+
+/* Non-volatile and volatile config register addresses and bits             */
+
+#define GD55_DUMMY_CYCLES_REG   1         /* Dummy Cycle Configuration      */
+#define GD55_ODT_DS_REG         3         /* On-die termination and driver
+                                           * strength configuration
+                                           */
+#define GD55_DLP_PROT_REG       4         /* Data Learning and protect mode */
+#define GD55_PROT_MODE_MASK     (1 << 2)  /* Bit 2, BP or WPS mode          */
+#define GD55_PROT_MODE_WPS      (0 << 2)  /* 0 = Sector Protect mode        */
+#define GD55_PROT_MODE_BP       (1 << 2)  /* 1 = Block Protect mode (def.)  */
+#define GD55_4BYTE_MODE_REG     5         /* 3 pr 4-byte address mode       */
+#define GD55_XIP_MODE_REG       6         /* XIP (continuous read) mode     */
+#define GD55_WRAP_CONFIG_REG    7         /* Wrap mode (none/64/32/16 byte) */
+
+/* Block protection bit */
+
+#define GD55_BLK_PROTECTED      (1 << 0)  /* lsb set means block is locked  */
+
+/* Cache flags **************************************************************/
+
+#define GD55_CACHE_VALID        (1 << 0)  /* 1=Cache has valid data         */
+#define GD55_CACHE_DIRTY        (1 << 1)  /* 1=Cache is dirty               */
+#define GD55_CACHE_ERASED       (1 << 2)  /* 1=Backing FLASH is erased      */
+
+#define IS_VALID(p)             ((((p)->flags) & GD55_CACHE_VALID)  != 0)
+#define IS_DIRTY(p)             ((((p)->flags) & GD55_CACHE_DIRTY)  != 0)
+#define IS_ERASED(p)            ((((p)->flags) & GD55_CACHE_ERASED) != 0)
+
+#define SET_VALID(p)  do { (p)->flags |= GD55_CACHE_VALID;   } while (0)
+#define SET_DIRTY(p)  do { (p)->flags |= GD55_CACHE_DIRTY;   } while (0)
+#define SET_ERASED(p) do { (p)->flags |= GD55_CACHE_ERASED;  } while (0)
+
+#define CLR_VALID(p)  do { (p)->flags &= ~GD55_CACHE_VALID;  } while (0)
+#define CLR_DIRTY(p)  do { (p)->flags &= ~GD55_CACHE_DIRTY;  } while (0)
+#define CLR_ERASED(p) do { (p)->flags &= ~GD55_CACHE_ERASED; } while (0)
+
+#define GD55_ERASED_STATE       0xff
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* Internal state of the MTD device */
+
+struct gd55_dev_s
+{
+  struct mtd_dev_s       mtd;         /* MTD interface                      */
+  FAR struct qspi_dev_s *qspi;        /* QuadSPI interface                  */
+  FAR uint8_t           *cmdbuf;      /* Allocated command buffer           */
+  FAR uint8_t           *readbuf;     /* Allocated status read buffer       */
+  uint32_t               nsectors;    /* Number of erase sectors            */
+#ifdef CONFIG_MTD_GD55_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
+ ****************************************************************************/
+
+/* MTD driver methods */
+
+static int     gd55_erase(FAR struct mtd_dev_s *dev, off_t startblock,
+                          size_t nblocks);
+static ssize_t gd55_bread(FAR struct mtd_dev_s *dev, off_t startblock,
+                          size_t nblocks, FAR uint8_t *buf);
+static ssize_t gd55_bwrite(FAR struct mtd_dev_s *dev, off_t startblock,
+                           size_t nblocks, FAR const uint8_t *buf);
+static ssize_t gd55_read(FAR struct mtd_dev_s *dev, off_t offset,
+                         size_t nbytes, FAR uint8_t *buffer);
+static int     gd55_ioctl(FAR struct mtd_dev_s *dev, int cmd,
+                          unsigned long arg);
+
+/* Internal driver methods */
+
+static void     gd55_lock(FAR struct gd55_dev_s *priv);
+static void     gd55_unlock(FAR struct gd55_dev_s *priv);
+static int      gd55_command_read(FAR struct gd55_dev_s *priv, uint8_t cmd,
+                                  FAR void *buffer, size_t buflen);
+static int      gd55_command(FAR struct gd55_dev_s *priv, uint8_t cmd);
+static int      gd55_command_address(FAR struct gd55_dev_s *priv,
+                                     uint8_t cmd, off_t addr,
+                                     uint8_t addrlen);
+static int      gd55_readid(FAR struct gd55_dev_s *priv);
+static int      gd55_protect(FAR struct gd55_dev_s *priv, off_t startblock,
+                             size_t nblocks);
+static int      gd55_unprotect(FAR struct gd55_dev_s *priv, off_t startblock,
+                               size_t nblocks);
+static bool     gd55_isprotected(FAR struct gd55_dev_s *priv, off_t addr,
+                                 uint8_t status);
+static int      gd55_read_bytes(FAR struct gd55_dev_s *priv,
+                                FAR uint8_t *buffer, off_t address,
+                                size_t buflen);
+static uint8_t  gd55_read_status1(FAR struct gd55_dev_s *priv);
+static uint8_t  gd55_read_status2(FAR struct gd55_dev_s *priv);
+static void     gd55_write_status1(FAR struct gd55_dev_s *priv);
+static int      gd55_command_write(FAR struct gd55_dev_s *priv, uint8_t cmd,
+                                   FAR const void *buffer, size_t buflen);
+static void     gd55_write_enable(FAR struct gd55_dev_s *priv);
+static int      gd55_write_page(FAR struct gd55_dev_s *priv,
+                                FAR const uint8_t *buffer, off_t address,
+                                size_t buflen);
+static int      gd55_erase_sector(FAR struct gd55_dev_s *priv, off_t sector);
+
+static int      gd55_erase_chip(FAR struct gd55_dev_s *priv);
+#ifdef CONFIG_MTD_GD55_SECTOR512
+static int          gd55_flush_cache(FAR struct gd55_dev_s *priv);
+static FAR uint8_t *gd55_read_cache(FAR struct gd55_dev_s *priv,
+                                    off_t sector);
+static void         gd55_erase_cache(FAR struct gd55_dev_s *priv,
+                                     off_t sector);
+static int          gd55_write_cache(FAR struct gd55_dev_s *priv,
+                                     FAR const uint8_t *buffer, off_t sector,
+                                     size_t nsectors);
+#else
+static int          gd55_erase_64kblock(FAR struct gd55_dev_s *priv,
+                                        off_t sector);
+static int          gd55_erase_32kblock(FAR struct gd55_dev_s *priv,
+                                        off_t sector);
+#endif
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: gd55_lock
+ *
+ * Description:
+ * On QSPI buses where there are multiple devices, it will be necessary to
+ * lock QSPI to have exclusive access to the bus 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 SPI bus. We will retain that exclusive access until the
+ * bus is unlocked.
+ *
+ * Input Parameters:
+ *   priv         - a reference to the device structure
+ *
+ * Returned Value:
+ *   none
+ *
+ ****************************************************************************/
+
+static void gd55_lock(FAR struct gd55_dev_s *priv)
+{
+  QSPI_LOCK(priv->qspi, true);
+
+  /* After locking the QSPI bus, the we also need call the setfrequency,
+   * setbits and setmode methods to make sure that the QSPI is properly
+   * configured for the device.  If the QSPI bus is being shared, then it
+   * may have been left in an incompatible state.
+   */
+
+  QSPI_SETMODE(priv->qspi, CONFIG_MTD_GD55_QSPIMODE);
+  QSPI_SETBITS(priv->qspi, 8);
+  QSPI_SETFREQUENCY(priv->qspi, CONFIG_MTD_GD55_FREQUENCY);
+}
+
+/****************************************************************************
+ * Name: gd55_unlock
+ *
+ * Description:
+ * On QSPI buses where there are multiple devices, it will have been
+ * necessary to lock QSSPI to have exclusive access to the bus for a sequence
+ * of transfers.  The bus must be unlocked after the transfers to relinquish
+ * the exclusive access from the call to LOCK the bus.
+ *
+ * Input Parameters:
+ *   priv         - a reference to the device structure
+ *
+ * Returned Value:
+ *   none
+ *
+ ****************************************************************************/
+
+static void gd55_unlock(FAR struct gd55_dev_s *priv)
+{
+  QSPI_LOCK(priv->qspi, false);
+}
+
+/****************************************************************************
+ * Name: gd55_command_read
+ *
+ * Description:
+ *   Read data from the device.
+ *
+ * Input Parameters:
+ *   priv         - a reference to the device structure
+ *   cmd          - the read command to be used
+ *   buffer       - pointer to variable to store the read data
+ *   buflen       - the number of bytes to be read into the buffer
+ *
+ * Returned Value:
+ *   Zero (OK) on SUCCESS, a negated errno on value of failure
+ *
+ ****************************************************************************/
+
+static int gd55_command_read(FAR struct gd55_dev_s *priv, 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(priv->qspi, &cmdinfo);
+}
+
+/****************************************************************************
+ * Name: gd55_command
+ *
+ * Description:
+ *   Send a command to the device.
+ *
+ * Input Parameters:
+ *   priv         - a reference to the device structure
+ *   cmd          - the command to be sent
+ *
+ * Returned Value:
+ *   Zero (OK) on SUCCESS, a negated errno on value of failure
+ *
+ ****************************************************************************/
+
+static int gd55_command(FAR struct gd55_dev_s *priv, 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(priv->qspi, &cmdinfo);
+}
+
+/****************************************************************************
+ * Name: gd55_command_write
+ *
+ * Description:
+ *   Send a command to the device with data
+ *
+ * Input Parameters:
+ *   priv         - a reference to the device structure
+ *   cmd          - the command to be sent
+ *   buffer       - pointer to variable with the data to write
+ *   buflen       - the number of data bytes to be written
+ *
+ * Returned Value:
+ *   Zero (OK) on SUCCESS, a negated errno on value of failure
+ *
+ ****************************************************************************/
+
+static int gd55_command_write(FAR struct gd55_dev_s *priv, 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(priv->qspi, &cmdinfo);
+}
+
+/****************************************************************************
+ * Name: gd55_command_address
+ *
+ * Description:
+ *   Send a command with an associated address to the device
+ *
+ * Input Parameters:
+ *   priv         - a reference to the device structure
+ *   cmd          - the command to be sent
+ *   addr         - address to send
+ *   addrlen      - address length
+ *
+ * Returned Value:
+ *   Zero (OK) on SUCCESS, a negated errno on value of failure
+ *
+ ****************************************************************************/
+
+static int gd55_command_address(FAR struct gd55_dev_s *priv, 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(priv->qspi, &cmdinfo);
+}
+
+/****************************************************************************
+ * Name: gd55_read_bytes
+ *
+ * Description:
+ *   Read data from the device
+ *
+ * Input Parameters:
+ *   priv         - a reference to the device structure
+ *   buffer       - pointer to buffer to read the data to
+ *   address      - address to read from
+ *   buflen       - number of bytes to read (buffer length)
+ *
+ * Returned Value:
+ *   Zero (OK) on SUCCESS, a negated errno on value of failure
+ *
+ ****************************************************************************/
+
+static int gd55_read_bytes(FAR struct gd55_dev_s *priv, FAR uint8_t *buffer,
+                           off_t address, size_t buflen)
+{
+  bool                  mode_4byte_addr;
+  int                   ret;
+  struct qspi_meminfo_s meminfo;
+
+  /* Check if any address exceeds range of 3 byte addressing */
+
+  if ((address + buflen) >= MODE_3BYTE_LIMIT)
+    {
+      gd55_command(priv, GD55_EN4B);
+      mode_4byte_addr = true;
+    }
+
+  finfo("4byte mode: %s\taddress: %08lx\tnbytes: %d\n",
+         mode_4byte_addr ? "true" : "false", (long)address, (int)buflen);
+
+  meminfo.flags   = QSPIMEM_READ | QSPIMEM_QUADIO;
+  meminfo.dummies = GD55_QC_READ_DUMMIES;
+  meminfo.buflen  = buflen;
+  meminfo.cmd     = GD55_QC_READ;
+  meminfo.addr    = address;
+  meminfo.addrlen = mode_4byte_addr ? 4 : 3;
+  meminfo.buffer  = buffer;
+
+  ret = QSPI_MEMORY(priv->qspi, &meminfo);
+  if (mode_4byte_addr)
+    {
+      gd55_command(priv, GD55_DIS4B);
+    }
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: gd55_write_page
+ *
+ * Description:
+ *   Write a page of data to the device
+ *
+ * Input Parameters:
+ *   priv         - a reference to the device structure
+ *   buffer       - pointer to the buffer with the data to write
+ *   address      - address to write to
+ *   buflen       - number of bytes to write (buffer length)
+ *
+ * Returned Value:
+ *   Zero (OK) on SUCCESS, a negated errno on value of failure
+ *
+ ****************************************************************************/
+
+static int gd55_write_page(FAR struct gd55_dev_s *priv,
+                           FAR const uint8_t *buffer,
+                           off_t address, size_t buflen)
+{
+  struct qspi_meminfo_s meminfo;
+  uint8_t               status;
+  unsigned int          npages;
+  int                   ret;
+  int                   i;
+
+  npages = (buflen >> GD55_PAGE_SHIFT);
+
+  /* Check if address exceeds range of 3 byte addressing */
+
+  if ((address + buflen) >= MODE_3BYTE_LIMIT)
+    {
+      gd55_command(priv, GD55_EN4B);
+      meminfo.addrlen = 4;
+    }
+  else
+    {
+      gd55_command(priv, GD55_DIS4B);
+      meminfo.addrlen = 3;
+    }
+
+  finfo("4byte mode: %s\taddress: %08lx\tbuflen: %u\n",
+        (meminfo.addrlen == 4) ? "true" : "false", (unsigned long)address,
+        (unsigned)buflen);
+
+  /* Set up non-varying parts of transfer description */
+
+  meminfo.flags   = QSPIMEM_WRITE | QSPIMEM_QUADIO;
+  meminfo.cmd     = GD55_EQPP;
+  meminfo.buflen  = GD55_PAGE_SIZE;
+  meminfo.dummies = GD55_EQPP_DUMMIES;
+
+  /* Then write each page */
+
+  for (i = 0; i < npages; i++)
+    {
+      /* Set up varying parts of the transfer description */
+
+      meminfo.addr   = address;
+      meminfo.buffer = (FAR void *)buffer;
+
+      /* Write one page */
+
+      gd55_write_enable(priv);
+      ret = QSPI_MEMORY(priv->qspi, &meminfo);
+
+      if (ret < 0)
+        {
+          ferr("ERROR: QSPI_MEMORY failed writing address=%06jx\n",
+               (intmax_t)address);
+          goto exit;
+        }
+
+      /* Update for the next time through the loop */
+
+      buffer  += GD55_PAGE_SIZE;
+      address += GD55_PAGE_SIZE;
+
+      /* Wait for write operation to finish */
+
+      do
+        {
+          status = gd55_read_status1(priv);
+        }
+      while ((status & GD55_SR_WIP) != 0);
+    }
+
+exit:
+  if (meminfo.addrlen == 4)
+    {
+      gd55_command(priv, GD55_DIS4B);
+    }
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: gd55_erase_sector
+ *
+ * Description:
+ *   Erase a single sector of th device
+ *
+ * Input Parameters:
+ *   priv         - a reference to the device structure
+ *   sector       - the sector to erase
+ *
+ * Returned Value:
+ *   Zero (OK) on SUCCESS, a negated errno on value of failure
+ *
+ ****************************************************************************/
+
+static int gd55_erase_sector(FAR struct gd55_dev_s *priv, off_t sector)
+{
+  uint8_t status;
+  bool    mode_4byte_addr = false;
+  off_t   addr = sector << GD55_SECTOR_SHIFT;
+
+  finfo("4byte mode: %s\tsector: %08lx\n", mode_4byte_addr ?
+                                           "true" : "false",
+                                           (unsigned long)sector);
+
+  /* Check that the flash is ready and unprotected */
+
+  status = gd55_read_status1(priv);
+  if ((status & GD55_SR_WIP) == GD55_SR_WIP)
+    {
+      ferr("ERROR: Flash busy: %02x", status);
+      return -EBUSY;
+    }
+
+  if (gd55_isprotected(priv, addr, status))
+    {
+      ferr("ERROR: Flash protected at addr: %02" PRIx32, addr);
+      return -EACCES;
+    }
+
+  /* Check if address exceeds range of 3 byte addressing */
+
+  if (addr >= MODE_3BYTE_LIMIT)
+    {
+      gd55_command(priv, GD55_EN4B);
+      mode_4byte_addr = true;
+    }
+
+  /* Send the sector erase command */
+
+  gd55_write_enable(priv);
+  gd55_command_address(priv, GD55_SE, addr, mode_4byte_addr ? 4 : 3);
+
+  /* Wait for erasure to finish */
+
+  do
+    {
+      nxsig_usleep(10 * 1000); /* Typical sector erase time is 30ms */
+      status = gd55_read_status1(priv);
+    }
+  while ((status & GD55_SR_WIP) != 0);
+
+  if (mode_4byte_addr)
+    {
+      gd55_command(priv, GD55_DIS4B);
+    }
+
+  return OK;
+}
+
+#ifndef CONFIG_MTD_GD55_SECTOR512
+/****************************************************************************
+ * Name: gd55_erase_64kblock
+ *
+ * Description:
+ *   Erase a 64k block of the device
+ *
+ * Input Parameters:
+ *   priv         - a reference to the device structure
+ *   sector       - an address of a sector within the 64k block to erase
+ *
+ * Returned Value:
+ *   Zero (OK) on SUCCESS, a negated errno on value of failure
+ *
+ ****************************************************************************/
+
+static int gd55_erase_64kblock(FAR struct gd55_dev_s *priv, off_t sector)
+{
+  off_t   addr = sector << GD55_SECTOR_SHIFT;
+  uint8_t status;
+  bool    mode_4byte_addr = false;
+
+  finfo("4byte mode: %s\tsector: %08lx\n", mode_4byte_addr ?
+                                           "true" : "false",
+                                           (unsigned long)sector);
+
+  /* Check that the flash is ready and unprotected */
+
+  status = gd55_read_status1(priv);
+  if ((status & GD55_SR_WIP) == GD55_SR_WIP)
+    {
+      ferr("ERROR: Flash busy: %02x", status);
+      return -EBUSY;
+    }
+
+  if (gd55_isprotected(priv, addr, status))
+    {
+      ferr("ERROR: Flash protected at addr: %02" PRIx32, addr);
+      return -EACCES;
+    }
+
+  if (addr >= MODE_3BYTE_LIMIT)
+    {
+      gd55_command(priv, GD55_EN4B);
+      mode_4byte_addr = true;
+    }
+
+  /* Send the 64k block erase command */
+
+  gd55_write_enable(priv);
+  gd55_command_address(priv, GD55_BE64, addr, mode_4byte_addr ?
+                                                       4 : 3);
+
+  /* Wait for erasure to finish */
+
+  do
+    {
+      nxsig_usleep(50 * 1000); /* typical 64k erase time is 220ms */
+      status = gd55_read_status1(priv);
+    }
+  while ((status & GD55_SR_WIP) != 0);
+
+  if (mode_4byte_addr)
+    {
+      gd55_command(priv, GD55_DIS4B);
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: gd55_erase_32kblock
+ *
+ * Description:
+ *   Erase a 32k block of the device
+ *
+ * Input Parameters:
+ *   priv         - a reference to the device structure
+ *   sector       - an address of a sector within the 32k block to erase
+ *
+ * Returned Value:
+ *   Zero (OK) on SUCCESS, a negated errno on value of failure
+ *
+ ****************************************************************************/
+
+static int gd55_erase_32kblock(FAR struct gd55_dev_s *priv, off_t sector)
+{
+  off_t   addr = sector << GD55_SECTOR_SHIFT;
+  uint8_t status;
+  bool    mode_4byte_addr = false;
+
+  finfo("4byte mode: %s\tsector: %08lx\n", mode_4byte_addr ?
+                                           "true" : "false",
+                                           (unsigned long)sector);
+
+  /* Check that the flash is ready and unprotected */
+
+  status = gd55_read_status1(priv);
+  if ((status & GD55_SR_WIP) == GD55_SR_WIP)
+    {
+      ferr("ERROR: Flash busy: %02x", status);
+      return -EBUSY;
+    }
+
+  if (gd55_isprotected(priv, addr, status))
+    {
+      ferr("ERROR: Flash protected at addr: %02" PRIx32, addr);
+      return -EACCES;
+    }
+
+  if (addr >= MODE_3BYTE_LIMIT)
+    {
+      gd55_command(priv, GD55_EN4B);
+      mode_4byte_addr = true;
+    }
+
+  /* Send the 32k block erase command */
+
+  gd55_write_enable(priv);
+  gd55_command_address(priv, GD55_BE32, addr, mode_4byte_addr ? 4 : 3);
+
+  /* Wait for erasure to finish */
+
+  do
+    {
+      nxsig_usleep(50 * 1000); /* typical 32k erase time is 150ms */
+      status = gd55_read_status1(priv);
+    }
+  while ((status & GD55_SR_WIP) != 0);
+
+  if (mode_4byte_addr)
+    {
+      gd55_command(priv, GD55_DIS4B);
+    }
+
+  return OK;
+}
+#endif
+
+/****************************************************************************
+ * Name: gd55_erase_chip
+ *
+ * Description:
+ *   Erase entire chip
+ *
+ * Input Parameters:
+ *   priv         - a reference to the device structure
+ *
+ * Returned Value:
+ *   Zero (OK) on SUCCESS, a negated errno on value of failure
+ *
+ ****************************************************************************/
+
+static int gd55_erase_chip(FAR struct gd55_dev_s *priv)
+{
+  uint8_t status;
+
+  /* Check if the FLASH is protected */
+
+  status = gd55_read_status1(priv);
+  if ((status & GD55_SR_BP_MASK) != 0)
+    {
+      ferr("ERROR: FLASH is Protected: %02x", status);
+      return -EACCES;
+    }
+
+  /* Erase the whole chip */
+
+  gd55_write_enable(priv);
+  gd55_command(priv, GD55_CE);
+
+  /* Wait for the erasure to complete */
+
+  status = gd55_read_status1(priv);
+
+  while ((status & GD55_SR_WIP) != 0)
+    {
+      nxsig_sleep(2);
+      status = gd55_read_status1(priv);
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: gd55_write_enable
+ *
+ * Description:
+ *   Enable the device for writing by setting the wriet enable latch bit
+ *
+ * Input Parameters:
+ *   priv         - a reference to the device structure
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void gd55_write_enable(FAR struct gd55_dev_s *priv)
+{
+  uint8_t status;
+
+  gd55_command(priv, GD55_WREN);
+  do
+    {
+      status = gd55_read_status1(priv);
+    }
+  while ((status & GD55_SR_WEL) != GD55_SR_WEL);
+}
+
+/****************************************************************************
+ * Name: gd55_read_status1
+ *
+ * Description:
+ *   Read status register 1
+ *
+ * Input Parameters:
+ *   priv         - a reference to the device structure
+ *
+ * Returned Value:
+ *   The status register data
+ *
+ ****************************************************************************/
+
+static uint8_t gd55_read_status1(FAR struct gd55_dev_s *priv)
+{
+  uint8_t status;
+
+  gd55_command_read(priv, GD55_RDSR1, &status, 1);
+  return status;
+}
+
+/****************************************************************************
+ * Name: gd55_read_status2
+ *
+ * Description:
+ *   Read status register 2
+ *
+ * Input Parameters:
+ *   priv         - a reference to the device structure
+ *
+ * Returned Value:
+ *   The status register data
+ *
+ ****************************************************************************/
+
+static uint8_t gd55_read_status2(FAR struct gd55_dev_s *priv)
+{
+  uint8_t status;
+
+  gd55_command_read(priv, GD55_RDSR2, &status, 1);
+  return status;
+}
+
+/****************************************************************************
+ * Name: gd55_write_status1
+ *
+ * Description:
+ *   Write data to status register 1
+ *   The data to be written must have been written to the device structures
+ *   command buffer (cmdbuf)
+ *
+ * Input Parameters:
+ *   priv         - a reference to the device structure
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void gd55_write_status1(FAR struct gd55_dev_s *priv)
+{
+  uint8_t status;
+
+  gd55_write_enable(priv);
+
+  /* take care to mask of the SRP bit; it is one-time-programmable */
+
+  priv->cmdbuf[0] &= ~GD55_SR_SRP0;
+
+  gd55_command_write(priv, GD55_WRSR1,
+                     (FAR const void *)priv->cmdbuf, 1);
+
+  /* Wait for write operation to finish */
+
+  do
+    {
+      status = gd55_read_status1(priv);
+    }
+  while ((status & GD55_SR_WIP) != 0);
+}
+
+/****************************************************************************
+ * Name: gd55_erase
+ *
+ * Description:
+ *   Erase a number of blocks of data.
+ *
+ * Input Parameters:
+ *   dev        - a reference to the device structure
+ *   startblock - start block of the erase
+ *   nblocks    - nblocks to erase
+ *
+ * Returned Value:
+ *   Success (OK) or fail (negated error code)
+ *
+ ****************************************************************************/
+
+static int gd55_erase(FAR struct mtd_dev_s *dev, off_t startblock,
+                      size_t nblocks)
+{
+  FAR struct gd55_dev_s *priv = (FAR struct gd55_dev_s *)dev;
+  size_t                blocksleft = nblocks;
+  int                   ret;
+#ifndef CONFIG_MTD_GD55_SECTOR512
+  const size_t         sectorsper64kblock = (64 * 1024) >> GD55_SECTOR_SHIFT;
+  const size_t         sectorsper32kblock = (32 * 1024) >> GD55_SECTOR_SHIFT;
+#endif
+
+  finfo("startblock: %08lx nblocks: %d\n", (long)startblock, (int)nblocks);
+
+  /* Lock access to the SPI bus until we complete the erase */
+
+  gd55_lock(priv);
+
+#ifdef CONFIG_MTD_GD55_SECTOR512
+  while (blocksleft-- > 0)
+    {
+      /* Erase each sector */
+
+      gd55_erase_cache(priv, startblock);
+      startblock++;
+    }
+
+  /* Flush the last erase block left in the cache */
+
+  ret = gd55_flush_cache(priv);
+  if (ret < 0)
+    {
+      nblocks = ret;
+    }
+#else
+  while (blocksleft > 0)
+    {
+      /* Check if block is aligned on 64k or 32k block for faster erase */
+
+      if (((startblock & (sectorsper64kblock - 1)) == 0) &&
+          (blocksleft >= sectorsper64kblock))
+        {
+          /* Erase 64k block */
+
+          ret = gd55_erase_64kblock(priv, startblock);
+          if (ret < 0)
+            {
+              nblocks = ret;
+            }
+
+          startblock += sectorsper64kblock;
+          blocksleft -= sectorsper64kblock;
+          finfo("Erased 64kbytes at address 0x%08" PRIx32 "\n",
+                  startblock << GD55_SECTOR_SHIFT);
+        }
+      else if (((startblock & (sectorsper32kblock - 1)) == 0) &&
+          (blocksleft >= sectorsper32kblock))
+        {
+          /* Erase 32k block */
+
+          ret = gd55_erase_32kblock(priv, startblock);
+          if (ret < 0)
+            {
+              nblocks = ret;
+            }
+
+          startblock += sectorsper32kblock;
+          blocksleft -= sectorsper32kblock;
+          finfo("Erased 32kbytes at address 0x%08" PRIx32 "\n",
+                  startblock << GD55_SECTOR_SHIFT);
+        }
+      else
+        {
+          /* Erase each sector */
+
+          ret = gd55_erase_sector(priv, startblock);
+          if (ret < 0)
+            {
+             nblocks = ret;
+            }
+
+          startblock++;
+          blocksleft--;
+          finfo("Erased 4kbytes at address 0x%08" PRIx32 "\n",
+                  startblock << GD55_SECTOR_SHIFT);
+        }
+    }
+#endif
+
+  ret = (int)nblocks;
+  gd55_unlock(priv);
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: gd55_bread
+ *
+ * Description:
+ *   Read a number of blocks of data.
+ *
+ * Input Parameters:
+ *   dev        - a reference to the device structure
+ *   startblock - start block of the memory to read
+ *   nblocks    - nblocks to read
+ *   buf        - pointer to the buffer to store the read data
+ *
+ * Returned Value:
+ *   Size of the data read
+ *
+ ****************************************************************************/
+
+static ssize_t gd55_bread(FAR struct mtd_dev_s *dev, off_t startblock,
+                          size_t nblocks, FAR uint8_t *buf)
+{
+  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_MTD_GD55_SECTOR512
+  nbytes = gd55_read(dev, startblock << GD55_SECTOR512_SHIFT,
+                     nblocks << GD55_SECTOR512_SHIFT, buf);
+  if (nbytes > 0)
+    {
+      nbytes >>= GD55_SECTOR512_SHIFT;
+    }
+#else
+  nbytes = gd55_read(dev, startblock << GD55_PAGE_SHIFT,
+                       nblocks << GD55_PAGE_SHIFT, buf);
+  if (nbytes > 0)
+    {
+      nbytes >>= GD55_PAGE_SHIFT;
+    }
+#endif
+
+  return nbytes;
+}
+
+/****************************************************************************
+ * Name: gd55_bwrite
+ *
+ * Description:
+ *   Write a number of blocks of data.
+ *
+ * Input Parameters:
+ *   dev        - a reference to the device structure
+ *   startblock - start block of the memory to write
+ *   nblocks    - nblocks to write
+ *   buf        - pointer to the buffer with the data to write
+ *
+ * Returned Value:
+ *   Size of the data written
+ *
+ ****************************************************************************/
+
+static ssize_t gd55_bwrite(FAR struct mtd_dev_s *dev, off_t startblock,
+                           size_t nblocks, FAR const uint8_t *buf)
+{
+  FAR struct gd55_dev_s *priv = (FAR struct gd55_dev_s *)dev;
+  int                   ret;
+
+  finfo("startblock: %08lx nblocks: %d\n", (long)startblock, (int)nblocks);
+
+  /* Lock the QuadSPI bus and write all of the pages to FLASH */
+
+  gd55_lock(priv);
+
+#if defined(CONFIG_MTD_GD55_SECTOR512)
+  ret = gd55_write_cache(priv, buf, startblock, nblocks);
+  if (ret < 0)
+    {
+      ferr("ERROR: gd55_write_cache failed: %d\n", ret);
+    }
+#else
+  ret = gd55_write_page(priv, buf, startblock << GD55_PAGE_SHIFT,
+                        nblocks << GD55_PAGE_SHIFT);
+  if (ret < 0)
+    {
+      ferr("ERROR: gd55_write_page failed: %d\n", ret);
+    }
+#endif
+
+  gd55_unlock(priv);
+
+  return ret < 0 ? ret : nblocks;
+}
+
+/****************************************************************************
+ * Name: gd55_read
+ *
+ * Description:
+ *   Read a number of bytes of data.
+ *
+ * Input Parameters:
+ *   dev        - a reference to the device structure
+ *   offset     - starting address of the memory to read
+ *   nbytes     - nbytes to read
+ *   buf        - pointer to the buffer to store the read data
+ *
+ * Returned Value:
+ *   Size of the data read
+ *
+ ****************************************************************************/
+
+static ssize_t gd55_read(FAR struct mtd_dev_s *dev, off_t offset,
+                         size_t nbytes, FAR uint8_t *buffer)
+{
+  int                   ret;
+  FAR struct gd55_dev_s *priv = (FAR struct gd55_dev_s *)dev;
+
+  finfo("offset: %08lx nbytes: %d\n", (long)offset, (int)nbytes);
+
+  /* Lock the QuadSPI bus and select this FLASH part */
+
+  gd55_lock(priv);
+  ret = gd55_read_bytes(priv, buffer, offset, nbytes);
+  gd55_unlock(priv);
+
+  if (ret < 0)
+    {
+      ferr("ERROR: gd55_read_bytes returned: %d\n", ret);
+      return (ssize_t)ret;
+    }
+
+  finfo("return nbytes: %d\n", (int)nbytes);
+  return (ssize_t)nbytes;
+}
+
+/****************************************************************************
+ * Name: gd55_ioctl
+ *
+ * Description:
+ *   IOCTLS relating to the GD55 mtd device
+ *
+ * Input Parameters:
+ *   dev        - a reference to the device structure
+ *   cmd        - ioctl command
+ *   arg        - ioctl argument
+ *
+ * Returned Value:
+ *   Success (OK) or fail (negated error code)
+ ****************************************************************************/
+
+static int gd55_ioctl(FAR struct mtd_dev_s *dev, int cmd, unsigned long arg)
+{
+  FAR struct gd55_dev_s *priv = (FAR struct gd55_dev_s *)dev;
+  int                    ret  = -EINVAL;
+
+  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)
+            {
+              memset(geo, 0, sizeof(*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_MTD_GD55_SECTOR512
+              geo->blocksize    = GD55_SECTOR512_SIZE;
+              geo->erasesize    = GD55_SECTOR512_SIZE;
+              geo->neraseblocks = priv->nsectors <<
+                                  (GD55_SECTOR_SHIFT -
+                                   GD55_SECTOR512_SHIFT);
+#else
+              geo->blocksize    = GD55_PAGE_SIZE;
+              geo->erasesize    = GD55_SECTOR_SIZE;
+              geo->neraseblocks = priv->nsectors;
+#endif
+              ret               = OK;
+
+              finfo("blocksize: %" PRId32
+                    " erasesize: %" PRId32
+                    " neraseblocks: %" PRId32 "\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_MTD_GD55_SECTOR512
+              info->numsectors  = priv->nsectors <<
+                               (GD55_SECTOR_SHIFT - GD55_SECTOR512_SHIFT);
+              info->sectorsize  = GD55_SECTOR512_SIZE;
+#else
+              info->numsectors  = priv->nsectors <<
+                                  (GD55_SECTOR_SHIFT - GD55_PAGE_SHIFT);
+              info->sectorsize  = GD55_PAGE_SIZE;
+#endif
+              info->startsector = 0;
+              info->parent[0]   = '\0';
+              ret               = OK;
+            }
+        }
+        break;
+
+      case MTDIOC_PROTECT:
+        {
+          FAR const struct mtd_protect_s *prot =
+            (FAR const struct mtd_protect_s *)((uintptr_t)arg);
+
+          DEBUGASSERT(prot);
+          gd55_lock(priv);
+          ret = gd55_protect(priv, prot->startblock, prot->nblocks);
+          gd55_unlock(priv);
+        }
+        break;
+
+      case MTDIOC_UNPROTECT:
+        {
+          FAR const struct mtd_protect_s *prot =
+            (FAR const struct mtd_protect_s *)((uintptr_t)arg);
+
+          DEBUGASSERT(prot);
+          gd55_lock(priv);
+          ret = gd55_unprotect(priv, prot->startblock, prot->nblocks);
+          gd55_unlock(priv);
+        }
+        break;
+
+      case MTDIOC_BULKERASE:
+        {
+          /* Erase the entire device */
+
+          gd55_lock(priv);
+          ret = gd55_erase_chip(priv);
+          gd55_unlock(priv);
+        }
+        break;
+
+      case MTDIOC_ERASESTATE:
+        {
+          FAR uint8_t *result = (FAR uint8_t *)arg;
+          *result = GD55_ERASED_STATE;
+
+          ret = OK;
+        }
+        break;
+
+      default:
+        ret = -ENOTTY; /* Bad/unsupported command */
+        break;
+    }
+
+  finfo("return %d\n", ret);
+  return ret;
+}
+
+/****************************************************************************
+ * Name: gd55_readid
+ *
+ * Description:
+ *   Read the device ID.
+ *   - the read ID is stored in the cmdbuf variable of the device structure
+ *
+ * Input Parameters:
+ *   priv       - a reference to the device structure
+ *
+ * Returned Value:
+ *   Success (OK) or fail (negated error code)
+ *
+ ****************************************************************************/
+
+static int gd55_readid(FAR struct gd55_dev_s *priv)
+{
+  /* Lock the QuadSPI bus and configure the bus. */
+
+  gd55_lock(priv);
+
+  /* Read the JEDEC ID */
+
+  gd55_command_read(priv, GD55_RDID, priv->cmdbuf, 4);
+
+  /* Unlock the bus */
+
+  gd55_unlock(priv);
+
+  finfo("Manufacturer: %02x Device Type %02x, Capacity: %02x\n",
+        priv->cmdbuf[0], priv->cmdbuf[1], priv->cmdbuf[2]);
+
+  /* Check for GigaDevices GD55 chip */
+
+  if (priv->cmdbuf[0] != GD55_JEDEC_MANUFACTURER &&
+      (priv->cmdbuf[1] != GD55L_JEDEC_MEMORY_TYPE ||
+       priv->cmdbuf[1] != GD55B_JEDEC_MEMORY_TYPE))
+    {
+      ferr("ERROR: Unrecognized device type: 0x%02x 0x%02x\n",
+           priv->cmdbuf[0], priv->cmdbuf[1]);
+      return -ENODEV;
+    }
+
+  /* Check for a supported capacity */
+
+  switch (priv->cmdbuf[2])
+    {
+      case GD55_JEDEC_1G_CAPACITY:
+        priv->nsectors    = GD55_NSECTORS_1GBIT;
+        break;
+
+      case GD55_JEDEC_2G_CAPACITY:
+        priv->nsectors    = GD55_NSECTORS_2GBIT;
+        break;
+
+      default:
+        ferr("ERROR: Unsupported memory capacity: %02x\n", priv->cmdbuf[2]);
+        return -ENODEV;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: gd55_protect
+ *
+ * Description:
+ *   The GD55 flash supports sector protection either by individual 64KiB
+ *   blocks, or in a (64KiB * n^2) block from the bottom of the device memory
+ *   OR from the top of the device memory.
+ *
+ * Input Parameters:
+ *   priv       - a reference to the device structure
+ *   startblock - first block to protect
+ *   nblocks    - nblocks to protect
+ *
+ * Returned Value:
+ *   Success (OK) or fail (negated error code)
+ *
+ ****************************************************************************/
+
+static int gd55_protect(FAR struct gd55_dev_s *priv, off_t startblock,
+                        size_t nblocks)
+{
+  uint8_t status[2];
+  int     blkmask;
+
+  if (nblocks < GD55_MIN_BP_BLKS)
+    {
+      return -EINVAL; /* Too few blocks to protect */
+    }
+
+  /* Check if sector protection registers are locked */
+
+  status[0] = gd55_read_status1(priv);
+  status[1] = gd55_read_status2(priv);
+  if (status[1] & GD55_SR_SRP1)
+    {
+      /* Status register cannot be written to as device is in
+       * power supply lockdown or is set for OTP.
+       * If the external HW WP# pin is asserted we won't know until we
+       * attempt to unlock sectors though, regardless of state of SRP0 bit
+       * in status register 0.
+       */
+
+      return -EACCES;
+    }
+
+  if (nblocks == (priv->nsectors * GD55_SECTORS_PER_BP_BLK))
+    {
+      if (startblock == 0)
+        {
+          blkmask = GD55_BP_ALL; /* protect every block */
+        }
+      else
+        {
+          return -EINVAL;        /* Invalid size and startblock */
+        }
+    }
+  else
+    {
+      /* We can only protect in certain increments of size */
+
+      blkmask = 0;
+      while (nblocks > (GD55_MIN_BP_BLKS << blkmask))
+        {
+          if ((startblock % (GD55_MIN_BP_BLKS << blkmask)) ||
+              (nblocks % (GD55_MIN_BP_BLKS << blkmask)))
+            {
+              return -EINVAL; /* Not a size we can protect */
+            }
+
+          blkmask++;
+        }
+
+        blkmask = (startblock == 0) ? GD55_SR_BP_BOTTOM(blkmask) :
+                                      GD55_SR_BP_TOP(blkmask);
+    }
+
+  /* startblock must be first block, or (memory top - nblocks) */
+
+  if ((startblock != 0) &&
+      (startblock != (((priv->nsectors << GD55_SECTOR_SHIFT) /
+                        GD55_MIN_BP_BLKS) - nblocks)))
+    {
+      return -EINVAL;
+    }
+
+  /* Clear the relevant status register bits for the new mask */
+
+  priv->cmdbuf[0] = status[0] & ~GD55_SR_BP_MASK;
+
+  /* Now set them */
+
+  priv->cmdbuf[0] |= blkmask;
+
+  if ((priv->cmdbuf[0] & GD55_SR_BP_MASK) == (status[0] & GD55_SR_BP_MASK))
+    {
+      return OK; /* this protection is already set */
+    }
+
+  gd55_write_status1(priv);
+  status[0] = gd55_read_status1(priv);
+  if ((status[0] & GD55_SR_BP_MASK) != (priv->cmdbuf[0] & GD55_SR_BP_MASK))
+    {
+      return -EACCES; /* Likely that the external HW WP# pin is asserted */
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: gd55_unprotect
+ *
+ * Description:
+ *   The GD55 flash supports sector protection either by individual 64KiB
+ *   blocks, or in a (64KiB * n^2) block from the bottom of the device memory
+ *   OR from the top of the device memory.
+ *
+ *   This function removes protection from all blocks
+ *
+ *   REVISIT - there may be benefit from trying to only unprotect a range of
+ *   sectors but this means complex checking of the request range against the
+ *   current range of blocks that are currently protected so is non-trivial
+ *
+ * Input Parameters:
+ *   priv       - a reference to the device structure
+ *   startblock - first block to unprotect (ignored for now)
+ *   nblocks    - nblocks to unprotect (ignored for now)
+ *
+ * Returned Value:
+ *   Success (OK) or fail (negated error code)
+ *
+ ****************************************************************************/
+
+static int gd55_unprotect(FAR struct gd55_dev_s *priv, off_t startblock,
+                          size_t nblocks)
+{
+  uint8_t status[2];
+
+  /* Check if sector protection registers are locked */
+
+  status[0] = gd55_read_status1(priv);
+  status[1] = gd55_read_status2(priv);
+  if (status[1] & GD55_SR_SRP1)
+    {
+      /* Status register cannot be written to as device is in
+       * power supply lockdown or is set for OTP.
+       * If the external HW WP# pin is asserted we won't know until we
+       * attempt to unlock sectors though, regardless of state of SRP0 bit
+       * in status register 0.
+       */
+
+      return -EACCES;
+    }
+
+  if (!(status[0] & GD55_SR_BP_MASK))
+    {
+      return OK; /* all blocks are already unprotected */
+    }
+
+  /* Clear all the status register BP bits */
+
+  priv->cmdbuf[0] = status[0] & ~GD55_SR_BP_MASK;
+
+  gd55_write_status1(priv);
+  status[0] = gd55_read_status1(priv);
+  if ((status[0] & GD55_SR_BP_MASK) != (priv->cmdbuf[0] & GD55_SR_BP_MASK))
+    {
+      return -EACCES; /* Likely that the external HW WP# pin is asserted */
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: gd55_isprotected
+ *
+ * Description:
+ *   Check if an address has been write protected
+ *
+ * Input Parameters:
+ *   priv       - a reference to the device structure
+ *   addr       - address to check
+ *   status     - the previously read status register value
+ *
+ * Returned Value:
+ *   Protected (true) or unprotected (false)
+ *
+ ****************************************************************************/
+
+static bool gd55_isprotected(FAR struct gd55_dev_s *priv, off_t addr,
+                             uint8_t status)
+{
+  off_t        protstart;
+  off_t        protend;
+  off_t        protsize;
+  unsigned int bp;
+
+  /* the BP field is essentially the power-of-two of the number of 64k
+   * sectors that are protected, saturated to the device size.
+   * The msb determines if protection is:
+   *   - top down  (msb not set)
+   *   - bottom up (msb set)
+   */
+
+  bp = (status & GD55_SR_BP_MASK);
+  bp &= ~GD55_STATUS_TB_MASK; /* Ignore top/bottom for now */
+  bp >>= GD55_SR_BP_SHIFT;
+
+  if (bp == 0)
+    {
+      return false;
+    }
+
+  protsize = GD55_BP_SIZE;
+  protsize <<= (bp - 1);
+  protend = GD55_SECTOR_SIZE * 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.
+   */
+
+  if ((status & GD55_STATUS_TB_BOTTOM))
+    {
+      protstart = 0;
+      protend   = protstart + protsize;
+    }
+  else
+    {
+      protstart = protend - protsize;
+
+      /* protend already computed above */
+    }
+
+  return (addr >= protstart && addr < protend);
+}
+
+#ifdef CONFIG_MTD_GD55_SECTOR512
+/****************************************************************************
+ * Name: gd55_flush_cache
+ *
+ * Description:
+ *   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.
+ *
+ * Input Parameters:
+ *   priv       - a reference to the device structure
+ *
+ * Returned Value:
+ *   Success (OK) or fail (negated error code)
+ *
+ ****************************************************************************/
+
+static int gd55_flush_cache(FAR struct gd55_dev_s *priv)
+{
+  int ret = OK;
+
+  if (IS_DIRTY(priv) || IS_ERASED(priv))
+    {
+      off_t address;
+
+      /* Convert the erase sector number into a FLASH address */
+
+      address = (off_t)priv->esectno << GD55_SECTOR_SHIFT;
+
+      /* Write entire erase block to FLASH */
+
+      ret = gd55_write_page(priv, priv->sector, address, GD55_SECTOR_SIZE);
+      if (ret < 0)
+        {
+          ferr("ERROR: gd55_write_page failed: %d\n", ret);
+        }
+
+      /* The cache is no long dirty and the FLASH is no longer erased */
+
+      CLR_DIRTY(priv);
+      CLR_ERASED(priv);
+    }
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: gd55_read_cache
+ *
+ * Description:
+ *   Read cached data
+ *
+ * Input Parameters:
+ *   priv       - a reference to the device structure
+ *   sector     = sector to read
+ *
+ * Returned Value:
+ *   Success (OK) or fail (negated error code)
+ *
+ ****************************************************************************/
+
+static FAR uint8_t *gd55_read_cache(FAR struct gd55_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 example, if the actual erase sector size is 4Kb (1 << 12), then we
+   * first shift to the right by 3 to get the sector number in 4096
+   * increments.
+   */
+
+  shift    = GD55_SECTOR_SHIFT - GD55_SECTOR512_SHIFT;
+  esectno  = sector >> shift;
+  finfo("sector: %jd esectno: %jd (%d) shift=%d\n",
+        (intmax_t)sector, (intmax_t)esectno, priv->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 = gd55_flush_cache(priv);
+      if (ret < 0)
+        {
+          ferr("ERROR: gd55_flush_cache failed: %d\n", ret);
+          return NULL;
+        }
+
+      /* Read the erase block into the cache */
+
+      ret = gd55_read_bytes(priv, priv->sector,
+                             (esectno << GD55_SECTOR_SHIFT),
+                              GD55_SECTOR_SIZE);
+      if (ret < 0)
+        {
+          ferr("ERROR: gd55_read_bytes 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 << GD55_SECTOR512_SHIFT];
+}
+
+/****************************************************************************
+ * Name: gd55_erase_cache
+ *
+ * Description:
+ *   erase cached data
+ *
+ * Input Parameters:
+ *   priv       - a reference to the device structure
+ *   sector     = sector to read
+ *
+ * Returned Value:
+ *   Success (OK) or fail (negated error code)
+ *
+ ****************************************************************************/
+
+static void gd55_erase_cache(FAR struct gd55_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 = gd55_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 >>
+                      (GD55_SECTOR_SHIFT - GD55_SECTOR512_SHIFT);
+      finfo("sector: %jd esectno: %jd\n",
+            (intmax_t)sector, (intmax_t)esectno);
+
+      DEBUGVERIFY(gd55_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, GD55_ERASED_STATE, GD55_SECTOR512_SIZE);
+  SET_DIRTY(priv);
+}
+
+/****************************************************************************
+ * Name: gd55_write_cache
+ *
+ * Description:
+ *   write cached data
+ *
+ * Input Parameters:
+ *   priv       - a reference to the device structure
+ *   sector     = sector to read
+ *
+ * Returned Value:
+ *   Success (OK) or fail (negated error code)
+ *
+ ****************************************************************************/
+
+static int gd55_write_cache(FAR struct gd55_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 = gd55_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 >>
+                           (GD55_SECTOR_SHIFT - GD55_SECTOR512_SHIFT);
+          finfo("sector: %jd esectno: %jd\n",
+                (intmax_t)sector, (intmax_t)esectno);
+
+          ret = gd55_erase_sector(priv, esectno);
+          if (ret < 0)
+            {
+              ferr("ERROR: gd55_erase_sector failed: %d\n", ret);
+              return ret;
+            }
+
+          SET_ERASED(priv);
+        }
+
+      /* Copy the new sector data into cached erase block */
+
+      memcpy(dest, buffer, GD55_SECTOR512_SIZE);
+      SET_DIRTY(priv);
+
+      /* Set up for the next 512 byte sector */
+
+      finfo("address: %08jx nbytes: %d 0x%04" PRIx32 "\n",
+            (intmax_t)(sector << GD55_SECTOR512_SHIFT),
+            GD55_SECTOR512_SIZE,
+            *(FAR uint32_t *)buffer);
+      buffer += GD55_SECTOR512_SIZE;
+      sector++;
+    }
+
+  /* Flush the last erase block left in the cache */
+
+  return gd55_flush_cache(priv);
+}
+#endif /* CONFIG_MTD_GD55_SECTOR512 */
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: gd55_initialize
+ *
+ * Description:
+ *   Create an initialize MTD device instance.
+ *
+ *   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).
+ *
+ * Input Parameters:
+ *   qspi       - a reference to the qspi device to initialize
+ *   unprotect  - if true, unprotect the device
+ *
+ * Returned Value:
+ *   Success (OK) or fail (negated error code)
+ ****************************************************************************/
+
+FAR struct mtd_dev_s *gd55_initialize(FAR struct qspi_dev_s *qspi,
+                                      bool unprotect)
+{
+  FAR struct gd55_dev_s *dev;
+  int                   ret;
+  uint8_t               status;
+
+  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.
+   */
+
+  dev = kmm_zalloc(sizeof(*dev));
+  if (dev == NULL)
+    {
+      ferr("Failed to allocate mtd device\n");
+      return NULL;
+    }
+
+  dev->mtd.erase  = gd55_erase;
+  dev->mtd.bread  = gd55_bread;
+  dev->mtd.bwrite = gd55_bwrite;
+  dev->mtd.read   = gd55_read;
+  dev->mtd.ioctl  = gd55_ioctl;
+  dev->mtd.name   = "gd55";
+  dev->qspi       = qspi;
+
+  /* Allocate a 4-byte buffer to support DMA-able command data */
+
+  dev->cmdbuf = (FAR uint8_t *)QSPI_ALLOC(qspi, 4);
+  if (dev->cmdbuf == NULL)
+    {
+      ferr("Failed to allocate command buffer\n");
+      goto exit_free_dev;
+    }
+
+  dev->readbuf = (FAR uint8_t *)QSPI_ALLOC(qspi, 2);
+  if (dev->readbuf == NULL)
+    {
+      ferr("ERROR Failed to allocate read buffer\n");
+      goto exit_free_cmdbuf;
+    }
+
+  /* Identify the FLASH chip and get its capacity */
+
+  ret = gd55_readid(dev);
+  if (ret != OK)
+    {
+      /* Unrecognized! Discard all of that work we just did and return NULL */
+
+      ferr("Unrecognized QSPI device\n");
+      goto exit_free_readbuf;
+    }
+
+  /* Unprotect all FLASH sectors if so requested. */
+
+  if (unprotect)
+    {
+      ret = gd55_unprotect(dev, 0, dev->nsectors - 1);
+      if (ret < 0)
+        {
+          ferr("ERROR: Sector unprotect failed\n");
+        }
+    }
+
+#ifdef CONFIG_MTD_GD55_SECTOR512  /* Simulate a 512 byte sector */
+  /* Allocate a buffer for the erase block cache */
+
+  dev->sector = (FAR uint8_t *)QSPI_ALLOC(qspi, GD55_SECTOR_SIZE);
+  if (dev->sector == NULL)
+    {
+      /* Allocation failed! Discard all of that work we just did and
+       * return NULL
+       */
+
+      ferr("ERROR: Sector allocation failed\n");
+      goto exit_free_readbuf;
+    }
+#endif
+
+  status = gd55_read_status1(dev);
+
+  /* Avoid compiler warnings in case info logs are disabled */
+
+  UNUSED(status);
+
+  finfo("device ready Status  = 0x%02x\n", status);
+
+  /* Return the implementation-specific state structure as the MTD device */
+
+  return &dev->mtd;
+
+exit_free_readbuf:
+  QSPI_FREE(qspi, dev->readbuf);
+exit_free_cmdbuf:
+  QSPI_FREE(qspi, dev->cmdbuf);
+exit_free_dev:
+  kmm_free(dev);
+  return NULL;
+}
diff --git a/include/nuttx/mtd/mtd.h b/include/nuttx/mtd/mtd.h
index c528db4e8e..960560a6f8 100644
--- a/include/nuttx/mtd/mtd.h
+++ b/include/nuttx/mtd/mtd.h
@@ -590,6 +590,17 @@ FAR struct mtd_dev_s *w25_initialize(FAR struct spi_dev_s 
*dev);
 FAR struct mtd_dev_s *gd25_initialize(FAR struct spi_dev_s *dev,
                                       uint32_t spi_devid);
 
+/****************************************************************************
+ * Name: gd55_initialize
+ *
+ * Description:
+ *   Initializes the driver for QSPI-based GD55 FLASH
+ *
+ ****************************************************************************/
+
+FAR struct mtd_dev_s *gd55_initialize(FAR struct qspi_dev_s *dev,
+                                      bool unprotect);
+
 /****************************************************************************
  * Name: gd5f_initialize
  *
diff --git a/include/nuttx/spi/qspi.h b/include/nuttx/spi/qspi.h
index 79a037272d..eecce3bbc6 100644
--- a/include/nuttx/spi/qspi.h
+++ b/include/nuttx/spi/qspi.h
@@ -208,14 +208,14 @@
 
 /* QSPI Memory Transfer Flags */
 
-#define QSPIMEM_READ          (0)       /* Bit 2: 0=Memory read data transfer 
*/
+#define QSPIMEM_READ          (0)       /* Bit 2: 0=Memory read data transfer  
*/
 #define QSPIMEM_WRITE         (1 << 2)  /* Bit 2: 1=Memory write data transfer 
*/
-#define QSPIMEM_DUALIO        (1 << 3)  /* Bit 3: Use Dual I/O (READ only) */
-#define QSPIMEM_QUADIO        (1 << 4)  /* Bit 4: Use Quad I/O (READ only) */
-#define QSPIMEM_SCRAMBLE      (1 << 5)  /* Bit 5: Scramble data */
-#define QSPIMEM_RANDOM        (1 << 6)  /* Bit 6: Use random key in scrambler 
*/
-#define QSPIMEM_IDUAL         (1 << 7)  /* Bit 7: Instruction on two lines */
-#define QSPIMEM_IQUAD         (1 << 0)  /* Bit 0: Instruction on four lines */
+#define QSPIMEM_DUALIO        (1 << 3)  /* Bit 3: Use Dual I/O (READ only)     
*/
+#define QSPIMEM_QUADIO        (1 << 4)  /* Bit 4: Use Quad I/O (READ only)     
*/
+#define QSPIMEM_SCRAMBLE      (1 << 5)  /* Bit 5: Scramble data                
*/
+#define QSPIMEM_RANDOM        (1 << 6)  /* Bit 6: Use random key in scrambler  
*/
+#define QSPIMEM_IDUAL         (1 << 7)  /* Bit 7: Instruction on two lines     
*/
+#define QSPIMEM_IQUAD         (1 << 0)  /* Bit 0: Instruction on four lines    
*/
 
 #define QSPIMEM_ISREAD(f)     (((f) & QSPIMEM_WRITE) == 0)
 #define QSPIMEM_ISWRITE(f)    (((f) & QSPIMEM_WRITE) != 0)

Reply via email to