556 lines
16 KiB
C
556 lines
16 KiB
C
/*
|
|
ChibiOS/RT - Copyright (C) 2006,2007,2008,2009,2010,
|
|
2011,2012,2013 Giovanni Di Sirio.
|
|
|
|
This file is part of ChibiOS/RT.
|
|
|
|
ChibiOS/RT 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
/*
|
|
Concepts and parts of this file have been contributed by Uladzimir Pylinsky
|
|
aka barthess.
|
|
*/
|
|
|
|
/**
|
|
* @file emcnand_lld.c
|
|
* @brief EMCNAND Driver subsystem low level driver source template.
|
|
*
|
|
* @addtogroup EMCNAND
|
|
* @{
|
|
*/
|
|
|
|
#include "hal.h"
|
|
|
|
#if HAL_USE_EMCNAND || defined(__DOXYGEN__)
|
|
|
|
/*===========================================================================*/
|
|
/* Driver local definitions. */
|
|
/*===========================================================================*/
|
|
#define EMCNAND_DMA_CHANNEL \
|
|
STM32_DMA_GETCHANNEL(STM32_EMCNAND_EMCNAND1_DMA_STREAM, \
|
|
STM32_EMC_DMA_CHN)
|
|
|
|
/*===========================================================================*/
|
|
/* Driver exported variables. */
|
|
/*===========================================================================*/
|
|
|
|
/**
|
|
* @brief EMCNAND1 driver identifier.
|
|
*/
|
|
#if STM32_EMCNAND_USE_EMCNAND1 || defined(__DOXYGEN__)
|
|
EMCNANDDriver EMCNANDD1;
|
|
#endif
|
|
|
|
/**
|
|
* @brief EMCNAND2 driver identifier.
|
|
*/
|
|
#if STM32_EMCNAND_USE_EMCNAND2 || defined(__DOXYGEN__)
|
|
EMCNANDDriver EMCNANDD2;
|
|
#endif
|
|
|
|
/*===========================================================================*/
|
|
/* Driver local types. */
|
|
/*===========================================================================*/
|
|
|
|
/*===========================================================================*/
|
|
/* Driver local variables and types. */
|
|
/*===========================================================================*/
|
|
|
|
/*===========================================================================*/
|
|
/* Driver local functions. */
|
|
/*===========================================================================*/
|
|
/**
|
|
* @brief Wakes up the waiting thread.
|
|
*
|
|
* @param[in] emcnandp pointer to the @p EMCNANDDriver object
|
|
* @param[in] msg wakeup message
|
|
*
|
|
* @notapi
|
|
*/
|
|
static void wakeup_isr(EMCNANDDriver *emcnandp, msg_t msg){
|
|
|
|
osalDbgCheck(emcnandp->thread != NULL);
|
|
|
|
if (emcnandp->thread) {
|
|
thread_t *tp = emcnandp->thread;
|
|
emcnandp->thread = NULL;
|
|
tp->p_u.rdymsg = msg;
|
|
chSchReadyI(tp);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Put calling thread in suspend and switch driver state
|
|
*
|
|
* @param[in] emcnandp pointer to the @p EMCNANDDriver object
|
|
*/
|
|
static void emcnand_lld_suspend_thread(EMCNANDDriver *emcnandp) {
|
|
|
|
emcnandp->thread = chThdGetSelfX();
|
|
chSchGoSleepS(CH_STATE_SUSPENDED);
|
|
}
|
|
|
|
/**
|
|
* @brief Caclulate ECCPS register value
|
|
*
|
|
* @param[in] emcnandp pointer to the @p EMCNANDDriver object
|
|
*/
|
|
static uint32_t calc_eccps(EMCNANDDriver *emcnandp){
|
|
|
|
uint32_t i = 0;
|
|
uint32_t eccps = emcnandp->config->page_data_size;
|
|
|
|
eccps = eccps >> 9;
|
|
while (eccps > 0){
|
|
i++;
|
|
eccps >>= 1;
|
|
}
|
|
|
|
return i << 17;
|
|
}
|
|
|
|
/*===========================================================================*/
|
|
/* Driver interrupt handlers. */
|
|
/*===========================================================================*/
|
|
|
|
#if STM32_EMC_USE_INT
|
|
/**
|
|
* @brief Enable interrupts from FSMC
|
|
*
|
|
* @param[in] emcnandp pointer to the @p EMCNANDDriver object
|
|
*
|
|
* @notapi
|
|
*/
|
|
static void emcnand_ready_isr_enable(EMCNANDDriver *emcnandp) {
|
|
emcnandp->nand->SR |= FSMC_SR_IREN;
|
|
chDbgPanic("Function untested");
|
|
}
|
|
|
|
/**
|
|
* @brief Disable interrupts from FSMC
|
|
*
|
|
* @param[in] emcnandp pointer to the @p EMCNANDDriver object
|
|
*
|
|
* @notapi
|
|
*/
|
|
static void emcnand_ready_isr_disable(EMCNANDDriver *emcnandp) {
|
|
emcnandp->nand->SR &= ~FSMC_SR_IREN;
|
|
chDbgPanic("Function untested");
|
|
}
|
|
|
|
/**
|
|
* @brief Ready interrupt handler
|
|
*
|
|
* @param[in] emcnandp pointer to the @p EMCNANDDriver object
|
|
* @param[in] flags flags passed from FSMC intrrupt handler
|
|
*
|
|
* @notapi
|
|
*/
|
|
static void emcnand_isr_handler (EMCNANDDriver *emcnandp,
|
|
emcnandflags_t flags){
|
|
(void)emcnandp;
|
|
(void)flags;
|
|
|
|
chDbgPanic("Unrealized");
|
|
}
|
|
#else /* STM32_EMC_USE_INT */
|
|
/**
|
|
* @brief Disable interrupts from EXTI
|
|
*
|
|
* @param[in] emcnandp pointer to the @p EMCNANDDriver object
|
|
*
|
|
* @notapi
|
|
*/
|
|
static void emcnand_ready_isr_enable(EMCNANDDriver *emcnandp) {
|
|
emcnandp->config->ext_isr_enable();
|
|
}
|
|
|
|
/**
|
|
* @brief Enable interrupts from EXTI
|
|
*
|
|
* @param[in] emcnandp pointer to the @p EMCNANDDriver object
|
|
*
|
|
* @notapi
|
|
*/
|
|
static void emcnand_ready_isr_disable(EMCNANDDriver *emcnandp) {
|
|
emcnandp->config->ext_isr_disable();
|
|
}
|
|
|
|
/**
|
|
* @brief Ready pin interrupt handler.
|
|
*
|
|
* @param[in] emcnandp pointer to the @p EMCNANDDriver object
|
|
*
|
|
* @notapi
|
|
*/
|
|
static void emcnand_isr_handler(EMCNANDDriver *emcnandp){
|
|
|
|
osalSysLockFromISR();
|
|
|
|
switch (emcnandp->state){
|
|
case EMCNAND_READ:
|
|
emcnandp->state = EMCNAND_DMA_RX;
|
|
dmaStartMemCopy(emcnandp->dma, emcnandp->dmamode,
|
|
emcnandp->map_data, emcnandp->rxdata, emcnandp->datalen);
|
|
/* thread will be woked up from DMA ISR */
|
|
break;
|
|
|
|
case EMCNAND_ERASE:
|
|
/* NAND reports about erase finish */
|
|
emcnandp->state = EMCNAND_READY;
|
|
wakeup_isr(emcnandp, MSG_OK);
|
|
break;
|
|
|
|
case EMCNAND_PROGRAM:
|
|
/* NAND reports about page programming finish */
|
|
emcnandp->state = EMCNAND_READY;
|
|
wakeup_isr(emcnandp, MSG_OK);
|
|
break;
|
|
|
|
default:
|
|
osalSysHalt("Unhandled case");
|
|
break;
|
|
}
|
|
|
|
osalSysUnlockFromISR();
|
|
}
|
|
#endif /* STM32_EMC_USE_INT */
|
|
|
|
/**
|
|
* @brief DMA RX end IRQ handler.
|
|
*
|
|
* @param[in] emcnandp pointer to the @p EMCNANDDriver object
|
|
* @param[in] flags pre-shifted content of the ISR register
|
|
*
|
|
* @notapi
|
|
*/
|
|
static void emcnand_lld_serve_transfer_end_irq(EMCNANDDriver *emcnandp,
|
|
uint32_t flags) {
|
|
/* DMA errors handling.*/
|
|
#if defined(STM32_EMCNAND_DMA_ERROR_HOOK)
|
|
if ((flags & (STM32_DMA_ISR_TEIF | STM32_DMA_ISR_DMEIF)) != 0) {
|
|
STM32_EMCNAND_DMA_ERROR_HOOK(emcnandp);
|
|
}
|
|
#else
|
|
(void)flags;
|
|
#endif
|
|
|
|
osalSysLockFromISR();
|
|
|
|
dmaStreamDisable(emcnandp->dma);
|
|
|
|
switch (emcnandp->state){
|
|
case EMCNAND_DMA_TX:
|
|
emcnandp->state = EMCNAND_PROGRAM;
|
|
emcnandp->map_cmd[0] = NAND_CMD_PAGEPROG;
|
|
/* thread will be woken from ready_isr() */
|
|
break;
|
|
|
|
case EMCNAND_DMA_RX:
|
|
emcnandp->state = EMCNAND_READY;
|
|
emcnandp->rxdata = NULL;
|
|
emcnandp->datalen = 0;
|
|
wakeup_isr(emcnandp, MSG_OK);
|
|
break;
|
|
|
|
default:
|
|
osalSysHalt("Unhandled case");
|
|
break;
|
|
}
|
|
|
|
osalSysUnlockFromISR();
|
|
}
|
|
|
|
/*===========================================================================*/
|
|
/* Driver exported functions. */
|
|
/*===========================================================================*/
|
|
|
|
/**
|
|
* @brief Low level EMCNAND driver initialization.
|
|
*
|
|
* @notapi
|
|
*/
|
|
void emcnand_lld_init(void) {
|
|
|
|
#if STM32_EMCNAND_USE_EMCNAND1
|
|
/* Driver initialization.*/
|
|
emcnandObjectInit(&EMCNANDD1);
|
|
EMCNANDD1.rxdata = NULL;
|
|
EMCNANDD1.datalen = 0;
|
|
EMCNANDD1.thread = NULL;
|
|
EMCNANDD1.dma = STM32_DMA_STREAM(STM32_EMCNAND_EMCNAND1_DMA_STREAM);
|
|
EMCNANDD1.nand = (FSMC_NAND_TypeDef *)FSMC_Bank2_R_BASE;
|
|
EMCNANDD1.map_data = (uint8_t*)FSMC_Bank2_MAP_COMMON_DATA;
|
|
EMCNANDD1.map_cmd = (uint8_t*)FSMC_Bank2_MAP_COMMON_CMD;
|
|
EMCNANDD1.map_addr = (uint8_t*)FSMC_Bank2_MAP_COMMON_ADDR;
|
|
#endif /* STM32_EMCNAND_USE_EMCNAND1 */
|
|
|
|
#if STM32_EMCNAND_USE_EMCNAND2
|
|
/* Driver initialization.*/
|
|
#warning "Untested"
|
|
emcnandObjectInit(&EMCNANDD1);
|
|
EMCNANDD2.rxdata = NULL;
|
|
EMCNANDD2.datalen = 0;
|
|
EMCNANDD2.thread = NULL;
|
|
EMCNANDD2.dma = STM32_DMA_STREAM(STM32_EMCNAND_EMCNAND2_DMA_STREAM);
|
|
EMCNANDD2.nand = (FSMC_NAND_TypeDef *)FSMC_Bank3_R_BASE;
|
|
EMCNANDD2.map_data = (uint8_t*)FSMC_Bank3_MAP_COMMON_DATA;
|
|
EMCNANDD2.map_cmd = (uint8_t*)FSMC_Bank3_MAP_COMMON_CMD;
|
|
EMCNANDD2.map_addr = (uint8_t*)FSMC_Bank3_MAP_COMMON_ADDR;
|
|
#endif /* STM32_EMCNAND_USE_EMCNAND2 */
|
|
}
|
|
|
|
/**
|
|
* @brief Configures and activates the EMCNAND peripheral.
|
|
*
|
|
* @param[in] emcnandp pointer to the @p EMCNANDDriver object
|
|
*
|
|
* @notapi
|
|
*/
|
|
void emcnand_lld_start(EMCNANDDriver *emcnandp) {
|
|
|
|
bool_t b;
|
|
|
|
if (emcnandp->state == EMCNAND_STOP) {
|
|
b = dmaStreamAllocate(emcnandp->dma,
|
|
STM32_EMC_FSMC1_IRQ_PRIORITY,
|
|
(stm32_dmaisr_t)emcnand_lld_serve_transfer_end_irq,
|
|
(void *)emcnandp);
|
|
osalDbgAssert(!b, "stream already allocated");
|
|
emcnandp->dmamode = STM32_DMA_CR_CHSEL(EMCNAND_DMA_CHANNEL) |
|
|
STM32_DMA_CR_PL(STM32_EMCNAND_EMCNAND1_DMA_PRIORITY) |
|
|
STM32_DMA_CR_PSIZE_BYTE | STM32_DMA_CR_MSIZE_BYTE |
|
|
STM32_DMA_CR_DMEIE | STM32_DMA_CR_TEIE |
|
|
STM32_DMA_CR_TCIE;
|
|
/* dmaStreamSetFIFO(emcnandp->dma,
|
|
STM32_DMA_FCR_DMDIS | EMCNAND_STM32_DMA_FCR_FTH_LVL); */
|
|
emcnandp->nand->PCR = calc_eccps(emcnandp) | FSMC_PCR_PTYP | FSMC_PCR_PBKEN;
|
|
emcnandp->nand->PMEM = emcnandp->config->pmem;
|
|
emcnandp->nand->PATT = emcnandp->config->pmem;
|
|
emcnandp->isr_handler = emcnand_isr_handler;
|
|
emcnand_ready_isr_enable(emcnandp);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Deactivates the EMCNAND peripheral.
|
|
*
|
|
* @param[in] emcnandp pointer to the @p EMCNANDDriver object
|
|
*
|
|
* @notapi
|
|
*/
|
|
void emcnand_lld_stop(EMCNANDDriver *emcnandp) {
|
|
|
|
if (emcnandp->state == EMCNAND_READY) {
|
|
dmaStreamRelease(emcnandp->dma);
|
|
emcnandp->nand->PCR &= ~FSMC_PCR_PBKEN;
|
|
emcnand_ready_isr_disable(emcnandp);
|
|
emcnandp->isr_handler = NULL;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Read data from NAND.
|
|
*
|
|
* @param[in] emcnandp pointer to the @p EMCNANDDriver object
|
|
* @param[out] data pointer to data buffer
|
|
* @param[in] datalen size of data buffer
|
|
* @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 emcnand_lld_read_data(EMCNANDDriver *emcnandp, uint8_t *data,
|
|
size_t datalen, uint8_t *addr, size_t addrlen, uint32_t *ecc){
|
|
|
|
emcnandp->state = EMCNAND_READ;
|
|
emcnandp->rxdata = data;
|
|
emcnandp->datalen = datalen;
|
|
|
|
emcnand_lld_write_cmd (emcnandp, NAND_CMD_READ0);
|
|
emcnand_lld_write_addr(emcnandp, addr, addrlen);
|
|
osalSysLock();
|
|
emcnand_lld_write_cmd (emcnandp, NAND_CMD_READ0_CONFIRM);
|
|
|
|
/* 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((emcnandp->nand->PCR & FSMC_PCR_ECCEN) == 0,
|
|
"State machine broken. ECCEN must be previously disabled.");
|
|
|
|
if (NULL != ecc){
|
|
emcnandp->nand->PCR |= FSMC_PCR_ECCEN;
|
|
}
|
|
|
|
emcnand_lld_suspend_thread(emcnandp);
|
|
osalSysUnlock();
|
|
|
|
/* thread was woken up from DMA ISR */
|
|
if (NULL != ecc){
|
|
while (! (emcnandp->nand->SR & FSMC_SR_FEMPT))
|
|
;
|
|
*ecc = emcnandp->nand->ECCR;
|
|
emcnandp->nand->PCR &= ~FSMC_PCR_ECCEN;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Write data to NAND.
|
|
*
|
|
* @param[in] emcnandp pointer to the @p EMCNANDDriver object
|
|
* @param[in] data buffer with data to be written
|
|
* @param[in] datalen size of data buffer
|
|
* @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 emcnand_lld_write_data(EMCNANDDriver *emcnandp, const uint8_t *data,
|
|
size_t datalen, uint8_t *addr, size_t addrlen, uint32_t *ecc){
|
|
|
|
emcnandp->state = EMCNAND_WRITE;
|
|
|
|
emcnand_lld_write_cmd (emcnandp, NAND_CMD_WRITE);
|
|
osalSysLock();
|
|
emcnand_lld_write_addr(emcnandp, addr, addrlen);
|
|
|
|
/* Now start DMA transfer to NAND buffer and put thread in sleep state.
|
|
Tread will we woken up from ready ISR. */
|
|
emcnandp->state = EMCNAND_DMA_TX;
|
|
osalDbgAssert((emcnandp->nand->PCR & FSMC_PCR_ECCEN) == 0,
|
|
"State machine broken. ECCEN must be previously disabled.");
|
|
|
|
if (NULL != ecc){
|
|
emcnandp->nand->PCR |= FSMC_PCR_ECCEN;
|
|
}
|
|
|
|
dmaStartMemCopy(emcnandp->dma, emcnandp->dmamode,
|
|
data, emcnandp->map_data, datalen);
|
|
|
|
emcnand_lld_suspend_thread(emcnandp);
|
|
osalSysUnlock();
|
|
|
|
if (NULL != ecc){
|
|
while (! (emcnandp->nand->SR & FSMC_SR_FEMPT))
|
|
;
|
|
*ecc = emcnandp->nand->ECCR;
|
|
emcnandp->nand->PCR &= ~FSMC_PCR_ECCEN;
|
|
}
|
|
|
|
return emcnand_lld_read_status(emcnandp);
|
|
}
|
|
|
|
/**
|
|
* @brief Erase block.
|
|
*
|
|
* @param[in] emcnandp pointer to the @p EMCNANDDriver 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 emcnand_lld_erase(EMCNANDDriver *emcnandp,
|
|
uint8_t *addr, size_t addrlen){
|
|
|
|
emcnandp->state = EMCNAND_ERASE;
|
|
|
|
emcnand_lld_write_cmd (emcnandp, NAND_CMD_ERASE);
|
|
emcnand_lld_write_addr(emcnandp, addr, addrlen);
|
|
osalSysLock();
|
|
emcnand_lld_write_cmd (emcnandp, NAND_CMD_ERASE_CONFIRM);
|
|
emcnand_lld_suspend_thread(emcnandp);
|
|
osalSysUnlock();
|
|
|
|
return emcnand_lld_read_status(emcnandp);
|
|
}
|
|
|
|
/**
|
|
* @brief Read data from NAND using polling approach.
|
|
*
|
|
* @detatils Use this function to read data when no waiting expected. For
|
|
* Example read status word after 0x70 command
|
|
*
|
|
* @param[in] emcnandp pointer to the @p EMCNANDDriver object
|
|
* @param[out] data pointer to output buffer
|
|
* @param[in] len length of data to be read
|
|
*
|
|
* @notapi
|
|
*/
|
|
void emcnand_lld_polled_read_data(EMCNANDDriver *emcnandp,
|
|
uint8_t *data, size_t len){
|
|
size_t i = 0;
|
|
|
|
for (i=0; i<len; i++)
|
|
data[i] = emcnandp->map_data[i];
|
|
}
|
|
|
|
/**
|
|
* @brief Send addres to NAND.
|
|
*
|
|
* @param[in] emcnandp pointer to the @p EMCNANDDriver object
|
|
* @param[in] len length of address array
|
|
* @param[in] addr pointer to address array
|
|
*
|
|
* @notapi
|
|
*/
|
|
void emcnand_lld_write_addr(EMCNANDDriver *emcnandp,
|
|
const uint8_t *addr, size_t len){
|
|
size_t i = 0;
|
|
|
|
for (i=0; i<len; i++)
|
|
emcnandp->map_addr[i] = addr[i];
|
|
}
|
|
|
|
/**
|
|
* @brief Send command to NAND.
|
|
*
|
|
* @param[in] emcnandp pointer to the @p EMCNANDDriver object
|
|
* @param[in] cmd command value
|
|
*
|
|
* @notapi
|
|
*/
|
|
void emcnand_lld_write_cmd(EMCNANDDriver *emcnandp, uint8_t cmd){
|
|
emcnandp->map_cmd[0] = cmd;
|
|
}
|
|
|
|
/**
|
|
* @brief Read status byte from NAND.
|
|
*
|
|
* @param[in] emcnandp pointer to the @p EMCNANDDriver object
|
|
*
|
|
* @return Status byte.
|
|
*
|
|
* @notapi
|
|
*/
|
|
uint8_t emcnand_lld_read_status(EMCNANDDriver *emcnandp) {
|
|
|
|
uint8_t status[1] = {0x01}; /* presume worse */
|
|
|
|
emcnand_lld_write_cmd(emcnandp, NAND_CMD_STATUS);
|
|
emcnand_lld_polled_read_data(emcnandp, status, 1);
|
|
|
|
return status[0];
|
|
}
|
|
|
|
#endif /* HAL_USE_EMCNAND */
|
|
|
|
/** @} */
|
|
|