* Authors: Nick Spence <nick.spence@freescale.com>,
* Scott Wood <scottwood@freescale.com>
*
- * This program 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 2 of the License, or
- * (at your option) any later version.
- *
- * This program 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, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * SPDX-License-Identifier: GPL-2.0+
*/
#include <common.h>
#include <malloc.h>
+#include <nand.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/nand.h>
#include <linux/mtd/nand_ecc.h>
#include <asm/io.h>
-#include <asm/errno.h>
+#include <linux/errno.h>
#ifdef VERBOSE_DEBUG
#define DEBUG_ELBC
#define MAX_BANKS 8
#define ERR_BYTE 0xFF /* Value returned for read bytes when read failed */
-#define FCM_TIMEOUT_MSECS 10 /* Maximum number of mSecs to wait for FCM */
#define LTESR_NAND_MASK (LTESR_FCT | LTESR_PAR | LTESR_CC)
/* mtd information per set */
struct fsl_elbc_mtd {
- struct mtd_info mtd;
struct nand_chip chip;
struct fsl_elbc_ctrl *ctrl;
*/
static void set_addr(struct mtd_info *mtd, int column, int page_addr, int oob)
{
- struct nand_chip *chip = mtd->priv;
- struct fsl_elbc_mtd *priv = chip->priv;
+ struct nand_chip *chip = mtd_to_nand(mtd);
+ struct fsl_elbc_mtd *priv = nand_get_controller_data(chip);
struct fsl_elbc_ctrl *ctrl = priv->ctrl;
fsl_lbc_t *lbc = ctrl->regs;
int buf_num;
*/
static int fsl_elbc_run_command(struct mtd_info *mtd)
{
- struct nand_chip *chip = mtd->priv;
- struct fsl_elbc_mtd *priv = chip->priv;
+ struct nand_chip *chip = mtd_to_nand(mtd);
+ struct fsl_elbc_mtd *priv = nand_get_controller_data(chip);
struct fsl_elbc_ctrl *ctrl = priv->ctrl;
fsl_lbc_t *lbc = ctrl->regs;
- long long end_tick;
+ u32 timeo = (CONFIG_SYS_HZ * 10) / 1000;
+ u32 time_start;
u32 ltesr;
/* Setup the FMR[OP] to execute without write protection */
out_be32(&lbc->lsor, priv->bank);
/* wait for FCM complete flag or timeout */
- end_tick = usec2ticks(FCM_TIMEOUT_MSECS * 1000) + get_ticks();
+ time_start = get_timer(0);
ltesr = 0;
- while (end_tick > get_ticks()) {
+ while (get_timer(time_start) < timeo) {
ltesr = in_be32(&lbc->ltesr);
if (ltesr & LTESR_CC)
break;
static void fsl_elbc_do_read(struct nand_chip *chip, int oob)
{
- struct fsl_elbc_mtd *priv = chip->priv;
+ struct fsl_elbc_mtd *priv = nand_get_controller_data(chip);
struct fsl_elbc_ctrl *ctrl = priv->ctrl;
fsl_lbc_t *lbc = ctrl->regs;
static void fsl_elbc_cmdfunc(struct mtd_info *mtd, unsigned int command,
int column, int page_addr)
{
- struct nand_chip *chip = mtd->priv;
- struct fsl_elbc_mtd *priv = chip->priv;
+ struct nand_chip *chip = mtd_to_nand(mtd);
+ struct fsl_elbc_mtd *priv = nand_get_controller_data(chip);
struct fsl_elbc_ctrl *ctrl = priv->ctrl;
fsl_lbc_t *lbc = ctrl->regs;
*/
static void fsl_elbc_write_buf(struct mtd_info *mtd, const u8 *buf, int len)
{
- struct nand_chip *chip = mtd->priv;
- struct fsl_elbc_mtd *priv = chip->priv;
+ struct nand_chip *chip = mtd_to_nand(mtd);
+ struct fsl_elbc_mtd *priv = nand_get_controller_data(chip);
struct fsl_elbc_ctrl *ctrl = priv->ctrl;
unsigned int bufsize = mtd->writesize + mtd->oobsize;
*/
static u8 fsl_elbc_read_byte(struct mtd_info *mtd)
{
- struct nand_chip *chip = mtd->priv;
- struct fsl_elbc_mtd *priv = chip->priv;
+ struct nand_chip *chip = mtd_to_nand(mtd);
+ struct fsl_elbc_mtd *priv = nand_get_controller_data(chip);
struct fsl_elbc_ctrl *ctrl = priv->ctrl;
/* If there are still bytes in the FCM, then use the next byte. */
*/
static void fsl_elbc_read_buf(struct mtd_info *mtd, u8 *buf, int len)
{
- struct nand_chip *chip = mtd->priv;
- struct fsl_elbc_mtd *priv = chip->priv;
+ struct nand_chip *chip = mtd_to_nand(mtd);
+ struct fsl_elbc_mtd *priv = nand_get_controller_data(chip);
struct fsl_elbc_ctrl *ctrl = priv->ctrl;
int avail;
len, avail);
}
-/*
- * Verify buffer against the FCM Controller Data Buffer
- */
-static int fsl_elbc_verify_buf(struct mtd_info *mtd,
- const u_char *buf, int len)
-{
- struct nand_chip *chip = mtd->priv;
- struct fsl_elbc_mtd *priv = chip->priv;
- struct fsl_elbc_ctrl *ctrl = priv->ctrl;
- int i;
-
- if (len < 0) {
- printf("write_buf of %d bytes", len);
- return -EINVAL;
- }
-
- if ((unsigned int)len > ctrl->read_bytes - ctrl->index) {
- printf("verify_buf beyond end of buffer "
- "(%d requested, %u available)\n",
- len, ctrl->read_bytes - ctrl->index);
-
- ctrl->index = ctrl->read_bytes;
- return -EINVAL;
- }
-
- for (i = 0; i < len; i++)
- if (in_8(&ctrl->addr[ctrl->index + i]) != buf[i])
- break;
-
- ctrl->index += len;
- return i == len && ctrl->status == LTESR_CC ? 0 : -EIO;
-}
-
/* This function is called after Program and Erase Operations to
* check for success or failure.
*/
static int fsl_elbc_wait(struct mtd_info *mtd, struct nand_chip *chip)
{
- struct fsl_elbc_mtd *priv = chip->priv;
+ struct fsl_elbc_mtd *priv = nand_get_controller_data(chip);
struct fsl_elbc_ctrl *ctrl = priv->ctrl;
fsl_lbc_t *lbc = ctrl->regs;
return fsl_elbc_read_byte(mtd);
}
-static int fsl_elbc_read_page(struct mtd_info *mtd,
- struct nand_chip *chip,
- uint8_t *buf, int page)
+static int fsl_elbc_read_page(struct mtd_info *mtd, struct nand_chip *chip,
+ uint8_t *buf, int oob_required, int page)
{
fsl_elbc_read_buf(mtd, buf, mtd->writesize);
fsl_elbc_read_buf(mtd, chip->oob_poi, mtd->oobsize);
/* ECC will be calculated automatically, and errors will be detected in
* waitfunc.
*/
-static void fsl_elbc_write_page(struct mtd_info *mtd,
- struct nand_chip *chip,
- const uint8_t *buf)
+static int fsl_elbc_write_page(struct mtd_info *mtd, struct nand_chip *chip,
+ const uint8_t *buf, int oob_required,
+ int page)
{
fsl_elbc_write_buf(mtd, buf, mtd->writesize);
fsl_elbc_write_buf(mtd, chip->oob_poi, mtd->oobsize);
+
+ return 0;
}
static struct fsl_elbc_ctrl *elbc_ctrl;
+/* ECC will be calculated automatically, and errors will be detected in
+ * waitfunc.
+ */
+static int fsl_elbc_write_subpage(struct mtd_info *mtd, struct nand_chip *chip,
+ uint32_t offset, uint32_t data_len,
+ const uint8_t *buf, int oob_required, int page)
+{
+ fsl_elbc_write_buf(mtd, buf, mtd->writesize);
+ fsl_elbc_write_buf(mtd, chip->oob_poi, mtd->oobsize);
+
+ return 0;
+}
+
static void fsl_elbc_ctrl_init(void)
{
elbc_ctrl = kzalloc(sizeof(*elbc_ctrl), GFP_KERNEL);
elbc_ctrl->addr = NULL;
}
-int board_nand_init(struct nand_chip *nand)
+static int fsl_elbc_chip_init(int devnum, u8 *addr)
{
+ struct mtd_info *mtd;
+ struct nand_chip *nand;
struct fsl_elbc_mtd *priv;
uint32_t br = 0, or = 0;
+ int ret;
if (!elbc_ctrl) {
fsl_elbc_ctrl_init();
return -ENOMEM;
priv->ctrl = elbc_ctrl;
- priv->vbase = nand->IO_ADDR_R;
+ priv->vbase = addr;
/* Find which chip select it is connected to. It'd be nice
* if we could pass more than one datum to the NAND driver...
*/
for (priv->bank = 0; priv->bank < MAX_BANKS; priv->bank++) {
- phys_addr_t base_addr = virt_to_phys(nand->IO_ADDR_R);
+ phys_addr_t phys_addr = virt_to_phys(addr);
br = in_be32(&elbc_ctrl->regs->bank[priv->bank].br);
or = in_be32(&elbc_ctrl->regs->bank[priv->bank].or);
if ((br & BR_V) && (br & BR_MSEL) == BR_MS_FCM &&
- (br & or & BR_BA) == BR_PHYS_ADDR(base_addr))
+ (br & or & BR_BA) == BR_PHYS_ADDR(phys_addr))
break;
}
if (priv->bank >= MAX_BANKS) {
printf("fsl_elbc_nand: address did not match any "
"chip selects\n");
+ kfree(priv);
return -ENODEV;
}
+ nand = &priv->chip;
+ mtd = nand_to_mtd(nand);
+
elbc_ctrl->chips[priv->bank] = priv;
/* fill in nand_chip structure */
nand->read_byte = fsl_elbc_read_byte;
nand->write_buf = fsl_elbc_write_buf;
nand->read_buf = fsl_elbc_read_buf;
- nand->verify_buf = fsl_elbc_verify_buf;
nand->select_chip = fsl_elbc_select_chip;
nand->cmdfunc = fsl_elbc_cmdfunc;
nand->waitfunc = fsl_elbc_wait;
nand->bbt_md = &bbt_mirror_descr;
/* set up nand options */
- nand->options = NAND_NO_READRDY | NAND_NO_AUTOINCR |
- NAND_USE_FLASH_BBT;
+ nand->options = NAND_NO_SUBPAGE_WRITE;
+ nand->bbt_options = NAND_BBT_USE_FLASH;
nand->controller = &elbc_ctrl->controller;
- nand->priv = priv;
+ nand_set_controller_data(nand, priv);
nand->ecc.read_page = fsl_elbc_read_page;
nand->ecc.write_page = fsl_elbc_write_page;
+ nand->ecc.write_subpage = fsl_elbc_write_subpage;
-#ifdef CONFIG_FSL_ELBC_FMR
- priv->fmr = CONFIG_FSL_ELBC_FMR;
-#else
priv->fmr = (15 << FMR_CWTO_SHIFT) | (2 << FMR_AL_SHIFT);
- /*
- * Hardware expects small page has ECCM0, large page has ECCM1
- * when booting from NAND. Board config can override if not
- * booting from NAND.
- */
- if (or & OR_FCM_PGS)
- priv->fmr |= FMR_ECCM;
-#endif
-
/* If CS Base Register selects full hardware ECC then use it */
if ((br & BR_DECC) == BR_DECC_CHK_GEN) {
nand->ecc.mode = NAND_ECC_HW;
nand->ecc.size = 512;
nand->ecc.bytes = 3;
nand->ecc.steps = 1;
+ nand->ecc.strength = 1;
} else {
- /* otherwise fall back to default software ECC */
+ /* otherwise fall back to software ECC */
+#if defined(CONFIG_NAND_ECC_BCH)
+ nand->ecc.mode = NAND_ECC_SOFT_BCH;
+#else
nand->ecc.mode = NAND_ECC_SOFT;
+#endif
}
+ ret = nand_scan_ident(mtd, 1, NULL);
+ if (ret)
+ return ret;
+
/* Large-page-specific setup */
- if (or & OR_FCM_PGS) {
+ if (mtd->writesize == 2048) {
+ setbits_be32(&elbc_ctrl->regs->bank[priv->bank].or,
+ OR_FCM_PGS);
+ in_be32(&elbc_ctrl->regs->bank[priv->bank].or);
+
priv->page_size = 1;
nand->badblock_pattern = &largepage_memorybased;
+ /*
+ * Hardware expects small page has ECCM0, large page has
+ * ECCM1 when booting from NAND, and we follow that even
+ * when not booting from NAND.
+ */
+ priv->fmr |= FMR_ECCM;
+
/* adjust ecc setup if needed */
if ((br & BR_DECC) == BR_DECC_CHK_GEN) {
nand->ecc.steps = 4;
&fsl_elbc_oob_lp_eccm1 :
&fsl_elbc_oob_lp_eccm0;
}
+ } else if (mtd->writesize == 512) {
+ clrbits_be32(&elbc_ctrl->regs->bank[priv->bank].or,
+ OR_FCM_PGS);
+ in_be32(&elbc_ctrl->regs->bank[priv->bank].or);
+ } else {
+ return -ENODEV;
}
+ ret = nand_scan_tail(mtd);
+ if (ret)
+ return ret;
+
+ ret = nand_register(devnum, mtd);
+ if (ret)
+ return ret;
+
return 0;
}
+
+#ifndef CONFIG_SYS_NAND_BASE_LIST
+#define CONFIG_SYS_NAND_BASE_LIST { CONFIG_SYS_NAND_BASE }
+#endif
+
+static unsigned long base_address[CONFIG_SYS_MAX_NAND_DEVICE] =
+ CONFIG_SYS_NAND_BASE_LIST;
+
+void board_nand_init(void)
+{
+ int i;
+
+ for (i = 0; i < CONFIG_SYS_MAX_NAND_DEVICE; i++)
+ fsl_elbc_chip_init(i, (u8 *)base_address[i]);
+}