mirror of
https://gitee.com/mirrors_PX4/PX4-Autopilot.git
synced 2026-10-27 03:33:34 +08:00
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
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user