/* ChibiOS/HAL - Copyright (C) 2014 Uladzimir Pylinsky aka barthess This file is part of ChibiOS/HAL ChibiOS/HAL is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. ChibiOS/RT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /* Concepts and parts of this file have been contributed by Uladzimir Pylinsky aka barthess. */ /** * @file nand.c * @brief NAND Driver code. * * @addtogroup NAND * @{ */ #include "hal.h" #if HAL_USE_NAND || defined(__DOXYGEN__) #include "string.h" /* for memset */ /*===========================================================================*/ /* Driver local definitions. */ /*===========================================================================*/ /*===========================================================================*/ /* Driver exported variables. */ /*===========================================================================*/ /*===========================================================================*/ /* Driver local types. */ /*===========================================================================*/ /*===========================================================================*/ /* Driver local variables. */ /*===========================================================================*/ /*===========================================================================*/ /* Driver local functions. */ /*===========================================================================*/ /** * @brief Check page size. * * @param[in] page_data_size size of page data area * * @notapi */ static void pagesize_check(size_t page_data_size){ /* Page size out of bounds.*/ osalDbgCheck((page_data_size >= NAND_MIN_PAGE_SIZE) && (page_data_size <= NAND_MAX_PAGE_SIZE)); /* Page size must be power of 2.*/ osalDbgCheck(((page_data_size - 1) & page_data_size) == 0); } /** * @brief Translate block-page-offset scheme to NAND internal address. * * @param[in] cfg pointer to the @p NANDConfig from * corresponding NAND driver * @param[in] block block number * @param[in] page page number related to begin of block * @param[in] offset data offset related to begin of page * @param[out] addr buffer to store calculated address * @param[in] addr_len length of address buffer * * @notapi */ static void calc_addr(const NANDConfig *cfg, uint32_t block, uint32_t page, uint32_t offset, uint8_t *addr, size_t addr_len){ size_t i = 0; uint32_t row = 0; /* Incorrect buffer length.*/ osalDbgCheck(cfg->rowcycles + cfg->colcycles == addr_len); osalDbgCheck((block < cfg->blocks) && (page < cfg->pages_per_block) && (offset < cfg->page_data_size + cfg->page_spare_size)); /* convert address to NAND specific */ memset(addr, 0, addr_len); row = (block * cfg->pages_per_block) + page; for (i=0; icolcycles; i++){ addr[i] = offset & 0xFF; offset = offset >> 8; } for (; i> 8; } } /** * @brief Translate block number to NAND internal address. * @note This function designed for erasing purpose. * * @param[in] cfg pointer to the @p NANDConfig from * corresponding NAND driver * @param[in] block block number * @param[out] addr buffer to store calculated address * @param[in] addr_len length of address buffer * * @notapi */ static void calc_blk_addr(const NANDConfig *cfg, uint32_t block, uint8_t *addr, size_t addr_len){ size_t i = 0; uint32_t row = 0; /* Incorrect buffer length.*/ osalDbgCheck(cfg->rowcycles == addr_len); osalDbgCheck((block < cfg->blocks)); /* convert address to NAND specific */ memset(addr, 0, addr_len); row = block * cfg->pages_per_block; for (i=0; i> 8; } } #if NAND_USE_BAD_MAP /** * @brief Add new bad block to map. * * @param[in] nandp pointer to the @p NANDDriver object * @param[in] block block number * @param[in] map pointer to bad block map */ static void bad_map_update(NANDDriver *nandp, size_t block) { uint32_t *map = nandp->config->bb_map; const size_t BPMC = sizeof(uint32_t) * 8; /* bits per map claster */ size_t i; size_t shift; /* Nand device overflow.*/ osalDbgCheck(nandp->config->blocks > block); i = block / BPMC; shift = block % BPMC; /* This block already mapped.*/ osalDbgCheck(((map[i] >> shift) & 1) != 1); map[i] |= (uint32_t)1 << shift; } /** * @brief Scan for bad blocks and fill map with their numbers. * * @param[in] nandp pointer to the @p NANDDriver object */ static void scan_bad_blocks(NANDDriver *nandp) { const size_t blocks = nandp->config->blocks; const size_t maplen = blocks / 32; size_t b; uint8_t m0; uint8_t m1; /* clear map just to be safe */ for (b=0; bconfig->bb_map[b] = 0; /* now write numbers of bad block to map */ for (b=0; bmutex); #else chSemObjectInit(&nandp->semaphore, 1); #endif /* CH_CFG_USE_MUTEXES */ #endif /* NAND_USE_MUTUAL_EXCLUSION */ nandp->state = NAND_STOP; nandp->config = NULL; } /** * @brief Configures and activates the NAND peripheral. * * @param[in] nandp pointer to the @p NANDDriver object * @param[in] config pointer to the @p NANDConfig object * * @api */ void nandStart(NANDDriver *nandp, const NANDConfig *config) { osalDbgCheck((nandp != NULL) && (config != NULL)); osalDbgAssert((nandp->state == NAND_STOP) || (nandp->state == NAND_READY), "invalid state"); nandp->config = config; pagesize_check(nandp->config->page_data_size); nand_lld_start(nandp); nandp->state = NAND_READY; #if NAND_USE_BAD_MAP scan_bad_blocks(nandp); #endif /* NAND_USE_BAD_MAP */ } /** * @brief Deactivates the NAND peripheral. * * @param[in] nandp pointer to the @p NANDDriver object * * @api */ void nandStop(NANDDriver *nandp) { osalDbgCheck(nandp != NULL); osalDbgAssert((nandp->state == NAND_STOP) || (nandp->state == NAND_READY), "invalid state"); nand_lld_stop(nandp); nandp->state = NAND_STOP; } /** * @brief Read whole page. * * @param[in] nandp pointer to the @p NANDDriver object * @param[in] block block number * @param[in] page page number related to begin of block * @param[out] data buffer to store data * @param[in] datalen length of data buffer * * @api */ void nandReadPageWhole(NANDDriver *nandp, uint32_t block, uint32_t page, uint8_t *data, size_t datalen) { const NANDConfig *cfg = nandp->config; uint8_t addrbuf[8]; size_t addrlen = cfg->rowcycles + cfg->colcycles; osalDbgCheck((nandp != NULL) && (data != NULL)); osalDbgCheck((datalen <= (cfg->page_data_size + cfg->page_spare_size))); osalDbgAssert(nandp->state == NAND_READY, "invalid state"); calc_addr(cfg, block, page, 0, addrbuf, addrlen); nand_lld_read_data(nandp, data, datalen, addrbuf, addrlen, NULL); } /** * @brief Write whole page. * * @param[in] nandp pointer to the @p NANDDriver object * @param[in] block block number * @param[in] page page number related to begin of block * @param[in] data buffer with data to be written * @param[in] datalen length of data buffer * * @return The operation status reported by NAND IC (0x70 command). * * @api */ uint8_t nandWritePageWhole(NANDDriver *nandp, uint32_t block, uint32_t page, const uint8_t *data, size_t datalen) { uint8_t retval; const NANDConfig *cfg = nandp->config; uint8_t addr[8]; size_t addrlen = cfg->rowcycles + cfg->colcycles; osalDbgCheck((nandp != NULL) && (data != NULL)); osalDbgCheck((datalen <= (cfg->page_data_size + cfg->page_spare_size))); osalDbgAssert(nandp->state == NAND_READY, "invalid state"); calc_addr(cfg, block, page, 0, addr, addrlen); retval = nand_lld_write_data(nandp, data, datalen, addr, addrlen, NULL); return retval; } /** * @brief Read page data without spare area. * * @param[in] nandp pointer to the @p NANDDriver object * @param[in] block block number * @param[in] page page number related to begin of block * @param[out] data buffer to store data * @param[in] datalen length of data buffer * @param[out] ecc pointer to calculated ECC. Ignored when NULL. * * @api */ void nandReadPageData(NANDDriver *nandp, uint32_t block, uint32_t page, uint8_t *data, size_t datalen, uint32_t *ecc) { const NANDConfig *cfg = nandp->config; uint8_t addrbuf[8]; size_t addrlen = cfg->rowcycles + cfg->colcycles; osalDbgCheck((nandp != NULL) && (data != NULL)); osalDbgCheck((datalen <= cfg->page_data_size)); osalDbgAssert(nandp->state == NAND_READY, "invalid state"); calc_addr(cfg, block, page, 0, addrbuf, addrlen); nand_lld_read_data(nandp, data, datalen, addrbuf, addrlen, ecc); } /** * @brief Write page data without spare area. * * @param[in] nandp pointer to the @p NANDDriver object * @param[in] block block number * @param[in] page page number related to begin of block * @param[in] data buffer with data to be written * @param[in] datalen length of data buffer * @param[out] ecc pointer to calculated ECC. Ignored when NULL. * * @return The operation status reported by NAND IC (0x70 command). * * @api */ uint8_t nandWritePageData(NANDDriver *nandp, uint32_t block, uint32_t page, const uint8_t *data, size_t datalen, uint32_t *ecc) { uint8_t retval; const NANDConfig *cfg = nandp->config; uint8_t addr[8]; size_t addrlen = cfg->rowcycles + cfg->colcycles; osalDbgCheck((nandp != NULL) && (data != NULL)); osalDbgCheck((datalen <= cfg->page_data_size)); osalDbgAssert(nandp->state == NAND_READY, "invalid state"); calc_addr(cfg, block, page, 0, addr, addrlen); retval = nand_lld_write_data(nandp, data, datalen, addr, addrlen, ecc); return retval; } /** * @brief Read page spare area. * * @param[in] nandp pointer to the @p NANDDriver object * @param[in] block block number * @param[in] page page number related to begin of block * @param[out] spare buffer to store data * @param[in] sparelen length of data buffer * * @api */ void nandReadPageSpare(NANDDriver *nandp, uint32_t block, uint32_t page, uint8_t *spare, size_t sparelen) { const NANDConfig *cfg = nandp->config; uint8_t addr[8]; size_t addrlen = cfg->rowcycles + cfg->colcycles; osalDbgCheck((NULL != spare) && (nandp != NULL)); osalDbgCheck(sparelen <= cfg->page_spare_size); osalDbgAssert(nandp->state == NAND_READY, "invalid state"); calc_addr(cfg, block, page, cfg->page_data_size, addr, addrlen); nand_lld_read_data(nandp, spare, sparelen, addr, addrlen, NULL); } /** * @brief Write page spare area. * * @param[in] nandp pointer to the @p NANDDriver object * @param[in] block block number * @param[in] page page number related to begin of block * @param[in] spare buffer with spare data to be written * @param[in] sparelen length of data buffer * * @return The operation status reported by NAND IC (0x70 command). * * @api */ uint8_t nandWritePageSpare(NANDDriver *nandp, uint32_t block, uint32_t page, const uint8_t *spare, size_t sparelen) { uint8_t retVal; const NANDConfig *cfg = nandp->config; uint8_t addr[8]; size_t addrlen = cfg->rowcycles + cfg->colcycles; osalDbgCheck((NULL != spare) && (nandp != NULL)); osalDbgCheck(sparelen <= cfg->page_spare_size); osalDbgAssert(nandp->state == NAND_READY, "invalid state"); calc_addr(cfg, block, page, cfg->page_data_size, addr, addrlen); retVal = nand_lld_write_data(nandp, spare, sparelen, addr, addrlen, NULL); return retVal; } /** * @brief Mark block as bad. * * @param[in] nandp pointer to the @p NANDDriver object * @param[in] block block number * * @api */ void nandMarkBad(NANDDriver *nandp, uint32_t block) { uint8_t bb_mark[2] = {0, 0}; uint8_t op_status; op_status = nandWritePageSpare(nandp, block, 0, bb_mark, sizeof(bb_mark)); osalDbgCheck(0 == (op_status & 1)); /* operation failed*/ op_status = nandWritePageSpare(nandp, block, 1, bb_mark, sizeof(bb_mark)); osalDbgCheck(0 == (op_status & 1)); /* operation failed*/ #if NAND_USE_BAD_MAP bad_map_update(nandp, block); #endif } /** * @brief Read bad mark out. * * @param[in] nandp pointer to the @p NANDDriver object * @param[in] block block number * @param[in] page page number related to begin of block * * @return Bad mark. * * @api */ uint8_t nandReadBadMark(NANDDriver *nandp, uint32_t block, uint32_t page) { uint8_t bb_mark[1]; nandReadPageSpare(nandp, block, page, bb_mark, sizeof(bb_mark)); return bb_mark[0]; } /** * @brief Erase block. * * @param[in] nandp pointer to the @p NANDDriver object * @param[in] block block number * * @return The operation status reported by NAND IC (0x70 command). * * @api */ uint8_t nandErase(NANDDriver *nandp, uint32_t block){ uint8_t retVal; const NANDConfig *cfg = nandp->config; uint8_t addr[4]; size_t addrlen = cfg->rowcycles; osalDbgCheck(nandp != NULL); osalDbgAssert(nandp->state == NAND_READY, "invalid state"); calc_blk_addr(cfg, block, addr, addrlen); retVal = nand_lld_erase(nandp, addr, addrlen); return retVal; } /** * @brief Report block badness. * * @param[in] nandp pointer to the @p NANDDriver object * @param[in] block block number * * @return block condition * @retval true if the block is bad. * @retval false if the block is good. * * @api */ bool nandIsBad(NANDDriver *nandp, uint32_t block){ osalDbgCheck(nandp != NULL); osalDbgAssert(nandp->state == NAND_READY, "invalid state"); #if NAND_USE_BAD_MAP uint32_t *map = nandp->config->bb_map; const size_t BPMC = sizeof(uint32_t) * 8; /* bits per map claster */ size_t i; size_t shift; i = block / BPMC; shift = block % BPMC; if (((map[i] >> shift) & 1) == 1) return true; else return false; #else uint8_t m0, m1; m0 = nandReadBadMark(nandp, block, 0); m1 = nandReadBadMark(nandp, block, 1); if ((0xFF != m0) || (0xFF != m1)) return true; else return false; #endif /* NAND_USE_BAD_MAP */ } #if NAND_USE_MUTUAL_EXCLUSION || defined(__DOXYGEN__) /** * @brief Gains exclusive access to the NAND bus. * @details This function tries to gain ownership to the NAND bus, if the bus * is already being used then the invoking thread is queued. * @pre In order to use this function the option * @p NAND_USE_MUTUAL_EXCLUSION must be enabled. * * @param[in] nandp pointer to the @p NANDDriver object * * @api */ void nandAcquireBus(NANDDriver *nandp) { osalDbgCheck(nandp != NULL); #if CH_CFG_USE_MUTEXES chMtxLock(&nandp->mutex); #elif CH_CFG_USE_SEMAPHORES chSemWait(&nandp->semaphore); #endif } /** * @brief Releases exclusive access to the NAND bus. * @pre In order to use this function the option * @p NAND_USE_MUTUAL_EXCLUSION must be enabled. * * @param[in] nandp pointer to the @p NANDDriver object * * @api */ void nandReleaseBus(NANDDriver *nandp) { osalDbgCheck(nandp != NULL); #if CH_CFG_USE_MUTEXES chMtxUnlock(&nandp->mutex); #elif CH_CFG_USE_SEMAPHORES chSemSignal(&nandp->semaphore); #endif } #endif /* NAND_USE_MUTUAL_EXCLUSION */ #endif /* HAL_USE_NAND */ /** @} */