nuttx/arch/arm/src/sama5/sam_nand.c

3108 lines
81 KiB
C

/****************************************************************************
* arch/arm/src/sama5/sam_nand.c
*
* Copyright (C) 2013 Gregory Nutt. All rights reserved.
* Author: Gregory Nutt <gnutt@nuttx.org>
*
* References:
* SAMA5D3 Series Data Sheet
* Atmel NoOS sample code.
*
* The Atmel sample code has a BSD compatible license that requires this
* copyright notice:
*
* Copyright (c) 2011, 2012, Atmel Corporation
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the names NuttX nor Atmel nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <nuttx/mtd/nand_config.h>
#include <sys/types.h>
#include <stdint.h>
#include <string.h>
#include <semaphore.h>
#include <errno.h>
#include <assert.h>
#include <debug.h>
#include <nuttx/arch.h>
#include <nuttx/fs/ioctl.h>
#include <nuttx/mtd/mtd.h>
#include <nuttx/mtd/nand.h>
#include <nuttx/mtd/nand_raw.h>
#include <nuttx/mtd/nand_model.h>
#include <arch/irq.h>
#include <arch/board/board.h>
#include "up_arch.h"
#include "cache.h"
#include "sam_memories.h"
#include "sam_dmac.h"
#include "sam_pmecc.h"
#include "sam_nand.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#ifdef CONFIG_SAMA5_NAND_CE
# define ENABLE_CE(priv) board_nand_ce(priv->cs, true)
# define DISABLE_CE(priv) board_nand_ce(priv->cs, false)
#else
# define ENABLE_CE(priv)
# define DISABLE_CE(priv)
#endif
/* Nand flash chip status codes */
#define STATUS_ERROR (1 << 0)
#define STATUS_READY (1 << 6)
/* NFC ALE CLE command parameter */
#define HSMC_ALE_COL_EN (1 << 0)
#define HSMC_ALE_ROW_EN (1 << 1)
#define HSMC_CLE_WRITE_EN (1 << 2)
#define HSMC_CLE_DATA_EN (1 << 3)
#define HSMC_CLE_VCMD2_EN (1 << 4)
/* Number of tries for erasing or writing block */
#define NAND_ERASE_NRETRIES 2
#define NAND_WRITE_NRETRIES 2
/* DMA Configuration */
#define NFCSRAM_DMA_FLAGS \
DMACH_FLAG_FIFOCFG_LARGEST | \
(DMACH_FLAG_PERIPHPID_MAX | DMACH_FLAG_PERIPHAHB_AHB_IF0 | \
DMACH_FLAG_PERIPHWIDTH_32BITS | DMACH_FLAG_PERIPHINCREMENT | \
DMACH_FLAG_PERIPHCHUNKSIZE_1 | \
DMACH_FLAG_MEMPID_MAX | DMACH_FLAG_MEMAHB_AHB_IF0 | \
DMACH_FLAG_MEMWIDTH_32BITS | DMACH_FLAG_MEMINCREMENT | \
DMACH_FLAG_MEMCHUNKSIZE_1 | DMACH_FLAG_MEMBURST_4)
#define NAND_DMA_FLAGS8 \
DMACH_FLAG_FIFOCFG_LARGEST | \
(DMACH_FLAG_PERIPHPID_MAX | DMACH_FLAG_PERIPHAHB_AHB_IF0 | \
DMACH_FLAG_PERIPHWIDTH_8BITS | DMACH_FLAG_PERIPHCHUNKSIZE_1 | \
DMACH_FLAG_MEMPID_MAX | DMACH_FLAG_MEMAHB_AHB_IF0 | \
DMACH_FLAG_MEMWIDTH_8BITS | DMACH_FLAG_MEMINCREMENT | \
DMACH_FLAG_MEMCHUNKSIZE_1 | DMACH_FLAG_MEMBURST_4)
#define NAND_DMA_FLAGS16 \
DMACH_FLAG_FIFOCFG_LARGEST | \
(DMACH_FLAG_PERIPHPID_MAX | DMACH_FLAG_PERIPHAHB_AHB_IF0 | \
DMACH_FLAG_PERIPHWIDTH_16BITS | DMACH_FLAG_PERIPHCHUNKSIZE_1 | \
DMACH_FLAG_MEMPID_MAX | DMACH_FLAG_MEMAHB_AHB_IF0 | \
DMACH_FLAG_MEMWIDTH_16BITS | DMACH_FLAG_MEMINCREMENT | \
DMACH_FLAG_MEMCHUNKSIZE_1 | DMACH_FLAG_MEMBURST_4)
/****************************************************************************
* Private Types
****************************************************************************/
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
/* Low-level HSMC Helpers */
#if NAND_NBANKS > 1
void nand_lock(void);
void nand_unlock(void);
#else
# define nand_lock()
# define nand_unlock()
#endif
#ifdef CONFIG_SAMA5_NAND_DUMP
# define nand_dump(m,b,s) lib_dumpbuffer(m,b,s)
#else
# define nand_dump(m,b,s)
#endif
static void nand_wait_ready(struct sam_nandcs_s *priv);
static void nand_nfc_cmdsend(struct sam_nandcs_s *priv, uint32_t cmd,
uint32_t acycle, uint32_t cycle0);
static int nand_operation_complete(struct sam_nandcs_s *priv);
static int nand_translate_address(struct sam_nandcs_s *priv,
uint16_t coladdr, uint32_t rowaddr, uint32_t *acycle0,
uint32_t *acycle1234, bool rowonly);
static uint32_t nand_get_acycle(int ncycles);
static void nand_nfc_cleale(struct sam_nandcs_s *priv,
uint8_t mode, uint32_t cmd1, uint32_t cmd2,
uint32_t coladdr, uint32_t rowaddr);
/* Interrupt Handling */
static void nand_wait_cmddone(struct sam_nandcs_s *priv);
static void nand_setup_cmddone(struct sam_nandcs_s *priv);
static void nand_wait_xfrdone(struct sam_nandcs_s *priv);
static void nand_setup_xfrdone(struct sam_nandcs_s *priv);
static void nand_wait_rbedge(struct sam_nandcs_s *priv);
static void nand_setup_rbedge(struct sam_nandcs_s *priv);
#if 0 /* Not used */
static void nand_wait_nfcbusy(struct sam_nandcs_s *priv);
#endif
static uint32_t nand_nfc_poll(void);
#ifdef CONFIG_SAMA5_NAND_HSMCINTERRUPTS
static int hsmc_interrupt(int irq, void *context);
#endif
/* DMA Helpers */
#ifdef CONFIG_SAMA5_NAND_DMA
#ifdef CONFIG_SAMA5_NAND_DMADEBUG
static void nand_dma_sampleinit(struct sam_nandcs_s *priv);
# define nand_dma_sample(p,i) sam_dmasample((p)->dma, &(p)->dmaregs[i])
static void nand_dma_sampledone(struct sam_nandcs_s *priv, int result);
#else
# define nand_dma_sampleinit(p)
# define nand_dma_sample(p,i)
# define nand_dma_sampledone(p,r)
#endif
static int nand_wait_dma(struct sam_nandcs_s *priv);
static void nand_dmacallback(DMA_HANDLE handle, void *arg, int result);
static int nand_dma_read(struct sam_nandcs_s *priv,
uintptr_t vsrc, uintptr_t vdest, size_t nbytes,
uint32_t dmaflags);
static int nand_dma_write(struct sam_nandcs_s *priv,
uintptr_t vsrc, uintptr_t vdest, size_t nbytes,
uint32_t dmaflags);
#endif
/* Raw Data Transfer Helpers */
static int nand_nfcsram_read(struct sam_nandcs_s *priv,
uint8_t *buffer, uint16_t buflen, uint16_t offset);
#ifdef CONFIG_SAMA5_HAVE_PMECC
static int nand_read(struct sam_nandcs_s *priv, uint8_t *buffer,
uint16_t buflen);
#endif
#ifdef CONFIG_SAMA5_HAVE_PMECC
static int nand_read_pmecc(struct sam_nandcs_s *priv, off_t block,
unsigned int page, void *data);
#endif
static int nand_nfcsram_write(struct sam_nandcs_s *priv,
uint8_t *buffer, uint16_t buflen, uint16_t offset);
static int nand_write(struct sam_nandcs_s *priv, uint8_t *buffer,
uint16_t buflen, uint16_t offset);
/* NAND Access Helpers */
static int nand_readpage_noecc(struct sam_nandcs_s *priv, off_t block,
unsigned int page, void *data, void *spare);
#ifdef CONFIG_SAMA5_HAVE_PMECC
static int nand_readpage_pmecc(struct sam_nandcs_s *priv, off_t block,
unsigned int page, void *data);
#endif
static int nand_writepage_noecc(struct sam_nandcs_s *priv, off_t block,
unsigned int page, const void *data, const void *spare);
#ifdef CONFIG_SAMA5_HAVE_PMECC
static int nand_writepage_pmecc(struct sam_nandcs_s *priv, off_t block,
unsigned int page, const void *data);
#endif
/* MTD driver methods */
static int nand_eraseblock(struct nand_raw_s *raw, off_t block);
static int nand_rawread(struct nand_raw_s *raw, off_t block,
unsigned int page, void *data, void *spare);
static int nand_rawwrite(struct nand_raw_s *raw, off_t block,
unsigned int page, const void *data, const void *spare);
#ifdef CONFIG_MTD_NAND_HWECC
static int nand_readpage(struct nand_raw_s *raw, off_t block,
unsigned int page, void *data, void *spare);
static int nand_writepage(struct nand_raw_s *raw, off_t block,
unsigned int page, const void *data, const void *spare);
#endif
/* Initialization */
static void nand_reset(struct sam_nandcs_s *priv);
/****************************************************************************
* Private Data
****************************************************************************/
/* These pre-allocated structures hold the state of the MTD driver for NAND
* on CS0..3 as configured.
*/
#ifdef CONFIG_SAMA5_EBICS0_NAND
static struct sam_nandcs_s g_cs0nand;
#endif
#ifdef CONFIG_SAMA5_EBICS1_NAND
static struct sam_nandcs_s g_cs1nand;
#endif
#ifdef CONFIG_SAMA5_EBICS2_NAND
static struct sam_nandcs_s g_cs2nand;
#endif
#ifdef CONFIG_SAMA5_EBICS3_NAND
static struct sam_nandcs_s g_cs3nand;
#endif
/****************************************************************************
* Public Data
****************************************************************************/
/* NAND global state */
struct sam_nand_s g_nand;
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: nand_lock
*
* Description:
* Get exclusive access to PMECC hardware
*
* Input Parameters:
* None
*
* Returned Value:
* None
*
****************************************************************************/
#if NAND_NBANKS > 1
void nand_lock(void)
{
int ret;
do
{
ret = sem_wait(&g_nand.exclsem);
DEBUGASSERT(ret == OK || errno == EINTR);
}
while (ret != OK);
}
#endif
/****************************************************************************
* Name: nand_unlock
*
* Description:
* Relinquish exclusive access to PMECC hardware
*
* Input Parameters:
* None
*
* Returned Value:
* None
*
****************************************************************************/
#if NAND_NBANKS > 1
void nand_unlock(void)
{
sem_post(&g_nand.exclsem);
}
#endif
/****************************************************************************
* Name: nand_wait_ready
*
* Description:
* Waiting for the completion of a page program, erase and random read
* completion.
*
* Input parameters:
* priv Pointer to a sam_nandcs_s instance.
*
* Returned value.
* None
*
****************************************************************************/
static void nand_wait_ready(struct sam_nandcs_s *priv)
{
#ifdef SAMA5_NAND_READYBUSY
while (board_nand_busy(priv->cs));
#endif
nand_nfc_cleale(priv, 0, COMMAND_STATUS, 0, 0, 0);
while ((READ_DATA8(&priv->raw) & STATUS_READY) == 0);
}
/****************************************************************************
* Name: nand_nfc_cmdsend
*
* Description:
* Use the HOST NAND FLASH controller to send a command to the NFC.
*
* Input parameters:
* priv - Lower-half, private NAND FLASH device state
* cmd - command to send
* acycle - address cycle when command access id decoded
* cycle0 - address at first cycle
*
* Returned value.
* None
*
****************************************************************************/
static void nand_nfc_cmdsend(struct sam_nandcs_s *priv, uint32_t cmd,
uint32_t acycle, uint32_t cycle0)
{
uintptr_t cmdaddr;
/* Wait until host controller is not busy. */
while ((nand_getreg(NFCCMD_BASE + NFCADDR_CMD_NFCCMD) & 0x08000000) != 0);
nand_setup_cmddone(priv);
/* Send the command plus the ADDR_CYCLE */
cmdaddr = NFCCMD_BASE + cmd;
nand_putreg(SAM_HSMC_ADDR, cycle0);
nand_putreg(cmdaddr, acycle);
/* Wait for the command transfer to complete */
nand_wait_cmddone(priv);
}
/****************************************************************************
* Name: nand_operation_complete
*
* Description:
* Check if a program or erase operation completed successfully
*
* Input parameters:
* priv - Lower-half, private NAND FLASH device state
*
* Returned value.
* OK on success, a negated errnor value on failure
*
****************************************************************************/
static int nand_operation_complete(struct sam_nandcs_s *priv)
{
uint8_t status;
nand_nfc_cleale(priv, 0, COMMAND_STATUS, 0, 0, 0);
status = READ_DATA8(&priv->raw);
/* On successful completion, the NAND will be READY with no ERROR conditions */
if ((status & STATUS_ERROR) != 0)
{
return -EIO;
}
else if ((status & STATUS_READY) == 0)
{
return -EBUSY;
}
return OK;
}
/****************************************************************************
* Name: nand_translate_address
*
* Description:
* Translates the given column and row address into first and other (1-4)
* address cycles. The resulting values are stored in the provided
* variables if they are not null.
*
* Input parameters:
* priv - Lower-half, private NAND FLASH device state
* coladdr - Column address to translate.
* rowaddr - Row address to translate.
* acycle0 - First address cycle
* acycle1234 - Four address cycles.
* rowonly - True:Only ROW address is used.
*
* Returned value.
* Number of address cycles converted.
*
****************************************************************************/
static int nand_translate_address(struct sam_nandcs_s *priv,
uint16_t coladdr, uint32_t rowaddr,
uint32_t *acycle0, uint32_t *acycle1234,
bool rowonly)
{
uint16_t maxsize;
uint32_t maxpage;
uint32_t accum0;
uint32_t accum1234;
uint8_t bytes[8];
int ncycles;
int ndx;
int pos;
/* Setup */
maxsize = nandmodel_getpagesize(&priv->raw.model) +
nandmodel_getsparesize(&priv->raw.model) - 1;
maxpage = nandmodel_getdevpagesize(&priv->raw.model) - 1;
ncycles = 0;
accum0 = 0;
accum1234 = 0;
/* Check the data bus width of the NAND FLASH */
if (nandmodel_getbuswidth(&priv->raw.model) == 16)
{
/* Use word vs. bytes addressing */
coladdr >>= 1;
}
/* Convert column address */
if (!rowonly)
{
/* Send single column address byte for small block devices, or two
* column address bytes for large block devices
*/
while (maxsize > 2)
{
bytes[ncycles++] = coladdr & 0xff;
maxsize >>= 8;
coladdr >>= 8;
}
}
/* Convert row address */
while (maxpage > 0)
{
bytes[ncycles++] = rowaddr & 0xff;
maxpage >>= 8;
rowaddr >>= 8;
}
/* Build acycle0 and acycle1234 */
ndx = 0;
/* If more than 4 cycles, acycle0 is used */
if (ncycles > 4)
{
for (pos = 0; ndx < ncycles - 4; ndx++)
{
accum0 += bytes[ndx] << pos;
pos += 8;
}
}
/* acycle1234 */
for (pos = 0; ndx < ncycles; ndx++)
{
accum1234 += bytes[ndx] << pos;
pos += 8;
}
/* Store values */
if (acycle0)
{
*acycle0 = accum0;
}
if (acycle1234)
{
*acycle1234 = accum1234;
}
return ncycles;
}
/****************************************************************************
* Name: nand_get_acycle
*
* Description:
* Map the number of address cycles the bit setting for the NFC command
*
* Input parameters:
* ncycles - Number of address cycles
*
* Returned value.
* NFC command value
*
****************************************************************************/
static uint32_t nand_get_acycle(int ncycles)
{
switch(ncycles)
{
case 1:
return NFCADDR_CMD_ACYCLE_ONE;
case 2:
return NFCADDR_CMD_ACYCLE_TWO;
case 3:
return NFCADDR_CMD_ACYCLE_THREE;
case 4:
return NFCADDR_CMD_ACYCLE_FOUR;
case 5:
return NFCADDR_CMD_ACYCLE_FIVE;
}
return 0;
}
/****************************************************************************
* Name: nand_nfc_cleale
*
* Description:
* Sends NAND CLE/ALE command.
*
* Input parameters:
* priv - Pointer to a sam_nandcs_s instance.
* mode - SMC ALE CLE mode parameter.
* cmd1 - First command to be sent.
* cmd2 - Second command to be sent.
* coladdr - Column address.
* rowaddr - Row address.
*
* Returned value.
* None
*
****************************************************************************/
static void nand_nfc_cleale(struct sam_nandcs_s *priv, uint8_t mode,
uint32_t cmd1, uint32_t cmd2,
uint32_t coladdr, uint32_t rowaddr)
{
uint32_t cmd;
uint32_t regval;
uint32_t rw;
uint32_t acycle;
uint32_t acycle0 = 0;
uint32_t acycle1234 = 0;
int ncycles;
if ((mode & HSMC_CLE_WRITE_EN) != 0)
{
rw = NFCADDR_CMD_NFCWR;
}
else
{
rw = NFCADDR_CMD_NFCRD;
}
if ((mode & HSMC_CLE_DATA_EN) != 0)
{
regval = NFCADDR_CMD_DATAEN;
}
else
{
regval = NFCADDR_CMD_DATADIS;
}
if (((mode & HSMC_ALE_COL_EN) != 0) || ((mode & HSMC_ALE_ROW_EN) != 0))
{
bool rowonly = ((mode & HSMC_ALE_COL_EN) == 0);
ncycles = nand_translate_address(priv, coladdr, rowaddr,
&acycle0, &acycle1234, rowonly);
acycle = nand_get_acycle(ncycles);
}
else
{
acycle = NFCADDR_CMD_ACYCLE_NONE;
}
cmd = (rw | regval | NFCADDR_CMD_CSID(priv->cs) | acycle |
(((mode & HSMC_CLE_VCMD2_EN) == HSMC_CLE_VCMD2_EN) ? NFCADDR_CMD_VCMD2 : 0) |
(cmd1 << NFCADDR_CMD_CMD1_SHIFT) | (cmd2 << NFCADDR_CMD_CMD2_SHIFT));
nand_nfc_cmdsend(priv, cmd, acycle1234, acycle0);
}
/****************************************************************************
* Name: nand_wait_cmddone
*
* Description:
* Wait for NFC command done
*
* Input parameters:
* priv - CS state structure instance
*
* Returned value.
* None
*
****************************************************************************/
static void nand_wait_cmddone(struct sam_nandcs_s *priv)
{
#ifdef CONFIG_SAMA5_NAND_HSMCINTERRUPTS
irqstate_t flags;
int ret;
/* Wait for the CMDDONE interrupt to occur */
flags = irqsave();
do
{
ret = sem_wait(&g_nand.waitsem);
if (ret < 0)
{
DEBUGASSERT(errno == EINTR);
}
}
while (!g_nand.cmddone);
/* CMDDONE received */
g_nand.cmddone = false;
irqrestore(flags);
#else
/* Poll for the CMDDONE event (latching other events as necessary) */
do
{
(void)nand_nfc_poll();
}
while (!g_nand.cmddone);
#endif
}
/****************************************************************************
* Name: nand_setup_cmddone
*
* Description:
* Setup to wait for CMDDONE event
*
* Input parameters:
* priv - CS state structure instance
*
* Returned value.
* None
*
****************************************************************************/
static void nand_setup_cmddone(struct sam_nandcs_s *priv)
{
#ifdef CONFIG_SAMA5_NAND_HSMCINTERRUPTS
irqstate_t flags;
/* Clear all pending interrupts. This must be done with interrupts
* enabled or we could lose interrupts.
*/
nand_getreg(SAM_HSMC_SR);
flags = irqsave();
/* Mark CMDDONE not received */
g_nand.cmddone = false;
/* Enable the CMDDONE interrupt */
nand_putreg(SAM_HSMC_IER, HSMC_NFCINT_CMDDONE);
irqrestore(flags);
#else
/* Just sample and clear any pending NFC status, then clear CMDDONE status */
(void)nand_nfc_poll();
g_nand.cmddone = false;
#endif
}
/****************************************************************************
* Name: nand_wait_xfrdone
*
* Description:
* Wait for a transfer to complete
*
* Input parameters:
* priv - CS state structure instance
*
* Returned value.
* None
*
****************************************************************************/
static void nand_wait_xfrdone(struct sam_nandcs_s *priv)
{
#ifdef CONFIG_SAMA5_NAND_HSMCINTERRUPTS
irqstate_t flags;
int ret;
/* Wait for the XFRDONE interrupt to occur */
flags = irqsave();
do
{
ret = sem_wait(&g_nand.waitsem);
if (ret < 0)
{
DEBUGASSERT(errno == EINTR);
}
}
while (!g_nand.xfrdone);
/* XFRDONE received */
g_nand.xfrdone = false;
irqrestore(flags);
#else
/* Poll for the XFRDONE event (latching other events as necessary) */
do
{
(void)nand_nfc_poll();
}
while (!g_nand.xfrdone);
#endif
}
/****************************************************************************
* Name: nand_setup_xfrdone
*
* Description:
* Setup to wait for XFDONE event
*
* Input parameters:
* priv - CS state structure instance
*
* Returned value.
* None
*
****************************************************************************/
static void nand_setup_xfrdone(struct sam_nandcs_s *priv)
{
#ifdef CONFIG_SAMA5_NAND_HSMCINTERRUPTS
irqstate_t flags;
/* Clear all pending interrupts. This must be done with interrupts
* enabled or we could lose interrupts.
*/
nand_getreg(SAM_HSMC_SR);
flags = irqsave();
/* Mark XFRDONE not received */
g_nand.xfrdone = false;
/* Enable the XFRDONE interrupt */
nand_putreg(SAM_HSMC_IER, HSMC_NFCINT_XFRDONE);
irqrestore(flags);
#else
/* Just sample and clear any pending NFC status, then clear XFRDONE status */
(void)nand_nfc_poll();
g_nand.xfrdone = false;
#endif
}
/****************************************************************************
* Name: nand_wait_rbedge
*
* Description:
* Wait for read/busy edge detection
*
* Input parameters:
* priv - CS state structure instance
*
* Returned value.
* None
*
****************************************************************************/
static void nand_wait_rbedge(struct sam_nandcs_s *priv)
{
#ifdef CONFIG_SAMA5_NAND_HSMCINTERRUPTS
irqstate_t flags;
int ret;
/* Wait for the RBEDGE0 interrupt to occur */
flags = irqsave();
do
{
ret = sem_wait(&g_nand.waitsem);
if (ret < 0)
{
DEBUGASSERT(errno == EINTR);
}
}
while (!g_nand.rbedge);
/* RBEDGE0 received */
g_nand.rbedge = false;
irqrestore(flags);
#else
/* Poll for the RBEDGE0 event (latching other events as necessary) */
do
{
(void)nand_nfc_poll();
}
while (!g_nand.rbedge);
#endif
}
/****************************************************************************
* Name: nand_setup_rbedge
*
* Description:
* Setup to wait for RBEDGE0 event
*
* Input parameters:
* priv - CS state structure instance
*
* Returned value.
* None
*
****************************************************************************/
static void nand_setup_rbedge(struct sam_nandcs_s *priv)
{
#ifdef CONFIG_SAMA5_NAND_HSMCINTERRUPTS
irqstate_t flags;
/* Clear all pending interrupts. This must be done with interrupts
* enabled or we could lose interrupts.
*/
nand_getreg(SAM_HSMC_SR);
flags = irqsave();
/* Mark RBEDGE0 not received */
g_nand.rbedge = false;
/* Enable the RBEDGE0 interrupt */
nand_putreg(SAM_HSMC_IER, HSMC_NFCINT_RBEDGE0);
irqrestore(flags);
#else
/* Just sample and clear any pending NFC status, then clear RBEDGE0 status */
(void)nand_nfc_poll();
g_nand.rbedge = false;
#endif
}
/****************************************************************************
* Name: nand_wait_nfcbusy
*
* Description:
* Wait for NFC not busy
*
* Input parameters:
* priv - CS state structure instance
*
* Returned value.
* None
*
****************************************************************************/
#if 0 /* Not used */
static void nand_wait_nfcbusy(struct sam_nandcs_s *priv)
{
uint32_t sr;
/* Poll for the NFC not busy state (latching other events as necessary) */
do
{
sr = nand_nfc_poll();
}
while ((sr & HSMC_SR_NFCBUSY) != 0);
}
#endif
/****************************************************************************
* Name: nand_nfc_poll
*
* Description:
* Sample, latch, and return NFC status. Some pending status is cleared.
* This latching capability function is needed to prevent loss of pending
* status when sampling the HSMC_SR register.
*
* Input parameters:
* None
*
* Returned value.
* Current HSMC_SR register value;
*
****************************************************************************/
static uint32_t nand_nfc_poll(void)
{
uint32_t sr;
#ifdef CONFIG_SAMA5_NAND_HSMCINTERRUPTS
irqstate_t flags;
/* Disable interrupts while we sample NFS status as this may be done from
* the interrupt level as well.
*/
flags = irqsave();
#endif
/* Read the current HSMC status, clearing most pending conditions */
sr = nand_getreg(SAM_HSMC_SR);
#ifndef CONFIG_SAMA5_NAND_REGDEBUG
// fllvdbg("sr=%08x\n", sr);
#endif
/* When set to one, this XFRDONE indicates that the NFC has terminated
* the data transfer. This flag is reset after the status read.
*/
if ((sr & HSMC_NFCINT_XFRDONE) != 0)
{
/* Set the latching XFRDONE status */
g_nand.xfrdone = true;
}
/* When set to one, the CMDDONE flag indicates that the NFC has terminated
* the Command. This flag is reset after the status read.
*/
if ((sr & HSMC_NFCINT_CMDDONE) != 0)
{
/* Set the latching CMDDONE status */
g_nand.cmddone = true;
}
/* If set to one, the RBEDGE0 flag indicates that an edge has been detected
* on the Ready/Busy Line x. Depending on the EDGE CTRL field located in the
* SMC_CFG register, only rising or falling edge is detected. This flag is
* reset after the status read.
*/
if ((sr & HSMC_NFCINT_RBEDGE0) != 0)
{
/* Set the latching RBEDGE0 status */
g_nand.rbedge = true;
}
#ifdef CONFIG_SAMA5_NAND_HSMCINTERRUPTS
irqrestore(flags);
#endif
return sr;
}
/****************************************************************************
* Name: hsmc_interrupt
*
* Description:
* HSMC interrupt handler
*
* Input parameters:
* Standard interrupt arguments
*
* Returned value.
* Always returns OK
*
****************************************************************************/
#ifdef CONFIG_SAMA5_NAND_HSMCINTERRUPTS
static int hsmc_interrupt(int irq, void *context)
{
uint32_t sr = nand_nfc_poll();
uint32_t imr = nand_getreg(SAM_HSMC_IMR);
uint32_t pending = sr & imr;
#ifndef CONFIG_SAMA5_NAND_REGDEBUG
fllvdbg("sr=%08x imr=%08x\n", sr, imr);
#endif
/* When set to one, this XFRDONE indicates that the NFC has terminated
* the data transfer. This flag is reset after the status read.
*/
if ((g_nand.xfrdone && (imr & HSMC_NFCINT_XFRDONE) != 0)
{
/* Post the XFRDONE event */
sem_post(&g_nand.waitsem);
/* Disable further XFRDONE interrupts */
nand_putreg(SAM_HSMC_IDR, HSMC_NFCINT_XFRDONE);
}
/* When set to one, the CMDDONE flag indicates that the NFC has terminated
* the Command. This flag is reset after the status read.
*/
if (g_nand.xfrdone && (imr & HSMC_NFCINT_CMDDONE) != 0)
{
/* Post the CMDDONE event */
sem_post(&g_nand.waitsem);
/* Disable further CMDDONE interrupts */
nand_putreg(SAM_HSMC_IDR, HSMC_NFCINT_CMDDONE);
}
/* If set to one, the RBEDGE0 flag indicates that an edge has been detected
* on the Ready/Busy Line x. Depending on the EDGE CTRL field located in the
* SMC_CFG register, only rising or falling edge is detected. This flag is
* reset after the status read.
*/
if (g_nand.rbedge && (imr & HSMC_NFCINT_RBEDGE0) != 0)
{
/* Post the RBEDGE0 event */
sem_post(&g_nand.waitsem);
/* Disable further RBEDGE0 interrupts */
nand_putreg(SAM_HSMC_IDR, HSMC_NFCINT_RBEDGE0);
}
return OK;
}
#endif /* CONFIG_SAMA5_NAND_HSMCINTERRUPTS */
/****************************************************************************
* Name: nand_dma_sampleinit
*
* Description:
* Initialize sampling of DMA registers (if CONFIG_SAMA5_NAND_DMADEBUG)
*
* Input Parameters:
* priv - Lower-half, private NAND FLASH device state
*
* Returned Value:
* None
*
****************************************************************************/
#ifdef CONFIG_SAMA5_NAND_DMADEBUG
static void nand_dma_sampleinit(struct sam_nandcs_s *priv)
{
/* Put contents of register samples into a known state */
memset(priv->dmaregs, 0xff, DMA_NSAMPLES * sizeof(struct sam_dmaregs_s));
/* Then get the initial samples */
sam_dmasample(priv->dma, &priv->dmaregs[DMA_INITIAL]);
}
#endif
/****************************************************************************
* Name: nand_dma_sampledone
*
* Description:
* Dump sampled RX DMA registers
*
* Input Parameters:
* priv - Lower-half, private NAND FLASH device state
*
* Returned Value:
* None
*
****************************************************************************/
#ifdef CONFIG_SAMA5_NAND_DMADEBUG
static void nand_dma_sampledone(struct sam_nandcs_s *priv, int result)
{
lldbg("result: %d\n", result);
/* Sample the final registers */
sam_dmasample(priv->dma, &priv->dmaregs[DMA_END_TRANSFER]);
/* Then dump the sampled DMA registers */
/* Initial register values */
sam_dmadump(priv->dma, &priv->dmaregs[DMA_INITIAL], "Initial Registers");
/* Register values after DMA setup */
sam_dmadump(priv->dma, &priv->dmaregs[DMA_AFTER_SETUP], "After DMA Setup");
/* Register values after DMA start */
sam_dmadump(priv->dma, &priv->dmaregs[DMA_AFTER_START], "After DMA Start");
/* Register values at the time of the TX and RX DMA callbacks
* -OR- DMA timeout.
*
* If the DMA timedout, then there will not be any RX DMA
* callback samples. There is probably no TX DMA callback
* samples either, but we don't know for sure.
*/
#if 0 /* No timeout */
if (result == -ETIMEDOUT || result == -EINTR)
{
sam_dmadump(priv->dma, &priv->dmaregs[DMA_TIMEOUT], "At DMA timeout");
}
else
#endif
{
sam_dmadump(priv->dma, &priv->dmaregs[DMA_CALLBACK], "At DMA callback");
}
sam_dmadump(priv->dma, &priv->dmaregs[DMA_END_TRANSFER], "At End-of-Transfer");
}
#endif
/****************************************************************************
* Name: nand_wait_dma
*
* Description:
* Wait for the completion of a DMA transfer
*
* Input parameters:
* Wait for read/busy edge detection
*
* Returned value.
* The result of the DMA. OK on success; a negated ernno value on failure.
*
****************************************************************************/
#ifdef CONFIG_SAMA5_NAND_DMA
static int nand_wait_dma(struct sam_nandcs_s *priv)
{
int ret;
while (!priv->dmadone)
{
ret = sem_wait(&priv->waitsem);
if (ret < 0)
{
DEBUGASSERT(errno == EINTR);
}
}
fvdbg("Awakened: result=%d\n", priv->result);
priv->dmadone = false;
return priv->result;
}
#endif
/****************************************************************************
* Name: sam_adc_dmacallback
*
* Description:
* Called when one NAND DMA sequence completes. This function just wakes
* the the waiting NAND driver logic.
*
****************************************************************************/
#ifdef CONFIG_SAMA5_NAND_DMA
static void nand_dmacallback(DMA_HANDLE handle, void *arg, int result)
{
struct sam_nandcs_s *priv = (struct sam_nandcs_s *)arg;
DEBUGASSERT(priv);
nand_dma_sample(priv, DMA_CALLBACK);
/* Wake up the thread that is waiting for the DMA result */
priv->result = result;
priv->dmadone = true;
sem_post(&priv->waitsem);
}
#endif
/****************************************************************************
* Name: nand_dma_read
*
* Description:
* Transfer data to NAND from the provided buffer via DMA.
*
* Input Parameters:
* priv - Lower-half, private NAND FLASH device state
* vsrc - NAND data destination address.
* vdest - Buffer where data read from NAND will be returned.
* nbytes - The number of bytes to transfer
* dmaflags - Describes the DMA configuration
*
* Returned Value
* OK on success; a negated errno value on failure.
*
****************************************************************************/
#ifdef CONFIG_SAMA5_NAND_DMA
static int nand_dma_read(struct sam_nandcs_s *priv,
uintptr_t vsrc, uintptr_t vdest, size_t nbytes,
uint32_t dmaflags)
{
uint32_t psrc;
uint32_t pdest;
int ret;
DEBUGASSERT(priv->dma);
fvdbg("vsrc=%08x vdest=%08x nbytes=%d\n",
(int)vsrc, (int)vdest, (int)nbytes);
/* Initialize sampling */
nand_dma_sampleinit(priv);
/* Invalidate the destination memory buffer before performing the DMA (so
* that nothing gets flushed later, corrupting the DMA transfer, and so
* that memory will be re-cached after the DMA completes).
*/
arch_invalidate_dcache(vdest, vdest + nbytes);
/* DMA will need physical addresses. */
psrc = sam_physregaddr(vsrc); /* Source is NAND */
pdest = sam_physramaddr(vdest); /* Destination is normal memory */
/* Configure the DMA: 8- vs 16-bit, NFC SRAM or NAND */
sam_dmaconfig(priv->dma, dmaflags);
/* Setup the Memory-to-Memory DMA. The semantics of the DMA module are
* awkward here. We will treat the NAND (src) as the peripheral source
* and memory as the destination. Internally, the DMA module will realize
* that this is a memory to memory transfer and should do the right thing.
*/
ret = sam_dmarxsetup(priv->dma, psrc, pdest, nbytes);
if (ret < 0)
{
fdbg("ERROR: sam_dmarxsetup failed: %d\n", ret);
return ret;
}
nand_dma_sample(priv, DMA_AFTER_SETUP);
/* Start the DMA */
priv->dmadone = false;
priv->result = -EBUSY;
sam_dmastart(priv->dma, nand_dmacallback, priv);
nand_dma_sample(priv, DMA_AFTER_START);
/* Wait for the DMA to complete */
ret = nand_wait_dma(priv);
if (ret < 0)
{
fdbg("ERROR: DMA failed: %d\n", ret);
}
nand_dma_sample(priv, DMA_END_TRANSFER);
nand_dma_sampledone(priv, ret);
return ret;
}
#endif
/****************************************************************************
* Name: nand_dma_write
*
* Description:
* Transfer data to NAND from the provided buffer via DMA.
*
* Input Parameters:
* priv - Lower-half, private NAND FLASH device state
* vsrc - Buffer that provides the data for the write
* vdest - NAND data destination address
* nbytes - The number of bytes to transfer
* dmaflags - Describes the DMA configuration
*
* Returned Value
* OK on success; a negated errno value on failure.
*
****************************************************************************/
#ifdef CONFIG_SAMA5_NAND_DMA
static int nand_dma_write(struct sam_nandcs_s *priv,
uintptr_t vsrc, uintptr_t vdest, size_t nbytes,
uint32_t dmaflags)
{
uint32_t psrc;
uint32_t pdest;
int ret;
DEBUGASSERT(priv->dma);
/* Initialize sampling */
nand_dma_sampleinit(priv);
/* Clean the D-Cache associated with the source data buffer so that all of
* the data to be transferred lies in physical memory
*/
arch_clean_dcache(vsrc, vsrc + nbytes);
/* DMA will need physical addresses. */
psrc = sam_physramaddr(vsrc); /* Source is normal memory */
pdest = sam_physregaddr(vdest); /* Destination is NAND (or NAND host SRAM) */
/* Configure the DMA: 8- vs 16-bit, NFC SRAM or NAND */
sam_dmaconfig(priv->dma, dmaflags);
/* Setup the Memory-to-Memory DMA. The semantics of the DMA module are
* awkward here. We will treat the NAND (dest) as the peripheral destination
* and memory as the source. Internally, the DMA module will realize taht
* this is a memory to memory transfer and should do the right thing.
*/
ret = sam_dmatxsetup(priv->dma, pdest, psrc, nbytes);
if (ret < 0)
{
fdbg("ERROR: sam_dmatxsetup failed: %d\n", ret);
return ret;
}
nand_dma_sample(priv, DMA_AFTER_SETUP);
/* Start the DMA */
priv->dmadone = false;
priv->result = -EBUSY;
sam_dmastart(priv->dma, nand_dmacallback, priv);
nand_dma_sample(priv, DMA_AFTER_START);
/* Wait for the DMA to complete */
ret = nand_wait_dma(priv);
if (ret < 0)
{
fdbg("ERROR: DMA failed: %d\n", ret);
}
nand_dma_sample(priv, DMA_END_TRANSFER);
nand_dma_sampledone(priv, ret);
return ret;
}
#endif
/****************************************************************************
* Name: nand_nfcsram_read
*
* Description:
* Read data from NAND using the NFC SRAM
*
* Input Parameters:
* priv - Lower-half, private NAND FLASH device state
* buffer - Buffer that provides the data for the write
* buflen - The amount of data to read into the buffer
* offset - If reading from NFC SRAM, this is the offset into
* the SRAM.
*
* Returned Value
* OK on success; a negated errno value on failure.
*
****************************************************************************/
static int nand_nfcsram_read(struct sam_nandcs_s *priv, uint8_t *buffer,
uint16_t buflen, uint16_t offset)
{
uintptr_t src;
int remaining;
int ret;
fvdbg("buffer=%p buflen=%d\n", buffer, buflen);
/* Get the offset data source address */
src = NFCSRAM_BASE + (uintptr_t)offset;
#ifdef CONFIG_SAMA5_NAND_DMA
/* Then perform the transfer via memory-to-memory DMA or not, depending
* on if we have a DMA channel assigned and if the transfer is
* sufficiently large. Small DMAs (e.g., for spare data) are not performed
* because the DMA context switch can take more time that the DMA itself.
*/
if (priv->dma && buflen > CONFIG_SAMA5_NAND_DMA_THRESHOLD)
{
DEBUGASSERT(((uintptr_t)buffer & 3) == 0 && ((uintptr_t)src & 3) == 0);
/* Transfer using DMA */
ret = nand_dma_read(priv, src, (uintptr_t)buffer, buflen, NFCSRAM_DMA_FLAGS);
}
else
#endif
/* Transfer without DMA */
{
uint8_t *src8 = (uint8_t *)src;
uint8_t *dest8 = buffer;
for (remaining = buflen; remaining > 0; remaining--)
{
*dest8++ = *src8++;
}
ret = OK;
}
nand_dump("NFS SRAM Read", buffer, buflen);
return ret;
}
/****************************************************************************
* Name: nand_read
*
* Description:
* Read data directly from the NAND data address. Currently this only used
* by the PMECC logic which I could not get working if I read from NFC SRAM.
*
* Input Parameters:
* priv - Lower-half, private NAND FLASH device state
* nfcsram - True: Use NFC Host SRAM
* buffer - Buffer that provides the data for the write
*
* Returned Value
* OK on success; a negated errno value on failure.
*
****************************************************************************/
#ifdef CONFIG_SAMA5_HAVE_PMECC
static int nand_read(struct sam_nandcs_s *priv, uint8_t *buffer,
uint16_t buflen)
{
int remaining;
int buswidth;
int ret;
fvdbg("buffer=%p buflen=%d\n", buffer, (int)buflen);
/* Get the buswidth */
buswidth = nandmodel_getbuswidth(&priv->raw.model);
#ifdef CONFIG_SAMA5_NAND_DMA
/* Then perform the transfer via memory-to-memory DMA or not, depending
* on if we have a DMA channel assigned and if the transfer is
* sufficiently large. Small DMAs (e.g., for spare data) are not peformed
* because the DMA context switch can take more time that the DMA itself.
*/
if (priv->dma && buflen > CONFIG_SAMA5_NAND_DMA_THRESHOLD)
{
/* Select NFC DATA DMA */
uint32_t dmaflags =
(buswidth == 16 ? NAND_DMA_FLAGS16 : NAND_DMA_FLAGS8);
/* Transfer using DMA */
ret = nand_dma_read(priv, priv->raw.dataaddr, (uintptr_t)buffer,
buflen, dmaflags);
}
else
#endif
/* Transfer without DMA */
{
/* Check the data bus width of the NAND FLASH */
remaining = buflen;
if (buswidth == 16)
{
volatile uint16_t *src16 = (volatile uint16_t *)priv->raw.dataaddr;
uint16_t *dest16 = (uint16_t *)buffer;
DEBUGASSERT(((uintptr_t)buffer & 1) == 0);
for (; remaining > 1; remaining -= sizeof(uint16_t))
{
*dest16++ = *src16;
}
}
else
{
volatile uint8_t *src8 = (volatile uint8_t *)priv->raw.dataaddr;
uint8_t *dest8 = (uint8_t *)buffer;
for (; remaining > 0; remaining--)
{
*dest8++ = *src8;
}
}
ret = OK;
}
nand_dump("NAND Read", buffer, buflen);
return ret;
}
#endif
/****************************************************************************
* Name: nand_read_pmecc
*
* Description:
* Reads the data area of a page of a NAND FLASH into the provided buffer.
*
* Input parameters:
* priv - Lower-half, raw NAND FLASH interface
* block - Number of the block where the page to read resides.
* page - Number of the page to read inside the given block.
* data - Buffer where the data area will be stored.
*
* Returned value.
* OK is returned in succes; a negated errno value is returned on failure.
*
****************************************************************************/
#ifdef CONFIG_SAMA5_HAVE_PMECC
static int nand_read_pmecc(struct sam_nandcs_s *priv, off_t block,
unsigned int page, void *data)
{
uint32_t rowaddr;
uint32_t regval;
uint16_t pagesize;
uint16_t sparesize;
int ret;
fvdbg("block=%d page=%d data=%p\n", (int)block, page, data);
DEBUGASSERT(priv && data);
/* Get page and spare sizes */
pagesize = nandmodel_getpagesize(&priv->raw.model);
sparesize = nandmodel_getsparesize(&priv->raw.model);
/* Convert the page size to something understood by the hardware */
switch (pagesize)
{
case 512:
regval = HSMC_CFG_PAGESIZE_512;
break;
case 1024:
regval = HSMC_CFG_PAGESIZE_1024;
break;
case 2048:
regval = HSMC_CFG_PAGESIZE_2048;
break;
case 4096:
regval = HSMC_CFG_PAGESIZE_4096;
break;
case 8192:
regval = HSMC_CFG_PAGESIZE_8192;
break;
default:
fdbg("ERROR: Unsupported page size: %d\n", pagesize);
return -EINVAL;
}
/* Configure the SMC */
regval |= (HSMC_CFG_RSPARE | HSMC_CFG_RBEDGE | HSMC_CFG_DTOCYC(15) |
HSMC_CFG_DTOMUL_1048576 |
HSMC_CFG_NFCSPARESIZE((sparesize - 1) >> 2));
nand_putreg(SAM_HSMC_CFG, regval);
/* Calculate actual address of the page */
rowaddr = block * nandmodel_pagesperblock(&priv->raw.model) + page;
/* Reset and enable the PMECC */
nand_putreg(SAM_HSMC_PMECCTRL, HSMC_PMECCTRL_RST);
nand_putreg(SAM_HSMC_PMECCTRL, HSMC_PMECCTRL_ENABLE);
regval = nand_getreg(SAM_HSMC_PMECCFG);
if ((regval & HSMC_PMECCFG_SPAREEN_MASK) == HSMC_PMECCFG_SPARE_DISABLE)
{
regval |= HSMC_PMECCFG_AUTO_ENABLE;
}
nand_putreg(SAM_HSMC_PMECCFG, regval);
/* Start the data phase and perform the transfer */
nand_putreg(SAM_HSMC_PMECCTRL, HSMC_PMECCTRL_DATA);
#if 0 /* Don't use NFC SRAM */
nand_nfc_cleale(priv,
HSMC_ALE_COL_EN | HSMC_ALE_ROW_EN | HSMC_CLE_VCMD2_EN | HSMC_CLE_DATA_EN,
COMMAND_READ_1, COMMAND_READ_2, 0, rowaddr);
#else
nand_setup_rbedge(priv);
nand_nfc_cleale(priv,
HSMC_ALE_COL_EN | HSMC_ALE_ROW_EN | HSMC_CLE_VCMD2_EN,
COMMAND_READ_1, COMMAND_READ_2, 0, rowaddr);
nand_wait_rbedge(priv);
#endif
/* Reset the PMECC module */
nand_putreg(SAM_HSMC_PMECCTRL, HSMC_PMECCTRL_RST);
/* Start a Data Phase */
nand_putreg(SAM_HSMC_PMECCTRL, HSMC_PMECCTRL_DATA);
/* Read the data area into the caller provided buffer (pagesize bytes).
* NOTE: NFC SRAM is not used. In that case, the wait for PMECC not
* busy below would hang.
*/
#if 0 /* Don't use NFC SRAM */
ret = nand_nfcsram_read(priv, (uint8_t *)data, pagesize, 0);
#else
ret = nand_read(priv, (uint8_t *)data, pagesize);
#endif
if (ret < 0)
{
fdbg("ERROR: nand_read for data region failed: %d\n", ret);
return ret;
}
/* Now read the spare area into priv->raw.spare (sparesize bytes). */
#if 0 /* Don't use NFC SRAM */
ret = nand_nfcsram_read(priv, priv->raw.spare, sparesize, pagesize);
#else
ret = nand_read(priv, priv->raw.spare, sparesize);
#endif
if (ret < 0)
{
fdbg("ERROR: nand_read for spare region failed: %d\n", ret);
return ret;
}
/* Wait until the kernel of the PMECC is not busy */
while ((nand_getreg(SAM_HSMC_PMECCSR) & HSMC_PMECCSR_BUSY) != 0);
return OK;
}
#endif
/****************************************************************************
* Name: nand_nfcsram_write
*
* Description:
* Write data to NAND using NAND NFC SRAM
*
* Input Parameters:
* priv - Lower-half, private NAND FLASH device state
* buffer - Buffer that provides the data for the write
* offset - Data offset in bytes
*
* Returned Value
* OK on success; a negated errno value on failure.
*
****************************************************************************/
static int nand_nfcsram_write(struct sam_nandcs_s *priv, uint8_t *buffer,
uint16_t buflen, uint16_t offset)
{
uintptr_t dest;
int ret;
fvdbg("buffer=%p buflen=%d offset=%d\n", buffer, buflen, offset);
nand_dump("NFC SRAM Write", buffer, buflen);
/* Apply the offset to the destination address */
dest = NFCSRAM_BASE + offset;
#ifdef CONFIG_SAMA5_NAND_DMA
/* Then perform the transfer via memory-to-memory DMA or not, depending
* on if we have a DMA channel assigned and if the transfer is
* sufficiently large. Small DMAs (e.g., for spare data) are not peformed
* because the DMA context switch can take more time that the DMA itself.
*/
if (priv->dma && buflen > CONFIG_SAMA5_NAND_DMA_THRESHOLD)
{
DEBUGASSERT(((uintptr_t)buffer & 3) == 0 && ((uintptr_t)dest & 3) == 0);
/* Transfer using DMA */
ret = nand_dma_write(priv, (uintptr_t)buffer, dest, buflen, NFCSRAM_DMA_FLAGS);
}
else
#endif
/* Transfer without DMA */
{
uint8_t *dest8 = (uint8_t *)dest;
for (; buflen > 0; buflen--)
{
*dest8++ = *buffer++;
}
ret = OK;
}
return ret;
}
/****************************************************************************
* Name: nand_write
*
* Description:
* Write data to NAND using the NAND data address.
*
* Input Parameters:
* priv - Lower-half, private NAND FLASH device state
* buffer - Buffer that provides the data for the write
* offset - Data offset in bytes
*
* Returned Value
* OK on success; a negated errno value on failure.
*
****************************************************************************/
static int nand_write(struct sam_nandcs_s *priv, uint8_t *buffer,
uint16_t buflen, uint16_t offset)
{
uintptr_t dest;
int buswidth;
int ret;
fvdbg("buffer=%p buflen=%d offset=%d\n", buffer, buflen, offset);
nand_dump("NAND Write", buffer, buflen);
/* Apply the offset to the destination address */
dest = priv->raw.dataaddr + offset;
/* Get the buswidth */
buswidth = nandmodel_getbuswidth(&priv->raw.model);
#ifdef CONFIG_SAMA5_NAND_DMA
/* Then perform the transfer via memory-to-memory DMA or not, depending
* on if we have a DMA channel assigned and if the transfer is
* sufficiently large. Small DMAs (e.g., for spare data) are not peformed
* because the DMA context switch can take more time that the DMA itself.
*/
if (priv->dma && buflen > CONFIG_SAMA5_NAND_DMA_THRESHOLD)
{
/* Select NFC DATA DMA */
uint32_t dmaflags =
(buswidth == 16 ? NAND_DMA_FLAGS16 : NAND_DMA_FLAGS8);
/* Transfer using DMA */
ret = nand_dma_write(priv, (uintptr_t)buffer, dest, buflen, dmaflags);
}
else
#endif
/* Transfer without DMA */
{
/* Check the data bus width of the NAND FLASH */
if (buswidth == 16)
{
volatile uint16_t *dest16 = (volatile uint16_t *)dest;
const uint16_t *src16 = (const uint16_t *)buffer;
DEBUGASSERT(((uintptr_t)buffer & 1) == 0);
for (; buflen > 1; buflen -= sizeof(uint16_t))
{
*dest16 = *src16++;
}
}
else
{
volatile uint8_t *dest8 = (volatile uint8_t *)dest;
for (; buflen > 0; buflen--)
{
*dest8 = *buffer++;
}
}
ret = OK;
}
return ret;
}
/****************************************************************************
* Name: nand_readpage_noecc
*
* Description:
* Reads the data and/or the spare areas of a page of a NAND FLASH into the
* provided buffers. The raw NAND contents are returned with no ECC
* corrections.
*
* Input parameters:
* priv - Lower-half, private NAND FLASH device state
* block - Number of the block where the page to read resides.
* page - Number of the page to read inside the given block.
* data - Buffer where the data area will be stored.
* spare - Buffer where the spare area will be stored.
*
* Returned value.
* OK is returned in succes; a negated errno value is returned on failure.
*
****************************************************************************/
static int nand_readpage_noecc(struct sam_nandcs_s *priv, off_t block,
unsigned int page, void *data, void *spare)
{
uint32_t regval;
uint16_t pagesize;
uint16_t sparesize;
off_t rowaddr;
off_t coladdr;
int ret;
fvdbg("block=%d page=%d data=%p spare=%p\n", (int)block, page, data, spare);
DEBUGASSERT(priv && (data || spare));
/* Get page and spare sizes */
pagesize = nandmodel_getpagesize(&priv->raw.model);
sparesize = nandmodel_getsparesize(&priv->raw.model);
/* Convert the page size to something understood by the hardware */
switch (pagesize)
{
case 512:
regval = HSMC_CFG_PAGESIZE_512;
break;
case 1024:
regval = HSMC_CFG_PAGESIZE_1024;
break;
case 2048:
regval = HSMC_CFG_PAGESIZE_2048;
break;
case 4096:
regval = HSMC_CFG_PAGESIZE_4096;
break;
case 8192:
regval = HSMC_CFG_PAGESIZE_8192;
break;
default:
fdbg("ERROR: Unsupported page size: %d\n", pagesize);
return -EINVAL;
}
/* Configure the SMC */
regval |= (HSMC_CFG_RBEDGE | HSMC_CFG_DTOCYC(15) | HSMC_CFG_DTOMUL_1048576 |
HSMC_CFG_NFCSPARESIZE((sparesize -1 ) >> 2));
nand_putreg(SAM_HSMC_CFG, regval);
/* Calculate actual address of the page */
rowaddr = block * nandmodel_pagesperblock(&priv->raw.model) + page;
coladdr = data ? 0 : pagesize;
/* Initialize the NFC */
nand_setup_xfrdone(priv);
nand_nfc_cleale(priv,
HSMC_ALE_COL_EN | HSMC_ALE_ROW_EN | HSMC_CLE_VCMD2_EN | HSMC_CLE_DATA_EN,
COMMAND_READ_1, COMMAND_READ_2, coladdr, rowaddr);
nand_wait_xfrdone(priv);
/* Read data area if so requested */
if (data)
{
ret = nand_nfcsram_read(priv, (uint8_t *)data, pagesize, 0);
if (ret < 0)
{
fdbg("ERROR: nand_nfcsram_read for data region failed: %d\n", ret);
return ret;
}
}
/* Read the spare area if so requested. If there is no data, then the
* spare data will appear at offset 0; If there is data, thenthe spare data
* will appear followign the data at offset pagesize.
*/
if (spare)
{
uint16_t offset = data ? pagesize : 0;
ret = nand_nfcsram_read(priv, (uint8_t *)spare, sparesize, offset);
if (ret < 0)
{
fdbg("ERROR: nand_nfcsram_read for spare region failed: %d\n", ret);
return ret;
}
}
return OK;
}
/****************************************************************************
* Name: nand_readpage_pmecc
*
* Description:
* Reads the data and/or the spare areas of a page of a NAND FLASH into the
* provided buffers. PMECC is used
*
* Input parameters:
* priv - Lower-half, private NAND FLASH device state
* block - Number of the block where the page to read resides.
* page - Number of the page to read inside the given block.
* data - Buffer where the data area will be stored.
*
* Returned value.
* OK is returned in succes; a negated errno value is returned on failure.
*
****************************************************************************/
#ifdef CONFIG_SAMA5_HAVE_PMECC
static int nand_readpage_pmecc(struct sam_nandcs_s *priv, off_t block,
unsigned int page, void *data)
{
uint32_t regval;
uint16_t sparesize;
int ret;
int i;
fvdbg("block=%d page=%d data=%p\n", (int)block, page, data);
DEBUGASSERT(priv && data);
/* Make sure that we have exclusive access to the PMECC and that the PMECC
* is properly configured for this CS.
*/
pmecc_lock();
ret = pmecc_configure(priv, false);
if (ret < 0)
{
fdbg("ERROR: pmecc_configure failed: %d\n", ret);
goto errout;
}
/* Read page data into the user data buffer and spared data
* into the priv->raw.spare buffer.
*/
ret = nand_read_pmecc(priv, block, page, data);
if (ret < 0)
{
fdbg("ERROR: Block %d page %d Failed to read page\n",
block, page, ret);
goto errout;
}
/* Check if any sector is corrupted */
regval = nand_getreg(SAM_HSMC_PMECCISR);
if (regval)
{
/* Bad sectors. Check if this is because spare area has been erased */
/* First, re-read the spare area. REVISIT: Is this necessary? */
ret = nand_readpage_noecc(priv, block, page, NULL, priv->raw.spare);
if (ret < 0)
{
fdbg("ERROR: Block %d page %d Failed to re-read spare area: %d\n",
block, page, ret);
goto errout;
}
/* Then check if all bytes are in the erased state */
sparesize = nandmodel_getsparesize(&priv->raw.model);
for (i = 0 ; i < sparesize; i++)
{
if (priv->raw.spare[i] != 0xff)
{
break;
}
}
/* Has the spare area has been erased? */
if (i >= sparesize)
{
/* Yes.. clear sector errors */
fdbg("Block=%d page=%d has been erased: %08x\n",
block, page, regval);
regval = 0;
}
else
{
fdbg("ERROR: block=%d page=%d Corrupted sectors: %08x\n",
block, page, regval);
}
}
/* Bit correction will be done directly in destination buffer. */
ret = pmecc_correction(regval, (uintptr_t)data);
if (ret < 0)
{
fdbg("ERROR: block=%d page=%d Unrecoverable data error: %d\n",
block, page, ret);
}
/* Disable auto mode */
errout:
regval = nand_getreg(SAM_HSMC_PMECCFG);
regval &= ~HSMC_PMECCFG_AUTO_MASK;
nand_putreg(SAM_HSMC_PMECCFG, regval);
nand_putreg(SAM_HSMC_PMECCTRL, HSMC_PMECCTRL_DISABLE);
pmecc_unlock();
return ret;
}
#endif /* CONFIG_SAMA5_HAVE_PMECC */
/****************************************************************************
* Name: nand_writepage_noecc
*
* Description:
* Writes the data and/or the spare area of a page on a NAND FLASH chip.
* No ECC calculations are performed.
*
* Input parameters:
* priv - Lower-half, private NAND FLASH device state
* block - Number of the block where the page to write resides.
* page - Number of the page to write inside the given block.
* data - Buffer containing the data to be writting
* spare - Buffer conatining the spare data to be written.
*
* Returned value.
* OK is returned in succes; a negated errno value is returned on failure.
*
****************************************************************************/
static int nand_writepage_noecc(struct sam_nandcs_s *priv, off_t block,
unsigned int page, const void *data, const void *spare)
{
uint32_t regval;
uint16_t pagesize;
uint16_t sparesize;
off_t rowaddr;
int ret = OK;
fvdbg("block=%d page=%d data=%p spare=%p\n", (int)block, page, data, spare);
/* Get page and spare sizes */
pagesize = nandmodel_getpagesize(&priv->raw.model);
sparesize = nandmodel_getsparesize(&priv->raw.model);
/* Convert the page size to something understood by the hardware */
switch (pagesize)
{
case 512:
regval = HSMC_CFG_PAGESIZE_512;
break;
case 1024:
regval = HSMC_CFG_PAGESIZE_1024;
break;
case 2048:
regval = HSMC_CFG_PAGESIZE_2048;
break;
case 4096:
regval = HSMC_CFG_PAGESIZE_4096;
break;
case 8192:
regval = HSMC_CFG_PAGESIZE_8192;
break;
default:
fdbg("ERROR: Unsupported page size: %d\n", pagesize);
return -EINVAL;
}
/* Configure the SMC */
regval |= (HSMC_CFG_RBEDGE | HSMC_CFG_DTOCYC(15) | HSMC_CFG_DTOMUL_1048576 |
HSMC_CFG_NFCSPARESIZE((sparesize - 1) >> 2));
if (spare)
{
/* Write spare area */
regval |= HSMC_CFG_WSPARE;
}
nand_putreg(SAM_HSMC_CFG, regval);
/* Calculate physical address of the page */
rowaddr = block * nandmodel_pagesperblock(&priv->raw.model) + page;
/* Write the data and, if present, the spare bytes. */
if (data)
{
ret = nand_nfcsram_write(priv, (uint8_t *)data, pagesize, 0);
if (ret < 0)
{
fdbg("ERROR: nand_nfcsram_write for data region failed: %d\n", ret);
return ret;
}
if (spare)
{
ret = nand_nfcsram_write(priv, (uint8_t *)spare, sparesize, pagesize);
if (ret < 0)
{
fdbg("ERROR: nand_nfcsram_write for data region failed: %d\n", ret);
return ret;
}
}
}
/* Write data area if needed */
if (data)
{
/* Start a Data Phase */
nand_setup_xfrdone(priv);
nand_nfc_cleale(priv,
HSMC_CLE_WRITE_EN | HSMC_ALE_COL_EN |
HSMC_ALE_ROW_EN | HSMC_CLE_DATA_EN,
COMMAND_WRITE_1, 0, 0, rowaddr);
nand_wait_xfrdone(priv);
nand_setup_rbedge(priv);
nand_nfc_cleale(priv, HSMC_CLE_WRITE_EN, COMMAND_WRITE_2, 0, 0, 0);
nand_wait_rbedge(priv);
/* Check if the transfer completed successfully */
ret = nand_operation_complete(priv);
if (ret < 0)
{
fdbg("ERROR: Failed writing data area: %d\n", ret);
}
}
/* Write spare area alone if needed */
else if (spare)
{
nand_nfc_cleale(priv,
HSMC_CLE_WRITE_EN | HSMC_ALE_COL_EN | HSMC_ALE_ROW_EN,
COMMAND_WRITE_1,0, pagesize, rowaddr);
ret = nand_write(priv, (uint8_t *)spare, sparesize, 0);
if (ret < 0)
{
fdbg("ERROR: nand_write for spare region failed: %d\n", ret);
ret = -EPERM;
}
nand_nfc_cleale(priv, HSMC_CLE_WRITE_EN, COMMAND_WRITE_2, 0, 0, 0);
nand_wait_ready(priv);
}
return ret;
}
/****************************************************************************
* Name: nand_writepage_pmecc
*
* Description:
* Writes the data area of a NAND FLASH page, The PMECC module generates
* redundancy at encoding time. When a NAND write page operation is
* performed. The redundancy is appended to the page and written in the
* spare area.
*
* Input parameters:
* priv - Lower-half, private NAND FLASH device state
* block - Number of the block where the page to write resides.
* page - Number of the page to write inside the given block.
* data - Buffer containing the data to be writting
*
* Returned value.
* OK is returned in succes; a negated errno value is returned on failure.
*
****************************************************************************/
#ifdef CONFIG_SAMA5_HAVE_PMECC
static int nand_writepage_pmecc(struct sam_nandcs_s *priv, off_t block,
unsigned int page, const void *data)
{
uint32_t regval;
volatile uint8_t *pmecc;
uint8_t *ecc;
unsigned int pagesize;
unsigned int rowaddr;
unsigned int eccsaddr;
unsigned int eccpersector;
unsigned int sectersperpage;
unsigned int eccsize;
unsigned int sector;
unsigned int i;
int ret = 0;
fvdbg("block=%d page=%d data=%p\n", (int)block, page, data);
DEBUGASSERT(priv && data);
/* Make sure that we have exclusive access to the PMECC and that the PMECC
* is properly configured for this CS.
*/
pmecc_lock();
ret = pmecc_configure(priv, false);
if (ret < 0)
{
fdbg("ERROR: pmecc_configure failed: %d\n", ret);
goto errout;
}
/* Calculate the start page address */
regval = nand_getreg(SAM_HSMC_PMECCSADDR);
pagesize = nandmodel_getpagesize(&priv->raw.model);
eccsaddr = pagesize + regval;
/* Calculate physical address of the page */
rowaddr = block * nandmodel_pagesperblock(&priv->raw.model) + page;
fvdbg("pagesize=%d eccsaddr=%d rowaddr=%d\n", pagesize, eccsaddr, rowaddr);
#if 1 /* Use NFC SRAM */
/* Write the data area to NFC SRAM */
ret = nand_nfcsram_write(priv, (uint8_t *)data, pagesize, 0);
if (ret < 0)
{
fdbg("ERROR: Block %d page %d nand_nfcsram_write for data region failed: %d\n",
block, page, ret);
goto errout;
}
#endif
/* Get the encoded number of sectors per page */
switch (pmecc_get_pagesize())
{
case HSMC_PMECCFG_PAGESIZE_1SEC:
sectersperpage = 1;
break;
case HSMC_PMECCFG_PAGESIZE_2SEC:
sectersperpage = 2;
break;
case HSMC_PMECCFG_PAGESIZE_4SEC:
sectersperpage = 4;
break;
case HSMC_PMECCFG_PAGESIZE_8SEC:
sectersperpage = 8;
break;
default:
sectersperpage = 1;
break;
}
/* Reset and enable the PMECC */
nand_putreg(SAM_HSMC_PMECCTRL, HSMC_PMECCTRL_RST);
nand_putreg(SAM_HSMC_PMECCTRL, HSMC_PMECCTRL_ENABLE);
/* Start a data phase */
nand_putreg(SAM_HSMC_PMECCTRL, HSMC_PMECCTRL_DATA);
regval = nand_getreg(SAM_HSMC_PMECCFG);
regval |= HSMC_PMECCFG_NANDWR_WRITE;
nand_putreg(SAM_HSMC_PMECCFG, regval);
#if 1 /* Use NFC SRAM */
/* Setup the NFC and wait for the transfer to complete */
nand_setup_xfrdone(priv);
nand_nfc_cleale(priv,
HSMC_CLE_WRITE_EN | HSMC_ALE_COL_EN |
HSMC_ALE_ROW_EN | HSMC_CLE_DATA_EN,
COMMAND_WRITE_1, 0, 0, rowaddr);
nand_wait_xfrdone(priv);
#else
/* Setup the for the data transfer */
nand_nfc_cleale(priv,
HSMC_CLE_WRITE_EN | HSMC_ALE_COL_EN | HSMC_ALE_ROW_EN,
COMMAND_WRITE_1, 0, 0, rowaddr);
/* Transfer the data via the NAND */
ret = nand_write(priv, (uint8_t *)data, pagesize, 0);
if (ret < 0)
{
fdbg("ERROR: Block %d page %d nand_write for data region failed: %d\n",
block, page, ret);
goto errout;
}
#endif
/* Set up for the ECC transfer */
nand_nfc_cleale(priv, HSMC_CLE_WRITE_EN | HSMC_ALE_COL_EN,
COMMAND_RANDOM_IN, 0, eccsaddr, 0);
/* Wait until the kernel of the PMECC is not busy */
while ((nand_getreg(SAM_HSMC_PMECCSR) & HSMC_PMECCSR_BUSY) != 0);
/* Get the ECC values from the PMECC */
eccpersector = (pmecc_get_eccsize()) / sectersperpage;
eccsize = sectersperpage * eccpersector;
fvdbg("sectersperpage=%d eccpersector=%d eccsize=%d\n",
sectersperpage, eccpersector, eccsize);
#ifdef CONFIG_SAMA5_PMECC_TRIMPAGE
if (nand_trrimffs(priv) && page >= nand_get_trimpage(priv))
{
/* Comments in the Atmel sample say that this behavior was found to
* fix both UBI and JFFS2 images written to cleanly erased NAND
* partitions
*/
memset(g_nand.ecctab, 0xff, eccsize);
}
else
#endif
{
/* Read ECC registers for each sector in the page */
ecc = g_nand.ecctab;
for (sector = 0; sector < sectersperpage; sector++)
{
pmecc = (volatile uint8_t *)SAM_HSMC_PMECC_BASE(sector);
/* Read all EEC registers for this page */
for (i = 0; i < eccpersector; i++)
{
*ecc++ = *pmecc++;
}
}
}
/* Write the ECC to NAND */
ret = nand_write(priv, (uint8_t *)g_nand.ecctab, eccsize, 0);
if (ret < 0)
{
fdbg("ERROR: Block %d page %d nand_write for spare region failed: %d\n",
block, page, ret);
goto errout;
}
nand_nfc_cleale(priv, HSMC_CLE_WRITE_EN, COMMAND_WRITE_2, 0, 0, 0);
nand_wait_ready(priv);
/* Check for success */
ret = nand_operation_complete(priv);
if (ret < 0)
{
fdbg("ERROR: Block %d page %d Failed writing data area: %d\n",
block, page, ret);
}
/* Disable the PMECC */
errout:
nand_putreg(SAM_HSMC_PMECCTRL, HSMC_PMECCTRL_DISABLE);
pmecc_unlock();
return ret;
}
#endif /* CONFIG_SAMA5_HAVE_PMECC */
/****************************************************************************
* Name: nand_eraseblock
*
* Description:
* Erases the specified block of the device.
*
* Input parameters:
* raw - Lower-half, raw NAND FLASH interface
* block - Number of the physical block to erase.
*
* Returned value.
* OK is returned in succes; a negated errno value is returned on failure.
*
****************************************************************************/
static inline int nand_tryeraseblock(struct sam_nandcs_s *priv, off_t block)
{
uint32_t rowaddr;
int ret;
/* Calculate address used for erase */
rowaddr = block * nandmodel_pagesperblock(&priv->raw.model);
/* Configure the NFC for the block erase */
nand_nfc_cleale(priv, HSMC_CLE_VCMD2_EN | HSMC_ALE_ROW_EN,
COMMAND_ERASE_1, COMMAND_ERASE_2, 0, rowaddr);
/* Wait for the erase operation to complete */
nand_wait_ready(priv);
ret = nand_operation_complete(priv);
if (ret < 0)
{
fdbg("ERROR: Block %d Could not erase: %d\n", block, ret);
}
return ret;
}
static int nand_eraseblock(struct nand_raw_s *raw, off_t block)
{
struct sam_nandcs_s *priv = (struct sam_nandcs_s *)raw;
int retries = NAND_ERASE_NRETRIES;
int ret = OK;
DEBUGASSERT(priv);
fvdbg("block=%d\n", (int)block);
/* Get exclusvie access to the HSMC hardware.
* REVISIT: The scope of this exclusivity is just NAND.
*/
nand_lock();
/* Try up to NAND_ERASE_NRETRIES times to erase the FLASH */
while (retries > 0)
{
ret = nand_tryeraseblock(priv, block);
if (ret == OK)
{
nand_unlock();
return OK;
}
retries--;
}
fdbg("ERROR: Block %d Failed to erase after %d tries\n",
(int)block, NAND_ERASE_NRETRIES);
nand_unlock();
return -EAGAIN;
}
/****************************************************************************
* Name: nand_rawread
*
* Description:
* Reads the data and/or the spare areas of a page of a NAND FLASH into the
* provided buffers. This is a raw read of the flash contents.
*
* Input parameters:
* raw - Lower-half, raw NAND FLASH interface
* block - Number of the block where the page to read resides.
* page - Number of the page to read inside the given block.
* data - Buffer where the data area will be stored.
* spare - Buffer where the spare area will be stored.
*
* Returned value.
* OK is returned in succes; a negated errno value is returned on failure.
*
****************************************************************************/
static int nand_rawread(struct nand_raw_s *raw, off_t block,
unsigned int page, void *data, void *spare)
{
struct sam_nandcs_s *priv = (struct sam_nandcs_s *)raw;
int ret;
DEBUGASSERT(raw);
/* Get exclusvie access to the HSMC hardware.
* REVISIT: The scope of this exclusivity is just NAND.
*/
nand_lock();
ret = nand_readpage_noecc(priv, block, page, data, spare);
nand_unlock();
return ret;
}
/****************************************************************************
* Name: nand_rawwrite
*
* Description:
* Writes the data and/or the spare area of a page on a NAND FLASH chip.
* This is a raw write of the flash contents.
*
* Input parameters:
* raw - Lower-half, raw NAND FLASH interface
* block - Number of the block where the page to write resides.
* page - Number of the page to write inside the given block.
* data - Buffer containing the data to be writting
* spare - Buffer containing the spare data to be written.
*
* Returned value.
* OK is returned in succes; a negated errno value is returned on failure.
*
****************************************************************************/
static int nand_rawwrite(struct nand_raw_s *raw, off_t block,
unsigned int page, const void *data,
const void *spare)
{
struct sam_nandcs_s *priv = (struct sam_nandcs_s *)raw;
int ret;
DEBUGASSERT(raw);
/* Get exclusvie access to the HSMC hardware.
* REVISIT: The scope of this exclusivity is just NAND.
*/
nand_lock();
ret = nand_writepage_noecc(priv, block, page, data, spare);
nand_unlock();
return ret;
}
/****************************************************************************
* Name: nand_readpage
*
* Description:
* Reads the data and/or the spare areas of a page of a NAND FLASH into the
* provided buffers. Hardware ECC checking will be performed if so
* configured.
*
* Input parameters:
* raw - Lower-half, raw NAND FLASH interface
* block - Number of the block where the page to read resides.
* page - Number of the page to read inside the given block.
* data - Buffer where the data area will be stored.
* spare - Buffer where the spare area will be stored.
*
* Returned value.
* OK is returned in succes; a negated errno value is returned on failure.
*
****************************************************************************/
#ifdef CONFIG_MTD_NAND_HWECC
static int nand_readpage(struct nand_raw_s *raw, off_t block,
unsigned int page, void *data, void *spare)
{
struct sam_nandcs_s *priv = (struct sam_nandcs_s *)raw;
int ret;
DEBUGASSERT(raw);
/* Get exclusvie access to the HSMC hardware.
* REVISIT: The scope of this exclusivity is just NAND.
*/
nand_lock();
/* Read the page */
DEBUGASSERT(raw->ecctype != NANDECC_SWECC);
switch (raw->ecctype)
{
case NANDECC_NONE:
case NANDECC_CHIPECC:
ret = nand_readpage_noecc(priv, block, page, data, spare);
break;
#ifdef CONFIG_SAMA5_HAVE_PMECC
case NANDECC_PMECC:
DEBUGASSERT(!spare);
ret = nand_readpage_pmecc(priv, block, page, data);
break;
#endif
case NANDECC_SWECC:
default:
ret = -EINVAL;
break;
}
nand_unlock();
return ret;
}
#endif
/****************************************************************************
* Name: nand_writepage
*
* Description:
* Writes the data and/or the spare area of a page on a NAND FLASH chip.
* Hardware ECC checking will be performed if so configured.
*
* Input parameters:
* raw - Lower-half, raw NAND FLASH interface
* block - Number of the block where the page to write resides.
* page - Number of the page to write inside the given block.
* data - Buffer containing the data to be writting
* spare - Buffer conatining the spare data to be written.
*
* Returned value.
* OK is returned in succes; a negated errno value is returned on failure.
*
****************************************************************************/
#ifdef CONFIG_MTD_NAND_HWECC
static int nand_writepage(struct nand_raw_s *raw, off_t block,
unsigned int page, const void *data,
const void *spare)
{
struct sam_nandcs_s *priv = (struct sam_nandcs_s *)raw;
int ret;
DEBUGASSERT(raw);
/* Get exclusvie access to the HSMC hardware.
* REVISIT: The scope of this exclusivity is just NAND.
*/
nand_lock();
/* Write the page */
DEBUGASSERT(raw->ecctype != NANDECC_SWECC);
switch (raw->ecctype)
{
case NANDECC_NONE:
case NANDECC_CHIPECC:
ret = nand_writepage_noecc(priv, block, page, data, spare);
break;
#ifdef CONFIG_SAMA5_HAVE_PMECC
case NANDECC_PMECC:
DEBUGASSERT(!spare);
ret = nand_writepage_pmecc(priv, block, page, data);
break;
#endif
case NANDECC_SWECC:
default:
ret = -EINVAL;
break;
}
nand_unlock();
return ret;
}
#endif
/****************************************************************************
* Name: nand_reset
*
* Description:
* Resets a NAND FLASH device
*
* Input parameters:
* priv - Lower-half, private NAND FLASH device state
*
* Returned value.
* None
*
****************************************************************************/
static void nand_reset(struct sam_nandcs_s *priv)
{
fvdbg("Resetting\n");
nand_nfc_cleale(priv, 0, COMMAND_RESET, 0, 0, 0);
nand_wait_ready(priv);
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: sam_nand_initialize
*
* Description:
* Create and initialize an raw NAND device instance. This driver
* implements the RAW NAND interface: No software ECC or sparing is
* performed here. Those necessary NAND features are provided by common,
* higher level NAND MTD layers found in drivers/mtd.
*
* Input parameters:
* cs - Chip select number (in the event that multiple NAND devices
* are connected on-board).
*
* Returned value.
* On success a non-NULL pointer to an MTD device structure is returned;
* NULL is returned on a failure.
*
****************************************************************************/
struct mtd_dev_s *sam_nand_initialize(int cs)
{
struct sam_nandcs_s *priv;
struct mtd_dev_s *mtd;
uintptr_t cmdaddr;
uintptr_t addraddr;
uintptr_t dataaddr;
uint8_t ecctype;
int ret;
fvdbg("CS%d\n", cs);
/* Select the device structure (In SAMA5D3, NAND is only supported on CS3). */
#ifdef CONFIG_SAMA5_EBICS0_NAND
if (cs == HSMC_CS0)
{
/* Refer to the pre-allocated NAND device structure */
priv = &g_cs0nand;
/* Set up the NAND addresses. These must be provided in the board.h
* header file.
*/
cmdaddr = BOARD_EBICS0_NAND_CMDADDR;
addraddr = BOARD_EBICS0_NAND_ADDRADDR;
dataaddr = BOARD_EBICS0_NAND_DATAADDR;
/* Pass on the configured ECC type */
ecctype = SAMA5_EBICS0_ECCTYPE;
}
else
#endif
#ifdef CONFIG_SAMA5_EBICS1_NAND
if (cs == HSMC_CS1)
{
/* Refer to the pre-allocated NAND device structure */
priv = &g_cs1nand;
/* Set up the NAND addresses. These must be provided in the board.h
* header file.
*/
cmdaddr = BOARD_EBICS1_NAND_CMDADDR;
addraddr = BOARD_EBICS1_NAND_ADDRADDR;
dataaddr = BOARD_EBICS1_NAND_DATAADDR;
/* Pass on the configured ECC type */
ecctype = SAMA5_EBICS1_ECCTYPE;
}
else
#endif
#ifdef CONFIG_SAMA5_EBICS2_NAND
if (cs == HSMC_CS2)
{
/* Refer to the pre-allocated NAND device structure */
priv = &g_cs2nand;
/* Set up the NAND addresses. These must be provided in the board.h
* header file.
*/
cmdaddr = BOARD_EBICS2_NAND_CMDADDR;
addraddr = BOARD_EBICS2_NAND_ADDRADDR;
dataaddr = BOARD_EBICS2_NAND_DATAADDR;
/* Pass on the configured ECC type */
ecctype = SAMA5_EBICS2_ECCTYPE;
}
else
#endif
#ifdef CONFIG_SAMA5_EBICS3_NAND
if (cs == HSMC_CS3)
{
/* Refer to the pre-allocated NAND device structure */
priv = &g_cs3nand;
/* Set up the NAND addresses. These must be provided in the board.h
* header file.
*/
cmdaddr = BOARD_EBICS3_NAND_CMDADDR;
addraddr = BOARD_EBICS3_NAND_ADDRADDR;
dataaddr = BOARD_EBICS3_NAND_DATAADDR;
/* Pass on the configured ECC type */
ecctype = SAMA5_EBICS3_ECCTYPE;
}
else
#endif
{
fdbg("ERROR: CS%d unsupported or invalid\n", cs);
return NULL;
}
/* Initialize the device structure */
memset(priv, 0, sizeof(struct sam_nandcs_s));
priv->raw.cmdaddr = cmdaddr;
priv->raw.addraddr = addraddr;
priv->raw.dataaddr = dataaddr;
priv->raw.ecctype = ecctype;
priv->raw.eraseblock = nand_eraseblock;
priv->raw.rawread = nand_rawread;
priv->raw.rawwrite = nand_rawwrite;
#ifdef CONFIG_MTD_NAND_HWECC
priv->raw.readpage = nand_readpage;
priv->raw.writepage = nand_writepage;
#endif
priv->cs = cs;
#ifdef CONFIG_SAMA5_NAND_DMA
sem_init(&priv->waitsem, 0, 0);
#endif
/* Perform one-time, global NFC/PMECC initialization */
if (!g_nand.initialized)
{
/* Initialize the global nand state structure */
#if NAND_NBANKS > 1
sem_init(&g_nand.exclsem, 0, 1);
#endif
#ifdef CONFIG_SAMA5_NAND_HSMCINTERRUPTS
sem_init(&g_nand.waitsem, 0, 0);
#endif
/* Enable the NAND FLASH Controller (The NFC is always used) */
nand_putreg(SAM_HSMC_CTRL, HSMC_CTRL_NFCEN);
#ifdef CONFIG_SAMA5_HAVE_PMECC
/* Perform one-time initialization of the PMECC */
pmecc_initialize();
#else
/* Disable the PMECC if it is not being used */
nand_putreg(SAM_HSMC_PMECCTRL, HSMC_PMECCTRL_RST);
nand_putreg(SAM_HSMC_PMECCTRL, HSMC_PMECCTRL_DISABLE);
nand_putreg(SAM_HSMC_PMECCFG, 0);
#endif
#ifdef CONFIG_SAMA5_NAND_HSMCINTERRUPTS
/* Attach the CAN interrupt handler */
ret = irq_attach(SAM_IRQ_HSMC, hsmc_interrupt);
if (ret < 0)
{
fdbg("Failed to attach HSMC IRQ (%d)", SAM_IRQ_HSMC);
return NULL;
}
#endif
/* Disable all interrupts at the HSMC */
nand_putreg(SAM_HSMC_IDR, HSMC_NFCINT_ALL);
#ifdef CONFIG_SAMA5_NAND_HSMCINTERRUPTS
/* Enable the HSMC interrupts at the interrupt controller */
up_enable_irq(SAM_IRQ_HSMC);
g_nand.initialized = true;
#endif
}
/* Initialize the NAND hardware for this CS */
/* Perform board-specific SMC initialization for this CS. This should include:
*
* 1. Enabling of clocking to the HSMC
* 2. Configuration of timing for the HSMC NAND CS
* 3. Configuration of PIO pins
*
* Other than enabling the HSMC, these are all things that the board-cognizant
* logic is best prepared to handle.
*/
ret = board_nandflash_config(cs);
if (ret < 0)
{
fdbg("ERROR: board_nandflash_config failed for CS%d: %d\n",
cs, ret);
return NULL;
}
/* Reset the NAND FLASH part */
nand_reset(priv);
/* Probe the NAND part. On success, an MTD interface that wraps
* our raw NAND interface is returned.
*/
mtd = nand_initialize(&priv->raw);
if (!mtd)
{
fdbg("ERROR: CS%d nand_initialize failed %d\n", cs);
return NULL;
}
#ifdef CONFIG_SAMA5_NAND_DMA
/* Allocate a DMA channel for NAND transfers. The channels will be
* configured as needed on-the-fly. NOTE that no failure is declared
* if we fail to allocate DMA channel; in that case, only non-DMA
* transfers will be performed.
*/
priv->dma = sam_dmachannel(NAND_DMAC, 0);
if (!priv->dma)
{
fdbg("ERROR: Failed to allocate the DMA channel for CS%d\n", cs);
}
#endif
/* Return the MTD wrapper interface as the MTD device */
return mtd;
}
/****************************************************************************
* Name: nand_checkreg
*
* Description:
* Check if the current HSMC register access is a duplicate of the preceding.
*
* Input Parameters:
* regval - The value to be written
* regaddr - The address of the register to write to
*
* Returned Value:
* true: This is the first register access of this type.
* flase: This is the same as the preceding register access.
*
****************************************************************************/
#ifdef CONFIG_SAMA5_NAND_REGDEBUG
bool nand_checkreg(bool wr, uintptr_t regaddr, uint32_t regval)
{
if (wr == g_nand.wr && /* Same kind of access? */
regval == g_nand.regval && /* Same regval? */
regaddr == g_nand.regadddr) /* Same address? */
{
/* Yes, then just keep a count of the number of times we did this. */
g_nand.ntimes++;
return false;
}
else
{
/* Did we do the previous operation more than once? */
if (g_nand.ntimes > 0)
{
/* Yes... show how many times we did it */
lldbg("...[Repeats %d times]...\n", g_nand.ntimes);
}
/* Save information about the new access */
g_nand.wr = wr;
g_nand.regval = regval;
g_nand.regadddr = regaddr;
g_nand.ntimes = 0;
}
/* Return true if this is the first time that we have done this operation */
return true;
}
#endif