Commit 103e40f6 authored by David Woodhouse's avatar David Woodhouse
parents 95b93a0c f4f91ac3
...@@ -61,6 +61,7 @@ static int __devinit generic_onenand_probe(struct device *dev) ...@@ -61,6 +61,7 @@ static int __devinit generic_onenand_probe(struct device *dev)
} }
info->onenand.mmcontrol = pdata->mmcontrol; info->onenand.mmcontrol = pdata->mmcontrol;
info->onenand.irq = platform_get_irq(pdev, 0);
info->mtd.name = pdev->dev.bus_id; info->mtd.name = pdev->dev.bus_id;
info->mtd.priv = &info->onenand; info->mtd.priv = &info->onenand;
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#include <linux/module.h> #include <linux/module.h>
#include <linux/init.h> #include <linux/init.h>
#include <linux/sched.h> #include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/jiffies.h> #include <linux/jiffies.h>
#include <linux/mtd/mtd.h> #include <linux/mtd/mtd.h>
#include <linux/mtd/onenand.h> #include <linux/mtd/onenand.h>
...@@ -330,15 +331,123 @@ static int onenand_wait(struct mtd_info *mtd, int state) ...@@ -330,15 +331,123 @@ static int onenand_wait(struct mtd_info *mtd, int state)
if (interrupt & ONENAND_INT_READ) { if (interrupt & ONENAND_INT_READ) {
ecc = this->read_word(this->base + ONENAND_REG_ECC_STATUS); ecc = this->read_word(this->base + ONENAND_REG_ECC_STATUS);
if (ecc & ONENAND_ECC_2BIT_ALL) { if (ecc) {
DEBUG(MTD_DEBUG_LEVEL0, "onenand_wait: ECC error = 0x%04x\n", ecc); DEBUG(MTD_DEBUG_LEVEL0, "onenand_wait: ECC error = 0x%04x\n", ecc);
return -EBADMSG; if (ecc & ONENAND_ECC_2BIT_ALL)
mtd->ecc_stats.failed++;
else if (ecc & ONENAND_ECC_1BIT_ALL)
mtd->ecc_stats.corrected++;
} }
} }
return 0; return 0;
} }
/*
* onenand_interrupt - [DEFAULT] onenand interrupt handler
* @param irq onenand interrupt number
* @param dev_id interrupt data
*
* complete the work
*/
static irqreturn_t onenand_interrupt(int irq, void *data)
{
struct onenand_chip *this = (struct onenand_chip *) data;
/* To handle shared interrupt */
if (!this->complete.done)
complete(&this->complete);
return IRQ_HANDLED;
}
/*
* onenand_interrupt_wait - [DEFAULT] wait until the command is done
* @param mtd MTD device structure
* @param state state to select the max. timeout value
*
* Wait for command done.
*/
static int onenand_interrupt_wait(struct mtd_info *mtd, int state)
{
struct onenand_chip *this = mtd->priv;
/* To prevent soft lockup */
touch_softlockup_watchdog();
wait_for_completion(&this->complete);
return onenand_wait(mtd, state);
}
/*
* onenand_try_interrupt_wait - [DEFAULT] try interrupt wait
* @param mtd MTD device structure
* @param state state to select the max. timeout value
*
* Try interrupt based wait (It is used one-time)
*/
static int onenand_try_interrupt_wait(struct mtd_info *mtd, int state)
{
struct onenand_chip *this = mtd->priv;
unsigned long remain, timeout;
/* We use interrupt wait first */
this->wait = onenand_interrupt_wait;
/* To prevent soft lockup */
touch_softlockup_watchdog();
timeout = msecs_to_jiffies(100);
remain = wait_for_completion_timeout(&this->complete, timeout);
if (!remain) {
printk(KERN_INFO "OneNAND: There's no interrupt. "
"We use the normal wait\n");
/* Release the irq */
free_irq(this->irq, this);
this->wait = onenand_wait;
}
return onenand_wait(mtd, state);
}
/*
* onenand_setup_wait - [OneNAND Interface] setup onenand wait method
* @param mtd MTD device structure
*
* There's two method to wait onenand work
* 1. polling - read interrupt status register
* 2. interrupt - use the kernel interrupt method
*/
static void onenand_setup_wait(struct mtd_info *mtd)
{
struct onenand_chip *this = mtd->priv;
int syscfg;
init_completion(&this->complete);
if (this->irq <= 0) {
this->wait = onenand_wait;
return;
}
if (request_irq(this->irq, &onenand_interrupt,
IRQF_SHARED, "onenand", this)) {
/* If we can't get irq, use the normal wait */
this->wait = onenand_wait;
return;
}
/* Enable interrupt */
syscfg = this->read_word(this->base + ONENAND_REG_SYS_CFG1);
syscfg |= ONENAND_SYS_CFG1_IOBE;
this->write_word(syscfg, this->base + ONENAND_REG_SYS_CFG1);
this->wait = onenand_try_interrupt_wait;
}
/** /**
* onenand_bufferram_offset - [DEFAULT] BufferRAM offset * onenand_bufferram_offset - [DEFAULT] BufferRAM offset
* @param mtd MTD data structure * @param mtd MTD data structure
...@@ -609,6 +718,7 @@ static int onenand_read(struct mtd_info *mtd, loff_t from, size_t len, ...@@ -609,6 +718,7 @@ static int onenand_read(struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, u_char *buf) size_t *retlen, u_char *buf)
{ {
struct onenand_chip *this = mtd->priv; struct onenand_chip *this = mtd->priv;
struct mtd_ecc_stats stats;
int read = 0, column; int read = 0, column;
int thislen; int thislen;
int ret = 0; int ret = 0;
...@@ -627,6 +737,7 @@ static int onenand_read(struct mtd_info *mtd, loff_t from, size_t len, ...@@ -627,6 +737,7 @@ static int onenand_read(struct mtd_info *mtd, loff_t from, size_t len,
/* TODO handling oob */ /* TODO handling oob */
stats = mtd->ecc_stats;
while (read < len) { while (read < len) {
thislen = min_t(int, mtd->writesize, len - read); thislen = min_t(int, mtd->writesize, len - read);
...@@ -668,7 +779,11 @@ static int onenand_read(struct mtd_info *mtd, loff_t from, size_t len, ...@@ -668,7 +779,11 @@ static int onenand_read(struct mtd_info *mtd, loff_t from, size_t len,
* retlen == desired len and result == -EBADMSG * retlen == desired len and result == -EBADMSG
*/ */
*retlen = read; *retlen = read;
return ret;
if (mtd->ecc_stats.failed - stats.failed)
return -EBADMSG;
return mtd->ecc_stats.corrected - stats.corrected ? -EUCLEAN : 0;
} }
/** /**
...@@ -1129,7 +1244,6 @@ static void onenand_sync(struct mtd_info *mtd) ...@@ -1129,7 +1244,6 @@ static void onenand_sync(struct mtd_info *mtd)
onenand_release_device(mtd); onenand_release_device(mtd);
} }
/** /**
* onenand_block_isbad - [MTD Interface] Check whether the block at the given offset is bad * onenand_block_isbad - [MTD Interface] Check whether the block at the given offset is bad
* @param mtd MTD device structure * @param mtd MTD device structure
...@@ -1196,32 +1310,38 @@ static int onenand_block_markbad(struct mtd_info *mtd, loff_t ofs) ...@@ -1196,32 +1310,38 @@ static int onenand_block_markbad(struct mtd_info *mtd, loff_t ofs)
} }
/** /**
* onenand_unlock - [MTD Interface] Unlock block(s) * onenand_do_lock_cmd - [OneNAND Interface] Lock or unlock block(s)
* @param mtd MTD device structure * @param mtd MTD device structure
* @param ofs offset relative to mtd start * @param ofs offset relative to mtd start
* @param len number of bytes to unlock * @param len number of bytes to lock or unlock
* *
* Unlock one or more blocks * Lock or unlock one or more blocks
*/ */
static int onenand_unlock(struct mtd_info *mtd, loff_t ofs, size_t len) static int onenand_do_lock_cmd(struct mtd_info *mtd, loff_t ofs, size_t len, int cmd)
{ {
struct onenand_chip *this = mtd->priv; struct onenand_chip *this = mtd->priv;
int start, end, block, value, status; int start, end, block, value, status;
int wp_status_mask;
start = ofs >> this->erase_shift; start = ofs >> this->erase_shift;
end = len >> this->erase_shift; end = len >> this->erase_shift;
if (cmd == ONENAND_CMD_LOCK)
wp_status_mask = ONENAND_WP_LS;
else
wp_status_mask = ONENAND_WP_US;
/* Continuous lock scheme */ /* Continuous lock scheme */
if (this->options & ONENAND_HAS_CONT_LOCK) { if (this->options & ONENAND_HAS_CONT_LOCK) {
/* Set start block address */ /* Set start block address */
this->write_word(start, this->base + ONENAND_REG_START_BLOCK_ADDRESS); this->write_word(start, this->base + ONENAND_REG_START_BLOCK_ADDRESS);
/* Set end block address */ /* Set end block address */
this->write_word(start + end - 1, this->base + ONENAND_REG_END_BLOCK_ADDRESS); this->write_word(start + end - 1, this->base + ONENAND_REG_END_BLOCK_ADDRESS);
/* Write unlock command */ /* Write lock command */
this->command(mtd, ONENAND_CMD_UNLOCK, 0, 0); this->command(mtd, cmd, 0, 0);
/* There's no return value */ /* There's no return value */
this->wait(mtd, FL_UNLOCKING); this->wait(mtd, FL_LOCKING);
/* Sanity check */ /* Sanity check */
while (this->read_word(this->base + ONENAND_REG_CTRL_STATUS) while (this->read_word(this->base + ONENAND_REG_CTRL_STATUS)
...@@ -1230,7 +1350,7 @@ static int onenand_unlock(struct mtd_info *mtd, loff_t ofs, size_t len) ...@@ -1230,7 +1350,7 @@ static int onenand_unlock(struct mtd_info *mtd, loff_t ofs, size_t len)
/* Check lock status */ /* Check lock status */
status = this->read_word(this->base + ONENAND_REG_WP_STATUS); status = this->read_word(this->base + ONENAND_REG_WP_STATUS);
if (!(status & ONENAND_WP_US)) if (!(status & wp_status_mask))
printk(KERN_ERR "wp status = 0x%x\n", status); printk(KERN_ERR "wp status = 0x%x\n", status);
return 0; return 0;
...@@ -1246,11 +1366,11 @@ static int onenand_unlock(struct mtd_info *mtd, loff_t ofs, size_t len) ...@@ -1246,11 +1366,11 @@ static int onenand_unlock(struct mtd_info *mtd, loff_t ofs, size_t len)
this->write_word(value, this->base + ONENAND_REG_START_ADDRESS2); this->write_word(value, this->base + ONENAND_REG_START_ADDRESS2);
/* Set start block address */ /* Set start block address */
this->write_word(block, this->base + ONENAND_REG_START_BLOCK_ADDRESS); this->write_word(block, this->base + ONENAND_REG_START_BLOCK_ADDRESS);
/* Write unlock command */ /* Write lock command */
this->command(mtd, ONENAND_CMD_UNLOCK, 0, 0); this->command(mtd, cmd, 0, 0);
/* There's no return value */ /* There's no return value */
this->wait(mtd, FL_UNLOCKING); this->wait(mtd, FL_LOCKING);
/* Sanity check */ /* Sanity check */
while (this->read_word(this->base + ONENAND_REG_CTRL_STATUS) while (this->read_word(this->base + ONENAND_REG_CTRL_STATUS)
...@@ -1259,13 +1379,39 @@ static int onenand_unlock(struct mtd_info *mtd, loff_t ofs, size_t len) ...@@ -1259,13 +1379,39 @@ static int onenand_unlock(struct mtd_info *mtd, loff_t ofs, size_t len)
/* Check lock status */ /* Check lock status */
status = this->read_word(this->base + ONENAND_REG_WP_STATUS); status = this->read_word(this->base + ONENAND_REG_WP_STATUS);
if (!(status & ONENAND_WP_US)) if (!(status & wp_status_mask))
printk(KERN_ERR "block = %d, wp status = 0x%x\n", block, status); printk(KERN_ERR "block = %d, wp status = 0x%x\n", block, status);
} }
return 0; return 0;
} }
/**
* onenand_lock - [MTD Interface] Lock block(s)
* @param mtd MTD device structure
* @param ofs offset relative to mtd start
* @param len number of bytes to unlock
*
* Lock one or more blocks
*/
static int onenand_lock(struct mtd_info *mtd, loff_t ofs, size_t len)
{
return onenand_do_lock_cmd(mtd, ofs, len, ONENAND_CMD_LOCK);
}
/**
* onenand_unlock - [MTD Interface] Unlock block(s)
* @param mtd MTD device structure
* @param ofs offset relative to mtd start
* @param len number of bytes to unlock
*
* Unlock one or more blocks
*/
static int onenand_unlock(struct mtd_info *mtd, loff_t ofs, size_t len)
{
return onenand_do_lock_cmd(mtd, ofs, len, ONENAND_CMD_UNLOCK);
}
/** /**
* onenand_check_lock_status - [OneNAND Interface] Check lock status * onenand_check_lock_status - [OneNAND Interface] Check lock status
* @param this onenand chip data structure * @param this onenand chip data structure
...@@ -1310,7 +1456,7 @@ static int onenand_unlock_all(struct mtd_info *mtd) ...@@ -1310,7 +1456,7 @@ static int onenand_unlock_all(struct mtd_info *mtd)
this->command(mtd, ONENAND_CMD_UNLOCK_ALL, 0, 0); this->command(mtd, ONENAND_CMD_UNLOCK_ALL, 0, 0);
/* There's no return value */ /* There's no return value */
this->wait(mtd, FL_UNLOCKING); this->wait(mtd, FL_LOCKING);
/* Sanity check */ /* Sanity check */
while (this->read_word(this->base + ONENAND_REG_CTRL_STATUS) while (this->read_word(this->base + ONENAND_REG_CTRL_STATUS)
...@@ -1334,7 +1480,7 @@ static int onenand_unlock_all(struct mtd_info *mtd) ...@@ -1334,7 +1480,7 @@ static int onenand_unlock_all(struct mtd_info *mtd)
return 0; return 0;
} }
mtd->unlock(mtd, 0x0, this->chipsize); onenand_unlock(mtd, 0x0, this->chipsize);
return 0; return 0;
} }
...@@ -1762,7 +1908,7 @@ static int onenand_probe(struct mtd_info *mtd) ...@@ -1762,7 +1908,7 @@ static int onenand_probe(struct mtd_info *mtd)
/* Read manufacturer and device IDs from Register */ /* Read manufacturer and device IDs from Register */
maf_id = this->read_word(this->base + ONENAND_REG_MANUFACTURER_ID); maf_id = this->read_word(this->base + ONENAND_REG_MANUFACTURER_ID);
dev_id = this->read_word(this->base + ONENAND_REG_DEVICE_ID); dev_id = this->read_word(this->base + ONENAND_REG_DEVICE_ID);
ver_id= this->read_word(this->base + ONENAND_REG_VERSION_ID); ver_id = this->read_word(this->base + ONENAND_REG_VERSION_ID);
/* Check OneNAND device */ /* Check OneNAND device */
if (maf_id != bram_maf_id || dev_id != bram_dev_id) if (maf_id != bram_maf_id || dev_id != bram_dev_id)
...@@ -1846,7 +1992,7 @@ int onenand_scan(struct mtd_info *mtd, int maxchips) ...@@ -1846,7 +1992,7 @@ int onenand_scan(struct mtd_info *mtd, int maxchips)
if (!this->command) if (!this->command)
this->command = onenand_command; this->command = onenand_command;
if (!this->wait) if (!this->wait)
this->wait = onenand_wait; onenand_setup_wait(mtd);
if (!this->read_bufferram) if (!this->read_bufferram)
this->read_bufferram = onenand_read_bufferram; this->read_bufferram = onenand_read_bufferram;
...@@ -1922,7 +2068,7 @@ int onenand_scan(struct mtd_info *mtd, int maxchips) ...@@ -1922,7 +2068,7 @@ int onenand_scan(struct mtd_info *mtd, int maxchips)
mtd->lock_user_prot_reg = onenand_lock_user_prot_reg; mtd->lock_user_prot_reg = onenand_lock_user_prot_reg;
#endif #endif
mtd->sync = onenand_sync; mtd->sync = onenand_sync;
mtd->lock = NULL; mtd->lock = onenand_lock;
mtd->unlock = onenand_unlock; mtd->unlock = onenand_unlock;
mtd->suspend = onenand_suspend; mtd->suspend = onenand_suspend;
mtd->resume = onenand_resume; mtd->resume = onenand_resume;
......
...@@ -100,6 +100,7 @@ static int create_bbt(struct mtd_info *mtd, uint8_t *buf, struct nand_bbt_descr ...@@ -100,6 +100,7 @@ static int create_bbt(struct mtd_info *mtd, uint8_t *buf, struct nand_bbt_descr
bbm->bbt[i >> 3] |= 0x03 << (i & 0x6); bbm->bbt[i >> 3] |= 0x03 << (i & 0x6);
printk(KERN_WARNING "Bad eraseblock %d at 0x%08x\n", printk(KERN_WARNING "Bad eraseblock %d at 0x%08x\n",
i >> 1, (unsigned int) from); i >> 1, (unsigned int) from);
mtd->ecc_stats.badblocks++;
break; break;
} }
} }
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#define __LINUX_MTD_ONENAND_H #define __LINUX_MTD_ONENAND_H
#include <linux/spinlock.h> #include <linux/spinlock.h>
#include <linux/completion.h>
#include <linux/mtd/onenand_regs.h> #include <linux/mtd/onenand_regs.h>
#include <linux/mtd/bbm.h> #include <linux/mtd/bbm.h>
...@@ -33,7 +34,6 @@ typedef enum { ...@@ -33,7 +34,6 @@ typedef enum {
FL_WRITING, FL_WRITING,
FL_ERASING, FL_ERASING,
FL_SYNCING, FL_SYNCING,
FL_UNLOCKING,
FL_LOCKING, FL_LOCKING,
FL_RESETING, FL_RESETING,
FL_OTPING, FL_OTPING,
...@@ -120,6 +120,9 @@ struct onenand_chip { ...@@ -120,6 +120,9 @@ struct onenand_chip {
int (*block_markbad)(struct mtd_info *mtd, loff_t ofs); int (*block_markbad)(struct mtd_info *mtd, loff_t ofs);
int (*scan_bbt)(struct mtd_info *mtd); int (*scan_bbt)(struct mtd_info *mtd);
struct completion complete;
int irq;
spinlock_t chip_lock; spinlock_t chip_lock;
wait_queue_head_t wq; wait_queue_head_t wq;
onenand_state_t state; onenand_state_t state;
......
...@@ -179,6 +179,7 @@ ...@@ -179,6 +179,7 @@
* ECC Status Reigser FF00h (R) * ECC Status Reigser FF00h (R)
*/ */
#define ONENAND_ECC_1BIT (1 << 0) #define ONENAND_ECC_1BIT (1 << 0)
#define ONENAND_ECC_1BIT_ALL (0x5555)
#define ONENAND_ECC_2BIT (1 << 1) #define ONENAND_ECC_2BIT (1 << 1)
#define ONENAND_ECC_2BIT_ALL (0xAAAA) #define ONENAND_ECC_2BIT_ALL (0xAAAA)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment