/* * Copyright (C) 2008-2009 by egnite GmbH * Copyright (C) 2006 by egnite Software GmbH * * All rights reserved. * * 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 of the copyright holders nor the names of * 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. * * For additional information see http://www.ethernut.de/ */ /*! * \file dev/spi_at45d.c * \brief Routines for Adesto/Atmel AT45 serial DataFlash memory chips. * * \verbatim * $Id: spi_at45d.c 5227 2013-07-19 08:48:47Z u_bonnes $ * \endverbatim */ #include #include #include #include #include #include #include #include #ifndef AT45_WRITE_POLLS #define AT45_WRITE_POLLS 1000 #endif #if 0 /*! \brief Parameter table of known DataFlash types. */ AT45D_INFO at45d_info[] = { {8, 512, 256, 0x0D}, /* AT45DB011B - 128kB */ {9, 512, 264, 0x0C}, /* AT45DB011B - 128kB */ {8, 1025, 256, 0x15}, /* AT45DB021B - 256kB */ {9, 1025, 264, 0x14}, /* AT45DB021B - 256kB */ {8, 2048, 256, 0x1D}, /* AT45DB041B - 512kB */ {9, 2048, 264, 0x1C}, /* AT45DB041B - 512kB */ {8, 4096, 256, 0x25}, /* AT45DB081B - 1MB */ {9, 4096, 264, 0x24}, /* AT45DB081B - 1MB */ {9, 4096, 512, 0x2D}, /* AT45DB0161B - 2MB */ {10, 4096, 528, 0x2C}, /* AT45DB0161B - 2MB */ {9, 8192, 512, 0x35}, /* AT45DB0321B - 4MB */ {10, 8192, 528, 0x34}, /* AT45DB0321B - 4MB */ {10, 8192, 1024, 0x39}, /* AT45DB0642 - 8MB */ {11, 8192, 1056, 0x38}, /* AT45DB0642 - 8MB */ {10, 8192, 1024, 0x3D}, /* AT45DB0642D - 8MB */ {11, 8192, 1056, 0x3C} /* AT45DB0642D - 8MB */ }; /*! \brief Number of known Dataflash types. */ uint_fast8_t at45d_known_types = sizeof(at45d_info) / sizeof(AT45D_INFO); #endif /*! * \brief Send DataFlash command. * * \param node Specifies the SPI node. * \param op Command operation code. * \param parm Optional command parameter. * \param oplen Command length. * \param txbuf Pointer to the transmit data buffer, may be set to NULL. * \param rxbuf Pointer to the receive data buffer, may be set to NULL. * \param xlen Number of byte to receive and/or transmit. */ static int At45dCommand(NUTSPINODE * node, uint8_t op, uint32_t parm, int oplen, const void *txbuf, void *rxbuf, int xlen) { int rc = -1; NUTSPIBUS *bus; uint8_t cmd[8]; NUTASSERT(node != NULL); bus = (NUTSPIBUS *) node->node_bus; NUTASSERT(bus != NULL); NUTASSERT(bus->bus_alloc != NULL); NUTASSERT(bus->bus_transfer != NULL); NUTASSERT(bus->bus_release != NULL); NUTASSERT(oplen <= 8); memset(cmd, 0, oplen); cmd[0] = op; if (parm) { cmd[1] = (uint8_t) (parm >> 16); cmd[2] = (uint8_t) (parm >> 8); cmd[3] = (uint8_t) parm; } rc = (*bus->bus_alloc) (node, 1000); if (rc == 0) { rc = (*bus->bus_transfer) (node, cmd, NULL, oplen); if (rc == 0 && xlen) { rc = (*bus->bus_transfer) (node, txbuf, rxbuf, xlen); } (*bus->bus_release) (node); } return rc; } /*! * \brief Query the status of the DataFlash. * * \param node Specifies the SPI node. * * \return 0 on success or -1 in case of an error. */ static uint8_t At45dStatus(NUTSPINODE * node) { int rc; uint8_t cmd[2] = { DFCMD_READ_STATUS, 0xFF }; NUTSPIBUS *bus; NUTASSERT(node != NULL); NUTASSERT(node->node_bus != NULL); bus = (NUTSPIBUS *) node->node_bus; NUTASSERT(bus->bus_alloc != NULL); NUTASSERT(bus->bus_transfer != NULL); NUTASSERT(bus->bus_wait != NULL); NUTASSERT(bus->bus_release != NULL); rc = (*bus->bus_alloc) (node, 1000); if (rc == 0) { rc = (*bus->bus_transfer) (node, cmd, cmd, 2); if (rc == 0) { (*bus->bus_wait) (node, NUT_WAIT_INFINITE); rc = cmd[1]; } (*bus->bus_release) (node); } return (uint8_t) rc; } /*! * \brief Wait until DataFlash memory cycle finished. * * \param node Specifies the SPI node. * * \return 0 on success or -1 in case of an error. */ static int At45dWaitReady(NUTSPINODE * node, uint32_t tmo, int poll) { uint8_t sr; while (((sr = At45dStatus(node)) & 0x80) == 0) { if (!poll) { NutSleep(1); } if (tmo-- == 0) { return -1; } } return 0; } /*! * \brief Send DataFlash command. * * \param dev Specifies the DataFlash device. * \param op Command operation code. * \param parm Optional command parameter. * \param oplen Command length. * \param txbuf Pointer to the transmit data buffer, may be set to NULL. * \param rxbuf Pointer to the receive data buffer, may be set to NULL. * \param xlen Number of byte to receive and/or transmit. */ int SpiAt45dCommand(NUTDEVICE * dev, uint8_t op, uint32_t parm, int oplen, const void *txbuf, void *rxbuf, int xlen) { NUTASSERT(dev != NULL); return At45dCommand(dev->dev_icb, op, parm, oplen, txbuf, rxbuf, xlen); } /*! * \brief Query the status of the DataFlash. * * \param dev Specifies the Flash device. * * \return 0 on success or -1 in case of an error. */ uint8_t SpiAt45dStatus(NUTDEVICE * dev) { NUTASSERT(dev != NULL); return At45dStatus(dev->dev_icb); } /*! * \brief Wait until DataFlash memory cycle finished. * * \param dev Specifies the DataFlash device. * * \return 0 on success or -1 in case of an error. */ int SpiAt45dWaitReady(NUTDEVICE * dev, uint32_t tmo, int poll) { NUTASSERT(dev != NULL); return At45dWaitReady(dev->dev_icb, tmo, poll); } /*! * \brief Erase specified DataFlash page. * * \param dev Specifies the registered DataFlash device. * \param pgn Page number to erase, starting at 0. * * \return 0 on success or -1 in case of an error. */ int SpiAt45dPageErase(NUTDEVICE * dev, uint32_t pgn) { NUTBLOCKIO *blkio; AT45D_INFO *info; NUTASSERT(dev != NULL); NUTASSERT(dev->dev_dcb != NULL); blkio = dev->dev_dcb; info = (AT45D_INFO *) blkio->blkio_info; NUTASSERT(blkio->blkio_info != NULL); if (pgn >= info->at45d_pages) { return -1; } pgn <<= info->at45d_pshft; return At45dCommand((NUTSPINODE *) dev->dev_icb, DFCMD_PAGE_ERASE, pgn, 4, NULL, NULL, 0); } /*! * \brief Erase all DataFlash pages. */ int SpiAt45dChipErase(NUTDEVICE * dev) { return -1; } /*! * \brief Initialize the DataFlash device. * * This routine determines the DataFlash type. It is internally called * by Nut/OS during device registration. * * The driver framework may call this function more than once. * * \param dev Specifies the DataFlash device. * * \return 0 on success or -1 if no valid DataFlash was found. */ int SpiAt45dInit(NUTDEVICE * dev) { NUTBLOCKIO *blkio; NUTSPINODE *node; uint8_t sr; int_fast8_t i; NUTASSERT(dev != NULL); NUTASSERT(dev->dev_dcb != NULL); NUTASSERT(dev->dev_icb != NULL); blkio = dev->dev_dcb; node = dev->dev_icb; if (At45dWaitReady(node, 10, 1) == 0) { /* Read the status byte and locate the related table entry. */ sr = At45dStatus(node); if (sr != 0xff) { sr &= AT45D_STATUS_DENSITY | AT45D_STATUS_PAGE_SIZE; for (i = at45d_known_types; --i >= 0;) { if (sr == at45d_info[i].at45d_srval) { /* Known DataFlash type. */ blkio->blkio_info = &at45d_info[i]; blkio->blkio_blk_cnt = at45d_info[i].at45d_pages; blkio->blkio_blk_siz = at45d_info[i].at45d_psize; return 0; } } } } /* Unknown DataFlash type. */ return -1; } /*! * \brief Read data from DataFlash memory. * * \param dev Specifies the registered DataFlash device. * \param pgn Page number to read, starting at 0. * \param data Points to a buffer that receives the data. * \param len Number of bytes to read. * * \return The number of bytes actually read. A return value of -1 indicates * an error. */ int SpiAt45dPageRead(NUTDEVICE * dev, uint32_t pgn, void *data, int len) { NUTBLOCKIO *blkio; AT45D_INFO *info; NUTASSERT(dev != NULL); NUTASSERT(dev->dev_dcb != NULL); blkio = dev->dev_dcb; info = (AT45D_INFO *) blkio->blkio_info; NUTASSERT(blkio->blkio_info != NULL); if (pgn >= info->at45d_pages) { return -1; } pgn <<= info->at45d_pshft; if (At45dCommand((NUTSPINODE *) dev->dev_icb, DFCMD_CONT_READ, pgn, 8, NULL, data, len)) { return -1; } return len; } /*! * \brief Write data to DataFlash memory. * * Each page will be automatically erased before writing the data. If the * last page is not completely filled with new data, the contents of * remaining bytes at the end of the page is undetermined. * * \param dev Specifies the registered DataFlash device. * \param pgn The page number. * \param data Points to the buffer that contains the bytes to be written. * \param len Number of bytes available in the buffer. This may be less * than the page size, in which case the remaining bytes of * the page will be set to 0xff. * * \return The number of bytes actually written. A return value of -1 indicates * an error. */ int SpiAt45dPageWrite(NUTDEVICE * dev, uint32_t pgn, const void *data, int len) { int rc = -1; uint8_t *dp = (uint8_t *) data; int step; uint_fast8_t pshft; uint32_t limit; NUTBLOCKIO *blkio; NUTSPINODE *node; /* Sanity check. */ if (len == 0) { return 0; } NUTASSERT(data != NULL); NUTASSERT(dev != NULL); NUTASSERT(dev->dev_dcb != NULL); NUTASSERT(dev->dev_icb != NULL); blkio = (NUTBLOCKIO *) dev->dev_dcb; node = (NUTSPINODE *) dev->dev_icb; NUTASSERT(blkio->blkio_info != NULL); pshft = ((AT45D_INFO *) blkio->blkio_info)->at45d_pshft; step = ((AT45D_INFO *) blkio->blkio_info)->at45d_psize; limit = ((AT45D_INFO *) blkio->blkio_info)->at45d_pages; while (len) { if (step > len) { step = len; } /* Copy data to the internal DataFlash RAM buffer. */ if (At45dCommand(node, DFCMD_BUF1_WRITE, 0, 4, dp, NULL, step)) { break; } /* Erase the page and flash the RAM buffer contents. */ if (At45dCommand(node, DFCMD_BUF1_FLASH, pgn << pshft, 4, NULL, NULL, 0)) { break; } if (At45dWaitReady(node, AT45_WRITE_POLLS, 1)) { break; } if (rc < 0) { rc = 0; } rc += step; dp += step; len -= step; if (++pgn >= limit) { break; } } return rc; } #ifdef __HARVARD_ARCH__ int SpiAt45dPageWrite_P(NUTDEVICE * dev, uint32_t pgn, PGM_P data, int len) { return -1; } #endif /*! * \brief Return the number of pages. * * \param dev Specifies the registered DataFlash device. * * \return The total number of pages available in this device. */ uint32_t SpiAt45dPages(NUTDEVICE * dev) { NUTBLOCKIO *blkio; NUTASSERT(dev != NULL); NUTASSERT(dev->dev_dcb != NULL); blkio = dev->dev_dcb; NUTASSERT(blkio->blkio_info != NULL); return ((AT45D_INFO *) blkio->blkio_info)->at45d_pages; } /*! * \brief Return the page size. * * \param dev Specifies the registered DataFlash device. * * \return The number of bytes per page. */ int SpiAt45dPageSize(NUTDEVICE * dev) { NUTBLOCKIO *blkio; NUTASSERT(dev != NULL); NUTASSERT(dev->dev_dcb != NULL); blkio = dev->dev_dcb; NUTASSERT(blkio->blkio_info != NULL); return ((AT45D_INFO *) blkio->blkio_info)->at45d_psize; } /*! * \brief Perform block I/O device control functions. * * This function is called by the ioctl() function of the C runtime * library. Applications should not directly call this function. * * \param dev Identifies the device that receives the control command. * \param req Requested control command. May be set to one of the * following constants: * - \ref NUTBLKDEV_MEDIAAVAIL * - \ref NUTBLKDEV_MEDIACHANGE * * \param conf Points to a buffer that contains any data required for * the given control function or receives data from that * function. * \return 0 on success, -1 otherwise. */ int SpiAt45dIOCtl(NUTDEVICE * dev, int req, void *conf) { int rc = 0; switch (req) { case NUTBLKDEV_MEDIAAVAIL: /* Modification required for DataFlash cards. */ { int *flg; NUTASSERT(conf != NULL); flg = (int *) conf; *flg = 1; } break; case NUTBLKDEV_MEDIACHANGE: /* Modification required for DataFlash cards. */ { int *flg; NUTASSERT(conf != NULL); flg = (int *) conf; *flg = 0; } break; default: rc = -1; break; } return rc; }