This is an automated email from the ASF dual-hosted git repository.

xiaoxiang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git


The following commit(s) were added to refs/heads/master by this push:
     new 222ca5b040 Add MTD for AT25 eeprom
222ca5b040 is described below

commit 222ca5b0403fb1f834dc2382019a85ec398f7716
Author: TimJTi <[email protected]>
AuthorDate: Tue Dec 19 14:03:58 2023 +0000

    Add MTD for AT25 eeprom
---
 drivers/mtd/CMakeLists.txt        |    4 +
 drivers/mtd/Kconfig               |  300 ++++++----
 drivers/mtd/Make.defs             |    4 +
 drivers/mtd/at25ee.c              | 1083 +++++++++++++++++++++++++++++++++++++
 include/nuttx/eeprom/spi_xx25xx.h |    4 +-
 include/nuttx/mtd/mtd.h           |   22 +
 6 files changed, 1322 insertions(+), 95 deletions(-)

diff --git a/drivers/mtd/CMakeLists.txt b/drivers/mtd/CMakeLists.txt
index bf8307ffdf..af7fbcdeaf 100644
--- a/drivers/mtd/CMakeLists.txt
+++ b/drivers/mtd/CMakeLists.txt
@@ -85,6 +85,10 @@ if(CONFIG_MTD)
     list(APPEND SRCS at24xx.c)
   endif()
 
+  if(CONFIG_MTD_AT25EE)
+    list(APPEND SRCS at25ee.c)
+  endif()
+
   if(CONFIG_MTD_AT45DB)
     list(APPEND SRCS at45db.c)
   endif()
diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig
index 136272bdff..bf36892c53 100644
--- a/drivers/mtd/Kconfig
+++ b/drivers/mtd/Kconfig
@@ -33,7 +33,8 @@ config MTD_PARTITION
                is described in:
 
                        include/nuttx/mtd/mtd.h
-                       FAR struct mtd_dev_s *mtd_partition(FAR struct 
mtd_dev_s *mtd, off_t offset, off_t nblocks);
+                       FAR struct mtd_dev_s *mtd_partition(FAR struct 
mtd_dev_s *mtd,
+                       off_t offset, off_t nblocks);
 
                Each call to mtd_partition() will create a new MTD driver 
instance
                managing the sub-region of flash beginning at 'offset' (in 
blocks)
@@ -115,7 +116,8 @@ config MTD_READAHEAD
        select DRVR_INVALIDATE
        select DRVR_READBYTES
        ---help---
-               Build the mtd_rwbuffer layer and enable support for read-ahead 
buffering.
+               Build the mtd_rwbuffer layer and enable support for read-ahead
+               buffering.
 
 if MTD_READAHEAD
 
@@ -350,7 +352,7 @@ config NULLMTD_BLOCKSIZE
        default 512
 
 config NULLMTD_ERASESIZE
-       int "MTD null detault erase block size"
+       int "MTD null default erase block size"
        default 4096
 
 config NULLMTD_ERASESTATE
@@ -382,10 +384,10 @@ config AT24XX_SIZE
        int "AT24xx size (Kbit)"
        default 64
        ---help---
-               This is the XX in the AT24Cxx part number.  For example, if you 
have a
-               AT 24C512, then the correct value is 512.  This value is also 
the capacity
-               of the part in kilobits.  For example, the 24C512 supports 512 
Kbits or
-               512 /8 = 64 KiB.
+               This is the XX in the AT24Cxx part number.  For example, if you 
have
+               an AT24C64, then the correct value is 64.
+               This value is also the capacity of the part in kilobits.
+               For example, the 64 supports 64 Kbits or 64/8 = 8 KiB.
 
 config AT24XX_ADDR
        hex "AT24XX I2C address"
@@ -403,8 +405,8 @@ config AT24XX_EXTENDED
        bool "Extended memory"
        default n
        ---help---
-               If the device supports extended memory, then this operation may 
be set
-               to enabled the MTDIOC_EXTENDED ioctl() operation.  When the
+               If the device supports extended memory, then this operation may 
be
+               set to enable the MTDIOC_EXTENDED ioctl() operation.  When the
                extended operation is selected, calls to the driver read method 
will
                return data from the extended memory region.
 
@@ -422,12 +424,107 @@ config AT24XX_FREQUENCY
        int "AT24xx I2C bus frequency"
        default 100000
        ---help---
