From: aleph Date: Tue, 10 May 2011 16:41:52 +0000 (+0000) Subject: mt29f driver becomes nand driver. X-Git-Tag: 2.7.0~78 X-Git-Url: https://codewiz.org/gitweb?a=commitdiff_plain;h=b003c670f7603be23f66b9e13cdb2369ef17a736;p=bertos.git mt29f driver becomes nand driver. git-svn-id: https://src.develer.com/svnoss/bertos/trunk@4888 38d2e660-2303-0410-9eaa-f027e97ec537 --- diff --git a/bertos/cfg/cfg_mt29f.h b/bertos/cfg/cfg_mt29f.h deleted file mode 100644 index 7fb51089..00000000 --- a/bertos/cfg/cfg_mt29f.h +++ /dev/null @@ -1,111 +0,0 @@ -/** - * \file - * - * - * \author Stefano Fedrigo - * - * \brief Configuration file for MT29F driver module. - */ - -#ifndef CFG_MT29F_H -#define CFG_MT29F_H - -/** - * Page data size - * - * Size of the data section of a programmable page in bytes. - * - * $WIZ$ type = "int" - */ -#define CONFIG_MT29F_DATA_SIZE 2048 - -/** - * Page spare area size - * - * Size of the spare section of a programmable page in bytes. - * - * $WIZ$ type = "int" - */ -#define CONFIG_MT29F_SPARE_SIZE 64 - -/** - * Pages per block - * - * Number of pages in a erase block. - * - * $WIZ$ type = "int" - */ -#define CONFIG_MT29F_PAGES_PER_BLOCK 64 - -/** - * Number of blocks - * - * Total number of erase blocks in one NAND chip. - * - * $WIZ$ type = "int" - */ -#define CONFIG_MT29F_NUM_BLOCK 2048 - -/** - * Number of reserved blocks - * - * Blocks reserved for remapping defective NAND blocks. - * - * $WIZ$ type = "int" - */ -#define CONFIG_MT29F_NUM_REMAP_BLOCKS 128 - -/** - * NAND operations timeout - * - * How many milliseconds the cpu waits for - * completion of NAND operations. - * - * $WIZ$ type = "int" - */ -#define CONFIG_MT29F_TMOUT 100 - -/** - * Module logging level - * - * $WIZ$ type = "enum" - * $WIZ$ value_list = "log_level" - */ -#define CONFIG_MT29F_LOG_LEVEL LOG_LVL_INFO - -/** - * Module logging format - * - * $WIZ$ type = "enum" - * $WIZ$ value_list = "log_format" - */ -#define CONFIG_MT29F_LOG_FORMAT LOG_FMT_TERSE - -#endif /* CFG_MT29F_H */ diff --git a/bertos/cfg/cfg_nand.h b/bertos/cfg/cfg_nand.h new file mode 100644 index 00000000..7fb51089 --- /dev/null +++ b/bertos/cfg/cfg_nand.h @@ -0,0 +1,111 @@ +/** + * \file + * + * + * \author Stefano Fedrigo + * + * \brief Configuration file for MT29F driver module. + */ + +#ifndef CFG_MT29F_H +#define CFG_MT29F_H + +/** + * Page data size + * + * Size of the data section of a programmable page in bytes. + * + * $WIZ$ type = "int" + */ +#define CONFIG_MT29F_DATA_SIZE 2048 + +/** + * Page spare area size + * + * Size of the spare section of a programmable page in bytes. + * + * $WIZ$ type = "int" + */ +#define CONFIG_MT29F_SPARE_SIZE 64 + +/** + * Pages per block + * + * Number of pages in a erase block. + * + * $WIZ$ type = "int" + */ +#define CONFIG_MT29F_PAGES_PER_BLOCK 64 + +/** + * Number of blocks + * + * Total number of erase blocks in one NAND chip. + * + * $WIZ$ type = "int" + */ +#define CONFIG_MT29F_NUM_BLOCK 2048 + +/** + * Number of reserved blocks + * + * Blocks reserved for remapping defective NAND blocks. + * + * $WIZ$ type = "int" + */ +#define CONFIG_MT29F_NUM_REMAP_BLOCKS 128 + +/** + * NAND operations timeout + * + * How many milliseconds the cpu waits for + * completion of NAND operations. + * + * $WIZ$ type = "int" + */ +#define CONFIG_MT29F_TMOUT 100 + +/** + * Module logging level + * + * $WIZ$ type = "enum" + * $WIZ$ value_list = "log_level" + */ +#define CONFIG_MT29F_LOG_LEVEL LOG_LVL_INFO + +/** + * Module logging format + * + * $WIZ$ type = "enum" + * $WIZ$ value_list = "log_format" + */ +#define CONFIG_MT29F_LOG_FORMAT LOG_FMT_TERSE + +#endif /* CFG_MT29F_H */ diff --git a/bertos/cpu/cortex-m3/drv/mt29f_sam3.c b/bertos/cpu/cortex-m3/drv/mt29f_sam3.c deleted file mode 100644 index 4d2c1c97..00000000 --- a/bertos/cpu/cortex-m3/drv/mt29f_sam3.c +++ /dev/null @@ -1,267 +0,0 @@ -/** - * \file - * - * - * \brief Micron MT29F serial NAND driver for SAM3's static memory controller. - * - * \author Stefano Fedrigo - */ - -#include "mt29f_sam3.h" -#include -#include -#include -#include -#include -#include /* cpu_relax() */ -#include - -#include /* memcpy, memset */ - - -/* - * PIO definitions. - */ -#define MT29F_PIN_CE BV(6) -#define MT29F_PIN_RB BV(2) -#define MT29F_PINS_PORTA (MT29F_PIN_CE | MT29F_PIN_RB) -#define MT29F_PERIPH_PORTA PIO_PERIPH_B - -#define MT29F_PIN_OE BV(19) -#define MT29F_PIN_WE BV(20) -#define MT29F_PIN_IO 0x0000FFFF -#define MT29F_PINS_PORTC (MT29F_PIN_OE | MT29F_PIN_WE | MT29F_PIN_IO) -#define MT29F_PERIPH_PORTC PIO_PERIPH_A - -#define MT29F_PIN_CLE BV(9) -#define MT29F_PIN_ALE BV(8) -#define MT29F_PINS_PORTD (MT29F_PIN_CLE | MT29F_PIN_ALE) -#define MT29F_PERIPH_PORTD PIO_PERIPH_A - - -/* - * Wait for edge transition of READY/BUSY NAND - * signal. - * Return true for edge detection, false in case of timeout. - */ -bool mt29f_waitReadyBusy(UNUSED_ARG(Mt29f *, chip), time_t timeout) -{ - time_t start = timer_clock(); - - while (!(SMC_SR & SMC_SR_RB_EDGE0)) - { - cpu_relax(); - if (timer_clock() - start > timeout) - { - LOG_INFO("mt29f: R/B timeout\n"); - return false; - } - } - - return true; -} - -/* - * Wait for transfer to complete until timeout. - * If transfer completes return true, false in case of timeout. - */ -bool mt29f_waitTransferComplete(UNUSED_ARG(Mt29f *, chip), time_t timeout) -{ - time_t start = timer_clock(); - - while (!(SMC_SR & SMC_SR_XFRDONE)) - { - cpu_relax(); - if (timer_clock() - start > timeout) - { - LOG_INFO("mt29f: xfer complete timeout\n"); - return false; - } - } - - return true; -} - - -/* - * Send command to NAND and wait for completion. - */ -void mt29f_sendCommand(Mt29f *chip, - uint32_t cmd1, uint32_t cmd2, - int num_cycles, uint32_t cycle0, uint32_t cycle1234) -{ - reg32_t *cmd_addr; - uint32_t cmd_val; - - while (HWREG(NFC_CMD_BASE_ADDR + NFC_CMD_NFCCMD) & 0x8000000); - - if (num_cycles == 5) - SMC_ADDR = cycle0; - - cmd_val = NFC_CMD_NFCCMD - | ((chip->chip_select << NFC_CMD_CSID_SHIFT) & NFC_CMD_CSID_MASK) - | ((num_cycles << NFC_CMD_ACYCLE_SHIFT) & NFC_CMD_ACYCLE_MASK) - | cmd1 << 2 - | cmd2 << 10; - - // Check for commands transferring data - if (cmd1 == MT29F_CMD_WRITE_1 || cmd1 == MT29F_CMD_READ_1 || cmd1 == MT29F_CMD_READID) - cmd_val |= NFC_CMD_NFCEN; - - // Check for commands writing data - if (cmd1 == MT29F_CMD_WRITE_1) - cmd_val |= NFC_CMD_NFCWR; - - // Check for two command cycles - if (cmd2) - cmd_val |= NFC_CMD_VCMD2; - - cmd_addr = (reg32_t *)(NFC_CMD_BASE_ADDR + cmd_val); - *cmd_addr = cycle1234; - - while (!(SMC_SR & SMC_SR_CMDDONE)); -} - - -/* - * Get NAND chip status register. - * - * NOTE: this is global between different chip selects, so returns - * the status register of the last used NAND chip. - */ -uint8_t mt29f_getChipStatus(UNUSED_ARG(Mt29f *, chip)) -{ - return (uint8_t)HWREG(NFC_CMD_BASE_ADDR); -} - - -void *mt29f_dataBuffer(UNUSED_ARG(Mt29f *, chip)) -{ - return (void *)NFC_SRAM_BASE_ADDR; -} - - -bool mt29f_checkEcc(Mt29f *chip) -{ - uint32_t sr1 = SMC_ECC_SR1; - if (sr1) - { - LOG_INFO("ECC error, ECC_SR1=0x%lx\n", sr1); - chip->status |= MT29F_ERR_ECC; - return false; - } - else - return true; -} - - -/* - * Compute ECC on data in a buffer. - * - * \param chip nand context - * \param buf buffer containing data - * \param size size of data buffer - * \param ecc pointer to buffer where computed ECC is stored - * \param ecc_size max size for ecc buffer - */ -void mt29f_computeEcc(UNUSED_ARG(Mt29f *, chip), - UNUSED_ARG(const void *, buf), UNUSED_ARG(size_t, size), uint32_t *ecc, size_t ecc_size) -{ - size_t i; - for (i = 0; i < ecc_size; i++) - ecc[i] = *((reg32_t *)(SMC_BASE + SMC_ECC_PR0_OFF) + i); -} - - -void mt29f_hwInit(UNUSED_ARG(Mt29f *, chip)) -{ - // FIXME: Parameters specific for MT29F8G08AAD - - // PIO init - pmc_periphEnable(PIOA_ID); - pmc_periphEnable(PIOC_ID); - pmc_periphEnable(PIOD_ID); - - PIO_PERIPH_SEL(PIOA_BASE, MT29F_PINS_PORTA, MT29F_PERIPH_PORTA); - PIOA_PDR = MT29F_PINS_PORTA; - PIOA_PUER = MT29F_PINS_PORTA; - - PIO_PERIPH_SEL(PIOC_BASE, MT29F_PINS_PORTC, MT29F_PERIPH_PORTC); - PIOC_PDR = MT29F_PINS_PORTC; - PIOC_PUER = MT29F_PINS_PORTC; - - PIO_PERIPH_SEL(PIOD_BASE, MT29F_PINS_PORTD, MT29F_PERIPH_PORTD); - PIOD_PDR = MT29F_PINS_PORTD; - PIOD_PUER = MT29F_PINS_PORTD; - - pmc_periphEnable(SMC_SDRAMC_ID); - - // SMC init - SMC_SETUP0 = SMC_SETUP_NWE_SETUP(0) - | SMC_SETUP_NCS_WR_SETUP(0) - | SMC_SETUP_NRD_SETUP(0) - | SMC_SETUP_NCS_RD_SETUP(0); - - SMC_PULSE0 = SMC_PULSE_NWE_PULSE(2) - | SMC_PULSE_NCS_WR_PULSE(3) - | SMC_PULSE_NRD_PULSE(2) - | SMC_PULSE_NCS_RD_PULSE(3); - - SMC_CYCLE0 = SMC_CYCLE_NWE_CYCLE(3) - | SMC_CYCLE_NRD_CYCLE(3); - - SMC_TIMINGS0 = SMC_TIMINGS_TCLR(1) - | SMC_TIMINGS_TADL(6) - | SMC_TIMINGS_TAR(4) - | SMC_TIMINGS_TRR(2) - | SMC_TIMINGS_TWB(9) - | SMC_TIMINGS_RBNSEL(7) - | SMC_TIMINGS_NFSEL; - - SMC_MODE0 = SMC_MODE_READ_MODE - | SMC_MODE_WRITE_MODE; - - SMC_CFG = SMC_CFG_PAGESIZE_PS2048_64 - | SMC_CFG_EDGECTRL - | SMC_CFG_DTOMUL_X1048576 - | SMC_CFG_DTOCYC(0xF) - | SMC_CFG_WSPARE - | SMC_CFG_RSPARE; - - // Disable SMC interrupts, reset and enable NFC controller - SMC_IDR = ~0; - SMC_CTRL = 0; - SMC_CTRL = SMC_CTRL_NFCEN; - - // Enable ECC, 1 ECC per 256 bytes - SMC_ECC_CTRL = SMC_ECC_CTRL_SWRST; - SMC_ECC_MD = SMC_ECC_MD_ECC_PAGESIZE_PS2048_64 | SMC_ECC_MD_TYPCORREC_C256B; -} diff --git a/bertos/cpu/cortex-m3/drv/mt29f_sam3.h b/bertos/cpu/cortex-m3/drv/mt29f_sam3.h deleted file mode 100644 index d3c02669..00000000 --- a/bertos/cpu/cortex-m3/drv/mt29f_sam3.h +++ /dev/null @@ -1,43 +0,0 @@ -/** - * \file - * - * - * \brief Micron MT29F serial NAND driver for SAM3's static memory controller (interface). - * - * \author Stefano Fedrigo - */ - -#ifndef MT29F_SAM3_H -#define MT29F_SAM3_H - - -#endif /* MT29F_SAM3_H */ - diff --git a/bertos/cpu/cortex-m3/drv/nand_sam3.c b/bertos/cpu/cortex-m3/drv/nand_sam3.c new file mode 100644 index 00000000..4d2c1c97 --- /dev/null +++ b/bertos/cpu/cortex-m3/drv/nand_sam3.c @@ -0,0 +1,267 @@ +/** + * \file + * + * + * \brief Micron MT29F serial NAND driver for SAM3's static memory controller. + * + * \author Stefano Fedrigo + */ + +#include "mt29f_sam3.h" +#include +#include +#include +#include +#include +#include /* cpu_relax() */ +#include + +#include /* memcpy, memset */ + + +/* + * PIO definitions. + */ +#define MT29F_PIN_CE BV(6) +#define MT29F_PIN_RB BV(2) +#define MT29F_PINS_PORTA (MT29F_PIN_CE | MT29F_PIN_RB) +#define MT29F_PERIPH_PORTA PIO_PERIPH_B + +#define MT29F_PIN_OE BV(19) +#define MT29F_PIN_WE BV(20) +#define MT29F_PIN_IO 0x0000FFFF +#define MT29F_PINS_PORTC (MT29F_PIN_OE | MT29F_PIN_WE | MT29F_PIN_IO) +#define MT29F_PERIPH_PORTC PIO_PERIPH_A + +#define MT29F_PIN_CLE BV(9) +#define MT29F_PIN_ALE BV(8) +#define MT29F_PINS_PORTD (MT29F_PIN_CLE | MT29F_PIN_ALE) +#define MT29F_PERIPH_PORTD PIO_PERIPH_A + + +/* + * Wait for edge transition of READY/BUSY NAND + * signal. + * Return true for edge detection, false in case of timeout. + */ +bool mt29f_waitReadyBusy(UNUSED_ARG(Mt29f *, chip), time_t timeout) +{ + time_t start = timer_clock(); + + while (!(SMC_SR & SMC_SR_RB_EDGE0)) + { + cpu_relax(); + if (timer_clock() - start > timeout) + { + LOG_INFO("mt29f: R/B timeout\n"); + return false; + } + } + + return true; +} + +/* + * Wait for transfer to complete until timeout. + * If transfer completes return true, false in case of timeout. + */ +bool mt29f_waitTransferComplete(UNUSED_ARG(Mt29f *, chip), time_t timeout) +{ + time_t start = timer_clock(); + + while (!(SMC_SR & SMC_SR_XFRDONE)) + { + cpu_relax(); + if (timer_clock() - start > timeout) + { + LOG_INFO("mt29f: xfer complete timeout\n"); + return false; + } + } + + return true; +} + + +/* + * Send command to NAND and wait for completion. + */ +void mt29f_sendCommand(Mt29f *chip, + uint32_t cmd1, uint32_t cmd2, + int num_cycles, uint32_t cycle0, uint32_t cycle1234) +{ + reg32_t *cmd_addr; + uint32_t cmd_val; + + while (HWREG(NFC_CMD_BASE_ADDR + NFC_CMD_NFCCMD) & 0x8000000); + + if (num_cycles == 5) + SMC_ADDR = cycle0; + + cmd_val = NFC_CMD_NFCCMD + | ((chip->chip_select << NFC_CMD_CSID_SHIFT) & NFC_CMD_CSID_MASK) + | ((num_cycles << NFC_CMD_ACYCLE_SHIFT) & NFC_CMD_ACYCLE_MASK) + | cmd1 << 2 + | cmd2 << 10; + + // Check for commands transferring data + if (cmd1 == MT29F_CMD_WRITE_1 || cmd1 == MT29F_CMD_READ_1 || cmd1 == MT29F_CMD_READID) + cmd_val |= NFC_CMD_NFCEN; + + // Check for commands writing data + if (cmd1 == MT29F_CMD_WRITE_1) + cmd_val |= NFC_CMD_NFCWR; + + // Check for two command cycles + if (cmd2) + cmd_val |= NFC_CMD_VCMD2; + + cmd_addr = (reg32_t *)(NFC_CMD_BASE_ADDR + cmd_val); + *cmd_addr = cycle1234; + + while (!(SMC_SR & SMC_SR_CMDDONE)); +} + + +/* + * Get NAND chip status register. + * + * NOTE: this is global between different chip selects, so returns + * the status register of the last used NAND chip. + */ +uint8_t mt29f_getChipStatus(UNUSED_ARG(Mt29f *, chip)) +{ + return (uint8_t)HWREG(NFC_CMD_BASE_ADDR); +} + + +void *mt29f_dataBuffer(UNUSED_ARG(Mt29f *, chip)) +{ + return (void *)NFC_SRAM_BASE_ADDR; +} + + +bool mt29f_checkEcc(Mt29f *chip) +{ + uint32_t sr1 = SMC_ECC_SR1; + if (sr1) + { + LOG_INFO("ECC error, ECC_SR1=0x%lx\n", sr1); + chip->status |= MT29F_ERR_ECC; + return false; + } + else + return true; +} + + +/* + * Compute ECC on data in a buffer. + * + * \param chip nand context + * \param buf buffer containing data + * \param size size of data buffer + * \param ecc pointer to buffer where computed ECC is stored + * \param ecc_size max size for ecc buffer + */ +void mt29f_computeEcc(UNUSED_ARG(Mt29f *, chip), + UNUSED_ARG(const void *, buf), UNUSED_ARG(size_t, size), uint32_t *ecc, size_t ecc_size) +{ + size_t i; + for (i = 0; i < ecc_size; i++) + ecc[i] = *((reg32_t *)(SMC_BASE + SMC_ECC_PR0_OFF) + i); +} + + +void mt29f_hwInit(UNUSED_ARG(Mt29f *, chip)) +{ + // FIXME: Parameters specific for MT29F8G08AAD + + // PIO init + pmc_periphEnable(PIOA_ID); + pmc_periphEnable(PIOC_ID); + pmc_periphEnable(PIOD_ID); + + PIO_PERIPH_SEL(PIOA_BASE, MT29F_PINS_PORTA, MT29F_PERIPH_PORTA); + PIOA_PDR = MT29F_PINS_PORTA; + PIOA_PUER = MT29F_PINS_PORTA; + + PIO_PERIPH_SEL(PIOC_BASE, MT29F_PINS_PORTC, MT29F_PERIPH_PORTC); + PIOC_PDR = MT29F_PINS_PORTC; + PIOC_PUER = MT29F_PINS_PORTC; + + PIO_PERIPH_SEL(PIOD_BASE, MT29F_PINS_PORTD, MT29F_PERIPH_PORTD); + PIOD_PDR = MT29F_PINS_PORTD; + PIOD_PUER = MT29F_PINS_PORTD; + + pmc_periphEnable(SMC_SDRAMC_ID); + + // SMC init + SMC_SETUP0 = SMC_SETUP_NWE_SETUP(0) + | SMC_SETUP_NCS_WR_SETUP(0) + | SMC_SETUP_NRD_SETUP(0) + | SMC_SETUP_NCS_RD_SETUP(0); + + SMC_PULSE0 = SMC_PULSE_NWE_PULSE(2) + | SMC_PULSE_NCS_WR_PULSE(3) + | SMC_PULSE_NRD_PULSE(2) + | SMC_PULSE_NCS_RD_PULSE(3); + + SMC_CYCLE0 = SMC_CYCLE_NWE_CYCLE(3) + | SMC_CYCLE_NRD_CYCLE(3); + + SMC_TIMINGS0 = SMC_TIMINGS_TCLR(1) + | SMC_TIMINGS_TADL(6) + | SMC_TIMINGS_TAR(4) + | SMC_TIMINGS_TRR(2) + | SMC_TIMINGS_TWB(9) + | SMC_TIMINGS_RBNSEL(7) + | SMC_TIMINGS_NFSEL; + + SMC_MODE0 = SMC_MODE_READ_MODE + | SMC_MODE_WRITE_MODE; + + SMC_CFG = SMC_CFG_PAGESIZE_PS2048_64 + | SMC_CFG_EDGECTRL + | SMC_CFG_DTOMUL_X1048576 + | SMC_CFG_DTOCYC(0xF) + | SMC_CFG_WSPARE + | SMC_CFG_RSPARE; + + // Disable SMC interrupts, reset and enable NFC controller + SMC_IDR = ~0; + SMC_CTRL = 0; + SMC_CTRL = SMC_CTRL_NFCEN; + + // Enable ECC, 1 ECC per 256 bytes + SMC_ECC_CTRL = SMC_ECC_CTRL_SWRST; + SMC_ECC_MD = SMC_ECC_MD_ECC_PAGESIZE_PS2048_64 | SMC_ECC_MD_TYPCORREC_C256B; +} diff --git a/bertos/cpu/cortex-m3/drv/nand_sam3.h b/bertos/cpu/cortex-m3/drv/nand_sam3.h new file mode 100644 index 00000000..d3c02669 --- /dev/null +++ b/bertos/cpu/cortex-m3/drv/nand_sam3.h @@ -0,0 +1,43 @@ +/** + * \file + * + * + * \brief Micron MT29F serial NAND driver for SAM3's static memory controller (interface). + * + * \author Stefano Fedrigo + */ + +#ifndef MT29F_SAM3_H +#define MT29F_SAM3_H + + +#endif /* MT29F_SAM3_H */ + diff --git a/bertos/drv/mt29f.c b/bertos/drv/mt29f.c deleted file mode 100644 index e6a10279..00000000 --- a/bertos/drv/mt29f.c +++ /dev/null @@ -1,657 +0,0 @@ -/** -* \file -* -* -* \brief Micron MT29F serial NAND driver -* -* This module allows read/write access to Micron MT29F serial -* NANDs. -* -* \author Stefano Fedrigo -*/ - -#include "mt29f.h" - -#include -#include -#include // memset - - -/* - * Remap info written in the first page of each block - * used to remap bad blocks. - */ -struct RemapInfo -{ - uint32_t tag; // Magic number to detect valid info - uint16_t mapped_blk; // Bad block the block containing this info is remapping -}; - -#define MT29F_REMAP_TAG_OFFSET (CONFIG_MT29F_SPARE_SIZE - sizeof(struct RemapInfo)) -#define MT29F_REMAP_TAG 0x3e10c8ed - -#define MT29F_ECC_NWORDS (CONFIG_MT29F_DATA_SIZE / 256) - -// NAND flash status codes -#define MT29F_STATUS_READY BV(6) -#define MT29F_STATUS_ERROR BV(0) - - -/* - * Translate flash page index plus a byte offset - * in the five address cycles format needed by NAND. - * - * Cycles in x8 mode as the MT29F2G08AAD - * CA = column addr, PA = page addr, BA = block addr - * - * Cycle I/O7 I/O6 I/O5 I/O4 I/O3 I/O2 I/O1 I/O0 - * ------------------------------------------------------- - * First CA7 CA6 CA5 CA4 CA3 CA2 CA1 CA0 - * Second LOW LOW LOW LOW CA11 CA10 CA9 CA8 - * Third BA7 BA6 PA5 PA4 PA3 PA2 PA1 PA0 - * Fourth BA15 BA14 BA13 BA12 BA11 BA10 BA9 BA8 - * Fifth LOW LOW LOW LOW LOW LOW LOW BA16 - */ -static void getAddrCycles(uint32_t page, uint16_t offset, uint32_t *cycle0, uint32_t *cycle1234) -{ - ASSERT(offset < MT29F_PAGE_SIZE); - - *cycle0 = offset & 0xff; - *cycle1234 = (page << 8) | ((offset >> 8) & 0xf); - - //LOG_INFO("mt29f addr: %lx %lx\n", *cycle1234, *cycle0); -} - - -static void chipReset(Mt29f *chip) -{ - mt29f_sendCommand(chip, MT29F_CMD_RESET, 0, 0, 0, 0); - mt29f_waitReadyBusy(chip, CONFIG_MT29F_TMOUT); -} - - -static bool isOperationComplete(Mt29f *chip) -{ - uint8_t status; - - mt29f_sendCommand(chip, MT29F_CMD_STATUS, 0, 0, 0, 0); - - status = mt29f_getChipStatus(chip); - return (status & MT29F_STATUS_READY) && !(status & MT29F_STATUS_ERROR); -} - - -/** - * Erase the whole block. - */ -int mt29f_blockErase(Mt29f *chip, uint16_t block) -{ - uint32_t cycle0; - uint32_t cycle1234; - - uint16_t remapped_block = chip->block_map[block]; - if (block != remapped_block) - { - LOG_INFO("mt29f_blockErase: remapped block: blk %d->%d\n", block, remapped_block); - block = remapped_block; - } - - getAddrCycles(PAGE(block), 0, &cycle0, &cycle1234); - - mt29f_sendCommand(chip, MT29F_CMD_ERASE_1, MT29F_CMD_ERASE_2, 3, 0, cycle1234 >> 8); - - mt29f_waitReadyBusy(chip, CONFIG_MT29F_TMOUT); - - if (!isOperationComplete(chip)) - { - LOG_ERR("mt29f: error erasing block\n"); - chip->status |= MT29F_ERR_ERASE; - return -1; - } - - return 0; -} - - -/** - * Read Device ID and configuration codes. - */ -bool mt29f_getDevId(Mt29f *chip, uint8_t dev_id[5]) -{ - mt29f_sendCommand(chip, MT29F_CMD_READID, 0, 1, 0, 0); - - mt29f_waitReadyBusy(chip, CONFIG_MT29F_TMOUT); - if (!mt29f_waitTransferComplete(chip, CONFIG_MT29F_TMOUT)) - { - LOG_ERR("mt29f: getDevId timeout\n"); - chip->status |= MT29F_ERR_RD_TMOUT; - return false; - } - - memcpy(dev_id, mt29f_dataBuffer(chip), sizeof(dev_id)); - return true; -} - - -static bool mt29f_readPage(Mt29f *chip, uint32_t page, uint16_t offset) -{ - uint32_t cycle0; - uint32_t cycle1234; - - //LOG_INFO("mt29f_readPage: page 0x%lx off 0x%x\n", page, offset); - - getAddrCycles(page, offset, &cycle0, &cycle1234); - - mt29f_sendCommand(chip, MT29F_CMD_READ_1, MT29F_CMD_READ_2, 5, cycle0, cycle1234); - - mt29f_waitReadyBusy(chip, CONFIG_MT29F_TMOUT); - if (!mt29f_waitTransferComplete(chip, CONFIG_MT29F_TMOUT)) - { - LOG_ERR("mt29f: read timeout\n"); - chip->status |= MT29F_ERR_RD_TMOUT; - return false; - } - - return true; -} - - -/* - * Read page data and ECC, checking for errors. - * TODO: fix errors with ECC when possible. - */ -static bool mt29f_read(Mt29f *chip, uint32_t page, void *buf, uint16_t offset, uint16_t size) -{ - struct RemapInfo remap_info; - uint32_t remapped_page = PAGE(chip->block_map[BLOCK(page)]) + PAGE_IN_BLOCK(page); - - //LOG_INFO("mt29f_read: page=%ld, offset=%d, size=%d\n", page, offset, size); - - if (page != remapped_page) - { - LOG_INFO("mt29f_read: remapped block: blk %d->%d, pg %ld->%ld\n", - BLOCK(page), chip->block_map[BLOCK(page)], page, remapped_page); - page = remapped_page; - } - - if (!mt29f_readPage(chip, page, 0)) - return false; - - memcpy(buf, (char *)mt29f_dataBuffer(chip) + offset, size); - - /* - * Check for ECC hardware status only if a valid RemapInfo structure is found. - * That guarantees the page is written by us and a valid ECC is present. - */ - memcpy(&remap_info, (char *)buf + MT29F_REMAP_TAG_OFFSET, sizeof(remap_info)); - if (remap_info.tag == MT29F_REMAP_TAG) - return mt29f_checkEcc(chip); - else - return true; -} - - -/* - * Write data in NFC SRAM buffer to a NAND page, starting at a given offset. - * Usually offset will be 0 to write data or CONFIG_MT29F_DATA_SIZE to write the spare - * area. - * - * According to datasheet to get ECC computed by hardware is sufficient - * to write the main area. But it seems that in that way the last ECC_PR - * register is not generated. The workaround is to write data and dummy (ff) - * spare data in one write, at this point the last ECC_PR is correct and - * ECC data can be written in the spare area with a second program operation. - */ -static bool mt29f_writePage(Mt29f *chip, uint32_t page, uint16_t offset) -{ - uint32_t cycle0; - uint32_t cycle1234; - - //LOG_INFO("mt29f_writePage: page 0x%lx off 0x%x\n", page, offset); - - getAddrCycles(page, offset, &cycle0, &cycle1234); - - mt29f_sendCommand(chip, MT29F_CMD_WRITE_1, 0, 5, cycle0, cycle1234); - - if (!mt29f_waitTransferComplete(chip, CONFIG_MT29F_TMOUT)) - { - LOG_ERR("mt29f: write timeout\n"); - chip->status |= MT29F_ERR_WR_TMOUT; - return false; - } - - mt29f_sendCommand(chip, MT29F_CMD_WRITE_2, 0, 0, 0, 0); - - mt29f_waitReadyBusy(chip, CONFIG_MT29F_TMOUT); - - if (!isOperationComplete(chip)) - { - LOG_ERR("mt29f: error writing page\n"); - chip->status |= MT29F_ERR_WRITE; - return false; - } - - return true; -} - - -/* - * Write data, ECC and remap block info. - * - * \param page the page to be written - * \parma original_page if different from page, it's the page that's being remapped - * - * ECC data are extracted from ECC_PRx registers and written - * in the page's spare area. - * For 2048 bytes pages and 1 ECC word each 256 bytes, - * 24 bytes of ECC data are stored. - */ -static bool mt29f_write(Mt29f *chip, uint32_t page, const void *buf, size_t size) -{ - struct RemapInfo remap_info; - uint32_t *nand_buf = (uint32_t *)mt29f_dataBuffer(chip); - uint32_t remapped_page = PAGE(chip->block_map[BLOCK(page)]) + PAGE_IN_BLOCK(page); - - ASSERT(size <= CONFIG_MT29F_DATA_SIZE); - - if (page != remapped_page) - LOG_INFO("mt29f_write: remapped block: blk %d->%d, pg %ld->%ld\n", - BLOCK(page), chip->block_map[BLOCK(page)], page, remapped_page); - - // Data - memset(nand_buf, 0xff, MT29F_PAGE_SIZE); - memcpy(nand_buf, buf, size); - if (!mt29f_writePage(chip, remapped_page, 0)) - return false; - - // ECC - memset(nand_buf, 0xff, CONFIG_MT29F_SPARE_SIZE); - mt29f_computeEcc(chip, buf, size, nand_buf, MT29F_ECC_NWORDS); - - // Remap info - remap_info.tag = MT29F_REMAP_TAG; - remap_info.mapped_blk = BLOCK(page); - memcpy((char *)nand_buf + MT29F_REMAP_TAG_OFFSET, &remap_info, sizeof(remap_info)); - - return mt29f_writePage(chip, remapped_page, CONFIG_MT29F_DATA_SIZE); -} - - -/* - * Check if the given block is marked bad: ONFI standard mandates - * that bad block are marked with "00" bytes on the spare area of the - * first page in block. - */ -static bool blockIsGood(Mt29f *chip, uint16_t blk) -{ - uint8_t *first_byte = (uint8_t *)mt29f_dataBuffer(chip); - bool good; - - // Check first byte in spare area of first page in block - mt29f_readPage(chip, PAGE(blk), CONFIG_MT29F_DATA_SIZE); - good = *first_byte != 0; - - if (!good) - LOG_INFO("mt29f: bad block %d\n", blk); - - return good; -} - - -/* - * Return the main partition block remapped on given block in the remap - * partition (dest_blk). - */ -static int getBadBlockFromRemapBlock(Mt29f *chip, uint16_t dest_blk) -{ - struct RemapInfo *remap_info = (struct RemapInfo *)mt29f_dataBuffer(chip); - - if (!mt29f_readPage(chip, PAGE(dest_blk), CONFIG_MT29F_DATA_SIZE + MT29F_REMAP_TAG_OFFSET)) - return -1; - - if (remap_info->tag == MT29F_REMAP_TAG) - return remap_info->mapped_blk; - else - return -1; -} - - -/* - * Set a block remapping: src_blk (a block in main data partition) is remappend - * on dest_blk (block in reserved remapped blocks partition). - */ -static bool setMapping(Mt29f *chip, uint32_t src_blk, uint32_t dest_blk) -{ - struct RemapInfo *remap_info = (struct RemapInfo *)mt29f_dataBuffer(chip); - - LOG_INFO("mt29f, setMapping(): src=%ld dst=%ld\n", src_blk, dest_blk); - - if (!mt29f_readPage(chip, PAGE(dest_blk), CONFIG_MT29F_DATA_SIZE + MT29F_REMAP_TAG_OFFSET)) - return false; - - remap_info->tag = MT29F_REMAP_TAG; - remap_info->mapped_blk = src_blk; - - return mt29f_writePage(chip, PAGE(dest_blk), CONFIG_MT29F_DATA_SIZE + MT29F_REMAP_TAG_OFFSET); -} - - -/* - * Get a new block from the remap partition to use as a substitute - * for a bad block. - */ -static uint16_t getFreeRemapBlock(Mt29f *chip) -{ - int blk; - - for (blk = chip->remap_start; blk < CONFIG_MT29F_NUM_BLOCK; blk++) - { - if (blockIsGood(chip, blk)) - { - chip->remap_start = blk + 1; - return blk; - } - } - - LOG_ERR("mt29f: reserved blocks for bad block remapping exhausted!\n"); - return 0; -} - - -/* - * Check if NAND is initialized. - */ -static bool chipIsMarked(Mt29f *chip) -{ - return getBadBlockFromRemapBlock(chip, MT29F_NUM_USER_BLOCKS) != -1; -} - - -/* - * Initialize NAND (format). Scan NAND for factory marked bad blocks. - * All bad blocks found are remapped to the remap partition: each - * block in the remap partition used to remap bad blocks is marked. - */ -static void initBlockMap(Mt29f *chip) -{ - int b, last; - - // Default is for each block to not be remapped - for (b = 0; b < CONFIG_MT29F_NUM_BLOCK; b++) - chip->block_map[b] = b; - chip->remap_start = MT29F_NUM_USER_BLOCKS; - - if (chipIsMarked(chip)) - { - LOG_INFO("mt29f: found initialized NAND, searching for remapped blocks\n"); - - // Scan for assigned blocks in remap area - for (b = last = MT29F_NUM_USER_BLOCKS; b < CONFIG_MT29F_NUM_BLOCK; b++) - { - int remapped_blk = getBadBlockFromRemapBlock(chip, b); - if (remapped_blk != -1 && remapped_blk != b) - { - LOG_INFO("mt29f: found remapped block %d->%d\n", remapped_blk, b); - chip->block_map[remapped_blk] = b; - last = b + 1; - } - } - chip->remap_start = last; - } - else - { - bool remapped_anything = false; - - LOG_INFO("mt29f: found new NAND, searching for bad blocks\n"); - - for (b = 0; b < MT29F_NUM_USER_BLOCKS; b++) - { - if (!blockIsGood(chip, b)) - { - chip->block_map[b] = getFreeRemapBlock(chip); - setMapping(chip, b, chip->block_map[b]); - remapped_anything = true; - LOG_INFO("mt29f: found new bad block %d, remapped to %d\n", b, chip->block_map[b]); - } - } - - /* - * If no bad blocks are found (we're lucky!) write a dummy - * remap to mark NAND and detect we already scanned it next time. - */ - if (!remapped_anything) - { - setMapping(chip, MT29F_NUM_USER_BLOCKS, MT29F_NUM_USER_BLOCKS); - LOG_INFO("mt29f: no bad block founds, marked NAND\n"); - } - } -} - - -/** - * Reset bad blocks map and erase all blocks. - * - * \note DON'T USE on production chips: this function will try to erase - * factory marked bad blocks too. - */ -void mt29f_format(Mt29f *chip) -{ - int b; - - for (b = 0; b < CONFIG_MT29F_NUM_BLOCK; b++) - { - LOG_INFO("mt29f: erasing block %d\n", b); - chip->block_map[b] = b; - mt29f_blockErase(chip, b); - } - chip->remap_start = MT29F_NUM_USER_BLOCKS; -} - -#ifdef _DEBUG - -/* - * Create some bad blocks, erasing them and writing the bad block mark. - */ -void mt29f_ruinSomeBlocks(Mt29f *chip) -{ - int bads[] = { 7, 99, 555, 1003, 1004, 1432 }; - unsigned i; - - LOG_INFO("mt29f: erasing mark\n"); - mt29f_blockErase(chip, MT29F_NUM_USER_BLOCKS); - - for (i = 0; i < countof(bads); i++) - { - LOG_INFO("mt29f: erasing block %d\n", bads[i]); - mt29f_blockErase(chip, bads[i]); - - LOG_INFO("mt29f: marking page %d as bad\n", PAGE(bads[i])); - memset(mt29f_dataBuffer(chip), 0, CONFIG_MT29F_SPARE_SIZE); - mt29f_writePage(chip, PAGE(bads[i]), CONFIG_MT29F_DATA_SIZE); - } -} - -#endif - -static bool commonInit(Mt29f *chip, struct Heap *heap, unsigned chip_select) -{ - memset(chip, 0, sizeof(Mt29f)); - - DB(chip->fd.priv.type = KBT_NAND); - chip->fd.blk_size = MT29F_BLOCK_SIZE; - chip->fd.blk_cnt = MT29F_NUM_USER_BLOCKS; - - chip->chip_select = chip_select; - chip->block_map = heap_allocmem(heap, CONFIG_MT29F_NUM_BLOCK * sizeof(*chip->block_map)); - if (!chip->block_map) - { - LOG_ERR("mt29f: error allocating block map\n"); - return false; - } - - mt29f_hwInit(chip); - chipReset(chip); - initBlockMap(chip); - - return true; -} - - -/**************** Kblock interface ****************/ - - -static size_t mt29f_writeDirect(struct KBlock *kblk, block_idx_t idx, const void *buf, size_t offset, size_t size) -{ - ASSERT(offset <= MT29F_BLOCK_SIZE); - ASSERT(offset % CONFIG_MT29F_DATA_SIZE == 0); - ASSERT(size <= MT29F_BLOCK_SIZE); - ASSERT(size % CONFIG_MT29F_DATA_SIZE == 0); - - //LOG_INFO("mt29f_writeDirect: idx=%ld offset=%d size=%d\n", idx, offset, size); - - mt29f_blockErase(MT29F_CAST(kblk), idx); - - while (offset < size) - { - uint32_t page = PAGE(idx) + (offset / CONFIG_MT29F_DATA_SIZE); - - if (!mt29f_write(MT29F_CAST(kblk), page, buf, CONFIG_MT29F_DATA_SIZE)) - break; - - offset += CONFIG_MT29F_DATA_SIZE; - buf = (const char *)buf + CONFIG_MT29F_DATA_SIZE; - } - - return offset; -} - - -static size_t mt29f_readDirect(struct KBlock *kblk, block_idx_t idx, void *buf, size_t offset, size_t size) -{ - uint32_t page; - size_t read_size; - size_t read_offset; - size_t nread = 0; - - ASSERT(offset < MT29F_BLOCK_SIZE); - ASSERT(size <= MT29F_BLOCK_SIZE); - - //LOG_INFO("mt29f_readDirect: idx=%ld offset=%d size=%d\n", idx, offset, size); - - while (nread < size) - { - page = PAGE(idx) + (offset / CONFIG_MT29F_DATA_SIZE); - read_offset = offset % CONFIG_MT29F_DATA_SIZE; - read_size = MIN(size, CONFIG_MT29F_DATA_SIZE - read_offset); - - if (!mt29f_read(MT29F_CAST(kblk), page, (char *)buf + nread, read_offset, read_size)) - break; - - offset += read_size; - nread += read_size; - } - - return nread; -} - - -static int mt29f_error(struct KBlock *kblk) -{ - Mt29f *chip = MT29F_CAST(kblk); - return chip->status; -} - - -static void mt29f_clearError(struct KBlock *kblk) -{ - Mt29f *chip = MT29F_CAST(kblk); - chip->status = 0; -} - - -static const KBlockVTable mt29f_buffered_vt = -{ - .readDirect = mt29f_readDirect, - .writeDirect = mt29f_writeDirect, - - .readBuf = kblock_swReadBuf, - .writeBuf = kblock_swWriteBuf, - .load = kblock_swLoad, - .store = kblock_swStore, - - .error = mt29f_error, - .clearerr = mt29f_clearError, -}; - -static const KBlockVTable mt29f_unbuffered_vt = -{ - .readDirect = mt29f_readDirect, - .writeDirect = mt29f_writeDirect, - - .error = mt29f_error, - .clearerr = mt29f_clearError, -}; - - -/** - * Initialize NAND kblock driver in buffered mode. - */ -bool mt29f_init(Mt29f *chip, struct Heap *heap, unsigned chip_select) -{ - if (!commonInit(chip, heap, chip_select)) - return false; - - chip->fd.priv.vt = &mt29f_buffered_vt; - chip->fd.priv.flags |= KB_BUFFERED; - - chip->fd.priv.buf = heap_allocmem(heap, MT29F_BLOCK_SIZE); - if (!chip->fd.priv.buf) - { - LOG_ERR("mt29f: error allocating block buffer\n"); - return false; - } - - // Load the first block in the cache - return mt29f_readDirect(&chip->fd, 0, chip->fd.priv.buf, 0, chip->fd.blk_size); -} - - -/** - * Initialize NAND kblock driver in unbuffered mode. - */ -bool mt29f_initUnbuffered(Mt29f *chip, struct Heap *heap, unsigned chip_select) -{ - if (!commonInit(chip, heap, chip_select)) - return false; - - chip->fd.priv.vt = &mt29f_unbuffered_vt; - return true; -} - diff --git a/bertos/drv/mt29f.h b/bertos/drv/mt29f.h deleted file mode 100644 index 5c678ea6..00000000 --- a/bertos/drv/mt29f.h +++ /dev/null @@ -1,152 +0,0 @@ -/** -* \file -* -* -* \brief Micron MT29F serial NAND driver -* -* \author Stefano Fedrigo -* -* $WIZ$ module_name = "mt29f" -* $WIZ$ module_depends = "timer", "kblock", "heap" -* $WIZ$ module_configuration = "bertos/cfg/cfg_mt29f.h" -*/ - -#ifndef DRV_MT29F_H -#define DRV_MT29F_H - -#include "cfg/cfg_mt29f.h" -#include -#include - - -// Define log settings for cfg/log.h -#define LOG_LEVEL CONFIG_MT29F_LOG_LEVEL -#define LOG_FORMAT CONFIG_MT29F_LOG_FORMAT - -/** - * \name Error codes. - * \{ - */ -#define MT29F_ERR_ERASE BV(1) ///< Error erasing a block -#define MT29F_ERR_WRITE BV(2) ///< Error writing a page -#define MT29F_ERR_RD_TMOUT BV(3) ///< Read timeout -#define MT29F_ERR_WR_TMOUT BV(4) ///< Write timeout -#define MT29F_ERR_ECC BV(5) ///< Unrecoverable ECC error -/** \} */ - -#define MT29F_PAGE_SIZE (CONFIG_MT29F_DATA_SIZE + CONFIG_MT29F_SPARE_SIZE) -#define MT29F_BLOCK_SIZE (CONFIG_MT29F_DATA_SIZE * CONFIG_MT29F_PAGES_PER_BLOCK) - -// Number of usable blocks, and index of first remapping block -#define MT29F_NUM_USER_BLOCKS (CONFIG_MT29F_NUM_BLOCK - CONFIG_MT29F_NUM_REMAP_BLOCKS) - - -// NAND commands -#define MT29F_CMD_READ_1 0x00 -#define MT29F_CMD_READ_2 0x30 -#define MT29F_CMD_COPYBACK_READ_1 0x00 -#define MT29F_CMD_COPYBACK_READ_2 0x35 -#define MT29F_CMD_COPYBACK_PROGRAM_1 0x85 -#define MT29F_CMD_COPYBACK_PROGRAM_2 0x10 -#define MT29F_CMD_RANDOM_OUT 0x05 -#define MT29F_CMD_RANDOM_OUT_2 0xE0 -#define MT29F_CMD_RANDOM_IN 0x85 -#define MT29F_CMD_READID 0x90 -#define MT29F_CMD_WRITE_1 0x80 -#define MT29F_CMD_WRITE_2 0x10 -#define MT29F_CMD_ERASE_1 0x60 -#define MT29F_CMD_ERASE_2 0xD0 -#define MT29F_CMD_STATUS 0x70 -#define MT29F_CMD_RESET 0xFF - - -// Get block from page -#define PAGE(blk) ((blk) * CONFIG_MT29F_PAGES_PER_BLOCK) - -// Page from block and page in block -#define BLOCK(page) ((uint16_t)((page) / CONFIG_MT29F_PAGES_PER_BLOCK)) -#define PAGE_IN_BLOCK(page) ((uint16_t)((page) % CONFIG_MT29F_PAGES_PER_BLOCK)) - - -/** - * MT29F context. - */ -typedef struct Mt29f -{ - KBlock fd; // KBlock descriptor - - uint8_t chip_select; // Chip select where NAND is connected - uint8_t status; // Status bitmap - - uint16_t *block_map; // For bad blocks remapping - uint16_t remap_start; // First unused remap block -} Mt29f; - -/* - * Kblock id. - */ -#define KBT_NAND MAKE_ID('N', 'A', 'N', 'D') - -/** -* Convert + ASSERT from generic KBlock to NAND context. -*/ -INLINE Mt29f *MT29F_CAST(KBlock *kb) -{ - ASSERT(kb->priv.type == KBT_NAND); - return (Mt29f *)kb; -} - -struct Heap; - -// Kblock interface -bool mt29f_init(Mt29f *chip, struct Heap *heap, unsigned chip_select); -bool mt29f_initUnbuffered(Mt29f *chip, struct Heap *heap, unsigned chip_select); - -// NAND specific functions -bool mt29f_getDevId(Mt29f *chip, uint8_t dev_id[5]); -int mt29f_blockErase(Mt29f *chip, uint16_t block); -void mt29f_format(Mt29f *chip); - -#ifdef _DEBUG -void mt29f_ruinSomeBlocks(Mt29f *chip); -#endif - -// Hardware specific functions, implemented by cpu specific module -bool mt29f_waitReadyBusy(Mt29f *chip, time_t timeout); -bool mt29f_waitTransferComplete(Mt29f *chip, time_t timeout); -void mt29f_sendCommand(Mt29f *chip, uint32_t cmd1, uint32_t cmd2, - int num_cycles, uint32_t cycle0, uint32_t cycle1234); -uint8_t mt29f_getChipStatus(Mt29f *chip); -void *mt29f_dataBuffer(Mt29f *chip); -bool mt29f_checkEcc(Mt29f *chip); -void mt29f_computeEcc(Mt29f *chip, const void *buf, size_t size, uint32_t *ecc, size_t ecc_size); -void mt29f_hwInit(Mt29f *chip); - -#endif /* DRV_MT29F_H */ diff --git a/bertos/drv/nand.c b/bertos/drv/nand.c new file mode 100644 index 00000000..e6a10279 --- /dev/null +++ b/bertos/drv/nand.c @@ -0,0 +1,657 @@ +/** +* \file +* +* +* \brief Micron MT29F serial NAND driver +* +* This module allows read/write access to Micron MT29F serial +* NANDs. +* +* \author Stefano Fedrigo +*/ + +#include "mt29f.h" + +#include +#include +#include // memset + + +/* + * Remap info written in the first page of each block + * used to remap bad blocks. + */ +struct RemapInfo +{ + uint32_t tag; // Magic number to detect valid info + uint16_t mapped_blk; // Bad block the block containing this info is remapping +}; + +#define MT29F_REMAP_TAG_OFFSET (CONFIG_MT29F_SPARE_SIZE - sizeof(struct RemapInfo)) +#define MT29F_REMAP_TAG 0x3e10c8ed + +#define MT29F_ECC_NWORDS (CONFIG_MT29F_DATA_SIZE / 256) + +// NAND flash status codes +#define MT29F_STATUS_READY BV(6) +#define MT29F_STATUS_ERROR BV(0) + + +/* + * Translate flash page index plus a byte offset + * in the five address cycles format needed by NAND. + * + * Cycles in x8 mode as the MT29F2G08AAD + * CA = column addr, PA = page addr, BA = block addr + * + * Cycle I/O7 I/O6 I/O5 I/O4 I/O3 I/O2 I/O1 I/O0 + * ------------------------------------------------------- + * First CA7 CA6 CA5 CA4 CA3 CA2 CA1 CA0 + * Second LOW LOW LOW LOW CA11 CA10 CA9 CA8 + * Third BA7 BA6 PA5 PA4 PA3 PA2 PA1 PA0 + * Fourth BA15 BA14 BA13 BA12 BA11 BA10 BA9 BA8 + * Fifth LOW LOW LOW LOW LOW LOW LOW BA16 + */ +static void getAddrCycles(uint32_t page, uint16_t offset, uint32_t *cycle0, uint32_t *cycle1234) +{ + ASSERT(offset < MT29F_PAGE_SIZE); + + *cycle0 = offset & 0xff; + *cycle1234 = (page << 8) | ((offset >> 8) & 0xf); + + //LOG_INFO("mt29f addr: %lx %lx\n", *cycle1234, *cycle0); +} + + +static void chipReset(Mt29f *chip) +{ + mt29f_sendCommand(chip, MT29F_CMD_RESET, 0, 0, 0, 0); + mt29f_waitReadyBusy(chip, CONFIG_MT29F_TMOUT); +} + + +static bool isOperationComplete(Mt29f *chip) +{ + uint8_t status; + + mt29f_sendCommand(chip, MT29F_CMD_STATUS, 0, 0, 0, 0); + + status = mt29f_getChipStatus(chip); + return (status & MT29F_STATUS_READY) && !(status & MT29F_STATUS_ERROR); +} + + +/** + * Erase the whole block. + */ +int mt29f_blockErase(Mt29f *chip, uint16_t block) +{ + uint32_t cycle0; + uint32_t cycle1234; + + uint16_t remapped_block = chip->block_map[block]; + if (block != remapped_block) + { + LOG_INFO("mt29f_blockErase: remapped block: blk %d->%d\n", block, remapped_block); + block = remapped_block; + } + + getAddrCycles(PAGE(block), 0, &cycle0, &cycle1234); + + mt29f_sendCommand(chip, MT29F_CMD_ERASE_1, MT29F_CMD_ERASE_2, 3, 0, cycle1234 >> 8); + + mt29f_waitReadyBusy(chip, CONFIG_MT29F_TMOUT); + + if (!isOperationComplete(chip)) + { + LOG_ERR("mt29f: error erasing block\n"); + chip->status |= MT29F_ERR_ERASE; + return -1; + } + + return 0; +} + + +/** + * Read Device ID and configuration codes. + */ +bool mt29f_getDevId(Mt29f *chip, uint8_t dev_id[5]) +{ + mt29f_sendCommand(chip, MT29F_CMD_READID, 0, 1, 0, 0); + + mt29f_waitReadyBusy(chip, CONFIG_MT29F_TMOUT); + if (!mt29f_waitTransferComplete(chip, CONFIG_MT29F_TMOUT)) + { + LOG_ERR("mt29f: getDevId timeout\n"); + chip->status |= MT29F_ERR_RD_TMOUT; + return false; + } + + memcpy(dev_id, mt29f_dataBuffer(chip), sizeof(dev_id)); + return true; +} + + +static bool mt29f_readPage(Mt29f *chip, uint32_t page, uint16_t offset) +{ + uint32_t cycle0; + uint32_t cycle1234; + + //LOG_INFO("mt29f_readPage: page 0x%lx off 0x%x\n", page, offset); + + getAddrCycles(page, offset, &cycle0, &cycle1234); + + mt29f_sendCommand(chip, MT29F_CMD_READ_1, MT29F_CMD_READ_2, 5, cycle0, cycle1234); + + mt29f_waitReadyBusy(chip, CONFIG_MT29F_TMOUT); + if (!mt29f_waitTransferComplete(chip, CONFIG_MT29F_TMOUT)) + { + LOG_ERR("mt29f: read timeout\n"); + chip->status |= MT29F_ERR_RD_TMOUT; + return false; + } + + return true; +} + + +/* + * Read page data and ECC, checking for errors. + * TODO: fix errors with ECC when possible. + */ +static bool mt29f_read(Mt29f *chip, uint32_t page, void *buf, uint16_t offset, uint16_t size) +{ + struct RemapInfo remap_info; + uint32_t remapped_page = PAGE(chip->block_map[BLOCK(page)]) + PAGE_IN_BLOCK(page); + + //LOG_INFO("mt29f_read: page=%ld, offset=%d, size=%d\n", page, offset, size); + + if (page != remapped_page) + { + LOG_INFO("mt29f_read: remapped block: blk %d->%d, pg %ld->%ld\n", + BLOCK(page), chip->block_map[BLOCK(page)], page, remapped_page); + page = remapped_page; + } + + if (!mt29f_readPage(chip, page, 0)) + return false; + + memcpy(buf, (char *)mt29f_dataBuffer(chip) + offset, size); + + /* + * Check for ECC hardware status only if a valid RemapInfo structure is found. + * That guarantees the page is written by us and a valid ECC is present. + */ + memcpy(&remap_info, (char *)buf + MT29F_REMAP_TAG_OFFSET, sizeof(remap_info)); + if (remap_info.tag == MT29F_REMAP_TAG) + return mt29f_checkEcc(chip); + else + return true; +} + + +/* + * Write data in NFC SRAM buffer to a NAND page, starting at a given offset. + * Usually offset will be 0 to write data or CONFIG_MT29F_DATA_SIZE to write the spare + * area. + * + * According to datasheet to get ECC computed by hardware is sufficient + * to write the main area. But it seems that in that way the last ECC_PR + * register is not generated. The workaround is to write data and dummy (ff) + * spare data in one write, at this point the last ECC_PR is correct and + * ECC data can be written in the spare area with a second program operation. + */ +static bool mt29f_writePage(Mt29f *chip, uint32_t page, uint16_t offset) +{ + uint32_t cycle0; + uint32_t cycle1234; + + //LOG_INFO("mt29f_writePage: page 0x%lx off 0x%x\n", page, offset); + + getAddrCycles(page, offset, &cycle0, &cycle1234); + + mt29f_sendCommand(chip, MT29F_CMD_WRITE_1, 0, 5, cycle0, cycle1234); + + if (!mt29f_waitTransferComplete(chip, CONFIG_MT29F_TMOUT)) + { + LOG_ERR("mt29f: write timeout\n"); + chip->status |= MT29F_ERR_WR_TMOUT; + return false; + } + + mt29f_sendCommand(chip, MT29F_CMD_WRITE_2, 0, 0, 0, 0); + + mt29f_waitReadyBusy(chip, CONFIG_MT29F_TMOUT); + + if (!isOperationComplete(chip)) + { + LOG_ERR("mt29f: error writing page\n"); + chip->status |= MT29F_ERR_WRITE; + return false; + } + + return true; +} + + +/* + * Write data, ECC and remap block info. + * + * \param page the page to be written + * \parma original_page if different from page, it's the page that's being remapped + * + * ECC data are extracted from ECC_PRx registers and written + * in the page's spare area. + * For 2048 bytes pages and 1 ECC word each 256 bytes, + * 24 bytes of ECC data are stored. + */ +static bool mt29f_write(Mt29f *chip, uint32_t page, const void *buf, size_t size) +{ + struct RemapInfo remap_info; + uint32_t *nand_buf = (uint32_t *)mt29f_dataBuffer(chip); + uint32_t remapped_page = PAGE(chip->block_map[BLOCK(page)]) + PAGE_IN_BLOCK(page); + + ASSERT(size <= CONFIG_MT29F_DATA_SIZE); + + if (page != remapped_page) + LOG_INFO("mt29f_write: remapped block: blk %d->%d, pg %ld->%ld\n", + BLOCK(page), chip->block_map[BLOCK(page)], page, remapped_page); + + // Data + memset(nand_buf, 0xff, MT29F_PAGE_SIZE); + memcpy(nand_buf, buf, size); + if (!mt29f_writePage(chip, remapped_page, 0)) + return false; + + // ECC + memset(nand_buf, 0xff, CONFIG_MT29F_SPARE_SIZE); + mt29f_computeEcc(chip, buf, size, nand_buf, MT29F_ECC_NWORDS); + + // Remap info + remap_info.tag = MT29F_REMAP_TAG; + remap_info.mapped_blk = BLOCK(page); + memcpy((char *)nand_buf + MT29F_REMAP_TAG_OFFSET, &remap_info, sizeof(remap_info)); + + return mt29f_writePage(chip, remapped_page, CONFIG_MT29F_DATA_SIZE); +} + + +/* + * Check if the given block is marked bad: ONFI standard mandates + * that bad block are marked with "00" bytes on the spare area of the + * first page in block. + */ +static bool blockIsGood(Mt29f *chip, uint16_t blk) +{ + uint8_t *first_byte = (uint8_t *)mt29f_dataBuffer(chip); + bool good; + + // Check first byte in spare area of first page in block + mt29f_readPage(chip, PAGE(blk), CONFIG_MT29F_DATA_SIZE); + good = *first_byte != 0; + + if (!good) + LOG_INFO("mt29f: bad block %d\n", blk); + + return good; +} + + +/* + * Return the main partition block remapped on given block in the remap + * partition (dest_blk). + */ +static int getBadBlockFromRemapBlock(Mt29f *chip, uint16_t dest_blk) +{ + struct RemapInfo *remap_info = (struct RemapInfo *)mt29f_dataBuffer(chip); + + if (!mt29f_readPage(chip, PAGE(dest_blk), CONFIG_MT29F_DATA_SIZE + MT29F_REMAP_TAG_OFFSET)) + return -1; + + if (remap_info->tag == MT29F_REMAP_TAG) + return remap_info->mapped_blk; + else + return -1; +} + + +/* + * Set a block remapping: src_blk (a block in main data partition) is remappend + * on dest_blk (block in reserved remapped blocks partition). + */ +static bool setMapping(Mt29f *chip, uint32_t src_blk, uint32_t dest_blk) +{ + struct RemapInfo *remap_info = (struct RemapInfo *)mt29f_dataBuffer(chip); + + LOG_INFO("mt29f, setMapping(): src=%ld dst=%ld\n", src_blk, dest_blk); + + if (!mt29f_readPage(chip, PAGE(dest_blk), CONFIG_MT29F_DATA_SIZE + MT29F_REMAP_TAG_OFFSET)) + return false; + + remap_info->tag = MT29F_REMAP_TAG; + remap_info->mapped_blk = src_blk; + + return mt29f_writePage(chip, PAGE(dest_blk), CONFIG_MT29F_DATA_SIZE + MT29F_REMAP_TAG_OFFSET); +} + + +/* + * Get a new block from the remap partition to use as a substitute + * for a bad block. + */ +static uint16_t getFreeRemapBlock(Mt29f *chip) +{ + int blk; + + for (blk = chip->remap_start; blk < CONFIG_MT29F_NUM_BLOCK; blk++) + { + if (blockIsGood(chip, blk)) + { + chip->remap_start = blk + 1; + return blk; + } + } + + LOG_ERR("mt29f: reserved blocks for bad block remapping exhausted!\n"); + return 0; +} + + +/* + * Check if NAND is initialized. + */ +static bool chipIsMarked(Mt29f *chip) +{ + return getBadBlockFromRemapBlock(chip, MT29F_NUM_USER_BLOCKS) != -1; +} + + +/* + * Initialize NAND (format). Scan NAND for factory marked bad blocks. + * All bad blocks found are remapped to the remap partition: each + * block in the remap partition used to remap bad blocks is marked. + */ +static void initBlockMap(Mt29f *chip) +{ + int b, last; + + // Default is for each block to not be remapped + for (b = 0; b < CONFIG_MT29F_NUM_BLOCK; b++) + chip->block_map[b] = b; + chip->remap_start = MT29F_NUM_USER_BLOCKS; + + if (chipIsMarked(chip)) + { + LOG_INFO("mt29f: found initialized NAND, searching for remapped blocks\n"); + + // Scan for assigned blocks in remap area + for (b = last = MT29F_NUM_USER_BLOCKS; b < CONFIG_MT29F_NUM_BLOCK; b++) + { + int remapped_blk = getBadBlockFromRemapBlock(chip, b); + if (remapped_blk != -1 && remapped_blk != b) + { + LOG_INFO("mt29f: found remapped block %d->%d\n", remapped_blk, b); + chip->block_map[remapped_blk] = b; + last = b + 1; + } + } + chip->remap_start = last; + } + else + { + bool remapped_anything = false; + + LOG_INFO("mt29f: found new NAND, searching for bad blocks\n"); + + for (b = 0; b < MT29F_NUM_USER_BLOCKS; b++) + { + if (!blockIsGood(chip, b)) + { + chip->block_map[b] = getFreeRemapBlock(chip); + setMapping(chip, b, chip->block_map[b]); + remapped_anything = true; + LOG_INFO("mt29f: found new bad block %d, remapped to %d\n", b, chip->block_map[b]); + } + } + + /* + * If no bad blocks are found (we're lucky!) write a dummy + * remap to mark NAND and detect we already scanned it next time. + */ + if (!remapped_anything) + { + setMapping(chip, MT29F_NUM_USER_BLOCKS, MT29F_NUM_USER_BLOCKS); + LOG_INFO("mt29f: no bad block founds, marked NAND\n"); + } + } +} + + +/** + * Reset bad blocks map and erase all blocks. + * + * \note DON'T USE on production chips: this function will try to erase + * factory marked bad blocks too. + */ +void mt29f_format(Mt29f *chip) +{ + int b; + + for (b = 0; b < CONFIG_MT29F_NUM_BLOCK; b++) + { + LOG_INFO("mt29f: erasing block %d\n", b); + chip->block_map[b] = b; + mt29f_blockErase(chip, b); + } + chip->remap_start = MT29F_NUM_USER_BLOCKS; +} + +#ifdef _DEBUG + +/* + * Create some bad blocks, erasing them and writing the bad block mark. + */ +void mt29f_ruinSomeBlocks(Mt29f *chip) +{ + int bads[] = { 7, 99, 555, 1003, 1004, 1432 }; + unsigned i; + + LOG_INFO("mt29f: erasing mark\n"); + mt29f_blockErase(chip, MT29F_NUM_USER_BLOCKS); + + for (i = 0; i < countof(bads); i++) + { + LOG_INFO("mt29f: erasing block %d\n", bads[i]); + mt29f_blockErase(chip, bads[i]); + + LOG_INFO("mt29f: marking page %d as bad\n", PAGE(bads[i])); + memset(mt29f_dataBuffer(chip), 0, CONFIG_MT29F_SPARE_SIZE); + mt29f_writePage(chip, PAGE(bads[i]), CONFIG_MT29F_DATA_SIZE); + } +} + +#endif + +static bool commonInit(Mt29f *chip, struct Heap *heap, unsigned chip_select) +{ + memset(chip, 0, sizeof(Mt29f)); + + DB(chip->fd.priv.type = KBT_NAND); + chip->fd.blk_size = MT29F_BLOCK_SIZE; + chip->fd.blk_cnt = MT29F_NUM_USER_BLOCKS; + + chip->chip_select = chip_select; + chip->block_map = heap_allocmem(heap, CONFIG_MT29F_NUM_BLOCK * sizeof(*chip->block_map)); + if (!chip->block_map) + { + LOG_ERR("mt29f: error allocating block map\n"); + return false; + } + + mt29f_hwInit(chip); + chipReset(chip); + initBlockMap(chip); + + return true; +} + + +/**************** Kblock interface ****************/ + + +static size_t mt29f_writeDirect(struct KBlock *kblk, block_idx_t idx, const void *buf, size_t offset, size_t size) +{ + ASSERT(offset <= MT29F_BLOCK_SIZE); + ASSERT(offset % CONFIG_MT29F_DATA_SIZE == 0); + ASSERT(size <= MT29F_BLOCK_SIZE); + ASSERT(size % CONFIG_MT29F_DATA_SIZE == 0); + + //LOG_INFO("mt29f_writeDirect: idx=%ld offset=%d size=%d\n", idx, offset, size); + + mt29f_blockErase(MT29F_CAST(kblk), idx); + + while (offset < size) + { + uint32_t page = PAGE(idx) + (offset / CONFIG_MT29F_DATA_SIZE); + + if (!mt29f_write(MT29F_CAST(kblk), page, buf, CONFIG_MT29F_DATA_SIZE)) + break; + + offset += CONFIG_MT29F_DATA_SIZE; + buf = (const char *)buf + CONFIG_MT29F_DATA_SIZE; + } + + return offset; +} + + +static size_t mt29f_readDirect(struct KBlock *kblk, block_idx_t idx, void *buf, size_t offset, size_t size) +{ + uint32_t page; + size_t read_size; + size_t read_offset; + size_t nread = 0; + + ASSERT(offset < MT29F_BLOCK_SIZE); + ASSERT(size <= MT29F_BLOCK_SIZE); + + //LOG_INFO("mt29f_readDirect: idx=%ld offset=%d size=%d\n", idx, offset, size); + + while (nread < size) + { + page = PAGE(idx) + (offset / CONFIG_MT29F_DATA_SIZE); + read_offset = offset % CONFIG_MT29F_DATA_SIZE; + read_size = MIN(size, CONFIG_MT29F_DATA_SIZE - read_offset); + + if (!mt29f_read(MT29F_CAST(kblk), page, (char *)buf + nread, read_offset, read_size)) + break; + + offset += read_size; + nread += read_size; + } + + return nread; +} + + +static int mt29f_error(struct KBlock *kblk) +{ + Mt29f *chip = MT29F_CAST(kblk); + return chip->status; +} + + +static void mt29f_clearError(struct KBlock *kblk) +{ + Mt29f *chip = MT29F_CAST(kblk); + chip->status = 0; +} + + +static const KBlockVTable mt29f_buffered_vt = +{ + .readDirect = mt29f_readDirect, + .writeDirect = mt29f_writeDirect, + + .readBuf = kblock_swReadBuf, + .writeBuf = kblock_swWriteBuf, + .load = kblock_swLoad, + .store = kblock_swStore, + + .error = mt29f_error, + .clearerr = mt29f_clearError, +}; + +static const KBlockVTable mt29f_unbuffered_vt = +{ + .readDirect = mt29f_readDirect, + .writeDirect = mt29f_writeDirect, + + .error = mt29f_error, + .clearerr = mt29f_clearError, +}; + + +/** + * Initialize NAND kblock driver in buffered mode. + */ +bool mt29f_init(Mt29f *chip, struct Heap *heap, unsigned chip_select) +{ + if (!commonInit(chip, heap, chip_select)) + return false; + + chip->fd.priv.vt = &mt29f_buffered_vt; + chip->fd.priv.flags |= KB_BUFFERED; + + chip->fd.priv.buf = heap_allocmem(heap, MT29F_BLOCK_SIZE); + if (!chip->fd.priv.buf) + { + LOG_ERR("mt29f: error allocating block buffer\n"); + return false; + } + + // Load the first block in the cache + return mt29f_readDirect(&chip->fd, 0, chip->fd.priv.buf, 0, chip->fd.blk_size); +} + + +/** + * Initialize NAND kblock driver in unbuffered mode. + */ +bool mt29f_initUnbuffered(Mt29f *chip, struct Heap *heap, unsigned chip_select) +{ + if (!commonInit(chip, heap, chip_select)) + return false; + + chip->fd.priv.vt = &mt29f_unbuffered_vt; + return true; +} + diff --git a/bertos/drv/nand.h b/bertos/drv/nand.h new file mode 100644 index 00000000..5c678ea6 --- /dev/null +++ b/bertos/drv/nand.h @@ -0,0 +1,152 @@ +/** +* \file +* +* +* \brief Micron MT29F serial NAND driver +* +* \author Stefano Fedrigo +* +* $WIZ$ module_name = "mt29f" +* $WIZ$ module_depends = "timer", "kblock", "heap" +* $WIZ$ module_configuration = "bertos/cfg/cfg_mt29f.h" +*/ + +#ifndef DRV_MT29F_H +#define DRV_MT29F_H + +#include "cfg/cfg_mt29f.h" +#include +#include + + +// Define log settings for cfg/log.h +#define LOG_LEVEL CONFIG_MT29F_LOG_LEVEL +#define LOG_FORMAT CONFIG_MT29F_LOG_FORMAT + +/** + * \name Error codes. + * \{ + */ +#define MT29F_ERR_ERASE BV(1) ///< Error erasing a block +#define MT29F_ERR_WRITE BV(2) ///< Error writing a page +#define MT29F_ERR_RD_TMOUT BV(3) ///< Read timeout +#define MT29F_ERR_WR_TMOUT BV(4) ///< Write timeout +#define MT29F_ERR_ECC BV(5) ///< Unrecoverable ECC error +/** \} */ + +#define MT29F_PAGE_SIZE (CONFIG_MT29F_DATA_SIZE + CONFIG_MT29F_SPARE_SIZE) +#define MT29F_BLOCK_SIZE (CONFIG_MT29F_DATA_SIZE * CONFIG_MT29F_PAGES_PER_BLOCK) + +// Number of usable blocks, and index of first remapping block +#define MT29F_NUM_USER_BLOCKS (CONFIG_MT29F_NUM_BLOCK - CONFIG_MT29F_NUM_REMAP_BLOCKS) + + +// NAND commands +#define MT29F_CMD_READ_1 0x00 +#define MT29F_CMD_READ_2 0x30 +#define MT29F_CMD_COPYBACK_READ_1 0x00 +#define MT29F_CMD_COPYBACK_READ_2 0x35 +#define MT29F_CMD_COPYBACK_PROGRAM_1 0x85 +#define MT29F_CMD_COPYBACK_PROGRAM_2 0x10 +#define MT29F_CMD_RANDOM_OUT 0x05 +#define MT29F_CMD_RANDOM_OUT_2 0xE0 +#define MT29F_CMD_RANDOM_IN 0x85 +#define MT29F_CMD_READID 0x90 +#define MT29F_CMD_WRITE_1 0x80 +#define MT29F_CMD_WRITE_2 0x10 +#define MT29F_CMD_ERASE_1 0x60 +#define MT29F_CMD_ERASE_2 0xD0 +#define MT29F_CMD_STATUS 0x70 +#define MT29F_CMD_RESET 0xFF + + +// Get block from page +#define PAGE(blk) ((blk) * CONFIG_MT29F_PAGES_PER_BLOCK) + +// Page from block and page in block +#define BLOCK(page) ((uint16_t)((page) / CONFIG_MT29F_PAGES_PER_BLOCK)) +#define PAGE_IN_BLOCK(page) ((uint16_t)((page) % CONFIG_MT29F_PAGES_PER_BLOCK)) + + +/** + * MT29F context. + */ +typedef struct Mt29f +{ + KBlock fd; // KBlock descriptor + + uint8_t chip_select; // Chip select where NAND is connected + uint8_t status; // Status bitmap + + uint16_t *block_map; // For bad blocks remapping + uint16_t remap_start; // First unused remap block +} Mt29f; + +/* + * Kblock id. + */ +#define KBT_NAND MAKE_ID('N', 'A', 'N', 'D') + +/** +* Convert + ASSERT from generic KBlock to NAND context. +*/ +INLINE Mt29f *MT29F_CAST(KBlock *kb) +{ + ASSERT(kb->priv.type == KBT_NAND); + return (Mt29f *)kb; +} + +struct Heap; + +// Kblock interface +bool mt29f_init(Mt29f *chip, struct Heap *heap, unsigned chip_select); +bool mt29f_initUnbuffered(Mt29f *chip, struct Heap *heap, unsigned chip_select); + +// NAND specific functions +bool mt29f_getDevId(Mt29f *chip, uint8_t dev_id[5]); +int mt29f_blockErase(Mt29f *chip, uint16_t block); +void mt29f_format(Mt29f *chip); + +#ifdef _DEBUG +void mt29f_ruinSomeBlocks(Mt29f *chip); +#endif + +// Hardware specific functions, implemented by cpu specific module +bool mt29f_waitReadyBusy(Mt29f *chip, time_t timeout); +bool mt29f_waitTransferComplete(Mt29f *chip, time_t timeout); +void mt29f_sendCommand(Mt29f *chip, uint32_t cmd1, uint32_t cmd2, + int num_cycles, uint32_t cycle0, uint32_t cycle1234); +uint8_t mt29f_getChipStatus(Mt29f *chip); +void *mt29f_dataBuffer(Mt29f *chip); +bool mt29f_checkEcc(Mt29f *chip); +void mt29f_computeEcc(Mt29f *chip, const void *buf, size_t size, uint32_t *ecc, size_t ecc_size); +void mt29f_hwInit(Mt29f *chip); + +#endif /* DRV_MT29F_H */