/**************************************************************************** * drivers/eeprom/spi_xx25xx.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. * ****************************************************************************/ /* This is a driver for SPI EEPROMs that use 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) * 20 MHz for <1Mbit STM devices (>4.5V) * 16 MHz for 1Mbit STM devices (>4.5V) * 10 MHz for all STM devices (>2.5V) * 5 MHz for 1Mbit STM devices (>1.8V) * 2 MHz for 1Mbit STM devices (>1.7V) * 5 MHz for 2Mbit STM devices * 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 * ST Microelectronics * M95010 128 16 1 * M95020 256 16 1 * M95040 512 16 1+bit * M95080 1024 32 2 * M95160 2048 32 2 * M95320 4096 32 2 * M95640 8192 32 2 * M95128 16384 64 2 * M95256 32768 64 2 * M95512 65536 128 2 * M95M01 131072 256 3 * M95M02 262144 256 3 */ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #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) * * 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 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 */ mutex_t lock; /* 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 */ /* STM devices */ { 11, 5, 3, 0 }, /* M95M02 262144 256 3 */ }; /* Driver operations */ static const struct file_operations g_ee25xx_fops = { ee25xx_open, /* open */ ee25xx_close, /* close */ ee25xx_read, /* read */ ee25xx_write, /* write */ ee25xx_seek, /* seek */ ee25xx_ioctl, /* ioctl */ }; /**************************************************************************** * 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. */ 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); SPI_HWFEATURES(dev, 0); SPI_SETFREQUENCY(dev, CONFIG_EE25XX_FREQUENCY); } /**************************************************************************** * Name: ee25xx_unlock ****************************************************************************/ static inline void ee25xx_unlock(FAR struct spi_dev_s *dev) { 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 */ ee25xx_lock(priv->spi); SPI_SELECT(priv->spi, SPIDEV_EEPROM(0), true); /* Send "Read Status Register (RDSR)" command */ 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(0), false); ee25xx_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 & EE25XX_SR_WIP) != 0) { nxsig_usleep(1000); } } while ((status & EE25XX_SR_WIP) != 0); } /**************************************************************************** * 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(0), true); SPI_SEND(spi, enable ? EE25XX_CMD_WREN : EE25XX_CMD_WRDIS); SPI_SELECT(spi, SPIDEV_EEPROM(0), 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(0), true); ee25xx_sendcmd(eedev->spi, EE25XX_CMD_WRITE, eedev->addrlen, devaddr); SPI_SNDBLOCK(eedev->spi, data, len); SPI_SELECT(eedev->spi, SPIDEV_EEPROM(0), false); ee25xx_unlock(eedev->spi); } /**************************************************************************** * 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->i_private); eedev = inode->i_private; ret = nxmutex_lock(&eedev->lock); if (ret < 0) { return ret; } /* Increment the reference count */ if ((eedev->refs + 1) == 0) { ret = -EMFILE; } else { eedev->refs += 1; } nxmutex_unlock(&eedev->lock); 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->i_private); eedev = inode->i_private; ret = nxmutex_lock(&eedev->lock); if (ret < 0) { return ret; } /* 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; } nxmutex_unlock(&eedev->lock); 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->i_private); eedev = inode->i_private; ret = nxmutex_lock(&eedev->lock); if (ret < 0) { return ret; } /* 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 */ nxmutex_unlock(&eedev->lock); 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; } nxmutex_unlock(&eedev->lock); 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; int ret; DEBUGASSERT(inode->i_private); eedev = inode->i_private; ret = nxmutex_lock(&eedev->lock); if (ret < 0) { return ret; } /* 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(0), 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(0), false); ee25xx_unlock(eedev->spi); /* Update the file position */ filep->f_pos += len; nxmutex_unlock(&eedev->lock); 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->i_private); eedev = inode->i_private; if (eedev->readonly) { return ret; } /* Forbid writes past the end of the device */ if (filep->f_pos >= eedev->size) { return -EFBIG; } /* Clamp len to avoid crossing the end of the memory */ if ((len + filep->f_pos) > eedev->size) { len = eedev->size - filep->f_pos; } ret = nxmutex_lock(&eedev->lock); if (ret < 0) { return ret; } /* 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 */ /* 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 = 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; } nxmutex_unlock(&eedev->lock); 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->i_private); eedev = inode->i_private; UNUSED(eedev); switch (cmd) { default: ret = -ENOTTY; } 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; } nxmutex_init(&eedev->lock); 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; finfo("EEPROM device %s, %"PRIu32" bytes, " "%u per page, addrlen %u, readonly %d\n", devname, eedev->size, eedev->pgsize, eedev->addrlen, eedev->readonly); return register_driver(devname, &g_ee25xx_fops, 0666, eedev); }