-               Set the I2C frequency to use when accessing the AT24CXX EEPROM. 
This value
-               must represent a valid I2C speed (normally less than 400.000) 
or the driver
-               might fail.
+               Set the I2C frequency to use when accessing the AT24CXX EEPROM. 
+               This value must represent a valid I2C speed (normally less than
+               400.000) or the driver might fail.
 
 endif # MTD_AT24XX
 
+config MTD_AT25EE
+       bool "SPI-based AT25xx EEPROM"
+       default n
+       select SPI
+       select MTD_BYTE_WRITE
+       ---help---
+               Build support for SPI-based AT25xx type EEPROMs. MTD on EEPROM 
can
+               perform poorly, so it is possible only usable if the EEPROM has 
a 
+               clock speed 10MHz or higher. EEPROMs that use the same commands 
as
+               the 25AA160 should work OK.
+
+if MTD_AT25EE
+
+choice
+       prompt "Block Size"
+       default USE_NATIVE_AT25EE_BLOCK_SIZE
+       ---help---
+               For applications where a file system is used on the AT25 EEPROM,
+               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, 128, 256 or
+               512 bytes.
+
+               In any event, the block size *must* be an even multiple of the 
+               number of pages and, often, needs to be a factor 2.
+
+               This is up to the user to check!
+
+config USE_NATIVE_AT25EE_BLOCK_SIZE
+       bool "Use EEPROM's native block size"
+
+config MANUALLY_SET_AT25EE_BLOCK_SIZE
+       bool "Manually set block size"
+
+endchoice # Block Size
+
+if MANUALLY_SET_AT25EE_BLOCK_SIZE
+
+config MANUAL_AT25EE_BLOCK_SIZE
+       int "Manually-set EEPROM block size"
+       default 512
+
+endif # MANUALLY_SET_BLOCK_SIZE
+
+config AT25EE_ENABLE_BLOCK_ERASE
+       bool "Enabled block erase"
+       default n
+       ---help---
+               EEPROM does not need to be erased before write. However, in some
+               applications (e.g if an erase verify is wanted, or if a 
particular
+               file system requires this) block erase (i.e. writing each byte 
to
+               0xff) can be enabled here.
+
+config AT25EE_SPIMODE
+       int "AT25EE SPI Mode"
+       default 0
+
+config AT25EE_SPIFREQUENCY
+       int "AT25EE SPI Frequency"
+       default 10000000
+
+config AT25EE_START_DELAY
+       int "AT25EE startdelay"
+       ---help---
+               The delay between CS active and first CLK. In ns.
+       depends on SPI_DELAY_CONTROL
+       range 0 1000000
+       default 5000
+
+config AT25EE_STOP_DELAY
+       int "AT25EE stopdelay"
+       ---help---
+               The delay between last CLK and CS inactive. In ns.
+       depends on SPI_DELAY_CONTROL
+       range 0 1000000
+       default 5000
+
+config AT25EE_CS_DELAY
+       int "AT25EE csdelay"
+       ---help---
+               The delay between CS inactive and CS active again. In ns.
+       depends on SPI_DELAY_CONTROL
+       range 0 1000000
+       default 5000
+
+config AT25EE_IFDELAY
+       int "AT25EE ifdelay"
+       ---help---
+               The delay between frames. In ns.
+       depends on SPI_DELAY_CONTROL
+       range 0 1000000
+       default 5000
+
+endif # MTD_AT25EE
+
 config MTD_AT25
        bool "SPI-based AT25 FLASH"
        default n
@@ -502,18 +599,20 @@ config M25P_MANUFACTURER
        hex "M25P manufacturers ID"
        default 0x20
        ---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.
+               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 M25P_MEMORY_TYPE
        hex "M25P memory type ID"
        default 0x20
        ---help---
-               The memory type for M25 "P" series is 0x20, but the driver also 
supports "F" series
-               devices, such as the EON EN25F80 part which adds a 4K sector 
erase capability.  The
-               memory type for "F" series parts from EON is 0x31.  The 4K 
sector erase size will
-               automatically be enabled when filesystems that can use it are 
enabled, such as SMART.
+               The memory type for M25 "P" series is 0x20, but the driver also
+               supports "F" series devices, such as the EON EN25F80 part which 
adds
+               a 4K sector erase capability.  The memory type for "F" series 
parts
+               from EON is 0x31.  The 4K sector erase size will automatically 
be
+               enabled when filesystems that can use it are enabled, such as 
SMART.
 
 config MT25Q_MEMORY_TYPE
        hex "MT25Q memory type ID"
