diff options
author | patacongo <patacongo@42af7a65-404d-4744-a932-0658087f49c3> | 2012-09-17 18:18:44 +0000 |
---|---|---|
committer | patacongo <patacongo@42af7a65-404d-4744-a932-0658087f49c3> | 2012-09-17 18:18:44 +0000 |
commit | 57623d42ebb04f0a0b9e6eb7c0847a3ece2aa0ff (patch) | |
tree | 25d07d14e920d31c0b1947c9ca586f2a01fc32d8 /nuttx/drivers/mtd | |
download | px4-firmware-57623d42ebb04f0a0b9e6eb7c0847a3ece2aa0ff.tar.gz px4-firmware-57623d42ebb04f0a0b9e6eb7c0847a3ece2aa0ff.tar.bz2 px4-firmware-57623d42ebb04f0a0b9e6eb7c0847a3ece2aa0ff.zip |
Resync new repository with old repo r5166
git-svn-id: http://svn.code.sf.net/p/nuttx/code/trunk@5153 42af7a65-404d-4744-a932-0658087f49c3
Diffstat (limited to 'nuttx/drivers/mtd')
-rw-r--r-- | nuttx/drivers/mtd/Kconfig | 68 | ||||
-rw-r--r-- | nuttx/drivers/mtd/Make.defs | 54 | ||||
-rw-r--r-- | nuttx/drivers/mtd/at24xx.c | 429 | ||||
-rw-r--r-- | nuttx/drivers/mtd/at45db.c | 899 | ||||
-rw-r--r-- | nuttx/drivers/mtd/flash_eraseall.c | 117 | ||||
-rw-r--r-- | nuttx/drivers/mtd/ftl.c | 578 | ||||
-rw-r--r-- | nuttx/drivers/mtd/m25px.c | 798 | ||||
-rw-r--r-- | nuttx/drivers/mtd/rammtd.c | 417 | ||||
-rw-r--r-- | nuttx/drivers/mtd/ramtron.c | 674 | ||||
-rw-r--r-- | nuttx/drivers/mtd/skeleton.c | 275 | ||||
-rw-r--r-- | nuttx/drivers/mtd/sst25.c | 1250 |
11 files changed, 5559 insertions, 0 deletions
diff --git a/nuttx/drivers/mtd/Kconfig b/nuttx/drivers/mtd/Kconfig new file mode 100644 index 000000000..bda5afa84 --- /dev/null +++ b/nuttx/drivers/mtd/Kconfig @@ -0,0 +1,68 @@ +# +# For a description of the syntax of this configuration file, +# see misc/tools/kconfig-language.txt. +# +config MTD_AT24XX + bool "I2C-based AT24XX eeprom" + default n + select I2C + +config AT24XX_SIZE + int "at24xx size(kByte)" + default 64 + depends on MTD_AT24XX + +config AT24XX_ADDR + hex "at24xx i2c address" + default 0x50 + depends on MTD_AT24XX + +config MTD_AT45DB + bool "SPI-based AT45DB flash" + default n + select SPI + +config AT45DB_FREQUENCY + int "at45db frequency" + default 1000000 + depends on MTD_AT45DB + +config AT45DB_PREWAIT + bool "enables higher performance write logic" + default y + depends on MTD_AT45DB + +config AT45DB_PWRSAVE + bool "enables power save" + default n + depends on MTD_AT45DB + +config MTD_MP25P + bool "SPI-based M25P1 falsh" + default n + select SPI + +config MP25P_SPIMODE + int "mp25p spi mode" + default 0 + depends on MTD_MP25P + +config MP25P_MANUFACTURER + hex "mp25p manufacturers ID" + default 0x20 + depends on MTD_MP25P + ---help--- + Various manufacturers may have produced the parts. 0x20 is the manufacturer ID + for the STMicro MP25x serial FLASH. If, for example, you are using the a Macronix + International MX25 serial FLASH, the correct manufacturer ID would be 0xc2. + +config MTD_RAMTRON + bool "SPI-based RAMTRON NVRAM Devices FM25V10" + default n + select SPI + ---help--- + SPI-based RAMTRON NVRAM Devices FM25V10 + +config MTD_RAM + bool "Memory bus ram" + default n diff --git a/nuttx/drivers/mtd/Make.defs b/nuttx/drivers/mtd/Make.defs new file mode 100644 index 000000000..866d7c713 --- /dev/null +++ b/nuttx/drivers/mtd/Make.defs @@ -0,0 +1,54 @@ +############################################################################ +# drivers/mtd/Make.defs +# These driver supports various Memory Technology Devices (MTD) using the +# NuttX MTD interface. +# +# Copyright (C) 2009-2012 Gregory Nutt. All rights reserved. +# Author: Gregory Nutt <gnutt@nuttx.org> +# +# 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. +# +############################################################################ + +# Include MTD drivers + +CSRCS += at45db.c flash_eraseall.c ftl.c m25px.c rammtd.c ramtron.c + +ifeq ($(CONFIG_MTD_AT24XX),y) +CSRCS += at24xx.c +endif + +ifeq ($(CONFIG_MTD_SST25),y) +CSRCS += sst25.c +endif + +# Include MTD driver support + +DEPPATH += --dep-path mtd +VPATH += :mtd + diff --git a/nuttx/drivers/mtd/at24xx.c b/nuttx/drivers/mtd/at24xx.c new file mode 100644 index 000000000..d157a9c47 --- /dev/null +++ b/nuttx/drivers/mtd/at24xx.c @@ -0,0 +1,429 @@ +/************************************************************************************ + * drivers/mtd/at24xx.c + * Driver for I2C-based at24cxx EEPROM(at24c32,at24c64,at24c128,at24c256) + * + * Copyright (C) 2011 Li Zhuoyi. All rights reserved. + * Author: Li Zhuoyi <lzyy.cn@gmail.com> + * History: 0.1 2011-08-20 initial version + * + * 2011-11-1 Added support for larger MTD block sizes: Hal Glenn <hglenn@2g-eng.com> + * + * Derived from drivers/mtd/m25px.c + * + * Copyright (C) 2009-2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <gnutt@nuttx.org> + * + * 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 <string.h> +#include <errno.h> +#include <debug.h> + +#include <nuttx/kmalloc.h> +#include <nuttx/fs/ioctl.h> +#include <nuttx/i2c.h> +#include <nuttx/mtd.h> + +#ifdef CONFIG_MTD_AT24XX + +/************************************************************************************ + * Pre-processor Definitions + ************************************************************************************/ + +/* As a minimum, the size of the AT24 part and its 7-bit I2C address are required. */ + +#ifndef CONFIG_AT24XX_SIZE +# warning "Assuming AT24 size 64" +# define CONFIG_AT24XX_SIZE 64 +#endif +#ifndef CONFIG_AT24XX_ADDR +# warning "Assuming AT24 address of 0x50" +# define CONFIG_AT24XX_ADDR 0x50 +#endif + +/* Get the part configuration based on the size configuration */ + +#if CONFIG_AT24XX_SIZE == 32 +# define AT24XX_NPAGES 128 +# define AT24XX_PAGESIZE 32 +#elif CONFIG_AT24XX_SIZE == 48 +# define AT24XX_NPAGES 192 +# define AT24XX_PAGESIZE 32 +#elif CONFIG_AT24XX_SIZE == 64 +# define AT24XX_NPAGES 256 +# define AT24XX_PAGESIZE 32 +#elif CONFIG_AT24XX_SIZE == 128 +# define AT24XX_NPAGES 256 +# define AT24XX_PAGESIZE 64 +#elif CONFIG_AT24XX_SIZE == 256 +# define AT24XX_NPAGES 512 +# define AT24XX_PAGESIZE 64 +#endif + +/* For applications where a file system is used on the AT24, the tiny page sizes + * will result in very inefficient FLASH usage. In such cases, it is better if + * blocks are comprised of "clusters" of pages so that the file system block + * size is, say, 256 or 512 bytes. In any event, the block size *must* be an + * even multiple of the pages. + */ + +#ifndef CONFIG_AT24XX_MTD_BLOCKSIZE +# warning "Assuming driver block size is the same as the FLASH page size" +# define CONFIG_AT24XX_MTD_BLOCKSIZE AT24XX_PAGESIZE +#endif + +/************************************************************************************ + * 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 at24c_dev_s. + */ + +struct at24c_dev_s +{ + struct mtd_dev_s mtd; /* MTD interface */ + FAR struct i2c_dev_s *dev; /* Saved I2C interface instance */ + uint8_t addr; /* I2C address */ + uint16_t pagesize; /* 32, 63 */ + uint16_t npages; /* 128, 256, 512, 1024 */ +}; + +/************************************************************************************ + * Private Function Prototypes + ************************************************************************************/ + +/* MTD driver methods */ + +static int at24c_erase(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks); +static ssize_t at24c_bread(FAR struct mtd_dev_s *dev, off_t startblock, + size_t nblocks, FAR uint8_t *buf); +static ssize_t at24c_bwrite(FAR struct mtd_dev_s *dev, off_t startblock, + size_t nblocks, FAR const uint8_t *buf); +static ssize_t at24c_read(FAR struct mtd_dev_s *dev, off_t offset, + size_t nbytes,FAR uint8_t *buffer); +static int at24c_ioctl(FAR struct mtd_dev_s *dev, int cmd, unsigned long arg); + +/************************************************************************************ + * Private Data + ************************************************************************************/ + +/* At present, only a signal AT24 part is supported. In this case, a statically + * allocated state structure may be used. + */ + +static struct at24c_dev_s g_at24c; + +/************************************************************************************ + * Private Functions + ************************************************************************************/ + +static int at24c_eraseall(FAR struct at24c_dev_s *priv) +{ + int startblock = 0; + uint8_t buf[AT24XX_PAGESIZE + 2]; + + memset(&buf[2],0xff,priv->pagesize); + I2C_SETADDRESS(priv->dev,priv->addr,7); + I2C_SETFREQUENCY(priv->dev,100000); + + for (startblock = 0; startblock < priv->npages; startblock++) + { + uint16_t offset = startblock * priv->pagesize; + buf[1] = offset & 0xff; + buf[0] = (offset >> 8) & 0xff; + + while (I2C_WRITE(priv->dev, buf, 2) < 0) + { + usleep(1000); + } + I2C_WRITE(priv->dev, buf, priv->pagesize + 2); + } + + return OK; +} + +/************************************************************************************ + * Name: at24c_erase + ************************************************************************************/ + +static int at24c_erase(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks) +{ + /* EEprom need not erase */ + + return (int)nblocks; +} + +/************************************************************************************ + * Name: at24c_bread + ************************************************************************************/ + +static ssize_t at24c_bread(FAR struct mtd_dev_s *dev, off_t startblock, + size_t nblocks, FAR uint8_t *buffer) +{ + FAR struct at24c_dev_s *priv = (FAR struct at24c_dev_s *)dev; + size_t blocksleft; + +#if CONFIG_AT24XX_MTD_BLOCKSIZE > AT24XX_PAGESIZE + startblock *= (CONFIG_AT24XX_MTD_BLOCKSIZE / AT24XX_PAGESIZE); + nblocks *= (CONFIG_AT24XX_MTD_BLOCKSIZE / AT24XX_PAGESIZE); +#endif + blocksleft = nblocks; + + fvdbg("startblock: %08lx nblocks: %d\n", (long)startblock, (int)nblocks); + + if (startblock >= priv->npages) + { + return 0; + } + + if (startblock + nblocks > priv->npages) + { + nblocks = priv->npages - startblock; + } + + I2C_SETADDRESS(priv->dev,priv->addr,7); + I2C_SETFREQUENCY(priv->dev,100000); + + while (blocksleft-- > 0) + { + uint16_t offset = startblock * priv->pagesize; + uint8_t buf[2]; + buf[1] = offset & 0xff; + buf[0] = (offset >> 8) & 0xff; + + while (I2C_WRITE(priv->dev, buf, 2) < 0) + { + fvdbg("wait\n"); + usleep(1000); + } + + I2C_READ(priv->dev, buffer, priv->pagesize); + startblock++; + buffer += priv->pagesize; + } + +#if CONFIG_AT24XX_MTD_BLOCKSIZE > AT24XX_PAGESIZE + return nblocks / (CONFIG_AT24XX_MTD_BLOCKSIZE / AT24XX_PAGESIZE); +#else + return nblocks; +#endif +} + +/************************************************************************************ + * Name: at24c_bwrite + * + * Operates on MTD block's and translates to FLASH pages + * + ************************************************************************************/ + +static ssize_t at24c_bwrite(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks, + FAR const uint8_t *buffer) +{ + FAR struct at24c_dev_s *priv = (FAR struct at24c_dev_s *)dev; + size_t blocksleft; + uint8_t buf[AT24XX_PAGESIZE+2]; + +#if CONFIG_AT24XX_MTD_BLOCKSIZE > AT24XX_PAGESIZE + startblock *= (CONFIG_AT24XX_MTD_BLOCKSIZE / AT24XX_PAGESIZE); + nblocks *= (CONFIG_AT24XX_MTD_BLOCKSIZE / AT24XX_PAGESIZE); +#endif + blocksleft = nblocks; + + if (startblock >= priv->npages) + { + return 0; + } + + if (startblock + nblocks > priv->npages) + { + nblocks = priv->npages - startblock; + } + + fvdbg("startblock: %08lx nblocks: %d\n", (long)startblock, (int)nblocks); + I2C_SETADDRESS(priv->dev, priv->addr, 7); + I2C_SETFREQUENCY(priv->dev, 100000); + + while (blocksleft-- > 0) + { + uint16_t offset = startblock * priv->pagesize; + while (I2C_WRITE(priv->dev, (uint8_t *)&offset, 2) < 0) + { + fvdbg("wait\n"); + usleep(1000); + } + + buf[1] = offset & 0xff; + buf[0] = (offset >> 8) & 0xff; + memcpy(&buf[2], buffer, priv->pagesize); + + I2C_WRITE(priv->dev, buf, priv->pagesize + 2); + startblock++; + buffer += priv->pagesize; + } + +#if CONFIG_AT24XX_MTD_BLOCKSIZE > AT24XX_PAGESIZE + return nblocks / (CONFIG_AT24XX_MTD_BLOCKSIZE / AT24XX_PAGESIZE); +#else + return nblocks; +#endif +} + +/************************************************************************************ + * Name: at24c_ioctl + ************************************************************************************/ + +static int at24c_ioctl(FAR struct mtd_dev_s *dev, int cmd, unsigned long arg) +{ + FAR struct at24c_dev_s *priv = (FAR struct at24c_dev_s *)dev; + int ret = -EINVAL; /* Assume good command with bad parameters */ + + fvdbg("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) + { + /* Populate the geometry structure with information need to know + * the capacity and how to access the device. + * + * NOTE: that the device is treated as though it where just an array + * of fixed size blocks. That is most likely not true, but the client + * will expect the device logic to do whatever is necessary to make it + * appear so. + * + * blocksize: + * May be user defined. The block size for the at24XX devices may be + * larger than the page size in order to better support file systems. + * The read and write functions translate BLOCKS to pages for the + * small flash devices + * erasesize: + * It has to be at least as big as the blocksize, bigger serves no + * purpose. + * neraseblocks + * Note that the device size is in kilobits and must be scaled by + * 1024 / 8 + */ + +#if CONFIG_AT24XX_MTD_BLOCKSIZE > AT24XX_PAGESIZE + geo->blocksize = CONFIG_AT24XX_MTD_BLOCKSIZE; + geo->erasesize = CONFIG_AT24XX_MTD_BLOCKSIZE; + geo->neraseblocks = (CONFIG_AT24XX_SIZE * 1024 / 8) / CONFIG_AT24XX_MTD_BLOCKSIZE; +#else + geo->blocksize = priv->pagesize; + geo->erasesize = priv->pagesize; + geo->neraseblocks = priv->npages; +#endif + ret = OK; + + fvdbg("blocksize: %d erasesize: %d neraseblocks: %d\n", + geo->blocksize, geo->erasesize, geo->neraseblocks); + } + } + break; + + case MTDIOC_BULKERASE: + ret=at24c_eraseall(priv); + break; + + case MTDIOC_XIPBASE: + default: + ret = -ENOTTY; /* Bad command */ + break; + } + + return ret; +} + +/************************************************************************************ + * Public Functions + ************************************************************************************/ + +/************************************************************************************ + * Name: at24c_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 *at24c_initialize(FAR struct i2c_dev_s *dev) +{ + FAR struct at24c_dev_s *priv; + + fvdbg("dev: %p\n", dev); + + /* Allocate a state structure (we allocate the structure instead of using + * a fixed, static allocation so that we can handle multiple FLASH devices. + * The current implementation would handle only one FLASH part per I2C + * device (only because of the SPIDEV_FLASH definition) and so would have + * to be extended to handle multiple FLASH parts on the same I2C bus. + */ + + priv = &g_at24c; + if (priv) + { + /* Initialize the allocated structure */ + + priv->addr = CONFIG_AT24XX_ADDR; + priv->pagesize = AT24XX_PAGESIZE; + priv->npages = AT24XX_NPAGES; + + priv->mtd.erase = at24c_erase; + priv->mtd.bread = at24c_bread; + priv->mtd.bwrite = at24c_bwrite; + priv->mtd.ioctl = at24c_ioctl; + priv->dev = dev; + } + + /* Return the implementation-specific state structure as the MTD device */ + + fvdbg("Return %p\n", priv); + return (FAR struct mtd_dev_s *)priv; +} + +#endif diff --git a/nuttx/drivers/mtd/at45db.c b/nuttx/drivers/mtd/at45db.c new file mode 100644 index 000000000..f3c0c72c1 --- /dev/null +++ b/nuttx/drivers/mtd/at45db.c @@ -0,0 +1,899 @@ +/************************************************************************************ + * drivers/mtd/at45db.c + * Driver for SPI-based AT45DB161D (16Mbit) + * + * Copyright (C) 2010-2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <gnutt@nuttx.org> + * + * 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. + * + ************************************************************************************/ + +/* Ordering Code Detail: + * + * AT 45DB 16 1 D – SS U + * | | | | | | `- Device grade + * | | | | | `- Package Option + * | | | | `- Device revision + * | | | `- Interface: 1=serial + * | | `- Capacity: 16=16Mbit + * | `- Product family + * `- Atmel designator + */ + +/************************************************************************************ + * Included Files + ************************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/types.h> +#include <stdint.h> +#include <stdbool.h> +#include <stdlib.h> +#include <errno.h> +#include <debug.h> + +#include <nuttx/kmalloc.h> +#include <nuttx/arch.h> +#include <nuttx/fs/ioctl.h> +#include <nuttx/spi.h> +#include <nuttx/mtd.h> + +/************************************************************************************ + * Pre-processor Definitions + ************************************************************************************/ + +/* Configuration ********************************************************************/ +/* CONFIG_AT45DB_PREWAIT enables higher performance write logic: We leave the chip + * busy after write and erase operations. This improves write and erase performance + * because we do not have to wait as long between transactions (other processing can + * occur while the chip is busy) but means that the chip must stay powered: + */ + +#if defined(CONFIG_AT45DB_PWRSAVE) && defined(CONFIG_AT45DB_PREWAIT) +# error "Both CONFIG_AT45DB_PWRSAVE and CONFIG_AT45DB_PREWAIT are defined" +#endif + +/* If the user has provided no frequency, use 1MHz */ + +#ifndef CONFIG_AT45DB_FREQUENCY +# define CONFIG_AT45DB_FREQUENCY 1000000 +#endif + +/* SPI Commands *********************************************************************/ + +/* Read commands */ + +#define AT45DB_RDMN 0xd2 /* Main Memory Page Read */ +#define AT45DB_RDARRY 0xe8 /* Continuous Array Read (Legacy Command) */ +#define AT45DB_RDARRAYLF 0x03 /* Continuous Array Read (Low Frequency) */ +#define AT45DB_RDARRAYHF 0x0b /* Continuous Array Read (High Frequency) */ +#define AT45DB_RDBF1LF 0xd1 /* Buffer 1 Read (Low Frequency) */ +#define AT45DB_RDBF2LF 0xd3 /* Buffer 2 Read (Low Frequency) */ +#define AT45DB_RDBF1 0xd4 /* Buffer 1 Read */ +#define AT45DB_RDBF2 0xd6 /* Buffer 2 Read */ + +/* Program and Erase Commands */ + +#define AT45DB_WRBF1 0x84 /* Buffer 1 Write */ +#define AT45DB_WRBF2 0x87 /* Buffer 2 Write */ +#define AT45DB_BF1TOMNE 0x83 /* Buffer 1 to Main Memory Page Program with Built-in Erase */ +#define AT45DB_BF2TOMNE 0x86 /* Buffer 2 to Main Memory Page Program with Built-in Erase */ +#define AT45DB_BF1TOMN 0x88 /* Buffer 1 to Main Memory Page Program without Built-in Erase */ +#define AT45DB_BF2TOMN 0x89 /* Buffer 2 to Main Memory Page Program without Built-in Erase */ +#define AT45DB_PGERASE 0x81 /* Page Erase */ +#define AT45DB_BLKERASE 0x50 /* Block Erase */ +#define AT45DB_SECTERASE 0x7c /* Sector Erase */ +#define AT45DB_CHIPERASE1 0xc7 /* Chip Erase - byte 1 */ +# define AT45DB_CHIPERASE2 0x94 /* Chip Erase - byte 2 */ +# define AT45DB_CHIPERASE3 0x80 /* Chip Erase - byte 3 */ +# define AT45DB_CHIPERASE4 0x9a /* Chip Erase - byte 4 */ +#define AT45DB_MNTHRUBF1 0x82 /* Main Memory Page Program Through Buffer 1 */ +#define AT45DB_MNTHRUBF2 0x85 /* Main Memory Page Program Through Buffer 2 */ + +/* Protection and Security Commands */ + +#define AT45DB_ENABPROT1 0x3d /* Enable Sector Protection - byte 1 */ +# define AT45DB_ENABPROT2 0x2a /* Enable Sector Protection - byte 2 */ +# define AT45DB_ENABPROT3 0x7f /* Enable Sector Protection - byte 3 */ +# define AT45DB_ENABPROT4 0xa9 /* Enable Sector Protection - byte 4 */ +#define AT45DB_DISABPROT1 0x3d /* Disable Sector Protection - byte 1 */ +# define AT45DB_DISABPROT2 0x2a /* Disable Sector Protection - byte 2 */ +# define AT45DB_DISABPROT3 0x7f /* Disable Sector Protection - byte 3 */ +# define AT45DB_DISABPROT4 0x9a /* Disable Sector Protection - byte 4 */ +#define AT45DB_ERASEPROT1 0x3d /* Erase Sector Protection Register - byte 1 */ +# define AT45DB_ERASEPROT2 0x2a /* Erase Sector Protection Register - byte 2 */ +# define AT45DB_ERASEPROT3 0x7f /* Erase Sector Protection Register - byte 3 */ +# define AT45DB_ERASEPROT4 0xcf /* Erase Sector Protection Register - byte 4 */ +#define AT45DB_PROGPROT1 0x3d /* Program Sector Protection Register - byte 1 */ +# define AT45DB_PROGPROT2 0x2a /* Program Sector Protection Register - byte 2 */ +# define AT45DB_PROGPROT3 0x7f /* Program Sector Protection Register - byte 3 */ +# define AT45DB_PROGPROT4 0xfc /* Program Sector Protection Register - byte 4 */ +#define AT45DB_RDPROT 0x32 /* Read Sector Protection Register */ +#define AT45DB_LOCKDOWN1 0x3d /* Sector Lockdown - byte 1 */ +# define AT45DB_LOCKDOWN2 0x2a /* Sector Lockdown - byte 2 */ +# define AT45DB_LOCKDOWN3 0x7f /* Sector Lockdown - byte 3 */ +# define AT45DB_LOCKDOWN4 0x30 /* Sector Lockdown - byte 4 */ +#define AT45DB_RDLOCKDOWN 0x35 /* Read Sector Lockdown Register */ +#define AT45DB_PROGSEC1 0x9b /* Program Security Register - byte 1 */ +# define AT45DB_PROGSEC2 0x00 /* Program Security Register - byte 2 */ +# define AT45DB_PROGSEC3 0x00 /* Program Security Register - byte 3 */ +# define AT45DB_PROGSEC4 0x00 /* Program Security Register - byte 4 */ +#define AT45DB_RDSEC 0x77 /* Read Security Register */ + +/* Additional commands */ + +#define AT45DB_MNTOBF1XFR 0x53 /* Main Memory Page to Buffer 1 Transfer */ +#define AT45DB_MNTOBF2XFR 0x55 /* Main Memory Page to Buffer 2 Transfer */ +#define AT45DB_MNBF1CMP 0x60 /* Main Memory Page to Buffer 1 Compare */ +#define AT45DB_MNBF2CMP 0x61 /* Main Memory Page to Buffer 2 Compare */ +#define AT45DB_AUTOWRBF1 0x58 /* Auto Page Rewrite through Buffer 1 */ +#define AT45DB_AUTOWRBF2 0x59 /* Auto Page Rewrite through Buffer 2 */ +#define AT45DB_PWRDOWN 0xb9 /* Deep Power-down */ +#define AT45DB_RESUME 0xab /* Resume from Deep Power-down */ +#define AT45DB_RDSR 0xd7 /* Status Register Read */ +#define AT45DB_RDDEVID 0x9f /* Manufacturer and Device ID Read */ + +#define AT45DB_MANUFACTURER 0x1f /* Manufacturer ID: Atmel */ +#define AT45DB_DEVID1_CAPMSK 0x1f /* Bits 0-4: Capacity */ +#define AT45DB_DEVID1_1MBIT 0x02 /* xxx0 0010 = 1Mbit AT45DB011 */ +#define AT45DB_DEVID1_2MBIT 0x03 /* xxx0 0012 = 2Mbit AT45DB021 */ +#define AT45DB_DEVID1_4MBIT 0x04 /* xxx0 0100 = 4Mbit AT45DB041 */ +#define AT45DB_DEVID1_8MBIT 0x05 /* xxx0 0101 = 8Mbit AT45DB081 */ +#define AT45DB_DEVID1_16MBIT 0x06 /* xxx0 0110 = 16Mbit AT45DB161 */ +#define AT45DB_DEVID1_32MBIT 0x07 /* xxx0 0111 = 32Mbit AT45DB321 */ +#define AT45DB_DEVID1_64MBIT 0x08 /* xxx0 1000 = 32Mbit AT45DB641 */ +#define AT45DB_DEVID1_FAMMSK 0xe0 /* Bits 5-7: Family */ +#define AT45DB_DEVID1_DFLASH 0x20 /* 001x xxxx = Dataflash */ +#define AT45DB_DEVID1_AT26DF 0x40 /* 010x xxxx = AT26DFxxx series (Not supported) */ +#define AT45DB_DEVID2_VERMSK 0x1f /* Bits 0-4: MLC mask */ +#define AT45DB_DEVID2_MLCMSK 0xe0 /* Bits 5-7: MLC mask */ + +/* Status register bit definitions */ + +#define AT45DB_SR_RDY (1 << 7) /* Bit 7: RDY/ Not BUSY */ +#define AT45DB_SR_COMP (1 << 6) /* Bit 6: COMP */ +#define AT45DB_SR_PROTECT (1 << 1) /* Bit 1: PROTECT */ +#define AT45DB_SR_PGSIZE (1 << 0) /* Bit 0: PAGE_SIZE */ + +/* 1 Block = 16 pages; 1 sector = 256 pages */ + +#define PG_PER_BLOCK (16) +#define PG_PER_SECTOR (256) + +/************************************************************************************ + * 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 at45db_dev_s. + */ + +struct at45db_dev_s +{ + struct mtd_dev_s mtd; /* MTD interface */ + FAR struct spi_dev_s *spi; /* Saved SPI interface instance */ + uint8_t pageshift; /* log2 of the page size (eg. 1 << 9 = 512) */ + uint32_t npages; /* Number of pages in the device */ +}; + +/************************************************************************************ + * Private Function Prototypes + ************************************************************************************/ + +/* Lock and per-transaction configuration */ + +static void at45db_lock(struct at45db_dev_s *priv); +static inline void at45db_unlock(struct at45db_dev_s *priv); + +/* Power management */ + +#ifdef CONFIG_AT45DB_PWRSAVE +static void at45db_pwrdown(struct at45db_dev_s *priv); +static void at45db_resume(struct at45db_dev_s *priv); +#else +# define at45db_pwrdown(priv) +# define at45db_resume(priv) +#endif + +/* Low-level AT45DB Helpers */ + +static inline int at45db_rdid(struct at45db_dev_s *priv); +static inline uint8_t at45db_rdsr(struct at45db_dev_s *priv); +static uint8_t at45db_waitbusy(struct at45db_dev_s *priv); +static inline void at45db_pgerase(struct at45db_dev_s *priv, off_t offset); +static inline int at32db_chiperase(struct at45db_dev_s *priv); +static inline void at45db_pgwrite(struct at45db_dev_s *priv, FAR const uint8_t *buffer, + off_t offset); + +/* MTD driver methods */ + +static int at45db_erase(FAR struct mtd_dev_s *mtd, off_t startblock, size_t nblocks); +static ssize_t at45db_bread(FAR struct mtd_dev_s *mtd, off_t startblock, + size_t nblocks, FAR uint8_t *buf); +static ssize_t at45db_bwrite(FAR struct mtd_dev_s *mtd, off_t startblock, + size_t nblocks, FAR const uint8_t *buf); +static ssize_t at45db_read(FAR struct mtd_dev_s *mtd, off_t offset, size_t nbytes, + FAR uint8_t *buffer); +static int at45db_ioctl(FAR struct mtd_dev_s *mtd, int cmd, unsigned long arg); + +/************************************************************************************ + * Private Data + ************************************************************************************/ + +/* Chip erase sequence */ + +#define CHIP_ERASE_SIZE 4 +static const uint8_t g_chiperase[CHIP_ERASE_SIZE] = {0xc7, 0x94, 0x80, 0x9a}; + +/* Sequence to program the device to binary page sizes{256, 512, 1024} */ + +#define BINPGSIZE_SIZE 4 +static const uint8_t g_binpgsize[BINPGSIZE_SIZE] = {0x3d, 0x2a, 0x80, 0xa6}; + +/************************************************************************************ + * Private Functions + ************************************************************************************/ + +/************************************************************************************ + * Name: at45db_lock + ************************************************************************************/ + +static void at45db_lock(struct at45db_dev_s *priv) +{ + /* On SPI busses where there are multiple devices, it will be necessary to + * lock SPI to have exclusive access to the busses for a sequence of + * transfers. The bus should be locked before the chip is selected. + * + * This is a blocking call and will not return until we have exclusiv access to + * the SPI buss. We will retain that exclusive access until the bus is unlocked. + */ + + (void)SPI_LOCK(priv->spi, true); + + /* After locking the SPI bus, the we also need call the setfrequency, setbits, and + * setmode methods to make sure that the SPI is properly configured for the device. + * If the SPI buss is being shared, then it may have been left in an incompatible + * state. + */ + + SPI_SETMODE(priv->spi, SPIDEV_MODE0); + SPI_SETBITS(priv->spi, 8); + (void)SPI_SETFREQUENCY(priv->spi, CONFIG_AT45DB_FREQUENCY); +} + +/************************************************************************************ + * Name: at45db_unlock + ************************************************************************************/ + +static inline void at45db_unlock(struct at45db_dev_s *priv) +{ + (void)SPI_LOCK(priv->spi, false); +} + +/************************************************************************************ + * Name: at45db_pwrdown + ************************************************************************************/ + +#ifdef CONFIG_AT45DB_PWRSAVE +static void at45db_pwrdown(struct at45db_dev_s *priv) +{ + SPI_SELECT(priv->spi, SPIDEV_FLASH, true); + SPI_SEND(priv->spi, AT45DB_PWRDOWN); + SPI_SELECT(priv->spi, SPIDEV_FLASH, false); +} +#endif + +/************************************************************************************ + * Name: at45db_resume + ************************************************************************************/ + +#ifdef CONFIG_AT45DB_PWRSAVE +static void at45db_resume(struct at45db_dev_s *priv) +{ + SPI_SELECT(priv->spi, SPIDEV_FLASH, true); + SPI_SEND(priv->spi, AT45DB_RESUME); + SPI_SELECT(priv->spi, SPIDEV_FLASH, false); + up_udelay(50); +} +#endif + +/************************************************************************************ + * Name: at45db_rdid + ************************************************************************************/ + +static inline int at45db_rdid(struct at45db_dev_s *priv) +{ + uint8_t capacity; + uint8_t devid[3]; + + fvdbg("priv: %p\n", priv); + + /* Configure the bus, and select this FLASH part. (The caller should alread have + * loced the bus for exclusive access) + */ + + SPI_SELECT(priv->spi, SPIDEV_FLASH, true); + + /* Send the " Manufacturer and Device ID Read" command and read the next three + * ID bytes from the FLASH. + */ + + (void)SPI_SEND(priv->spi, AT45DB_RDDEVID); + SPI_RECVBLOCK(priv->spi, devid, 3); + + /* Deselect the FLASH */ + + SPI_SELECT(priv->spi, SPIDEV_FLASH, false); + + fvdbg("manufacturer: %02x devid1: %02x devid2: %02x\n", + devid[0], devid[1], devid[2]); + + /* Check for a valid manufacturer and memory family */ + + if (devid[0] == AT45DB_MANUFACTURER && + (devid[1] & AT45DB_DEVID1_FAMMSK) == AT45DB_DEVID1_DFLASH) + { + /* Okay.. is it a FLASH capacity that we understand? */ + + capacity = devid[1] & AT45DB_DEVID1_CAPMSK; + switch (capacity) + { + case AT45DB_DEVID1_1MBIT: + /* Save the FLASH geometry for the 16Mbit AT45DB011 */ + + priv->pageshift = 8; /* Page size = 256 bytes */ + priv->npages = 512; /* 512 pages */ + return OK; + + case AT45DB_DEVID1_2MBIT: + /* Save the FLASH geometry for the 16Mbit AT45DB021 */ + + priv->pageshift = 8; /* Page size = 256/264 bytes */ + priv->npages = 1024; /* 1024 pages */ + return OK; + + case AT45DB_DEVID1_4MBIT: + /* Save the FLASH geometry for the 16Mbit AT45DB041 */ + + priv->pageshift = 8; /* Page size = 256/264 bytes */ + priv->npages = 2048; /* 2048 pages */ + return OK; + + case AT45DB_DEVID1_8MBIT: + /* Save the FLASH geometry for the 16Mbit AT45DB081 */ + + priv->pageshift = 8; /* Page size = 256/264 bytes */ + priv->npages = 4096; /* 4096 pages */ + return OK; + + case AT45DB_DEVID1_16MBIT: + /* Save the FLASH geometry for the 16Mbit AT45DB161 */ + + priv->pageshift = 9; /* Page size = 512/528 bytes */ + priv->npages = 4096; /* 4096 pages */ + return OK; + + case AT45DB_DEVID1_32MBIT: + /* Save the FLASH geometry for the 16Mbit AT45DB321 */ + + priv->pageshift = 9; /* Page size = 512/528 bytes */ + priv->npages = 8192; /* 8192 pages */ + return OK; + + case AT45DB_DEVID1_64MBIT: + /* Save the FLASH geometry for the 16Mbit AT45DB321 */ + + priv->pageshift = 10; /* Page size = 1024/1056 bytes */ + priv->npages = 8192; /* 8192 pages */ + return OK; + + default: + return -ENODEV; + } + } + + return -ENODEV; +} + +/************************************************************************************ + * Name: at45db_rdsr + ************************************************************************************/ + +static inline uint8_t at45db_rdsr(struct at45db_dev_s *priv) +{ + uint8_t retval; + + SPI_SELECT(priv->spi, SPIDEV_FLASH, true); + SPI_SEND(priv->spi, AT45DB_RDSR); + retval = SPI_SEND(priv->spi, 0xff); + SPI_SELECT(priv->spi, SPIDEV_FLASH, false); + return retval; +} + +/************************************************************************************ + * Name: at45db_waitbusy + ************************************************************************************/ + +static uint8_t at45db_waitbusy(struct at45db_dev_s *priv) +{ + uint8_t sr; + + /* Poll the device, waiting for it to report that it is ready */ + + do + { + up_udelay(10); + sr = (uint8_t)at45db_rdsr(priv); + } + while ((sr & AT45DB_SR_RDY) == 0); + return sr; +} + +/************************************************************************************ + * Name: at45db_pgerase + ************************************************************************************/ + +static inline void at45db_pgerase(struct at45db_dev_s *priv, off_t sector) +{ + uint8_t erasecmd[4]; + off_t offset = sector << priv->pageshift; + + fvdbg("sector: %08lx\n", (long)sector); + + /* Higher performance write logic: We leave the chip busy after write and erase + * operations. This improves write and erase performance because we do not have + * to wait as long between transactions (other processing can occur while the chip + * is busy) but means that the chip must stay powered and that we must check if + * the chip is still busy on each entry point. + */ + +#ifdef CONFIG_AT45DB_PREWAIT + at45db_waitbusy(priv); +#endif + + /* "The Page Erase command can be used to individually erase any page in the main + * memory array allowing the Buffer to Main Memory Page Program to be utilized at a + * later time. ... To perform a page erase in the binary page size ..., the + * opcode 81H must be loaded into the device, followed by three address bytes + * ... When a low-to-high transition occurs on the CS pin, the part will erase the + * selected page (the erased state is a logical 1). ... the status register and the + * RDY/BUSY pin will indicate that the part is busy." + */ + + erasecmd[0] = AT45DB_PGERASE; /* Page erase command */ + erasecmd[1] = (offset >> 16) & 0xff; /* 24-bit offset MS bytes */ + erasecmd[2] = (offset >> 8) & 0xff; /* 24-bit offset middle bytes */ + erasecmd[3] = offset & 0xff; /* 24-bit offset LS bytes */ + + /* Erase the page */ + + SPI_SELECT(priv->spi, SPIDEV_FLASH, true); + SPI_SNDBLOCK(priv->spi, erasecmd, 4); + SPI_SELECT(priv->spi, SPIDEV_FLASH, false); + + /* Wait for any erase to complete if we are not trying to improve write + * performance. (see comments above). + */ + +#ifndef CONFIG_AT45DB_PREWAIT + at45db_waitbusy(priv); +#endif + fvdbg("Erased\n"); +} + +/************************************************************************************ + * Name: at32db_chiperase + ************************************************************************************/ + +static inline int at32db_chiperase(struct at45db_dev_s *priv) +{ + fvdbg("priv: %p\n", priv); + + /* Higher performance write logic: We leave the chip busy after write and erase + * operations. This improves write and erase performance because we do not have + * to wait as long between transactions (other processing can occur while the chip + * is busy) but means that the chip must stay powered and that we must check if + * the chip is still busy on each entry point. + */ + +#ifdef CONFIG_AT45DB_PREWAIT + at45db_waitbusy(priv); +#endif + + /* "The entire main memory can be erased at one time by using the Chip Erase + * command. To execute the Chip Erase command, a 4-byte command sequence C7H, 94H, + * 80H and 9AH must be clocked into the device. ... After the last bit of the opcode + * sequence has been clocked in, the CS pin can be deasserted to start the erase + * process. ... the Status Register will indicate that the device is busy. The Chip + * Erase command will not affect sectors that are protected or locked down... + */ + + SPI_SELECT(priv->spi, SPIDEV_FLASH, true); + SPI_SNDBLOCK(priv->spi, g_chiperase, CHIP_ERASE_SIZE); + SPI_SELECT(priv->spi, SPIDEV_FLASH, false); + + /* Wait for any erase to complete if we are not trying to improve write + * performance. (see comments above). + */ + +#ifndef CONFIG_AT45DB_PREWAIT + at45db_waitbusy(priv); +#endif + return OK; +} + +/************************************************************************************ + * Name: at45db_pgwrite + ************************************************************************************/ + +static inline void at45db_pgwrite(struct at45db_dev_s *priv, FAR const uint8_t *buffer, + off_t page) +{ + uint8_t wrcmd [4]; + off_t offset = page << priv->pageshift; + + fvdbg("page: %08lx offset: %08lx\n", (long)page, (long)offset); + + /* We assume that sectors are not write protected */ + + wrcmd[0] = AT45DB_MNTHRUBF1; /* To main memory through buffer 1 */ + wrcmd[1] = (offset >> 16) & 0xff; /* 24-bit address MS byte */ + wrcmd[2] = (offset >> 8) & 0xff; /* 24-bit address middle byte */ + wrcmd[3] = offset & 0xff; /* 24-bit address LS byte */ + + /* Higher performance write logic: We leave the chip busy after write and erase + * operations. This improves write and erase performance because we do not have + * to wait as long between transactions (other processing can occur while the chip + * is busy) but means that the chip must stay powered and that we must check if + * the chip is still busy on each entry point. + */ + +#ifdef CONFIG_AT45DB_PREWAIT + at45db_waitbusy(priv); +#endif + + SPI_SELECT(priv->spi, SPIDEV_FLASH, true); + SPI_SNDBLOCK(priv->spi, wrcmd, 4); + SPI_SNDBLOCK(priv->spi, buffer, 1 << priv->pageshift); + SPI_SELECT(priv->spi, SPIDEV_FLASH, false); + + /* Wait for any erase to complete if we are not trying to improve write + * performance. (see comments above). + */ + +#ifndef CONFIG_AT45DB_PREWAIT + at45db_waitbusy(priv); +#endif + fvdbg("Written\n"); +} + +/************************************************************************************ + * Name: at45db_erase + ************************************************************************************/ + +static int at45db_erase(FAR struct mtd_dev_s *mtd, off_t startblock, size_t nblocks) +{ + FAR struct at45db_dev_s *priv = (FAR struct at45db_dev_s *)mtd; + size_t pgsleft = nblocks; + + fvdbg("startblock: %08lx nblocks: %d\n", (long)startblock, (int)nblocks); + + /* Take the lock so that we have exclusive access to the bus, then power up the + * FLASH device. + */ + + at45db_lock(priv); + at45db_resume(priv); + + /* Then erase each page */ + + while (pgsleft-- > 0) + { + /* Erase each sector */ + + at45db_pgerase(priv, startblock); + startblock++; + } + + at45db_pwrdown(priv); + at45db_unlock(priv); + return (int)nblocks; +} + +/************************************************************************************ + * Name: at45db_bread + ************************************************************************************/ + +static ssize_t at45db_bread(FAR struct mtd_dev_s *mtd, off_t startblock, size_t nblocks, + FAR uint8_t *buffer) +{ + FAR struct at45db_dev_s *priv = (FAR struct at45db_dev_s *)mtd; + ssize_t nbytes; + + /* On this device, we can handle the block read just like the byte-oriented read */ + + nbytes = at45db_read(mtd, startblock << priv->pageshift, nblocks << priv->pageshift, buffer); + if (nbytes > 0) + { + return nbytes >> priv->pageshift; + } + return nbytes; +} + +/************************************************************************************ + * Name: at45db_bwrite + ************************************************************************************/ + +static ssize_t at45db_bwrite(FAR struct mtd_dev_s *mtd, off_t startblock, size_t nblocks, + FAR const uint8_t *buffer) +{ + FAR struct at45db_dev_s *priv = (FAR struct at45db_dev_s *)mtd; + size_t pgsleft = nblocks; + + fvdbg("startblock: %08lx nblocks: %d\n", (long)startblock, (int)nblocks); + + /* Take the lock so that we have exclusive access to the bus, then power up the + * FLASH device. + */ + + at45db_lock(priv); + at45db_resume(priv); + + /* Write each page to FLASH */ + + while (pgsleft-- > 0) + { + at45db_pgwrite(priv, buffer, startblock); + startblock++; + } + + at45db_pwrdown(priv); + at45db_unlock(priv); + + return nblocks; +} + +/************************************************************************************ + * Name: at45db_read + ************************************************************************************/ + +static ssize_t at45db_read(FAR struct mtd_dev_s *mtd, off_t offset, size_t nbytes, + FAR uint8_t *buffer) +{ + FAR struct at45db_dev_s *priv = (FAR struct at45db_dev_s *)mtd; + uint8_t rdcmd [5]; + + fvdbg("offset: %08lx nbytes: %d\n", (long)offset, (int)nbytes); + + /* Set up for the read */ + + rdcmd[0] = AT45DB_RDARRAYHF; /* FAST_READ is safe at all supported SPI speeds. */ + rdcmd[1] = (offset >> 16) & 0xff; /* 24-bit address upper byte */ + rdcmd[2] = (offset >> 8) & 0xff; /* 24-bit address middle byte */ + rdcmd[3] = offset & 0xff; /* 24-bit address least significant byte */ + rdcmd[4] = 0; /* Dummy byte */ + + /* Take the lock so that we have exclusive access to the bus, then power up the + * FLASH device. + */ + + at45db_lock(priv); + at45db_resume(priv); + + /* Higher performance write logic: We leave the chip busy after write and erase + * operations. This improves write and erase performance because we do not have + * to wait as long between transactions (other processing can occur while the chip + * is busy) but means that the chip must stay powered and that we must check if + * the chip is still busy on each entry point. + */ + +#ifdef CONFIG_AT45DB_PREWAIT + at45db_waitbusy(priv); +#endif + + /* Perform the read */ + + SPI_SELECT(priv->spi, SPIDEV_FLASH, true); + SPI_SNDBLOCK(priv->spi, rdcmd, 5); + SPI_RECVBLOCK(priv->spi, buffer, nbytes); + SPI_SELECT(priv->spi, SPIDEV_FLASH, false); + + at45db_pwrdown(priv); + at45db_unlock(priv); + + fvdbg("return nbytes: %d\n", (int)nbytes); + return nbytes; +} + +/************************************************************************************ + * Name: at45db_ioctl + ************************************************************************************/ + +static int at45db_ioctl(FAR struct mtd_dev_s *mtd, int cmd, unsigned long arg) +{ + FAR struct at45db_dev_s *priv = (FAR struct at45db_dev_s *)mtd; + int ret = -EINVAL; /* Assume good command with bad parameters */ + + fvdbg("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) + { + /* Populate the geometry structure with information need to know + * the capacity and how to access the device. + * + * NOTE: that the device is treated as though it where just an array + * of fixed size blocks. That is most likely not true, but the client + * will expect the device logic to do whatever is necessary to make it + * appear so. + */ + + geo->blocksize = (1 << priv->pageshift); + geo->erasesize = geo->blocksize; + geo->neraseblocks = priv->npages; + ret = OK; + + fvdbg("blocksize: %d erasesize: %d neraseblocks: %d\n", + geo->blocksize, geo->erasesize, geo->neraseblocks); + } + } + break; + + case MTDIOC_BULKERASE: + { + /* Take the lock so that we have exclusive access to the bus, then + * power up the FLASH device. + */ + + at45db_lock(priv); + at45db_resume(priv); + + /* Erase the entire device */ + + ret = at32db_chiperase(priv); + at45db_pwrdown(priv); + at45db_unlock(priv); + } + break; + + case MTDIOC_XIPBASE: + default: + ret = -ENOTTY; /* Bad command */ + break; + } + + fvdbg("return %d\n", ret); + return ret; +} + +/************************************************************************************ + * Public Functions + ************************************************************************************/ + +/************************************************************************************ + * Name: at45db_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 *at45db_initialize(FAR struct spi_dev_s *spi) +{ + FAR struct at45db_dev_s *priv; + uint8_t sr; + int ret; + + fvdbg("spi: %p\n", spi); + + /* Allocate a state structure (we allocate the structure instead of using + * a fixed, static allocation so that we can handle multiple FLASH devices. + * The current implementation would handle only one FLASH part per SPI + * device (only because of the SPIDEV_FLASH definition) and so would have + * to be extended to handle multiple FLASH parts on the same SPI bus. + */ + + priv = (FAR struct at45db_dev_s *)kmalloc(sizeof(struct at45db_dev_s)); + if (priv) + { + /* Initialize the allocated structure */ + + priv->mtd.erase = at45db_erase; + priv->mtd.bread = at45db_bread; + priv->mtd.bwrite = at45db_bwrite; + priv->mtd.read = at45db_read; + priv->mtd.ioctl = at45db_ioctl; + priv->spi = spi; + + /* Deselect the FLASH */ + + SPI_SELECT(spi, SPIDEV_FLASH, false); + + /* Lock and configure the SPI bus. */ + + at45db_lock(priv); + at45db_resume(priv); + + /* Identify the FLASH chip and get its capacity */ + + ret = at45db_rdid(priv); + if (ret != OK) + { + /* Unrecognized! Discard all of that work we just did and return NULL */ + + fdbg("Unrecognized\n"); + goto errout; + } + + /* Get the value of the status register (as soon as the device is ready) */ + + sr = at45db_waitbusy(priv); + + /* Check if the device is configured as 256, 512 or 1024 bytes-per-page device */ + + if ((sr & AT45DB_SR_PGSIZE) == 0) + { + /* No, re-program it for the binary page size. NOTE: A power cycle + * is required after the device has be re-programmed. + */ + + fdbg("Reprogramming page size\n"); + SPI_SELECT(priv->spi, SPIDEV_FLASH, true); + SPI_SNDBLOCK(priv->spi, g_binpgsize, BINPGSIZE_SIZE); + SPI_SELECT(priv->spi, SPIDEV_FLASH, false); + goto errout; + } + + /* Release the lock and power down the device */ + + at45db_pwrdown(priv); + at45db_unlock(priv); + } + + fvdbg("Return %p\n", priv); + return (FAR struct mtd_dev_s *)priv; + +/* On any failure, we need free memory allocations and release the lock that + * we hold on the SPI bus. On failures, assume that we cannot talk to the + * device to do any more. + */ + +errout: + at45db_unlock(priv); + kfree(priv); + return NULL; +} diff --git a/nuttx/drivers/mtd/flash_eraseall.c b/nuttx/drivers/mtd/flash_eraseall.c new file mode 100644 index 000000000..ce0cfe649 --- /dev/null +++ b/nuttx/drivers/mtd/flash_eraseall.c @@ -0,0 +1,117 @@ +/**************************************************************************** + * drivers/mtd/flash_eraseall.c + * + * Copyright (C) 2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <gnutt@nuttx.org> + * + * 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 <errno.h> +#include <debug.h> + +#include <nuttx/fs/fs.h> +#include <nuttx/fs/ioctl.h> +#include <nuttx/mtd.h> + +/**************************************************************************** + * Private Definitions + ****************************************************************************/ + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: flash_eraseall + * + * Description: + * Call a block driver with the MDIOC_BULKERASE ioctl command. This will + * cause the MTD driver to erase all of the flash. + * + ****************************************************************************/ + +int flash_eraseall(FAR const char *driver) +{ + FAR struct inode *inode; + FAR const struct block_operations *ops; + int ret; + + /* Open the block driver */ + + ret = open_blockdriver(driver ,0, &inode); + if (ret < 0) + { + fdbg("ERROR: Failed to open '%s': %d\n", driver, ret); + return ret; + } + + /* Get the block operations */ + + ops = inode->u.i_bops; + + /* Invoke the block driver ioctl method */ + + ret = -EPERM; + if (ops->ioctl) + { + ret = ops->ioctl(inode, MTDIOC_BULKERASE, 0); + if (ret < 0) + { + fdbg("ERROR: MTD ioctl(%04x) failed: %d\n", MTDIOC_BULKERASE, ret); + } + } + + /* Close the block driver */ + + close_blockdriver(inode); + return ret; +} diff --git a/nuttx/drivers/mtd/ftl.c b/nuttx/drivers/mtd/ftl.c new file mode 100644 index 000000000..cdb35aa5c --- /dev/null +++ b/nuttx/drivers/mtd/ftl.c @@ -0,0 +1,578 @@ +/**************************************************************************** + * drivers/mtd/ftl.c + * + * Copyright (C) 2009, 2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <gnutt@nuttx.org> + * + * 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 <sys/ioctl.h> +#include <stdint.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <debug.h> +#include <errno.h> + +#include <nuttx/kmalloc.h> +#include <nuttx/fs/fs.h> +#include <nuttx/fs/ioctl.h> +#include <nuttx/mtd.h> +#include <nuttx/rwbuffer.h> + +/**************************************************************************** + * Private Definitions + ****************************************************************************/ + +#if defined(CONFIG_FS_READAHEAD) || (defined(CONFIG_FS_WRITABLE) && defined(CONFIG_FS_WRITEBUFFER)) +# defined CONFIG_FTL_RWBUFFER 1 +#endif + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct ftl_struct_s +{ + FAR struct mtd_dev_s *mtd; /* Contained MTD interface */ + struct mtd_geometry_s geo; /* Device geometry */ +#ifdef CONFIG_FTL_RWBUFFER + struct rwbuffer_s rwb; /* Read-ahead/write buffer support */ +#endif + uint16_t blkper; /* R/W blocks per erase block */ +#ifdef CONFIG_FS_WRITABLE + FAR uint8_t *eblock; /* One, in-memory erase block */ +#endif +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static int ftl_open(FAR struct inode *inode); +static int ftl_close(FAR struct inode *inode); +static ssize_t ftl_reload(FAR void *priv, FAR uint8_t *buffer, + off_t startblock, size_t nblocks); +static ssize_t ftl_read(FAR struct inode *inode, unsigned char *buffer, + size_t start_sector, unsigned int nsectors); +#ifdef CONFIG_FS_WRITABLE +static ssize_t ftl_flush(FAR void *priv, FAR const uint8_t *buffer, + off_t startblock, size_t nblocks); +static ssize_t ftl_write(FAR struct inode *inode, const unsigned char *buffer, + size_t start_sector, unsigned int nsectors); +#endif +static int ftl_geometry(FAR struct inode *inode, struct geometry *geometry); +static int ftl_ioctl(FAR struct inode *inode, int cmd, unsigned long arg); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct block_operations g_bops = +{ + ftl_open, /* open */ + ftl_close, /* close */ + ftl_read, /* read */ +#ifdef CONFIG_FS_WRITABLE + ftl_write, /* write */ +#else + NULL, /* write */ +#endif + ftl_geometry, /* geometry */ + ftl_ioctl /* ioctl */ +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: ftl_open + * + * Description: Open the block device + * + ****************************************************************************/ + +static int ftl_open(FAR struct inode *inode) +{ + fvdbg("Entry\n"); + return OK; +} + +/**************************************************************************** + * Name: ftl_close + * + * Description: close the block device + * + ****************************************************************************/ + +static int ftl_close(FAR struct inode *inode) +{ + fvdbg("Entry\n"); + return OK; +} + +/**************************************************************************** + * Name: ftl_reload + * + * Description: Read the specified numer of sectors + * + ****************************************************************************/ + +static ssize_t ftl_reload(FAR void *priv, FAR uint8_t *buffer, + off_t startblock, size_t nblocks) +{ + struct ftl_struct_s *dev = (struct ftl_struct_s *)priv; + ssize_t nread; + + /* Read the full erase block into the buffer */ + + nread = MTD_BREAD(dev->mtd, startblock, nblocks, buffer); + if (nread != nblocks) + { + fdbg("Read %d blocks starting at block %d failed: %d\n", + nblocks, startblock, nread); + } + return nread; +} + +/**************************************************************************** + * Name: ftl_read + * + * Description: Read the specified numer of sectors + * + ****************************************************************************/ + +static ssize_t ftl_read(FAR struct inode *inode, unsigned char *buffer, + size_t start_sector, unsigned int nsectors) +{ + struct ftl_struct_s *dev; + + fvdbg("sector: %d nsectors: %d\n", start_sector, nsectors); + + DEBUGASSERT(inode && inode->i_private); + dev = (struct ftl_struct_s *)inode->i_private; +#ifdef CONFIG_FS_READAHEAD + return rwb_read(&dev->rwb, start_sector, nsectors, buffer); +#else + return ftl_reload(dev, buffer, start_sector, nsectors); +#endif +} + +/**************************************************************************** + * Name: ftl_flush + * + * Description: Write the specified number of sectors + * + ****************************************************************************/ + +#ifdef CONFIG_FS_WRITABLE +static ssize_t ftl_flush(FAR void *priv, FAR const uint8_t *buffer, + off_t startblock, size_t nblocks) +{ + struct ftl_struct_s *dev = (struct ftl_struct_s *)priv; + off_t alignedblock; + off_t mask; + off_t rwblock; + off_t eraseblock; + off_t offset; + size_t remaining; + size_t nxfrd; + int nbytes; + int ret; + + /* Get the aligned block. Here is is assumed: (1) The number of R/W blocks + * per erase block is a power of 2, and (2) the erase begins with that same + * alignment. + */ + + mask = dev->blkper - 1; + alignedblock = (startblock + mask) & ~mask; + + /* Handle partial erase blocks before the first unaligned block */ + + remaining = nblocks; + if (alignedblock > startblock) + { + /* Read the full erase block into the buffer */ + + rwblock = startblock & ~mask; + nxfrd = MTD_BREAD(dev->mtd, rwblock, dev->blkper, dev->eblock); + if (nxfrd != dev->blkper) + { + fdbg("Read erase block %d failed: %d\n", rwblock, nxfrd); + return -EIO; + } + + /* Then erase the erase block */ + + eraseblock = rwblock / dev->blkper; + ret = MTD_ERASE(dev->mtd, eraseblock, 1); + if (ret < 0) + { + fdbg("Erase block=%d failed: %d\n", eraseblock, ret); + return ret; + } + + /* Copy the user data at the end of the buffered erase block */ + + offset = (startblock & mask) * dev->geo.blocksize; + nbytes = dev->geo.erasesize - offset; + fvdbg("Copy %d bytes into erase block=%d at offset=%d\n", + nbytes, eraseblock, offset); + memcpy(dev->eblock + offset, buffer, nbytes); + + /* And write the erase back to flash */ + + nxfrd = MTD_BWRITE(dev->mtd, rwblock, dev->blkper, dev->eblock); + if (nxfrd != dev->blkper) + { + fdbg("Write erase block %d failed: %d\n", rwblock, nxfrd); + return -EIO; + } + + /* Then update for amount written */ + + remaining -= dev->blkper - (startblock & mask); + buffer += nbytes; + } + + /* How handle full erase pages in the middle */ + + while (remaining >= dev->blkper) + { + /* Erase the erase block */ + + eraseblock = alignedblock / dev->blkper; + ret = MTD_ERASE(dev->mtd, eraseblock, 1); + if (ret < 0) + { + fdbg("Erase block=%d failed: %d\n", eraseblock, ret); + return ret; + } + + /* Write a full erase back to flash */ + + fvdbg("Write %d bytes into erase block=%d at offset=0\n", + dev->geo.erasesize, alignedblock); + nxfrd = MTD_BWRITE(dev->mtd, alignedblock, dev->blkper, buffer); + if (nxfrd != dev->blkper) + { + fdbg("Write erase block %d failed: %d\n", alignedblock, nxfrd); + return -EIO; + } + + /* Then update for amount written */ + + alignedblock += dev->blkper; + remaining -= dev->blkper; + buffer += dev->geo.erasesize; + } + + /* Finally, handler any partial blocks after the last full erase block */ + + if (remaining > 0) + { + /* Read the full erase block into the buffer */ + + nxfrd = MTD_BREAD(dev->mtd, alignedblock, dev->blkper, dev->eblock); + if (nxfrd != dev->blkper) + { + fdbg("Read erase block %d failed: %d\n", alignedblock, nxfrd); + return -EIO; + } + + /* Then erase the erase block */ + + eraseblock = alignedblock / dev->blkper; + ret = MTD_ERASE(dev->mtd, eraseblock, 1); + if (ret < 0) + { + fdbg("Erase block=%d failed: %d\n", eraseblock, ret); + return ret; + } + + /* Copy the user data at the beginning the buffered erase block */ + + nbytes = remaining * dev->geo.blocksize; + fvdbg("Copy %d bytes into erase block=%d at offset=0\n", + nbytes, alignedblock); + memcpy(dev->eblock, buffer, nbytes); + + /* And write the erase back to flash */ + + nxfrd = MTD_BWRITE(dev->mtd, alignedblock, dev->blkper, dev->eblock); + if (nxfrd != dev->blkper) + { + fdbg("Write erase block %d failed: %d\n", alignedblock, nxfrd); + return -EIO; + } + } + + return nblocks; +} +#endif + +/**************************************************************************** + * Name: ftl_write + * + * Description: Write (or buffer) the specified number of sectors + * + ****************************************************************************/ + +#ifdef CONFIG_FS_WRITABLE +static ssize_t ftl_write(FAR struct inode *inode, const unsigned char *buffer, + size_t start_sector, unsigned int nsectors) +{ + struct ftl_struct_s *dev; + + fvdbg("sector: %d nsectors: %d\n", start_sector, nsectors); + + DEBUGASSERT(inode && inode->i_private); + dev = (struct ftl_struct_s *)inode->i_private; +#ifdef CONFIG_FS_WRITEBUFFER + return rwb_write(&dev->rwb, start_sector, nsectors, buffer); +#else + return ftl_flush(dev, buffer, start_sector, nsectors); +#endif +} +#endif + +/**************************************************************************** + * Name: ftl_geometry + * + * Description: Return device geometry + * + ****************************************************************************/ + +static int ftl_geometry(FAR struct inode *inode, struct geometry *geometry) +{ + struct ftl_struct_s *dev; + + fvdbg("Entry\n"); + + DEBUGASSERT(inode); + if (geometry) + { + dev = (struct ftl_struct_s *)inode->i_private; + geometry->geo_available = true; + geometry->geo_mediachanged = false; +#ifdef CONFIG_FS_WRITABLE + geometry->geo_writeenabled = true; +#else + geometry->geo_writeenabled = false; +#endif + geometry->geo_nsectors = dev->geo.neraseblocks * dev->blkper; + geometry->geo_sectorsize = dev->geo.blocksize; + + fvdbg("available: true mediachanged: false writeenabled: %s\n", + geometry->geo_writeenabled ? "true" : "false"); + fvdbg("nsectors: %d sectorsize: %d\n", + geometry->geo_nsectors, geometry->geo_sectorsize); + + return OK; + } + return -EINVAL; +} + +/**************************************************************************** + * Name: ftl_ioctl + * + * Description: Return device geometry + * + ****************************************************************************/ + +static int ftl_ioctl(FAR struct inode *inode, int cmd, unsigned long arg) +{ + struct ftl_struct_s *dev ; + int ret; + + fvdbg("Entry\n"); + DEBUGASSERT(inode && inode->i_private); + + /* Only one block driver ioctl command is supported by this driver (and + * that command is just passed on to the MTD driver in a slightly + * different form). + */ + + if (cmd == BIOC_XIPBASE) + { + /* The argument accompanying the BIOC_XIPBASE should be non-NULL. If + * DEBUG is enabled, we will catch it here instead of in the MTD + * driver. + */ + +#ifdef CONFIG_DEBUG + if (arg == 0) + { + fdbg("ERROR: BIOC_XIPBASE argument is NULL\n"); + return -EINVAL; + } +#endif + + /* Just change the BIOC_XIPBASE command to the MTDIOC_XIPBASE command. */ + + cmd = MTDIOC_XIPBASE; + } + + /* No other block driver ioctl commmands are not recognized by this + * driver. Other possible MTD driver ioctl commands are passed through + * to the MTD driver (unchanged). + */ + + dev = (struct ftl_struct_s *)inode->i_private; + ret = MTD_IOCTL(dev->mtd, cmd, arg); + if (ret < 0) + { + fdbg("ERROR: MTD ioctl(%04x) failed: %d\n", cmd, ret); + } + + return ret; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: ftl_initialize + * + * Description: + * Initialize to provide a block driver wrapper around an MTD interface + * + * Input Parameters: + * minor - The minor device number. The MTD block device will be + * registered as as /dev/mtdblockN where N is the minor number. + * mtd - The MTD device that supports the FLASH interface. + * + ****************************************************************************/ + +int ftl_initialize(int minor, FAR struct mtd_dev_s *mtd) +{ + struct ftl_struct_s *dev; + char devname[16]; + int ret = -ENOMEM; + + /* Sanity check */ + +#ifdef CONFIG_DEBUG + if (minor < 0 || minor > 255 || !mtd) + { + return -EINVAL; + } +#endif + + /* Allocate a FTL device structure */ + + dev = (struct ftl_struct_s *)kmalloc(sizeof(struct ftl_struct_s)); + if (dev) + { + /* Initialize the FTL device structure */ + + dev->mtd = mtd; + + /* Get the device geometry. (casting to uintptr_t first eliminates + * complaints on some architectures where the sizeof long is different + * from the size of a pointer). + */ + + ret = MTD_IOCTL(mtd, MTDIOC_GEOMETRY, (unsigned long)((uintptr_t)&dev->geo)); + if (ret < 0) + { + fdbg("MTD ioctl(MTDIOC_GEOMETRY) failed: %d\n", ret); + kfree(dev); + return ret; + } + + /* Allocate one, in-memory erase block buffer */ + +#ifdef CONFIG_FS_WRITABLE + dev->eblock = (FAR uint8_t *)kmalloc(dev->geo.erasesize); + if (!dev->eblock) + { + fdbg("Failed to allocate an erase block buffer\n"); + kfree(dev); + return -ENOMEM; + } +#endif + + /* Get the number of R/W blocks per erase block */ + + dev->blkper = dev->geo.erasesize / dev->geo.blocksize; + DEBUGASSERT(dev->blkper * dev->geo.blocksize == dev->geo.erasesize); + + /* Configure read-ahead/write buffering */ + +#ifdef CONFIG_FTL_RWBUFFER + dev->rwb.blocksize = dev->geo.blocksize; + dev->rwb.nblocks = dev->geo.neraseblocks * dev->blkper; + dev->rwb.dev = (FAR void *)dev; + +#if defined(CONFIG_FS_WRITABLE) && defined(CONFIG_FS_WRITEBUFFER) + dev->rwb.wrmaxblocks = dev->blkper; + dev->rwb.wrflush = ftl_flush; +#endif + +#ifdef CONFIG_FS_READAHEAD + dev->rwb.rhmaxblocks = dev->blkper; + dev->rwb.rhreload = ftl_reload; +#endif + ret = rwb_initialize(&dev->rwb); + if (ret < 0) + { + fdbg("rwb_initialize failed: %d\n", ret); + kfree(dev); + return ret; + } +#endif + + /* Create a MTD block device name */ + + snprintf(devname, 16, "/dev/mtdblock%d", minor); + + /* Inode private data is a reference to the FTL device structure */ + + ret = register_blockdriver(devname, &g_bops, 0, dev); + if (ret < 0) + { + fdbg("register_blockdriver failed: %d\n", -ret); + kfree(dev); + } + } + return ret; +} diff --git a/nuttx/drivers/mtd/m25px.c b/nuttx/drivers/mtd/m25px.c new file mode 100644 index 000000000..4f96c5a3b --- /dev/null +++ b/nuttx/drivers/mtd/m25px.c @@ -0,0 +1,798 @@ +/************************************************************************************ + * drivers/mtd/m25px.c + * Driver for SPI-based M25P1 (128Kbit), M25P64 (32Mbit), M25P64 (64Mbit), and + * M25P128 (128Mbit) FLASH (and compatible). + * + * Copyright (C) 2009-2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <gnutt@nuttx.org> + * + * 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/fs/ioctl.h> +#include <nuttx/spi.h> +#include <nuttx/mtd.h> + +/************************************************************************************ + * Pre-processor Definitions + ************************************************************************************/ +/* Configuration ********************************************************************/ +/* Per the data sheet, MP25P10 parts can be driven with either SPI mode 0 (CPOL=0 and + * CPHA=0) or mode 3 (CPOL=1 and CPHA=1). But I have heard that other devices can + * operated in mode 0 or 1. So you may need to specify CONFIG_MP25P_SPIMODE to + * select the best mode for your device. If CONFIG_MP25P_SPIMODE is not defined, + * mode 0 will be used. + */ + +#ifndef CONFIG_MP25P_SPIMODE +# define CONFIG_MP25P_SPIMODE SPIDEV_MODE0 +#endif + +/* Various manufacturers may have produced the parts. 0x20 is the manufacturer ID + * for the STMicro MP25x serial FLASH. If, for example, you are using the a Macronix + * International MX25 serial FLASH, the correct manufacturer ID would be 0xc2. + */ + +#ifndef CONFIG_MP25P_MANUFACTURER +# define CONFIG_MP25P_MANUFACTURER 0x20 +#endif + +/* M25P Registers *******************************************************************/ +/* Indentification register values */ + +#define M25P_MANUFACTURER CONFIG_MP25P_MANUFACTURER +#define M25P_MEMORY_TYPE 0x20 +#define M25P_M25P1_CAPACITY 0x11 /* 1 M-bit */ +#define M25P_M25P32_CAPACITY 0x16 /* 32 M-bit */ +#define M25P_M25P64_CAPACITY 0x17 /* 64 M-bit */ +#define M25P_M25P128_CAPACITY 0x18 /* 128 M-bit */ + +/* M25P1 capacity is 131,072 bytes: + * (4 sectors) * (32,768 bytes per sector) + * (512 pages) * (256 bytes per page) + */ + +#define M25P_M25P1_SECTOR_SHIFT 15 /* Sector size 1 << 15 = 65,536 */ +#define M25P_M25P1_NSECTORS 4 +#define M25P_M25P1_PAGE_SHIFT 8 /* Page size 1 << 8 = 256 */ +#define M25P_M25P1_NPAGES 512 + +/* M25P32 capacity is 4,194,304 bytes: + * (64 sectors) * (65,536 bytes per sector) + * (16384 pages) * (256 bytes per page) + */ + +#define M25P_M25P32_SECTOR_SHIFT 16 /* Sector size 1 << 16 = 65,536 */ +#define M25P_M25P32_NSECTORS 64 +#define M25P_M25P32_PAGE_SHIFT 8 /* Page size 1 << 8 = 256 */ +#define M25P_M25P32_NPAGES 16384 + +/* M25P64 capacity is 8,338,608 bytes: + * (128 sectors) * (65,536 bytes per sector) + * (32768 pages) * (256 bytes per page) + */ + +#define M25P_M25P64_SECTOR_SHIFT 16 /* Sector size 1 << 16 = 65,536 */ +#define M25P_M25P64_NSECTORS 128 +#define M25P_M25P64_PAGE_SHIFT 8 /* Page size 1 << 8 = 256 */ +#define M25P_M25P64_NPAGES 32768 + +/* M25P128 capacity is 16,777,216 bytes: + * (64 sectors) * (262,144 bytes per sector) + * (65536 pages) * (256 bytes per page) + */ + +#define M25P_M25P128_SECTOR_SHIFT 18 /* Sector size 1 << 18 = 262,144 */ +#define M25P_M25P128_NSECTORS 64 +#define M25P_M25P128_PAGE_SHIFT 8 /* Page size 1 << 8 = 256 */ +#define M25P_M25P128_NPAGES 65536 + +/* Instructions */ +/* Command Value N Description Addr Dummy Data */ +#define M25P_WREN 0x06 /* 1 Write Enable 0 0 0 */ +#define M25P_WRDI 0x04 /* 1 Write Disable 0 0 0 */ +#define M25P_RDID 0x9f /* 1 Read Identification 0 0 1-3 */ +#define M25P_RDSR 0x05 /* 1 Read Status Register 0 0 >=1 */ +#define M25P_WRSR 0x01 /* 1 Write Status Register 0 0 1 */ +#define M25P_READ 0x03 /* 1 Read Data Bytes 3 0 >=1 */ +#define M25P_FAST_READ 0x0b /* 1 Higher speed read 3 1 >=1 */ +#define M25P_PP 0x02 /* 1 Page Program 3 0 1-256 */ +#define M25P_SE 0xd8 /* 1 Sector Erase 3 0 0 */ +#define M25P_BE 0xc7 /* 1 Bulk Erase 0 0 0 */ +#define M25P_DP 0xb9 /* 2 Deep power down 0 0 0 */ +#define M25P_RES 0xab /* 2 Read Electronic Signature 0 3 >=1 */ + +/* NOTE 1: All parts, NOTE 2: M25P632/M25P64 */ + +/* Status register bit definitions */ + +#define M25P_SR_WIP (1 << 0) /* Bit 0: Write in progress bit */ +#define M25P_SR_WEL (1 << 1) /* Bit 1: Write enable latch bit */ +#define M25P_SR_BP_SHIFT (2) /* Bits 2-4: Block protect bits */ +#define M25P_SR_BP_MASK (7 << M25P_SR_BP_SHIFT) +# define M25P_SR_BP_NONE (0 << M25P_SR_BP_SHIFT) /* Unprotected */ +# define M25P_SR_BP_UPPER64th (1 << M25P_SR_BP_SHIFT) /* Upper 64th */ +# define M25P_SR_BP_UPPER32nd (2 << M25P_SR_BP_SHIFT) /* Upper 32nd */ +# define M25P_SR_BP_UPPER16th (3 << M25P_SR_BP_SHIFT) /* Upper 16th */ +# define M25P_SR_BP_UPPER8th (4 << M25P_SR_BP_SHIFT) /* Upper 8th */ +# define M25P_SR_BP_UPPERQTR (5 << M25P_SR_BP_SHIFT) /* Upper quarter */ +# define M25P_SR_BP_UPPERHALF (6 << M25P_SR_BP_SHIFT) /* Upper half */ +# define M25P_SR_BP_ALL (7 << M25P_SR_BP_SHIFT) /* All sectors */ + /* Bits 5-6: Unused, read zero */ +#define M25P_SR_SRWD (1 << 7) /* Bit 7: Status register write protect */ + +#define M25P_DUMMY 0xa5 + +/************************************************************************************ + * 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 m25p_dev_s. + */ + +struct m25p_dev_s +{ + struct mtd_dev_s mtd; /* MTD interface */ + FAR struct spi_dev_s *dev; /* Saved SPI interface instance */ + uint8_t sectorshift; /* 16 or 18 */ + uint8_t pageshift; /* 8 */ + uint16_t nsectors; /* 128 or 64 */ + uint32_t npages; /* 32,768 or 65,536 */ +}; + +/************************************************************************************ + * Private Function Prototypes + ************************************************************************************/ + +/* Helpers */ + +static void m25p_lock(FAR struct spi_dev_s *dev); +static inline void m25p_unlock(FAR struct spi_dev_s *dev); +static inline int m25p_readid(struct m25p_dev_s *priv); +static void m25p_waitwritecomplete(struct m25p_dev_s *priv); +static void m25p_writeenable(struct m25p_dev_s *priv); +static inline void m25p_sectorerase(struct m25p_dev_s *priv, off_t offset); +static inline int m25p_bulkerase(struct m25p_dev_s *priv); +static inline void m25p_pagewrite(struct m25p_dev_s *priv, FAR const uint8_t *buffer, + off_t offset); + +/* MTD driver methods */ + +static int m25p_erase(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks); +static ssize_t m25p_bread(FAR struct mtd_dev_s *dev, off_t startblock, + size_t nblocks, FAR uint8_t *buf); +static ssize_t m25p_bwrite(FAR struct mtd_dev_s *dev, off_t startblock, + size_t nblocks, FAR const uint8_t *buf); +static ssize_t m25p_read(FAR struct mtd_dev_s *dev, off_t offset, size_t nbytes, + FAR uint8_t *buffer); +static int m25p_ioctl(FAR struct mtd_dev_s *dev, int cmd, unsigned long arg); + +/************************************************************************************ + * Private Data + ************************************************************************************/ + +/************************************************************************************ + * Private Functions + ************************************************************************************/ + +/************************************************************************************ + * Name: m25p_lock + ************************************************************************************/ + +static void m25p_lock(FAR struct spi_dev_s *dev) +{ + /* On SPI busses where there are multiple devices, it will be necessary to + * lock SPI to have exclusive access to the busses for a sequence of + * transfers. The bus should be locked before the chip is selected. + * + * This is a blocking call and will not return until we have exclusiv access to + * the SPI buss. We will retain that exclusive access until the bus is unlocked. + */ + + (void)SPI_LOCK(dev, true); + + /* After locking the SPI bus, the we also need call the setfrequency, setbits, and + * setmode methods to make sure that the SPI is properly configured for the device. + * If the SPI buss is being shared, then it may have been left in an incompatible + * state. + */ + + SPI_SETMODE(dev, CONFIG_MP25P_SPIMODE); + SPI_SETBITS(dev, 8); + (void)SPI_SETFREQUENCY(dev, 20000000); +} + +/************************************************************************************ + * Name: m25p_unlock + ************************************************************************************/ + +static inline void m25p_unlock(FAR struct spi_dev_s *dev) +{ + (void)SPI_LOCK(dev, false); +} + +/************************************************************************************ + * Name: m25p_readid + ************************************************************************************/ + +static inline int m25p_readid(struct m25p_dev_s *priv) +{ + uint16_t manufacturer; + uint16_t memory; + uint16_t capacity; + + fvdbg("priv: %p\n", priv); + + /* Lock the SPI bus, configure the bus, and select this FLASH part. */ + + m25p_lock(priv->dev); + SPI_SELECT(priv->dev, SPIDEV_FLASH, true); + + /* Send the "Read ID (RDID)" command and read the first three ID bytes */ + + (void)SPI_SEND(priv->dev, M25P_RDID); + manufacturer = SPI_SEND(priv->dev, M25P_DUMMY); + memory = SPI_SEND(priv->dev, M25P_DUMMY); + capacity = SPI_SEND(priv->dev, M25P_DUMMY); + + /* Deselect the FLASH and unlock the bus */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, false); + m25p_unlock(priv->dev); + + fvdbg("manufacturer: %02x memory: %02x capacity: %02x\n", + manufacturer, memory, capacity); + + /* Check for a valid manufacturer and memory type */ + + if (manufacturer == M25P_MANUFACTURER && memory == M25P_MEMORY_TYPE) + { + /* Okay.. is it a FLASH capacity that we understand? */ + + if (capacity == M25P_M25P1_CAPACITY) + { + /* Save the FLASH geometry */ + + priv->sectorshift = M25P_M25P1_SECTOR_SHIFT; + priv->nsectors = M25P_M25P1_NSECTORS; + priv->pageshift = M25P_M25P1_PAGE_SHIFT; + priv->npages = M25P_M25P1_NPAGES; + return OK; + } + else if (capacity == M25P_M25P32_CAPACITY) + { + /* Save the FLASH geometry */ + + priv->sectorshift = M25P_M25P32_SECTOR_SHIFT; + priv->nsectors = M25P_M25P32_NSECTORS; + priv->pageshift = M25P_M25P32_PAGE_SHIFT; + priv->npages = M25P_M25P32_NPAGES; + return OK; + } + else if (capacity == M25P_M25P64_CAPACITY) + { + /* Save the FLASH geometry */ + + priv->sectorshift = M25P_M25P64_SECTOR_SHIFT; + priv->nsectors = M25P_M25P64_NSECTORS; + priv->pageshift = M25P_M25P64_PAGE_SHIFT; + priv->npages = M25P_M25P64_NPAGES; + return OK; + } + else if (capacity == M25P_M25P128_CAPACITY) + { + /* Save the FLASH geometry */ + + priv->sectorshift = M25P_M25P128_SECTOR_SHIFT; + priv->nsectors = M25P_M25P128_NSECTORS; + priv->pageshift = M25P_M25P128_PAGE_SHIFT; + priv->npages = M25P_M25P128_NPAGES; + return OK; + } + } + + return -ENODEV; +} + +/************************************************************************************ + * Name: m25p_waitwritecomplete + ************************************************************************************/ + +static void m25p_waitwritecomplete(struct m25p_dev_s *priv) +{ + uint8_t status; + + /* Are we the only device on the bus? */ + +#ifdef CONFIG_SPI_OWNBUS + + /* Select this FLASH part */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, true); + + /* Send "Read Status Register (RDSR)" command */ + + (void)SPI_SEND(priv->dev, M25P_RDSR); + + /* Loop as long as the memory is busy with a write cycle */ + + do + { + /* Send a dummy byte to generate the clock needed to shift out the status */ + + status = SPI_SEND(priv->dev, M25P_DUMMY); + } + while ((status & M25P_SR_WIP) != 0); + + /* Deselect the FLASH */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, false); + +#else + + /* Loop as long as the memory is busy with a write cycle */ + + do + { + /* Select this FLASH part */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, true); + + /* Send "Read Status Register (RDSR)" command */ + + (void)SPI_SEND(priv->dev, M25P_RDSR); + + /* Send a dummy byte to generate the clock needed to shift out the status */ + + status = SPI_SEND(priv->dev, M25P_DUMMY); + + /* Deselect the FLASH */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, false); + + /* Given that writing could take up to few tens of milliseconds, and erasing + * could take more. The following short delay in the "busy" case will allow + * other peripherals to access the SPI bus. + */ + + if ((status & M25P_SR_WIP) != 0) + { + m25p_unlock(priv->dev); + usleep(1000); + m25p_lock(priv->dev); + } + } + while ((status & M25P_SR_WIP) != 0); +#endif + + fvdbg("Complete\n"); +} + +/************************************************************************************ + * Name: m25p_writeenable + ************************************************************************************/ + +static void m25p_writeenable(struct m25p_dev_s *priv) +{ + /* Select this FLASH part */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, true); + + /* Send "Write Enable (WREN)" command */ + + (void)SPI_SEND(priv->dev, M25P_WREN); + + /* Deselect the FLASH */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, false); + fvdbg("Enabled\n"); +} + +/************************************************************************************ + * Name: m25p_sectorerase + ************************************************************************************/ + +static inline void m25p_sectorerase(struct m25p_dev_s *priv, off_t sector) +{ + off_t offset = sector << priv->sectorshift; + + fvdbg("sector: %08lx\n", (long)sector); + + /* Wait for any preceding write to complete. We could simplify things by + * perform this wait at the end of each write operation (rather than at + * the beginning of ALL operations), but have the wait first will slightly + * improve performance. + */ + + m25p_waitwritecomplete(priv); + + /* Send write enable instruction */ + + m25p_writeenable(priv); + + /* Select this FLASH part */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, true); + + /* Send the "Sector Erase (SE)" instruction */ + + (void)SPI_SEND(priv->dev, M25P_SE); + + /* Send the sector offset high byte first. For all of the supported + * parts, the sector number is completely contained in the first byte + * and the values used in the following two bytes don't really matter. + */ + + (void)SPI_SEND(priv->dev, (offset >> 16) & 0xff); + (void)SPI_SEND(priv->dev, (offset >> 8) & 0xff); + (void)SPI_SEND(priv->dev, offset & 0xff); + + /* Deselect the FLASH */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, false); + fvdbg("Erased\n"); +} + +/************************************************************************************ + * Name: m25p_bulkerase + ************************************************************************************/ + +static inline int m25p_bulkerase(struct m25p_dev_s *priv) +{ + fvdbg("priv: %p\n", priv); + + /* Wait for any preceding write to complete. We could simplify things by + * perform this wait at the end of each write operation (rather than at + * the beginning of ALL operations), but have the wait first will slightly + * improve performance. + */ + + m25p_waitwritecomplete(priv); + + /* Send write enable instruction */ + + m25p_writeenable(priv); + + /* Select this FLASH part */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, true); + + /* Send the "Bulk Erase (BE)" instruction */ + + (void)SPI_SEND(priv->dev, M25P_BE); + + /* Deselect the FLASH */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, false); + fvdbg("Return: OK\n"); + return OK; +} + +/************************************************************************************ + * Name: m25p_pagewrite + ************************************************************************************/ + +static inline void m25p_pagewrite(struct m25p_dev_s *priv, FAR const uint8_t *buffer, + off_t page) +{ + off_t offset = page << priv->pageshift; + + fvdbg("page: %08lx offset: %08lx\n", (long)page, (long)offset); + + /* Wait for any preceding write to complete. We could simplify things by + * perform this wait at the end of each write operation (rather than at + * the beginning of ALL operations), but have the wait first will slightly + * improve performance. + */ + + m25p_waitwritecomplete(priv); + + /* Enable the write access to the FLASH */ + + m25p_writeenable(priv); + + /* Select this FLASH part */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, true); + + /* Send "Page Program (PP)" command */ + + (void)SPI_SEND(priv->dev, M25P_PP); + + /* Send the page offset high byte first. */ + + (void)SPI_SEND(priv->dev, (offset >> 16) & 0xff); + (void)SPI_SEND(priv->dev, (offset >> 8) & 0xff); + (void)SPI_SEND(priv->dev, offset & 0xff); + + /* Then write the specified number of bytes */ + + SPI_SNDBLOCK(priv->dev, buffer, 1 << priv->pageshift); + + /* Deselect the FLASH: Chip Select high */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, false); + fvdbg("Written\n"); +} + +/************************************************************************************ + * Name: m25p_erase + ************************************************************************************/ + +static int m25p_erase(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks) +{ + FAR struct m25p_dev_s *priv = (FAR struct m25p_dev_s *)dev; + size_t blocksleft = nblocks; + + fvdbg("startblock: %08lx nblocks: %d\n", (long)startblock, (int)nblocks); + + /* Lock access to the SPI bus until we complete the erase */ + + m25p_lock(priv->dev); + while (blocksleft-- > 0) + { + /* Erase each sector */ + + m25p_sectorerase(priv, startblock); + startblock++; + } + m25p_unlock(priv->dev); + return (int)nblocks; +} + +/************************************************************************************ + * Name: m25p_bread + ************************************************************************************/ + +static ssize_t m25p_bread(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks, + FAR uint8_t *buffer) +{ + FAR struct m25p_dev_s *priv = (FAR struct m25p_dev_s *)dev; + ssize_t nbytes; + + fvdbg("startblock: %08lx nblocks: %d\n", (long)startblock, (int)nblocks); + + /* On this device, we can handle the block read just like the byte-oriented read */ + + nbytes = m25p_read(dev, startblock << priv->pageshift, nblocks << priv->pageshift, buffer); + if (nbytes > 0) + { + return nbytes >> priv->pageshift; + } + return (int)nbytes; +} + +/************************************************************************************ + * Name: m25p_bwrite + ************************************************************************************/ + +static ssize_t m25p_bwrite(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks, + FAR const uint8_t *buffer) +{ + FAR struct m25p_dev_s *priv = (FAR struct m25p_dev_s *)dev; + size_t blocksleft = nblocks; + + fvdbg("startblock: %08lx nblocks: %d\n", (long)startblock, (int)nblocks); + + /* Lock the SPI bus and write each page to FLASH */ + + m25p_lock(priv->dev); + while (blocksleft-- > 0) + { + m25p_pagewrite(priv, buffer, startblock); + startblock++; + } + m25p_unlock(priv->dev); + + return nblocks; +} + +/************************************************************************************ + * Name: m25p_read + ************************************************************************************/ + +static ssize_t m25p_read(FAR struct mtd_dev_s *dev, off_t offset, size_t nbytes, + FAR uint8_t *buffer) +{ + FAR struct m25p_dev_s *priv = (FAR struct m25p_dev_s *)dev; + + fvdbg("offset: %08lx nbytes: %d\n", (long)offset, (int)nbytes); + + /* Wait for any preceding write to complete. We could simplify things by + * perform this wait at the end of each write operation (rather than at + * the beginning of ALL operations), but have the wait first will slightly + * improve performance. + */ + + m25p_waitwritecomplete(priv); + + /* Lock the SPI bus and select this FLASH part */ + + m25p_lock(priv->dev); + SPI_SELECT(priv->dev, SPIDEV_FLASH, true); + + /* Send "Read from Memory " instruction */ + + (void)SPI_SEND(priv->dev, M25P_READ); + + /* Send the page offset high byte first. */ + + (void)SPI_SEND(priv->dev, (offset >> 16) & 0xff); + (void)SPI_SEND(priv->dev, (offset >> 8) & 0xff); + (void)SPI_SEND(priv->dev, offset & 0xff); + + /* Then read all of the requested bytes */ + + SPI_RECVBLOCK(priv->dev, buffer, nbytes); + + /* Deselect the FLASH and unlock the SPI bus */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, false); + m25p_unlock(priv->dev); + fvdbg("return nbytes: %d\n", (int)nbytes); + return nbytes; +} + +/************************************************************************************ + * Name: m25p_ioctl + ************************************************************************************/ + +static int m25p_ioctl(FAR struct mtd_dev_s *dev, int cmd, unsigned long arg) +{ + FAR struct m25p_dev_s *priv = (FAR struct m25p_dev_s *)dev; + int ret = -EINVAL; /* Assume good command with bad parameters */ + + fvdbg("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) + { + /* Populate the geometry structure with information need to know + * the capacity and how to access the device. + * + * NOTE: that the device is treated as though it where just an array + * of fixed size blocks. That is most likely not true, but the client + * will expect the device logic to do whatever is necessary to make it + * appear so. + */ + + geo->blocksize = (1 << priv->pageshift); + geo->erasesize = (1 << priv->sectorshift); + geo->neraseblocks = priv->nsectors; + ret = OK; + + fvdbg("blocksize: %d erasesize: %d neraseblocks: %d\n", + geo->blocksize, geo->erasesize, geo->neraseblocks); + } + } + break; + + case MTDIOC_BULKERASE: + { + /* Erase the entire device */ + + m25p_lock(priv->dev); + ret = m25p_bulkerase(priv); + m25p_unlock(priv->dev); + } + break; + + case MTDIOC_XIPBASE: + default: + ret = -ENOTTY; /* Bad command */ + break; + } + + fvdbg("return %d\n", ret); + return ret; +} + +/************************************************************************************ + * Public Functions + ************************************************************************************/ + +/************************************************************************************ + * Name: m25p_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 *m25p_initialize(FAR struct spi_dev_s *dev) +{ + FAR struct m25p_dev_s *priv; + int ret; + + fvdbg("dev: %p\n", dev); + + /* Allocate a state structure (we allocate the structure instead of using + * a fixed, static allocation so that we can handle multiple FLASH devices. + * The current implementation would handle only one FLASH part per SPI + * device (only because of the SPIDEV_FLASH definition) and so would have + * to be extended to handle multiple FLASH parts on the same SPI bus. + */ + + priv = (FAR struct m25p_dev_s *)kmalloc(sizeof(struct m25p_dev_s)); + if (priv) + { + /* Initialize the allocated structure */ + + priv->mtd.erase = m25p_erase; + priv->mtd.bread = m25p_bread; + priv->mtd.bwrite = m25p_bwrite; + priv->mtd.read = m25p_read; + priv->mtd.ioctl = m25p_ioctl; + priv->dev = dev; + + /* Deselect the FLASH */ + + SPI_SELECT(dev, SPIDEV_FLASH, false); + + /* Identify the FLASH chip and get its capacity */ + + ret = m25p_readid(priv); + if (ret != OK) + { + /* Unrecognized! Discard all of that work we just did and return NULL */ + + fdbg("Unrecognized\n"); + kfree(priv); + priv = NULL; + } + } + + /* Return the implementation-specific state structure as the MTD device */ + + fvdbg("Return %p\n", priv); + return (FAR struct mtd_dev_s *)priv; +} diff --git a/nuttx/drivers/mtd/rammtd.c b/nuttx/drivers/mtd/rammtd.c new file mode 100644 index 000000000..82a7191ea --- /dev/null +++ b/nuttx/drivers/mtd/rammtd.c @@ -0,0 +1,417 @@ +/**************************************************************************** + * drivers/mtd/rammtd.c + * + * Copyright (C) 2011-2012 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <gnutt@nuttx.org> + * + * 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 <string.h> +#include <assert.h> +#include <errno.h> +#include <debug.h> + +#include <nuttx/kmalloc.h> +#include <nuttx/fs/ioctl.h> +#include <nuttx/mtd.h> + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ +/* Configuration ************************************************************/ + +#ifndef CONFIG_RAMMTD_BLOCKSIZE +# define CONFIG_RAMMTD_BLOCKSIZE 512 +#endif + +#ifndef CONFIG_RAMMTD_ERASESIZE +# define CONFIG_RAMMTD_ERASESIZE 4096 +#endif + +#ifndef CONFIG_RAMMTD_ERASESTATE +# define CONFIG_RAMMTD_ERASESTATE 0xff +#endif + +#if CONFIG_RAMMTD_ERASESTATE != 0xff && CONFIG_RAMMTD_ERASESTATE != 0x00 +# error "Unsupported value for CONFIG_RAMMTD_ERASESTATE" +#endif + +#if CONFIG_RAMMTD_BLOCKSIZE > CONFIG_RAMMTD_ERASESIZE +# error "Must have CONFIG_RAMMTD_BLOCKSIZE <= CONFIG_RAMMTD_ERASESIZE" +#endif + +#undef CONFIG_RAMMTD_BLKPER +#define CONFIG_RAMMTD_BLKPER (CONFIG_RAMMTD_ERASESIZE/CONFIG_RAMMTD_BLOCKSIZE) + +#if CONFIG_RAMMTD_BLKPER*CONFIG_RAMMTD_BLOCKSIZE != CONFIG_RAMMTD_ERASESIZE +# error "CONFIG_RAMMTD_ERASESIZE must be an even multiple of CONFIG_RAMMTD_BLOCKSIZE" +#endif + +/**************************************************************************** + * 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 ram_dev_s. + */ + +struct ram_dev_s +{ + struct mtd_dev_s mtd; /* MTD device */ + FAR uint8_t *start; /* Start of RAM */ + size_t nblocks; /* Number of erase blocks */ +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ +/* The RAM MTD driver may be useful just as it is, but another good use of + * the RAM MTD driver is as a FLASH simulation -- to support testing of FLASH + * based logic without having FLASH. CONFIG_RAMMTD_FLASHSIM will add some + * extra logic to improve the level of FLASH simulation. + */ + +#define ram_read(dest, src, len) memcpy(dest, src, len) +#ifdef CONFIG_RAMMTD_FLASHSIM +static void *ram_write(FAR void *dest, FAR const void *src, size_t len); +#else +# define ram_write(dest, src, len) memcpy(dest, src, len) +#endif + +/* MTD driver methods */ + +static int ram_erase(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks); +static ssize_t ram_bread(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks, + FAR uint8_t *buf); +static ssize_t ram_bwrite(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks, + FAR const uint8_t *buf); +static int ram_ioctl(FAR struct mtd_dev_s *dev, int cmd, unsigned long arg); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: ram_write + ****************************************************************************/ + +#ifdef CONFIG_RAMMTD_FLASHSIM +static void *ram_write(FAR void *dest, FAR const void *src, size_t len) +{ + FAR uint8_t *pout = (FAR uint8_t *)dest; + FAR const uint8_t *pin = (FAR const uint8_t *)src; + + while (len-- > 0) + { + /* Get the source and destination values */ + + uint8_t oldvalue = *pout; + uint8_t srcvalue = *pin++; + uint8_t newvalue; + + /* Get the new destination value, accounting for bits that cannot be + * changes because they are not in the erased state. + */ + +#if CONFIG_RAMMTD_ERASESTATE == 0xff + newvalue = oldvalue & srcvalue; /* We can only clear bits */ +#else /* CONFIG_RAMMTD_ERASESTATE == 0x00 */ + newvalue = oldvalue | srcvalue; /* We can only set bits */ +#endif + + /* Report any attempt to change the value of bits that are not in the + * erased state. + */ + +#ifdef CONFIG_DEBUG + if (newvalue != srcvalue) + { + dbg("ERROR: Bad write: source=%02x dest=%02x result=%02x\n", + srcvalue, oldvalue, newvalue); + } +#endif + + /* Write the modified value to simulated FLASH */ + + *pout++ = newvalue; + } + + return dest; +} +#endif + +/**************************************************************************** + * Name: ram_erase + ****************************************************************************/ + +static int ram_erase(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks) +{ + FAR struct ram_dev_s *priv = (FAR struct ram_dev_s *)dev; + off_t offset; + size_t nbytes; + + DEBUGASSERT(dev); + + /* Don't let the erase exceed the size of the ram buffer */ + + if (startblock >= priv->nblocks) + { + return 0; + } + + if (startblock + nblocks > priv->nblocks) + { + nblocks = priv->nblocks - startblock; + } + + /* Convert the erase block to a logical block and the number of blocks + * in logical block numbers + */ + + startblock *= CONFIG_RAMMTD_BLKPER; + nblocks *= CONFIG_RAMMTD_BLKPER; + + /* Get the offset corresponding to the first block and the size + * corresponding to the number of blocks. + */ + + offset = startblock * CONFIG_RAMMTD_BLOCKSIZE; + nbytes = nblocks * CONFIG_RAMMTD_BLOCKSIZE; + + /* Then erase the data in RAM */ + + memset(&priv->start[offset], CONFIG_RAMMTD_ERASESTATE, nbytes); + return OK; +} + +/**************************************************************************** + * Name: ram_bread + ****************************************************************************/ + +static ssize_t ram_bread(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks, + FAR uint8_t *buf) +{ + FAR struct ram_dev_s *priv = (FAR struct ram_dev_s *)dev; + off_t offset; + off_t maxblock; + size_t nbytes; + + DEBUGASSERT(dev && buf); + + /* Don't let the read exceed the size of the ram buffer */ + + maxblock = priv->nblocks * CONFIG_RAMMTD_BLKPER; + if (startblock >= maxblock) + { + return 0; + } + + if (startblock + nblocks > maxblock) + { + nblocks = maxblock - startblock; + } + + /* Get the offset corresponding to the first block and the size + * corresponding to the number of blocks. + */ + + offset = startblock * CONFIG_RAMMTD_BLOCKSIZE; + nbytes = nblocks * CONFIG_RAMMTD_BLOCKSIZE; + + /* Then read the data frp, RAM */ + + ram_read(buf, &priv->start[offset], nbytes); + return nblocks; +} + +/**************************************************************************** + * Name: ram_bwrite + ****************************************************************************/ + +static ssize_t ram_bwrite(FAR struct mtd_dev_s *dev, off_t startblock, + size_t nblocks, FAR const uint8_t *buf) +{ + FAR struct ram_dev_s *priv = (FAR struct ram_dev_s *)dev; + off_t offset; + off_t maxblock; + size_t nbytes; + + DEBUGASSERT(dev && buf); + + /* Don't let the write exceed the size of the ram buffer */ + + maxblock = priv->nblocks * CONFIG_RAMMTD_BLKPER; + if (startblock >= maxblock) + { + return 0; + } + + if (startblock + nblocks > maxblock) + { + nblocks = maxblock - startblock; + } + + /* Get the offset corresponding to the first block and the size + * corresponding to the number of blocks. + */ + + offset = startblock * CONFIG_RAMMTD_BLOCKSIZE; + nbytes = nblocks * CONFIG_RAMMTD_BLOCKSIZE; + + /* Then write the data to RAM */ + + ram_write(&priv->start[offset], buf, nbytes); + return nblocks; +} + +/**************************************************************************** + * Name: ram_ioctl + ****************************************************************************/ + +static int ram_ioctl(FAR struct mtd_dev_s *dev, int cmd, unsigned long arg) +{ + FAR struct ram_dev_s *priv = (FAR struct ram_dev_s *)dev; + int ret = -EINVAL; /* Assume good command with bad parameters */ + + switch (cmd) + { + case MTDIOC_GEOMETRY: + { + FAR struct mtd_geometry_s *geo = (FAR struct mtd_geometry_s *)((uintptr_t)arg); + if (geo) + { + /* Populate the geometry structure with information need to know + * the capacity and how to access the device. + */ + + geo->blocksize = CONFIG_RAMMTD_BLOCKSIZE; + geo->erasesize = CONFIG_RAMMTD_ERASESIZE; + geo->neraseblocks = priv->nblocks; + ret = OK; + } + } + break; + + case MTDIOC_XIPBASE: + { + FAR void **ppv = (FAR void**)((uintptr_t)arg); + if (ppv) + { + /* Return (void*) base address of device memory */ + + *ppv = (FAR void*)priv->start; + ret = OK; + } + } + break; + + case MTDIOC_BULKERASE: + { + size_t size = priv->nblocks * CONFIG_RAMMTD_ERASESIZE; + + /* Erase the entire device */ + + memset(priv->start, CONFIG_RAMMTD_ERASESTATE, size); + ret = OK; + } + break; + + default: + ret = -ENOTTY; /* Bad command */ + break; + } + + return ret; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: rammtd_initialize + * + * Description: + * Create and initialize a RAM MTD device instance. + * + * Input Parameters: + * start - Address of the beginning of the allocated RAM regions. + * size - The size in bytes of the allocated RAM region. + * + ****************************************************************************/ + +FAR struct mtd_dev_s *rammtd_initialize(FAR uint8_t *start, size_t size) +{ + FAR struct ram_dev_s *priv; + size_t nblocks; + + /* Create an instance of the RAM MTD device state structure */ + + priv = (FAR struct ram_dev_s *)kmalloc(sizeof(struct ram_dev_s)); + if (!priv) + { + fdbg("Failed to allocate the RAM MTD state structure\n"); + return NULL; + } + + /* Force the size to be an even number of the erase block size */ + + nblocks = size / CONFIG_RAMMTD_ERASESIZE; + if (nblocks <= 0) + { + fdbg("Need to provide at least one full erase block\n"); + return NULL; + } + + /* Perform initialization as necessary */ + + priv->mtd.erase = ram_erase; + priv->mtd.bread = ram_bread; + priv->mtd.bwrite = ram_bwrite; + priv->mtd.ioctl = ram_ioctl; + priv->mtd.erase = ram_erase; + + priv->start = start; + priv->nblocks = nblocks; + return &priv->mtd; +} diff --git a/nuttx/drivers/mtd/ramtron.c b/nuttx/drivers/mtd/ramtron.c new file mode 100644 index 000000000..074545e2d --- /dev/null +++ b/nuttx/drivers/mtd/ramtron.c @@ -0,0 +1,674 @@ +/************************************************************************************ + * drivers/mtd/ramtron.c + * Driver for SPI-based RAMTRON NVRAM Devices FM25V10 and others (not tested) + * + * Copyright (C) 2011 Uros Platise. All rights reserved. + * Copyright (C) 2009-2010, 2012 Gregory Nutt. All rights reserved. + * Author: Uros Platise <uros.platise@isotel.eu> + * Gregory Nutt <gnutt@nuttx.org> + * + * 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. + * + ************************************************************************************/ + +/* OPTIONS: + * - additional non-jedec standard device: FM25H20 + * must be enabled with the CONFIG_RAMTRON_FRAM_NON_JEDEC=y + * + * NOTE: + * - frequency is fixed to desired max by RAMTRON_INIT_CLK_MAX + * if new devices with different speed arrive, then SETFREQUENCY() + * needs to handle freq changes and INIT_CLK_MAX must be reduced + * to fit all devices. Note that STM32_SPI driver is prone to + * too high freq. parameters and limit it within physical constraints. + * + * TODO: + * - add support for sleep + * - add support for faster read FSTRD command + */ + +/************************************************************************************ + * Included Files + ************************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/types.h> +#include <stdint.h> +#include <stdbool.h> +#include <stdlib.h> +#include <errno.h> +#include <debug.h> +#include <assert.h> + +#include <nuttx/kmalloc.h> +#include <nuttx/fs/ioctl.h> +#include <nuttx/spi.h> +#include <nuttx/mtd.h> + +/************************************************************************************ + * Pre-processor Definitions + ************************************************************************************/ + +/* RAMTRON devices are flat! + * For purpose of the VFAT file system we emulate the following configuration: + */ + +#define RAMTRON_EMULATE_SECTOR_SHIFT 9 +#define RAMTRON_EMULATE_PAGE_SHIFT 9 + +/* RAMTRON Indentification register values */ + +#define RAMTRON_MANUFACTURER 0x7F +#define RAMTRON_MEMORY_TYPE 0xC2 + +/* Instructions: + * Command Value N Description Addr Dummy Data */ +#define RAMTRON_WREN 0x06 /* 1 Write Enable 0 0 0 */ +#define RAMTRON_WRDI 0x04 /* 1 Write Disable 0 0 0 */ +#define RAMTRON_RDSR 0x05 /* 1 Read Status Register 0 0 >=1 */ +#define RAMTRON_WRSR 0x01 /* 1 Write Status Register 0 0 1 */ +#define RAMTRON_READ 0x03 /* 1 Read Data Bytes A 0 >=1 */ +#define RAMTRON_FSTRD 0x0b /* 1 Higher speed read A 1 >=1 */ +#define RAMTRON_WRITE 0x02 /* 1 Write A 0 1-256 */ +#define RAMTRON_SLEEP 0xb9 // TODO: +#define RAMTRON_RDID 0x9f /* 1 Read Identification 0 0 1-3 */ +#define RAMTRON_SN 0xc3 // TODO: + + +/* Status register bit definitions */ + +#define RAMTRON_SR_WIP (1 << 0) /* Bit 0: Write in progress bit */ +#define RAMTRON_SR_WEL (1 << 1) /* Bit 1: Write enable latch bit */ +#define RAMTRON_SR_BP_SHIFT (2) /* Bits 2-4: Block protect bits */ +#define RAMTRON_SR_BP_MASK (7 << RAMTRON_SR_BP_SHIFT) +# define RAMTRON_SR_BP_NONE (0 << RAMTRON_SR_BP_SHIFT) /* Unprotected */ +# define RAMTRON_SR_BP_UPPER64th (1 << RAMTRON_SR_BP_SHIFT) /* Upper 64th */ +# define RAMTRON_SR_BP_UPPER32nd (2 << RAMTRON_SR_BP_SHIFT) /* Upper 32nd */ +# define RAMTRON_SR_BP_UPPER16th (3 << RAMTRON_SR_BP_SHIFT) /* Upper 16th */ +# define RAMTRON_SR_BP_UPPER8th (4 << RAMTRON_SR_BP_SHIFT) /* Upper 8th */ +# define RAMTRON_SR_BP_UPPERQTR (5 << RAMTRON_SR_BP_SHIFT) /* Upper quarter */ +# define RAMTRON_SR_BP_UPPERHALF (6 << RAMTRON_SR_BP_SHIFT) /* Upper half */ +# define RAMTRON_SR_BP_ALL (7 << RAMTRON_SR_BP_SHIFT) /* All sectors */ +#define RAMTRON_SR_SRWD (1 << 7) /* Bit 7: Status register write protect */ + +#define RAMTRON_DUMMY 0xa5 + +/************************************************************************************ + * Private Types + ************************************************************************************/ + +struct ramtron_parts_s +{ + const char *name; + uint8_t id1; + uint8_t id2; + uint32_t size; + uint8_t addr_len; + uint32_t speed; +}; + +/* 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 ramtron_dev_s. + */ + +struct ramtron_dev_s +{ + struct mtd_dev_s mtd; /* MTD interface */ + FAR struct spi_dev_s *dev; /* Saved SPI interface instance */ + uint8_t sectorshift; + uint8_t pageshift; + uint16_t nsectors; + uint32_t npages; + const struct ramtron_parts_s *part; /* part instance */ +}; + +/************************************************************************************ + * Supported Part Lists + ************************************************************************************/ + +// Defines the initial speed compatible with all devices. In case of RAMTRON +// the defined devices within the part list have all the same speed. +#define RAMTRON_INIT_CLK_MAX 40000000UL + +static struct ramtron_parts_s ramtron_parts[] = +{ + { + "FM25V02", /* name */ + 0x22, /* id1 */ + 0x00, /* id2 */ + 32L*1024L, /* size */ + 2, /* addr_len */ + 40000000 /* speed */ + }, + { + "FM25VN02", /* name */ + 0x22, /* id1 */ + 0x01, /* id2 */ + 32L*1024L, /* size */ + 2, /* addr_len */ + 40000000 /* speed */ + }, + { + "FM25V05", /* name */ + 0x23, /* id1 */ + 0x00, /* id2 */ + 64L*1024L, /* size */ + 2, /* addr_len */ + 40000000 /* speed */ + }, + { + "FM25VN05", /* name */ + 0x23, /* id1 */ + 0x01, /* id2 */ + 64L*1024L, /* size */ + 2, /* addr_len */ + 40000000 /* speed */ + }, + { + "FM25V10", /* name */ + 0x24, /* id1 */ + 0x00, /* id2 */ + 128L*1024L, /* size */ + 3, /* addr_len */ + 40000000 /* speed */ + }, + { + "FM25VN10", /* name */ + 0x24, /* id1 */ + 0x01, /* id2 */ + 128L*1024L, /* size */ + 3, /* addr_len */ + 40000000 /* speed */ + }, +#ifdef CONFIG_RAMTRON_FRAM_NON_JEDEC + { + "FM25H20", /* name */ + 0xff, /* id1 */ + 0xff, /* id2 */ + 256L*1024L, /* size */ + 3, /* addr_len */ + 40000000 /* speed */ + }, + { + NULL, /* name */ + 0, /* id1 */ + 0, /* id2 */ + 0, /* size */ + 0, /* addr_len */ + 0 /* speed */ + } +#endif +}; + + +/************************************************************************************ + * Private Function Prototypes + ************************************************************************************/ + +/* Helpers */ + +static void ramtron_lock(FAR struct spi_dev_s *dev); +static inline void ramtron_unlock(FAR struct spi_dev_s *dev); +static inline int ramtron_readid(struct ramtron_dev_s *priv); +static void ramtron_waitwritecomplete(struct ramtron_dev_s *priv); +static void ramtron_writeenable(struct ramtron_dev_s *priv); +static inline void ramtron_pagewrite(struct ramtron_dev_s *priv, FAR const uint8_t *buffer, + off_t offset); + +/* MTD driver methods */ + +static int ramtron_erase(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks); +static ssize_t ramtron_bread(FAR struct mtd_dev_s *dev, off_t startblock, + size_t nblocks, FAR uint8_t *buf); +static ssize_t ramtron_bwrite(FAR struct mtd_dev_s *dev, off_t startblock, + size_t nblocks, FAR const uint8_t *buf); +static ssize_t ramtron_read(FAR struct mtd_dev_s *dev, off_t offset, size_t nbytes, + FAR uint8_t *buffer); +static int ramtron_ioctl(FAR struct mtd_dev_s *dev, int cmd, unsigned long arg); + +/************************************************************************************ + * Private Data + ************************************************************************************/ + +/************************************************************************************ + * Private Functions + ************************************************************************************/ + +/************************************************************************************ + * Name: ramtron_lock + ************************************************************************************/ + +static void ramtron_lock(FAR struct spi_dev_s *dev) +{ + /* On SPI busses where there are multiple devices, it will be necessary to + * lock SPI to have exclusive access to the busses for a sequence of + * transfers. The bus should be locked before the chip is selected. + * + * This is a blocking call and will not return until we have exclusiv access to + * the SPI buss. We will retain that exclusive access until the bus is unlocked. + */ + + (void)SPI_LOCK(dev, true); + + /* After locking the SPI bus, the we also need call the setfrequency, setbits, and + * setmode methods to make sure that the SPI is properly configured for the device. + * If the SPI buss is being shared, then it may have been left in an incompatible + * state. + */ + + SPI_SETMODE(dev, SPIDEV_MODE3); + SPI_SETBITS(dev, 8); + + (void)SPI_SETFREQUENCY(dev, RAMTRON_INIT_CLK_MAX); +} + +/************************************************************************************ + * Name: ramtron_unlock + ************************************************************************************/ + +static inline void ramtron_unlock(FAR struct spi_dev_s *dev) +{ + (void)SPI_LOCK(dev, false); +} + +/************************************************************************************ + * Name: ramtron_readid + ************************************************************************************/ + +static inline int ramtron_readid(struct ramtron_dev_s *priv) +{ + uint16_t manufacturer, memory, capacity, part; + int i; + + fvdbg("priv: %p\n", priv); + + /* Lock the SPI bus, configure the bus, and select this FLASH part. */ + + ramtron_lock(priv->dev); + SPI_SELECT(priv->dev, SPIDEV_FLASH, true); + + /* Send the "Read ID (RDID)" command and read the first three ID bytes */ + + (void)SPI_SEND(priv->dev, RAMTRON_RDID); + for (i=0; i<6; i++) manufacturer = SPI_SEND(priv->dev, RAMTRON_DUMMY); + memory = SPI_SEND(priv->dev, RAMTRON_DUMMY); + capacity = SPI_SEND(priv->dev, RAMTRON_DUMMY); // fram.id1 + part = SPI_SEND(priv->dev, RAMTRON_DUMMY); // fram.id2 + + /* Deselect the FLASH and unlock the bus */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, false); + ramtron_unlock(priv->dev); + + // Select part from the part list + for (priv->part = ramtron_parts; + priv->part->name != NULL && !(priv->part->id1 == capacity && priv->part->id2 == part); + priv->part++); + + if (priv->part->name) { + fvdbg("RAMTRON %s of size %d bytes (mf:%02x mem:%02x cap:%02x part:%02x)\n", + priv->part->name, priv->part->size, manufacturer, memory, capacity, part); + + priv->sectorshift = RAMTRON_EMULATE_SECTOR_SHIFT; + priv->nsectors = priv->part->size / (1 << RAMTRON_EMULATE_SECTOR_SHIFT); + priv->pageshift = RAMTRON_EMULATE_PAGE_SHIFT; + priv->npages = priv->part->size / (1 << RAMTRON_EMULATE_PAGE_SHIFT); + return OK; + } + + fvdbg("RAMTRON device not found\n"); + return -ENODEV; +} + +/************************************************************************************ + * Name: ramtron_waitwritecomplete + ************************************************************************************/ + +static void ramtron_waitwritecomplete(struct ramtron_dev_s *priv) +{ + uint8_t status; + + /* Select this FLASH part */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, true); + + /* Send "Read Status Register (RDSR)" command */ + + (void)SPI_SEND(priv->dev, RAMTRON_RDSR); + + /* Loop as long as the memory is busy with a write cycle */ + + do + { + /* Send a dummy byte to generate the clock needed to shift out the status */ + + status = SPI_SEND(priv->dev, RAMTRON_DUMMY); + } + while ((status & RAMTRON_SR_WIP) != 0); + + /* Deselect the FLASH */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, false); + fvdbg("Complete\n"); +} + +/************************************************************************************ + * Name: ramtron_writeenable + ************************************************************************************/ + +static void ramtron_writeenable(struct ramtron_dev_s *priv) +{ + /* Select this FLASH part */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, true); + + /* Send "Write Enable (WREN)" command */ + + (void)SPI_SEND(priv->dev, RAMTRON_WREN); + + /* Deselect the FLASH */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, false); + fvdbg("Enabled\n"); +} + +/************************************************************************************ + * Name: ramtron_sendaddr + ************************************************************************************/ + +static inline void ramtron_sendaddr(const struct ramtron_dev_s *priv, uint32_t addr) +{ + DEBUGASSERT(priv->part->addr_len == 3 || priv->part->addr_len == 2); + + if (priv->part->addr_len == 3) + (void)SPI_SEND(priv->dev, (addr >> 16) & 0xff); + + (void)SPI_SEND(priv->dev, (addr >> 8) & 0xff); + (void)SPI_SEND(priv->dev, addr & 0xff); +} + +/************************************************************************************ + * Name: ramtron_pagewrite + ************************************************************************************/ + +static inline void ramtron_pagewrite(struct ramtron_dev_s *priv, FAR const uint8_t *buffer, + off_t page) +{ + off_t offset = page << priv->pageshift; + + fvdbg("page: %08lx offset: %08lx\n", (long)page, (long)offset); + + /* Wait for any preceding write to complete. We could simplify things by + * perform this wait at the end of each write operation (rather than at + * the beginning of ALL operations), but have the wait first will slightly + * improve performance. + */ + + ramtron_waitwritecomplete(priv); + + /* Enable the write access to the FLASH */ + + ramtron_writeenable(priv); + + /* Select this FLASH part */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, true); + + /* Send "Page Program (PP)" command */ + + (void)SPI_SEND(priv->dev, RAMTRON_WRITE); + + /* Send the page offset high byte first. */ + + ramtron_sendaddr(priv, offset); + + /* Then write the specified number of bytes */ + + SPI_SNDBLOCK(priv->dev, buffer, 1 << priv->pageshift); + + /* Deselect the FLASH: Chip Select high */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, false); + fvdbg("Written\n"); +} + +/************************************************************************************ + * Name: ramtron_erase + ************************************************************************************/ + +static int ramtron_erase(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks) +{ + fvdbg("startblock: %08lx nblocks: %d\n", (long)startblock, (int)nblocks); + fvdbg("On RAMTRON devices erasing makes no sense, returning as OK\n"); + return (int)nblocks; +} + +/************************************************************************************ + * Name: ramtron_bread + ************************************************************************************/ + +static ssize_t ramtron_bread(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks, + FAR uint8_t *buffer) +{ + FAR struct ramtron_dev_s *priv = (FAR struct ramtron_dev_s *)dev; + ssize_t nbytes; + + fvdbg("startblock: %08lx nblocks: %d\n", (long)startblock, (int)nblocks); + + /* On this device, we can handle the block read just like the byte-oriented read */ + + nbytes = ramtron_read(dev, startblock << priv->pageshift, nblocks << priv->pageshift, buffer); + if (nbytes > 0) + { + return nbytes >> priv->pageshift; + } + return (int)nbytes; +} + +/************************************************************************************ + * Name: ramtron_bwrite + ************************************************************************************/ + +static ssize_t ramtron_bwrite(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks, + FAR const uint8_t *buffer) +{ + FAR struct ramtron_dev_s *priv = (FAR struct ramtron_dev_s *)dev; + size_t blocksleft = nblocks; + + fvdbg("startblock: %08lx nblocks: %d\n", (long)startblock, (int)nblocks); + + /* Lock the SPI bus and write each page to FLASH */ + + ramtron_lock(priv->dev); + while (blocksleft-- > 0) + { + ramtron_pagewrite(priv, buffer, startblock); + startblock++; + } + ramtron_unlock(priv->dev); + + return nblocks; +} + +/************************************************************************************ + * Name: ramtron_read + ************************************************************************************/ + +static ssize_t ramtron_read(FAR struct mtd_dev_s *dev, off_t offset, size_t nbytes, + FAR uint8_t *buffer) +{ + FAR struct ramtron_dev_s *priv = (FAR struct ramtron_dev_s *)dev; + + fvdbg("offset: %08lx nbytes: %d\n", (long)offset, (int)nbytes); + + /* Wait for any preceding write to complete. We could simplify things by + * perform this wait at the end of each write operation (rather than at + * the beginning of ALL operations), but have the wait first will slightly + * improve performance. + */ + + ramtron_waitwritecomplete(priv); + + /* Lock the SPI bus and select this FLASH part */ + + ramtron_lock(priv->dev); + SPI_SELECT(priv->dev, SPIDEV_FLASH, true); + + /* Send "Read from Memory " instruction */ + + (void)SPI_SEND(priv->dev, RAMTRON_READ); + + /* Send the page offset high byte first. */ + + ramtron_sendaddr(priv, offset); + + /* Then read all of the requested bytes */ + + SPI_RECVBLOCK(priv->dev, buffer, nbytes); + + /* Deselect the FLASH and unlock the SPI bus */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, false); + ramtron_unlock(priv->dev); + fvdbg("return nbytes: %d\n", (int)nbytes); + return nbytes; +} + +/************************************************************************************ + * Name: ramtron_ioctl + ************************************************************************************/ + +static int ramtron_ioctl(FAR struct mtd_dev_s *dev, int cmd, unsigned long arg) +{ + FAR struct ramtron_dev_s *priv = (FAR struct ramtron_dev_s *)dev; + int ret = -EINVAL; /* Assume good command with bad parameters */ + + fvdbg("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) + { + /* Populate the geometry structure with information need to know + * the capacity and how to access the device. + * + * NOTE: that the device is treated as though it where just an array + * of fixed size blocks. That is most likely not true, but the client + * will expect the device logic to do whatever is necessary to make it + * appear so. + */ + + geo->blocksize = (1 << priv->pageshift); + geo->erasesize = (1 << priv->sectorshift); + geo->neraseblocks = priv->nsectors; + ret = OK; + + fvdbg("blocksize: %d erasesize: %d neraseblocks: %d\n", + geo->blocksize, geo->erasesize, geo->neraseblocks); + } + } + break; + + case MTDIOC_BULKERASE: + fvdbg("BULDERASE: Makes no sense in ramtron. Let's confirm operation as OK\n"); + ret = OK; + break; + + case MTDIOC_XIPBASE: + default: + ret = -ENOTTY; /* Bad command */ + break; + } + + fvdbg("return %d\n", ret); + return ret; +} + +/************************************************************************************ + * Public Functions + ************************************************************************************/ + +/************************************************************************************ + * Name: ramtron_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 *ramtron_initialize(FAR struct spi_dev_s *dev) +{ + FAR struct ramtron_dev_s *priv; + + fvdbg("dev: %p\n", dev); + + /* Allocate a state structure (we allocate the structure instead of using + * a fixed, static allocation so that we can handle multiple FLASH devices. + * The current implementation would handle only one FLASH part per SPI + * device (only because of the SPIDEV_FLASH definition) and so would have + * to be extended to handle multiple FLASH parts on the same SPI bus. + */ + + priv = (FAR struct ramtron_dev_s *)kmalloc(sizeof(struct ramtron_dev_s)); + if (priv) + { + /* Initialize the allocated structure */ + + priv->mtd.erase = ramtron_erase; + priv->mtd.bread = ramtron_bread; + priv->mtd.bwrite = ramtron_bwrite; + priv->mtd.read = ramtron_read; + priv->mtd.ioctl = ramtron_ioctl; + priv->dev = dev; + + /* Deselect the FLASH */ + + SPI_SELECT(dev, SPIDEV_FLASH, false); + + /* Identify the FLASH chip and get its capacity */ + + if (ramtron_readid(priv) != OK) + { + /* Unrecognized! Discard all of that work we just did and return NULL */ + kfree(priv); + priv = NULL; + } + } + + /* Return the implementation-specific state structure as the MTD device */ + + fvdbg("Return %p\n", priv); + return (FAR struct mtd_dev_s *)priv; +} diff --git a/nuttx/drivers/mtd/skeleton.c b/nuttx/drivers/mtd/skeleton.c new file mode 100644 index 000000000..a2fb98238 --- /dev/null +++ b/nuttx/drivers/mtd/skeleton.c @@ -0,0 +1,275 @@ +/**************************************************************************** + * drivers/mtd/skeleton.c + * + * Copyright (C) 2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <gnutt@nuttx.org> + * + * 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 <errno.h> + +#include <nuttx/fs/ioctl.h> +#include <nuttx/mtd.h> + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * 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 skel_dev_s. + */ + +struct skel_dev_s +{ + struct mtd_dev_s mtd; + + /* Other implementation specific data may follow here */ +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/* MTD driver methods */ + +static int skel_erase(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks); +static ssize_t skel_bread(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks, + FAR uint8_t *buf); +static ssize_t skel_bwrite(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks, + FAR const uint8_t *buf); +static ssize_t skel_read(FAR struct mtd_dev_s *dev, off_t offset, size_t nbytes, + FAR uint8_t *buffer); +static int skel_ioctl(FAR struct mtd_dev_s *dev, int cmd, unsigned long arg); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static struct skel_dev_s g_skeldev = +{ + { skel_erase, skel_rbead, skel_bwrite, skel_read, skel_ioctl }, + /* Initialization of any other implemenation specific data goes here */ +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: skel_erase + ****************************************************************************/ + +static int skel_erase(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks) +{ + FAR struct skel_dev_s *priv = (FAR struct skel_dev_s *)dev; + + /* The interface definition assumes that all erase blocks ar the same size. + * If that is not true for this particular device, then transform the + * start block and nblocks as necessary. + */ + + /* Erase the specified blocks and return status (OK or a negated errno) */ + + return OK; +} + +/**************************************************************************** + * Name: skel_bread + ****************************************************************************/ + +static ssize_t skel_bread(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks, + FAR uint8_t *buf) +{ + FAR struct skel_dev_s *priv = (FAR struct skel_dev_s *)dev; + + /* The interface definition assumes that all read/write blocks ar the same size. + * If that is not true for this particular device, then transform the + * start block and nblocks as necessary. + */ + + /* Read the specified blocks into the provided user buffer and return status + * (The positive, number of blocks actually read or a negated errno). + */ + + return 0; +} + +/**************************************************************************** + * Name: skel_bwrite + ****************************************************************************/ + +static ssize_t skel_bwrite(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks, + FAR const uint8_t *buf) +{ + FAR struct skel_dev_s *priv = (FAR struct skel_dev_s *)dev; + + /* The interface definition assumes that all read/write blocks ar the same size. + * If that is not true for this particular device, then transform the + * start block and nblocks as necessary. + */ + + /* Write the specified blocks from the provided user buffer and return status + * (The positive, number of blocks actually written or a negated errno) + */ + + return 0; +} + +/**************************************************************************** + * Name: skel_read + ****************************************************************************/ + +static ssize_t skel_read(FAR struct mtd_dev_s *dev, off_t offset, size_t nbytes, + FAR uint8_t *buffer) +{ + FAR struct skel_dev_s *priv = (FAR struct skel_dev_s *)dev; + + /* Some devices may support byte oriented read (optional). Byte-oriented + * writing is inherently block oriented on most MTD devices and is not supported. + * It is recommended that low-level drivers not support read() if it requires + * buffering -- let the higher level logic handle that. If the read method is + * not implemented, just set the method pointer to NULL in the struct mtd_dev_s + * instance. + */ + + /* The interface definition assumes that all read/write blocks ar the same size. + * If that is not true for this particular device, then transform the + * start block and nblocks as necessary. + */ + + /* Read the specified blocks into the provided user buffer and return status + * (The positive, number of blocks actually read or a negated errno) + */ + + return 0; +} + +/**************************************************************************** + * Name: skel_ioctl + ****************************************************************************/ + +static int skel_ioctl(FAR struct mtd_dev_s *dev, int cmd, unsigned long arg) +{ + FAR struct skel_dev_s *priv = (FAR struct skel_dev_s *)dev; + int ret = -EINVAL; /* Assume good command with bad parameters */ + + switch (cmd) + { + case MTDIOC_GEOMETRY: + { + FAR struct mtd_geometry_s *geo = (FARstruct mtd_geometry_s *)arg; + if (geo) + { + /* Populate the geometry structure with information need to know + * the capacity and how to access the device. + * + * NOTE: that the device is treated as though it where just an array + * of fixed size blocks. That is most likely not true, but the client + * will expect the device logic to do whatever is necessary to make it + * appear so. + */ + + geo->blocksize = 512; /* Size of one read/write block */ + geo->erasesize = 4096; /* Size of one erase block */ + geo->neraseblocks = 1024; /* Number of erase blocks */ + ret = OK; + } + } + break; + + case MTDIOC_XIPBASE: + { + FAR void **ppv = (FAR void**)arg; + + if (ppv) + { + /* If media is directly acccesible, return (void*) base address + * of device memory. NULL otherwise. It is acceptable to omit + * this case altogether and simply return -ENOTTY. + */ + + *ppv = NULL; + ret = OK; + } + } + break; + + case MTDIOC_BULKERASE: + { + /* Erase the entire device */ + + ret = OK; + } + break; + + default: + ret = -ENOTTY; /* Bad command */ + break; + } + + return ret; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: skel_initialize + * + * Description: + * Create and initialize an 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). + * + ****************************************************************************/ + +void skel_initialize(void) +{ + /* Perform initialization as necessary */ + + /* Return the implementation-specific state structure as the MTD device */ + + return (FAR struct mtd_dev_s *)&g_skeldev; +} diff --git a/nuttx/drivers/mtd/sst25.c b/nuttx/drivers/mtd/sst25.c new file mode 100644 index 000000000..01838f078 --- /dev/null +++ b/nuttx/drivers/mtd/sst25.c @@ -0,0 +1,1250 @@ +/************************************************************************************ + * drivers/mtd/m25px.c + * Driver for SPI-based SST25 FLASH. + * + * Copyright (C) 2012 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <gnutt@nuttx.org> + * + * 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 <string.h> +#include <assert.h> +#include <errno.h> +#include <debug.h> + +#include <nuttx/kmalloc.h> +#include <nuttx/fs/ioctl.h> +#include <nuttx/spi.h> +#include <nuttx/mtd.h> + +/************************************************************************************ + * Pre-processor Definitions + ************************************************************************************/ +/* Configuration ********************************************************************/ +/* Per the data sheet, the SST25 parts can be driven with either SPI mode 0 (CPOL=0 + * and CPHA=0) or mode 3 (CPOL=1 and CPHA=1). But I have heard that other devices + * can operate in mode 0 or 1. So you may need to specify CONFIG_SST25_SPIMODE to + * select the best mode for your device. If CONFIG_SST25_SPIMODE is not defined, + * mode 0 will be used. + */ + +#ifndef CONFIG_SST25_SPIMODE +# define CONFIG_SST25_SPIMODE SPIDEV_MODE0 +#endif + +/* SPI Frequency. May be up to 25MHz. */ + +#ifndef CONFIG_SST25_SPIFREQUENCY +# define CONFIG_SST25_SPIFREQUENCY 20000000 +#endif + +/* There is a bug in the current code when using the higher speed AAI write sequence. + * The nature of the bug is that the WRDI instruction is not working. At the end + * of the AAI sequence, the status register continues to report that the SST25 is + * write enabled (WEL bit) and in AAI mode (AAI bit). This *must* be fixed in any + * production code if you want to have proper write performance. + */ + +#warning "REVISIT" +#undef CONFIG_SST25_SLOWWRITE +#define CONFIG_SST25_SLOWWRITE 1 + +/* SST25 Instructions ***************************************************************/ +/* Command Value Description Addr Data */ +/* Dummy */ +#define SST25_READ 0x03 /* Read data bytes 3 0 >=1 */ +#define SST25_FAST_READ 0x0b /* Higher speed read 3 1 >=1 */ +#define SST25_SE 0x20 /* 4Kb Sector erase 3 0 0 */ +#define SST25_BE32 0x52 /* 32Kbit block Erase 3 0 0 */ +#define SST25_BE64 0xd8 /* 64Kbit block Erase 3 0 0 */ +#define SST25_CE 0xc7 /* Chip erase 0 0 0 */ +#define SST25_CE_ALT 0x60 /* Chip erase (alternate) 0 0 0 */ +#define SST25_BP 0x02 /* Byte program 3 0 1 */ +#define SST25_AAI 0xad /* Auto address increment 3 0 >=2 */ +#define SST25_RDSR 0x05 /* Read status register 0 0 >=1 */ +#define SST25_EWSR 0x50 /* Write enable status 0 0 0 */ +#define SST25_WRSR 0x01 /* Write Status Register 0 0 1 */ +#define SST25_WREN 0x06 /* Write Enable 0 0 0 */ +#define SST25_WRDI 0x04 /* Write Disable 0 0 0 */ +#define SST25_RDID 0xab /* Read Identification 0 0 >=1 */ +#define SST25_RDID_ALT 0x90 /* Read Identification (alt) 0 0 >=1 */ +#define SST25_JEDEC_ID 0x9f /* JEDEC ID read 0 0 >=3 */ +#define SST25_EBSY 0x70 /* Enable SO RY/BY# status 0 0 0 */ +#define SST25_DBSY 0x80 /* Disable SO RY/BY# status 0 0 0 */ + +/* SST25 Registers ******************************************************************/ +/* Read ID (RDID) register values */ + +#define SST25_MANUFACTURER 0xbf /* SST manufacturer ID */ +#define SST25_VF032_DEVID 0x20 /* SSTVF032B device ID */ + +/* JEDEC Read ID register values */ + +#define SST25_JEDEC_MANUFACTURER 0xbf /* SST manufacturer ID */ +#define SST25_JEDEC_MEMORY_TYPE 0x25 /* SST25 memory type */ +#define SST25_JEDEC_MEMORY_CAPACITY 0x4a /* SST25VF032B memory capacity */ + +/* Status register bit definitions */ + +#define SST25_SR_BUSY (1 << 0) /* Bit 0: Write in progress */ +#define SST25_SR_WEL (1 << 1) /* Bit 1: Write enable latch bit */ +#define SST25_SR_BP_SHIFT (2) /* Bits 2-5: Block protect bits */ +#define SST25_SR_BP_MASK (15 << SST25_SR_BP_SHIFT) +# define SST25_SR_BP_NONE (0 << SST25_SR_BP_SHIFT) /* Unprotected */ +# define SST25_SR_BP_UPPER64th (1 << SST25_SR_BP_SHIFT) /* Upper 64th */ +# define SST25_SR_BP_UPPER32nd (2 << SST25_SR_BP_SHIFT) /* Upper 32nd */ +# define SST25_SR_BP_UPPER16th (3 << SST25_SR_BP_SHIFT) /* Upper 16th */ +# define SST25_SR_BP_UPPER8th (4 << SST25_SR_BP_SHIFT) /* Upper 8th */ +# define SST25_SR_BP_UPPERQTR (5 << SST25_SR_BP_SHIFT) /* Upper quarter */ +# define SST25_SR_BP_UPPERHALF (6 << SST25_SR_BP_SHIFT) /* Upper half */ +# define SST25_SR_BP_ALL (7 << SST25_SR_BP_SHIFT) /* All sectors */ +#define SST25_SR_AAI (1 << 6) /* Bit 6: Auto Address increment programming */ +#define SST25_SR_SRWD (1 << 7) /* Bit 7: Status register write protect */ + +#define SST25_DUMMY 0xa5 + +/* Chip Geometries ******************************************************************/ +/* SST25VF512 capacity is 512Kbit (64Kbit x 8) = 64Kb (8Kb x 8)*/ +/* SST25VF010 capacity is 1Mbit (128Kbit x 8) = 128Kb (16Kb x 8*/ +/* SST25VF520 capacity is 2Mbit (256Kbit x 8) = 256Kb (32Kb x 8) */ +/* SST25VF540 capacity is 4Mbit (512Kbit x 8) = 512Kb (64Kb x 8) */ +/* SST25VF080 capacity is 8Mbit (1024Kbit x 8) = 1Mb (128Kb x 8) */ +/* SST25VF016 capacity is 16Mbit (2048Kbit x 8) = 2Mb (256Kb x 8) */ +/* Not yet supported */ + +/* SST25VF032 capacity is 32Mbit (4096Kbit x 8) = 4Mb (512kb x 8) */ + +#define SST25_VF032_SECTOR_SHIFT 12 /* Sector size 1 << 12 = 4Kb */ +#define SST25_VF032_NSECTORS 1024 /* 1024 sectors x 4096 bytes/sector = 4Mb */ + +#ifdef CONFIG_SST25_SECTOR512 /* Simulate a 512 byte sector */ +# define SST25_SECTOR_SHIFT 9 /* Sector size 1 << 9 = 512 bytes */ +# define SST25_SECTOR_SIZE 512 /* Sector size = 512 bytes */ +#endif + +#define SST25_ERASED_STATE 0xff /* State of FLASH when erased */ + +/* Cache flags */ + +#define SST25_CACHE_VALID (1 << 0) /* 1=Cache has valid data */ +#define SST25_CACHE_DIRTY (1 << 1) /* 1=Cache is dirty */ +#define SST25_CACHE_ERASED (1 << 2) /* 1=Backing FLASH is erased */ + +#define IS_VALID(p) ((((p)->flags) & SST25_CACHE_VALID) != 0) +#define IS_DIRTY(p) ((((p)->flags) & SST25_CACHE_DIRTY) != 0) +#define IS_ERASED(p) ((((p)->flags) & SST25_CACHE_DIRTY) != 0) + +#define SET_VALID(p) do { (p)->flags |= SST25_CACHE_VALID; } while (0) +#define SET_DIRTY(p) do { (p)->flags |= SST25_CACHE_DIRTY; } while (0) +#define SET_ERASED(p) do { (p)->flags |= SST25_CACHE_DIRTY; } while (0) + +#define CLR_VALID(p) do { (p)->flags &= ~SST25_CACHE_VALID; } while (0) +#define CLR_DIRTY(p) do { (p)->flags &= ~SST25_CACHE_DIRTY; } while (0) +#define CLR_ERASED(p) do { (p)->flags &= ~SST25_CACHE_DIRTY; } while (0) + +/************************************************************************************ + * 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 sst25_dev_s. + */ + +struct sst25_dev_s +{ + struct mtd_dev_s mtd; /* MTD interface */ + FAR struct spi_dev_s *dev; /* Saved SPI interface instance */ + uint16_t nsectors; /* Number of erase sectors */ + uint8_t sectorshift; /* Log2 of erase sector size */ + +#if defined(CONFIG_SST25_SECTOR512) && !defined(CONFIG_SST25_READONLY) + uint8_t flags; /* Buffered sector flags */ + uint16_t esectno; /* Erase sector number in the cache*/ + FAR uint8_t *sector; /* Allocated sector data */ +#endif +}; + +/************************************************************************************ + * Private Function Prototypes + ************************************************************************************/ + +/* Helpers */ + +static void sst25_lock(FAR struct spi_dev_s *dev); +static inline void sst25_unlock(FAR struct spi_dev_s *dev); +static inline int sst25_readid(FAR struct sst25_dev_s *priv); +#ifndef CONFIG_SST25_READONLY +static void sst25_unprotect(FAR struct spi_dev_s *dev); +#endif +static uint8_t sst25_waitwritecomplete(FAR struct sst25_dev_s *priv); +static inline void sst25_wren(FAR struct sst25_dev_s *priv); +static inline void sst25_wrdi(FAR struct sst25_dev_s *priv); +static void sst25_sectorerase(FAR struct sst25_dev_s *priv, off_t offset); +static inline int sst25_chiperase(FAR struct sst25_dev_s *priv); +static void sst25_byteread(FAR struct sst25_dev_s *priv, FAR uint8_t *buffer, + off_t address, size_t nbytes); +#ifndef CONFIG_SST25_READONLY +#ifdef CONFIG_SST25_SLOWWRITE +static void sst25_bytewrite(FAR struct sst25_dev_s *priv, FAR const uint8_t *buffer, + off_t address, size_t nbytes); +#else +static void sst25_wordwrite(FAR struct sst25_dev_s *priv, FAR const uint8_t *buffer, + off_t address, size_t nbytes); +#endif +#ifdef CONFIG_SST25_SECTOR512 +static void sst25_cacheflush(struct sst25_dev_s *priv); +static FAR uint8_t *sst25_cacheread(struct sst25_dev_s *priv, off_t sector); +static void sst25_cacheerase(struct sst25_dev_s *priv, off_t sector); +static void sst25_cachewrite(FAR struct sst25_dev_s *priv, FAR const uint8_t *buffer, + off_t sector, size_t nsectors); +#endif +#endif + +/* MTD driver methods */ + +static int sst25_erase(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks); +static ssize_t sst25_bread(FAR struct mtd_dev_s *dev, off_t startblock, + size_t nblocks, FAR uint8_t *buf); +static ssize_t sst25_bwrite(FAR struct mtd_dev_s *dev, off_t startblock, + size_t nblocks, FAR const uint8_t *buf); +static ssize_t sst25_read(FAR struct mtd_dev_s *dev, off_t offset, size_t nbytes, + FAR uint8_t *buffer); +static int sst25_ioctl(FAR struct mtd_dev_s *dev, int cmd, unsigned long arg); + +/************************************************************************************ + * Private Data + ************************************************************************************/ + +/************************************************************************************ + * Private Functions + ************************************************************************************/ + +/************************************************************************************ + * Name: sst25_lock + ************************************************************************************/ + +static void sst25_lock(FAR struct spi_dev_s *dev) +{ + /* On SPI busses where there are multiple devices, it will be necessary to + * lock SPI to have exclusive access to the busses for a sequence of + * transfers. The bus should be locked before the chip is selected. + * + * This is a blocking call and will not return until we have exclusiv access to + * the SPI buss. We will retain that exclusive access until the bus is unlocked. + */ + + (void)SPI_LOCK(dev, true); + + /* After locking the SPI bus, the we also need call the setfrequency, setbits, and + * setmode methods to make sure that the SPI is properly configured for the device. + * If the SPI buss is being shared, then it may have been left in an incompatible + * state. + */ + + SPI_SETMODE(dev, CONFIG_SST25_SPIMODE); + SPI_SETBITS(dev, 8); + (void)SPI_SETFREQUENCY(dev, CONFIG_SST25_SPIFREQUENCY); +} + +/************************************************************************************ + * Name: sst25_unlock + ************************************************************************************/ + +static inline void sst25_unlock(FAR struct spi_dev_s *dev) +{ + (void)SPI_LOCK(dev, false); +} + +/************************************************************************************ + * Name: sst25_readid + ************************************************************************************/ + +static inline int sst25_readid(struct sst25_dev_s *priv) +{ + uint16_t manufacturer; + uint16_t memory; + uint16_t capacity; + + fvdbg("priv: %p\n", priv); + + /* Lock the SPI bus, configure the bus, and select this FLASH part. */ + + sst25_lock(priv->dev); + SPI_SELECT(priv->dev, SPIDEV_FLASH, true); + + /* Send the "Read ID (RDID)" command and read the first three ID bytes */ + + (void)SPI_SEND(priv->dev, SST25_JEDEC_ID); + manufacturer = SPI_SEND(priv->dev, SST25_DUMMY); + memory = SPI_SEND(priv->dev, SST25_DUMMY); + capacity = SPI_SEND(priv->dev, SST25_DUMMY); + + /* Deselect the FLASH and unlock the bus */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, false); + sst25_unlock(priv->dev); + + fvdbg("manufacturer: %02x memory: %02x capacity: %02x\n", + manufacturer, memory, capacity); + + /* Check for a valid manufacturer and memory type */ + + if (manufacturer == SST25_JEDEC_MANUFACTURER && memory == SST25_JEDEC_MEMORY_TYPE) + { + /* Okay.. is it a FLASH capacity that we understand? This should be extended + * support other members of the SST25 family. + */ + + if (capacity == SST25_JEDEC_MEMORY_CAPACITY) + { + /* Save the FLASH geometry */ + + priv->sectorshift = SST25_VF032_SECTOR_SHIFT; + priv->nsectors = SST25_VF032_NSECTORS; + return OK; + } + } + + return -ENODEV; +} + +/************************************************************************************ + * Name: sst25_unprotect + ************************************************************************************/ + +#ifndef CONFIG_SST25_READONLY +static void sst25_unprotect(FAR struct spi_dev_s *dev) +{ + /* Select this FLASH part */ + + SPI_SELECT(dev, SPIDEV_FLASH, true); + + /* Send "Write enable status (EWSR)" */ + + SPI_SEND(dev, SST25_EWSR); + + /* Re-select this FLASH part (This might not be necessary... but is it shown in + * the timing diagrams) + */ + + SPI_SELECT(dev, SPIDEV_FLASH, false); + SPI_SELECT(dev, SPIDEV_FLASH, true); + + /* Send "Write enable status (EWSR)" */ + + SPI_SEND(dev, SST25_WRSR); + + /* Following by the new status value */ + + SPI_SEND(dev, 0); +} +#endif + +/************************************************************************************ + * Name: sst25_waitwritecomplete + ************************************************************************************/ + +static uint8_t sst25_waitwritecomplete(struct sst25_dev_s *priv) +{ + uint8_t status; + + /* Are we the only device on the bus? */ + +#ifdef CONFIG_SPI_OWNBUS + + /* Select this FLASH part */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, true); + + /* Send "Read Status Register (RDSR)" command */ + + (void)SPI_SEND(priv->dev, SST25_RDSR); + + /* Loop as long as the memory is busy with a write cycle */ + + do + { + /* Send a dummy byte to generate the clock needed to shift out the status */ + + status = SPI_SEND(priv->dev, SST25_DUMMY); + } + while ((status & SST25_SR_BUSY) != 0); + + /* Deselect the FLASH */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, false); + +#else + + /* Loop as long as the memory is busy with a write cycle */ + + do + { + /* Select this FLASH part */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, true); + + /* Send "Read Status Register (RDSR)" command */ + + (void)SPI_SEND(priv->dev, SST25_RDSR); + + /* Send a dummy byte to generate the clock needed to shift out the status */ + + status = SPI_SEND(priv->dev, SST25_DUMMY); + + /* Deselect the FLASH */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, false); + + /* Given that writing could take up to few tens of milliseconds, and erasing + * could take more. The following short delay in the "busy" case will allow + * other peripherals to access the SPI bus. + */ + +#if 0 /* Makes writes too slow */ + if ((status & SST25_SR_BUSY) != 0) + { + sst25_unlock(priv->dev); + usleep(1000); + sst25_lock(priv->dev); + } +#endif + } + while ((status & SST25_SR_BUSY) != 0); +#endif + + return status; +} + +/************************************************************************************ + * Name: sst25_wren + ************************************************************************************/ + +static inline void sst25_wren(struct sst25_dev_s *priv) +{ + /* Select this FLASH part */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, true); + + /* Send "Write Enable (WREN)" command */ + + (void)SPI_SEND(priv->dev, SST25_WREN); + + /* Deselect the FLASH */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, false); +} + +/************************************************************************************ + * Name: sst25_wrdi + ************************************************************************************/ + +static inline void sst25_wrdi(struct sst25_dev_s *priv) +{ + /* Select this FLASH part */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, true); + + /* Send "Write Disable (WRDI)" command */ + + (void)SPI_SEND(priv->dev, SST25_WRDI); + + /* Deselect the FLASH */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, false); +} + +/************************************************************************************ + * Name: sst25_sectorerase + ************************************************************************************/ + +static void sst25_sectorerase(struct sst25_dev_s *priv, off_t sector) +{ + off_t address = sector << priv->sectorshift; + + fvdbg("sector: %08lx\n", (long)sector); + + /* Wait for any preceding write or erase operation to complete. */ + + (void)sst25_waitwritecomplete(priv); + + /* Send write enable instruction */ + + sst25_wren(priv); + + /* Select this FLASH part */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, true); + + /* Send the "Sector Erase (SE)" instruction */ + + (void)SPI_SEND(priv->dev, SST25_SE); + + /* Send the sector address high byte first. Only the most significant bits (those + * corresponding to the sector) have any meaning. + */ + + (void)SPI_SEND(priv->dev, (address >> 16) & 0xff); + (void)SPI_SEND(priv->dev, (address >> 8) & 0xff); + (void)SPI_SEND(priv->dev, address & 0xff); + + /* Deselect the FLASH */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, false); +} + +/************************************************************************************ + * Name: sst25_chiperase + ************************************************************************************/ + +static inline int sst25_chiperase(struct sst25_dev_s *priv) +{ + fvdbg("priv: %p\n", priv); + + /* Wait for any preceding write or erase operation to complete. */ + + (void)sst25_waitwritecomplete(priv); + + /* Send write enable instruction */ + + sst25_wren(priv); + + /* Select this FLASH part */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, true); + + /* Send the "Chip Erase (CE)" instruction */ + + (void)SPI_SEND(priv->dev, SST25_CE); + + /* Deselect the FLASH */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, false); + fvdbg("Return: OK\n"); + return OK; +} + +/************************************************************************************ + * Name: sst25_byteread + ************************************************************************************/ + +static void sst25_byteread(FAR struct sst25_dev_s *priv, FAR uint8_t *buffer, + off_t address, size_t nbytes) +{ + uint8_t status; + + fvdbg("address: %08lx nbytes: %d\n", (long)address, (int)nbytes); + + /* Wait for any preceding write or erase operation to complete. */ + + status = sst25_waitwritecomplete(priv); + DEBUGASSERT((status & (SST25_SR_WEL|SST25_SR_BP_MASK|SST25_SR_AAI)) == 0); + + /* Select this FLASH part */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, true); + + /* Send "Read from Memory " instruction */ + +#ifdef CONFIG_SST25_SLOWREAD + (void)SPI_SEND(priv->dev, SST25_READ); +#else + (void)SPI_SEND(priv->dev, SST25_FAST_READ); +#endif + + /* Send the address high byte first. */ + + (void)SPI_SEND(priv->dev, (address >> 16) & 0xff); + (void)SPI_SEND(priv->dev, (address >> 8) & 0xff); + (void)SPI_SEND(priv->dev, address & 0xff); + + /* Send a dummy byte */ + +#ifndef CONFIG_SST25_SLOWREAD + (void)SPI_SEND(priv->dev, SST25_DUMMY); +#endif + + /* Then read all of the requested bytes */ + + SPI_RECVBLOCK(priv->dev, buffer, nbytes); + + /* Deselect the FLASH */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, false); +} + +/************************************************************************************ + * Name: sst25_bytewrite + ************************************************************************************/ + +#if defined(CONFIG_SST25_SLOWWRITE) && !defined(CONFIG_SST25_READONLY) +static void sst25_bytewrite(struct sst25_dev_s *priv, FAR const uint8_t *buffer, + off_t address, size_t nbytes) +{ + uint8_t status; + + fvdbg("address: %08lx nwords: %d\n", (long)address, (int)nbytes); + DEBUGASSERT(priv && buffer); + + /* Write each byte individually */ + + for (; nbytes > 0; nbytes--) + { + /* Skip over bytes that are begin written to the erased state */ + + if (*buffer != SST25_ERASED_STATE) + { + /* Wait for any preceding write or erase operation to complete. */ + + status = sst25_waitwritecomplete(priv); + DEBUGASSERT((status & (SST25_SR_WEL|SST25_SR_BP_MASK|SST25_SR_AAI)) == 0); + + /* Enable write access to the FLASH */ + + sst25_wren(priv); + + /* Select this FLASH part */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, true); + + /* Send "Byte Program (BP)" command */ + + (void)SPI_SEND(priv->dev, SST25_BP); + + /* Send the byte address high byte first. */ + + (void)SPI_SEND(priv->dev, (address >> 16) & 0xff); + (void)SPI_SEND(priv->dev, (address >> 8) & 0xff); + (void)SPI_SEND(priv->dev, address & 0xff); + + /* Then write the single byte */ + + (void)SPI_SEND(priv->dev, *buffer); + + /* Deselect the FLASH and setup for the next pass through the loop */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, false); + } + + /* Advance to the next byte */ + + buffer++; + address++; + } +} +#endif + +/************************************************************************************ + * Name: sst25_wordwrite + ************************************************************************************/ + +#if !defined(CONFIG_SST25_SLOWWRITE) && !defined(CONFIG_SST25_READONLY) +static void sst25_wordwrite(struct sst25_dev_s *priv, FAR const uint8_t *buffer, + off_t address, size_t nbytes) +{ + size_t nwords = (nbytes + 1) >> 1; + uint8_t status; + + fvdbg("address: %08lx nwords: %d\n", (long)address, (int)nwords); + DEBUGASSERT(priv && buffer); + + /* Loop until all of the bytes have been written */ + + while (nwords > 0) + { + /* Skip over any data that is being written to the erased state */ + + while (nwords > 0 && + buffer[0] == SST25_ERASED_STATE && + buffer[1] == SST25_ERASED_STATE) + { + /* Decrement the word count and advance the write position */ + + nwords--; + buffer += 2; + address += 2; + } + + /* If there are no further non-erased bytes in the user buffer, then + * we are finished. + */ + + if (nwords <= 0) + { + return; + } + + /* Wait for any preceding write or erase operation to complete. */ + + status = sst25_waitwritecomplete(priv); + DEBUGASSERT((status & (SST25_SR_WEL|SST25_SR_BP_MASK|SST25_SR_AAI)) == 0); + + /* Enable write access to the FLASH */ + + sst25_wren(priv); + + /* Select this FLASH part */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, true); + + /* Send "Auto Address Increment (AAI)" command */ + + (void)SPI_SEND(priv->dev, SST25_AAI); + + /* Send the word address high byte first. */ + + (void)SPI_SEND(priv->dev, (address >> 16) & 0xff); + (void)SPI_SEND(priv->dev, (address >> 8) & 0xff); + (void)SPI_SEND(priv->dev, address & 0xff); + + /* Then write one 16-bit word */ + + SPI_SNDBLOCK(priv->dev, buffer, 2); + + /* Deselect the FLASH: Chip Select high */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, false); + + /* Decrement the word count and advance the write position */ + + nwords--; + buffer += 2; + address += 2; + + /* Now loop, writing 16-bits of data on each pass through the loop + * until all of the words have been transferred or until we encounter + * data to be written to the erased state. + */ + + while (nwords > 0 && + (buffer[0] != SST25_ERASED_STATE || + buffer[1] != SST25_ERASED_STATE)) + { + /* Wait for the preceding write to complete. */ + + status = sst25_waitwritecomplete(priv); + DEBUGASSERT((status & (SST25_SR_WEL|SST25_SR_BP_MASK|SST25_SR_AAI)) == (SST25_SR_WEL|SST25_SR_AAI)); + + /* Select this FLASH part */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, true); + + /* Send "Auto Address Increment (AAI)" command with no address */ + + (void)SPI_SEND(priv->dev, SST25_AAI); + + /* Then write one 16-bit word */ + + SPI_SNDBLOCK(priv->dev, buffer, 2); + + /* Deselect the FLASH: Chip Select high */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, false); + + /* Decrement the word count and advance the write position */ + + nwords--; + buffer += 2; + address += 2; + } + + /* Disable writing */ + + sst25_wrdi(priv); + } +} +#endif + +/************************************************************************************ + * Name: sst25_cacheflush + ************************************************************************************/ + +#if defined(CONFIG_SST25_SECTOR512) && !defined(CONFIG_SST25_READONLY) +static void sst25_cacheflush(struct sst25_dev_s *priv) +{ + /* If the cached is dirty (meaning that it no longer matches the old FLASH contents) + * or was erased (with the cache containing the correct FLASH contents), then write + * the cached erase block to FLASH. + */ + + if (IS_DIRTY(priv) || IS_ERASED(priv)) + { + /* Write entire erase block to FLASH */ + +#ifdef CONFIG_SST25_SLOWWRITE + sst25_bytewrite(priv, priv->sector, (off_t)priv->esectno << priv->sectorshift, + (1 << priv->sectorshift)); +#else + sst25_wordwrite(priv, priv->sector, (off_t)priv->esectno << priv->sectorshift, + (1 << priv->sectorshift)); +#endif + + /* The case is no long dirty and the FLASH is no longer erased */ + + CLR_DIRTY(priv); + CLR_ERASED(priv); + } +} +#endif + +/************************************************************************************ + * Name: sst25_cacheread + ************************************************************************************/ + +#if defined(CONFIG_SST25_SECTOR512) && !defined(CONFIG_SST25_READONLY) +static FAR uint8_t *sst25_cacheread(struct sst25_dev_s *priv, off_t sector) +{ + off_t esectno; + int shift; + int index; + + /* Convert from the 512 byte sector to the erase sector size of the device. For + * exmample, if the actual erase sector size if 4Kb (1 << 12), then we first + * shift to the right by 3 to get the sector number in 4096 increments. + */ + + shift = priv->sectorshift - SST25_SECTOR_SHIFT; + esectno = sector >> shift; + fvdbg("sector: %ld esectno: %d shift=%d\n", sector, esectno, shift); + + /* Check if the requested erase block is already in the cache */ + + if (!IS_VALID(priv) || esectno != priv->esectno) + { + /* No.. Flush any dirty erase block currently in the cache */ + + sst25_cacheflush(priv); + + /* Read the erase block into the cache */ + + sst25_byteread(priv, priv->sector, (esectno << priv->sectorshift), 1 << priv->sectorshift); + + /* Mark the sector as cached */ + + priv->esectno = esectno; + + SET_VALID(priv); /* The data in the cache is valid */ + CLR_DIRTY(priv); /* It should match the FLASH contents */ + CLR_ERASED(priv); /* The underlying FLASH has not been erased */ + } + + /* Get the index to the 512 sector in the erase block that holds the argument */ + + index = sector & ((1 << shift) - 1); + + /* Return the address in the cache that holds this sector */ + + return &priv->sector[index << SST25_SECTOR_SHIFT]; +} +#endif + +/************************************************************************************ + * Name: sst25_cacheerase + ************************************************************************************/ + +#if defined(CONFIG_SST25_SECTOR512) && !defined(CONFIG_SST25_READONLY) +static void sst25_cacheerase(struct sst25_dev_s *priv, off_t sector) +{ + FAR uint8_t *dest; + + /* First, make sure that the erase block containing the 512 byte sector is in + * the cache. + */ + + dest = sst25_cacheread(priv, sector); + + /* Erase the block containing this sector if it is not already erased. + * The erased indicated will be cleared when the data from the erase sector + * is read into the cache and set here when we erase the block. + */ + + if (!IS_ERASED(priv)) + { + off_t esectno = sector >> (priv->sectorshift - SST25_SECTOR_SHIFT); + fvdbg("sector: %ld esectno: %d\n", sector, esectno); + + sst25_sectorerase(priv, esectno); + SET_ERASED(priv); + } + + /* Put the cached sector data into the erase state and mart the cache as dirty + * (but don't update the FLASH yet. The caller will do that at a more optimal + * time). + */ + + memset(dest, SST25_ERASED_STATE, SST25_SECTOR_SIZE); + SET_DIRTY(priv); +} +#endif + +/************************************************************************************ + * Name: sst25_cachewrite + ************************************************************************************/ + +#if defined(CONFIG_SST25_SECTOR512) && !defined(CONFIG_SST25_READONLY) +static void sst25_cachewrite(FAR struct sst25_dev_s *priv, FAR const uint8_t *buffer, + off_t sector, size_t nsectors) +{ + FAR uint8_t *dest; + + for (; nsectors > 0; nsectors--) + { + /* First, make sure that the erase block containing 512 byte sector is in + * memory. + */ + + dest = sst25_cacheread(priv, sector); + + /* Erase the block containing this sector if it is not already erased. + * The erased indicated will be cleared when the data from the erase sector + * is read into the cache and set here when we erase the sector. + */ + + if (!IS_ERASED(priv)) + { + off_t esectno = sector >> (priv->sectorshift - SST25_SECTOR_SHIFT); + fvdbg("sector: %ld esectno: %d\n", sector, esectno); + + sst25_sectorerase(priv, esectno); + SET_ERASED(priv); + } + + /* Copy the new sector data into cached erase block */ + + memcpy(dest, buffer, SST25_SECTOR_SIZE); + SET_DIRTY(priv); + + /* Set up for the next 512 byte sector */ + + buffer += SST25_SECTOR_SIZE; + sector++; + } + + /* Flush the last erase block left in the cache */ + + sst25_cacheflush(priv); +} +#endif + +/************************************************************************************ + * Name: sst25_erase + ************************************************************************************/ + +static int sst25_erase(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks) +{ +#ifdef CONFIG_SST25_READONLY + return -EACESS +#else + FAR struct sst25_dev_s *priv = (FAR struct sst25_dev_s *)dev; + size_t blocksleft = nblocks; + + fvdbg("startblock: %08lx nblocks: %d\n", (long)startblock, (int)nblocks); + + /* Lock access to the SPI bus until we complete the erase */ + + sst25_lock(priv->dev); + + while (blocksleft-- > 0) + { + /* Erase each sector */ + +#ifdef CONFIG_SST25_SECTOR512 + sst25_cacheerase(priv, startblock); +#else + sst25_sectorerase(priv, startblock); +#endif + startblock++; + } + +#ifdef CONFIG_SST25_SECTOR512 + /* Flush the last erase block left in the cache */ + + sst25_cacheflush(priv); +#endif + + sst25_unlock(priv->dev); + return (int)nblocks; +#endif +} + +/************************************************************************************ + * Name: sst25_bread + ************************************************************************************/ + +static ssize_t sst25_bread(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks, + FAR uint8_t *buffer) +{ +#ifdef CONFIG_SST25_SECTOR512 + ssize_t nbytes; + + fvdbg("startblock: %08lx nblocks: %d\n", (long)startblock, (int)nblocks); + + /* On this device, we can handle the block read just like the byte-oriented read */ + + nbytes = sst25_read(dev, startblock << SST25_SECTOR_SHIFT, nblocks << SST25_SECTOR_SHIFT, buffer); + if (nbytes > 0) + { + return nbytes >> SST25_SECTOR_SHIFT; + } + + return (int)nbytes; +#else + FAR struct sst25_dev_s *priv = (FAR struct sst25_dev_s *)dev; + ssize_t nbytes; + + fvdbg("startblock: %08lx nblocks: %d\n", (long)startblock, (int)nblocks); + + /* On this device, we can handle the block read just like the byte-oriented read */ + + nbytes = sst25_read(dev, startblock << priv->sectorshift, nblocks << priv->sectorshift, buffer); + if (nbytes > 0) + { + return nbytes >> priv->sectorshift; + } + + return (int)nbytes; +#endif +} + +/************************************************************************************ + * Name: sst25_bwrite + ************************************************************************************/ + +static ssize_t sst25_bwrite(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks, + FAR const uint8_t *buffer) +{ +#ifdef CONFIG_SST25_READONLY + return -EACCESS; +#else + FAR struct sst25_dev_s *priv = (FAR struct sst25_dev_s *)dev; + + fvdbg("startblock: %08lx nblocks: %d\n", (long)startblock, (int)nblocks); + + /* Lock the SPI bus and write all of the pages to FLASH */ + + sst25_lock(priv->dev); + +#if defined(CONFIG_SST25_SECTOR512) + sst25_cachewrite(priv, buffer, startblock, nblocks); +#elif defined(CONFIG_SST25_SLOWWRITE) + sst25_bytewrite(priv, buffer, startblock << priv->sectorshift, + nblocks << priv->sectorshift); +#else + sst25_wordwrite(priv, buffer, startblock << priv->sectorshift, + nblocks << priv->sectorshift); +#endif + sst25_unlock(priv->dev); + + return nblocks; +#endif +} + +/************************************************************************************ + * Name: sst25_read + ************************************************************************************/ + +static ssize_t sst25_read(FAR struct mtd_dev_s *dev, off_t offset, size_t nbytes, + FAR uint8_t *buffer) +{ + FAR struct sst25_dev_s *priv = (FAR struct sst25_dev_s *)dev; + + fvdbg("offset: %08lx nbytes: %d\n", (long)offset, (int)nbytes); + + /* Lock the SPI bus and select this FLASH part */ + + sst25_lock(priv->dev); + sst25_byteread(priv, buffer, offset, nbytes); + sst25_unlock(priv->dev); + + fvdbg("return nbytes: %d\n", (int)nbytes); + return nbytes; +} + +/************************************************************************************ + * Name: sst25_ioctl + ************************************************************************************/ + +static int sst25_ioctl(FAR struct mtd_dev_s *dev, int cmd, unsigned long arg) +{ + FAR struct sst25_dev_s *priv = (FAR struct sst25_dev_s *)dev; + int ret = -EINVAL; /* Assume good command with bad parameters */ + + fvdbg("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) + { + /* Populate the geometry structure with information need to know + * the capacity and how to access the device. + * + * NOTE: that the device is treated as though it where just an array + * of fixed size blocks. That is most likely not true, but the client + * will expect the device logic to do whatever is necessary to make it + * appear so. + */ + +#ifdef CONFIG_SST25_SECTOR512 + geo->blocksize = (1 << SST25_SECTOR_SHIFT); + geo->erasesize = (1 << SST25_SECTOR_SHIFT); + geo->neraseblocks = priv->nsectors << (priv->sectorshift - ); +#else + geo->blocksize = (1 << priv->sectorshift); + geo->erasesize = (1 << priv->sectorshift); + geo->neraseblocks = priv->nsectors; +#endif + ret = OK; + + fvdbg("blocksize: %d erasesize: %d neraseblocks: %d\n", + geo->blocksize, geo->erasesize, geo->neraseblocks); + } + } + break; + + case MTDIOC_BULKERASE: + { + /* Erase the entire device */ + + sst25_lock(priv->dev); + ret = sst25_chiperase(priv); + sst25_unlock(priv->dev); + } + break; + + case MTDIOC_XIPBASE: + default: + ret = -ENOTTY; /* Bad command */ + break; + } + + fvdbg("return %d\n", ret); + return ret; +} + +/************************************************************************************ + * Public Functions + ************************************************************************************/ + +/************************************************************************************ + * Name: sst25_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 *sst25_initialize(FAR struct spi_dev_s *dev) +{ + FAR struct sst25_dev_s *priv; + int ret; + + fvdbg("dev: %p\n", dev); + + /* Allocate a state structure (we allocate the structure instead of using + * a fixed, static allocation so that we can handle multiple FLASH devices. + * The current implementation would handle only one FLASH part per SPI + * device (only because of the SPIDEV_FLASH definition) and so would have + * to be extended to handle multiple FLASH parts on the same SPI bus. + */ + + priv = (FAR struct sst25_dev_s *)kzalloc(sizeof(struct sst25_dev_s)); + if (priv) + { + /* Initialize the allocated structure */ + + priv->mtd.erase = sst25_erase; + priv->mtd.bread = sst25_bread; + priv->mtd.bwrite = sst25_bwrite; + priv->mtd.read = sst25_read; + priv->mtd.ioctl = sst25_ioctl; + priv->dev = dev; + + /* Deselect the FLASH */ + + SPI_SELECT(dev, SPIDEV_FLASH, false); + + /* Identify the FLASH chip and get its capacity */ + + ret = sst25_readid(priv); + if (ret != OK) + { + /* Unrecognized! Discard all of that work we just did and return NULL */ + + fdbg("Unrecognized\n"); + kfree(priv); + priv = NULL; + } + else + { + /* Make sure the the FLASH is unprotected so that we can write into it */ + +#ifndef CONFIG_SST25_READONLY + sst25_unprotect(priv->dev); +#endif + +#ifdef CONFIG_SST25_SECTOR512 /* Simulate a 512 byte sector */ + /* Allocate a buffer for the erase block cache */ + + priv->sector = (FAR uint8_t *)kmalloc(1 << priv->sectorshift); + if (!priv->sector) + { + /* Allocation failed! Discard all of that work we just did and return NULL */ + + fdbg("Allocation failed\n"); + kfree(priv); + priv = NULL; + } +#endif + } + } + + /* Return the implementation-specific state structure as the MTD device */ + + fvdbg("Return %p\n", priv); + return (FAR struct mtd_dev_s *)priv; +} |