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)