1084 lines
30 KiB
C
1084 lines
30 KiB
C
|
/****************************************************************************
|
||
|
* drivers/mtd/at25ee.c
|
||
|
*
|
||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||
|
* contributor license agreements. See the NOTICE file distributed with
|
||
|
* this work for additional information regarding copyright ownership. The
|
||
|
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
||
|
* "License"); you may not use this file except in compliance with the
|
||
|
* License. You may obtain a copy of the License at
|
||
|
*
|
||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing, software
|
||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||
|
* License for the specific language governing permissions and limitations
|
||
|
* under the License.
|
||
|
*
|
||
|
****************************************************************************/
|
||
|
|
||
|
/****************************************************************************
|
||
|
* Included Files
|
||
|
****************************************************************************/
|
||
|
|
||
|
#include <nuttx/config.h>
|
||
|
#include <sys/types.h>
|
||
|
#include <inttypes.h>
|
||
|
#include <stdint.h>
|
||
|
#include <stdbool.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <unistd.h>
|
||
|
#include <string.h>
|
||
|
#include <assert.h>
|
||
|
#include <errno.h>
|
||
|
#include <debug.h>
|
||
|
|
||
|
#include <nuttx/kmalloc.h>
|
||
|
#include <nuttx/signal.h>
|
||
|
#include <nuttx/fs/ioctl.h>
|
||
|
#include <nuttx/spi/spi.h>
|
||
|
#include <nuttx/mtd/mtd.h>
|
||
|
|
||
|
#ifdef CONFIG_MTD_AT25EE
|
||
|
|
||
|
/****************************************************************************
|
||
|
* Pre-processor Definitions
|
||
|
****************************************************************************/
|
||
|
|
||
|
#ifndef CONFIG_AT25EE_SPIMODE
|
||
|
# define CONFIG_AT25EE_SPIMODE 0
|
||
|
#endif
|
||
|
|
||
|
/* EEPROM commands
|
||
|
* High bit of low nibble used for A8 in 25xx040/at25040 products
|
||
|
*/
|
||
|
|
||
|
#define AT25EE_CMD_WRSR 0x01
|
||
|
#define AT25EE_CMD_WRITE 0x02
|
||
|
#define AT25EE_CMD_READ 0x03
|
||
|
#define AT25EE_CMD_WRDIS 0x04
|
||
|
#define AT25EE_CMD_RDSR 0x05
|
||
|
#define AT25EE_CMD_WREN 0x06
|
||
|
|
||
|
/* Following commands will be available some day via IOCTLs
|
||
|
* PE 0x42 Page erase (25xx512/1024)
|
||
|
* SE 0xD8 Sector erase (25xx512/1024)
|
||
|
* CE 0xC7 Chip erase (25xx512/1024)
|
||
|
* RDID 0xAB Wake up and read electronic signature (25xx512/1024)
|
||
|
* DPD 0xB9 Sleep (25xx512/1024)
|
||
|
*
|
||
|
* Identification page access for ST devices
|
||
|
* RDID/RDLS 0x83 Read identification page / Read ID page lock status
|
||
|
* WRID/LID 0x82 Write identification page / Lock ID page
|
||
|
*/
|
||
|
|
||
|
/* SR bits definitions */
|
||
|
|
||
|
#define AT25EE_SR_WIP 0x01 /* Write in Progress */
|
||
|
#define AT25EE_SR_WEL 0x02 /* Write Enable Latch */
|
||
|
#define AT25EE_SR_BP0 0x04 /* First Block Protect bit */
|
||
|
#define AT25EE_SR_BP1 0x08 /* Second Block Protect bit */
|
||
|
#define AT25EE_SR_WPEN 0x80 /* Write Protect Enable */
|
||
|
|
||
|
#define AT25EE_DUMMY 0xFF
|
||
|
|
||
|
/* For applications where a file system is used on the AT25EE, the tiny page
|
||
|
* sizes will result in very inefficient EEPROM 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.
|
||
|
*/
|
||
|
|
||
|
/****************************************************************************
|
||
|
* Private Types
|
||
|
****************************************************************************/
|
||
|
|
||
|
/* Device geometry description, compact form (2 bytes per entry) */
|
||
|
|
||
|
struct at25ee_geom_s
|
||
|
{
|
||
|
uint8_t bytes : 4; /* Power of 2 of 128 bytes (0:128 1:256 2:512 etc) */
|
||
|
uint8_t pagesize : 4; /* Power of 2 of 8 bytes (0:8 1:16 2:32 3:64 etc) */
|
||
|
uint8_t addrlen : 4; /* Number of bytes in command address field */
|
||
|
uint8_t flags : 4; /* Addr. management for 25xx040, 1=A8 in inst */
|
||
|
};
|
||
|
|
||
|
/* 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 at25ee_dev_s.
|
||
|
*/
|
||
|
|
||
|
struct at25ee_dev_s
|
||
|
{
|
||
|
struct mtd_dev_s mtd; /* MTD interface */
|
||
|
struct spi_dev_s *spi; /* SPI device where the EEPROM is attached */
|
||
|
uint32_t size; /* in bytes, expanded from geometry */
|
||
|
uint16_t pgsize; /* write block size, in bytes, expanded from
|
||
|
* geometry
|
||
|
*/
|
||
|
uint16_t npages; /* numpages, derived from geometry */
|
||
|
uint16_t addrlen; /* number of BITS in data addresses */
|
||
|
uint16_t blocksize; /* Block sized to report */
|
||
|
mutex_t lock; /* file access serialization */
|
||
|
uint8_t readonly; /* Flags */
|
||
|
};
|
||
|
|
||
|
/****************************************************************************
|
||
|
* Private Function Prototypes
|
||
|
****************************************************************************/
|
||
|
|
||
|
static void at25ee_lock(FAR struct spi_dev_s *dev);
|
||
|
|
||
|
/* MTD driver methods */
|
||
|
|
||
|
static int at25ee_erase(FAR struct mtd_dev_s *dev,
|
||
|
off_t startblock,
|
||
|
size_t nblocks);
|
||
|
static ssize_t at25ee_bread(FAR struct mtd_dev_s *dev,
|
||
|
off_t startblock,
|
||
|
size_t nblocks, FAR uint8_t *buf);
|
||
|
static ssize_t at25ee_bwrite(FAR struct mtd_dev_s *dev, off_t startblock,
|
||
|
size_t nblocks, FAR const uint8_t *buf);
|
||
|
static ssize_t at25ee_read(FAR struct mtd_dev_s *dev, off_t offset,
|
||
|
size_t nbytes, FAR uint8_t *buf);
|
||
|
static ssize_t at25ee_write(FAR struct mtd_dev_s *dev, off_t offset,
|
||
|
size_t nbytes, FAR const uint8_t *buf);
|
||
|
static int at25ee_ioctl(FAR struct mtd_dev_s *dev, int cmd,
|
||
|
unsigned long arg);
|
||
|
static void at25ee_writepage(FAR struct at25ee_dev_s *priv, uint32_t devaddr,
|
||
|
FAR const uint8_t *data, size_t len);
|
||
|
static void at25ee_writeenable(FAR struct at25ee_dev_s *priv, int enable);
|
||
|
static void at25ee_waitwritecomplete(struct at25ee_dev_s *priv);
|
||
|
static void at25ee_sendcmd(FAR struct spi_dev_s *spi, uint8_t cmd,
|
||
|
uint8_t addrlen, uint32_t addr);
|
||
|
static inline void at25ee_unlock(FAR struct spi_dev_s *dev);
|
||
|
static void at25ee_lock(FAR struct spi_dev_s *dev);
|
||
|
|
||
|
/****************************************************************************
|
||
|
* Private Data
|
||
|
****************************************************************************/
|
||
|
|
||
|
/* Supported device geometries.
|
||
|
* One geometry can fit more than one device.
|
||
|
* The user will use an enum'd index from include/eeprom/spi_xx25xx.h
|
||
|
*/
|
||
|
|
||
|
static const struct at25ee_geom_s g_at25ee_devices[] =
|
||
|
{
|
||
|
/* Microchip devices */
|
||
|
|
||
|
{
|
||
|
0, 1, 1, 0
|
||
|
}, /* 25xx010A 128 16 1 */
|
||
|
{
|
||
|
1, 1, 1, 0
|
||
|
}, /* 25xx020A 256 16 1 */
|
||
|
{
|
||
|
2, 1, 1, 1
|
||
|
}, /* 25xx040 512 16 1+bit */
|
||
|
{
|
||
|
3, 1, 1, 0
|
||
|
}, /* 25xx080 1024 16 1 */
|
||
|
{
|
||
|
3, 2, 2, 0
|
||
|
}, /* 25xx080B 1024 32 2 */
|
||
|
{
|
||
|
4, 1, 2, 0
|
||
|
}, /* 25xx160 2048 16 2 */
|
||
|
{
|
||
|
4, 2, 2, 0
|
||
|
}, /* 25xx160B/D 2048 32 2 */
|
||
|
{
|
||
|
5, 2, 2, 0
|
||
|
}, /* 25xx320 4096 32 2 */
|
||
|
{
|
||
|
6, 2, 2, 0
|
||
|
}, /* 25xx640 8192 32 2 */
|
||
|
{
|
||
|
7, 3, 2, 0
|
||
|
}, /* 25xx128 16384 64 2 */
|
||
|
{
|
||
|
8, 3, 2, 0
|
||
|
}, /* 25xx256 32768 64 2 */
|
||
|
{
|
||
|
9, 4, 2, 0
|
||
|
}, /* 25xx512 65536 128 2 */
|
||
|
{
|
||
|
10, 5, 3, 0
|
||
|
}, /* 25xx1024 131072 256 3 */
|
||
|
|
||
|
/* Atmel devices */
|
||
|
|
||
|
{
|
||
|
0, 0, 1, 0
|
||
|
}, /* AT25010B 128 8 1 */
|
||
|
{
|
||
|
1, 0, 1, 0
|
||
|
}, /* AT25020B 256 8 1 */
|
||
|
{
|
||
|
2, 0, 1, 1
|
||
|
}, /* AT25040B 512 8 1+bit */
|
||
|
|
||
|
/* STM devices */
|
||
|
|
||
|
{
|
||
|
11, 5, 3, 0
|
||
|
}, /* M95M02 262144 256 3 */
|
||
|
};
|
||
|
|
||
|
/****************************************************************************
|
||
|
* Private Functions
|
||
|
****************************************************************************/
|
||
|
|
||
|
/****************************************************************************
|
||
|
* Name: at25ee_lock
|
||
|
*
|
||
|
* Description:
|
||
|
* On SPI buses where there are multiple devices, it will be necessary to
|
||
|
* lock SPI to have exclusive access to the buses 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 exclusive
|
||
|
* access to the SPI bus. We will retain that exclusive access until the
|
||
|
* bus is unlocked.
|
||
|
*
|
||
|
* 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 bus is being shared, then it may
|
||
|
* have been left in an incompatible state.
|
||
|
*
|
||
|
* Input Parameters:
|
||
|
* dev - pointer to device structure
|
||
|
* Returned Value:
|
||
|
* none
|
||
|
*
|
||
|
****************************************************************************/
|
||
|
|
||
|
static void at25ee_lock(FAR struct spi_dev_s *dev)
|
||
|
{
|
||
|
SPI_LOCK(dev, true);
|
||
|
SPI_SETMODE(dev, CONFIG_AT25EE_SPIMODE);
|
||
|
SPI_SETBITS(dev, 8);
|
||
|
SPI_HWFEATURES(dev, 0);
|
||
|
SPI_SETFREQUENCY(dev, CONFIG_AT25EE_SPIFREQUENCY);
|
||
|
#ifdef CONFIG_SPI_DELAY_CONTROL
|
||
|
SPI_SETDELAY(dev, CONFIG_AT25EE_START_DELAY, CONFIG_AT25EE_STOP_DELAY,
|
||
|
CONFIG_AT25EE_CS_DELAY, CONFIG_AT25EE_IFDELAY);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
/****************************************************************************
|
||
|
* Name: at25ee_unlock
|
||
|
*
|
||
|
* Description:
|
||
|
* Unlocks the SPI bus
|
||
|
*
|
||
|
* Input Parameters:
|
||
|
* dev - pointer to device structure
|
||
|
* Returned Value:
|
||
|
* none
|
||
|
*
|
||
|
****************************************************************************/
|
||
|
|
||
|
static inline void at25ee_unlock(FAR struct spi_dev_s *dev)
|
||
|
{
|
||
|
SPI_LOCK(dev, false);
|
||
|
}
|
||
|
|
||
|
/****************************************************************************
|
||
|
* Name: at25ee_sendcmd
|
||
|
*
|
||
|
* Description:
|
||
|
* Send command and address as one transaction to take advantage
|
||
|
* of possible faster DMA transfers.
|
||
|
* Sending byte per byte is MUCH slower.
|
||
|
*
|
||
|
* Input Parameters:
|
||
|
* spi - a reference to the spi device
|
||
|
* cmd - SPI command to send
|
||
|
* addrlen - length of the address, in bits
|
||
|
* addr - address to write to
|
||
|
*
|
||
|
* Returned Value:
|
||
|
* none
|
||
|
*
|
||
|
****************************************************************************/
|
||
|
|
||
|
static void at25ee_sendcmd(FAR struct spi_dev_s *spi, uint8_t cmd,
|
||
|
uint8_t addrlen, uint32_t addr)
|
||
|
{
|
||
|
uint8_t buf[4];
|
||
|
int cmdlen = 1;
|
||
|
|
||
|
/* Store command */
|
||
|
|
||
|
buf[0] = cmd;
|
||
|
|
||
|
/* Store address according to its length */
|
||
|
|
||
|
if (addrlen == 9)
|
||
|
{
|
||
|
buf[0] |= (((addr >> 8) & 1) << 3);
|
||
|
}
|
||
|
|
||
|
if (addrlen > 16)
|
||
|
{
|
||
|
buf[cmdlen++] = (addr >> 16) & 0xff;
|
||
|
}
|
||
|
|
||
|
if (addrlen > 9)
|
||
|
{
|
||
|
buf[cmdlen++] = (addr >> 8) & 0xff;
|
||
|
}
|
||
|
|
||
|
buf[cmdlen++] = addr & 0xff;
|
||
|
|
||
|
SPI_SNDBLOCK(spi, buf, cmdlen);
|
||
|
}
|
||
|
|
||
|
/****************************************************************************
|
||
|
* Name: at25ee_waitwritecomplete
|
||
|
*
|
||
|
* Description:
|
||
|
* loop until the write operation is done.
|
||
|
*
|
||
|
* Input Parameters:
|
||
|
* priv - a reference to the device structure
|
||
|
*
|
||
|
* Returned Value:
|
||
|
* none
|
||
|
*
|
||
|
****************************************************************************/
|
||
|
|
||
|
static void at25ee_waitwritecomplete(struct at25ee_dev_s *priv)
|
||
|
{
|
||
|
uint8_t status;
|
||
|
|
||
|
/* Loop as long as the memory is busy with a write cycle */
|
||
|
|
||
|
do
|
||
|
{
|
||
|
/* Select this FLASH part */
|
||
|
|
||
|
at25ee_lock(priv->spi);
|
||
|
SPI_SELECT(priv->spi, SPIDEV_EEPROM(0), true);
|
||
|
|
||
|
/* Send "Read Status Register (RDSR)" command */
|
||
|
|
||
|
SPI_SEND(priv->spi, AT25EE_CMD_RDSR);
|
||
|
|
||
|
/* Send a dummy byte to generate the clock needed to shift out the
|
||
|
* status
|
||
|
*/
|
||
|
|
||
|
status = SPI_SEND(priv->spi, AT25EE_DUMMY);
|
||
|
|
||
|
/* Deselect the FLASH */
|
||
|
|
||
|
SPI_SELECT(priv->spi, SPIDEV_EEPROM(0), false);
|
||
|
at25ee_unlock(priv->spi);
|
||
|
|
||
|
/* Given that writing could take up to a few milliseconds,
|
||
|
* the following short delay in the "busy" case will allow
|
||
|
* other peripherals to access the SPI bus.
|
||
|
*/
|
||
|
|
||
|
if ((status & AT25EE_SR_WIP) != 0)
|
||
|
{
|
||
|
nxsig_usleep(1000);
|
||
|
}
|
||
|
}
|
||
|
while ((status & AT25EE_SR_WIP) != 0);
|
||
|
}
|
||
|
|
||
|
/****************************************************************************
|
||
|
* Name: at25ee_writeenable
|
||
|
*
|
||
|
* Description:
|
||
|
* Enable or disable write operations.
|
||
|
* This is required before any write, since a lot of operations
|
||
|
* automatically disable the write latch.
|
||
|
*
|
||
|
* Input Parameters:
|
||
|
* priv - a reference to the device structure
|
||
|
* enable - enable (true) or disable(false) write operations
|
||
|
*
|
||
|
* Returned Value:
|
||
|
* none
|
||
|
*
|
||
|
****************************************************************************/
|
||
|
|
||
|
static void at25ee_writeenable(FAR struct at25ee_dev_s *priv, int enable)
|
||
|
{
|
||
|
at25ee_lock(priv->spi);
|
||
|
SPI_SELECT(priv->spi, SPIDEV_EEPROM(0), true);
|
||
|
|
||
|
SPI_SEND(priv->spi, enable ? AT25EE_CMD_WREN : AT25EE_CMD_WRDIS);
|
||
|
|
||
|
SPI_SELECT(priv->spi, SPIDEV_EEPROM(0), false);
|
||
|
at25ee_unlock(priv->spi);
|
||
|
}
|
||
|
|
||
|
/****************************************************************************
|
||
|
* Name: at25ee_writepage
|
||
|
*
|
||
|
* Description:
|
||
|
* Write data to the EEPROM, NOT crossing page boundaries.
|
||
|
*
|
||
|
* Input Parameters:
|
||
|
* priv - a reference to the device structure
|
||
|
* devaddr - the address to start the write
|
||
|
* data - pointer to data buffer to write
|
||
|
* len - length of the data to write
|
||
|
*
|
||
|
* Returned Value:
|
||
|
* none
|
||
|
*
|
||
|
****************************************************************************/
|
||
|
|
||
|
static void at25ee_writepage(FAR struct at25ee_dev_s *priv, uint32_t devaddr,
|
||
|
FAR const uint8_t *data, size_t len)
|
||
|
{
|
||
|
at25ee_lock(priv->spi);
|
||
|
SPI_SELECT(priv->spi, SPIDEV_EEPROM(0), true);
|
||
|
|
||
|
at25ee_sendcmd(priv->spi, AT25EE_CMD_WRITE, priv->addrlen, devaddr);
|
||
|
SPI_SNDBLOCK(priv->spi, data, len);
|
||
|
|
||
|
SPI_SELECT(priv->spi, SPIDEV_EEPROM(0), false);
|
||
|
at25ee_unlock(priv->spi);
|
||
|
}
|
||
|
|
||
|
/****************************************************************************
|
||
|
* Name: at25ee_eraseall
|
||
|
*
|
||
|
* Description:
|
||
|
* Erase all data in the device
|
||
|
*
|
||
|
* Input Parameters:
|
||
|
* priv - a reference to the device structure
|
||
|
* devaddr - the address to start the write
|
||
|
* data - pointer to data buffer to write
|
||
|
* len - length of the data to write
|
||
|
*
|
||
|
* Returned Value:
|
||
|
* none
|
||
|
*
|
||
|
****************************************************************************/
|
||
|
|
||
|
static int at25ee_eraseall(FAR struct at25ee_dev_s *priv)
|
||
|
{
|
||
|
uint8_t *buf;
|
||
|
int startblock = 0;
|
||
|
|
||
|
DEBUGASSERT(priv);
|
||
|
|
||
|
buf = kmm_malloc(priv->pgsize);
|
||
|
if (!buf)
|
||
|
{
|
||
|
ferr("ERROR: Failed to alloc memory for at25ee eraseall!\n");
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
|
||
|
memset(buf, 0xff, priv->pgsize);
|
||
|
|
||
|
for (startblock = 0; startblock < priv->npages; startblock++)
|
||
|
{
|
||
|
uint16_t offset = startblock * priv->pgsize;
|
||
|
at25ee_write(&priv->mtd, offset, priv->pgsize, buf);
|
||
|
}
|
||
|
|
||
|
kmm_free(buf);
|
||
|
return OK;
|
||
|
}
|
||
|
|
||
|
/****************************************************************************
|
||
|
* Name: at25ee_erase
|
||
|
*
|
||
|
* Description:
|
||
|
* Erase a number of blocks of data.
|
||
|
*
|
||
|
* Input Parameters:
|
||
|
* dev - a reference to the device structure
|
||
|
* startblock - start block of the erase
|
||
|
* nblocks - nblocks to erase
|
||
|
*
|
||
|
* Returned Value:
|
||
|
* Success (OK) or fail (negated error code)
|
||
|
****************************************************************************/
|
||
|
|
||
|
static int at25ee_erase(FAR struct mtd_dev_s *dev,
|
||
|
off_t startblock,
|
||
|
size_t nblocks)
|
||
|
{
|
||
|
#ifndef CONFIG_AT25EE_ENABLE_BLOCK_ERASE
|
||
|
return (int)nblocks;
|
||
|
#else
|
||
|
FAR struct at25ee_dev_s *priv = (FAR struct at25ee_dev_s *)dev;
|
||
|
uint8_t *buf;
|
||
|
size_t blocksleft;
|
||
|
|
||
|
DEBUGASSERT(dev);
|
||
|
|
||
|
if (priv->blocksize > priv->pgsize)
|
||
|
{
|
||
|
startblock *= (priv->blocksize / priv->pgsize);
|
||
|
nblocks *= (priv->blocksize / priv->pgsize);
|
||
|
}
|
||
|
|
||
|
blocksleft = nblocks;
|
||
|
|
||
|
if (startblock >= priv->npages)
|
||
|
{
|
||
|
return -E2BIG;
|
||
|
}
|
||
|
|
||
|
buf = kmm_malloc(priv->pgsize);
|
||
|
if (!buf)
|
||
|
{
|
||
|
ferr("ERROR: Failed to alloc memory for at25ee erase!\n");
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
|
||
|
memset(buf, 0xff, priv->pgsize);
|
||
|
|
||
|
if (startblock + nblocks > priv->npages)
|
||
|
{
|
||
|
nblocks = priv->npages - startblock;
|
||
|
}
|
||
|
|
||
|
finfo("startblock: %08lx nblocks: %d\n", (long)startblock, (int)nblocks);
|
||
|
|
||
|
while (blocksleft-- > 0)
|
||
|
{
|
||
|
off_t offset = startblock * priv->pgsize;
|
||
|
|
||
|
finfo("startblock: %08lx offset: %d\n", (long)startblock, (int)offset);
|
||
|
at25ee_write(dev, offset, priv->pgsize, buf);
|
||
|
startblock++;
|
||
|
}
|
||
|
|
||
|
kmm_free(buf);
|
||
|
if (priv->blocksize > priv->pgsize)
|
||
|
{
|
||
|
return (int)(nblocks / (priv->blocksize / priv->pgsize));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return (int)nblocks;
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
/****************************************************************************
|
||
|
* Name: at25ee_read
|
||
|
*
|
||
|
* Description:
|
||
|
* Read a number of bytes of data.
|
||
|
*
|
||
|
* Input Parameters:
|
||
|
* dev - a reference to the device structure
|
||
|
* offset - start of the memory to read
|
||
|
* nbytes - number of bytes to read
|
||
|
* buffer - pointer to variable to store the read data
|
||
|
*
|
||
|
* Returned Value:
|
||
|
* Size of the data read
|
||
|
****************************************************************************/
|
||
|
|
||
|
static ssize_t at25ee_read(FAR struct mtd_dev_s *dev, off_t offset,
|
||
|
size_t nbytes, FAR uint8_t *buf)
|
||
|
{
|
||
|
int ret;
|
||
|
FAR struct at25ee_dev_s *priv = (FAR struct at25ee_dev_s *)dev;
|
||
|
|
||
|
DEBUGASSERT(buf);
|
||
|
DEBUGASSERT(dev);
|
||
|
|
||
|
ret = nxmutex_lock(&priv->lock);
|
||
|
if (ret < 0)
|
||
|
{
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
if ((offset + nbytes) > priv->size)
|
||
|
{
|
||
|
return 0; /* end-of-file */
|
||
|
}
|
||
|
|
||
|
at25ee_lock(priv->spi);
|
||
|
|
||
|
SPI_SELECT(priv->spi, SPIDEV_EEPROM(0), true);
|
||
|
|
||
|
/* STM32F4Disco: There is a 25 us delay here */
|
||
|
|
||
|
at25ee_sendcmd(priv->spi, AT25EE_CMD_READ, priv->addrlen, offset);
|
||
|
|
||
|
SPI_RECVBLOCK(priv->spi, buf, nbytes);
|
||
|
|
||
|
SPI_SELECT(priv->spi, SPIDEV_EEPROM(0), false);
|
||
|
|
||
|
at25ee_unlock(priv->spi);
|
||
|
|
||
|
nxmutex_unlock(&priv->lock);
|
||
|
return nbytes;
|
||
|
}
|
||
|
|
||
|
/****************************************************************************
|
||
|
* Name: at25ee_write
|
||
|
*
|
||
|
* Description:
|
||
|
* Write a number of bytes of data.
|
||
|
*
|
||
|
* Input Parameters:
|
||
|
* dev - a reference to the device structure
|
||
|
* offset - start of the memory to write
|
||
|
* nbytes - number of bytes to write
|
||
|
* buf - pointer to buffer of data to write
|
||
|
*
|
||
|
* Returned Value:
|
||
|
* Size of the data written
|
||
|
****************************************************************************/
|
||
|
|
||
|
static ssize_t at25ee_write(FAR struct mtd_dev_s *dev, off_t offset,
|
||
|
size_t nbytes, FAR const uint8_t *buf)
|
||
|
{
|
||
|
int ret = -EACCES;
|
||
|
FAR struct at25ee_dev_s *priv = (FAR struct at25ee_dev_s *)dev;
|
||
|
int pageoff;
|
||
|
size_t cnt;
|
||
|
|
||
|
DEBUGASSERT(buf);
|
||
|
DEBUGASSERT(dev);
|
||
|
|
||
|
if (priv->readonly)
|
||
|
{
|
||
|
return -EPERM;
|
||
|
}
|
||
|
|
||
|
/* Forbid writes past the end of the device */
|
||
|
|
||
|
if (nbytes + offset >= priv->size)
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
ret = nxmutex_lock(&priv->lock);
|
||
|
if (ret < 0)
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* From this point no failure cannot be detected anymore.
|
||
|
* The user should verify the write by rereading memory.
|
||
|
*/
|
||
|
|
||
|
ret = nbytes; /* save number of bytes written */
|
||
|
|
||
|
/* Writes can't happen in a row like the read does.
|
||
|
* The EEPROM is made of pages, and write sequences
|
||
|
* cannot cross page boundaries. So every time the last
|
||
|
* byte of a page is programmed, the SPI transaction is
|
||
|
* stopped, and the status register is read until the
|
||
|
* write operation has completed.
|
||
|
*/
|
||
|
|
||
|
/* First, write some page-unaligned data */
|
||
|
|
||
|
pageoff = offset & (priv->pgsize - 1);
|
||
|
cnt = priv->pgsize - pageoff;
|
||
|
if (cnt > nbytes)
|
||
|
{
|
||
|
cnt = nbytes;
|
||
|
}
|
||
|
|
||
|
if (pageoff > 0)
|
||
|
{
|
||
|
at25ee_writeenable(priv, true);
|
||
|
at25ee_writepage(priv, offset, buf, cnt);
|
||
|
at25ee_waitwritecomplete(priv);
|
||
|
nbytes -= cnt;
|
||
|
buf += cnt;
|
||
|
offset += cnt;
|
||
|
}
|
||
|
|
||
|
/* Then, write remaining bytes at page-aligned addresses */
|
||
|
|
||
|
while (nbytes > 0)
|
||
|
{
|
||
|
cnt = nbytes;
|
||
|
if (cnt > priv->pgsize)
|
||
|
{
|
||
|
cnt = priv->pgsize;
|
||
|
}
|
||
|
|
||
|
at25ee_writeenable(priv, true);
|
||
|
at25ee_writepage(priv, offset, buf, cnt);
|
||
|
at25ee_waitwritecomplete(priv);
|
||
|
nbytes -= cnt;
|
||
|
buf += cnt;
|
||
|
offset += cnt;
|
||
|
}
|
||
|
|
||
|
nxmutex_unlock(&priv->lock);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/****************************************************************************
|
||
|
* Name: at25ee_bread
|
||
|
*
|
||
|
* Description:
|
||
|
* Read a number of blocks of data.
|
||
|
*
|
||
|
* Input Parameters:
|
||
|
* dev - a reference to the device structure
|
||
|
* startblock - start block of the read
|
||
|
* nblocks - nblocks to read
|
||
|
* buf - pointer to variable to store the read data
|
||
|
*
|
||
|
* Returned Value:
|
||
|
* Number of blocks written
|
||
|
****************************************************************************/
|
||
|
|
||
|
static ssize_t at25ee_bread(FAR struct mtd_dev_s *dev,
|
||
|
off_t startblock,
|
||
|
size_t nblocks, FAR uint8_t *buf)
|
||
|
{
|
||
|
FAR struct at25ee_dev_s *priv = (FAR struct at25ee_dev_s *)dev;
|
||
|
off_t offset;
|
||
|
ssize_t nread;
|
||
|
size_t i;
|
||
|
|
||
|
DEBUGASSERT(dev);
|
||
|
DEBUGASSERT(buf);
|
||
|
|
||
|
if (priv->blocksize > priv->pgsize)
|
||
|
{
|
||
|
startblock *= (priv->blocksize / priv->pgsize);
|
||
|
nblocks *= (priv->blocksize / priv->pgsize);
|
||
|
}
|
||
|
|
||
|
finfo("startblock: %08lx nblocks: %lu\n",
|
||
|
(unsigned long)startblock, (unsigned long)nblocks);
|
||
|
|
||
|
if (startblock >= priv->npages)
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if (startblock + nblocks > priv->npages)
|
||
|
{
|
||
|
nblocks = priv->npages - startblock;
|
||
|
}
|
||
|
|
||
|
/* Convert the access from startblock and number of blocks to a byte
|
||
|
* offset and number of bytes.
|
||
|
*/
|
||
|
|
||
|
offset = startblock * priv->pgsize;
|
||
|
|
||
|
/* Then perform the byte-oriented read for each block separately */
|
||
|
|
||
|
for (i = 0; i < nblocks; i++)
|
||
|
{
|
||
|
nread = at25ee_read(dev, offset, priv->pgsize, buf);
|
||
|
if (nread < 0)
|
||
|
{
|
||
|
return nread;
|
||
|
}
|
||
|
|
||
|
offset += priv->pgsize;
|
||
|
buf += priv->pgsize;
|
||
|
}
|
||
|
|
||
|
if (priv->blocksize > priv->pgsize)
|
||
|
{
|
||
|
return nblocks / (priv->blocksize / priv->pgsize);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return nblocks;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/****************************************************************************
|
||
|
* Name: at25ee_bwrite
|
||
|
*
|
||
|
* Description:
|
||
|
* Write a number of blocks of data.
|
||
|
*
|
||
|
* Input Parameters:
|
||
|
* dev - a reference to the device structure
|
||
|
* startblock - starting block to write to
|
||
|
* nblocks - nblocks to write
|
||
|
* buf - pointer to data buffer to write
|
||
|
*
|
||
|
* Returned Value:
|
||
|
* Size of the data written
|
||
|
****************************************************************************/
|
||
|
|
||
|
static ssize_t at25ee_bwrite(FAR struct mtd_dev_s *dev, off_t startblock,
|
||
|
size_t nblocks, FAR const uint8_t *buf)
|
||
|
{
|
||
|
FAR struct at25ee_dev_s *priv = (FAR struct at25ee_dev_s *)dev;
|
||
|
size_t blocksleft;
|
||
|
|
||
|
DEBUGASSERT(dev);
|
||
|
DEBUGASSERT(buf);
|
||
|
|
||
|
if (priv->blocksize > priv->pgsize)
|
||
|
{
|
||
|
startblock *= (priv->blocksize / priv->pgsize);
|
||
|
nblocks *= (priv->blocksize / priv->pgsize);
|
||
|
}
|
||
|
|
||
|
blocksleft = nblocks;
|
||
|
|
||
|
if (startblock >= priv->npages)
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if (startblock + nblocks > priv->npages)
|
||
|
{
|
||
|
nblocks = priv->npages - startblock;
|
||
|
}
|
||
|
|
||
|
finfo("startblock: %08lx nblocks: %d\n", (long)startblock, (int)nblocks);
|
||
|
|
||
|
while (blocksleft-- > 0)
|
||
|
{
|
||
|
off_t offset = startblock * priv->pgsize;
|
||
|
|
||
|
finfo("startblock: %08lx offset: %d\n", (long)startblock, (int)offset);
|
||
|
at25ee_write(dev, offset, priv->pgsize, buf);
|
||
|
startblock++;
|
||
|
buf += priv->pgsize;
|
||
|
}
|
||
|
|
||
|
if (priv->blocksize > priv->pgsize)
|
||
|
{
|
||
|
return nblocks / (priv->blocksize / priv->pgsize);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return nblocks;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/****************************************************************************
|
||
|
* Name: at25ee_ioctl
|
||
|
* * Description:
|
||
|
* IOCTLS relating to the EEPROM mtd device
|
||
|
*
|
||
|
* Input Parameters:
|
||
|
* dev - a reference to the device structure
|
||
|
* cmd - ioctl command
|
||
|
* arg - ioctl argument
|
||
|
*
|
||
|
* Returned Value:
|
||
|
* Success (OK) or fail (negated error code)
|
||
|
****************************************************************************/
|
||
|
|
||
|
static int at25ee_ioctl(FAR struct mtd_dev_s *dev,
|
||
|
int cmd,
|
||
|
unsigned long arg)
|
||
|
{
|
||
|
FAR struct at25ee_dev_s *priv = (FAR struct at25ee_dev_s *)dev;
|
||
|
int ret = -EINVAL; /* Assume good command with bad parameters */
|
||
|
|
||
|
DEBUGASSERT(dev);
|
||
|
|
||
|
finfo("cmd: %d\n", cmd);
|
||
|
|
||
|
switch (cmd)
|
||
|
{
|
||
|
case MTDIOC_GEOMETRY:
|
||
|
{
|
||
|
FAR struct mtd_geometry_s *geo = (FAR struct mtd_geometry_s *)
|
||
|
((uintptr_t)arg);
|
||
|
if (geo)
|
||
|
{
|
||
|
memset(geo, 0, sizeof(*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 (priv->blocksize > priv->pgsize)
|
||
|
{
|
||
|
geo->blocksize = priv->blocksize;
|
||
|
geo->erasesize = priv->blocksize;
|
||
|
geo->neraseblocks = priv->size / priv->blocksize;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
geo->blocksize = priv->pgsize;
|
||
|
geo->erasesize = priv->pgsize;
|
||
|
geo->neraseblocks = priv->npages;
|
||
|
}
|
||
|
|
||
|
ret = OK;
|
||
|
|
||
|
finfo("blocksize: %" PRId32 " erasesize: %" PRId32
|
||
|
" neraseblocks: %" PRId32 "\n",
|
||
|
geo->blocksize, geo->erasesize, geo->neraseblocks);
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case BIOC_PARTINFO:
|
||
|
{
|
||
|
FAR struct partition_info_s *info =
|
||
|
(FAR struct partition_info_s *)arg;
|
||
|
if (info != NULL)
|
||
|
{
|
||
|
if (priv->blocksize > priv->pgsize)
|
||
|
{
|
||
|
info->numsectors = priv->size / priv->blocksize;
|
||
|
info->sectorsize = priv->blocksize;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
info->numsectors = priv->npages;
|
||
|
info->sectorsize = priv->pgsize;
|
||
|
}
|
||
|
|
||
|
info->startsector = 0;
|
||
|
info->parent[0] = '\0';
|
||
|
ret = OK;
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case MTDIOC_BULKERASE:
|
||
|
ret = at25ee_eraseall(priv);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
ret = -ENOTTY; /* Bad command */
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/****************************************************************************
|
||
|
* Public Functions
|
||
|
****************************************************************************/
|
||
|
|
||
|
/****************************************************************************
|
||
|
* Name: at25ee_initialize
|
||
|
*
|
||
|
* Description:
|
||
|
* Create an initialized MTD device instance for an AT25 SPI EEPROM
|
||
|
* 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).
|
||
|
*
|
||
|
* Input Parameters:
|
||
|
* dev - a reference to the spi device structure
|
||
|
* devtype - device type, from include/nuttx/eeprom/spi_xx25xx.h
|
||
|
* readonly - sets block driver to be readonly
|
||
|
*
|
||
|
* Returned Value:
|
||
|
* Initialised device instance (success) or NULL (fail)
|
||
|
*
|
||
|
****************************************************************************/
|
||
|
|
||
|
FAR struct mtd_dev_s *at25ee_initialize(FAR struct spi_dev_s *dev,
|
||
|
int devtype, int readonly)
|
||
|
{
|
||
|
FAR struct at25ee_dev_s *priv;
|
||
|
|
||
|
DEBUGASSERT(dev);
|
||
|
|
||
|
/* Check device type early */
|
||
|
|
||
|
if ((devtype < 0) ||
|
||
|
(devtype >= sizeof(g_at25ee_devices) / sizeof(g_at25ee_devices[0])))
|
||
|
{
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
priv = kmm_zalloc(sizeof(struct at25ee_dev_s));
|
||
|
if (priv == NULL)
|
||
|
{
|
||
|
ferr("ERROR: Failed to allocate device structure\n");
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
/* Initialize the allocated structure */
|
||
|
|
||
|
nxmutex_init(&priv->lock);
|
||
|
|
||
|
priv->spi = dev;
|
||
|
priv->size = 128 << g_at25ee_devices[devtype].bytes;
|
||
|
priv->pgsize = 8 << g_at25ee_devices[devtype].pagesize;
|
||
|
priv->addrlen = g_at25ee_devices[devtype].addrlen << 3;
|
||
|
priv->npages = priv->size / priv->pgsize;
|
||
|
#ifdef CONFIG_USE_NATIVE_AT25EE_BLOCK_SIZE
|
||
|
priv->blocksize = priv->pgsize;
|
||
|
#else
|
||
|
if ((CONFIG_MANUAL_AT25EE_BLOCK_SIZE % priv->pgsize) ||
|
||
|
(CONFIG_MANUAL_AT25EE_BLOCK_SIZE > priv->size))
|
||
|
{
|
||
|
ferr("ERROR: Configured block size is incorrect!\n");
|
||
|
DEBUGASSERT(0);
|
||
|
priv->blocksize = priv->pgsize;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
priv->blocksize = CONFIG_MANUAL_AT25EE_BLOCK_SIZE;
|
||
|
}
|
||
|
|
||
|
#endif
|
||
|
if ((g_at25ee_devices[devtype].flags & 1))
|
||
|
{
|
||
|
priv->addrlen = 9;
|
||
|
}
|
||
|
|
||
|
priv->readonly = !!readonly;
|
||
|
|
||
|
finfo("EEPROM device, %"PRIu32" bytes, "
|
||
|
"%u per page, addrlen %u, readonly %d\n",
|
||
|
priv->size, priv->pgsize, priv->addrlen,
|
||
|
priv->readonly);
|
||
|
|
||
|
priv->mtd.erase = at25ee_erase;
|
||
|
priv->mtd.bread = at25ee_bread;
|
||
|
priv->mtd.bwrite = at25ee_bwrite;
|
||
|
priv->mtd.read = at25ee_read;
|
||
|
priv->mtd.write = at25ee_write;
|
||
|
priv->mtd.ioctl = at25ee_ioctl;
|
||
|
priv->mtd.name = "at25ee";
|
||
|
|
||
|
/* Return the implementation-specific state structure as the MTD device */
|
||
|
|
||
|
finfo("Return %p\n", priv);
|
||
|
return (FAR struct mtd_dev_s *)priv;
|
||
|
}
|
||
|
|
||
|
#endif /* CONFIG_MTD_AT25EE */
|