nuttx/drivers/mtd/sst39vf.c
Xiang Xiao 9f027208d4 fs: Add model field to geometry and mtd_geometry_s
the model is very useful to track the device info

Signed-off-by: Xiang Xiao <xiaoxiang@xiaomi.com>
2023-01-31 11:50:28 -03:00

845 lines
24 KiB
C

/****************************************************************************
* drivers/mtd/sst39vf.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 <sys/types.h>
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <debug.h>
#include <nuttx/clock.h>
#include <nuttx/arch.h>
#include <nuttx/signal.h>
#include <nuttx/fs/ioctl.h>
#include <nuttx/mtd/mtd.h>
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* Configuration */
#ifndef CONFIG_SST39VF_BASE_ADDRESS
# error "The FLASH base address was not provided (CONFIG_SST39VF_BASE_ADDRESS)"
#endif
/* MAP SST39VF address to a 16-bit bus address */
#define SST39VF_ADDR(addr) \
(volatile FAR uint16_t *)(CONFIG_SST39VF_BASE_ADDRESS | (addr << 1))
/* Timing */
#define SST39VF_TBP_USEC 10 /* Word-Program Time (max); 7uS typical */
#define SST39VF_TIDA_NSEC 150 /* Software ID Access and Exit Time (max) */
#define SST39VF_TSE_MSEC 25 /* Sector-Erase 25 ms (max); 18 ms typical */
#define SST39VF_TBE_MSEC 25 /* Block-Erase 25 ms (max); 18 ms typical */
#define SST39VF_TSCE_MSEC 50 /* Chip-Erase 50 ms (max); */
#define WORDWRITE_TIMEOUT 0x080000000
/* IDs */
#define SST_MANUFACTURER_ID 0xbf
/****************************************************************************
* Private Types
****************************************************************************/
/* This describes one chip in the SST39VF family */
struct sst39vf_chip_s
{
uint16_t chipid; /* ID of the chip */
uint16_t nsectors; /* Number of erase-ablesectors */
uint32_t sectorsize; /* Size of one sector */
};
/* This type holds one FLASH address and one 16-bit FLASH data value */
struct sst39vf_wrinfo_s
{
uintptr_t address;
uint16_t data;
};
/* 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 sst39vf_dev_s.
*/
struct sst39vf_dev_s
{
struct mtd_dev_s mtd;
FAR const struct sst39vf_chip_s *chip;
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
/* Low Level Helpers */
static inline void
sst39vf_flashwrite(FAR const struct sst39vf_wrinfo_s *wrinfo);
static inline uint16_t sst39vf_flashread(uintptr_t address);
static void sst39vf_writeseq(FAR const struct sst39vf_wrinfo_s *wrinfo,
int nseq);
static int sst39vf_chiperase(FAR struct sst39vf_dev_s *priv);
static int sst39vf_sectorerase(FAR struct sst39vf_dev_s *priv,
uintptr_t sectaddr);
static int sst39vf_writeword(FAR const struct sst39vf_wrinfo_s *wrinfo);
/* MTD driver methods */
static int sst39vf_erase(FAR struct mtd_dev_s *dev, off_t startblock,
size_t nblocks);
static ssize_t sst39vf_bread(FAR struct mtd_dev_s *dev,
off_t startblock, size_t nblocks,
FAR uint8_t *buf);
static ssize_t sst39vf_bwrite(FAR struct mtd_dev_s *dev, off_t startblock,
size_t nblocks, FAR const uint8_t *buf);
static ssize_t sst39vf_read(FAR struct mtd_dev_s *dev, off_t offset,
size_t nbytes, FAR uint8_t *buffer);
static int sst39vf_ioctl(FAR struct mtd_dev_s *dev, int cmd,
unsigned long arg);
/****************************************************************************
* Private Data
****************************************************************************/
static const struct sst39vf_chip_s g_sst39vf1601 =
{
0x234b, /* chipid */
512, /* nsectors */
4 * 1024 /* sectorsize */
};
static const struct sst39vf_chip_s g_sst39vf1602 =
{
0x234a, /* chipid */
512, /* nsectors */
4 * 1024 /* sectorsize */
};
static const struct sst39vf_chip_s g_sst39vf3201 =
{
0x235b, /* chipid */
1024, /* nsectors */
4 * 1024 /* sectorsize */
};
static const struct sst39vf_chip_s g_sst39vf3202 =
{
0x235a, /* chipid */
1024, /* nsectors */
4 * 1024 /* sectorsize */
};
/* This structure holds the state of the MTD driver */
static struct sst39vf_dev_s g_sst39vf =
{
{
sst39vf_erase, /* erase method */
sst39vf_bread, /* bread method */
sst39vf_bwrite, /* bwrte method */
sst39vf_read, /* read method */
#ifdef CONFIG_MTD_BYTE_WRITE
NULL, /* write method */
#endif
sst39vf_ioctl, /* ioctl method */
"sst39vf",
},
NULL /* Chip */
};
/* Command sequences */
static const struct sst39vf_wrinfo_s g_wordprogram[3] =
{
{
0x5555, 0x00aa
},
{
0x2aaa, 0x0055
},
{
0x5555, 0x00a0
}
};
static const struct sst39vf_wrinfo_s g_sectorerase[5] =
{
{
0x5555, 0x00aa
},
{
0x2aaa, 0x0055
},
{
0x5555, 0x0080
},
{
0x5555, 0x00aa
},
{
0x2aaa, 0x0055
}
};
static const struct sst39vf_wrinfo_s g_chiperase[6] =
{
{
0x5555, 0x00aa
},
{
0x2aaa, 0x0055
},
{
0x5555, 0x0080
},
{
0x5555, 0x00aa
},
{
0x2aaa, 0x0055
},
{
0x5555, 0x0010
}
};
static const struct sst39vf_wrinfo_s g_swid_entry[3] =
{
{
0x5555, 0x00aa
},
{
0x2aaa, 0x0055
},
{
0x5555, 0x0090
}
};
static const struct sst39vf_wrinfo_s g_swid_exit[3] =
{
{
0x5555, 0x00aa
},
{
0x2aaa, 0x0055
},
{
0x5555, 0x00f0
}
};
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: sst39vf_flashwrite
*
* Description:
* Write one value to FLASH
*
****************************************************************************/
static inline void
sst39vf_flashwrite(FAR const struct sst39vf_wrinfo_s *wrinfo)
{
volatile uint16_t *addr = SST39VF_ADDR(wrinfo->address);
*addr = wrinfo->data;
}
/****************************************************************************
* Name: sst39vf_flashread
*
* Description:
* Read one value from FLASH
*
****************************************************************************/
static inline uint16_t sst39vf_flashread(uintptr_t address)
{
return *SST39VF_ADDR(address);
}
/****************************************************************************
* Name: sst39vf_writeseq
*
* Description:
* Write a sequence of values to FLASH
*
****************************************************************************/
static void sst39vf_writeseq(FAR const struct sst39vf_wrinfo_s *wrinfo,
int nseq)
{
while (nseq--)
{
sst39vf_flashwrite(wrinfo);
wrinfo++;
}
}
/****************************************************************************
* Name: sst39vf_checktoggle
*
* Description:
* Check for bit toggle
*
* "Toggle Bits (DQ6 and DQ2). During the internal Program or Erase
* operation, any consecutive attempts to read DQ6 will produce
* alternating 1s and 0s, i.e., toggling between 1 and 0. When
* the internal Program or Erase operation is completed, the DQ6 bit
* will stop toggling. The device is then ready for the next operation.
* For Sector-, Block-, or Chip-Erase, the toggle bit (DQ6) is valid
* after the rising edge of sixth WE# (or CE#) pulse. DQ6 will be set to
* 1 if a Read operation is attempted on an Erase-Suspended
* Sector/Block. If Program operation is initiated in a sector/block not
* selected in Erase-Suspend mode, DQ6 will toggle.
*
* "An additional Toggle Bit is available on DQ2, which can be used in
* conjunction with DQ6 to check whether a particular sector is being
* actively erased or erase-suspended. ... The Toggle Bit (DQ2) is valid
* after the rising edge of the last WE# (or CE#) pulse of Write
* operation."
*
****************************************************************************/
static bool sst39vf_checktoggle(FAR const struct sst39vf_wrinfo_s *wrinfo)
{
uint16_t value1;
uint16_t value2;
value1 = sst39vf_flashread(wrinfo->address);
value2 = sst39vf_flashread(wrinfo->address);
return (value1 == value2);
}
/****************************************************************************
* Name: sst39vf_waittoggle
*
* Description:
* Wait until the data is no longer toggling.
*
****************************************************************************/
static int sst39vf_waittoggle(FAR const struct sst39vf_wrinfo_s *wrinfo,
uint32_t retries)
{
while (retries-- > 0)
{
if (sst39vf_checktoggle(wrinfo))
{
return OK;
}
}
return -ETIMEDOUT;
}
/****************************************************************************
* Name: sst39vf_chiperase
*
* Description:
* Erase the entire chip
*
* "The SST39VF160x/320x provide a Chip-Erase operation, which allows the
* user to erase the entire memory array to the 1 state. This is
* useful when the entire device must be quickly erased. The Chip-Erase
* operation is initiated by executing a six-byte command sequence with
* Chip-Erase command (10H) at address 5555H in the last byte sequence.
* The Erase operation begins with the rising edge of the sixth WE# or
* CE#, whichever occurs first. During the Erase operation, the only valid
* read is Toggle Bit or Data# Polling... Any commands issued during the
* Chip-Erase operation are ignored. When WP# is low, any attempt to
* Chip-Erase will be ignored. During the command sequence, WP# should
* be statically held high or low."
*
****************************************************************************/
static int sst39vf_chiperase(FAR struct sst39vf_dev_s *priv)
{
#if 0
struct sst39vf_wrinfo_s wrinfo;
clock_t start;
clock_t elapsed;
#endif
/* Send the sequence to erase the chip */
sst39vf_writeseq(g_chiperase, 6);
/* Use the data toggle delay method. The typical delay is 40 MSec. The
* maximum is 50 MSec. So using the data toggle delay method should give
* better chip erase performance by about 10MS.
*/
#if 0
wrinfo.address = CONFIG_SST39VF_BASE_ADDRESS;
wrinfo.data = 0xffff;
start = clock_systime_ticks();
while (delay < MSEC2TICK(SST39VF_TSCE_MSEC))
{
/* Check if the erase is complete */
if (sst39vf_checktoggle(&wrinfo))
{
return OK;
}
/* No, check if the timeout has elapsed */
elapsed = clock_systime_ticks() - start;
if (elapsed > MSEC2TICK(SST39VF_TSCE_MSEC))
{
return -ETIMEDOUT;
}
/* No, wait one system clock tick */
nxsig_usleep(USEC_PER_TICK);
}
#else
/* Delay the maximum amount of time for the chip erase to complete. */
nxsig_usleep(SST39VF_TSCE_MSEC * USEC_PER_MSEC);
#endif
return OK;
}
/****************************************************************************
* Name: sst39vf_sectorerase
*
* Description:
* Erase the entire chip
*
* "... The Sector-Erase operation is initiated by executing a six-byte
* command sequence with Sector-Erase command (30H) and sector address
* (SA) in the last bus cycle.
*
* The sector ... address is latched on the falling edge of the sixth
* WE# pulse, while the command (30H or 50H) is latched on the rising edge
* of the sixth WE# pulse. The internal Erase operation begins after the
* sixth WE# pulse. The End-of-Erase operation can be determined using
* either Data# Polling or Toggle Bit methods."
*
****************************************************************************/
static int sst39vf_sectorerase(FAR struct sst39vf_dev_s *priv,
uintptr_t sectaddr)
{
struct sst39vf_wrinfo_s wrinfo;
#if 0
clock_t start;
clock_t elapsed;
#endif
/* Set up the sector address */
wrinfo.address = sectaddr;
wrinfo.data = 0x0030;
/* Send the sequence to erase the chip */
sst39vf_writeseq(g_sectorerase, 5);
sst39vf_flashwrite(&wrinfo);
/* Use the data toggle delay method. The typical delay is 18 MSec. The
* maximum is 25 MSec. With a 10 MS system timer resolution, this is
* the difference of waiting 20MS vs. 20MS. So using the data toggle
* delay method should give better write performance by about 10MS per
* block.
*/
#if 0
start = clock_systime_ticks();
while (delay < MSEC2TICK(SST39VF_TSE_MSEC))
{
/* Check if the erase is complete */
if (sst39vf_checktoggle(&wrinfo))
{
return OK;
}
/* No, check if the timeout has elapsed */
elapsed = clock_systime_ticks() - start;
if (elapsed > MSEC2TICK(SST39VF_TSE_MSEC))
{
return -ETIMEDOUT;
}
/* No, wait one system clock tick */
nxsig_usleep(USEC_PER_TICK);
}
#else
/* Delay the maximum amount of time for the sector erase to complete. */
nxsig_usleep(SST39VF_TSE_MSEC * USEC_PER_MSEC);
#endif
return OK;
}
/****************************************************************************
* Name: sst39vf_writeword
*
* Description:
* Write one 16-bit word to FLASH
*
* "The SST39VF160x/320x are programmed on a word-by-word basis. Before
* programming, the sector where the word exists must be fully erased. The
* rogram operation is accomplished in three steps. The first step is the
* three-byte load sequence for Software Data Protection. The second step
* is to load word address and word data. During the Word-Program operation
* , the addresses are latched on the falling edge of either CE# or WE#,
* whichever occurs last. The data is latched on the rising edge of either
* CE# or WE#, whichever occurs first. The third step is the internal
* Program operation which is initiated after the rising edge of the
* fourth WE# or CE#, whichever occurs first. The Program operation, once
* initiated, will be completed within 10s. .... During the Program
* operation, the only valid reads are Data# Polling and Toggle Bit.
* During the internal Program operation, the host is free to perform
* additional tasks. Any commands issued during the internal Program
* operation are ignored. During the command sequence, WP# should be
* statically held high or low."
*
****************************************************************************/
static int sst39vf_writeword(FAR const struct sst39vf_wrinfo_s *wrinfo)
{
/* Send the sequence to write the word to the chip */
sst39vf_writeseq(g_wordprogram, 3);
sst39vf_flashwrite(wrinfo);
/* Use the data toggle delay method. The typical delay is 7 usec; the
* maximum is 10 usec.
*/
return sst39vf_waittoggle(wrinfo, WORDWRITE_TIMEOUT);
}
/****************************************************************************
* Name: sst39vf_erase
*
* Description:
* Erase several blocks, each of the size previously reported (i.e., one
* SST39VF sector).
*
****************************************************************************/
static int sst39vf_erase(FAR struct mtd_dev_s *dev, off_t startblock,
size_t nblocks)
{
FAR struct sst39vf_dev_s *priv = (FAR struct sst39vf_dev_s *)dev;
uintptr_t address;
int ret;
DEBUGASSERT(priv && priv->chip && startblock < priv->chip->nsectors);
for (address = startblock * priv->chip->sectorsize;
nblocks > 0;
nblocks--, address += priv->chip->sectorsize)
{
/* Clear the sector */
ret = sst39vf_sectorerase(priv, address >> 1);
if (ret < 0)
{
return ret;
}
}
return OK;
}
/****************************************************************************
* Name: sst39vf_bread
*
* Description:
* Read the specified number of blocks into the user provided buffer.
*
****************************************************************************/
static ssize_t sst39vf_bread(FAR struct mtd_dev_s *dev, off_t startblock,
size_t nblocks, FAR uint8_t *buf)
{
FAR struct sst39vf_dev_s *priv = (FAR struct sst39vf_dev_s *)dev;
FAR const uint8_t *source;
size_t nbytes;
DEBUGASSERT(priv && priv->chip && startblock < priv->chip->nsectors);
/* Get the source address and the size of the transfer */
source = (FAR const uint8_t *)
SST39VF_ADDR(startblock * priv->chip->sectorsize >> 1);
nbytes = nblocks * priv->chip->sectorsize;
/* Copy the data to the user buffer */
memcpy(buf, source, nbytes);
return nblocks;
}
/****************************************************************************
* Name: sst39vf_bwrite
*
* Description:
* Write the specified number of blocks from the user provided buffer.
*
****************************************************************************/
static ssize_t sst39vf_bwrite(FAR struct mtd_dev_s *dev, off_t startblock,
size_t nblocks, FAR const uint8_t *buf)
{
FAR struct sst39vf_dev_s *priv = (FAR struct sst39vf_dev_s *)dev;
struct sst39vf_wrinfo_s wrinfo;
FAR const uint16_t *source = (FAR const uint16_t *)buf;
size_t nwords;
int ret;
DEBUGASSERT(priv && priv->chip && ((uintptr_t)buf & 1) == 0 &&
startblock < priv->chip->nsectors);
/* Get the destination address and the size of the transfer */
wrinfo.address = (uintptr_t)(startblock * priv->chip->sectorsize >> 1);
nwords = nblocks * (priv->chip->sectorsize >> 1);
/* Copy the data to the user buffer */
while (nwords-- > 0)
{
wrinfo.data = *source++;
ret = sst39vf_writeword(&wrinfo);
if (ret < 0)
{
return ret;
}
wrinfo.address += sizeof(uint8_t);
}
return nblocks;
}
/****************************************************************************
* Name: sst39vf_read
*
* Description:
* Read the specified number of bytes to the user provided buffer.
*
****************************************************************************/
static ssize_t sst39vf_read(FAR struct mtd_dev_s *dev, off_t offset,
size_t nbytes, FAR uint8_t *buffer)
{
#ifdef CONFIG_DEBUG_FEATURES
FAR struct sst39vf_dev_s *priv = (FAR struct sst39vf_dev_s *)dev;
#endif
FAR const uint8_t *source;
DEBUGASSERT(priv && priv->chip &&
offset < priv->chip->nsectors * priv->chip->sectorssize);
/* Get the source address and the size of the transfer */
source = (FAR const uint8_t *)SST39VF_ADDR(offset >> 1);
/* Copy the data to the user buffer */
memcpy(buffer, source, nbytes);
return nbytes;
}
/****************************************************************************
* Name: sst39vf_ioctl
****************************************************************************/
static int sst39vf_ioctl(FAR struct mtd_dev_s *dev,
int cmd, unsigned long arg)
{
FAR struct sst39vf_dev_s *priv = (FAR struct sst39vf_dev_s *)dev;
int ret = -ENOTTY;
DEBUGASSERT(priv && priv->chip);
switch (cmd)
{
case MTDIOC_GEOMETRY:
{
FAR struct mtd_geometry_s *geo = (FAR struct mtd_geometry_s *)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.
*/
geo->blocksize = priv->chip->sectorsize;
geo->erasesize = priv->chip->sectorsize;
geo->neraseblocks = priv->chip->nsectors;
ret = OK;
}
}
break;
case BIOC_XIPBASE:
{
FAR void **ppv = (FAR void **)arg;
if (ppv)
{
/* Return the base address of FLASH memory */
*ppv = (FAR void *)CONFIG_SST39VF_BASE_ADDRESS;
ret = OK;
}
}
break;
case BIOC_PARTINFO:
{
FAR struct partition_info_s *info =
(FAR struct partition_info_s *)arg;
if (info != NULL)
{
info->numsectors = priv->chip->nsectors;
info->sectorsize = priv->chip->sectorsize;
info->startsector = 0;
info->parent[0] = '\0';
ret = OK;
}
}
break;
case MTDIOC_BULKERASE:
{
/* Erase the entire chip */
return sst39vf_chiperase(priv);
}
break;
default:
ret = -ENOTTY; /* Bad command */
break;
}
return ret;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: sst39vf_initialize
*
* Description:
* Create and initialize an MTD device instance assuming an SST39VF NOR
* FLASH device at the configured address in memory. 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 *sst39vf_initialize(void)
{
uint16_t manufacturer;
uint16_t chipid;
DEBUGASSERT(g_sst39vf.chip == NULL);
/* Issue the software entry command sequence */
sst39vf_writeseq(g_swid_entry, 3);
up_udelay(10);
/* Read the manufacturer and chip ID */
manufacturer = sst39vf_flashread(0x0000);
chipid = sst39vf_flashread(0x0001);
/* Issue the software exit sequence */
sst39vf_writeseq(g_swid_exit, 3);
up_udelay(10);
/* Now see if we can support the part */
finfo("Manufacturer: %02x\n", manufacturer);
finfo("Chip ID: %04x\n", chipid);
if (manufacturer != SST_MANUFACTURER_ID)
{
ferr("ERROR: Unrecognized manufacturer: %02x\n", manufacturer);
return NULL;
}
else if (chipid == g_sst39vf1601.chipid)
{
g_sst39vf.chip = &g_sst39vf1601;
}
else if (chipid == g_sst39vf1602.chipid)
{
g_sst39vf.chip = &g_sst39vf1602;
}
else if (chipid == g_sst39vf3201.chipid)
{
g_sst39vf.chip = &g_sst39vf3201;
}
else if (chipid == g_sst39vf3202.chipid)
{
g_sst39vf.chip = &g_sst39vf3202;
}
else
{
ferr("ERROR: Unrecognized chip ID: %04x\n", chipid);
return NULL;
}
/* Return the state structure as the MTD device */
return (FAR struct mtd_dev_s *)&g_sst39vf;
}