@@ -527,7 +626,8 @@ config M25P_SUBSECTOR_ERASE
        ---help---
                Some devices (such as the EON EN25F80) support a smaller erase 
block
                size (4K vs 64K).  This option enables support for sub-sector 
erase.
-               The SMART file system can take advantage of this option if it 
is enabled.
+               The SMART file system can take advantage of this option if it is
+               enabled.
 
 endif # MTD_M25P
 
@@ -558,7 +658,8 @@ config MX25L_SUBSECTOR_ERASE
        ---help---
                Some devices (such as the EON EN25F80) support a smaller erase 
block
                size (4K vs 64K).  This option enables support for sub-sector 
erase.
-               The SMART file system can take advantage of this option if it 
is enabled.
+               The SMART file system can take advantage of this option if it is
+               enabled.
 
 config MX25L_DEBUG
        bool "Enable driver debug features"
@@ -672,7 +773,8 @@ config MTD_W25QXXXJV
        bool "QuadSPI-based Winbond W25QXXXJV family FLASH"
        default n
        ---help---
-               Support the W25Q016JV, W25Q032JV, W25Q064JV, W25Q128JV, 
W25Q256JV, W25Q512JV, W25Q01JV
+               Support the W25Q016JV, W25Q032JV, W25Q064JV, W25Q128JV, 
W25Q256JV,
+               W25Q512JV, W25Q01JV
 
 if MTD_W25QXXXJV
 
@@ -744,13 +846,14 @@ config MTD_SMART
        bool "Sector Mapped Allocation for Really Tiny (SMART) Flash support"
        default n
        ---help---
-               The MP25x series of Flash devices are typically very small and 
have a very large
-               erase block size.  This causes issues with the standard Flash 
Translation Layer
-               block driver since it tries to allocate a RAM block the size of 
a flash erase
-               block, which is typically 64K.  This block driver uses a 
different approach
-               to sacrifice performance for RAM memory footprint by saving 
data in sectors
-               (typically 2K - 4K based on memory size) and relocating sectors 
as needed when
-               an erase block needs to be erased.
+               The MP25x series of Flash devices are typically very small and 
have a
+               very large erase block size.  This causes issues with the 
standard
+               Flash Translation Layer block driver since it tries to allocate 
a RAM
+               block the size of a flash erase block, which is typically 64K.
+               This block driver uses a different approach     to sacrifice 
performance
+               for RAM memory footprint by saving data in sectors (typically 
2K - 4K
+               based on memory size) and relocating sectors as needed when     
an erase
+               block needs to be erased.
 
 if MTD_SMART
 
@@ -766,9 +869,9 @@ config MTD_SMART_SECTOR_SIZE
        int "SMART Device sector size"
        default 1024
        ---help---
-               Sets the size of a single allocation on the SMART device.  
Larger sector sizes
-               reduce overhead per sector, but cause more wasted space with a 
lot of smaller
-               files.
+               Sets the size of a single allocation on the SMART device.
+               Larger sector sizes reduce overhead per sector, but cause more 
wasted
+               space with a lot of smaller files.
 
 config MTD_SMART_WRITEBUFFER
        bool "Enable SMART write buffering"
@@ -785,10 +888,11 @@ config MTD_SMART_WEAR_LEVEL
        depends on MTD_SMART
        default y
        ---help---
-               Adds extra logic and RAM to guarantee equal wear leveling of 
the FLASH
-               device by recording and monitoring erase block operations and 
selecting
-               sector allocations to ensure all erase blocks are worn evenly.  
This will
-               evenly wear both dynamic and static data on the device.
+               Adds extra logic and RAM to guarantee equal wear leveling of the
+               FLASH device by recording and monitoring erase block operations 
and
+               selecting sector allocations to ensure all erase blocks are worn
+               evenly.  This will evenly wear both dynamic and static data on 
the
+               device.
 
 if MTD_SMART_WEAR_LEVEL && !SMART_CRC_16
 
@@ -796,11 +900,11 @@ config MTD_SMART_CONVERT_WEAR_FORMAT
        bool "Convert existing non wear leveling FLASH to wear leveling"
        default n
        ---help---
