/**************************************************************************** * drivers/eeprom/spi_xx25xx.c * * Copyright (C) 2014 Gregory Nutt. All rights reserved. * Author: Sebastien Lorquet * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * 3. Neither the name NuttX nor the names of its contributors may be * used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ****************************************************************************/ /* This is a driver for SPI EEPROMs that uses the same commands as the * 25AA160. * * Write time 5ms, 6ms for 25xx1025 (determined automatically with polling) * Max SPI speed is : * 10 MHz for -A/B/C/D/E/UID versions * 1 MHz for 25AA versions * 2 MHz for 25LC versions * 3 MHz for 25C versions * 10 MHz for 25xxN versions where N=128 and more * 20 MHz for 25AA512, 25LC512, 25xx1024 * 20 MHz for Atmel devices (>4.5V) * 10 MHz for Atmel devices (>2.5V) * All devices have the same instruction set. * * The following devices should be supported: * * Manufacturer Device Bytes PgSize AddrLen * Microchip * 25xx010A 128 16 1 * 25xx020A 256 16 1 * 25AA02UID 256 16 1 * 25AA02E48 256 16 1 * 25AA02E64 256 16 1 * 25xx040 512 16 1+bit * 25xx040A 512 16 1+bit * 25xx080 1024 16 1 * 25xx080A 1024 16 2 * 25xx080B 1024 32 2 * 25xx080C 1024 16 x * 25xx080D 1024 32 x * 25xx160 2048 16 2 * 25xx160A/C 2048 16 2 TESTED * 25xx160B/D 2048 32 2 * 25xx160C 2048 16 2 * 25xx160D 2048 32 2 * 25xx320 4096 32 2 * 25xx320A 4096 32 2 * 25xx640 8192 32 2 * 25xx640A 8192 32 2 * 25xx128 16384 64 2 * 25xx256 32768 64 2 * 25xx512 65536 128 2 * 25xx1024 131072 256 3 * Atmel * AT25010B 128 8 1 * AT25020B 256 8 1 * AT25040B 512 8 1+bit * AT25080B 1024 32 2 * AT25160B 2048 32 2 * AT25320B 4096 32 2 * AT25640B 8192 32 2 * AT25128B 16384 64 2 * AT25256B 32768 64 2 * AT25512 65536 128 2 * AT25M01 131072 256 3 */ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ #ifndef CONFIG_EE25XX_SPIMODE # define CONFIG_EE25XX_SPIMODE 0 #endif /* EEPROM commands * High bit of low nibble used for A8 in 25xx040/at25040 products */ #define EE25XX_CMD_WRSR 0x01 #define EE25XX_CMD_WRITE 0x02 #define EE25XX_CMD_READ 0x03 #define EE25XX_CMD_WRDIS 0x04 #define EE25XX_CMD_RDSR 0x05 #define EE25XX_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) */ /* SR bits definitions */ #define EE25XX_SR_WIP 0x01 /* Write in Progress */ #define EE25XX_SR_WEL 0x02 /* Write Enable Latch */ #define EE25XX_SR_BP0 0x04 /* First Block Protect bit */ #define EE25XX_SR_BP1 0x08 /* Second Block Protect bit */ #define EE25XX_SR_WPEN 0x80 /* Write Protect Enable */ #define EE25XX_DUMMY 0xFF /**************************************************************************** * Types ****************************************************************************/ /* Device geometry description, compact form (2 bytes per entry) */ struct ee25xx_geom_s { uint8_t bytes : 4; /*power of two of 128 bytes (0:128 1:256 2:512 etc) */ uint8_t pagesize : 4; /*power of two 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; /*special address management for 25xx040, 1=A8 in inst*/ }; /* Private data attached to the inode */ struct ee25xx_dev_s { 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 addrlen; /* number of BITS in data addresses */ sem_t sem; /* file access serialization */ uint8_t refs; /* The number of times the device has been opened */ uint8_t readonly; /* Flags */ }; /**************************************************************************** * Private Function Prototypes ****************************************************************************/ static int ee25xx_open(FAR struct file *filep); static int ee25xx_close(FAR struct file *filep); static off_t ee25xx_seek(FAR struct file *filep, off_t offset, int whence); static ssize_t ee25xx_read(FAR struct file *filep, FAR char *buffer, size_t buflen); static ssize_t ee25xx_write(FAR struct file *filep, FAR const char *buffer, size_t buflen); static int ee25xx_ioctl(FAR struct file *filep, int cmd, unsigned long arg); /**************************************************************************** * Private Data ****************************************************************************/ /* Supported device geometries. * One geometry can fit more than one device. * The user will use an enum'ed index from include/eeprom/spi_xx25xx.h */ static const struct ee25xx_geom_s g_ee25xx_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*/ }; /* Driver operations */ static const struct file_operations ee25xx_fops = { ee25xx_open, /* open */ ee25xx_close, /* close */ ee25xx_read, /* read */ ee25xx_write, /* write */ ee25xx_seek, /* seek */ ee25xx_ioctl /* ioctl */ #ifndef CONFIG_DISABLE_POLL , 0 /* poll */ #endif }; /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: ee25xx_lock ****************************************************************************/ static void ee25xx_lock(FAR struct spi_dev_s *dev) { /* 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. */ (void)SPI_LOCK(dev, true); /* After locking the SPI bus, the we also need call the setfrequency, * setbits, and setmode methods to make sure that the SPI is properly * configured for the device. If the SPI bus is being shared, then it may * have been left in an incompatible state. */ SPI_SETMODE(dev, CONFIG_EE25XX_SPIMODE); SPI_SETBITS(dev, 8); (void)SPI_SETFREQUENCY(dev, 10000000); /* This is the default speed */ } /**************************************************************************** * Name: ee25xx_unlock ****************************************************************************/ static inline void ee25xx_unlock(FAR struct spi_dev_s *dev) { (void)SPI_LOCK(dev, false); } /**************************************************************************** * Name: ee25xx_sendcmd * * Description: Send command and address as one transaction to take advantage * of possible faster DMA transfers. Sending byte per byte is FAR FAR slower. * ****************************************************************************/ static void ee25xx_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: ee25xx_waitwritecomplete * * Description: loop until the write operation is done. * ****************************************************************************/ static void ee25xx_waitwritecomplete(struct ee25xx_dev_s *priv) { uint8_t status; /* Loop as long as the memory is busy with a write cycle */ do { /* Select this FLASH part */ SPI_SELECT(priv->spi, SPIDEV_EEPROM, true); /* Send "Read Status Register (RDSR)" command */ (void)SPI_SEND(priv->spi, EE25XX_CMD_RDSR); /* Send a dummy byte to generate the clock needed to shift out the * status */ status = SPI_SEND(priv->spi, EE25XX_DUMMY); /* Deselect the FLASH */ SPI_SELECT(priv->spi, SPIDEV_EEPROM, false); /* 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 & EE25XX_SR_WIP) != 0) { ee25xx_unlock(priv->spi); usleep(1000); ee25xx_lock(priv->spi); } } while ((status & EE25XX_SR_WIP) != 0); fvdbg("Complete\n"); } /**************************************************************************** * Name: ee25xx_writeenable * * Description: Enable or disable write operations. * This is required before any write, since a lot of operations automatically * disables the write latch. * ****************************************************************************/ static void ee25xx_writeenable(FAR struct spi_dev_s *spi, int enable) { ee25xx_lock(spi); SPI_SELECT(spi, SPIDEV_EEPROM, true); SPI_SEND(spi, enable ? EE25XX_CMD_WREN : EE25XX_CMD_WRDIS); SPI_SELECT(spi, SPIDEV_EEPROM, false); ee25xx_unlock(spi); } /**************************************************************************** * Name: ee25xx_writepage * * Description: Write data to the EEPROM, NOT crossing page boundaries. * ****************************************************************************/ static void ee25xx_writepage(FAR struct ee25xx_dev_s *eedev, uint32_t devaddr, FAR const char *data, size_t len) { ee25xx_lock(eedev->spi); SPI_SELECT(eedev->spi, SPIDEV_EEPROM, true); ee25xx_sendcmd(eedev->spi, EE25XX_CMD_WRITE, eedev->addrlen, devaddr); SPI_SNDBLOCK(eedev->spi, data, len); SPI_SELECT(eedev->spi, SPIDEV_EEPROM, false); ee25xx_unlock(eedev->spi); } /**************************************************************************** * Name: ee25xx_semtake * * Acquire a resource to access the device. * The purpose of the semaphore is to block tasks that try to access the * EEPROM while another task is actively using it. * ****************************************************************************/ static void ee25xx_semtake(FAR struct ee25xx_dev_s *eedev) { /* Take the semaphore (perhaps waiting) */ while (sem_wait(&eedev->sem) != 0) { /* The only case that an error should occur here is if * the wait was awakened by a signal. */ ASSERT(errno == EINTR); } } /**************************************************************************** * Name: ee25xx_semgive * * Release a resource to access the device. * ****************************************************************************/ static inline void ee25xx_semgive(FAR struct ee25xx_dev_s *eedev) { sem_post(&eedev->sem); } /**************************************************************************** * Driver Functions ****************************************************************************/ /**************************************************************************** * Name: ee25xx_open * * Description: Open the block device * ****************************************************************************/ static int ee25xx_open(FAR struct file *filep) { FAR struct inode *inode = filep->f_inode; FAR struct ee25xx_dev_s *eedev; int ret = OK; DEBUGASSERT(inode && inode->i_private); eedev = (FAR struct ee25xx_dev_s *)inode->i_private; ee25xx_semtake(eedev); /* Increment the reference count */ if ( (eedev->refs + 1) == 0) { ret = -EMFILE; } else { eedev->refs += 1; } ee25xx_semgive(eedev); return ret; } /**************************************************************************** * Name: ee25xx_close * * Description: Close the block device * ****************************************************************************/ static int ee25xx_close(FAR struct file *filep) { FAR struct inode *inode = filep->f_inode; FAR struct ee25xx_dev_s *eedev; int ret = OK; DEBUGASSERT(inode && inode->i_private); eedev = (FAR struct ee25xx_dev_s *)inode->i_private; ee25xx_semtake(eedev); /* Decrement the reference count. I want the entire close operation * to be atomic wrt other driver operations. */ if (eedev->refs == 0) { ret = -EIO; } else { eedev->refs -= 1; } ee25xx_semgive(eedev); return ret; } /**************************************************************************** * Name: ee25xx_seek * * Remark: Copied from bchlib * ****************************************************************************/ static off_t ee25xx_seek(FAR struct file *filep, off_t offset, int whence) { FAR struct ee25xx_dev_s *eedev; off_t newpos; int ret; FAR struct inode *inode = filep->f_inode; DEBUGASSERT(inode && inode->i_private); eedev = (FAR struct ee25xx_dev_s *)inode->i_private; ee25xx_semtake(eedev); /* Determine the new, requested file position */ switch (whence) { case SEEK_CUR: newpos = filep->f_pos + offset; break; case SEEK_SET: newpos = offset; break; case SEEK_END: newpos = eedev->size + offset; break; default: /* Return EINVAL if the whence argument is invalid */ ee25xx_semgive(eedev); return -EINVAL; } /* Opengroup.org: * * "The lseek() function shall allow the file offset to be set beyond the end * of the existing data in the file. If data is later written at this point, * subsequent reads of data in the gap shall return bytes with the value 0 * until data is actually written into the gap." * * We can conform to the first part, but not the second. But return EINVAL if * * "...the resulting file offset would be negative for a regular file, block * special file, or directory." */ if (newpos >= 0) { filep->f_pos = newpos; ret = newpos; } else { ret = -EINVAL; } ee25xx_semgive(eedev); return ret; } /**************************************************************************** * Name: ee25xx_read ****************************************************************************/ static ssize_t ee25xx_read(FAR struct file *filep, FAR char *buffer, size_t len) { FAR struct ee25xx_dev_s *eedev; FAR struct inode *inode = filep->f_inode; DEBUGASSERT(inode && inode->i_private); eedev = (FAR struct ee25xx_dev_s *)inode->i_private; ee25xx_semtake(eedev); /* trim len if read would go beyond end of device */ if ((filep->f_pos + len) > eedev->size) { len = eedev->size - filep->f_pos; } ee25xx_lock(eedev->spi); SPI_SELECT(eedev->spi, SPIDEV_EEPROM, true); /* STM32F4Disco: There is a 25 us delay here */ ee25xx_sendcmd(eedev->spi, EE25XX_CMD_READ, eedev->addrlen, filep->f_pos); /* STM32F4Disco: There is a 42 us delay here */ SPI_RECVBLOCK(eedev->spi, buffer, len); /* STM32F4Disco: There is a 20 us delay here */ SPI_SELECT(eedev->spi, SPIDEV_EEPROM, false); ee25xx_unlock(eedev->spi); /* Update the file position */ filep->f_pos += len; ee25xx_semgive(eedev); return len; } /**************************************************************************** * Name: ee25xx_write ****************************************************************************/ static ssize_t ee25xx_write(FAR struct file *filep, FAR const char *buffer, size_t len) { FAR struct ee25xx_dev_s *eedev; size_t cnt; int pageoff; FAR struct inode *inode = filep->f_inode; int ret = -EACCES; DEBUGASSERT(inode && inode->i_private); eedev = (FAR struct ee25xx_dev_s *)inode->i_private; if (eedev->readonly) { return ret; } /* Clamp len to avoid crossing the end of the memory */ if ( (len + filep->f_pos) > eedev->size) { len = eedev->size - filep->f_pos; } /* From this point no failure cannot be detected anymore. * The user should verify the write by rereading memory. */ ret = len; /* save number of bytes written */ ee25xx_semtake(eedev); /* Writes cant 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 = filep->f_pos & (eedev->pgsize - 1); cnt = eedev->pgsize - pageoff; if (cnt > len) { cnt = len; } if (pageoff > 0) { ee25xx_writeenable(eedev->spi, true); ee25xx_writepage(eedev, filep->f_pos, buffer, cnt); ee25xx_waitwritecomplete(eedev); len -= cnt; buffer += cnt; filep->f_pos += cnt; } /* Then, write remaining bytes at page-aligned addresses */ while (len > 0) { cnt = len; if (cnt > eedev->pgsize) { cnt = eedev->pgsize; } ee25xx_writeenable(eedev->spi, true); ee25xx_writepage(eedev, filep->f_pos, buffer, cnt); ee25xx_waitwritecomplete(eedev); len -= cnt; buffer += cnt; filep->f_pos += cnt; } ee25xx_semgive(eedev); return ret; } /**************************************************************************** * Name: ee25xx_ioctl * * Description: TODO: Erase a sector/page/device or read device ID. * This is completely optional and only applies to bigger devices. * ****************************************************************************/ static int ee25xx_ioctl(FAR struct file *filep, int cmd, unsigned long arg) { FAR struct ee25xx_dev_s *eedev; FAR struct inode *inode = filep->f_inode; int ret = 0; DEBUGASSERT(inode && inode->i_private); eedev = (FAR struct ee25xx_dev_s *)inode->i_private; switch (cmd) { default: (void)eedev; ret = -EINVAL; } return ret; } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: ee25xx_initialize * * Description: Bind a EEPROM driver to an SPI bus. The user MUST provide * a description of the device geometry, since it is not possible to read * this information from the device (contrary to the SPI flash devices). * ****************************************************************************/ int ee25xx_initialize(FAR struct spi_dev_s *dev, FAR char *devname, int devtype, int readonly) { FAR struct ee25xx_dev_s *eedev; /* Check device type early */ if ((devtype < 0) || (devtype >= sizeof(g_ee25xx_devices) / sizeof(g_ee25xx_devices[0]))) { return -EINVAL; } eedev = kmm_zalloc(sizeof(struct ee25xx_dev_s)); if (!eedev) { return -ENOMEM; } sem_init(&eedev->sem, 0, 1); eedev->spi = dev; eedev->size = 128 << g_ee25xx_devices[devtype].bytes; eedev->pgsize = 8 << g_ee25xx_devices[devtype].pagesize; eedev->addrlen = g_ee25xx_devices[devtype].addrlen << 3; if ( (g_ee25xx_devices[devtype].flags & 1)) { eedev->addrlen = 9; } eedev->readonly = !!readonly; lowsyslog(LOG_NOTICE, "EEPROM device %s, %d bytes, %d per page, addrlen %d, readonly %d\n", devname, eedev->size, eedev->pgsize, eedev->addrlen, eedev->readonly); return register_driver(devname, &ee25xx_fops, 0666, eedev); }