/* * Copyright (C) 2006 by egnite Software GmbH. All rights reserved. * Copyright (C) 2008 by egnite 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/at45db.c * \brief Routines for Adesto/AtmelAT45 serial dataflash memory chips. * * \verbatim * * $Log$ * Revision 1.91 2010/12/15 15:20:42 ve2yag * Added MCU_AT91SAM7S family. * * Revision 1.9 2009/02/06 15:53:42 haraldkipp * Corrected a bug with non-negated chip selects. * * Revision 1.8 2009/01/17 11:26:46 haraldkipp * Getting rid of two remaining BSD types in favor of stdint. * Replaced 'u_int' by 'unsinged int' and 'uptr_t' by 'uintptr_t'. * * Revision 1.7 2008/12/15 19:18:49 haraldkipp * Enable DataFlash support for EIR board. * * Revision 1.6 2008/08/11 06:59:41 haraldkipp * BSD types replaced by stdint types (feature request #1282721). * * Revision 1.5 2008/08/06 12:51:09 haraldkipp * Added support for Ethernut 5 (AT91SAM9XE reference design). * * Revision 1.4 2008/02/15 17:10:43 haraldkipp * At45dbPageErase selected the wrong bank. Fixed. Parameter pgn (page number) * of At45dbPageWrite() changed from unsigned int to unsigned long. * New routines At45dbPages() and At45dbPageSize() allow to determine the * chip's layout. * * Revision 1.3 2007/08/17 10:45:21 haraldkipp * Enhanced documentation. * * Revision 1.2 2006/10/08 16:48:09 haraldkipp * Documentation fixed * * Revision 1.1 2006/09/29 12:41:55 haraldkipp * Added support for AT45 serial DataFlash memory chips. Currently limited * to AT91 builds. * * * \endverbatim */ #include #include #include #include #include #include #include #ifndef MAX_AT45_DEVICES #define MAX_AT45_DEVICES 1 #endif #ifndef MAX_AT45_CMDLEN #define MAX_AT45_CMDLEN 8 #endif #ifndef AT45_CONF_DFSPI #define AT45_CONF_DFSPI SPI0_BASE #endif #ifndef AT45_CONF_DFPCS #define AT45_CONF_DFPCS 1 #endif #ifndef AT45_ERASE_WAIT #define AT45_ERASE_WAIT 3000 #endif #ifndef AT45_CHIP_ERASE_WAIT #define AT45_CHIP_ERASE_WAIT 50000 #endif #ifndef AT45_WRITE_POLLS #define AT45_WRITE_POLLS 1000 #endif /*! * \name AT45 DataFlash Commands */ /*@{*/ /*! \brief Continuos read (low frequency). * * Reads a continous stream in low speed mode. Automatically switches * to the the page and wraps to the first page after the last has been * read. Note, that the data buffers are not used for this operation. */ #define DFCMD_CONT_READ_LF 0x03 /*! \brief Continuos read (high frequency). * * Reads a continous stream in high speed mode. Automatically switches * to the the page and wraps to the first page after the last has been * read. Note, that the data buffers are not used for this operation. */ #define DFCMD_CONT_READ_HF 0x0B /*! \brief Block erase. */ #define DFCMD_BLOCK_ERASE 0x50 /*! \brief Sector erase. */ #define DFCMD_SECTOR_ERASE 0x7C /*! \brief Page erase. */ #define DFCMD_PAGE_ERASE 0x81 /*! \brief Main memory page program through buffer 1. */ #define DFCMD_BUF1_PROG 0x82 /*! \brief Buffer 1 flash with page erase. */ #define DFCMD_BUF1_FLASH 0x83 /*! \brief Buffer 1 write. */ #define DFCMD_BUF1_WRITE 0x84 /*! \brief Main memory page program through buffer 2. */ #define DFCMD_BUF2_PROG 0x85 /*! \brief Buffer 2 flash with page erase. */ #define DFCMD_BUF2_FLASH 0x86 /*! \brief Buffer 2 write. */ #define DFCMD_BUF2_WRITE 0x87 /*! \brief Buffer 1 flash without page erase. */ #define DFCMD_BUF1_FLASH_NE 0x88 /*! \brief Buffer 2 flash without page erase. */ #define DFCMD_BUF2_FLASH_NE 0x89 /*! \brief Chip erase. */ #define DFCMD_CHIP_ERASE 0xC7 /*! \brief Buffer 1 read (low frequency). */ #define DFCMD_BUF1_READ_LF 0xD1 /*! \brief Read main memory page. * * Automatically wraps to the first byte of the same page after * the last byte had been read. The data buffers are left unchanged. */ #define DFCMD_READ_PAGE 0xD2 /*! \brief Buffer 2 read (low frequency). */ #define DFCMD_BUF2_READ_LF 0xD3 /*! \brief Buffer 1 read. */ #define DFCMD_BUF1_READ 0xD4 /*! \brief Buffer 2 read. */ #define DFCMD_BUF2_READ 0xD6 /*! \brief Read status register. */ #define DFCMD_READ_STATUS 0xD7 /*! \brief Continuos read (legacy). * * Reads a continous stream. Automatically switches to the the page * and wraps to the first page after the last has been read. Note, * that the data buffers are not used for this operation. */ #define DFCMD_CONT_READ 0xE8 /*@}*/ #define AT45DB_AT91 #if defined(AT45DB_SPI0_DEVICE) #include #if defined(AT45DB_RESET_ACTIVE_HIGH) #define SpiReset(act) Sppi0ChipReset(AT45DB_SPI0_DEVICE, act) #else #define SpiReset(act) Sppi0ChipReset(AT45DB_SPI0_DEVICE, !act) #endif #define SpiSetMode() Sppi0SetMode(AT45DB_SPI0_DEVICE, AT45DB_SPI_MODE) #define SpiSetSpeed() Sppi0SetSpeed(AT45DB_SPI0_DEVICE, AT45DB_SPI_RATE) #if defined(AT45DB_SELECT_ACTIVE_HIGH) #define SpiSelect() Sppi0SelectDevice(AT45DB_SPI0_DEVICE) #define SpiDeselect() Sppi0DeselectDevice(AT45DB_SPI0_DEVICE) #else #define SpiSelect() Sppi0NegSelectDevice(AT45DB_SPI0_DEVICE) #define SpiDeselect() Sppi0NegDeselectDevice(AT45DB_SPI0_DEVICE) #endif #define SpiByte Sppi0Byte #elif defined(AT45DB_SBBI0_DEVICE) #include #if defined(AT45DB_RESET_ACTIVE_HIGH) #define SpiReset(act) Sbbi0ChipReset(AT45DB_SBBI0_DEVICE, act) #else #define SpiReset(act) Sbbi0ChipReset(AT45DB_SBBI0_DEVICE, !act) #endif #define SpiSetMode() Sbbi0SetMode(AT45DB_SBBI0_DEVICE, AT45DB_SPI_MODE) #define SpiSetSpeed() Sbbi0SetSpeed(AT45DB_SBBI0_DEVICE, AT45DB_SPI_RATE) #if defined(AT45DB_SELECT_ACTIVE_HIGH) #define SpiSelect() Sbbi0SelectDevice(AT45DB_SBBI0_DEVICE) #define SpiDeselect() Sbbi0DeselectDevice(AT45DB_SBBI0_DEVICE) #else #define SpiSelect() Sbbi0NegSelectDevice(AT45DB_SBBI0_DEVICE) #define SpiDeselect() Sbbi0NegDeselectDevice(AT45DB_SBBI0_DEVICE) #endif #define SpiByte Sbbi0Byte #endif /*! * \brief Known device type entry. */ typedef struct _AT45_DEVTAB { uint32_t devt_pages; unsigned int devt_pagsiz; unsigned int devt_offs; uint8_t devt_srmsk; uint8_t devt_srval; } AT45_DEVTAB; /*! * \brief Active device entry. */ typedef struct _AT45DB_DCB { AT45_DEVTAB *dcb_devt; unsigned int dcb_spibas; unsigned int dcb_spipcs; uint8_t dcb_cmdbuf[MAX_AT45_CMDLEN]; } AT45DB_DCB; /*! * \brief Table of known Dataflash types. */ AT45_DEVTAB at45_devt[] = { {512, 264, 9, 0x3C, 0x0C}, /* AT45DB011B - 128kB */ {1025, 264, 9, 0x3C, 0x14}, /* AT45DB021B - 256kB */ {2048, 264, 9, 0x3C, 0x1C}, /* AT45DB041B - 512kB */ {4096, 264, 9, 0x3C, 0x24}, /* AT45DB081B - 1MB */ {4096, 528, 10, 0x3C, 0x2C}, /* AT45DB0161B - 2MB */ {8192, 528, 10, 0x3C, 0x34}, /* AT45DB0321B - 4MB */ {8192, 1056, 11, 0x38, 0x38}, /* AT45DB0642 - 8MB */ {0, 0, 0, 0, 0} /* End of table */ }; /*! * \brief Table of active devices. */ static AT45DB_DCB dcbtab[MAX_AT45_DEVICES]; /* Number of active chips. */ static uint_least8_t dcbnum; /* Chip used for parameter storage. */ static int dd_param = -1; /*! * \brief Send DataFlash command. * * \param dd Device descriptor. * \param op Command operation code. * \param parm Command parameter. * \param len Command length. * \param tdata Transmit data. * \param rdata Receive data buffer. * \param datalen Data length. */ int At45dbSendCmd(int dd, uint8_t op, uint32_t parm, int len, const void *tdata, void *rdata, int datalen) { uint8_t *cb = dcbtab[dd].dcb_cmdbuf; if (len > MAX_AT45_CMDLEN) { return -1; } memset(cb, 0, len); cb[0] = op; if (parm) { cb[1] = (uint8_t) (parm >> 16); cb[2] = (uint8_t) (parm >> 8); cb[3] = (uint8_t) parm; } return At91SpiTransfer2(dcbtab[dd].dcb_spibas, dcbtab[dd].dcb_spipcs, cb, cb, len, tdata, rdata, datalen); } uint8_t At45dbGetStatus(int dd) { uint8_t buf[2] = { DFCMD_READ_STATUS, 0xFF }; if (At91SpiTransfer2(dcbtab[dd].dcb_spibas, dcbtab[dd].dcb_spipcs, buf, buf, 2, NULL, NULL, 0)) { return (uint8_t) - 1; } return buf[1]; } /*! * \brief Wait until flash memory cycle finished. * * \return 0 on success or -1 in case of an error. */ int At45dbWaitReady(int dd, uint32_t tmo, int poll) { uint8_t sr; while (((sr = At45dbGetStatus(dd)) & 0x80) == 0) { if (!poll) { NutSleep(1); } if (tmo-- == 0) { return -1; } } return 0; } /*! * \brief Initialize dataflash at specified interface and chip select. * * \param spibas Interface base address. For ARM MCUs this may be the * I/O base address of the hardware SPI. * \param spipcs Device chip select. * * \return Device descriptor or -1 in case of an error. */ int At45dbInit(unsigned int spibas, unsigned int spipcs) { int dd = -1; uint8_t sr; uint_fast8_t i; for (i = 0; i < dcbnum; i++) { if (dcbtab[i].dcb_spibas == spibas && dcbtab[i].dcb_spipcs == spipcs) { return i; } } if (dcbnum >= MAX_AT45_DEVICES) { return -1; } #if defined(MCU_AT91SAM7S) || defined(MCU_AT91SAM7X) || defined(MCU_AT91SAM7SE) || defined(MCU_AT91SAM9260) || defined(MCU_AT91SAM9XE) At91SpiInit(spibas); At91SpiReset(spibas); At91SpiInitChipSelects(spibas, _BV(spipcs)); At91SpiSetRate(spibas, spipcs, 1000000); At91SpiSetModeFlags(spibas, spipcs, SPIMF_MASTER | SPIMF_SCKIAHI | SPIMF_CAPRISE); #elif defined(MCU_AT91R40008) #endif dcbtab[dcbnum].dcb_spibas = spibas; dcbtab[dcbnum].dcb_spipcs = spipcs; sr = At45dbGetStatus(dcbnum); for (i = 0; at45_devt[i].devt_pages; i++) { if ((sr & at45_devt[i].devt_srmsk) == at45_devt[i].devt_srval) { dcbtab[dcbnum].dcb_devt = &at45_devt[i]; dd = dcbnum++; break; } } return dd; } /*! * \brief Erase sector at the specified offset. */ int At45dbPageErase(int dd, uint32_t pgn) { pgn <<= dcbtab[dd].dcb_devt->devt_offs; return At45dbSendCmd(dd, DFCMD_PAGE_ERASE, pgn, 4, NULL, NULL, 0); } /*! * \brief Erase entire flash memory chip. */ int At45dbChipErase(void) { return -1; } /*! * \brief Read data from flash memory. * * \param dd Device descriptor. * \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 0 on success or -1 in case of an error. */ int At45dbPageRead(int dd, uint32_t pgn, void *data, unsigned int len) { pgn <<= dcbtab[dd].dcb_devt->devt_offs; return At45dbSendCmd(dd, DFCMD_CONT_READ, pgn, 8, data, data, len); } /*! * \brief Write data into flash memory. * * The related sector must have been erased before calling this function. * * \param dd Device descriptor. * \param pgn Start location within the chip, starting at 0. * \param data Points to a buffer that contains the bytes to be written. * \param len Number of bytes to write. * * \return 0 on success or -1 in case of an error. */ int At45dbPageWrite(int dd, uint32_t pgn, const void *data, unsigned int len) { int rc = -1; void *rp; if ((rp = malloc(len)) != NULL) { /* Copy data to dataflash RAM buffer. */ if (At45dbSendCmd(dd, DFCMD_BUF1_WRITE, 0, 4, data, rp, len) == 0) { /* Flash RAM buffer. */ pgn <<= dcbtab[dd].dcb_devt->devt_offs; if (At45dbSendCmd(dd, DFCMD_BUF1_FLASH, pgn, 4, NULL, NULL, 0) == 0) { rc = At45dbWaitReady(dd, AT45_WRITE_POLLS, 1); } } free(rp); } return rc; } uint32_t At45dbPages(int dd) { return dcbtab[dd].dcb_devt->devt_pages; } unsigned int At45dbPageSize(int dd) { return dcbtab[dd].dcb_devt->devt_pagsiz; } uint32_t At45dbParamPage(void) { #ifdef AT45_CONF_PAGE return AT45_CONF_PAGE; #else return dcbtab[dd_param].dcb_devt->devt_pages - 1; #endif } /*! * \brief Get size of configuration area. * * A part of the DataFlash may be used to store configuration parameters, * like the network interface MAC address, the local hostname etc. The * size of this area may be configured by defining AT45_CONF_SIZE. * Otherwise one full page is used. * * \return The number of bytes available for configuration data. In case of * an error, -1 is returned. */ int At45dbParamSize(void) { int rc; if (dd_param == -1 && (dd_param = At45dbInit(AT45_CONF_DFSPI, AT45_CONF_DFPCS)) == -1) { return -1; } #ifdef AT45_CONF_SIZE rc = AT45_CONF_SIZE; #else rc = dcbtab[dd_param].dcb_devt->devt_pagsiz; #endif return rc; } /*! * \brief Load configuration parameters from flash memory. * * \param pos Start location within configuration sector. * \param data Points to a buffer that receives the contents. * \param len Number of bytes to read. * * \return Always 0. */ int At45dbParamRead(unsigned int pos, void *data, unsigned int len) { int rc = -1; uint8_t *buff; int csize = At45dbParamSize(); uint32_t cpage = At45dbParamPage(); /* Load the complete configuration area. */ if (csize > len && (buff = malloc(csize)) != NULL) { rc = At45dbPageRead(dd_param, cpage, buff, csize); /* Copy requested contents to caller's buffer. */ memcpy(data, buff + pos, len); free(buff); } return rc; } /*! * \brief Store configuration parameters in flash memory. * * \param pos Start location within configuration sector. * \param data Points to a buffer that contains the bytes to store. * \param len Number of bytes to store. * * \return 0 on success or -1 in case of an error. */ int At45dbParamWrite(unsigned int pos, const void *data, unsigned int len) { int rc = -1; uint8_t *buff; int csize = At45dbParamSize(); uint32_t cpage = At45dbParamPage(); /* Load the complete configuration area. */ if (csize > len && (buff = malloc(csize)) != NULL) { rc = At45dbPageRead(dd_param, cpage, buff, csize); /* Compare old with new contents. */ if (memcmp(buff + pos, data, len)) { /* New contents differs. Copy it into the sector buffer. */ memcpy(buff + pos, data, len); /* Erase sector and write new data. */ rc = At45dbPageWrite(dd_param, cpage, buff, csize); } free(buff); } return rc; }