Files
nuttx/drivers/mtd/at25ee.c
Antoine Juckler d5633f75a8 mtd/at25ee: Use eeprom/spi_xx25xx internally
In order to reduce code duplication, use the eeprom/spi_xx25xx
driver within mtd/at25ee.

The eeprom/xxx.h includes have been merged into eeprom/eeprom.h, to
provide a common include file like mtd/mtd.h.

Signed-off-by: Antoine Juckler <6445757+ajuckler@users.noreply.github.com>
2025-12-17 19:03:54 +01:00

668 lines
20 KiB
C

/****************************************************************************
* drivers/mtd/at25ee.c
*
* SPDX-License-Identifier: Apache-2.0
*
* 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 <assert.h>
#include <debug.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <nuttx/eeprom/eeprom.h>
#include <nuttx/fs/fs.h>
#include <nuttx/fs/ioctl.h>
#include <nuttx/kmalloc.h>
#include <nuttx/mtd/mtd.h>
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* 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
****************************************************************************/
/* 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 */
FAR struct eeprom_dev_s *eepdev; /* eeprom/spi_xx25xx device */
size_t size; /* Size in bytes, expanded from geometry */
blksize_t pgsize; /* Write block size (in bytes), from geometry */
blksize_t sectsize; /* Sector size (in bytes), expanded from geometry */
uint16_t blocksize; /* Block size to report */
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
#ifdef CONFIG_AT25EE_ENABLE_BLOCK_ERASE
static int at25ee_erasepages(FAR struct at25ee_dev_s *priv, off_t startpage,
blkcnt_t npages);
#endif
/* 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);
#ifdef CONFIG_MTD_BYTE_WRITE
static ssize_t at25ee_write(FAR struct mtd_dev_s *dev, off_t offset,
size_t nbytes, FAR const uint8_t *buf);
#endif
static int at25ee_ioctl(FAR struct mtd_dev_s *dev, int cmd,
unsigned long arg);
/****************************************************************************
* Private Data
****************************************************************************/
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: at25ee_erasepages
*
* Description:
* Erase a number of pages of data
*
* Remark;
* The startpage and npages parameter are assumed to be valid. If they are
* not, the underlying EEPROM character driver will return an error.
*
* Input Parameters:
* priv - a reference to the device structure
* startpage - start page of the erase
* npages - number of pages to erase
*
* Returned Value:
* Success (OK), or fail (negated error code)
*
****************************************************************************/
#ifdef CONFIG_AT25EE_ENABLE_BLOCK_ERASE
static int at25ee_erasepages(FAR struct at25ee_dev_s *priv, off_t startpage,
blkcnt_t npages)
{
blkcnt_t i;
int ret = OK;
DEBUGASSERT(priv);
for (i = 0; i < npages; ++i)
{
const unsigned long page = (unsigned long)(startpage + i);
ret = priv->eepdev->ioctl(priv->eepdev, EEPIOC_PAGEERASE, page);
if (ret < 0)
{
ferr("ERROR while erasing page #%ld (ret = %d)\n", page, ret);
break;
}
}
return ret;
}
#endif /* CONFIG_AT25EE_ENABLE_BLOCK_ERASE */
/****************************************************************************
* 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:
* Number of blocks erased, 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;
blkcnt_t npages = (blkcnt_t)nblocks;
off_t startpg = startblock;
DEBUGASSERT(dev);
if ((startblock == 0) && ((nblocks * priv->blocksize) >= priv->size))
{
return priv->eepdev->ioctl(priv->eepdev, EEPIOC_CHIPERASE, 0);
}
if (priv->blocksize > priv->pgsize)
{
startpg *= (priv->blocksize / priv->pgsize);
npages *= (priv->blocksize / priv->pgsize);
}
if ((startpg * priv->pgsize) >= priv->size)
{
return -EFAULT;
}
if (((startpg + npages) * priv->pgsize) > priv->size)
{
npages = (blkcnt_t)(priv->size / (size_t)priv->pgsize) - startpg;
}
finfo("startpg: %08lx npages: %d\n", (long)startpg, (int)npages);
if (priv->pgsize != priv->sectsize)
{
const blkcnt_t pgpersect = priv->sectsize / priv->pgsize;
const off_t startsect = (startpg + (pgpersect - 1)) / pgpersect;
const off_t endsect = (startpg + npages) / pgpersect;
int i;
int ret;
/* Erase the pages not aligned to a sector */
if ((startpg % pgpersect) != 0)
{
const blkcnt_t pgcnt = pgpersect - (startpg % pgpersect);
ret = at25ee_erasepages(priv, startpg, pgcnt);
if (ret < 0)
{
return ret;
}
}
/* Erase by sectors */
for (i = 0; i < (endsect - startsect); ++i)
{
const unsigned long sectorno = (unsigned long)(startsect + i);
ret = priv->eepdev->ioctl(priv->eepdev, EEPIOC_SECTORERASE,
sectorno);
if (ret < 0)
{
ferr("ERROR while erasing sector #%ld (ret = %d)\n",
sectorno, ret);
return ret;
}
}
/* Erase the remaining pages */
if ((endsect * priv->sectsize) < ((startpg + npages) * priv->pgsize))
{
const off_t start = endsect * pgpersect;
const blkcnt_t pgcnt = npages - (start - startpg);
ret = at25ee_erasepages(priv, endsect * pgpersect, pgcnt);
if (ret < 0)
{
return ret;
}
}
}
else
{
const int ret = at25ee_erasepages(priv, startpg, npages);
if (ret < 0)
{
return ret;
}
}
if (priv->blocksize > priv->pgsize)
{
return (int)((npages * priv->pgsize) / priv->blocksize);
}
else
{
return (int)nblocks;
}
#endif /* CONFIG_AT25EE_ENABLE_BLOCK_ERASE */
}
/****************************************************************************
* 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 on success, a negated error code on failure
*
****************************************************************************/
static ssize_t at25ee_read(FAR struct mtd_dev_s *dev, off_t offset,
size_t nbytes, FAR uint8_t *buf)
{
FAR struct at25ee_dev_s *priv = (FAR struct at25ee_dev_s *)dev;
DEBUGASSERT(dev);
DEBUGASSERT(buf);
return priv->eepdev->read(priv->eepdev, offset, nbytes, buf);
}
/****************************************************************************
* 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 on success, a negated error code on failure
*
****************************************************************************/
static ssize_t at25ee_write(FAR struct mtd_dev_s *dev, off_t offset,
size_t nbytes, FAR const uint8_t *buf)
{
FAR struct at25ee_dev_s *priv = (FAR struct at25ee_dev_s *)dev;
DEBUGASSERT(dev);
DEBUGASSERT(buf);
return priv->eepdev->write(priv->eepdev, offset, nbytes, buf);
}
/****************************************************************************
* 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 read on success, a negated error code on failure
*
****************************************************************************/
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 nbytes;
ssize_t ret;
DEBUGASSERT(dev);
DEBUGASSERT(priv->blocksize > 0);
offset = startblock * priv->blocksize;
nbytes = nblocks * priv->blocksize;
ret = at25ee_read(dev, offset, nbytes, buf);
if (ret > 0)
{
ret /= priv->blocksize;
}
return ret;
}
/****************************************************************************
* 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:
* Number of blocks written on success, a negated error code on failure
*
****************************************************************************/
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;
off_t offset;
ssize_t nbytes;
ssize_t ret;
DEBUGASSERT(dev);
DEBUGASSERT(priv->blocksize > 0);
offset = startblock * priv->blocksize;
nbytes = nblocks * priv->blocksize;
ret = at25ee_write(dev, offset, nbytes, buf);
if (ret > 0)
{
ret /= priv->blocksize;
}
return ret;
}
/****************************************************************************
* 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;
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
*/
geo->blocksize = priv->blocksize;
geo->erasesize = priv->blocksize;
geo->neraseblocks = priv->size / priv->blocksize;
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)
{
info->numsectors = priv->size / priv->blocksize;
info->sectorsize = priv->blocksize;
info->startsector = 0;
info->parent[0] = '\0';
ret = OK;
}
}
break;
case MTDIOC_BULKERASE:
{
ret = priv->eepdev->ioctl(priv->eepdev, EEPIOC_CHIPERASE, 0);
}
break;
case MTDIOC_SETSPEED:
{
ret = priv->eepdev->ioctl(priv->eepdev, EEPIOC_SETSPEED, arg);
}
break;
default:
{
/* Tunnel the command to the EEPROM driver */
ret = priv->eepdev->ioctl(priv->eepdev, cmd, arg);
}
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
* spi_devid - SPI device ID to manage CS lines in board
* devtype - device type, see eeprom/eeprom.h
* readonly - sets block driver to be readonly
*
* Returned Value:
* Initialized device instance (success) or NULL (fail)
*
****************************************************************************/
FAR struct mtd_dev_s *at25ee_initialize(FAR struct spi_dev_s *dev,
uint16_t spi_devid,
enum eeprom_25xx_e devtype,
int readonly)
{
FAR struct at25ee_dev_s *priv;
struct eeprom_geometry_s geo;
int ret;
/* Initialize the EEPROM driver */
FAR struct eeprom_dev_s *eepdev =
ee25xx_initialize_as_eeprom_dev(dev, spi_devid, devtype, readonly);
if (eepdev == NULL)
{
ferr("ERROR: Failed to initialize the EEPROM driver\n");
return NULL;
}
/* Allocate the device structure */
priv = (FAR struct at25ee_dev_s *)kmm_zalloc(sizeof(struct at25ee_dev_s));
if (priv == NULL)
{
ferr("ERROR: Failed to allocate device structure\n");
return NULL;
}
/* Retrieve the geometry */
ret = eepdev->ioctl(eepdev, EEPIOC_GEOMETRY,
(unsigned long)((uintptr_t)&geo));
if (ret < 0)
{
ferr("ERROR retrieving the EEPROM geometry (ret = %d)\n", ret);
goto cleanup;
}
/* Initialize the allocated structure */
priv->eepdev = eepdev;
priv->size = geo.pagesize * geo.npages;
priv->pgsize = geo.pagesize;
priv->sectsize = geo.sectsize;
#ifdef CONFIG_USE_NATIVE_AT25EE_BLOCK_SIZE
priv->blocksize = geo.pagesize;
#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 = geo.pagesize;
}
else
{
priv->blocksize = CONFIG_MANUAL_AT25EE_BLOCK_SIZE;
}
#endif
finfo("MTD wrapper around EEPROM device: %zd bytes, %" PRIu16
" per page, %" PRIu16 " per block, readonly %s\n",
priv->size, (uint16_t)priv->pgsize, priv->blocksize,
readonly ? "ON" : "OFF");
priv->mtd.erase = at25ee_erase;
priv->mtd.bread = at25ee_bread;
priv->mtd.bwrite = at25ee_bwrite;
priv->mtd.read = at25ee_read;
#ifdef CONFIG_MTD_BYTE_WRITE
priv->mtd.write = at25ee_write;
#endif
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;
cleanup:
ee25xx_teardown(eepdev);
kmm_free(priv);
return NULL;
}
/****************************************************************************
* Name: at25ee_teardown
*
* Description:
* Teardown a previously created at25ee device.
*
* Input Parameters:
* mtd - Pointer to the mtd driver instance.
*
****************************************************************************/
void at25ee_teardown(FAR struct mtd_dev_s *mtd)
{
FAR struct at25ee_dev_s *priv = (FAR struct at25ee_dev_s *)mtd;
if (priv != NULL)
{
/* Destroy the underlying EEPROM driver */
ee25xx_teardown(priv->eepdev);
/* Free the memory */
kmm_free(priv);
}
}