nuttx/drivers/mtd/at25ee.c
2024-01-04 09:25:52 -08:00

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 */