mirror of
https://github.com/apache/nuttx.git
synced 2026-08-25 15:37:23 +00:00
- Replace syslog() with finfo()/fwarn() to follow the NuttX subsystem debug convention. - Merge adjacent early-continue `if` branches in w25n_pick_free_spare and w25n_scan_factory_bad. - Add Input Parameters / Returned Value sections to the BBM helper headers. - Clarify why only -EIO triggers a remap retry in w25n_erase and w25n_bwrite (block-level E-FAIL/P-FAIL vs. transient bus faults). Signed-off-by: Julian Oes <julian@oes.ch>
1412 lines
42 KiB
C
1412 lines
42 KiB
C
/****************************************************************************
|
|
* drivers/mtd/w25n.c
|
|
*
|
|
* Driver for Winbond W25N SPI NAND flash.
|
|
* Currently only W25N01GV (1Gbit/128MB) is supported and tested.
|
|
*
|
|
* Bad block management uses the chip's built-in 20-entry hardware Bad
|
|
* Block Management Look-Up Table (datasheet section 8.2.7). Factory bad
|
|
* blocks are scanned at init by reading the OOB marker (byte 0 of the
|
|
* spare area of page 0) and remapped to spares from a reserved pool at
|
|
* the top of the array. Runtime erase/program failures (E-FAIL/P-FAIL)
|
|
* trigger the same remap path and retry the operation once. The chip
|
|
* routes accesses to remapped LBAs to the spare PBA transparently.
|
|
*
|
|
* Limitations:
|
|
* - No Quad SPI support (standard SPI only).
|
|
* - No access to OTP region.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*
|
|
* 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 <sys/types.h>
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <nuttx/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_W25N_SPIMODE
|
|
# define CONFIG_W25N_SPIMODE SPIDEV_MODE0
|
|
#endif
|
|
|
|
#ifndef CONFIG_W25N_SPIFREQUENCY
|
|
# define CONFIG_W25N_SPIFREQUENCY 20000000
|
|
#endif
|
|
|
|
/* W25N Commands ************************************************************/
|
|
|
|
#define W25N_RESET 0xff /* Device reset */
|
|
#define W25N_READ_ID 0x9f /* Read JEDEC ID */
|
|
#define W25N_READ_STATUS 0x0f /* Read status register */
|
|
#define W25N_WRITE_STATUS 0x1f /* Write status register */
|
|
#define W25N_WRITE_ENABLE 0x06 /* Write enable */
|
|
#define W25N_WRITE_DISABLE 0x04 /* Write disable */
|
|
#define W25N_PAGE_READ 0x13 /* Page data read (array to buffer) */
|
|
#define W25N_READ_DATA 0x03 /* Read data (from buffer) */
|
|
#define W25N_PROGRAM_LOAD 0x02 /* Program load (to buffer) */
|
|
#define W25N_PROGRAM_EXECUTE 0x10 /* Program execute (buffer to array) */
|
|
#define W25N_BLOCK_ERASE 0xd8 /* Block erase */
|
|
#define W25N_BBM_SWAP 0xa1 /* Bad Block Management (swap blocks) */
|
|
#define W25N_READ_BBM_LUT 0xa5 /* Read BBM Look-Up Table */
|
|
|
|
#define W25N_DUMMY 0x00 /* Dummy byte for SPI */
|
|
|
|
/* Status Register Addresses ************************************************/
|
|
|
|
#define W25N_SR1_ADDR 0xa0 /* Protection register */
|
|
#define W25N_SR2_ADDR 0xb0 /* Configuration register */
|
|
#define W25N_SR3_ADDR 0xc0 /* Status register */
|
|
|
|
/* Status Register 1 (Protection) bits **************************************/
|
|
|
|
#define W25N_SR1_SRP0 (1 << 7)
|
|
#define W25N_SR1_BP3 (1 << 6)
|
|
#define W25N_SR1_BP2 (1 << 5)
|
|
#define W25N_SR1_BP1 (1 << 4)
|
|
#define W25N_SR1_BP0 (1 << 3)
|
|
#define W25N_SR1_TB (1 << 2)
|
|
#define W25N_SR1_WPE (1 << 1)
|
|
#define W25N_SR1_SRP1 (1 << 0)
|
|
|
|
/* Status Register 2 (Configuration) bits ***********************************/
|
|
|
|
#define W25N_SR2_OTPL (1 << 7)
|
|
#define W25N_SR2_OTPE (1 << 6)
|
|
#define W25N_SR2_SR1L (1 << 5)
|
|
#define W25N_SR2_ECCE (1 << 4)
|
|
#define W25N_SR2_BUF (1 << 3)
|
|
|
|
/* Status Register 3 (Status) bits ******************************************/
|
|
|
|
#define W25N_SR3_LUTF (1 << 6)
|
|
#define W25N_SR3_ECC1 (1 << 5)
|
|
#define W25N_SR3_ECC0 (1 << 4)
|
|
#define W25N_SR3_PFAIL (1 << 3)
|
|
#define W25N_SR3_EFAIL (1 << 2)
|
|
#define W25N_SR3_WEL (1 << 1)
|
|
#define W25N_SR3_BUSY (1 << 0)
|
|
|
|
#define W25N_SR3_ECC_MASK (W25N_SR3_ECC1 | W25N_SR3_ECC0)
|
|
#define W25N_SR3_ECC_OK (0x00)
|
|
#define W25N_SR3_ECC_CORRECTED (W25N_SR3_ECC0)
|
|
#define W25N_SR3_ECC_ERROR (W25N_SR3_ECC1)
|
|
|
|
/* Device Identification ****************************************************/
|
|
|
|
#define W25N_MANUFACTURER_ID 0xef /* Winbond */
|
|
#define W25N01GV_DEVICE_ID 0xaa21
|
|
|
|
/* Memory Organization ******************************************************/
|
|
|
|
#define W25N_PAGE_SIZE 2048 /* Bytes per page (data only) */
|
|
#define W25N_PAGE_SHIFT 11 /* 2^11 = 2048 */
|
|
#define W25N_PAGES_PER_BLOCK 64
|
|
#define W25N_BLOCK_SIZE (W25N_PAGE_SIZE * W25N_PAGES_PER_BLOCK)
|
|
#define W25N_BLOCK_SHIFT 17 /* 2^17 = 128KB */
|
|
#define W25N01GV_BLOCKS 1024 /* Total blocks for 1Gbit device */
|
|
|
|
/* Bad Block Management *****************************************************/
|
|
|
|
/* The chip has a 20-entry hardware BBM LUT. We reserve 24 blocks at the
|
|
* top of the array as a spare pool to remap into; 24 > 20 gives headroom
|
|
* in case some spares are themselves factory bad. The remaining blocks
|
|
* (W25N_USER_BLOCKS) are exposed to the upper layer.
|
|
*/
|
|
|
|
#define W25N_BBM_LUT_ENTRIES 20
|
|
#define W25N_SPARE_RESERVE 24
|
|
#define W25N_USER_BLOCKS (W25N01GV_BLOCKS - W25N_SPARE_RESERVE)
|
|
#define W25N_OOB_BBM_COL 2048 /* Column of bad block marker in spare */
|
|
#define W25N_BBM_LBA_ENABLE (1 << 15)
|
|
#define W25N_BBM_LBA_INVALID (1 << 14)
|
|
#define W25N_BBM_BLOCK_MASK 0x03ff
|
|
|
|
/****************************************************************************
|
|
* Private Types
|
|
****************************************************************************/
|
|
|
|
struct w25n_dev_s
|
|
{
|
|
struct mtd_dev_s mtd; /* MTD interface */
|
|
FAR struct spi_dev_s *spi; /* SPI device */
|
|
uint32_t spi_devid; /* SPI device ID for chip select */
|
|
uint16_t nblocks; /* Number of erase blocks */
|
|
uint8_t blockshift; /* Block size shift (17 = 128KB) */
|
|
uint8_t pageshift; /* Page size shift (11 = 2KB) */
|
|
uint8_t eccstatus; /* Last ECC status */
|
|
|
|
/* Cached copy of the chip's BBM LUT */
|
|
|
|
uint32_t bbm[W25N_BBM_LUT_ENTRIES];
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Function Prototypes
|
|
****************************************************************************/
|
|
|
|
/* SPI helpers */
|
|
|
|
static void w25n_lock(FAR struct w25n_dev_s *priv);
|
|
static void w25n_unlock(FAR struct w25n_dev_s *priv);
|
|
|
|
/* Command helpers */
|
|
|
|
static void w25n_reset(FAR struct w25n_dev_s *priv);
|
|
static int w25n_readid(FAR struct w25n_dev_s *priv);
|
|
static uint8_t w25n_read_status(FAR struct w25n_dev_s *priv, uint8_t reg);
|
|
static void w25n_write_status(FAR struct w25n_dev_s *priv,
|
|
uint8_t reg, uint8_t val);
|
|
static void w25n_writeenable(FAR struct w25n_dev_s *priv);
|
|
static void w25n_writedisable(FAR struct w25n_dev_s *priv);
|
|
static int w25n_waitready(FAR struct w25n_dev_s *priv);
|
|
static int w25n_waitready_erase(FAR struct w25n_dev_s *priv);
|
|
|
|
/* Page operations */
|
|
|
|
static int w25n_read_page(FAR struct w25n_dev_s *priv, uint32_t page);
|
|
static void w25n_read_buffer(FAR struct w25n_dev_s *priv, uint16_t col,
|
|
FAR uint8_t *buf, size_t len);
|
|
static void w25n_load_buffer(FAR struct w25n_dev_s *priv, uint16_t col,
|
|
FAR const uint8_t *buf, size_t len);
|
|
static int w25n_program_execute(FAR struct w25n_dev_s *priv,
|
|
uint32_t page);
|
|
static int w25n_block_erase(FAR struct w25n_dev_s *priv, uint32_t block);
|
|
|
|
/* Configuration */
|
|
|
|
static void w25n_enable_ecc(FAR struct w25n_dev_s *priv);
|
|
static void w25n_unprotect(FAR struct w25n_dev_s *priv);
|
|
|
|
/* Bad block management */
|
|
|
|
static void w25n_read_bbm_lut(FAR struct w25n_dev_s *priv);
|
|
static int w25n_bbm_swap(FAR struct w25n_dev_s *priv,
|
|
uint16_t lba, uint16_t pba);
|
|
static int w25n_read_oob_marker(FAR struct w25n_dev_s *priv,
|
|
uint16_t block);
|
|
static bool w25n_is_factory_bad(FAR struct w25n_dev_s *priv,
|
|
uint16_t block);
|
|
static uint16_t w25n_pick_free_spare(FAR struct w25n_dev_s *priv);
|
|
static int w25n_remap_bad_block(FAR struct w25n_dev_s *priv,
|
|
uint16_t block);
|
|
static void w25n_scan_factory_bad(FAR struct w25n_dev_s *priv);
|
|
|
|
/* MTD driver methods */
|
|
|
|
static int w25n_erase(FAR struct mtd_dev_s *dev, off_t startblock,
|
|
size_t nblocks);
|
|
static ssize_t w25n_bread(FAR struct mtd_dev_s *dev, off_t startblock,
|
|
size_t nblocks, FAR uint8_t *buf);
|
|
static ssize_t w25n_bwrite(FAR struct mtd_dev_s *dev, off_t startblock,
|
|
size_t nblocks, FAR const uint8_t *buf);
|
|
static ssize_t w25n_read(FAR struct mtd_dev_s *dev, off_t offset,
|
|
size_t nbytes, FAR uint8_t *buf);
|
|
static int w25n_ioctl(FAR struct mtd_dev_s *dev, int cmd,
|
|
unsigned long arg);
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: w25n_lock
|
|
****************************************************************************/
|
|
|
|
static void w25n_lock(FAR struct w25n_dev_s *priv)
|
|
{
|
|
SPI_LOCK(priv->spi, true);
|
|
SPI_SETMODE(priv->spi, CONFIG_W25N_SPIMODE);
|
|
SPI_SETBITS(priv->spi, 8);
|
|
SPI_HWFEATURES(priv->spi, 0);
|
|
SPI_SETFREQUENCY(priv->spi, CONFIG_W25N_SPIFREQUENCY);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w25n_unlock
|
|
****************************************************************************/
|
|
|
|
static void w25n_unlock(FAR struct w25n_dev_s *priv)
|
|
{
|
|
SPI_LOCK(priv->spi, false);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w25n_reset
|
|
****************************************************************************/
|
|
|
|
static void w25n_reset(FAR struct w25n_dev_s *priv)
|
|
{
|
|
SPI_SELECT(priv->spi, SPIDEV_FLASH(priv->spi_devid), true);
|
|
SPI_SEND(priv->spi, W25N_RESET);
|
|
SPI_SELECT(priv->spi, SPIDEV_FLASH(priv->spi_devid), false);
|
|
|
|
/* Wait for reset to complete (tRST max 500us) */
|
|
|
|
nxsig_usleep(500);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w25n_readid
|
|
****************************************************************************/
|
|
|
|
static int w25n_readid(FAR struct w25n_dev_s *priv)
|
|
{
|
|
uint8_t manufacturer;
|
|
uint8_t device_hi;
|
|
uint8_t device_lo;
|
|
|
|
SPI_SELECT(priv->spi, SPIDEV_FLASH(priv->spi_devid), true);
|
|
SPI_SEND(priv->spi, W25N_READ_ID);
|
|
SPI_SEND(priv->spi, W25N_DUMMY);
|
|
manufacturer = SPI_SEND(priv->spi, W25N_DUMMY);
|
|
device_hi = SPI_SEND(priv->spi, W25N_DUMMY);
|
|
device_lo = SPI_SEND(priv->spi, W25N_DUMMY);
|
|
SPI_SELECT(priv->spi, SPIDEV_FLASH(priv->spi_devid), false);
|
|
|
|
finfo("W25N: Manufacturer=0x%02x Device=0x%02x%02x\n",
|
|
manufacturer, device_hi, device_lo);
|
|
|
|
if (manufacturer != W25N_MANUFACTURER_ID)
|
|
{
|
|
ferr("ERROR: Unexpected manufacturer ID: 0x%02x\n", manufacturer);
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (device_hi == 0xaa && device_lo == 0x21)
|
|
{
|
|
/* W25N01GV - 1Gbit */
|
|
|
|
priv->nblocks = W25N01GV_BLOCKS;
|
|
finfo("W25N01GV detected: %d blocks\n", priv->nblocks);
|
|
}
|
|
else
|
|
{
|
|
ferr("ERROR: Unsupported device ID: 0x%02x%02x\n",
|
|
device_hi, device_lo);
|
|
return -ENODEV;
|
|
}
|
|
|
|
priv->blockshift = W25N_BLOCK_SHIFT;
|
|
priv->pageshift = W25N_PAGE_SHIFT;
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w25n_read_status
|
|
****************************************************************************/
|
|
|
|
static uint8_t w25n_read_status(FAR struct w25n_dev_s *priv, uint8_t reg)
|
|
{
|
|
uint8_t status;
|
|
|
|
SPI_SELECT(priv->spi, SPIDEV_FLASH(priv->spi_devid), true);
|
|
SPI_SEND(priv->spi, W25N_READ_STATUS);
|
|
SPI_SEND(priv->spi, reg);
|
|
status = SPI_SEND(priv->spi, W25N_DUMMY);
|
|
SPI_SELECT(priv->spi, SPIDEV_FLASH(priv->spi_devid), false);
|
|
|
|
return status;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w25n_write_status
|
|
****************************************************************************/
|
|
|
|
static void w25n_write_status(FAR struct w25n_dev_s *priv,
|
|
uint8_t reg, uint8_t val)
|
|
{
|
|
SPI_SELECT(priv->spi, SPIDEV_FLASH(priv->spi_devid), true);
|
|
SPI_SEND(priv->spi, W25N_WRITE_STATUS);
|
|
SPI_SEND(priv->spi, reg);
|
|
SPI_SEND(priv->spi, val);
|
|
SPI_SELECT(priv->spi, SPIDEV_FLASH(priv->spi_devid), false);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w25n_writeenable
|
|
****************************************************************************/
|
|
|
|
static void w25n_writeenable(FAR struct w25n_dev_s *priv)
|
|
{
|
|
SPI_SELECT(priv->spi, SPIDEV_FLASH(priv->spi_devid), true);
|
|
SPI_SEND(priv->spi, W25N_WRITE_ENABLE);
|
|
SPI_SELECT(priv->spi, SPIDEV_FLASH(priv->spi_devid), false);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w25n_writedisable
|
|
****************************************************************************/
|
|
|
|
static void w25n_writedisable(FAR struct w25n_dev_s *priv)
|
|
{
|
|
SPI_SELECT(priv->spi, SPIDEV_FLASH(priv->spi_devid), true);
|
|
SPI_SEND(priv->spi, W25N_WRITE_DISABLE);
|
|
SPI_SELECT(priv->spi, SPIDEV_FLASH(priv->spi_devid), false);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w25n_waitready
|
|
****************************************************************************/
|
|
|
|
static int w25n_waitready(FAR struct w25n_dev_s *priv)
|
|
{
|
|
uint8_t status;
|
|
int timeout = 10000;
|
|
|
|
/* Busy-wait for fast operations (page read ~60us, page program ~250us) */
|
|
|
|
do
|
|
{
|
|
status = w25n_read_status(priv, W25N_SR3_ADDR);
|
|
if ((status & W25N_SR3_BUSY) == 0)
|
|
{
|
|
priv->eccstatus = (status & W25N_SR3_ECC_MASK);
|
|
return OK;
|
|
}
|
|
}
|
|
while (--timeout > 0);
|
|
|
|
ferr("ERROR: Timeout waiting for device ready\n");
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w25n_waitready_erase
|
|
*
|
|
* Description:
|
|
* Wait for block erase to complete. Uses sleep since erase is slow
|
|
* (2-10ms typical) and we can release the SPI bus for other tasks.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int w25n_waitready_erase(FAR struct w25n_dev_s *priv)
|
|
{
|
|
uint8_t status;
|
|
int timeout = 100; /* 100 iterations, ~1 second max */
|
|
|
|
do
|
|
{
|
|
status = w25n_read_status(priv, W25N_SR3_ADDR);
|
|
if ((status & W25N_SR3_BUSY) == 0)
|
|
{
|
|
priv->eccstatus = (status & W25N_SR3_ECC_MASK);
|
|
return OK;
|
|
}
|
|
|
|
/* Unlock SPI, sleep, re-lock - allows other SPI access while waiting */
|
|
|
|
w25n_unlock(priv);
|
|
nxsched_usleep(1000);
|
|
w25n_lock(priv);
|
|
}
|
|
while (--timeout > 0);
|
|
|
|
ferr("ERROR: Timeout waiting for erase to complete\n");
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w25n_read_page
|
|
*
|
|
* Description:
|
|
* Load a page from the NAND array into the device buffer.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int w25n_read_page(FAR struct w25n_dev_s *priv, uint32_t page)
|
|
{
|
|
int ret;
|
|
|
|
/* Send Page Data Read command with 16-bit page address */
|
|
|
|
SPI_SELECT(priv->spi, SPIDEV_FLASH(priv->spi_devid), true);
|
|
SPI_SEND(priv->spi, W25N_PAGE_READ);
|
|
SPI_SEND(priv->spi, W25N_DUMMY);
|
|
SPI_SEND(priv->spi, (page >> 8) & 0xff);
|
|
SPI_SEND(priv->spi, page & 0xff);
|
|
SPI_SELECT(priv->spi, SPIDEV_FLASH(priv->spi_devid), false);
|
|
|
|
/* Wait for page read to complete (tRD max 60us with ECC) */
|
|
|
|
ret = w25n_waitready(priv);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
/* Check ECC status */
|
|
|
|
if (priv->eccstatus == W25N_SR3_ECC_ERROR)
|
|
{
|
|
ferr("ERROR: Uncorrectable ECC error on page %lu\n",
|
|
(unsigned long)page);
|
|
return -EIO;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w25n_read_buffer
|
|
*
|
|
* Description:
|
|
* Read data from the device buffer (after page read).
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void w25n_read_buffer(FAR struct w25n_dev_s *priv, uint16_t col,
|
|
FAR uint8_t *buf, size_t len)
|
|
{
|
|
SPI_SELECT(priv->spi, SPIDEV_FLASH(priv->spi_devid), true);
|
|
SPI_SEND(priv->spi, W25N_READ_DATA);
|
|
SPI_SEND(priv->spi, (col >> 8) & 0xff);
|
|
SPI_SEND(priv->spi, col & 0xff);
|
|
SPI_SEND(priv->spi, W25N_DUMMY);
|
|
SPI_RECVBLOCK(priv->spi, buf, len);
|
|
SPI_SELECT(priv->spi, SPIDEV_FLASH(priv->spi_devid), false);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w25n_load_buffer
|
|
*
|
|
* Description:
|
|
* Load data into the device program buffer.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void w25n_load_buffer(FAR struct w25n_dev_s *priv, uint16_t col,
|
|
FAR const uint8_t *buf, size_t len)
|
|
{
|
|
SPI_SELECT(priv->spi, SPIDEV_FLASH(priv->spi_devid), true);
|
|
SPI_SEND(priv->spi, W25N_PROGRAM_LOAD);
|
|
SPI_SEND(priv->spi, (col >> 8) & 0xff);
|
|
SPI_SEND(priv->spi, col & 0xff);
|
|
SPI_SNDBLOCK(priv->spi, buf, len);
|
|
SPI_SELECT(priv->spi, SPIDEV_FLASH(priv->spi_devid), false);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w25n_program_execute
|
|
*
|
|
* Description:
|
|
* Program the buffer contents to the specified page.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int w25n_program_execute(FAR struct w25n_dev_s *priv, uint32_t page)
|
|
{
|
|
uint8_t status;
|
|
int ret;
|
|
|
|
/* Send Program Execute command with 16-bit page address */
|
|
|
|
SPI_SELECT(priv->spi, SPIDEV_FLASH(priv->spi_devid), true);
|
|
SPI_SEND(priv->spi, W25N_PROGRAM_EXECUTE);
|
|
SPI_SEND(priv->spi, W25N_DUMMY);
|
|
SPI_SEND(priv->spi, (page >> 8) & 0xff);
|
|
SPI_SEND(priv->spi, page & 0xff);
|
|
SPI_SELECT(priv->spi, SPIDEV_FLASH(priv->spi_devid), false);
|
|
|
|
/* Wait for program to complete (tPP max 700us) */
|
|
|
|
ret = w25n_waitready(priv);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
/* Check for program failure */
|
|
|
|
status = w25n_read_status(priv, W25N_SR3_ADDR);
|
|
if (status & W25N_SR3_PFAIL)
|
|
{
|
|
ferr("ERROR: Program failed on page %lu\n", (unsigned long)page);
|
|
return -EIO;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w25n_block_erase
|
|
*
|
|
* Description:
|
|
* Erase a 128KB block.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int w25n_block_erase(FAR struct w25n_dev_s *priv, uint32_t block)
|
|
{
|
|
uint32_t page;
|
|
uint8_t status;
|
|
int ret;
|
|
|
|
/* Convert block number to page address (block * 64) */
|
|
|
|
page = block << 6;
|
|
|
|
finfo("Erasing block %lu (page %lu)\n",
|
|
(unsigned long)block, (unsigned long)page);
|
|
|
|
w25n_writeenable(priv);
|
|
|
|
SPI_SELECT(priv->spi, SPIDEV_FLASH(priv->spi_devid), true);
|
|
SPI_SEND(priv->spi, W25N_BLOCK_ERASE);
|
|
SPI_SEND(priv->spi, W25N_DUMMY);
|
|
SPI_SEND(priv->spi, (page >> 8) & 0xff);
|
|
SPI_SEND(priv->spi, page & 0xff);
|
|
SPI_SELECT(priv->spi, SPIDEV_FLASH(priv->spi_devid), false);
|
|
|
|
/* Wait for erase to complete (2-10ms typical) */
|
|
|
|
ret = w25n_waitready_erase(priv);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
/* Check for erase failure */
|
|
|
|
status = w25n_read_status(priv, W25N_SR3_ADDR);
|
|
if (status & W25N_SR3_EFAIL)
|
|
{
|
|
ferr("ERROR: Erase failed on block %lu\n", (unsigned long)block);
|
|
w25n_writedisable(priv);
|
|
return -EIO;
|
|
}
|
|
|
|
w25n_writedisable(priv);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w25n_enable_ecc
|
|
****************************************************************************/
|
|
|
|
static void w25n_enable_ecc(FAR struct w25n_dev_s *priv)
|
|
{
|
|
uint8_t sr2;
|
|
|
|
sr2 = w25n_read_status(priv, W25N_SR2_ADDR);
|
|
|
|
/* Enable internal ECC and force Buffer Read mode (BUF=1). The W25N01GV*T*
|
|
* variant ships with BUF=0 (Continuous Read), in which Read Data ignores
|
|
* the column address and always starts at byte 0 - which silently breaks
|
|
* any read that targets a non-zero column (OOB markers, sub-page reads).
|
|
*/
|
|
|
|
sr2 |= W25N_SR2_ECCE | W25N_SR2_BUF;
|
|
w25n_write_status(priv, W25N_SR2_ADDR, sr2);
|
|
|
|
finfo("ECC enabled, BUF=1\n");
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w25n_unprotect
|
|
****************************************************************************/
|
|
|
|
static void w25n_unprotect(FAR struct w25n_dev_s *priv)
|
|
{
|
|
/* Clear all block protection bits */
|
|
|
|
w25n_write_status(priv, W25N_SR1_ADDR, 0x00);
|
|
|
|
finfo("All blocks unprotected\n");
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w25n_read_bbm_lut
|
|
*
|
|
* Description:
|
|
* Read all 20 entries of the hardware Bad Block Management Look-Up Table
|
|
* into priv->bbm. Each entry is encoded as (lba_raw << 16) | pba_raw,
|
|
* where the high two LBA bits are status flags (Enable, Invalid).
|
|
* See datasheet section 8.2.8.
|
|
*
|
|
* Input Parameters:
|
|
* priv - W25N device instance; priv->bbm is overwritten with the chip's
|
|
* current LUT contents.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void w25n_read_bbm_lut(FAR struct w25n_dev_s *priv)
|
|
{
|
|
int i;
|
|
|
|
SPI_SELECT(priv->spi, SPIDEV_FLASH(priv->spi_devid), true);
|
|
SPI_SEND(priv->spi, W25N_READ_BBM_LUT);
|
|
SPI_SEND(priv->spi, W25N_DUMMY);
|
|
|
|
/* Decode 4 bytes at a time straight into priv->bbm to avoid an 80-byte
|
|
* temporary on the stack. The runtime remap path is called from the
|
|
* logger thread and we have to keep this lean.
|
|
*/
|
|
|
|
for (i = 0; i < W25N_BBM_LUT_ENTRIES; i++)
|
|
{
|
|
uint8_t b[4];
|
|
|
|
SPI_RECVBLOCK(priv->spi, b, 4);
|
|
priv->bbm[i] = ((uint32_t)b[0] << 24) |
|
|
((uint32_t)b[1] << 16) |
|
|
((uint32_t)b[2] << 8) |
|
|
(uint32_t)b[3];
|
|
}
|
|
|
|
SPI_SELECT(priv->spi, SPIDEV_FLASH(priv->spi_devid), false);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w25n_bbm_swap
|
|
*
|
|
* Description:
|
|
* Add an LBA->PBA link to the chip's non-volatile BBM LUT. Subsequent
|
|
* accesses to the LBA are transparently routed to the PBA by the chip.
|
|
*
|
|
* Input Parameters:
|
|
* priv - W25N device instance.
|
|
* lba - Logical block that should be remapped. Must be within the user
|
|
* area (< W25N_USER_BLOCKS).
|
|
* pba - Physical block in the spare pool to route the LBA to. Must be
|
|
* in [W25N_USER_BLOCKS, W25N01GV_BLOCKS).
|
|
*
|
|
* Returned Value:
|
|
* OK on success, -EINVAL if the LBA/PBA fall outside the allowed ranges,
|
|
* or a negative errno propagated from w25n_waitready.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int w25n_bbm_swap(FAR struct w25n_dev_s *priv,
|
|
uint16_t lba, uint16_t pba)
|
|
{
|
|
int ret;
|
|
|
|
/* Last-line sanity check: a bad LBA or PBA here would burn a LUT slot
|
|
* permanently. Refuse to issue A1h unless LBA is in the user area and
|
|
* PBA is in the spare pool.
|
|
*/
|
|
|
|
if (lba >= W25N_USER_BLOCKS ||
|
|
pba < W25N_USER_BLOCKS || pba >= W25N01GV_BLOCKS)
|
|
{
|
|
ferr("ERROR: BBM swap rejected: LBA=%u PBA=%u\n", lba, pba);
|
|
return -EINVAL;
|
|
}
|
|
|
|
w25n_writeenable(priv);
|
|
|
|
SPI_SELECT(priv->spi, SPIDEV_FLASH(priv->spi_devid), true);
|
|
SPI_SEND(priv->spi, W25N_BBM_SWAP);
|
|
SPI_SEND(priv->spi, (lba >> 8) & 0xff);
|
|
SPI_SEND(priv->spi, lba & 0xff);
|
|
SPI_SEND(priv->spi, (pba >> 8) & 0xff);
|
|
SPI_SEND(priv->spi, pba & 0xff);
|
|
SPI_SELECT(priv->spi, SPIDEV_FLASH(priv->spi_devid), false);
|
|
|
|
ret = w25n_waitready(priv);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
if (w25n_read_status(priv, W25N_SR3_ADDR) & W25N_SR3_LUTF)
|
|
{
|
|
finfo("BBM LUT is now full\n");
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w25n_read_oob_marker
|
|
*
|
|
* Description:
|
|
* Read byte 0 of the spare area of the first page of `block`. The factory
|
|
* marks bad blocks with a non-FFh value here.
|
|
*
|
|
* Input Parameters:
|
|
* priv - W25N device instance.
|
|
* block - Block index to inspect (0..W25N01GV_BLOCKS - 1).
|
|
*
|
|
* Returned Value:
|
|
* The marker byte (0..255), or 0x00 if the page read failed or hit an
|
|
* uncorrectable ECC error (both cases are treated as bad).
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int w25n_read_oob_marker(FAR struct w25n_dev_s *priv, uint16_t block)
|
|
{
|
|
uint32_t page = (uint32_t)block << 6;
|
|
uint8_t marker = 0xff;
|
|
int ret;
|
|
|
|
ret = w25n_read_page(priv, page);
|
|
if (ret < 0 && ret != -EIO)
|
|
{
|
|
return 0x00;
|
|
}
|
|
|
|
w25n_read_buffer(priv, W25N_OOB_BBM_COL, &marker, 1);
|
|
|
|
if (ret == -EIO)
|
|
{
|
|
/* Uncorrectable ECC on the first page of the block: treat as bad. */
|
|
|
|
return 0x00;
|
|
}
|
|
|
|
return marker;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w25n_is_factory_bad
|
|
****************************************************************************/
|
|
|
|
static bool w25n_is_factory_bad(FAR struct w25n_dev_s *priv, uint16_t block)
|
|
{
|
|
return w25n_read_oob_marker(priv, block) != 0xff;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w25n_pick_free_spare
|
|
*
|
|
* Description:
|
|
* Find an unused, non-factory-bad block in the spare pool that is not
|
|
* already a remap target in the cached LUT (priv->bbm). Callers must
|
|
* refresh priv->bbm via w25n_read_bbm_lut beforehand. As a proof of
|
|
* life, the candidate is erased before being returned.
|
|
*
|
|
* Input Parameters:
|
|
* priv - W25N device instance with a freshly read priv->bbm.
|
|
*
|
|
* Returned Value:
|
|
* A usable spare block index in [W25N_USER_BLOCKS, W25N01GV_BLOCKS), or
|
|
* 0xffff if no candidate remains.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static uint16_t w25n_pick_free_spare(FAR struct w25n_dev_s *priv)
|
|
{
|
|
uint16_t b;
|
|
int i;
|
|
|
|
for (b = W25N_USER_BLOCKS; b < W25N01GV_BLOCKS; b++)
|
|
{
|
|
bool taken = false;
|
|
|
|
for (i = 0; i < W25N_BBM_LUT_ENTRIES; i++)
|
|
{
|
|
uint16_t lba_raw = priv->bbm[i] >> 16;
|
|
uint16_t pba = priv->bbm[i] & W25N_BBM_BLOCK_MASK;
|
|
|
|
if ((lba_raw & W25N_BBM_LBA_ENABLE) &&
|
|
!(lba_raw & W25N_BBM_LBA_INVALID) &&
|
|
pba == b)
|
|
{
|
|
taken = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (taken || w25n_is_factory_bad(priv, b))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
/* Active proof that the block actually works: erase it. The factory
|
|
* OOB marker can be missing or corrupted on a block that is still
|
|
* unusable (worn-out, latent defect). Burning a precious LUT slot on
|
|
* a dead spare would be permanent, so we verify before committing.
|
|
* The spare is virgin space - erasing it has no data cost.
|
|
*/
|
|
|
|
if (w25n_block_erase(priv, b) != OK)
|
|
{
|
|
fwarn("spare %u failed erase test, skipping\n", b);
|
|
continue;
|
|
}
|
|
|
|
return b;
|
|
}
|
|
|
|
return 0xffff;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w25n_remap_bad_block
|
|
*
|
|
* Description:
|
|
* Allocate a spare and add a BBM LUT entry mapping `block` to it. After
|
|
* this returns OK the chip transparently routes accesses to `block` to
|
|
* the allocated spare PBA.
|
|
*
|
|
* Input Parameters:
|
|
* priv - W25N device instance.
|
|
* block - Bad user-area block that should be remapped.
|
|
*
|
|
* Returned Value:
|
|
* OK on success, -ENOSPC if the BBM LUT is already full or no free
|
|
* spare could be found, or a negative errno from w25n_bbm_swap.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int w25n_remap_bad_block(FAR struct w25n_dev_s *priv, uint16_t block)
|
|
{
|
|
uint16_t spare;
|
|
|
|
if (w25n_read_status(priv, W25N_SR3_ADDR) & W25N_SR3_LUTF)
|
|
{
|
|
ferr("ERROR: BBM LUT full, cannot remap block %u\n", block);
|
|
return -ENOSPC;
|
|
}
|
|
|
|
w25n_read_bbm_lut(priv);
|
|
|
|
spare = w25n_pick_free_spare(priv);
|
|
if (spare == 0xffff)
|
|
{
|
|
ferr("ERROR: No free spare available to remap block %u\n", block);
|
|
return -ENOSPC;
|
|
}
|
|
|
|
finfo("remap block %u -> spare %u\n", block, spare);
|
|
return w25n_bbm_swap(priv, block, spare);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w25n_scan_factory_bad
|
|
*
|
|
* Description:
|
|
* Scan all blocks for factory bad markers and remap any user-area bad
|
|
* blocks via the chip's BBM LUT. Idempotent across reboots: blocks that
|
|
* are already remapped (present in the LUT) are skipped, so repeated
|
|
* scans don't consume LUT slots. Takes ~200 ms (1024 OOB reads).
|
|
*
|
|
* Input Parameters:
|
|
* priv - W25N device instance; priv->bbm is refreshed as a side effect.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void w25n_scan_factory_bad(FAR struct w25n_dev_s *priv)
|
|
{
|
|
uint16_t block;
|
|
int factory_bad = 0;
|
|
int remapped = 0;
|
|
int unremapped = 0;
|
|
int already_in_lut = 0;
|
|
int lut_used = 0;
|
|
int i;
|
|
|
|
w25n_read_bbm_lut(priv);
|
|
|
|
for (i = 0; i < W25N_BBM_LUT_ENTRIES; i++)
|
|
{
|
|
uint16_t lba_raw = priv->bbm[i] >> 16;
|
|
if (lba_raw & W25N_BBM_LBA_ENABLE)
|
|
{
|
|
lut_used++;
|
|
if (!(lba_raw & W25N_BBM_LBA_INVALID) &&
|
|
(lba_raw & W25N_BBM_BLOCK_MASK) < W25N_USER_BLOCKS)
|
|
{
|
|
already_in_lut++;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (block = 0; block < W25N_USER_BLOCKS; block++)
|
|
{
|
|
bool already_mapped = false;
|
|
|
|
for (i = 0; i < W25N_BBM_LUT_ENTRIES; i++)
|
|
{
|
|
uint16_t lba_raw = priv->bbm[i] >> 16;
|
|
|
|
if ((lba_raw & W25N_BBM_LBA_ENABLE) &&
|
|
!(lba_raw & W25N_BBM_LBA_INVALID) &&
|
|
(lba_raw & W25N_BBM_BLOCK_MASK) == block)
|
|
{
|
|
already_mapped = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Already-mapped: remapped on a previous boot, the chip handles
|
|
* routing. Not factory bad: nothing to do either.
|
|
*/
|
|
|
|
if (already_mapped || !w25n_is_factory_bad(priv, block))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
factory_bad++;
|
|
|
|
if (w25n_read_status(priv, W25N_SR3_ADDR) & W25N_SR3_LUTF)
|
|
{
|
|
unremapped++;
|
|
continue;
|
|
}
|
|
|
|
if (w25n_remap_bad_block(priv, block) == OK)
|
|
{
|
|
remapped++;
|
|
}
|
|
else
|
|
{
|
|
unremapped++;
|
|
}
|
|
}
|
|
|
|
finfo("BBM: %d new bad blocks found (%d remapped, %d unremapped); "
|
|
"%d previously remapped; LUT %d/%d slots used\n",
|
|
factory_bad, remapped, unremapped, already_in_lut,
|
|
lut_used + remapped, W25N_BBM_LUT_ENTRIES);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w25n_erase
|
|
****************************************************************************/
|
|
|
|
static int w25n_erase(FAR struct mtd_dev_s *dev, off_t startblock,
|
|
size_t nblocks)
|
|
{
|
|
FAR struct w25n_dev_s *priv = (FAR struct w25n_dev_s *)dev;
|
|
size_t i;
|
|
int ret;
|
|
|
|
finfo("startblock=%lu nblocks=%zu\n", (unsigned long)startblock, nblocks);
|
|
|
|
w25n_lock(priv);
|
|
|
|
ret = w25n_waitready(priv);
|
|
if (ret < 0)
|
|
{
|
|
w25n_unlock(priv);
|
|
return ret;
|
|
}
|
|
|
|
for (i = 0; i < nblocks; i++)
|
|
{
|
|
uint32_t block = startblock + i;
|
|
int attempt;
|
|
|
|
for (attempt = 0; attempt < 2; attempt++)
|
|
{
|
|
ret = w25n_block_erase(priv, block);
|
|
|
|
/* Only retry on -EIO, which w25n_block_erase returns on the
|
|
* chip's E-FAIL status (the block itself failed to erase).
|
|
* Other errors like -ETIMEDOUT from w25n_waitready_erase are
|
|
* SPI/comms faults, not block-level failures - remapping a
|
|
* block because of a transient bus issue would waste a
|
|
* non-recoverable BBM LUT slot.
|
|
*/
|
|
|
|
if (ret == OK || ret != -EIO)
|
|
{
|
|
break;
|
|
}
|
|
|
|
/* Erase failed (E-FAIL). Try to remap the block to a spare and
|
|
* retry once. The chip will route the second attempt to the
|
|
* spare PBA transparently.
|
|
*/
|
|
|
|
fwarn("erase failed on block %lu, attempting remap\n",
|
|
(unsigned long)block);
|
|
if (w25n_remap_bad_block(priv, (uint16_t)block) != OK)
|
|
{
|
|
ret = -EIO;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ret < 0)
|
|
{
|
|
w25n_unlock(priv);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
w25n_unlock(priv);
|
|
return (int)nblocks;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w25n_bread
|
|
****************************************************************************/
|
|
|
|
static ssize_t w25n_bread(FAR struct mtd_dev_s *dev, off_t startblock,
|
|
size_t nblocks, FAR uint8_t *buf)
|
|
{
|
|
FAR struct w25n_dev_s *priv = (FAR struct w25n_dev_s *)dev;
|
|
size_t i;
|
|
int ret;
|
|
|
|
finfo("startblock=%lu nblocks=%zu\n", (unsigned long)startblock, nblocks);
|
|
|
|
w25n_lock(priv);
|
|
|
|
for (i = 0; i < nblocks; i++)
|
|
{
|
|
uint32_t page = startblock + i;
|
|
|
|
ret = w25n_read_page(priv, page);
|
|
if (ret < 0)
|
|
{
|
|
w25n_unlock(priv);
|
|
return ret;
|
|
}
|
|
|
|
w25n_read_buffer(priv, 0, buf + (i * W25N_PAGE_SIZE), W25N_PAGE_SIZE);
|
|
}
|
|
|
|
w25n_unlock(priv);
|
|
return nblocks;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w25n_bwrite
|
|
****************************************************************************/
|
|
|
|
static ssize_t w25n_bwrite(FAR struct mtd_dev_s *dev, off_t startblock,
|
|
size_t nblocks, FAR const uint8_t *buf)
|
|
{
|
|
FAR struct w25n_dev_s *priv = (FAR struct w25n_dev_s *)dev;
|
|
size_t i;
|
|
int ret;
|
|
|
|
finfo("startblock=%lu nblocks=%zu\n", (unsigned long)startblock, nblocks);
|
|
|
|
w25n_lock(priv);
|
|
|
|
ret = w25n_waitready(priv);
|
|
if (ret < 0)
|
|
{
|
|
w25n_unlock(priv);
|
|
return ret;
|
|
}
|
|
|
|
for (i = 0; i < nblocks; i++)
|
|
{
|
|
uint32_t page = startblock + i;
|
|
int attempt;
|
|
|
|
for (attempt = 0; attempt < 2; attempt++)
|
|
{
|
|
/* Write Enable must be set before each Program Load. */
|
|
|
|
w25n_writeenable(priv);
|
|
w25n_load_buffer(priv, 0, buf + (i * W25N_PAGE_SIZE),
|
|
W25N_PAGE_SIZE);
|
|
ret = w25n_program_execute(priv, page);
|
|
|
|
/* As in w25n_erase, only retry on -EIO (chip P-FAIL); other
|
|
* errors indicate bus/comms faults rather than a bad block.
|
|
*/
|
|
|
|
if (ret == OK || ret != -EIO)
|
|
{
|
|
break;
|
|
}
|
|
|
|
/* Program failed (P-FAIL). Remap the underlying block and retry;
|
|
* the chip will route the next program to the spare PBA. The
|
|
* data buffer is reloaded above on the retry pass.
|
|
*/
|
|
|
|
fwarn("program failed on page %lu, remapping block %lu\n",
|
|
(unsigned long)page, (unsigned long)(page >> 6));
|
|
if (w25n_remap_bad_block(priv, (uint16_t)(page >> 6)) != OK)
|
|
{
|
|
ret = -EIO;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ret < 0)
|
|
{
|
|
w25n_writedisable(priv);
|
|
w25n_unlock(priv);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
w25n_writedisable(priv);
|
|
w25n_unlock(priv);
|
|
return nblocks;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w25n_read
|
|
****************************************************************************/
|
|
|
|
static ssize_t w25n_read(FAR struct mtd_dev_s *dev, off_t offset,
|
|
size_t nbytes, FAR uint8_t *buf)
|
|
{
|
|
FAR struct w25n_dev_s *priv = (FAR struct w25n_dev_s *)dev;
|
|
size_t bytesread = 0;
|
|
int ret;
|
|
|
|
finfo("offset=%lu nbytes=%zu\n", (unsigned long)offset, nbytes);
|
|
|
|
w25n_lock(priv);
|
|
|
|
while (bytesread < nbytes)
|
|
{
|
|
uint32_t page = offset >> priv->pageshift;
|
|
uint16_t col = offset & ((1 << priv->pageshift) - 1);
|
|
size_t chunk = nbytes - bytesread;
|
|
|
|
if (chunk > W25N_PAGE_SIZE - col)
|
|
{
|
|
chunk = W25N_PAGE_SIZE - col;
|
|
}
|
|
|
|
ret = w25n_read_page(priv, page);
|
|
if (ret < 0)
|
|
{
|
|
w25n_unlock(priv);
|
|
return ret;
|
|
}
|
|
|
|
w25n_read_buffer(priv, col, buf + bytesread, chunk);
|
|
|
|
bytesread += chunk;
|
|
offset += chunk;
|
|
}
|
|
|
|
w25n_unlock(priv);
|
|
return bytesread;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: w25n_ioctl
|
|
****************************************************************************/
|
|
|
|
static int w25n_ioctl(FAR struct mtd_dev_s *dev, int cmd, unsigned long arg)
|
|
{
|
|
FAR struct w25n_dev_s *priv = (FAR struct w25n_dev_s *)dev;
|
|
int ret = -EINVAL;
|
|
|
|
finfo("cmd=%d arg=%lu\n", cmd, arg);
|
|
|
|
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));
|
|
|
|
/* Block size is the page size (smallest writable unit) */
|
|
|
|
geo->blocksize = W25N_PAGE_SIZE;
|
|
|
|
/* Erase size is the block size (smallest erasable unit) */
|
|
|
|
geo->erasesize = W25N_BLOCK_SIZE;
|
|
|
|
/* Number of erase blocks */
|
|
|
|
geo->neraseblocks = priv->nblocks;
|
|
|
|
ret = OK;
|
|
|
|
finfo("blocksize=%lu erasesize=%lu neraseblocks=%lu\n",
|
|
(unsigned long)geo->blocksize,
|
|
(unsigned long)geo->erasesize,
|
|
(unsigned long)geo->neraseblocks);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case BIOC_PARTINFO:
|
|
{
|
|
FAR struct partition_info_s *info =
|
|
(FAR struct partition_info_s *)((uintptr_t)arg);
|
|
|
|
if (info != NULL)
|
|
{
|
|
info->numsectors = (uint32_t)priv->nblocks *
|
|
W25N_PAGES_PER_BLOCK;
|
|
info->sectorsize = W25N_PAGE_SIZE;
|
|
info->startsector = 0;
|
|
info->parent[0] = '\0';
|
|
ret = OK;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case MTDIOC_BULKERASE:
|
|
{
|
|
/* Erase the entire device */
|
|
|
|
ret = w25n_erase(dev, 0, priv->nblocks);
|
|
if (ret >= 0)
|
|
{
|
|
ret = OK;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case MTDIOC_ECCSTATUS:
|
|
{
|
|
FAR uint8_t *result = (FAR uint8_t *)((uintptr_t)arg);
|
|
|
|
if (result)
|
|
{
|
|
*result = priv->eccstatus >> 4;
|
|
ret = OK;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
ret = -ENOTTY;
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: w25n_initialize
|
|
*
|
|
* Description:
|
|
* Create an initialized MTD device instance for W25N SPI NAND FLASH.
|
|
*
|
|
****************************************************************************/
|
|
|
|
FAR struct mtd_dev_s *w25n_initialize(FAR struct spi_dev_s *spi,
|
|
uint32_t spi_devid)
|
|
{
|
|
FAR struct w25n_dev_s *priv;
|
|
uint32_t actual_freq;
|
|
int ret;
|
|
|
|
finfo("spi=%p spi_devid=%lu\n", spi, (unsigned long)spi_devid);
|
|
|
|
/* Allocate device structure */
|
|
|
|
priv = kmm_zalloc(sizeof(struct w25n_dev_s));
|
|
if (priv == NULL)
|
|
{
|
|
ferr("ERROR: Failed to allocate device structure\n");
|
|
return NULL;
|
|
}
|
|
|
|
/* Initialize the device structure */
|
|
|
|
priv->mtd.erase = w25n_erase;
|
|
priv->mtd.bread = w25n_bread;
|
|
priv->mtd.bwrite = w25n_bwrite;
|
|
priv->mtd.read = w25n_read;
|
|
priv->mtd.ioctl = w25n_ioctl;
|
|
priv->mtd.name = "w25n";
|
|
priv->spi = spi;
|
|
priv->spi_devid = spi_devid;
|
|
|
|
/* Deselect the device */
|
|
|
|
SPI_SELECT(spi, SPIDEV_FLASH(spi_devid), false);
|
|
|
|
/* Lock and configure SPI */
|
|
|
|
w25n_lock(priv);
|
|
|
|
/* Reset the device */
|
|
|
|
w25n_reset(priv);
|
|
|
|
/* Wait for reset and read ID */
|
|
|
|
ret = w25n_readid(priv);
|
|
if (ret < 0)
|
|
{
|
|
w25n_unlock(priv);
|
|
kmm_free(priv);
|
|
return NULL;
|
|
}
|
|
|
|
/* Unprotect all blocks */
|
|
|
|
w25n_unprotect(priv);
|
|
|
|
/* Enable ECC */
|
|
|
|
w25n_enable_ecc(priv);
|
|
|
|
/* Reserve the top of the array as the BBM spare pool and scan the user
|
|
* area for factory bad blocks, remapping any we find via the chip's
|
|
* hardware BBM LUT.
|
|
*/
|
|
|
|
priv->nblocks = W25N_USER_BLOCKS;
|
|
w25n_scan_factory_bad(priv);
|
|
|
|
w25n_unlock(priv);
|
|
|
|
/* Log actual SPI frequency */
|
|
|
|
w25n_lock(priv);
|
|
actual_freq = SPI_SETFREQUENCY(priv->spi, CONFIG_W25N_SPIFREQUENCY);
|
|
w25n_unlock(priv);
|
|
|
|
finfo("W25N: SPI freq: requested=%lu, actual=%lu Hz\n",
|
|
(unsigned long)CONFIG_W25N_SPIFREQUENCY,
|
|
(unsigned long)actual_freq);
|
|
|
|
finfo("W25N01GV ready: %u user blocks (%u spare), "
|
|
"%u bytes/block, %u total user MB, SPI %lu Hz\n",
|
|
(unsigned)priv->nblocks, (unsigned)W25N_SPARE_RESERVE,
|
|
(unsigned)W25N_BLOCK_SIZE,
|
|
(unsigned)(((uint32_t)priv->nblocks * W25N_BLOCK_SIZE) >> 20),
|
|
(unsigned long)actual_freq);
|
|
|
|
return &priv->mtd;
|
|
}
|