-               Adds a little extra code which detects an existing SMART format 
on a device
-               that was created prior to the wear leveling implementation.  
This conversion
-               only works if either no CRC is being used or if CRC-8 is being 
used as other
-               CRC versions use a different header format and require a 
mksmartfs on the
-               device even if an existing format is there.
+               Adds a little extra code which detects an existing SMART format 
on a
+               device that was created prior to the wear leveling 
implementation.
+               This conversion only works if either no CRC is being used or if 
CRC-8
+               is being used as other CRC versions use a different header 
format and
+               require a mksmartfs on the device even if an existing format is 
there
 
 endif # MTD_SMART_WEAR_LEVEL && !SMART_CRC_16
 
@@ -809,17 +913,19 @@ config MTD_SMART_ENABLE_CRC
        depends on MTD_SMART
        default n
        ---help---
-               Enables logic to compute and validate a CRC for logical 
sectors.  The
-               CRC is calculated for all bytes in the logical sector.  The CRC 
size is
-               selectable (8-bit, 16-bit, 32-bit).  For added protection, 
larger CRCs should
-               be used with larger (2K - 4K) sector sizes.  Enabling CRC 
protection will
-               cause increased sector relocation and increased erase block 
erasures since
-               directory and wear-level status updates can no longer be 
performed in-place
-               and mandate re-writing the information to a new sector.
+               Enables logic to compute and validate a CRC for logical sectors.
+               The CRC is calculated for all bytes in the logical sector.
+               The CRC size is selectable (8-bit, 16-bit, 32-bit). For added
+               protection, larger CRCs should be used with larger (2K - 4K) 
sector
+               sizes.  Enabling CRC protection will cause increased sector
+               relocation and increased erase block erasures since directory 
and
+               wear-level status updates can no longer be performed in-place 
and
+               mandate re-writing the information to a new sector.
 
-               An 8-bit CRC protection scheme can be added to an existing 
non-CRC formatted
-               SMART volume without needing to reformat the drive.  As sectors 
are re-written
-               or relocated, they will be converted to CRC protected sectors.
+               An 8-bit CRC protection scheme can be added to an existing 
non-CRC
+               formatted SMART volume without needing to reformat the drive. As
+               sectors are re-written or relocated, they will be converted to 
CRC
+               protected sectors.
 
 choice
        prompt "CRC level selection"
@@ -827,9 +933,9 @@ choice
        default SMART_CRC_8
        ---help---
                Select the level of CRC protection implemented in the SMART MTD 
layer.
-               Smaller CRC selection uses less overhead per logical sectors, 
but also has
-               a higher probability of not detecting multiple bit errors.  
Devices with
-               larger logical sector sizes should use a larger CRC.
+               Smaller CRC selection uses less overhead per logical sectors, 
but
+               also has a higher probability of not detecting multiple bit 
errors.
+               Devices with larger logical sector sizes should use a larger 
CRC.
 
 config SMART_CRC_8
        bool "CRC-8"
@@ -858,52 +964,54 @@ config MTD_SMART_MINIMIZE_RAM
        depends on MTD_SMART
        default 0
        ---help---
-               Reduces RAM usage in the SMART MTD layer by replacing the 
1-for-1 logical to
-               physical sector map with a smaller cache-based structure.  This 
can save a
-               considerable amount of RAM on devices with a large sector 
count, but at the
-               expense of increased read/write times when a cache miss occurs. 
 If the
-               requested logical sector has not been cached, then the device 
will need to be
-               scanned to located it on the physical medium.
+               Reduces RAM usage in the SMART MTD layer by replacing the 
1-for-1
+               logical to physical sector map with a smaller cache-based 
structure.
+               This can save a considerable amount of RAM on devices with a 
large
+               sector count, but at the expense of increased read/write times 
when a
+               cache miss occurs.  If the requested logical sector has not been
+               cached, then the device will need to be scanned to located it 
on the
+               physical medium.
 
 config MTD_SMART_SECTOR_CACHE_SIZE
        int "Number of entries in the SMART logical sector cache"
        depends on MTD_SMART_MINIMIZE_RAM
        default 512
        ---help---
