6a3c2aded6
* Simplify EINTR/ECANCEL error handling 1. Add semaphore uninterruptible wait function 2 .Replace semaphore wait loop with a single uninterruptible wait 3. Replace all sem_xxx to nxsem_xxx * Unify the void cast usage 1. Remove void cast for function because many place ignore the returned value witout cast 2. Replace void cast for variable with UNUSED macro
896 lines
29 KiB
C
896 lines
29 KiB
C
/************************************************************************************
|
|
* drivers/mtd/gd5f.c
|
|
* Driver for GigaDevice SPI nand flash.
|
|
*
|
|
* Copyright (C) 2019 FishSemi Inc. All rights reserved.
|
|
* Author: zhuyanlin <zhuyanlin@fishsemi.com>
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in
|
|
* the documentation and/or other materials provided with the
|
|
* distribution.
|
|
* 3. Neither the name NuttX nor the names of its contributors may be
|
|
* used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
|
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
|
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
|
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
|
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
************************************************************************************/
|
|
|
|
/************************************************************************************
|
|
* Included Files
|
|
************************************************************************************/
|
|
|
|
#include <nuttx/config.h>
|
|
|
|
#include <sys/types.h>
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <debug.h>
|
|
|
|
#include <nuttx/kmalloc.h>
|
|
#include <nuttx/signal.h>
|
|
#include <nuttx/fs/ioctl.h>
|
|
#include <nuttx/spi/spi.h>
|
|
#include <nuttx/mtd/mtd.h>
|
|
|
|
/************************************************************************************
|
|
* Pre-processor Definitions
|
|
************************************************************************************/
|
|
|
|
/* Configuration ********************************************************************/
|
|
|
|
#ifndef CONFIG_GD5F_SPIMODE
|
|
# define CONFIG_GD5F_SPIMODE SPIDEV_MODE0
|
|
#endif
|
|
|
|
#ifndef CONFIG_GD5F_SPIFREQUENCY
|
|
# define CONFIG_GD5F_SPIFREQUENCY 20000000
|
|
#endif
|
|
|
|
/* GD5F Instructions ****************************************************************/
|
|
|
|
/* Command Value Description Addr Data */
|
|
/* Dummy */
|
|
|
|
#define GD5F_GET_FEATURE 0x0f /* Get features 1 0 1 */
|
|
#define GD5F_SET_FEATURE 0x1f /* Set features 1 0 1 */
|
|
#define GD5F_PAGE_READ 0x13 /* Array read 3 0 0 */
|
|
#define GD5F_READ_FROM_CACHE 0x03 /* Output cache data
|
|
* on SO 2 1 1-2112 */
|
|
#define GD5F_READ_ID 0x9f /* Read device ID 0 1 2 */
|
|
#define GD5F_ECC_STATUS_READ 0x7c /* Internal ECC status
|
|
* output 0 1 1 */
|
|
#define GD5F_BLOCK_ERASE 0xd8 /* Block erase 3 0 0 */
|
|
#define GD5F_PROGRAM_EXECUTE 0x10 /* Enter block/page
|
|
* address, execute 3 0 0 */
|
|
#define GD5F_PROGRAM_LOAD 0x02 /* Load program data with
|
|
* cache reset first 2 0 1-2112 */
|
|
#define GD5F_PROGRAM_LOAD_RANDOM 0x84 /* Load program data
|
|
* without cache reset 2 0 1-2112 */
|
|
#define GD5F_WRITE_ENABLE 0x06 /* 0 0 0 */
|
|
#define GD5F_WRITE_DISABLE 0x04 /* 0 0 0 */
|
|
#define GD5F_RESET 0xff /* Reset the device 0 0 0 */
|
|
|
|
#define GD5F_DUMMY 0x00 /* No Operation 0 0 0 */
|
|
|
|
/* Feature register *****************************************************************/
|
|
|
|
/* JEDEC Read ID register values */
|
|
|
|
#define GD5F_MANUFACTURER 0xc8
|
|
#define GD5F_GD5F_CAPACITY_MASK 0x0f
|
|
#define GD5F_CAPACITY_1GBIT 0x01 /* 1 Gb */
|
|
#define GD5F_CAPACITY_2GBIT 0x02 /* 2 Gb */
|
|
#define GD5F_CAPACITY_4GBIT 0x04 /* 4 Gb */
|
|
|
|
#define GD5F_NSECTORS_1GBIT 1024 /* 1024x131072 = 1Gbit memory capacity */
|
|
#define GD5F_NSECTORS_2GBIT 2048 /* 2048x131072 = 2Gbit memory capacity */
|
|
#define GD5F_NSECTORS_4GBIT 4096 /* 4096x131072 = 4Gbit memory capacity */
|
|
|
|
#define GD5F_SECTOR_SHIFT 17 /* 131072 byte */
|
|
#define GD5F_PAGE_SHIFT 11 /* 2048 */
|
|
|
|
/* Register address */
|
|
|
|
#define GD5F_SECURE_OTP 0xb0
|
|
#define GD5F_STATUS 0xc0
|
|
#define GD5F_BLOCK_PROTECTION 0xa0
|
|
|
|
/* Bit definitions */
|
|
|
|
/* Secure OTP (On-Time-Programmable) register */
|
|
|
|
#define GD5F_SOTP_QE (1 << 0) /* Bit 0: Quad Enable */
|
|
#define GD5F_SOTP_ECC (1 << 4) /* Bit 4: ECC enabled */
|
|
#define GD5F_SOTP_SOTP_EN (1 << 6) /* Bit 6: Secure OTP Enable */
|
|
#define GD5F_SOTP_SOTP_PROT (1 << 7) /* Bit 7: Secure OTP Protect */
|
|
|
|
/* Status register */
|
|
|
|
#define GD5F_SR_OIP (1 << 0) /* Bit 0: Operation in progress */
|
|
#define GD5F_SR_WEL (1 << 1) /* Bit 1: Write enable latch */
|
|
#define GD5F_SR_E_FAIL (1 << 2) /* Bit 2: Erase fail */
|
|
#define GD5F_SR_P_FAIL (1 << 3) /* Bit 3: Program Fail */
|
|
#define GD5F_SR_ECC_S0 (1 << 4) /* Bit 4-5: ECC Status */
|
|
#define GD5F_SR_ECC_S1 (1 << 5)
|
|
|
|
/* Block Protection register */
|
|
|
|
#define GD5F_BP_SP (1 << 0) /* Bit 0: Solid-protection (1Gb only) */
|
|
#define GD5F_BP_COMPL (1 << 1) /* Bit 1: Complementary (1Gb only) */
|
|
#define GD5F_BP_INV (1 << 2) /* Bit 2: Invert (1Gb only) */
|
|
#define GD5F_BP_BP0 (1 << 3) /* Bit 3: Block Protection 0 */
|
|
#define GD5F_BP_BP1 (1 << 4) /* Bit 4: Block Protection 1 */
|
|
#define GD5F_BP_BP2 (1 << 5) /* Bit 5: Block Protection 2 */
|
|
#define GD5F_BP_BPRWD (1 << 7) /* Bit 7: Block Protection Register
|
|
* Write Disable */
|
|
|
|
/* ECC Status register */
|
|
|
|
#define GD5F_FEATURE_ECC_MASK (0x03 << 4)
|
|
#define GD5F_FEATURE_ECC_ERROR (0x02 << 4)
|
|
#define GD5F_FEATURE_ECC_OFFSET 4
|
|
#define GD5F_ECC_STATUS_MASK 0x0f
|
|
|
|
/************************************************************************************
|
|
* Private Types
|
|
************************************************************************************/
|
|
|
|
/* This type represents the state of the MTD device. The struct mtd_dev_s
|
|
* must appear at the beginning of the definition so that you can freely
|
|
* cast between pointers to struct mtd_dev_s and struct gd5f_dev_s.
|
|
*/
|
|
|
|
struct gd5f_dev_s
|
|
{
|
|
struct mtd_dev_s mtd; /* MTD interface */
|
|
FAR struct spi_dev_s *dev; /* Saved SPI interface instance */
|
|
uint32_t spi_devid; /* Chip select inputs */
|
|
uint16_t nsectors; /* 1024 or 2048 */
|
|
uint8_t sectorshift; /* 17 */
|
|
uint8_t pageshift; /* 11 */
|
|
uint8_t eccstatus; /* Internal ECC status */
|
|
};
|
|
|
|
/************************************************************************************
|
|
* Private Function Prototypes
|
|
************************************************************************************/
|
|
|
|
/* Helpers */
|
|
|
|
static inline void gd5f_lock(FAR struct spi_dev_s *dev);
|
|
static inline void gd5f_unlock(FAR struct spi_dev_s *dev);
|
|
|
|
static int gd5f_readid(FAR struct gd5f_dev_s *priv);
|
|
static bool gd5f_waitstatus(FAR struct gd5f_dev_s *priv, uint8_t mask,
|
|
bool successif);
|
|
static inline void gd5f_writeenable(FAR struct gd5f_dev_s *priv);
|
|
static inline void gd5f_writedisable(FAR struct gd5f_dev_s *priv);
|
|
static bool gd5f_sectorerase(FAR struct gd5f_dev_s *priv, off_t startsector);
|
|
static void gd5f_readbuffer(FAR struct gd5f_dev_s *priv, uint32_t address,
|
|
uint8_t *buffer, size_t length);
|
|
static bool gd5f_read_page(FAR struct gd5f_dev_s *priv, uint32_t position);
|
|
|
|
static void gd5f_write_to_cache(FAR struct gd5f_dev_s *priv, uint32_t address,
|
|
const uint8_t *buffer, size_t length);
|
|
static bool gd5f_execute_write(FAR struct gd5f_dev_s *priv, uint32_t position);
|
|
|
|
static inline void gd5f_eccstatusread(FAR struct gd5f_dev_s *priv);
|
|
static inline void gd5f_enable_ecc(FAR struct gd5f_dev_s *priv);
|
|
static inline void gd5f_unlockblocks(FAR struct gd5f_dev_s *priv);
|
|
|
|
/* MTD driver methods */
|
|
|
|
static ssize_t gd5f_bread(FAR struct mtd_dev_s *dev, off_t startblock,
|
|
size_t nblocks, FAR uint8_t *buffer);
|
|
static ssize_t gd5f_read(FAR struct mtd_dev_s *dev, off_t offset, size_t nbytes,
|
|
FAR uint8_t *buffer);
|
|
static ssize_t gd5f_bwrite(FAR struct mtd_dev_s *dev, off_t startblock,
|
|
size_t nblocks, FAR const uint8_t *buffer);
|
|
static ssize_t gd5f_write(FAR struct mtd_dev_s *dev, off_t offset, size_t nbytes,
|
|
FAR const uint8_t *buffer);
|
|
static int gd5f_ioctl(FAR struct mtd_dev_s *dev, int cmd, unsigned long arg);
|
|
static int gd5f_erase(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks);
|
|
|
|
/************************************************************************************
|
|
* Private Functions
|
|
************************************************************************************/
|
|
|
|
/************************************************************************************
|
|
* Name: gd5f_lock
|
|
************************************************************************************/
|
|
|
|
static inline void gd5f_lock(FAR struct spi_dev_s *dev)
|
|
{
|
|
SPI_LOCK(dev, true);
|
|
|
|
SPI_SETMODE(dev, CONFIG_GD5F_SPIMODE);
|
|
SPI_SETBITS(dev, 8);
|
|
SPI_HWFEATURES(dev, 0);
|
|
SPI_SETFREQUENCY(dev, CONFIG_GD5F_SPIFREQUENCY);
|
|
}
|
|
|
|
/************************************************************************************
|
|
* Name: gd5f_unlock
|
|
************************************************************************************/
|
|
|
|
static inline void gd5f_unlock(FAR struct spi_dev_s *dev)
|
|
{
|
|
SPI_LOCK(dev, false);
|
|
}
|
|
|
|
/************************************************************************************
|
|
* Name: gd5f_readid
|
|
************************************************************************************/
|
|
|
|
static int gd5f_readid(FAR struct gd5f_dev_s *priv)
|
|
{
|
|
uint16_t manufacturer;
|
|
uint16_t deviceid;
|
|
uint16_t capacity;
|
|
|
|
finfo("priv: %p\n", priv);
|
|
|
|
/* Lock the SPI bus, configure the bus, and select this FLASH part. */
|
|
|
|
gd5f_lock(priv->dev);
|
|
SPI_SELECT(priv->dev, SPIDEV_FLASH(priv->spi_devid), true);
|
|
|
|
/* Send the "Read ID" command and read two ID bytes */
|
|
|
|
SPI_SEND(priv->dev, GD5F_READ_ID);
|
|
SPI_SEND(priv->dev, GD5F_DUMMY);
|
|
manufacturer = SPI_SEND(priv->dev, GD5F_DUMMY);
|
|
deviceid = SPI_SEND(priv->dev, GD5F_DUMMY);
|
|
|
|
/* De-select the FLASH and unlock the bus */
|
|
|
|
SPI_SELECT(priv->dev, SPIDEV_FLASH(priv->spi_devid), false);
|
|
gd5f_unlock(priv->dev);
|
|
|
|
finfo("manufacturer: %02x deviceid: %02x\n",
|
|
manufacturer, deviceid);
|
|
|
|
/* Check for a valid manufacturer */
|
|
|
|
if (manufacturer == GD5F_MANUFACTURER)
|
|
{
|
|
capacity = deviceid & GD5F_GD5F_CAPACITY_MASK;
|
|
|
|
if (capacity == GD5F_CAPACITY_1GBIT)
|
|
{
|
|
priv->nsectors = GD5F_NSECTORS_1GBIT;
|
|
}
|
|
else if (capacity == GD5F_CAPACITY_2GBIT)
|
|
{
|
|
priv->nsectors = GD5F_NSECTORS_2GBIT;
|
|
}
|
|
else if (capacity == GD5F_CAPACITY_4GBIT)
|
|
{
|
|
priv->nsectors = GD5F_NSECTORS_4GBIT;
|
|
}
|
|
else
|
|
{
|
|
return -ENODEV;
|
|
}
|
|
|
|
priv->sectorshift = GD5F_SECTOR_SHIFT;
|
|
priv->pageshift = GD5F_PAGE_SHIFT;
|
|
return OK;
|
|
}
|
|
|
|
return -ENODEV;
|
|
}
|
|
|
|
/************************************************************************************
|
|
* Name: gd5f_waitstatus
|
|
************************************************************************************/
|
|
|
|
static bool gd5f_waitstatus(FAR struct gd5f_dev_s *priv, uint8_t mask, bool successif)
|
|
{
|
|
uint8_t status;
|
|
|
|
/* Loop as long as the memory is busy with a write cycle */
|
|
|
|
do
|
|
{
|
|
/* Select this FLASH part */
|
|
|
|
SPI_SELECT(priv->dev, SPIDEV_FLASH(priv->spi_devid), true);
|
|
|
|
/* Get feature command */
|
|
|
|
SPI_SEND(priv->dev, GD5F_GET_FEATURE);
|
|
SPI_SEND(priv->dev, GD5F_STATUS);
|
|
status = SPI_SEND(priv->dev, GD5F_DUMMY);
|
|
|
|
/* Deselect the FLASH */
|
|
|
|
SPI_SELECT(priv->dev, SPIDEV_FLASH(priv->spi_devid), false);
|
|
nxsig_usleep(1000);
|
|
}
|
|
while ((status & GD5F_SR_OIP) != 0);
|
|
|
|
finfo("Complete %02x\n", status);
|
|
|
|
return successif ? ((status & mask) != 0) : ((status & mask) == 0);
|
|
}
|
|
|
|
/************************************************************************************
|
|
* Name: gd5f_writeenable
|
|
************************************************************************************/
|
|
|
|
static inline void gd5f_writeenable(FAR struct gd5f_dev_s *priv)
|
|
{
|
|
/* Select this FLASH part */
|
|
|
|
SPI_SELECT(priv->dev, SPIDEV_FLASH(priv->spi_devid), true);
|
|
|
|
/* Send Write Enable command */
|
|
|
|
SPI_SEND(priv->dev, GD5F_WRITE_ENABLE);
|
|
|
|
/* Deselect the FLASH */
|
|
|
|
SPI_SELECT(priv->dev, SPIDEV_FLASH(priv->spi_devid), false);
|
|
}
|
|
|
|
/************************************************************************************
|
|
* Name: gd5f_writedisable
|
|
************************************************************************************/
|
|
|
|
static inline void gd5f_writedisable(FAR struct gd5f_dev_s *priv)
|
|
{
|
|
/* Select this FLASH part */
|
|
|
|
SPI_SELECT(priv->dev, SPIDEV_FLASH(priv->spi_devid), true);
|
|
|
|
/* Send Write Enable command */
|
|
|
|
SPI_SEND(priv->dev, GD5F_WRITE_DISABLE);
|
|
|
|
/* Deselect the FLASH */
|
|
|
|
SPI_SELECT(priv->dev, SPIDEV_FLASH(priv->spi_devid), false);
|
|
}
|
|
|
|
/************************************************************************************
|
|
* Name: gd5f_sectorerase (128K)
|
|
************************************************************************************/
|
|
|
|
static bool gd5f_sectorerase(FAR struct gd5f_dev_s *priv, off_t startsector)
|
|
{
|
|
const uint32_t block = startsector << (priv->sectorshift - priv->pageshift);
|
|
|
|
finfo("block sector: %08lx\n", (long)block);
|
|
|
|
/* Send write enable instruction */
|
|
|
|
gd5f_writeenable(priv);
|
|
|
|
/* Select this FLASH part */
|
|
|
|
SPI_SELECT(priv->dev, SPIDEV_FLASH(priv->spi_devid), true);
|
|
|
|
/* Send the Block Erase instruction */
|
|
|
|
SPI_SEND(priv->dev, GD5F_BLOCK_ERASE);
|
|
SPI_SEND(priv->dev, (block >> 16) & 0xff);
|
|
SPI_SEND(priv->dev, (block >> 8) & 0xff);
|
|
SPI_SEND(priv->dev, block & 0xff);
|
|
|
|
/* De-select the FLASH */
|
|
|
|
SPI_SELECT(priv->dev, SPIDEV_FLASH(priv->spi_devid), false);
|
|
|
|
finfo("Erased\n");
|
|
return gd5f_waitstatus(priv, GD5F_SR_E_FAIL, false);
|
|
}
|
|
|
|
/************************************************************************************
|
|
* Name: gd5f_erase
|
|
************************************************************************************/
|
|
|
|
static int gd5f_erase(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks)
|
|
{
|
|
FAR struct gd5f_dev_s *priv = (FAR struct gd5f_dev_s *)dev;
|
|
size_t blocksleft = nblocks;
|
|
|
|
finfo("Erase: startblock: %08lx nblocks: %d\n", (long)startblock, (int)nblocks);
|
|
|
|
/* Lock access to the SPI bus until we complete the erase */
|
|
|
|
gd5f_lock(priv->dev);
|
|
|
|
/* Wait all operations complete */
|
|
|
|
gd5f_waitstatus(priv, GD5F_SR_OIP, false);
|
|
|
|
while (blocksleft > 0)
|
|
{
|
|
if (!gd5f_sectorerase(priv, startblock))
|
|
{
|
|
break;
|
|
}
|
|
|
|
startblock++;
|
|
blocksleft--;
|
|
}
|
|
|
|
gd5f_unlock(priv->dev);
|
|
return nblocks - blocksleft;
|
|
}
|
|
|
|
/************************************************************************************
|
|
* Name: gd5f_readbuffer
|
|
************************************************************************************/
|
|
|
|
static void gd5f_readbuffer(FAR struct gd5f_dev_s *priv, uint32_t address,
|
|
uint8_t *buffer, size_t length)
|
|
{
|
|
const uint16_t offset = address & ((1 << priv->pageshift) - 1);
|
|
|
|
/* Select the FLASH */
|
|
|
|
SPI_SELECT(priv->dev, SPIDEV_FLASH(priv->spi_devid), true);
|
|
|
|
SPI_SEND(priv->dev, GD5F_READ_FROM_CACHE);
|
|
|
|
/* Send the address high byte first. */
|
|
|
|
SPI_SEND(priv->dev, (offset >> 8) & 0xff);
|
|
SPI_SEND(priv->dev, (offset) & 0xff);
|
|
|
|
/* Send a dummy byte */
|
|
|
|
SPI_SEND(priv->dev, GD5F_DUMMY);
|
|
|
|
/* Then read all of the requested bytes */
|
|
|
|
SPI_RECVBLOCK(priv->dev, buffer, length);
|
|
|
|
/* Deselect the FLASH */
|
|
|
|
SPI_SELECT(priv->dev, SPIDEV_FLASH(priv->spi_devid), false);
|
|
}
|
|
|
|
/************************************************************************************
|
|
* Name: gd5f_read_page
|
|
************************************************************************************/
|
|
|
|
static bool gd5f_read_page(FAR struct gd5f_dev_s *priv, uint32_t pageaddress)
|
|
{
|
|
const uint32_t row = pageaddress >> priv->pageshift;
|
|
|
|
/* Select this FLASH part */
|
|
|
|
SPI_SELECT(priv->dev, SPIDEV_FLASH(priv->spi_devid), true);
|
|
|
|
/* Send the Read Page instruction */
|
|
|
|
SPI_SEND(priv->dev, GD5F_PAGE_READ);
|
|
SPI_SEND(priv->dev, (row >> 16) & 0xff);
|
|
SPI_SEND(priv->dev, (row >> 8) & 0xff);
|
|
SPI_SEND(priv->dev, row & 0xff);
|
|
|
|
/* Deselect the FLASH */
|
|
|
|
SPI_SELECT(priv->dev, SPIDEV_FLASH(priv->spi_devid), false);
|
|
|
|
/* Wait Page Read Complete */
|
|
|
|
gd5f_waitstatus(priv, GD5F_SR_OIP, false);
|
|
|
|
/* Check HardWare ECC result */
|
|
|
|
gd5f_eccstatusread(priv);
|
|
if ((priv->eccstatus & GD5F_FEATURE_ECC_MASK) == GD5F_FEATURE_ECC_ERROR)
|
|
{
|
|
/* ECC report uncorrectable, discard data */
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/************************************************************************************
|
|
* Name: gd5f_read
|
|
************************************************************************************/
|
|
|
|
static ssize_t gd5f_read(FAR struct mtd_dev_s *dev, off_t offset, size_t nbytes,
|
|
FAR uint8_t *buffer)
|
|
{
|
|
FAR struct gd5f_dev_s *priv = (FAR struct gd5f_dev_s *)dev;
|
|
size_t bytesleft = nbytes;
|
|
uint32_t position = offset;
|
|
|
|
finfo("Read: offset: %08lx nbytes: %d\n", (long)offset, (int)nbytes);
|
|
|
|
/* Lock the SPI bus and select this FLASH part */
|
|
|
|
gd5f_lock(priv->dev);
|
|
|
|
/* Wait all operations complete */
|
|
|
|
gd5f_waitstatus(priv, GD5F_SR_OIP, false);
|
|
|
|
while (bytesleft)
|
|
{
|
|
const uint32_t pageaddress = (position >> priv->pageshift) << priv->pageshift;
|
|
const uint32_t spaceleft = pageaddress + (1 << priv->pageshift) - position;
|
|
const size_t chunklength = bytesleft < spaceleft ? bytesleft : spaceleft;
|
|
|
|
if (!gd5f_read_page(priv, pageaddress))
|
|
{
|
|
break;
|
|
}
|
|
|
|
gd5f_readbuffer(priv, position, buffer, chunklength);
|
|
|
|
position += chunklength;
|
|
buffer += chunklength;
|
|
bytesleft -= chunklength;
|
|
}
|
|
|
|
gd5f_unlock(priv->dev);
|
|
|
|
finfo("return nbytes: %d\n", (int)(nbytes - bytesleft));
|
|
return nbytes - bytesleft;
|
|
}
|
|
|
|
/**************************************************************************
|
|
* Name: gd5f_bread
|
|
**************************************************************************/
|
|
|
|
static ssize_t gd5f_bread(FAR struct mtd_dev_s *dev, off_t startblock,
|
|
size_t nblocks, FAR uint8_t *buffer)
|
|
{
|
|
ssize_t nbytes;
|
|
FAR struct gd5f_dev_s *priv = (FAR struct gd5f_dev_s *)dev;
|
|
|
|
finfo("Bread: startblock: %08lx nblocks: %d\n",
|
|
(long)startblock, (int)nblocks);
|
|
|
|
nbytes = gd5f_read(dev, startblock << priv->pageshift,
|
|
nblocks << priv->pageshift, buffer);
|
|
if (nbytes > 0)
|
|
{
|
|
nbytes >>= priv->pageshift;
|
|
}
|
|
|
|
return nbytes;
|
|
}
|
|
|
|
/************************************************************************************
|
|
* Name: gd5f_write_to_cache
|
|
************************************************************************************/
|
|
|
|
static void gd5f_write_to_cache(FAR struct gd5f_dev_s *priv, uint32_t address,
|
|
const uint8_t *buffer, size_t length)
|
|
{
|
|
const uint16_t offset = address & ((1 << priv->pageshift) - 1);
|
|
|
|
/* Select the FLASH */
|
|
|
|
SPI_SELECT(priv->dev, SPIDEV_FLASH(priv->spi_devid), true);
|
|
|
|
/* Send the Program Load command */
|
|
|
|
SPI_SEND(priv->dev, GD5F_PROGRAM_LOAD);
|
|
|
|
/* Send the address high byte first. */
|
|
|
|
SPI_SEND(priv->dev, (offset >> 8) & 0xff);
|
|
SPI_SEND(priv->dev, (offset) & 0xff);
|
|
|
|
/* Send block of bytes */
|
|
|
|
SPI_SNDBLOCK(priv->dev, buffer, length);
|
|
|
|
/* De-select the FLASH */
|
|
|
|
SPI_SELECT(priv->dev, SPIDEV_FLASH(priv->spi_devid), false);
|
|
}
|
|
|
|
/************************************************************************************
|
|
* Name: gd5f_execute_write
|
|
************************************************************************************/
|
|
|
|
static bool gd5f_execute_write(FAR struct gd5f_dev_s *priv, uint32_t pageaddress)
|
|
{
|
|
const uint32_t row = pageaddress >> priv->pageshift;
|
|
|
|
/* Select this FLASH part */
|
|
|
|
SPI_SELECT(priv->dev, SPIDEV_FLASH(priv->spi_devid), true);
|
|
|
|
/* Send the Program Execute instruction */
|
|
|
|
SPI_SEND(priv->dev, GD5F_PROGRAM_EXECUTE);
|
|
SPI_SEND(priv->dev, (row >> 16) & 0xff);
|
|
SPI_SEND(priv->dev, (row >> 8) & 0xff);
|
|
SPI_SEND(priv->dev, row & 0xff);
|
|
|
|
/* De-select the FLASH */
|
|
|
|
SPI_SELECT(priv->dev, SPIDEV_FLASH(priv->spi_devid), false);
|
|
|
|
return gd5f_waitstatus(priv, GD5F_SR_P_FAIL, false);
|
|
}
|
|
|
|
/************************************************************************************
|
|
* Name: gd5f_write
|
|
************************************************************************************/
|
|
|
|
static ssize_t gd5f_write(FAR struct mtd_dev_s *dev, off_t offset, size_t nbytes,
|
|
FAR const uint8_t *buffer)
|
|
{
|
|
FAR struct gd5f_dev_s *priv = (FAR struct gd5f_dev_s *)dev;
|
|
size_t bytesleft = nbytes;
|
|
uint32_t position = offset;
|
|
|
|
finfo("Write: offset: %08lx nbytes: %d \n", (long)offset, (int)nbytes);
|
|
gd5f_lock(priv->dev);
|
|
|
|
/* Wait all operations complete */
|
|
|
|
gd5f_waitstatus(priv, GD5F_SR_OIP, false);
|
|
|
|
while (bytesleft)
|
|
{
|
|
const uint32_t pageaddress = (position >> priv->pageshift) << priv->pageshift;
|
|
const uint32_t spaceleft = pageaddress + (1 << priv->pageshift) - position;
|
|
const size_t chunklength = bytesleft < spaceleft ? bytesleft : spaceleft;
|
|
|
|
gd5f_write_to_cache(priv, position, buffer, chunklength);
|
|
gd5f_writeenable(priv);
|
|
if (!gd5f_execute_write(priv, pageaddress))
|
|
{
|
|
break;
|
|
}
|
|
|
|
position += chunklength;
|
|
buffer += chunklength;
|
|
bytesleft -= chunklength;
|
|
}
|
|
|
|
gd5f_unlock(priv->dev);
|
|
|
|
return nbytes - bytesleft;
|
|
}
|
|
|
|
/**************************************************************************
|
|
* Name: gd5f_bwrite
|
|
**************************************************************************/
|
|
|
|
static ssize_t gd5f_bwrite(FAR struct mtd_dev_s *dev, off_t startblock,
|
|
size_t nblocks, FAR const uint8_t *buffer)
|
|
{
|
|
ssize_t nbytes;
|
|
|
|
FAR struct gd5f_dev_s *priv = (FAR struct gd5f_dev_s *)dev;
|
|
|
|
finfo("Bwrite: startblock: %08lx nblocks: %d\n",
|
|
(long)startblock, (int)nblocks);
|
|
|
|
/* Lock the SPI bus and write all of the pages to FLASH */
|
|
|
|
nbytes = gd5f_write(dev, startblock << priv->pageshift,
|
|
nblocks << priv->pageshift, buffer);
|
|
if (nbytes > 0)
|
|
{
|
|
nbytes >>= priv->pageshift;
|
|
}
|
|
|
|
return nbytes;
|
|
}
|
|
|
|
/************************************************************************************
|
|
* Name: mx25l_ioctl
|
|
************************************************************************************/
|
|
|
|
static int gd5f_ioctl(FAR struct mtd_dev_s *dev, int cmd, unsigned long arg)
|
|
{
|
|
FAR struct gd5f_dev_s *priv = (FAR struct gd5f_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)
|
|
{
|
|
geo->blocksize = (1 << priv->pageshift);
|
|
geo->erasesize = (1 << priv->sectorshift);
|
|
geo->neraseblocks = priv->nsectors;
|
|
|
|
ret = OK;
|
|
|
|
finfo("blocksize: %d erasesize: %d neraseblocks: %d\n",
|
|
geo->blocksize, geo->erasesize, geo->neraseblocks);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case MTDIOC_BULKERASE:
|
|
{
|
|
/* Erase the entire device */
|
|
|
|
ret = gd5f_erase(dev, 0, priv->nsectors);
|
|
}
|
|
break;
|
|
|
|
case MTDIOC_ECCSTATUS:
|
|
{
|
|
uint8_t *result = (uint8_t *)arg;
|
|
*result =
|
|
(priv->eccstatus & GD5F_FEATURE_ECC_MASK) >> GD5F_FEATURE_ECC_OFFSET;
|
|
|
|
ret = OK;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
ret = -ENOTTY; /* Bad command */
|
|
break;
|
|
}
|
|
|
|
finfo("return %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
/************************************************************************************
|
|
* Name: gd5f_eccstatusread
|
|
************************************************************************************/
|
|
|
|
static inline void gd5f_eccstatusread(FAR struct gd5f_dev_s *priv)
|
|
{
|
|
SPI_SELECT(priv->dev, SPIDEV_FLASH(priv->spi_devid), true);
|
|
SPI_SEND(priv->dev, GD5F_GET_FEATURE);
|
|
SPI_SEND(priv->dev, GD5F_STATUS);
|
|
priv->eccstatus = SPI_SEND(priv->dev, GD5F_DUMMY);
|
|
SPI_SELECT(priv->dev, SPIDEV_FLASH(priv->spi_devid), false);
|
|
}
|
|
|
|
/************************************************************************************
|
|
* Name: gd5f_enable_ecc
|
|
************************************************************************************/
|
|
|
|
static inline void gd5f_enable_ecc(FAR struct gd5f_dev_s *priv)
|
|
{
|
|
uint8_t secure_otp = GD5F_SOTP_ECC;
|
|
|
|
gd5f_lock(priv->dev);
|
|
gd5f_writeenable(priv);
|
|
|
|
SPI_SELECT(priv->dev, SPIDEV_FLASH(priv->spi_devid), true);
|
|
SPI_SEND(priv->dev, GD5F_SET_FEATURE);
|
|
SPI_SEND(priv->dev, GD5F_SECURE_OTP);
|
|
SPI_SEND(priv->dev, secure_otp);
|
|
SPI_SELECT(priv->dev, SPIDEV_FLASH(priv->spi_devid), false);
|
|
|
|
gd5f_writedisable(priv);
|
|
gd5f_unlock(priv->dev);
|
|
}
|
|
|
|
/************************************************************************************
|
|
* Name: gd5f_unlockblocks
|
|
************************************************************************************/
|
|
|
|
static inline void gd5f_unlockblocks(FAR struct gd5f_dev_s *priv)
|
|
{
|
|
uint8_t blockprotection = 0x00;
|
|
|
|
gd5f_lock(priv->dev);
|
|
gd5f_writeenable(priv);
|
|
|
|
SPI_SELECT(priv->dev, SPIDEV_FLASH(priv->spi_devid), true);
|
|
SPI_SEND(priv->dev, GD5F_SET_FEATURE);
|
|
SPI_SEND(priv->dev, GD5F_BLOCK_PROTECTION);
|
|
SPI_SEND(priv->dev, blockprotection);
|
|
SPI_SELECT(priv->dev, SPIDEV_FLASH(priv->spi_devid), false);
|
|
|
|
gd5f_writedisable(priv);
|
|
gd5f_unlock(priv->dev);
|
|
}
|
|
|
|
/************************************************************************************
|
|
* Public Functions
|
|
************************************************************************************/
|
|
|
|
/************************************************************************************
|
|
* Name: gd5f_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).
|
|
*
|
|
************************************************************************************/
|
|
|
|
FAR struct mtd_dev_s *gd5f_initialize(FAR struct spi_dev_s *dev,
|
|
uint32_t spi_devid)
|
|
{
|
|
FAR struct gd5f_dev_s *priv;
|
|
int ret;
|
|
|
|
finfo("dev: %p\n", dev);
|
|
|
|
priv = (FAR struct gd5f_dev_s *)kmm_zalloc(sizeof(struct gd5f_dev_s));
|
|
if (priv)
|
|
{
|
|
/* Initialize the allocated structure. (unsupported methods were
|
|
* nullified by kmm_zalloc).
|
|
*/
|
|
|
|
priv->mtd.erase = gd5f_erase;
|
|
priv->mtd.bread = gd5f_bread;
|
|
priv->mtd.bwrite = gd5f_bwrite;
|
|
priv->mtd.ioctl = gd5f_ioctl;
|
|
priv->mtd.name = "gd5f";
|
|
priv->dev = dev;
|
|
priv->spi_devid = spi_devid;
|
|
|
|
/* De-select the FLASH */
|
|
|
|
SPI_SELECT(dev, SPIDEV_FLASH(priv->spi_devid), false);
|
|
|
|
/* Reset the flash */
|
|
|
|
SPI_SELECT(priv->dev, SPIDEV_FLASH(priv->spi_devid), true);
|
|
SPI_SEND(priv->dev, GD5F_RESET);
|
|
SPI_SELECT(priv->dev, SPIDEV_FLASH(priv->spi_devid), false);
|
|
|
|
/* Wait reset complete */
|
|
|
|
gd5f_waitstatus(priv, GD5F_SR_OIP, false);
|
|
|
|
/* Identify the FLASH chip and get its capacity */
|
|
|
|
ret = gd5f_readid(priv);
|
|
if (ret != OK)
|
|
{
|
|
/* Unrecognized! Discard all of that work we just did and return NULL */
|
|
|
|
ferr("ERROR: Unrecognized\n");
|
|
kmm_free(priv);
|
|
return NULL;
|
|
}
|
|
|
|
gd5f_enable_ecc(priv);
|
|
gd5f_waitstatus(priv, GD5F_SR_OIP, false);
|
|
gd5f_unlockblocks(priv);
|
|
}
|
|
|
|
/* Return the implementation-specific state structure as the MTD device */
|
|
|
|
finfo("Return %p\n", priv);
|
|
return (FAR struct mtd_dev_s *)priv;
|
|
}
|