#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/jiffies.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/onenand.h>
#include <linux/mtd/partitions.h>
/**
* onenand_block_address - [DEFAULT] Get block address
- * @param device the device id
+ * @param this onenand chip data structure
* @param block the block
* @return translated block address if DDP, otherwise same
*
* Setup Start Address 1 Register (F100h)
*/
-static int onenand_block_address(int device, int block)
+static int onenand_block_address(struct onenand_chip *this, int block)
{
- if (device & ONENAND_DEVICE_IS_DDP) {
+ if (this->device_id & ONENAND_DEVICE_IS_DDP) {
/* Device Flash Core select, NAND Flash Block Address */
- int dfs = 0, density, mask;
+ int dfs = 0;
- density = device >> ONENAND_DEVICE_DENSITY_SHIFT;
- mask = (1 << (density + 6));
-
- if (block & mask)
+ if (block & this->density_mask)
dfs = 1;
- return (dfs << ONENAND_DDP_SHIFT) | (block & (mask - 1));
+ return (dfs << ONENAND_DDP_SHIFT) |
+ (block & (this->density_mask - 1));
}
return block;
/**
* onenand_bufferram_address - [DEFAULT] Get bufferram address
- * @param device the device id
+ * @param this onenand chip data structure
* @param block the block
* @return set DBS value if DDP, otherwise 0
*
* Setup Start Address 2 Register (F101h) for DDP
*/
-static int onenand_bufferram_address(int device, int block)
+static int onenand_bufferram_address(struct onenand_chip *this, int block)
{
- if (device & ONENAND_DEVICE_IS_DDP) {
+ if (this->device_id & ONENAND_DEVICE_IS_DDP) {
/* Device BufferRAM Select */
- int dbs = 0, density, mask;
-
- density = device >> ONENAND_DEVICE_DENSITY_SHIFT;
- mask = (1 << (density + 6));
+ int dbs = 0;
- if (block & mask)
+ if (block & this->density_mask)
dbs = 1;
return (dbs << ONENAND_DDP_SHIFT);
/* NOTE: The setting order of the registers is very important! */
if (cmd == ONENAND_CMD_BUFFERRAM) {
/* Select DataRAM for DDP */
- value = onenand_bufferram_address(this->device_id, block);
+ value = onenand_bufferram_address(this, block);
this->write_word(value, this->base + ONENAND_REG_START_ADDRESS2);
/* Switch to the next data buffer */
if (block != -1) {
/* Write 'DFS, FBA' of Flash */
- value = onenand_block_address(this->device_id, block);
+ value = onenand_block_address(this, block);
this->write_word(value, this->base + ONENAND_REG_START_ADDRESS1);
}
/* Write 'BSA, BSC' of DataRAM */
value = onenand_buffer_address(dataram, sectors, count);
this->write_word(value, this->base + ONENAND_REG_START_BUFFER);
-
+
if (readcmd) {
/* Select DataRAM for DDP */
- value = onenand_bufferram_address(this->device_id, block);
+ value = onenand_bufferram_address(this, block);
this->write_word(value, this->base + ONENAND_REG_START_ADDRESS2);
}
}
ctrl = this->read_word(this->base + ONENAND_REG_CTRL_STATUS);
if (ctrl & ONENAND_CTRL_ERROR) {
- DEBUG(MTD_DEBUG_LEVEL0, "onenand_wait: controller error = 0x%04x", ctrl);
- return -EIO;
+ /* It maybe occur at initial bad block */
+ DEBUG(MTD_DEBUG_LEVEL0, "onenand_wait: controller error = 0x%04x\n", ctrl);
+ /* Clear other interrupt bits for preventing ECC error */
+ interrupt &= ONENAND_INT_MASTER;
}
if (ctrl & ONENAND_CTRL_LOCK) {
- DEBUG(MTD_DEBUG_LEVEL0, "onenand_wait: it's locked error = 0x%04x", ctrl);
- return -EIO;
+ DEBUG(MTD_DEBUG_LEVEL0, "onenand_wait: it's locked error = 0x%04x\n", ctrl);
+ return -EACCES;
}
if (interrupt & ONENAND_INT_READ) {
ecc = this->read_word(this->base + ONENAND_REG_ECC_STATUS);
if (ecc & ONENAND_ECC_2BIT_ALL) {
- DEBUG(MTD_DEBUG_LEVEL0, "onenand_wait: ECC error = 0x%04x", ecc);
+ DEBUG(MTD_DEBUG_LEVEL0, "onenand_wait: ECC error = 0x%04x\n", ecc);
return -EBADMSG;
}
}
return 0;
}
+/**
+ * onenand_sync_read_bufferram - [OneNAND Interface] Read the bufferram area with Sync. Burst mode
+ * @param mtd MTD data structure
+ * @param area BufferRAM area
+ * @param buffer the databuffer to put/get data
+ * @param offset offset to read from or write to
+ * @param count number of bytes to read/write
+ *
+ * Read the BufferRAM area with Sync. Burst Mode
+ */
+static int onenand_sync_read_bufferram(struct mtd_info *mtd, int area,
+ unsigned char *buffer, int offset, size_t count)
+{
+ struct onenand_chip *this = mtd->priv;
+ void __iomem *bufferram;
+
+ bufferram = this->base + area;
+
+ bufferram += onenand_bufferram_offset(mtd, area);
+
+ this->mmcontrol(mtd, ONENAND_SYS_CFG1_SYNC_READ);
+
+ memcpy(buffer, bufferram + offset, count);
+
+ this->mmcontrol(mtd, 0);
+
+ return 0;
+}
+
/**
* onenand_write_bufferram - [OneNAND Interface] Write the bufferram area
* @param mtd MTD data structure
* onenand_check_bufferram - [GENERIC] Check BufferRAM information
* @param mtd MTD data structure
* @param addr address to check
- * @return 1 if there are valid data, otherwise 0
+ * @return 1 if there are valid data, otherwise 0
*
* Check bufferram if there is data we required
*/
struct onenand_chip *this = mtd->priv;
int block, page;
int i;
-
+
block = (int) (addr >> this->erase_shift);
page = (int) (addr >> this->page_shift);
page &= this->page_mask;
struct onenand_chip *this = mtd->priv;
int block, page;
int i;
-
+
block = (int) (addr >> this->erase_shift);
page = (int) (addr >> this->page_shift);
page &= this->page_mask;
*
* Get the device and lock it for exclusive access
*/
-static void onenand_get_device(struct mtd_info *mtd, int new_state)
+static int onenand_get_device(struct mtd_info *mtd, int new_state)
{
struct onenand_chip *this = mtd->priv;
DECLARE_WAITQUEUE(wait, current);
spin_unlock(&this->chip_lock);
break;
}
+ if (new_state == FL_PM_SUSPENDED) {
+ spin_unlock(&this->chip_lock);
+ return (this->state == FL_PM_SUSPENDED) ? 0 : -EAGAIN;
+ }
set_current_state(TASK_UNINTERRUPTIBLE);
add_wait_queue(&this->wq, &wait);
spin_unlock(&this->chip_lock);
schedule();
remove_wait_queue(&this->wq, &wait);
}
+
+ return 0;
}
/**
* onenand_verify_page - [GENERIC] verify the chip contents after a write
* @param mtd MTD device structure
* @param buf the databuffer to verify
- * @param block block address
- * @param page page address
*
* Check DataRAM area directly
*/
-static int onenand_verify_page(struct mtd_info *mtd, u_char *buf,
- loff_t addr, int block, int page)
+static int onenand_verify_page(struct mtd_info *mtd, u_char *buf, loff_t addr)
{
struct onenand_chip *this = mtd->priv;
void __iomem *dataram0, *dataram1;
if (memcmp(dataram0, dataram1, mtd->oobblock))
return -EBADMSG;
-
+
return 0;
}
#else
written += thislen;
/* Only check verify write turn on */
- ret = onenand_verify_page(mtd, (u_char *) buf, to, block, page);
+ ret = onenand_verify_page(mtd, (u_char *) buf, to);
if (ret) {
DEBUG(MTD_DEBUG_LEVEL0, "onenand_write_ecc: verify failed %d\n", ret);
goto out;
onenand_release_device(mtd);
*retlen = written;
-
+
return ret;
}
onenand_release_device(mtd);
*retlen = written;
-
+
return 0;
}
u_char *eccbuf, struct nand_oobinfo *oobsel)
{
struct onenand_chip *this = mtd->priv;
- unsigned char buffer[mtd->oobblock], *pbuf;
+ unsigned char buffer[MAX_ONENAND_PAGESIZE], *pbuf;
size_t total_len, len;
int i, written = 0;
int ret = 0;
onenand_get_device(mtd, FL_WRITING);
/* TODO handling oob */
-
+
/* Loop until all keve's data has been written */
len = 0;
while (count) {
pbuf = buffer;
- /*
+ /*
* If the given tuple is >= pagesize then
* write it out from the iov
*/
/* Only check verify write turn on */
- ret = onenand_verify_page(mtd, (u_char *) pbuf, to, block, page);
+ ret = onenand_verify_page(mtd, (u_char *) pbuf, to);
if (ret) {
DEBUG(MTD_DEBUG_LEVEL0, "onenand_writev_ecc: verify failed %d\n", ret);
goto out;
return onenand_writev_ecc(mtd, vecs, count, to, retlen, NULL, NULL);
}
+/**
+ * onenand_block_checkbad - [GENERIC] Check if a block is marked bad
+ * @param mtd MTD device structure
+ * @param ofs offset from device start
+ * @param getchip 0, if the chip is already selected
+ * @param allowbbt 1, if its allowed to access the bbt area
+ *
+ * Check, if the block is bad. Either by reading the bad block table or
+ * calling of the scan function.
+ */
+static int onenand_block_checkbad(struct mtd_info *mtd, loff_t ofs, int getchip, int allowbbt)
+{
+ struct onenand_chip *this = mtd->priv;
+ struct bbm_info *bbm = this->bbm;
+
+ /* Return info from the table */
+ return bbm->isbad_bbt(mtd, ofs, allowbbt);
+}
+
/**
* onenand_erase - [MTD Interface] erase block(s)
* @param mtd MTD device structure
while (len) {
- /* TODO Check badblock */
+ /* Check if we have a bad block, we do not erase bad blocks */
+ if (onenand_block_checkbad(mtd, addr, 0, 0)) {
+ printk (KERN_WARNING "onenand_erase: attempt to erase a bad block at addr 0x%08x\n", (unsigned int) addr);
+ instr->state = MTD_ERASE_FAILED;
+ goto erase_exit;
+ }
this->command(mtd, ONENAND_CMD_ERASE, addr, block_size);
onenand_release_device(mtd);
}
+
/**
* onenand_block_isbad - [MTD Interface] Check whether the block at the given offset is bad
* @param mtd MTD device structure
* @param ofs offset relative to mtd start
+ *
+ * Check whether the block is bad
*/
static int onenand_block_isbad(struct mtd_info *mtd, loff_t ofs)
{
- /*
- * TODO
- * 1. Bad block table (BBT)
- * -> using NAND BBT to support JFFS2
- * 2. Bad block management (BBM)
- * -> bad block replace scheme
- *
- * Currently we do nothing
- */
- return 0;
+ /* Check for invalid offset */
+ if (ofs > mtd->size)
+ return -EINVAL;
+
+ return onenand_block_checkbad(mtd, ofs, 1, 0);
+}
+
+/**
+ * onenand_default_block_markbad - [DEFAULT] mark a block bad
+ * @param mtd MTD device structure
+ * @param ofs offset from device start
+ *
+ * This is the default implementation, which can be overridden by
+ * a hardware specific driver.
+ */
+static int onenand_default_block_markbad(struct mtd_info *mtd, loff_t ofs)
+{
+ struct onenand_chip *this = mtd->priv;
+ struct bbm_info *bbm = this->bbm;
+ u_char buf[2] = {0, 0};
+ size_t retlen;
+ int block;
+
+ /* Get block number */
+ block = ((int) ofs) >> bbm->bbt_erase_shift;
+ if (bbm->bbt)
+ bbm->bbt[block >> 2] |= 0x01 << ((block & 0x03) << 1);
+
+ /* We write two bytes, so we dont have to mess with 16 bit access */
+ ofs += mtd->oobsize + (bbm->badblockpos & ~0x01);
+ return mtd->write_oob(mtd, ofs , 2, &retlen, buf);
}
/**
* onenand_block_markbad - [MTD Interface] Mark the block at the given offset as bad
* @param mtd MTD device structure
* @param ofs offset relative to mtd start
+ *
+ * Mark the block as bad
*/
static int onenand_block_markbad(struct mtd_info *mtd, loff_t ofs)
{
- /* see above */
- return 0;
+ struct onenand_chip *this = mtd->priv;
+ int ret;
+
+ ret = onenand_block_isbad(mtd, ofs);
+ if (ret) {
+ /* If it was bad already, return success and do nothing */
+ if (ret > 0)
+ return 0;
+ return ret;
+ }
+
+ return this->block_markbad(mtd, ofs);
}
/**
continue;
/* Set block address for read block status */
- value = onenand_block_address(this->device_id, block);
+ value = onenand_block_address(this, block);
this->write_word(value, this->base + ONENAND_REG_START_ADDRESS1);
/* Check lock status */
if (!(status & ONENAND_WP_US))
printk(KERN_ERR "block = %d, wp status = 0x%x\n", block, status);
}
-
+
return 0;
}
break;
}
- printk(KERN_DEBUG "OneNAND Manufacturer: %s\n",
- onenand_manuf_ids[i].name);
+ printk(KERN_DEBUG "OneNAND Manufacturer: %s (0x%0x)\n",
+ onenand_manuf_ids[i].name, manuf);
return (i != ONENAND_MFR_UNKNOWN);
}
density = dev_id >> ONENAND_DEVICE_DENSITY_SHIFT;
this->chipsize = (16 << density) << 20;
+ /* Set density mask. it is used for DDP */
+ this->density_mask = (1 << (density + 6));
/* OneNAND page size & block size */
/* The data buffer size is equal to page size */
printk(KERN_INFO "Lock scheme is Continues Lock\n");
this->options |= ONENAND_CONT_LOCK;
}
-
+
return 0;
}
+/**
+ * onenand_suspend - [MTD Interface] Suspend the OneNAND flash
+ * @param mtd MTD device structure
+ */
+static int onenand_suspend(struct mtd_info *mtd)
+{
+ return onenand_get_device(mtd, FL_PM_SUSPENDED);
+}
+
+/**
+ * onenand_resume - [MTD Interface] Resume the OneNAND flash
+ * @param mtd MTD device structure
+ */
+static void onenand_resume(struct mtd_info *mtd)
+{
+ struct onenand_chip *this = mtd->priv;
+
+ if (this->state == FL_PM_SUSPENDED)
+ onenand_release_device(mtd);
+ else
+ printk(KERN_ERR "resume() called for the chip which is not"
+ "in suspended state\n");
+}
+
/**
* onenand_scan - [OneNAND Interface] Scan for the OneNAND device
if (!this->write_bufferram)
this->write_bufferram = onenand_write_bufferram;
+ if (!this->block_markbad)
+ this->block_markbad = onenand_default_block_markbad;
+ if (!this->scan_bbt)
+ this->scan_bbt = onenand_default_bbt;
+
if (onenand_probe(mtd))
return -ENXIO;
+ /* Set Sync. Burst Read after probing */
+ if (this->mmcontrol) {
+ printk(KERN_INFO "OneNAND Sync. Burst Read support\n");
+ this->read_bufferram = onenand_sync_read_bufferram;
+ }
+
this->state = FL_READY;
init_waitqueue_head(&this->wq);
spin_lock_init(&this->chip_lock);
}
memcpy(&mtd->oobinfo, this->autooob, sizeof(mtd->oobinfo));
-
+
/* Fill in remaining MTD driver data */
mtd->type = MTD_NANDFLASH;
mtd->flags = MTD_CAP_NANDFLASH | MTD_ECC;
mtd->sync = onenand_sync;
mtd->lock = NULL;
mtd->unlock = onenand_unlock;
- mtd->suspend = NULL;
- mtd->resume = NULL;
+ mtd->suspend = onenand_suspend;
+ mtd->resume = onenand_resume;
mtd->block_isbad = onenand_block_isbad;
mtd->block_markbad = onenand_block_markbad;
mtd->owner = THIS_MODULE;
/* Unlock whole block */
mtd->unlock(mtd, 0x0, this->chipsize);
- return 0;
+ return this->scan_bbt(mtd);
}
/**