📄 nand.c
字号:
/* * drivers/mtd/nand.c * * Copyright (C) 2000 Steven J. Hill (sjhill@cotw.com) * * $Id: nand.c,v 1.12 2001/10/02 15:05:14 dwmw2 Exp $ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * Overview: * This is the generic MTD driver for NAND flash devices. It should be * capable of working with almost all NAND chips currently available. */#include <linux/delay.h>#include <linux/errno.h>#include <linux/sched.h>#include <linux/types.h>#include <linux/mtd/mtd.h>#include <linux/mtd/nand.h>#include <linux/mtd/nand_ids.h>#include <linux/interrupt.h>#include <asm/io.h>#ifdef CONFIG_MTD_NAND_ECC#include <linux/mtd/nand_ecc.h>#endif/* * Macros for low-level register control */#define NAND_CTRL (*(volatile unsigned char *) \ ((struct nand_chip *) mtd->priv)->CTRL_ADDR)#define nand_select() NAND_CTRL &= ~this->NCE; \ nand_command(mtd, NAND_CMD_RESET, -1, -1); \ udelay (10);#define nand_deselect() NAND_CTRL |= ~this->NCE;/* * NAND low-level MTD interface functions */static int nand_read (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);static int nand_read_ecc (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf, u_char *ecc_code);static int nand_read_oob (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);static int nand_write (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf);static int nand_write_ecc (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf, u_char *ecc_code);static int nand_write_oob (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf);static int nand_writev (struct mtd_info *mtd, const struct iovec *vecs, unsigned long count, loff_t to, size_t *retlen);static int nand_erase (struct mtd_info *mtd, struct erase_info *instr);static void nand_sync (struct mtd_info *mtd);/* * Send command to NAND device */static void nand_command (struct mtd_info *mtd, unsigned command, int column, int page_addr){ register struct nand_chip *this = mtd->priv; register unsigned long NAND_IO_ADDR = this->IO_ADDR; /* Begin command latch cycle */ NAND_CTRL |= this->CLE; /* * Write out the command to the device. */ if (command != NAND_CMD_SEQIN) writeb (command, NAND_IO_ADDR); else { if (mtd->oobblock == 256 && column >= 256) { column -= 256; writeb(NAND_CMD_RESET, NAND_IO_ADDR); writeb(NAND_CMD_READOOB, NAND_IO_ADDR); writeb(NAND_CMD_SEQIN, NAND_IO_ADDR); } else if (mtd->oobblock == 512 && column >= 256) { if (column < 512) { column -= 256; writeb(NAND_CMD_READ1, NAND_IO_ADDR); writeb(NAND_CMD_SEQIN, NAND_IO_ADDR); } else { column -= 512; writeb(NAND_CMD_READOOB, NAND_IO_ADDR); writeb(NAND_CMD_SEQIN, NAND_IO_ADDR); } } else { writeb(NAND_CMD_READ0, NAND_IO_ADDR); writeb(NAND_CMD_SEQIN, NAND_IO_ADDR); } } /* Set ALE and clear CLE to start address cycle */ NAND_CTRL &= ~this->CLE; NAND_CTRL |= this->ALE; /* Serially input address */ if (column != -1) writeb (column, NAND_IO_ADDR); if (page_addr != -1) { writeb ((unsigned char) (page_addr & 0xff), NAND_IO_ADDR); writeb ((unsigned char) ((page_addr >> 8) & 0xff), NAND_IO_ADDR); /* One more address cycle for higher density devices */ if (mtd->size & 0x0c000000) { writeb ((unsigned char) ((page_addr >> 16) & 0x0f), NAND_IO_ADDR); } } /* Latch in address */ NAND_CTRL &= ~this->ALE; /* Pause for 15us */ udelay (15);}/* * NAND read */static int nand_read (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf){#ifdef CONFIG_MTD_NAND_ECC struct nand_chip *this = mtd->priv; return nand_read_ecc (mtd, from, len, retlen, buf, this->ecc_code_buf);#else return nand_read_ecc (mtd, from, len, retlen, buf, NULL);#endif}/* * NAND read with ECC */static int nand_read_ecc (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf, u_char *ecc_code){ int j, col, page, state; int erase_state = 0; struct nand_chip *this = mtd->priv; DECLARE_WAITQUEUE(wait, current);#ifdef CONFIG_MTD_NAND_ECC int ecc_result; u_char ecc_calc[6];#endif DEBUG (MTD_DEBUG_LEVEL3, "nand_read_ecc: from = 0x%08x, len = %i\n", (unsigned int) from, (int) len); /* Do not allow reads past end of device */ if ((from + len) > mtd->size) { DEBUG (MTD_DEBUG_LEVEL0, "nand_read_ecc: Attempt read beyond end of device\n"); *retlen = 0; return -EINVAL; } /* Grab the lock and see if the device is available */retry: spin_lock_bh (&this->chip_lock); switch (this->state) { case FL_READY: this->state = FL_READING; spin_unlock_bh (&this->chip_lock); break; case FL_ERASING: this->state = FL_READING; erase_state = 1; spin_unlock_bh (&this->chip_lock); break; default: set_current_state (TASK_UNINTERRUPTIBLE); add_wait_queue (&this->wq, &wait); spin_unlock_bh (&this->chip_lock); schedule(); remove_wait_queue (&this->wq, &wait); goto retry; }; /* First we calculate the starting page */ page = from >> this->page_shift; /* Get raw starting column */ col = from & (mtd->oobblock - 1); /* State machine for devices having pages larger than 256 bytes */ state = (col < mtd->eccsize) ? 0 : 1; /* Calculate column address within ECC block context */ col = (col >= mtd->eccsize) ? (col - mtd->eccsize) : col; /* Initialize return value */ *retlen = 0; /* Select the NAND device */ nand_select (); /* Loop until all data read */ while (*retlen < len) {#ifdef CONFIG_MTD_NAND_ECC /* Send the read command */ if (!state) nand_command (mtd, NAND_CMD_READ0, 0x00, page); else nand_command (mtd, NAND_CMD_READ1, 0x00, page); /* Read in a block big enough for ECC */ for (j=0 ; j < mtd->eccsize ; j++) this->data_buf[j] = readb (this->IO_ADDR); /* Read in the out-of-band data */ if (!state) { nand_command (mtd, NAND_CMD_READOOB, 0x00, page); for (j=0 ; j<3 ; j++) ecc_code[j] = readb(this->IO_ADDR); nand_command (mtd, NAND_CMD_READ0, 0x00, page); } else { nand_command (mtd, NAND_CMD_READOOB, 0x03, page); for (j=3 ; j<6 ; j++) ecc_code[j] = readb(this->IO_ADDR); nand_command (mtd, NAND_CMD_READ0, 0x00, page); } /* Calculate the ECC and verify it */ if (!state) { nand_calculate_ecc (&this->data_buf[0], &ecc_calc[0]); ecc_result = nand_correct_data (&this->data_buf[0], &ecc_code[0], &ecc_calc[0]); } else { nand_calculate_ecc (&this->data_buf[0], &ecc_calc[3]); ecc_result = nand_correct_data (&this->data_buf[0], &ecc_code[3], &ecc_calc[3]); } if (ecc_result == -1) { DEBUG (MTD_DEBUG_LEVEL0, "nand_read_ecc: " \ "Failed ECC read, page 0x%08x\n", page); nand_deselect (); spin_lock_bh (&this->chip_lock); if (erase_state) this->state = FL_ERASING; else this->state = FL_READY; wake_up (&this->wq); spin_unlock_bh (&this->chip_lock); return -EIO; } /* Read the data from ECC data buffer into return buffer */ if ((*retlen + (mtd->eccsize - col)) >= len) { while (*retlen < len) buf[(*retlen)++] = this->data_buf[col++]; /* We're done */ continue; } else for (j=col ; j < mtd->eccsize ; j++) buf[(*retlen)++] = this->data_buf[j];#else /* Send the read command */ if (!state) nand_command (mtd, NAND_CMD_READ0, col, page); else nand_command (mtd, NAND_CMD_READ1, col, page); /* Read the data directly into the return buffer */ if ((*retlen + (mtd->eccsize - col)) >= len) { while (*retlen < len) buf[(*retlen)++] = readb (this->IO_ADDR); /* We're done */ continue; } else for (j=col ; j < mtd->eccsize ; j++) buf[(*retlen)++] = readb (this->IO_ADDR);#endif /* * If the amount of data to be read is greater than * (256 - col), then all subsequent reads will take * place on page or half-page (in the case of 512 byte * page devices) aligned boundaries and the column * address will be zero. Setting the column address to * to zero after the first read allows us to simplify * the reading of data and the if/else statements above. */ if (col) col = 0x00; /* Increment page address */ if ((mtd->oobblock == 256) || state) page++; /* Toggle state machine */ if (mtd->oobblock == 512) state = state ? 0 : 1; } /* De-select the NAND device */ nand_deselect (); /* Wake up anyone waiting on the device */ spin_lock_bh (&this->chip_lock); if (erase_state) this->state = FL_ERASING; else this->state = FL_READY; wake_up (&this->wq); spin_unlock_bh (&this->chip_lock); /* Return happy */ return 0;}/* * NAND read out-of-band */static int nand_read_oob (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf){ int i, col, page; int erase_state = 0; struct nand_chip *this = mtd->priv; DECLARE_WAITQUEUE(wait, current); DEBUG (MTD_DEBUG_LEVEL3, "nand_read_oob: from = 0x%08x, len = %i\n", (unsigned int) from, (int) len); /* Shift to get page */ page = ((int) from) >> this->page_shift; /* Mask to get column */ col = from & 0x0f; /* Initialize return length value */ *retlen = 0; /* Do not allow read past end of page */ if ((col + len) > mtd->oobsize) { DEBUG (MTD_DEBUG_LEVEL0, "nand_read_oob: Attempt read past end of page " \ "0x%08x, column %i, length %i\n", page, col, len); return -EINVAL; }retry: /* Grab the lock and see if the device is available */ spin_lock_bh (&this->chip_lock); switch (this->state) { case FL_READY: this->state = FL_READING; spin_unlock_bh (&this->chip_lock); break; case FL_ERASING: this->state = FL_READING; erase_state = 1; spin_unlock_bh (&this->chip_lock); break; default: set_current_state (TASK_UNINTERRUPTIBLE); add_wait_queue (&this->wq, &wait); spin_unlock_bh (&this->chip_lock); schedule(); remove_wait_queue (&this->wq, &wait); goto retry; }; /* Select the NAND device */ nand_select (); /* Send the read command */ nand_command (mtd, NAND_CMD_READOOB, col, page); /* Read the data */ for (i = 0 ; i < len ; i++) buf[i] = readb (this->IO_ADDR); /* De-select the NAND device */ nand_deselect (); /* Wake up anyone waiting on the device */ spin_lock_bh (&this->chip_lock); if (erase_state) this->state = FL_ERASING; else this->state = FL_READY; wake_up (&this->wq); spin_unlock_bh (&this->chip_lock); /* Return happy */ *retlen = len; return 0;}/* * NAND write */static int nand_write (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf){#ifdef CONFIG_MTD_NAND_ECC struct nand_chip *this = mtd->priv; return nand_write_ecc (mtd, to, len, retlen, buf, this->ecc_code_buf);#else return nand_write_ecc (mtd, to, len, retlen, buf, NULL);#endif}/* * NAND write with ECC */static int nand_write_ecc (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf, u_char *ecc_code){ int i, page, col, cnt, status; struct nand_chip *this = mtd->priv; DECLARE_WAITQUEUE(wait, current);#ifdef CONFIG_MTD_NAND_ECC int ecc_bytes = (mtd->oobblock == 512) ? 6 : 3;#endif DEBUG (MTD_DEBUG_LEVEL3, "nand_write_ecc: to = 0x%08x, len = %i\n", (unsigned int) to, (int) len); /* Do not allow write past end of page */ if ((to + len) > mtd->size) { DEBUG (MTD_DEBUG_LEVEL0, "nand_write_ecc: Attempted write past end of device\n"); return -EINVAL; }
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -