blob: cbcb39808c5cf0a7edad50f4dd80c023f8c4a7ce [file] [log] [blame]
/****************************************************************************
* arch/arm/src/sam34/sam4s_nand.c
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you 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.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <nuttx/mtd/nand_config.h>
#include <sys/types.h>
#include <inttypes.h>
#include <stdint.h>
#include <string.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 <nuttx/irq.h>
#include <arch/board/board.h>
#include "hardware/sam4s_pinmap.h"
#include "arm_internal.h"
#include "sam4s_nand.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* Nand flash chip status codes */
#define STATUS_ERROR (1 << 0)
#define STATUS_READY (1 << 6)
/* Number of tries for erasing or writing block */
#define NAND_ERASE_NRETRIES 2
#define NAND_WRITE_NRETRIES 2
/****************************************************************************
* Private Types
****************************************************************************/
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
/* Low-level HSMC Helpers */
#ifdef CONFIG_SAM34_NAND_DUMP
# define nand_dump(m,b,s) lib_dumpbuffer(m,b,s)
#else
# define nand_dump(m,b,s)
#endif
/* Raw Data Transfer Helpers */
static int nand_write(struct sam_nandcs_s *priv, uint32_t rowaddr,
uint32_t coladdr, uint8_t *buffer, uint16_t buflen,
uint16_t offset);
static int nand_read(struct sam_nandcs_s *priv, uint32_t rowaddr,
uint32_t coladdr, uint8_t *buffer, uint16_t buflen,
uint16_t offset);
/* 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);
/* 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_SAM34_NCS0_NAND
static struct sam_nandcs_s g_cs0nand;
#endif
/****************************************************************************
* Public Data
****************************************************************************/
/****************************************************************************
* Private Functions
****************************************************************************/
static void nand_reset(struct sam_nandcs_s *priv)
{
WRITE_COMMAND8(&priv->raw, COMMAND_RESET);
/* The device will be busy for a maximum of 1ms. */
up_mdelay(1);
}
/****************************************************************************
* 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 int nand_wait_ready(struct sam_nandcs_s *priv)
{
uint32_t timeout;
uint8_t status;
up_udelay(10);
/* The ready/busy (R/nB) signal of the NAND Flash */
while (!sam_gpioread(priv->rb))
{
}
WRITE_COMMAND8(&priv->raw, COMMAND_STATUS);
/* Issue command */
timeout = 0;
while (timeout < MAX_READ_STATUS_COUNT)
{
/* Read status byte */
status = READ_DATA8(&priv->raw);
/* Check status. If status bit 6 = 1 device is ready */
if ((status & STATUS_READY) == STATUS_READY)
{
/* If status bit 0 = 0 the last operation was successful */
if ((status & STATUS_ERROR) == 0)
{
return OK;
}
else
{
return -EIO;
}
}
timeout++;
}
return -ETIMEDOUT;
}
/****************************************************************************
* 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 success; 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 = OK;
/* Calculate address used for erase */
rowaddr = block * nandmodel_pagesperblock(&priv->raw.model);
WRITE_COMMAND8(&priv->raw, COMMAND_ERASE_1);
WRITE_ADDRESS8(&priv->raw, rowaddr); /* 3rd cycle row addr */
WRITE_ADDRESS8(&priv->raw, rowaddr >> 8); /* 4th cycle row addr */
WRITE_ADDRESS8(&priv->raw, rowaddr >> 16); /* 5st cycle row addr */
WRITE_COMMAND8(&priv->raw, COMMAND_ERASE_2);
ret = nand_wait_ready(priv);
if (ret < 0)
{
ferr("ERROR: Block %jd Could not erase: %d\n", (intmax_t)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);
finfo("block=%d\n", (int)block);
if (ret < 0)
{
return ret;
}
/* Try up to NAND_ERASE_NRETRIES times to erase the FLASH */
while (retries > 0)
{
ret = nand_tryeraseblock(priv, block);
if (ret == OK)
{
return OK;
}
retries--;
}
ferr("ERROR: Block %d Failed to erase after %d tries\n",
(int)block, NAND_ERASE_NRETRIES);
return -EAGAIN;
}
/****************************************************************************
* 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, uint32_t rowaddr,
uint32_t coladdr, uint8_t *buffer,
uint16_t buflen, uint16_t offset)
{
uintptr_t dest;
int ret = OK;
nand_dump("NAND Write", buffer, buflen);
dest = priv->raw.dataaddr + offset;
/* Apply the offset to the destination address */
WRITE_COMMAND8(&priv->raw, COMMAND_WRITE_1);
WRITE_ADDRESS8(&priv->raw, coladdr); /* 1st cycle column addr */
WRITE_ADDRESS8(&priv->raw, coladdr >> 8); /* 2nt cycle column addr */
WRITE_ADDRESS8(&priv->raw, rowaddr); /* 3rd cycle row addr */
WRITE_ADDRESS8(&priv->raw, rowaddr >> 8); /* 4th cycle row addr */
WRITE_ADDRESS8(&priv->raw, rowaddr >> 16); /* 5st cycle row addr */
volatile uint8_t *dest8 = (volatile uint8_t *)dest;
for (; buflen > 0; buflen--)
{
*dest8 = *buffer++;
}
WRITE_COMMAND8(&priv->raw, COMMAND_WRITE_2);
ret = nand_wait_ready(priv);
return ret;
}
static int nand_read(struct sam_nandcs_s *priv, uint32_t rowaddr,
uint32_t coladdr, uint8_t *buffer,
uint16_t buflen, uint16_t offset)
{
volatile uint8_t *src8 = (volatile uint8_t *)priv->raw.dataaddr + offset;
uint8_t *dest8 = (uint8_t *)buffer;
int remaining;
int ret = OK;
WRITE_COMMAND8(&priv->raw, COMMAND_READ_1);
WRITE_ADDRESS8(&priv->raw, coladdr); /* 1st cycle column addr */
WRITE_ADDRESS8(&priv->raw, coladdr >> 8); /* 2nt cycle column addr */
WRITE_ADDRESS8(&priv->raw, rowaddr); /* 3rd cycle row addr */
WRITE_ADDRESS8(&priv->raw, rowaddr >> 8); /* 4th cycle row addr */
WRITE_ADDRESS8(&priv->raw, rowaddr >> 16); /* 5st cycle row addr */
WRITE_COMMAND8(&priv->raw, COMMAND_READ_2);
up_udelay(10);
while (!sam_gpioread(priv->rb));
remaining = buflen;
for (; remaining > 0; remaining--)
{
*dest8++ = *src8;
}
nand_dump("NAND Read", buffer, buflen);
return ret;
}
/****************************************************************************
* 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 success; 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;
uint16_t pagesize;
uint16_t sparesize;
off_t rowaddr;
off_t coladdr;
int ret = OK;
DEBUGASSERT(priv && (data || spare));
/* Get page and spare sizes */
pagesize = nandmodel_getpagesize(&priv->raw.model);
sparesize = nandmodel_getsparesize(&priv->raw.model);
/* Calculate actual address of the page */
rowaddr = block * nandmodel_pagesperblock(&priv->raw.model) + page;
coladdr = data ? 0 : pagesize;
fwarn("block=%jd page=%d rowaddr=%jd coladdr %jd data=%p spare=%p\n",
(intmax_t)block, page, (intmax_t)rowaddr, (intmax_t)coladdr,
data, spare);
coladdr = (coladdr >> 8) & 0x4 ? coladdr & 0x83f : coladdr;
if (data)
{
ret = nand_read(priv, rowaddr, coladdr, (uint8_t *)data, pagesize, 0);
if (ret < 0)
{
ferr("ERROR: nand_nfcsram_read for data region failed: %d\n", ret);
return ret;
}
}
if (spare)
{
uint16_t offset = data ? pagesize : 0;
ret = nand_read(priv, rowaddr, coladdr, (uint8_t *)spare, sparesize,
offset);
if (ret < 0)
{
ferr("ERROR: nand_nfcsram_read for spare region failed: %d\n",
ret);
return ret;
}
}
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 writing
* spare - Buffer containing the spare data to be written.
*
* Returned Value:
* OK is returned in success; 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;
uint16_t pagesize;
uint16_t sparesize;
off_t rowaddr;
int ret = OK;
DEBUGASSERT(raw);
finfo("block=%" PRIdOFF " page=%d data=%p spare=%p\n",
block, page, data, spare);
/* Get page and spare sizes */
pagesize = nandmodel_getpagesize(&priv->raw.model);
sparesize = nandmodel_getsparesize(&priv->raw.model);
rowaddr = block * nandmodel_pagesperblock(&priv->raw.model) + page;
if (data)
{
ret = nand_write(priv, rowaddr, 0, (uint8_t *)data, pagesize, 0);
if (ret < 0)
{
ferr("ERROR: Failed writing data area: %d\n", ret);
}
}
if (spare)
{
ret = nand_write(priv, rowaddr, 0, (uint8_t *)spare, sparesize,
pagesize);
if (ret < 0)
{
ferr("ERROR: Failed writing data spare: %d\n", ret);
}
}
return ret;
}
/****************************************************************************
* 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;
finfo("CS%d\n", cs);
if (SAM_SMCCS_BASE(cs) == SAM_SMC_CS0_BASE)
{
/* 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_NCS0_NAND_CMDADDR;
addraddr = BOARD_NCS0_NAND_ADDRADDR;
dataaddr = BOARD_NCS0_NAND_DATAADDR;
/* Pass on the configured ECC type */
ecctype = SAM34_NCS0_ECCTYPE;
}
else
{
ferr("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;
priv->cs = cs;
priv->rb = GPIO_SMC_RB;
/* Initialize the NAND hardware for this CS
* Note: The initialization is shown for the reference purpose only, and
* for other MCUs, refer to the Package and Pinout chapter of the
* respective data sheet.
*
* The I/O pin initialization for the 8-bit NAND is connected to the NCS0:
*
* To initialize the 8-bit D0-D7 data bus, configure the Port C, PC0 to
* PC7 in Peripheral-A mode
*
* To initialize the NANDOE,configure the Port C,PC9 in Peripheral-A mode
*
* To initialize the NANDWE,configure the Port C,PC10 in Peripheral-A mode
*
* To initialize the NANDCLE,configure the Port C,PC17 in Peripheral-A mode
*
* To initialize the NANDALE,configure the Port C,PC16 in Peripheral-A mode
*
* To initialize the R/nB, configure any PIO as an input pin with pull-up
* enabled
*
* To initialize the nCE, configure any PIO as an output pin (refer to Tips
* and Tricks for the supported nCE connection types)
**/
ret = board_nandflash_config(cs);
if (ret < 0)
{
ferr("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)
{
ferr("ERROR: CS%d nand_initialize failed\n", cs);
return NULL;
}
/* Return the MTD wrapper interface as the MTD device */
return mtd;
}