-               Sets the size of the cache used for logical to physical sector 
mapping.  A
-               larger number allows larger files to be "seek"ed randomly 
without encountering
-               cache misses.  Any files larger than CACH_SIZE * SECTOR_SIZE 
that are sought
-               start to end will cause the cache to flush forcing manual 
scanning of the
-               MTD device to find the logical to physical mappings.
+               Sets the size of the cache used for logical to physical sector
+               mapping.  A     larger number allows larger files to be 
"seek"ed randomly
+               without encountering cache misses. Any files larger than
+               CACHE_SIZE * SECTOR_SIZE that are sought start to end will 
cause the
+               cache to flush forcing manual scanning of the MTD device to 
find the
+               logical to physical mappings.
 
 config MTD_SMART_SECTOR_PACK_COUNTS
        bool "Pack free and release counts when possible"
        depends on MTD_SMART_MINIMIZE_RAM
        default y
        ---help---
-               For volumes with 16 sectors per erase block or less, this 
option causes the
-               free sector and released sector counts used for allocation and 
garbage
-               collection to be packed such that two values are stored per 
byte.  For
-               volumes with 16 sectors per erase block, the 4 LSBs are packed 
and all of
-               the high-order bits are packed separately (8 per byte).  This 
squeezes even
-               more RAM out.
+               For volumes with 16 sectors per erase block or less, this option
+               causes the free sector and released sector counts used for 
allocation
+               and garbage collection to be packed such that two values are 
stored
+               per byte.  For volumes with 16 sectors per erase block, the 4 
LSBs
+               are packed and all of the high-order bits are packed separately
+               (8 per byte).  This squeezes even more RAM out.
 
 config MTD_SMART_SECTOR_ERASE_DEBUG
        bool "Track Erase Block erasure counts"
        depends on MTD_SMART
        default n
        ---help---
-               Allocates an Erase Block erase count array and keeps track of 
the number
-               of erases per erase block.  This data is then presented on the 
procfs
-               interface.
+               Allocates an Erase Block erase count array and keeps track of 
the
+               number of erases per erase block.  This data is then presented 
on the
+               procfs interface.
 
 config MTD_SMART_ALLOC_DEBUG
        bool "RAM Allocation Debug"
        depends on MTD_SMART
        default n
        ---help---
-               Records all SMART MTD layer allocations for debug purposes and 
makes them
-               accessible from the ProcFS interface if it is enabled.
+               Records all SMART MTD layer allocations for debug purposes and 
makes
+               them accessible from the ProcFS interface if it is enabled.
 
 endif # MTD_SMART
 
@@ -938,8 +1046,8 @@ config RAMTRON_EMULATE_SECTOR_SHIFT
                For purpose of the VFAT file system, we emulate them.
 
                Specify sector shift value to determine emulated sector size.
-               The relationship between sector shift value and emulated sector 
size is
-               described in the equation:
+               The relationship between sector shift value and emulated sector 
size
+               is described in the equation:
                RAMTRON_EMULATE_SECTOR_SIZE = (1 << 
RAMTRON_EMULATE_SECTOR_SHIFT)
 
                sector shift value      : sector size in bytes
@@ -1007,11 +1115,12 @@ config SST25_SLOWWRITE
        bool
        default n
        ---help---
-               There used to be a bug in the current code when using the 
higher speed AAI
-               write sequence. The nature of the bug is that the WRDI 
instruction is not
-               working. At the end of the AAI sequence, the status register 
continues to
-               report that the SST25 is write enabled (WEL bit) and in AAI 
mode (AAI
-               bit).  This has been fixed by David Sidrane!
+               There used to be a bug in the current code when using the higher
+               speed AAI write sequence. The nature of the bug is that the WRDI
+               instruction is not working. At the end of the AAI sequence, the
+               status register continues to report that the SST25 is write 
enabled
+               (WEL bit) and in AAI mode (AAI bit).
+               This has been fixed by David Sidrane!
 
 config SST25_SLOWREAD
        bool
@@ -1026,8 +1135,8 @@ config MTD_SST25XX
        ---help---
                With the 64 MBit and larger parts, SST changed the write 
mechanism to
                support page write instead of byte/word write like the smaller 
parts.
-               As a result, the SST25 driver is not compatible with the larger 
density
-               parts, and the SST25XX driver must be used instead.
+               As a result, the SST25 driver is not compatible with the larger
+               density parts, and the SST25XX driver must be used instead.
 
 if MTD_SST25XX
 
@@ -1043,15 +1152,16 @@ config SST25XX_MANUFACTURER
        hex "Manufacturers ID"
        default 0xBF
        ---help---
