diff options
| author | Scott Wood <scottwood@freescale.com> | 2008-03-21 16:12:51 -0500 | 
|---|---|---|
| committer | Scott Wood <scottwood@freescale.com> | 2008-08-12 11:31:25 -0500 | 
| commit | 9fd020d6b4b36b9fb67cd834bc1ae7fdba15ee9e (patch) | |
| tree | 0fcbaf26dbe5b8ea5d576d89fd457bd0a849b1e6 /drivers/mtd/nand/fsl_elbc_nand.c | |
| parent | 41ef8c716e93fdf50efe9c1ba733ca6675daaca6 (diff) | |
| download | olio-uboot-2014.01-9fd020d6b4b36b9fb67cd834bc1ae7fdba15ee9e.tar.xz olio-uboot-2014.01-9fd020d6b4b36b9fb67cd834bc1ae7fdba15ee9e.zip | |
Freescale eLBC FCM NAND driver
This is a driver for the Flash Control Machine of the enhanched Local Bus
Controller found on some Freescale chips (such as the mpc8313 and the
mpc8379).
Signed-off-by: Scott Wood <scottwood@freescale.com>
Diffstat (limited to 'drivers/mtd/nand/fsl_elbc_nand.c')
| -rw-r--r-- | drivers/mtd/nand/fsl_elbc_nand.c | 759 | 
1 files changed, 759 insertions, 0 deletions
| diff --git a/drivers/mtd/nand/fsl_elbc_nand.c b/drivers/mtd/nand/fsl_elbc_nand.c new file mode 100644 index 000000000..c1644c0f6 --- /dev/null +++ b/drivers/mtd/nand/fsl_elbc_nand.c @@ -0,0 +1,759 @@ +/* Freescale Enhanced Local Bus Controller FCM NAND driver + * + * Copyright (c) 2006-2008 Freescale Semiconductor + * + * 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 + */ + +#include <common.h> +#include <malloc.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> + +#ifdef VERBOSE_DEBUG +#define DEBUG_ELBC +#define vdbg(format, arg...) printf("DEBUG: " format, ##arg) +#else +#define vdbg(format, arg...) do {} while (0) +#endif + +/* Can't use plain old DEBUG because the linux mtd + * headers define it as a macro. + */ +#ifdef DEBUG_ELBC +#define dbg(format, arg...) printf("DEBUG: " format, ##arg) +#else +#define dbg(format, arg...) do {} while (0) +#endif + +#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) + +struct fsl_elbc_ctrl; + +/* mtd information per set */ + +struct fsl_elbc_mtd { +	struct mtd_info mtd; +	struct nand_chip chip; +	struct fsl_elbc_ctrl *ctrl; + +	struct device *dev; +	int bank;               /* Chip select bank number           */ +	u8 __iomem *vbase;      /* Chip select base virtual address  */ +	int page_size;          /* NAND page size (0=512, 1=2048)    */ +	unsigned int fmr;       /* FCM Flash Mode Register value     */ +}; + +/* overview of the fsl elbc controller */ + +struct fsl_elbc_ctrl { +	struct nand_hw_control controller; +	struct fsl_elbc_mtd *chips[MAX_BANKS]; + +	/* device info */ +	lbus83xx_t *regs; +	u8 __iomem *addr;        /* Address of assigned FCM buffer        */ +	unsigned int page;       /* Last page written to / read from      */ +	unsigned int read_bytes; /* Number of bytes read during command   */ +	unsigned int column;     /* Saved column from SEQIN               */ +	unsigned int index;      /* Pointer to next byte to 'read'        */ +	unsigned int status;     /* status read from LTESR after last op  */ +	unsigned int mdr;        /* UPM/FCM Data Register value           */ +	unsigned int use_mdr;    /* Non zero if the MDR is to be set      */ +	unsigned int oob;        /* Non zero if operating on OOB data     */ +	uint8_t *oob_poi;        /* Place to write ECC after read back    */ +}; + +/* These map to the positions used by the FCM hardware ECC generator */ + +/* Small Page FLASH with FMR[ECCM] = 0 */ +static struct nand_ecclayout fsl_elbc_oob_sp_eccm0 = { +	.eccbytes = 3, +	.eccpos = {6, 7, 8}, +	.oobfree = { {0, 5}, {9, 7} }, +	.oobavail = 12, +}; + +/* Small Page FLASH with FMR[ECCM] = 1 */ +static struct nand_ecclayout fsl_elbc_oob_sp_eccm1 = { +	.eccbytes = 3, +	.eccpos = {8, 9, 10}, +	.oobfree = { {0, 5}, {6, 2}, {11, 5} }, +	.oobavail = 12, +}; + +/* Large Page FLASH with FMR[ECCM] = 0 */ +static struct nand_ecclayout fsl_elbc_oob_lp_eccm0 = { +	.eccbytes = 12, +	.eccpos = {6, 7, 8, 22, 23, 24, 38, 39, 40, 54, 55, 56}, +	.oobfree = { {1, 5}, {9, 13}, {25, 13}, {41, 13}, {57, 7} }, +	.oobavail = 48, +}; + +/* Large Page FLASH with FMR[ECCM] = 1 */ +static struct nand_ecclayout fsl_elbc_oob_lp_eccm1 = { +	.eccbytes = 12, +	.eccpos = {8, 9, 10, 24, 25, 26, 40, 41, 42, 56, 57, 58}, +	.oobfree = { {1, 7}, {11, 13}, {27, 13}, {43, 13}, {59, 5} }, +	.oobavail = 48, +}; + +/*=================================*/ + +/* + * Set up the FCM hardware block and page address fields, and the fcm + * structure addr field to point to the correct FCM buffer in memory + */ +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 fsl_elbc_ctrl *ctrl = priv->ctrl; +	lbus83xx_t *lbc = ctrl->regs; +	int buf_num; + +	ctrl->page = page_addr; + +	out_be32(&lbc->fbar, +	         page_addr >> (chip->phys_erase_shift - chip->page_shift)); + +	if (priv->page_size) { +		out_be32(&lbc->fpar, +		         ((page_addr << FPAR_LP_PI_SHIFT) & FPAR_LP_PI) | +		         (oob ? FPAR_LP_MS : 0) | column); +		buf_num = (page_addr & 1) << 2; +	} else { +		out_be32(&lbc->fpar, +		         ((page_addr << FPAR_SP_PI_SHIFT) & FPAR_SP_PI) | +		         (oob ? FPAR_SP_MS : 0) | column); +		buf_num = page_addr & 7; +	} + +	ctrl->addr = priv->vbase + buf_num * 1024; +	ctrl->index = column; + +	/* for OOB data point to the second half of the buffer */ +	if (oob) +		ctrl->index += priv->page_size ? 2048 : 512; + +	vdbg("set_addr: bank=%d, ctrl->addr=0x%p (0x%p), " +	     "index %x, pes %d ps %d\n", +	     buf_num, ctrl->addr, priv->vbase, ctrl->index, +	     chip->phys_erase_shift, chip->page_shift); +} + +/* + * execute FCM command and wait for it to complete + */ +static int fsl_elbc_run_command(struct mtd_info *mtd) +{ +	struct nand_chip *chip = mtd->priv; +	struct fsl_elbc_mtd *priv = chip->priv; +	struct fsl_elbc_ctrl *ctrl = priv->ctrl; +	lbus83xx_t *lbc = ctrl->regs; +	long long end_tick; +	u32 ltesr; + +	/* Setup the FMR[OP] to execute without write protection */ +	out_be32(&lbc->fmr, priv->fmr | 3); +	if (ctrl->use_mdr) +		out_be32(&lbc->mdr, ctrl->mdr); + +	vdbg("fsl_elbc_run_command: fmr=%08x fir=%08x fcr=%08x\n", +	     in_be32(&lbc->fmr), in_be32(&lbc->fir), in_be32(&lbc->fcr)); +	vdbg("fsl_elbc_run_command: fbar=%08x fpar=%08x " +	     "fbcr=%08x bank=%d\n", +	     in_be32(&lbc->fbar), in_be32(&lbc->fpar), +	     in_be32(&lbc->fbcr), priv->bank); + +	/* execute special operation */ +	out_be32(&lbc->lsor, priv->bank); + +	/* wait for FCM complete flag or timeout */ +	end_tick = usec2ticks(FCM_TIMEOUT_MSECS * 1000) + get_ticks(); + +	ltesr = 0; +	while (end_tick > get_ticks()) { +		ltesr = in_be32(&lbc->ltesr); +		if (ltesr & LTESR_CC) +			break; +	} + +	ctrl->status = ltesr & LTESR_NAND_MASK; +	out_be32(&lbc->ltesr, ctrl->status); +	out_be32(&lbc->lteatr, 0); + +	/* store mdr value in case it was needed */ +	if (ctrl->use_mdr) +		ctrl->mdr = in_be32(&lbc->mdr); + +	ctrl->use_mdr = 0; + +	vdbg("fsl_elbc_run_command: stat=%08x mdr=%08x fmr=%08x\n", +	     ctrl->status, ctrl->mdr, in_be32(&lbc->fmr)); + +	/* returns 0 on success otherwise non-zero) */ +	return ctrl->status == LTESR_CC ? 0 : -EIO; +} + +static void fsl_elbc_do_read(struct nand_chip *chip, int oob) +{ +	struct fsl_elbc_mtd *priv = chip->priv; +	struct fsl_elbc_ctrl *ctrl = priv->ctrl; +	lbus83xx_t *lbc = ctrl->regs; + +	if (priv->page_size) { +		out_be32(&lbc->fir, +		         (FIR_OP_CW0 << FIR_OP0_SHIFT) | +		         (FIR_OP_CA  << FIR_OP1_SHIFT) | +		         (FIR_OP_PA  << FIR_OP2_SHIFT) | +		         (FIR_OP_CW1 << FIR_OP3_SHIFT) | +		         (FIR_OP_RBW << FIR_OP4_SHIFT)); + +		out_be32(&lbc->fcr, (NAND_CMD_READ0 << FCR_CMD0_SHIFT) | +		                    (NAND_CMD_READSTART << FCR_CMD1_SHIFT)); +	} else { +		out_be32(&lbc->fir, +		         (FIR_OP_CW0 << FIR_OP0_SHIFT) | +		         (FIR_OP_CA  << FIR_OP1_SHIFT) | +		         (FIR_OP_PA  << FIR_OP2_SHIFT) | +		         (FIR_OP_RBW << FIR_OP3_SHIFT)); + +		if (oob) +			out_be32(&lbc->fcr, +			         NAND_CMD_READOOB << FCR_CMD0_SHIFT); +		else +			out_be32(&lbc->fcr, NAND_CMD_READ0 << FCR_CMD0_SHIFT); +	} +} + +/* cmdfunc send commands to the FCM */ +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 fsl_elbc_ctrl *ctrl = priv->ctrl; +	lbus83xx_t *lbc = ctrl->regs; + +	ctrl->use_mdr = 0; + +	/* clear the read buffer */ +	ctrl->read_bytes = 0; +	if (command != NAND_CMD_PAGEPROG) +		ctrl->index = 0; + +	switch (command) { +	/* READ0 and READ1 read the entire buffer to use hardware ECC. */ +	case NAND_CMD_READ1: +		column += 256; + +	/* fall-through */ +	case NAND_CMD_READ0: +		vdbg("fsl_elbc_cmdfunc: NAND_CMD_READ0, page_addr:" +		     " 0x%x, column: 0x%x.\n", page_addr, column); + +		out_be32(&lbc->fbcr, 0); /* read entire page to enable ECC */ +		set_addr(mtd, 0, page_addr, 0); + +		ctrl->read_bytes = mtd->writesize + mtd->oobsize; +		ctrl->index += column; + +		fsl_elbc_do_read(chip, 0); +		fsl_elbc_run_command(mtd); +		return; + +	/* READOOB reads only the OOB because no ECC is performed. */ +	case NAND_CMD_READOOB: +		vdbg("fsl_elbc_cmdfunc: NAND_CMD_READOOB, page_addr:" +		     " 0x%x, column: 0x%x.\n", page_addr, column); + +		out_be32(&lbc->fbcr, mtd->oobsize - column); +		set_addr(mtd, column, page_addr, 1); + +		ctrl->read_bytes = mtd->writesize + mtd->oobsize; + +		fsl_elbc_do_read(chip, 1); +		fsl_elbc_run_command(mtd); + +		return; + +	/* READID must read all 5 possible bytes while CEB is active */ +	case NAND_CMD_READID: +		vdbg("fsl_elbc_cmdfunc: NAND_CMD_READID.\n"); + +		out_be32(&lbc->fir, (FIR_OP_CW0 << FIR_OP0_SHIFT) | +		                    (FIR_OP_UA  << FIR_OP1_SHIFT) | +		                    (FIR_OP_RBW << FIR_OP2_SHIFT)); +		out_be32(&lbc->fcr, NAND_CMD_READID << FCR_CMD0_SHIFT); +		/* 5 bytes for manuf, device and exts */ +		out_be32(&lbc->fbcr, 5); +		ctrl->read_bytes = 5; +		ctrl->use_mdr = 1; +		ctrl->mdr = 0; + +		set_addr(mtd, 0, 0, 0); +		fsl_elbc_run_command(mtd); +		return; + +	/* ERASE1 stores the block and page address */ +	case NAND_CMD_ERASE1: +		vdbg("fsl_elbc_cmdfunc: NAND_CMD_ERASE1, " +		     "page_addr: 0x%x.\n", page_addr); +		set_addr(mtd, 0, page_addr, 0); +		return; + +	/* ERASE2 uses the block and page address from ERASE1 */ +	case NAND_CMD_ERASE2: +		vdbg("fsl_elbc_cmdfunc: NAND_CMD_ERASE2.\n"); + +		out_be32(&lbc->fir, +		         (FIR_OP_CW0 << FIR_OP0_SHIFT) | +		         (FIR_OP_PA  << FIR_OP1_SHIFT) | +		         (FIR_OP_CM1 << FIR_OP2_SHIFT)); + +		out_be32(&lbc->fcr, +		         (NAND_CMD_ERASE1 << FCR_CMD0_SHIFT) | +		         (NAND_CMD_ERASE2 << FCR_CMD1_SHIFT)); + +		out_be32(&lbc->fbcr, 0); +		ctrl->read_bytes = 0; + +		fsl_elbc_run_command(mtd); +		return; + +	/* SEQIN sets up the addr buffer and all registers except the length */ +	case NAND_CMD_SEQIN: { +		u32 fcr; +		vdbg("fsl_elbc_cmdfunc: NAND_CMD_SEQIN/PAGE_PROG, " +		     "page_addr: 0x%x, column: 0x%x.\n", +		     page_addr, column); + +		ctrl->column = column; +		ctrl->oob = 0; + +		if (priv->page_size) { +			fcr = (NAND_CMD_SEQIN << FCR_CMD0_SHIFT) | +			      (NAND_CMD_PAGEPROG << FCR_CMD1_SHIFT); + +			out_be32(&lbc->fir, +			         (FIR_OP_CW0 << FIR_OP0_SHIFT) | +			         (FIR_OP_CA  << FIR_OP1_SHIFT) | +			         (FIR_OP_PA  << FIR_OP2_SHIFT) | +			         (FIR_OP_WB  << FIR_OP3_SHIFT) | +			         (FIR_OP_CW1 << FIR_OP4_SHIFT)); +		} else { +			fcr = (NAND_CMD_PAGEPROG << FCR_CMD1_SHIFT) | +			      (NAND_CMD_SEQIN << FCR_CMD2_SHIFT); + +			out_be32(&lbc->fir, +			         (FIR_OP_CW0 << FIR_OP0_SHIFT) | +			         (FIR_OP_CM2 << FIR_OP1_SHIFT) | +			         (FIR_OP_CA  << FIR_OP2_SHIFT) | +			         (FIR_OP_PA  << FIR_OP3_SHIFT) | +			         (FIR_OP_WB  << FIR_OP4_SHIFT) | +			         (FIR_OP_CW1 << FIR_OP5_SHIFT)); + +			if (column >= mtd->writesize) { +				/* OOB area --> READOOB */ +				column -= mtd->writesize; +				fcr |= NAND_CMD_READOOB << FCR_CMD0_SHIFT; +				ctrl->oob = 1; +			} else if (column < 256) { +				/* First 256 bytes --> READ0 */ +				fcr |= NAND_CMD_READ0 << FCR_CMD0_SHIFT; +			} else { +				/* Second 256 bytes --> READ1 */ +				fcr |= NAND_CMD_READ1 << FCR_CMD0_SHIFT; +			} +		} + +		out_be32(&lbc->fcr, fcr); +		set_addr(mtd, column, page_addr, ctrl->oob); +		return; +	} + +	/* PAGEPROG reuses all of the setup from SEQIN and adds the length */ +	case NAND_CMD_PAGEPROG: { +		int full_page; +		vdbg("fsl_elbc_cmdfunc: NAND_CMD_PAGEPROG " +		     "writing %d bytes.\n", ctrl->index); + +		/* if the write did not start at 0 or is not a full page +		 * then set the exact length, otherwise use a full page +		 * write so the HW generates the ECC. +		 */ +		if (ctrl->oob || ctrl->column != 0 || +		    ctrl->index != mtd->writesize + mtd->oobsize) { +			out_be32(&lbc->fbcr, ctrl->index); +			full_page = 0; +		} else { +			out_be32(&lbc->fbcr, 0); +			full_page = 1; +		} + +		fsl_elbc_run_command(mtd); + +		/* Read back the page in order to fill in the ECC for the +		 * caller.  Is this really needed? +		 */ +		if (full_page && ctrl->oob_poi) { +			out_be32(&lbc->fbcr, 3); +			set_addr(mtd, 6, page_addr, 1); + +			ctrl->read_bytes = mtd->writesize + 9; + +			fsl_elbc_do_read(chip, 1); +			fsl_elbc_run_command(mtd); + +			memcpy_fromio(ctrl->oob_poi + 6, +			              &ctrl->addr[ctrl->index], 3); +			ctrl->index += 3; +		} + +		ctrl->oob_poi = NULL; +		return; +	} + +	/* CMD_STATUS must read the status byte while CEB is active */ +	/* Note - it does not wait for the ready line */ +	case NAND_CMD_STATUS: +		out_be32(&lbc->fir, +		         (FIR_OP_CM0 << FIR_OP0_SHIFT) | +		         (FIR_OP_RBW << FIR_OP1_SHIFT)); +		out_be32(&lbc->fcr, NAND_CMD_STATUS << FCR_CMD0_SHIFT); +		out_be32(&lbc->fbcr, 1); +		set_addr(mtd, 0, 0, 0); +		ctrl->read_bytes = 1; + +		fsl_elbc_run_command(mtd); + +		/* The chip always seems to report that it is +		 * write-protected, even when it is not. +		 */ +		out_8(ctrl->addr, in_8(ctrl->addr) | NAND_STATUS_WP); +		return; + +	/* RESET without waiting for the ready line */ +	case NAND_CMD_RESET: +		dbg("fsl_elbc_cmdfunc: NAND_CMD_RESET.\n"); +		out_be32(&lbc->fir, FIR_OP_CM0 << FIR_OP0_SHIFT); +		out_be32(&lbc->fcr, NAND_CMD_RESET << FCR_CMD0_SHIFT); +		fsl_elbc_run_command(mtd); +		return; + +	default: +		printf("fsl_elbc_cmdfunc: error, unsupported command 0x%x.\n", +		        command); +	} +} + +static void fsl_elbc_select_chip(struct mtd_info *mtd, int chip) +{ +	/* The hardware does not seem to support multiple +	 * chips per bank. +	 */ +} + +/* + * Write buf to the FCM Controller Data Buffer + */ +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 fsl_elbc_ctrl *ctrl = priv->ctrl; +	unsigned int bufsize = mtd->writesize + mtd->oobsize; + +	if (len < 0) { +		printf("write_buf of %d bytes", len); +		ctrl->status = 0; +		return; +	} + +	if ((unsigned int)len > bufsize - ctrl->index) { +		printf("write_buf beyond end of buffer " +		       "(%d requested, %u available)\n", +		       len, bufsize - ctrl->index); +		len = bufsize - ctrl->index; +	} + +	memcpy_toio(&ctrl->addr[ctrl->index], buf, len); +	ctrl->index += len; +} + +/* + * read a byte from either the FCM hardware buffer if it has any data left + * otherwise issue a command to read a single byte. + */ +static u8 fsl_elbc_read_byte(struct mtd_info *mtd) +{ +	struct nand_chip *chip = mtd->priv; +	struct fsl_elbc_mtd *priv = chip->priv; +	struct fsl_elbc_ctrl *ctrl = priv->ctrl; + +	/* If there are still bytes in the FCM, then use the next byte. */ +	if (ctrl->index < ctrl->read_bytes) +		return in_8(&ctrl->addr[ctrl->index++]); + +	printf("read_byte beyond end of buffer\n"); +	return ERR_BYTE; +} + +/* + * Read from the FCM Controller Data Buffer + */ +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 fsl_elbc_ctrl *ctrl = priv->ctrl; +	int avail; + +	if (len < 0) +		return; + +	avail = min((unsigned int)len, ctrl->read_bytes - ctrl->index); +	memcpy_fromio(buf, &ctrl->addr[ctrl->index], avail); +	ctrl->index += avail; + +	if (len > avail) +		printf("read_buf beyond end of buffer " +		       "(%d requested, %d available)\n", +		       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_ctrl *ctrl = priv->ctrl; +	lbus83xx_t *lbc = ctrl->regs; + +	if (ctrl->status != LTESR_CC) +		return NAND_STATUS_FAIL; + +	/* Use READ_STATUS command, but wait for the device to be ready */ +	ctrl->use_mdr = 0; +	out_be32(&lbc->fir, +	         (FIR_OP_CW0 << FIR_OP0_SHIFT) | +	         (FIR_OP_RBW << FIR_OP1_SHIFT)); +	out_be32(&lbc->fcr, NAND_CMD_STATUS << FCR_CMD0_SHIFT); +	out_be32(&lbc->fbcr, 1); +	set_addr(mtd, 0, 0, 0); +	ctrl->read_bytes = 1; + +	fsl_elbc_run_command(mtd); + +	if (ctrl->status != LTESR_CC) +		return NAND_STATUS_FAIL; + +	/* The chip always seems to report that it is +	 * write-protected, even when it is not. +	 */ +	out_8(ctrl->addr, in_8(ctrl->addr) | NAND_STATUS_WP); +	return fsl_elbc_read_byte(mtd); +} + +static int fsl_elbc_read_page(struct mtd_info *mtd, +                              struct nand_chip *chip, +                              uint8_t *buf) +{ +	fsl_elbc_read_buf(mtd, buf, mtd->writesize); +	fsl_elbc_read_buf(mtd, chip->oob_poi, mtd->oobsize); + +	if (fsl_elbc_wait(mtd, chip) & NAND_STATUS_FAIL) +		mtd->ecc_stats.failed++; + +	return 0; +} + +/* 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) +{ +	struct fsl_elbc_mtd *priv = chip->priv; +	struct fsl_elbc_ctrl *ctrl = priv->ctrl; + +	fsl_elbc_write_buf(mtd, buf, mtd->writesize); +	fsl_elbc_write_buf(mtd, chip->oob_poi, mtd->oobsize); + +	ctrl->oob_poi = chip->oob_poi; +} + +static struct fsl_elbc_ctrl *elbc_ctrl; + +static void fsl_elbc_ctrl_init(void) +{ +	immap_t *im = (immap_t *)CFG_IMMR; + +	elbc_ctrl = kzalloc(sizeof(*elbc_ctrl), GFP_KERNEL); +	if (!elbc_ctrl) +		return; + +	elbc_ctrl->regs = &im->lbus; + +	/* clear event registers */ +	out_be32(&elbc_ctrl->regs->ltesr, LTESR_NAND_MASK); +	out_be32(&elbc_ctrl->regs->lteatr, 0); + +	/* Enable interrupts for any detected events */ +	out_be32(&elbc_ctrl->regs->lteir, LTESR_NAND_MASK); + +	elbc_ctrl->read_bytes = 0; +	elbc_ctrl->index = 0; +	elbc_ctrl->addr = NULL; +} + +int board_nand_init(struct nand_chip *nand) +{ +	struct fsl_elbc_mtd *priv; +	uint32_t br, or; + +	if (!elbc_ctrl) { +		fsl_elbc_ctrl_init(); +		if (!elbc_ctrl) +			return -1; +	} + +	priv = kzalloc(sizeof(*priv), GFP_KERNEL); +	if (!priv) +		return -ENOMEM; + +	priv->ctrl = elbc_ctrl; +	priv->vbase = nand->IO_ADDR_R; + +	/* 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++) { +		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) == (phys_addr_t)nand->IO_ADDR_R) +			break; +	} + +	if (priv->bank >= MAX_BANKS) { +		printf("fsl_elbc_nand: address did not match any " +		       "chip selects\n"); +		return -ENODEV; +	} + +	elbc_ctrl->chips[priv->bank] = priv; + +	/* fill in nand_chip structure */ +	/* set up function call table */ +	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; + +	/* set up nand options */ +	nand->options = NAND_NO_READRDY | NAND_NO_AUTOINCR; + +	nand->controller = &elbc_ctrl->controller; +	nand->priv = priv; + +	nand->ecc.read_page = fsl_elbc_read_page; +	nand->ecc.write_page = fsl_elbc_write_page; + +	/* 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.layout = (priv->fmr & FMR_ECCM) ? +		                   &fsl_elbc_oob_sp_eccm1 : +		                   &fsl_elbc_oob_sp_eccm0; + +		nand->ecc.size = 512; +		nand->ecc.bytes = 3; +		nand->ecc.steps = 1; +	} else { +		/* otherwise fall back to default software ECC */ +		nand->ecc.mode = NAND_ECC_SOFT; +	} + +	priv->fmr = (15 << FMR_CWTO_SHIFT) | (2 << FMR_AL_SHIFT); + +	/* adjust Option Register and ECC to match Flash page size */ +	if (or & OR_FCM_PGS) { +		priv->page_size = 1; + +		/* adjust ecc setup if needed */ +		if ((br & BR_DECC) == BR_DECC_CHK_GEN) { +			nand->ecc.steps = 4; +			nand->ecc.layout = (priv->fmr & FMR_ECCM) ? +			                   &fsl_elbc_oob_lp_eccm1 : +			                   &fsl_elbc_oob_lp_eccm0; +		} +	} + +	return 0; +} |