diff options
Diffstat (limited to 'os/hal/ports/STM32/LLD/FSMCv1/hal_nand_lld.c')
-rw-r--r-- | os/hal/ports/STM32/LLD/FSMCv1/hal_nand_lld.c | 592 |
1 files changed, 592 insertions, 0 deletions
diff --git a/os/hal/ports/STM32/LLD/FSMCv1/hal_nand_lld.c b/os/hal/ports/STM32/LLD/FSMCv1/hal_nand_lld.c new file mode 100644 index 0000000..abd5aa3 --- /dev/null +++ b/os/hal/ports/STM32/LLD/FSMCv1/hal_nand_lld.c @@ -0,0 +1,592 @@ +/* + ChibiOS/HAL - Copyright (C) 2014 Uladzimir Pylinsky aka barthess + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +/** + * @file hal_fsmc_nand_lld.c + * @brief FSMC NAND Driver subsystem low level driver source. + * + * @addtogroup NAND + * @{ + */ + +#include "hal.h" + +#if (HAL_USE_NAND == TRUE) || defined(__DOXYGEN__) + +#include "hal_nand_lld.h" + +/*===========================================================================*/ +/* Driver local definitions. */ +/*===========================================================================*/ +#define NAND_DMA_CHANNEL \ + STM32_DMA_GETCHANNEL(STM32_NAND_DMA_STREAM, \ + STM32_FSMC_DMA_CHN) + +/** + * @brief Bus width of NAND IC. + * @details Must be 8 or 16 + */ +#if ! defined(STM32_NAND_BUS_WIDTH) || defined(__DOXYGEN__) +#define STM32_NAND_BUS_WIDTH 8 +#endif + +/** + * @brief DMA transaction width on AHB bus in bytes + */ +#define AHB_TRANSACTION_WIDTH 2 + +/*===========================================================================*/ +/* Driver exported variables. */ +/*===========================================================================*/ + +/** + * @brief NAND1 driver identifier. + */ +#if STM32_NAND_USE_NAND1 || defined(__DOXYGEN__) +NANDDriver NANDD1; +#endif + +/** + * @brief NAND2 driver identifier. + */ +#if STM32_NAND_USE_NAND2 || defined(__DOXYGEN__) +NANDDriver NANDD2; +#endif + +/*===========================================================================*/ +/* Driver local types. */ +/*===========================================================================*/ + +/*===========================================================================*/ +/* Driver local variables and types. */ +/*===========================================================================*/ + +/*===========================================================================*/ +/* Driver local functions. */ +/*===========================================================================*/ + +/** + * @brief Helper function. + * + * @notapi + */ +static void align_check(const void *ptr, uint32_t len) { + osalDbgCheck((((uint32_t)ptr % AHB_TRANSACTION_WIDTH) == 0) && + ((len % AHB_TRANSACTION_WIDTH) == 0) && + (len >= AHB_TRANSACTION_WIDTH)); + (void)ptr; + (void)len; +} + +/** + * @brief Work around errata in STM32's FSMC core. + * @details Constant output clock (if enabled) disappears when CLKDIV value + * sets to 1 (FMC_CLK period = 2 × HCLK periods) AND 8-bit async + * transaction generated on AHB. This workaround eliminates 8-bit + * transactions on bus when you use 8-bit memory. It suitable only + * for 8-bit memory (i.e. PWID bits in PCR register must be set + * to 8-bit mode). + * + * @notapi + */ +static void set_16bit_bus(NANDDriver *nandp) { +#if STM32_NAND_BUS_WIDTH + nandp->nand->PCR |= FSMC_PCR_PWID_16; +#else + (void)nandp; +#endif +} + +static void set_8bit_bus(NANDDriver *nandp) { +#if STM32_NAND_BUS_WIDTH + nandp->nand->PCR &= ~FSMC_PCR_PWID_16; +#else + (void)nandp; +#endif +} + +/** + * @brief Wakes up the waiting thread. + * + * @param[in] nandp pointer to the @p NANDDriver object + * @param[in] msg wakeup message + * + * @notapi + */ +static void wakeup_isr(NANDDriver *nandp) { + + osalDbgCheck(nandp->thread != NULL); + osalThreadResumeI(&nandp->thread, MSG_OK); +} + +/** + * @brief Put calling thread in suspend and switch driver state + * + * @param[in] nandp pointer to the @p NANDDriver object + */ +static void nand_lld_suspend_thread(NANDDriver *nandp) { + + osalThreadSuspendS(&nandp->thread); +} + +/** + * @brief Caclulate ECCPS register value + * + * @param[in] nandp pointer to the @p NANDDriver object + */ +static uint32_t calc_eccps(NANDDriver *nandp) { + + uint32_t i = 0; + uint32_t eccps = nandp->config->page_data_size; + + eccps = eccps >> 9; + while (eccps > 0){ + i++; + eccps >>= 1; + } + + return i << 17; +} + +/*===========================================================================*/ +/* Driver interrupt handlers. */ +/*===========================================================================*/ + +/** + * @brief Enable interrupts from NAND + * + * @param[in] nandp pointer to the @p NANDDriver object + * + * @notapi + */ +static void nand_ready_isr_enable(NANDDriver *nandp) { + + nandp->nand->SR &= ~(FSMC_SR_IRS | FSMC_SR_ILS | FSMC_SR_IFS | + FSMC_SR_ILEN | FSMC_SR_IFEN); + nandp->nand->SR |= FSMC_SR_IREN; +} + +/** + * @brief Disable interrupts from NAND + * + * @param[in] nandp pointer to the @p NANDDriver object + * + * @notapi + */ +static void nand_ready_isr_disable(NANDDriver *nandp) { + + nandp->nand->SR &= ~FSMC_SR_IREN; +} + +/** + * @brief Ready interrupt handler + * + * @param[in] nandp pointer to the @p NANDDriver object + * + * @notapi + */ +static void nand_isr_handler(NANDDriver *nandp) { + + osalSysLockFromISR(); + + osalDbgCheck(nandp->nand->SR & FSMC_SR_IRS); /* spurious interrupt happened */ + nandp->nand->SR &= ~FSMC_SR_IRS; + + switch (nandp->state){ + case NAND_READ: + nandp->state = NAND_DMA_RX; + dmaStartMemCopy(nandp->dma, nandp->dmamode, nandp->map_data, nandp->rxdata, + nandp->datalen/AHB_TRANSACTION_WIDTH); + /* thread will be waked up from DMA ISR */ + break; + + case NAND_ERASE: /* NAND reports about erase finish */ + case NAND_PROGRAM: /* NAND reports about page programming finish */ + case NAND_RESET: /* NAND reports about finished reset recover */ + nandp->state = NAND_READY; + wakeup_isr(nandp); + break; + + default: + osalSysHalt("Unhandled case"); + break; + } + osalSysUnlockFromISR(); +} + +/** + * @brief DMA RX end IRQ handler. + * + * @param[in] nandp pointer to the @p NANDDriver object + * @param[in] flags pre-shifted content of the ISR register + * + * @notapi + */ +static void nand_lld_serve_transfer_end_irq(NANDDriver *nandp, uint32_t flags) { + /* DMA errors handling.*/ +#if defined(STM32_NAND_DMA_ERROR_HOOK) + if ((flags & (STM32_DMA_ISR_TEIF | STM32_DMA_ISR_DMEIF)) != 0) { + STM32_NAND_DMA_ERROR_HOOK(nandp); + } +#else + (void)flags; +#endif + + osalSysLockFromISR(); + + dmaStreamDisable(nandp->dma); + + switch (nandp->state){ + case NAND_DMA_TX: + nandp->state = NAND_PROGRAM; + nandp->map_cmd[0] = NAND_CMD_PAGEPROG; + /* thread will be woken up from ready_isr() */ + break; + + case NAND_DMA_RX: + nandp->state = NAND_READY; + nandp->rxdata = NULL; + nandp->datalen = 0; + wakeup_isr(nandp); + break; + + default: + osalSysHalt("Unhandled case"); + break; + } + + osalSysUnlockFromISR(); +} + +/*===========================================================================*/ +/* Driver exported functions. */ +/*===========================================================================*/ + +/** + * @brief Low level NAND driver initialization. + * + * @notapi + */ +void nand_lld_init(void) { + + fsmcInit(); + +#if STM32_NAND_USE_NAND1 + /* Driver initialization.*/ + nandObjectInit(&NANDD1); + NANDD1.rxdata = NULL; + NANDD1.datalen = 0; + NANDD1.thread = NULL; + NANDD1.dma = STM32_DMA_STREAM(STM32_NAND_DMA_STREAM); + NANDD1.nand = FSMCD1.nand1; + NANDD1.map_data = (void *)FSMC_Bank2_MAP_COMMON_DATA; + NANDD1.map_cmd = (uint16_t *)FSMC_Bank2_MAP_COMMON_CMD; + NANDD1.map_addr = (uint16_t *)FSMC_Bank2_MAP_COMMON_ADDR; + NANDD1.bb_map = NULL; +#endif /* STM32_NAND_USE_NAND1 */ + +#if STM32_NAND_USE_NAND2 + /* Driver initialization.*/ + nandObjectInit(&NANDD2); + NANDD2.rxdata = NULL; + NANDD2.datalen = 0; + NANDD2.thread = NULL; + NANDD2.dma = STM32_DMA_STREAM(STM32_NAND_DMA_STREAM); + NANDD2.nand = FSMCD1.nand2; + NANDD2.map_data = (void *)FSMC_Bank3_MAP_COMMON_DATA; + NANDD2.map_cmd = (uint16_t *)FSMC_Bank3_MAP_COMMON_CMD; + NANDD2.map_addr = (uint16_t *)FSMC_Bank3_MAP_COMMON_ADDR; + NANDD2.bb_map = NULL; +#endif /* STM32_NAND_USE_NAND2 */ +} + +/** + * @brief Configures and activates the NAND peripheral. + * + * @param[in] nandp pointer to the @p NANDDriver object + * + * @notapi + */ +void nand_lld_start(NANDDriver *nandp) { + + bool b; + uint32_t dmasize; + uint32_t pcr_bus_width; + + if (FSMCD1.state == FSMC_STOP) + fsmcStart(&FSMCD1); + + if (nandp->state == NAND_STOP) { + b = dmaStreamAlloc(nandp->dma, + STM32_EMC_FSMC1_IRQ_PRIORITY, + (stm32_dmaisr_t)nand_lld_serve_transfer_end_irq, + (void *)nandp); + osalDbgAssert(!b, "stream already allocated"); + +#if AHB_TRANSACTION_WIDTH == 4 + dmasize = STM32_DMA_CR_PSIZE_WORD | STM32_DMA_CR_MSIZE_WORD; +#elif AHB_TRANSACTION_WIDTH == 2 + dmasize = STM32_DMA_CR_PSIZE_HWORD | STM32_DMA_CR_MSIZE_HWORD; +#elif AHB_TRANSACTION_WIDTH == 1 + dmasize = STM32_DMA_CR_PSIZE_BYTE | STM32_DMA_CR_MSIZE_BYTE; +#else +#error "Incorrect AHB_TRANSACTION_WIDTH" +#endif + + nandp->dmamode = STM32_DMA_CR_CHSEL(NAND_DMA_CHANNEL) | + STM32_DMA_CR_PL(STM32_NAND_DMA_PRIORITY) | + dmasize | + STM32_DMA_CR_DMEIE | + STM32_DMA_CR_TEIE | + STM32_DMA_CR_TCIE; + +#if STM32_NAND_BUS_WIDTH == 8 + pcr_bus_width = FSMC_PCR_PWID_8; +#elif STM32_NAND_BUS_WIDTH == 16 + pcr_bus_width = FSMC_PCR_PWID_16; +#else +#error "Bus width must be 8 or 16 bits" +#endif + nandp->nand->PCR = pcr_bus_width | calc_eccps(nandp) | + FSMC_PCR_PTYP_NAND | FSMC_PCR_PBKEN; + nandp->nand->PMEM = nandp->config->pmem; + nandp->nand->PATT = nandp->config->pmem; + nandp->isr_handler = nand_isr_handler; + nand_ready_isr_enable(nandp); + } +} + +/** + * @brief Deactivates the NAND peripheral. + * + * @param[in] nandp pointer to the @p NANDDriver object + * + * @notapi + */ +void nand_lld_stop(NANDDriver *nandp) { + + if (nandp->state == NAND_READY) { + dmaStreamFree(nandp->dma); + nandp->nand->PCR &= ~FSMC_PCR_PBKEN; + nand_ready_isr_disable(nandp); + nandp->isr_handler = NULL; + } +} + +/** + * @brief Read data from NAND. + * + * @param[in] nandp pointer to the @p NANDDriver object + * @param[out] data pointer to data buffer + * @param[in] datalen size of data buffer in bytes + * @param[in] addr pointer to address buffer + * @param[in] addrlen length of address + * @param[out] ecc pointer to store computed ECC. Ignored when NULL. + * + * @notapi + */ +void nand_lld_read_data(NANDDriver *nandp, uint16_t *data, size_t datalen, + uint8_t *addr, size_t addrlen, uint32_t *ecc){ + + align_check(data, datalen); + + nandp->state = NAND_READ; + nandp->rxdata = data; + nandp->datalen = datalen; + + set_16bit_bus(nandp); + nand_lld_write_cmd(nandp, NAND_CMD_READ0); + nand_lld_write_addr(nandp, addr, addrlen); + osalSysLock(); + nand_lld_write_cmd(nandp, NAND_CMD_READ0_CONFIRM); + set_8bit_bus(nandp); + + /* Here NAND asserts busy signal and starts transferring from memory + array to page buffer. After the end of transmission ready_isr functions + starts DMA transfer from page buffer to MCU's RAM.*/ + osalDbgAssert((nandp->nand->PCR & FSMC_PCR_ECCEN) == 0, + "State machine broken. ECCEN must be previously disabled."); + + if (NULL != ecc){ + nandp->nand->PCR |= FSMC_PCR_ECCEN; + } + + nand_lld_suspend_thread(nandp); + osalSysUnlock(); + + /* thread was woken up from DMA ISR */ + if (NULL != ecc){ + while (! (nandp->nand->SR & FSMC_SR_FEMPT)) + ; + *ecc = nandp->nand->ECCR; + nandp->nand->PCR &= ~FSMC_PCR_ECCEN; + } +} + +/** + * @brief Write data to NAND. + * + * @param[in] nandp pointer to the @p NANDDriver object + * @param[in] data buffer with data to be written + * @param[in] datalen size of data buffer in bytes + * @param[in] addr pointer to address buffer + * @param[in] addrlen length of address + * @param[out] ecc pointer to store computed ECC. Ignored when NULL. + * + * @return The operation status reported by NAND IC (0x70 command). + * + * @notapi + */ +uint8_t nand_lld_write_data(NANDDriver *nandp, const uint16_t *data, + size_t datalen, uint8_t *addr, size_t addrlen, uint32_t *ecc) { + + align_check(data, datalen); + + nandp->state = NAND_WRITE; + + set_16bit_bus(nandp); + nand_lld_write_cmd(nandp, NAND_CMD_WRITE); + osalSysLock(); + nand_lld_write_addr(nandp, addr, addrlen); + set_8bit_bus(nandp); + + /* Now start DMA transfer to NAND buffer and put thread in sleep state. + Tread will be woken up from ready ISR. */ + nandp->state = NAND_DMA_TX; + osalDbgAssert((nandp->nand->PCR & FSMC_PCR_ECCEN) == 0, + "State machine broken. ECCEN must be previously disabled."); + + if (NULL != ecc){ + nandp->nand->PCR |= FSMC_PCR_ECCEN; + } + + dmaStartMemCopy(nandp->dma, nandp->dmamode, data, nandp->map_data, + datalen/AHB_TRANSACTION_WIDTH); + + nand_lld_suspend_thread(nandp); + osalSysUnlock(); + + if (NULL != ecc){ + while (! (nandp->nand->SR & FSMC_SR_FEMPT)) + ; + *ecc = nandp->nand->ECCR; + nandp->nand->PCR &= ~FSMC_PCR_ECCEN; + } + + return nand_lld_read_status(nandp); +} + +/** + * @brief Soft reset NAND device. + * + * @param[in] nandp pointer to the @p NANDDriver object + * + * @notapi + */ +void nand_lld_reset(NANDDriver *nandp) { + + nandp->state = NAND_RESET; + + set_16bit_bus(nandp); + nand_lld_write_cmd(nandp, NAND_CMD_RESET); + set_8bit_bus(nandp); + + osalSysLock(); + nand_lld_suspend_thread(nandp); + osalSysUnlock(); +} + +/** + * @brief Erase block. + * + * @param[in] nandp pointer to the @p NANDDriver object + * @param[in] addr pointer to address buffer + * @param[in] addrlen length of address + * + * @return The operation status reported by NAND IC (0x70 command). + * + * @notapi + */ +uint8_t nand_lld_erase(NANDDriver *nandp, uint8_t *addr, size_t addrlen) { + + nandp->state = NAND_ERASE; + + set_16bit_bus(nandp); + nand_lld_write_cmd(nandp, NAND_CMD_ERASE); + nand_lld_write_addr(nandp, addr, addrlen); + osalSysLock(); + nand_lld_write_cmd(nandp, NAND_CMD_ERASE_CONFIRM); + set_8bit_bus(nandp); + + nand_lld_suspend_thread(nandp); + osalSysUnlock(); + + return nand_lld_read_status(nandp); +} + +/** + * @brief Send addres to NAND. + * + * @param[in] nandp pointer to the @p NANDDriver object + * @param[in] len length of address array + * @param[in] addr pointer to address array + * + * @notapi + */ +void nand_lld_write_addr(NANDDriver *nandp, const uint8_t *addr, size_t len) { + size_t i = 0; + + for (i=0; i<len; i++) + nandp->map_addr[i] = addr[i]; +} + +/** + * @brief Send command to NAND. + * + * @param[in] nandp pointer to the @p NANDDriver object + * @param[in] cmd command value + * + * @notapi + */ +void nand_lld_write_cmd(NANDDriver *nandp, uint8_t cmd) { + nandp->map_cmd[0] = cmd; +} + +/** + * @brief Read status byte from NAND. + * + * @param[in] nandp pointer to the @p NANDDriver object + * + * @return Status byte. + * + * @notapi + */ +uint8_t nand_lld_read_status(NANDDriver *nandp) { + + uint16_t status; + + set_16bit_bus(nandp); + nand_lld_write_cmd(nandp, NAND_CMD_STATUS); + set_8bit_bus(nandp); + status = nandp->map_data[0]; + + return status & 0xFF; +} + +#endif /* HAL_USE_NAND */ + +/** @} */ + |