-               Various manufacturers may have produced the parts.  0xBF is the 
manufacturer ID
-               for the parts manufactured by SST.
+               Various manufacturers may have produced the parts.  0xBF is the
+               manufacturer ID for the parts manufactured by SST.
 
 config SST25XX_MEMORY_TYPE
        hex "Memory type ID"
        default 0x25
        ---help---
-               The memory type for SST25VF065 series is 0x25, but this can be 
modified if needed
-               to support compatible devices from different manufacturers.
+               The memory type for SST25VF065 series is 0x25, but this can be
+               modified if needed to support compatible devices from different
+               manufacturers.
 
 endif # MTD_SST25XX
 
@@ -1060,7 +1170,8 @@ config MTD_SST26
        default n
        select SPI
        ---help---
-               These part are also different from SST25 and SST25XX, they 
support both SPI and QSPI.
+               These part are also different from SST25 and SST25XX, they 
support
+               both SPI and QSPI.
 
 if MTD_SST26
 
@@ -1076,15 +1187,16 @@ config SST26_MANUFACTURER
        hex "Manufacturers ID"
        default 0xBF
        ---help---
-               Various manufacturers may have produced the parts.  0xBF is the 
manufacturer ID
-               for the parts manufactured by SST.
+               Various manufacturers may have produced the parts.  0xBF is the
+               manufacturer ID for the parts manufactured by SST.
 
 config SST26_MEMORY_TYPE
        hex "Memory type ID"
        default 0x26
        ---help---
-               The memory type for SST26VF0xx series is 0x26, but this can be 
modified if needed
-               to support compatible devices from different manufacturers.
+               The memory type for SST26VF0xx series is 0x26, but this can be
+               modified if needed to support compatible devices from different
+               manufacturers.
 
 config SST26_DEBUG
        bool "Debug output from the SST26 driver"
diff --git a/drivers/mtd/Make.defs b/drivers/mtd/Make.defs
index 0db6d50965..549b6c3fb8 100644
--- a/drivers/mtd/Make.defs
+++ b/drivers/mtd/Make.defs
@@ -86,6 +86,10 @@ ifeq ($(CONFIG_MTD_AT24XX),y)
 CSRCS += at24xx.c
 endif
 
+ifeq ($(CONFIG_MTD_AT25EE),y)
+CSRCS += at25ee.c
+endif
+
 ifeq ($(CONFIG_MTD_AT45DB),y)
 CSRCS += at45db.c
 endif
diff --git a/drivers/mtd/at25ee.c b/drivers/mtd/at25ee.c
new file mode 100644
index 0000000000..45873a7c46
--- /dev/null
+++ b/drivers/mtd/at25ee.c
@@ -0,0 +1,1083 @@
+/****************************************************************************
+ * 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 */
diff --git a/include/nuttx/eeprom/spi_xx25xx.h 
b/include/nuttx/eeprom/spi_xx25xx.h
index 5a1f760829..29fcc98cbf 100644
--- a/include/nuttx/eeprom/spi_xx25xx.h
+++ b/include/nuttx/eeprom/spi_xx25xx.h
@@ -25,7 +25,9 @@
  * Public Types
  ****************************************************************************/
 
-/* DO NOT CHANGE ORDER, IT MATCHES CODE IN drivers/eeprom/spieeprom.c */
+/* DO NOT CHANGE ORDER, IT MATCHES CODE IN drivers/eeprom/spieeprom.c and
+ * drivers/mtd/at25ee.c
+ */
 
 enum eeprom_25xx_e
 {
diff --git a/include/nuttx/mtd/mtd.h b/include/nuttx/mtd/mtd.h
index 1a1c4923d9..e6356e0b0e 100644
--- a/include/nuttx/mtd/mtd.h
+++ b/include/nuttx/mtd/mtd.h
@@ -397,6 +397,28 @@ FAR struct mtd_dev_s *at24c_initialize(FAR struct 
i2c_master_s *dev,
 FAR struct mtd_dev_s *at24c_initialize(FAR struct i2c_master_s *dev);
 #endif
 
+/****************************************************************************
+ * Name: at25xx_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 structure (success) of NULL (fail)
+ *
+ ****************************************************************************/
+
+FAR struct mtd_dev_s *at25ee_initialize(FAR struct spi_dev_s *dev,
+                                        int devtype, int readonly);
+
 /****************************************************************************
  * Name: at24c_uninitialize
  *

Reply via email to