📄 fsl_elbc_nand.c
字号:
/* Freescale Enhanced Local Bus Controller NAND driver * * Copyright (c) 2006-2007 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 <linux/module.h>#include <linux/types.h>#include <linux/init.h>#include <linux/kernel.h>#include <linux/string.h>#include <linux/ioport.h>#include <linux/of_platform.h>#include <linux/slab.h>#include <linux/interrupt.h>#include <linux/mtd/mtd.h>#include <linux/mtd/nand.h>#include <linux/mtd/nand_ecc.h>#include <linux/mtd/partitions.h>#include <asm/io.h>#include <asm/fsl_lbc.h>#define MAX_BANKS 8#define ERR_BYTE 0xFF /* Value returned for read bytes when read failed */#define FCM_TIMEOUT_MSECS 500 /* Maximum number of mSecs to wait for FCM */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 */ struct device *dev; struct fsl_lbc_regs __iomem *regs; int irq; wait_queue_head_t irq_wait; unsigned int irq_status; /* status read from LTESR by irq handler */ 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 */ char *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} },};/* 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} },};/* 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} },};/* 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} },};/* * fsl_elbc_oob_lp_eccm* specify that LP NAND's OOB free area starts at offset * 1, so we have to adjust bad block pattern. This pattern should be used for * x8 chips only. So far hardware does not support x16 chips anyway. */static u8 scan_ff_pattern[] = { 0xff, };static struct nand_bbt_descr largepage_memorybased = { .options = 0, .offs = 0, .len = 1, .pattern = scan_ff_pattern,};/* * ELBC may use HW ECC, so that OOB offsets, that NAND core uses for bbt, * interfere with ECC positions, that's why we implement our own descriptors. * OOB {11, 5}, works for both SP and LP chips, with ECCM = 1 and ECCM = 0. */static u8 bbt_pattern[] = {'B', 'b', 't', '0' };static u8 mirror_pattern[] = {'1', 't', 'b', 'B' };static struct nand_bbt_descr bbt_main_descr = { .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE | NAND_BBT_2BIT | NAND_BBT_VERSION, .offs = 11, .len = 4, .veroffs = 15, .maxblocks = 4, .pattern = bbt_pattern,};static struct nand_bbt_descr bbt_mirror_descr = { .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE | NAND_BBT_2BIT | NAND_BBT_VERSION, .offs = 11, .len = 4, .veroffs = 15, .maxblocks = 4, .pattern = mirror_pattern,};/*=================================*//* * 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; struct fsl_lbc_regs __iomem *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; dev_vdbg(ctrl->dev, "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; struct fsl_lbc_regs __iomem *lbc = ctrl->regs; /* 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); dev_vdbg(ctrl->dev, "fsl_elbc_run_command: fmr=%08x fir=%08x fcr=%08x\n", in_be32(&lbc->fmr), in_be32(&lbc->fir), in_be32(&lbc->fcr)); dev_vdbg(ctrl->dev, "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); ctrl->irq_status = 0; /* execute special operation */ out_be32(&lbc->lsor, priv->bank); /* wait for FCM complete flag or timeout */ wait_event_timeout(ctrl->irq_wait, ctrl->irq_status, FCM_TIMEOUT_MSECS * HZ/1000); ctrl->status = ctrl->irq_status; /* store mdr value in case it was needed */ if (ctrl->use_mdr) ctrl->mdr = in_be32(&lbc->mdr); ctrl->use_mdr = 0; dev_vdbg(ctrl->dev, "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; struct fsl_lbc_regs __iomem *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; struct fsl_lbc_regs __iomem *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: dev_dbg(ctrl->dev, "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: dev_vdbg(ctrl->dev, "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: dev_vdbg(ctrl->dev, "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: dev_vdbg(ctrl->dev, "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: dev_vdbg(ctrl->dev, "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));
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -