| /**************************************************************************** |
| * 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 = false; |
| 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 = OK; |
| 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 |
| { |
| nxsched_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 |
| { |
| nxsched_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 |
| { |
| nxsched_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) |
| { |
| nxsched_sleep(2); |
| status = gd55_read_status1(priv); |
| } |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: gd55_write_enable |
| * |
| * Description: |
| * Enable the device for writing by setting the write 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; |
| } |