Here is the result of my work of supporting an xD card reader that I
have on my notebook.
I had to fix few problems in mtd translation layer, add few workarounds.
This is by no way a final version of the patches, there is still lot of
cleanup to do.
The patches weren't tested with checkpatch.pl for example.
I can now read and write an xD card, and I have no crashes.
Still following issues remain:
1 - Write speed IS very SLOW, just about 200 Kbytes/s
In fact a reader in my printer gives me about 330 Kbytes/s, thus
partially card is to blame.
Also, hardware doesn't support interrupts to test when card is ready,
thus writes consume 100% of one cpu.
2 - I rely on mtd driver to have empty oob layout so I can read both oob
and data using ->read_oob with MTD_OOB_PLACE.
I thinking to add new mode to allow to read whole oob + data and check
ecc, or I will have to do pointless copying of data from 'censored' oob
to normal structure. (This applies to FTL driver I also include in this
patchset)
3 - Using the mtd device directly with anything but supplied FTL or
SSFDC, on 'modern' xD cards just doesn't. These cards implement a fake
nand command set, thus don't have a real oob.
It was disappointing for me too.
I suspect that all 'Type M' cards are of this fake type, but older cards
are OK.
4 - Suspend/resume support works, but relies on all mtd users not to
suspend in middle of card access, bacause in this case its very
difficult/impossible to know card state.
Driver will refuse suspend in that case.
Access via all block devices is safe.
5 - And of course there are bugs, thus I warn you now that this driver
has high probability to erase data from your card.
Thus DON'T USE THIS DRIVER IF ANYTHING IMPORTANT IS STORED ON THE CARD.
6 - driver only detects PCI ID of my notebook, so it might not load on
compatable chips with different ID.
7 - patches developed against stable 2.6.32, and won't compile against
latest git due to changes in kfifo api.
Feedback is welcome, flames too :-)
Best regards,
Maxim Levitsky
>From d8cb6b03919de5ab1587cd4340c684f2c468aec7 Mon Sep 17 00:00:00 2001
From: Maxim Levitsky <[email protected]>
Date: Tue, 5 Jan 2010 22:29:31 +0200
Subject: [PATCH 1/9] MTD: call remove notifiers before removing the device, so users
have chance to free the mtd device
Signed-off-by: Maxim Levitsky <[email protected]>
---
drivers/mtd/mtdcore.c | 21 +++++++++++----------
1 files changed, 11 insertions(+), 10 deletions(-)
diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c
index 467a4f1..738e329 100644
--- a/drivers/mtd/mtdcore.c
+++ b/drivers/mtd/mtdcore.c
@@ -341,31 +341,32 @@ int add_mtd_device(struct mtd_info *mtd)
int del_mtd_device (struct mtd_info *mtd)
{
int ret;
+ struct mtd_notifier *not;
mutex_lock(&mtd_table_mutex);
if (mtd_table[mtd->index] != mtd) {
ret = -ENODEV;
- } else if (mtd->usecount) {
+ goto out_error;
+ }
+
+ /* No need to get a refcount on the module containing
+ the notifier, since we hold the mtd_table_mutex */
+ list_for_each_entry(not, &mtd_notifiers, list)
+ not->remove(mtd);
+
+ if (mtd->usecount) {
printk(KERN_NOTICE "Removing MTD device #%d (%s) with use count %d\n",
mtd->index, mtd->name, mtd->usecount);
ret = -EBUSY;
} else {
- struct mtd_notifier *not;
-
device_unregister(&mtd->dev);
-
- /* No need to get a refcount on the module containing
- the notifier, since we hold the mtd_table_mutex */
- list_for_each_entry(not, &mtd_notifiers, list)
- not->remove(mtd);
-
mtd_table[mtd->index] = NULL;
-
module_put(THIS_MODULE);
ret = 0;
}
+out_error:
mutex_unlock(&mtd_table_mutex);
return ret;
}
--
1.6.3.3
>From 10333e0ed6dc7b176f2dfce04f08572f9e1b04ce Mon Sep 17 00:00:00 2001
From: Maxim Levitsky <[email protected]>
Date: Tue, 5 Jan 2010 22:29:31 +0200
Subject: [PATCH 2/9] MTD: create lockless versions of {get,put}_mtd_device
This will be used to resolve deadlock in block translation layer
---
drivers/mtd/mtdcore.c | 55 +++++++++++++++++++++++++++-------------------
include/linux/mtd/mtd.h | 3 +-
2 files changed, 34 insertions(+), 24 deletions(-)
diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c
index 738e329..e9daf87 100644
--- a/drivers/mtd/mtdcore.c
+++ b/drivers/mtd/mtdcore.c
@@ -454,27 +454,30 @@ struct mtd_info *get_mtd_device(struct mtd_info *mtd, int num)
ret = NULL;
}
- if (!ret)
- goto out_unlock;
-
- if (!try_module_get(ret->owner))
- goto out_unlock;
-
- if (ret->get_device) {
- err = ret->get_device(ret);
- if (err)
- goto out_put;
- }
+ if (!ret || (err = __get_mtd_device(ret)))
+ ret = ERR_PTR(err);
- ret->usecount++;
mutex_unlock(&mtd_table_mutex);
return ret;
+}
-out_put:
- module_put(ret->owner);
-out_unlock:
- mutex_unlock(&mtd_table_mutex);
- return ERR_PTR(err);
+
+int __get_mtd_device(struct mtd_info *mtd)
+{
+ int err;
+
+ if (!try_module_get(mtd->owner))
+ return -ENODEV;
+
+ if (mtd->get_device) {
+
+ if ((err = mtd->get_device(mtd))) {
+ module_put(mtd->owner);
+ return err;
+ }
+ }
+ mtd->usecount++;
+ return 0;
}
/**
@@ -525,18 +528,22 @@ out_unlock:
void put_mtd_device(struct mtd_info *mtd)
{
- int c;
-
mutex_lock(&mtd_table_mutex);
- c = --mtd->usecount;
- if (mtd->put_device)
- mtd->put_device(mtd);
+ __put_mtd_device(mtd);
mutex_unlock(&mtd_table_mutex);
- BUG_ON(c < 0);
module_put(mtd->owner);
}
+void __put_mtd_device(struct mtd_info *mtd)
+{
+ --mtd->usecount;
+ BUG_ON(mtd->usecount < 0);
+
+ if (mtd->put_device)
+ mtd->put_device(mtd);
+}
+
/* default_mtd_writev - default mtd writev method for MTD devices that
* don't implement their own
*/
@@ -570,7 +577,9 @@ EXPORT_SYMBOL_GPL(add_mtd_device);
EXPORT_SYMBOL_GPL(del_mtd_device);
EXPORT_SYMBOL_GPL(get_mtd_device);
EXPORT_SYMBOL_GPL(get_mtd_device_nm);
+EXPORT_SYMBOL_GPL(__get_mtd_device);
EXPORT_SYMBOL_GPL(put_mtd_device);
+EXPORT_SYMBOL_GPL(__put_mtd_device);
EXPORT_SYMBOL_GPL(register_mtd_user);
EXPORT_SYMBOL_GPL(unregister_mtd_user);
EXPORT_SYMBOL_GPL(default_mtd_writev);
diff --git a/include/linux/mtd/mtd.h b/include/linux/mtd/mtd.h
index 0f32a9b..662d747 100644
--- a/include/linux/mtd/mtd.h
+++ b/include/linux/mtd/mtd.h
@@ -290,8 +290,9 @@ extern int add_mtd_device(struct mtd_info *mtd);
extern int del_mtd_device (struct mtd_info *mtd);
extern struct mtd_info *get_mtd_device(struct mtd_info *mtd, int num);
+extern int __get_mtd_device(struct mtd_info *mtd);
+extern void __put_mtd_device(struct mtd_info *mtd);
extern struct mtd_info *get_mtd_device_nm(const char *name);
-
extern void put_mtd_device(struct mtd_info *mtd);
--
1.6.3.3
>From ebc405ef4e2bcf14870e1b17fb3e356f1a20980c Mon Sep 17 00:00:00 2001
From: Maxim Levitsky <[email protected]>
Date: Tue, 5 Jan 2010 23:25:12 +0200
Subject: [PATCH 3/9] MTD: blkdevs: major cleanups.
* Make disk queue and thread per mtd device
* Handle the case of mtd device diappearence correctly
Signed-off-by: Maxim Levitsky <[email protected]>
---
drivers/mtd/ftl.c | 1 -
drivers/mtd/inftlcore.c | 1 -
drivers/mtd/mtd_blkdevs.c | 237 ++++++++++++++++++++++++++---------------
drivers/mtd/mtdblock.c | 1 -
drivers/mtd/mtdblock_ro.c | 1 -
drivers/mtd/mtdcore.c | 3 +-
drivers/mtd/nftlcore.c | 1 -
drivers/mtd/rfd_ftl.c | 1 -
drivers/mtd/ssfdc.c | 1 -
include/linux/mtd/blktrans.h | 13 ++-
10 files changed, 160 insertions(+), 100 deletions(-)
diff --git a/drivers/mtd/ftl.c b/drivers/mtd/ftl.c
index e56d6b4..62da9eb 100644
--- a/drivers/mtd/ftl.c
+++ b/drivers/mtd/ftl.c
@@ -1082,7 +1082,6 @@ static void ftl_remove_dev(struct mtd_blktrans_dev *dev)
{
del_mtd_blktrans_dev(dev);
ftl_freepart((partition_t *)dev);
- kfree(dev);
}
static struct mtd_blktrans_ops ftl_tr = {
diff --git a/drivers/mtd/inftlcore.c b/drivers/mtd/inftlcore.c
index 8aca552..015a7fe 100755
--- a/drivers/mtd/inftlcore.c
+++ b/drivers/mtd/inftlcore.c
@@ -139,7 +139,6 @@ static void inftl_remove_dev(struct mtd_blktrans_dev *dev)
kfree(inftl->PUtable);
kfree(inftl->VUtable);
- kfree(inftl);
}
/*
diff --git a/drivers/mtd/mtd_blkdevs.c b/drivers/mtd/mtd_blkdevs.c
index 8ca17a3..6af4673 100644
--- a/drivers/mtd/mtd_blkdevs.c
+++ b/drivers/mtd/mtd_blkdevs.c
@@ -26,11 +26,6 @@
static LIST_HEAD(blktrans_majors);
-struct mtd_blkcore_priv {
- struct task_struct *thread;
- struct request_queue *rq;
- spinlock_t queue_lock;
-};
static int do_blktrans_request(struct mtd_blktrans_ops *tr,
struct mtd_blktrans_dev *dev,
@@ -78,8 +73,8 @@ static int do_blktrans_request(struct mtd_blktrans_ops *tr,
static int mtd_blktrans_thread(void *arg)
{
- struct mtd_blktrans_ops *tr = arg;
- struct request_queue *rq = tr->blkcore_priv->rq;
+ struct mtd_blktrans_dev *dev = arg;
+ struct request_queue *rq = dev->rq;
struct request *req = NULL;
/* we might get involved when memory gets low, so use PF_MEMALLOC */
@@ -88,7 +83,6 @@ static int mtd_blktrans_thread(void *arg)
spin_lock_irq(rq->queue_lock);
while (!kthread_should_stop()) {
- struct mtd_blktrans_dev *dev;
int res;
if (!req && !(req = blk_fetch_request(rq))) {
@@ -99,13 +93,10 @@ static int mtd_blktrans_thread(void *arg)
continue;
}
- dev = req->rq_disk->private_data;
- tr = dev->tr;
-
spin_unlock_irq(rq->queue_lock);
mutex_lock(&dev->lock);
- res = do_blktrans_request(tr, dev, req);
+ res = do_blktrans_request(dev->tr, dev, req);
mutex_unlock(&dev->lock);
spin_lock_irq(rq->queue_lock);
@@ -124,8 +115,14 @@ static int mtd_blktrans_thread(void *arg)
static void mtd_blktrans_request(struct request_queue *rq)
{
- struct mtd_blktrans_ops *tr = rq->queuedata;
- wake_up_process(tr->blkcore_priv->thread);
+ struct mtd_blktrans_dev *dev = rq->queuedata;
+ struct request *req = NULL;
+
+ if (dev->deleted)
+ while ((req = blk_fetch_request(rq)) != NULL)
+ __blk_end_request_all(req, -ENODEV);
+ else
+ wake_up_process(dev->thread);
}
@@ -133,72 +130,108 @@ static int blktrans_open(struct block_device *bdev, fmode_t mode)
{
struct mtd_blktrans_dev *dev = bdev->bd_disk->private_data;
struct mtd_blktrans_ops *tr = dev->tr;
- int ret = -ENODEV;
+ int ret = 0;
+
+ mutex_lock(&dev->lock);
+ if (dev->open++)
+ goto out;
- if (!get_mtd_device(NULL, dev->mtd->index))
+ if (dev->deleted)
goto out;
+ ret = -ENODEV;
if (!try_module_get(tr->owner))
- goto out_tr;
+ goto out;
- /* FIXME: Locking. A hot pluggable device can go away
- (del_mtd_device can be called for it) without its module
- being unloaded. */
- dev->mtd->usecount++;
+ if (__get_mtd_device(dev->mtd)) {
+ module_put(tr->owner);
+ goto out;
+ }
ret = 0;
if (tr->open && (ret = tr->open(dev))) {
- dev->mtd->usecount--;
- put_mtd_device(dev->mtd);
- out_tr:
module_put(tr->owner);
+ __put_mtd_device(dev->mtd);
+ goto out;
}
out:
+ mutex_unlock(&dev->lock);
return ret;
}
+
static int blktrans_release(struct gendisk *disk, fmode_t mode)
{
struct mtd_blktrans_dev *dev = disk->private_data;
struct mtd_blktrans_ops *tr = dev->tr;
int ret = 0;
- if (tr->release)
- ret = tr->release(dev);
+ mutex_lock(&dev->lock);
+ dev->open--;
+ if (dev->open)
+ goto out;
- if (!ret) {
- dev->mtd->usecount--;
- put_mtd_device(dev->mtd);
+ /* Free the private data */
+ if (dev->deleted) {
module_put(tr->owner);
+ mutex_unlock(&dev->lock);
+ kfree(dev);
+ return 0;
}
+ ret = tr->release ? tr->release(dev) : 0;
+ module_put(tr->owner);
+
+ if(dev->mtd)
+ __put_mtd_device(dev->mtd);
+out:
+ mutex_unlock(&dev->lock);
return ret;
}
static int blktrans_getgeo(struct block_device *bdev, struct hd_geometry *geo)
{
struct mtd_blktrans_dev *dev = bdev->bd_disk->private_data;
+ int error;
+
+ mutex_lock(&dev->lock);
+ error = -ENODEV;
+ if (dev->deleted)
+ goto out;
+
+ error = -ENOTTY;
if (dev->tr->getgeo)
- return dev->tr->getgeo(dev, geo);
- return -ENOTTY;
+ error = dev->tr->getgeo(dev, geo);
+out:
+ mutex_unlock(&dev->lock);
+ return error;
}
static int blktrans_ioctl(struct block_device *bdev, fmode_t mode,
unsigned int cmd, unsigned long arg)
{
struct mtd_blktrans_dev *dev = bdev->bd_disk->private_data;
- struct mtd_blktrans_ops *tr = dev->tr;
+ int error = -ENODEV;
+
+ mutex_lock(&dev->lock);
+
+ if (dev->deleted)
+ goto out;
+
+ error = -ENOTTY;
switch (cmd) {
case BLKFLSBUF:
- if (tr->flush)
- return tr->flush(dev);
- /* The core code did the work, we had nothing to do. */
- return 0;
+ if (dev->tr->flush)
+ error = dev->tr->flush(dev);
+ break;
default:
- return -ENOTTY;
+ break;
}
+out:
+ mutex_unlock(&dev->lock);
+ return error;
}
static const struct block_device_operations mtd_blktrans_ops = {
@@ -215,6 +248,7 @@ int add_mtd_blktrans_dev(struct mtd_blktrans_dev *new)
struct mtd_blktrans_dev *d;
int last_devnum = -1;
struct gendisk *gd;
+ int ret;
if (mutex_trylock(&mtd_table_mutex)) {
mutex_unlock(&mtd_table_mutex);
@@ -240,12 +274,13 @@ int add_mtd_blktrans_dev(struct mtd_blktrans_dev *new)
}
last_devnum = d->devnum;
}
+
+ ret= -EBUSY;
if (new->devnum == -1)
new->devnum = last_devnum+1;
- if ((new->devnum << tr->part_bits) > 256) {
- return -EBUSY;
- }
+ if ((new->devnum << tr->part_bits) > 256)
+ goto error1;
list_add_tail(&new->list, &tr->devs);
added:
@@ -253,11 +288,16 @@ int add_mtd_blktrans_dev(struct mtd_blktrans_dev *new)
if (!tr->writesect)
new->readonly = 1;
+
+ /* Create gendisk */
+ ret = -ENOMEM;
gd = alloc_disk(1 << tr->part_bits);
- if (!gd) {
- list_del(&new->list);
- return -ENOMEM;
- }
+
+ if (!gd)
+ goto error2;
+
+ new->disk = gd;
+ gd->private_data = new;
gd->major = tr->major;
gd->first_minor = (new->devnum) << tr->part_bits;
gd->fops = &mtd_blktrans_ops;
@@ -275,25 +315,58 @@ int add_mtd_blktrans_dev(struct mtd_blktrans_dev *new)
snprintf(gd->disk_name, sizeof(gd->disk_name),
"%s%d", tr->name, new->devnum);
- /* 2.5 has capacity in units of 512 bytes while still
- having BLOCK_SIZE_BITS set to 10. Just to keep us amused. */
set_capacity(gd, (new->size * tr->blksize) >> 9);
- gd->private_data = new;
- new->blkcore_priv = gd;
- gd->queue = tr->blkcore_priv->rq;
+
+ /* Create the request queue */
+ spin_lock_init(&new->queue_lock);
+ new->rq = blk_init_queue(mtd_blktrans_request, &new->queue_lock);
+
+ if (!new->rq)
+ goto error3;
+
+ new->rq->queuedata = new;
+ blk_queue_logical_block_size(new->rq, tr->blksize);
+
+ if (tr->discard)
+ queue_flag_set_unlocked(QUEUE_FLAG_DISCARD,
+ new->rq);
+
+ gd->queue = new->rq;
+
+ /* Create processing thread */
+ /* TODO: workqueue ? */
+ new->thread = kthread_run(mtd_blktrans_thread, new,
+ "%s%d", tr->name, new->mtd->index);
+ if (IS_ERR(new->thread)) {
+ ret = PTR_ERR(new->thread);
+ goto error4;
+ }
+
gd->driverfs_dev = &new->mtd->dev;
if (new->readonly)
set_disk_ro(gd, 1);
+ new->open = 0;
add_disk(gd);
return 0;
+error4:
+ blk_cleanup_queue(new->rq);
+error3:
+ put_disk(new->disk);
+error2:
+ list_del(&new->list);
+error1:
+ kfree(new);
+ return ret;
}
int del_mtd_blktrans_dev(struct mtd_blktrans_dev *old)
{
+ unsigned long flags;
+
if (mutex_trylock(&mtd_table_mutex)) {
mutex_unlock(&mtd_table_mutex);
BUG();
@@ -301,9 +374,35 @@ int del_mtd_blktrans_dev(struct mtd_blktrans_dev *old)
list_del(&old->list);
- del_gendisk(old->blkcore_priv);
- put_disk(old->blkcore_priv);
+ /* stop new requests to arrive */
+ del_gendisk(old->disk);
+
+
+ /* flush current requests */
+ spin_lock_irqsave(&old->queue_lock, flags);
+ old->deleted = 1;
+ blk_start_queue(old->rq);
+ spin_unlock_irqrestore(&old->queue_lock, flags);
+
+
+ /* Stop the thread */
+ kthread_stop(old->thread);
+ /* Tell trans driver to release the device */
+ mutex_lock(&old->lock);
+
+ if (old->open) {
+ if (old->tr->release)
+ old->tr->release(old);
+ __put_mtd_device(old->mtd);
+ }
+
+ /* From now on, no calls into trans can be made */
+ /* Mtd device will be gone real soon now */
+ old->mtd = NULL;
+ mutex_unlock(&old->lock);
+
+ blk_cleanup_queue(old->rq);
return 0;
}
@@ -344,9 +443,6 @@ int register_mtd_blktrans(struct mtd_blktrans_ops *tr)
if (!blktrans_notifier.list.next)
register_mtd_user(&blktrans_notifier);
- tr->blkcore_priv = kzalloc(sizeof(*tr->blkcore_priv), GFP_KERNEL);
- if (!tr->blkcore_priv)
- return -ENOMEM;
mutex_lock(&mtd_table_mutex);
@@ -354,39 +450,12 @@ int register_mtd_blktrans(struct mtd_blktrans_ops *tr)
if (ret) {
printk(KERN_WARNING "Unable to register %s block device on major %d: %d\n",
tr->name, tr->major, ret);
- kfree(tr->blkcore_priv);
mutex_unlock(&mtd_table_mutex);
return ret;
}
- spin_lock_init(&tr->blkcore_priv->queue_lock);
-
- tr->blkcore_priv->rq = blk_init_queue(mtd_blktrans_request, &tr->blkcore_priv->queue_lock);
- if (!tr->blkcore_priv->rq) {
- unregister_blkdev(tr->major, tr->name);
- kfree(tr->blkcore_priv);
- mutex_unlock(&mtd_table_mutex);
- return -ENOMEM;
- }
-
- tr->blkcore_priv->rq->queuedata = tr;
- blk_queue_logical_block_size(tr->blkcore_priv->rq, tr->blksize);
- if (tr->discard)
- queue_flag_set_unlocked(QUEUE_FLAG_DISCARD,
- tr->blkcore_priv->rq);
tr->blkshift = ffs(tr->blksize) - 1;
- tr->blkcore_priv->thread = kthread_run(mtd_blktrans_thread, tr,
- "%sd", tr->name);
- if (IS_ERR(tr->blkcore_priv->thread)) {
- int ret = PTR_ERR(tr->blkcore_priv->thread);
- blk_cleanup_queue(tr->blkcore_priv->rq);
- unregister_blkdev(tr->major, tr->name);
- kfree(tr->blkcore_priv);
- mutex_unlock(&mtd_table_mutex);
- return ret;
- }
-
INIT_LIST_HEAD(&tr->devs);
list_add(&tr->list, &blktrans_majors);
@@ -406,8 +475,6 @@ int deregister_mtd_blktrans(struct mtd_blktrans_ops *tr)
mutex_lock(&mtd_table_mutex);
- /* Clean up the kernel thread */
- kthread_stop(tr->blkcore_priv->thread);
/* Remove it from the list of active majors */
list_del(&tr->list);
@@ -415,13 +482,9 @@ int deregister_mtd_blktrans(struct mtd_blktrans_ops *tr)
list_for_each_entry_safe(dev, next, &tr->devs, list)
tr->remove_dev(dev);
- blk_cleanup_queue(tr->blkcore_priv->rq);
unregister_blkdev(tr->major, tr->name);
-
mutex_unlock(&mtd_table_mutex);
- kfree(tr->blkcore_priv);
-
BUG_ON(!list_empty(&tr->devs));
return 0;
}
diff --git a/drivers/mtd/mtdblock.c b/drivers/mtd/mtdblock.c
index 9f41b1a..d8322cc 100644
--- a/drivers/mtd/mtdblock.c
+++ b/drivers/mtd/mtdblock.c
@@ -368,7 +368,6 @@ static void mtdblock_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
static void mtdblock_remove_dev(struct mtd_blktrans_dev *dev)
{
del_mtd_blktrans_dev(dev);
- kfree(dev);
}
static struct mtd_blktrans_ops mtdblock_tr = {
diff --git a/drivers/mtd/mtdblock_ro.c b/drivers/mtd/mtdblock_ro.c
index 852165f..54ff288 100644
--- a/drivers/mtd/mtdblock_ro.c
+++ b/drivers/mtd/mtdblock_ro.c
@@ -49,7 +49,6 @@ static void mtdblock_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
static void mtdblock_remove_dev(struct mtd_blktrans_dev *dev)
{
del_mtd_blktrans_dev(dev);
- kfree(dev);
}
static struct mtd_blktrans_ops mtdblock_tr = {
diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c
index e9daf87..8c9c020 100644
--- a/drivers/mtd/mtdcore.c
+++ b/drivers/mtd/mtdcore.c
@@ -532,7 +532,6 @@ void put_mtd_device(struct mtd_info *mtd)
__put_mtd_device(mtd);
mutex_unlock(&mtd_table_mutex);
- module_put(mtd->owner);
}
void __put_mtd_device(struct mtd_info *mtd)
@@ -542,6 +541,8 @@ void __put_mtd_device(struct mtd_info *mtd)
if (mtd->put_device)
mtd->put_device(mtd);
+
+ module_put(mtd->owner);
}
/* default_mtd_writev - default mtd writev method for MTD devices that
diff --git a/drivers/mtd/nftlcore.c b/drivers/mtd/nftlcore.c
index 1002e18..a4578bf 100644
--- a/drivers/mtd/nftlcore.c
+++ b/drivers/mtd/nftlcore.c
@@ -126,7 +126,6 @@ static void nftl_remove_dev(struct mtd_blktrans_dev *dev)
del_mtd_blktrans_dev(dev);
kfree(nftl->ReplUnitTable);
kfree(nftl->EUNtable);
- kfree(nftl);
}
/*
diff --git a/drivers/mtd/rfd_ftl.c b/drivers/mtd/rfd_ftl.c
index d2aa9c4..63b83c0 100644
--- a/drivers/mtd/rfd_ftl.c
+++ b/drivers/mtd/rfd_ftl.c
@@ -817,7 +817,6 @@ static void rfd_ftl_remove_dev(struct mtd_blktrans_dev *dev)
vfree(part->sector_map);
kfree(part->header_cache);
kfree(part->blocks);
- kfree(part);
}
static struct mtd_blktrans_ops rfd_ftl_tr = {
diff --git a/drivers/mtd/ssfdc.c b/drivers/mtd/ssfdc.c
index 3f67e00..81c4ecd 100644
--- a/drivers/mtd/ssfdc.c
+++ b/drivers/mtd/ssfdc.c
@@ -375,7 +375,6 @@ static void ssfdcr_remove_dev(struct mtd_blktrans_dev *dev)
del_mtd_blktrans_dev(dev);
kfree(ssfdc->logic_block_map);
- kfree(ssfdc);
}
static int ssfdcr_readsect(struct mtd_blktrans_dev *dev,
diff --git a/include/linux/mtd/blktrans.h b/include/linux/mtd/blktrans.h
index 8b4aa05..054b053 100644
--- a/include/linux/mtd/blktrans.h
+++ b/include/linux/mtd/blktrans.h
@@ -24,10 +24,15 @@ struct mtd_blktrans_dev {
int devnum;
unsigned long size;
int readonly;
- void *blkcore_priv; /* gendisk in 2.5, devfs_handle in 2.4 */
-};
+ int deleted;
+ int open;
-struct blkcore_priv; /* Differs for 2.4 and 2.5 kernels; private */
+ struct gendisk *disk;
+ struct task_struct *thread;
+ struct request_queue *rq;
+ spinlock_t queue_lock;
+ void *priv;
+};
struct mtd_blktrans_ops {
char *name;
@@ -60,8 +65,6 @@ struct mtd_blktrans_ops {
struct list_head devs;
struct list_head list;
struct module *owner;
-
- struct mtd_blkcore_priv *blkcore_priv;
};
extern int register_mtd_blktrans(struct mtd_blktrans_ops *tr);
--
1.6.3.3
>From 23892d5e002d63b7980742fa5445443547fcf8cf Mon Sep 17 00:00:00 2001
From: Maxim Levitsky <[email protected]>
Date: Tue, 5 Jan 2010 23:25:19 +0200
Subject: [PATCH 4/9] mtd-make mtdtrans thread suspend friendly
Signed-of-by: Maxim Levitsky <[email protected]>
---
drivers/mtd/mtd_blkdevs.c | 15 +++++++--------
1 files changed, 7 insertions(+), 8 deletions(-)
diff --git a/drivers/mtd/mtd_blkdevs.c b/drivers/mtd/mtd_blkdevs.c
index 6af4673..7070d35 100644
--- a/drivers/mtd/mtd_blkdevs.c
+++ b/drivers/mtd/mtd_blkdevs.c
@@ -20,6 +20,7 @@
#include <linux/init.h>
#include <linux/mutex.h>
#include <linux/kthread.h>
+#include <linux/freezer.h>
#include <asm/uaccess.h>
#include "mtdcore.h"
@@ -79,37 +80,35 @@ static int mtd_blktrans_thread(void *arg)
/* we might get involved when memory gets low, so use PF_MEMALLOC */
current->flags |= PF_MEMALLOC;
-
- spin_lock_irq(rq->queue_lock);
+ set_freezable();
while (!kthread_should_stop()) {
int res;
+ try_to_freeze();
+
+ spin_lock_irq(rq->queue_lock);
if (!req && !(req = blk_fetch_request(rq))) {
set_current_state(TASK_INTERRUPTIBLE);
spin_unlock_irq(rq->queue_lock);
schedule();
- spin_lock_irq(rq->queue_lock);
continue;
}
-
spin_unlock_irq(rq->queue_lock);
+
mutex_lock(&dev->lock);
res = do_blktrans_request(dev->tr, dev, req);
mutex_unlock(&dev->lock);
spin_lock_irq(rq->queue_lock);
-
if (!__blk_end_request_cur(req, res))
req = NULL;
+ spin_unlock_irq(rq->queue_lock);
}
if (req)
__blk_end_request_all(req, -EIO);
-
- spin_unlock_irq(rq->queue_lock);
-
return 0;
}
--
1.6.3.3
>From 42a44a48f270f45e44595b6f3906df4a6cf28ba5 Mon Sep 17 00:00:00 2001
From: Maxim Levitsky <[email protected]>
Date: Tue, 5 Jan 2010 23:25:20 +0200
Subject: [PATCH 5/9] NAND: export nand_do_read_oob and nand_do_write_oob
Drivers might need sane way to read/write oob area, but can't
use mtd interface due to locking
Signed-off-by: Maxim Levitsky <[email protected]>
---
drivers/mtd/nand/nand_base.c | 10 +++++-----
include/linux/mtd/nand.h | 7 +++++++
2 files changed, 12 insertions(+), 5 deletions(-)
diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c
index 2957cc7..787e751 100644
--- a/drivers/mtd/nand/nand_base.c
+++ b/drivers/mtd/nand/nand_base.c
@@ -99,9 +99,6 @@ static struct nand_ecclayout nand_oob_128 = {
static int nand_get_device(struct nand_chip *chip, struct mtd_info *mtd,
int new_state);
-static int nand_do_write_oob(struct mtd_info *mtd, loff_t to,
- struct mtd_oob_ops *ops);
-
/*
* For devices which display every fart in the system on a separate LED. Is
* compiled away when LED support is disabled.
@@ -1458,7 +1455,7 @@ static int nand_write_oob_syndrome(struct mtd_info *mtd,
*
* NAND read out-of-band data from the spare area
*/
-static int nand_do_read_oob(struct mtd_info *mtd, loff_t from,
+int nand_do_read_oob(struct mtd_info *mtd, loff_t from,
struct mtd_oob_ops *ops)
{
int page, realpage, chipnr, sndcmd = 1;
@@ -1993,7 +1990,7 @@ static int nand_write(struct mtd_info *mtd, loff_t to, size_t len,
*
* NAND write out-of-band
*/
-static int nand_do_write_oob(struct mtd_info *mtd, loff_t to,
+int nand_do_write_oob(struct mtd_info *mtd, loff_t to,
struct mtd_oob_ops *ops)
{
int chipnr, page, status, len;
@@ -2962,6 +2959,9 @@ EXPORT_SYMBOL_GPL(nand_scan);
EXPORT_SYMBOL_GPL(nand_scan_ident);
EXPORT_SYMBOL_GPL(nand_scan_tail);
EXPORT_SYMBOL_GPL(nand_release);
+EXPORT_SYMBOL_GPL(nand_do_read_oob);
+EXPORT_SYMBOL_GPL(nand_do_write_oob);
+
static int __init nand_base_init(void)
{
diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h
index 7a232a9..7c3ff57 100644
--- a/include/linux/mtd/nand.h
+++ b/include/linux/mtd/nand.h
@@ -548,6 +548,13 @@ extern int nand_erase_nand(struct mtd_info *mtd, struct erase_info *instr,
extern int nand_do_read(struct mtd_info *mtd, loff_t from, size_t len,
size_t * retlen, uint8_t * buf);
+extern int nand_do_read_oob(struct mtd_info *mtd, loff_t from,
+ struct mtd_oob_ops *ops);
+
+extern int nand_do_write_oob(struct mtd_info *mtd, loff_t to,
+ struct mtd_oob_ops *ops);
+
+
/*
* Constants for oob configuration
*/
--
1.6.3.3
>From 06cb6f7fa6d9c708ca0a208b33c6e3b5969aab44 Mon Sep 17 00:00:00 2001
From: Maxim Levitsky <[email protected]>
Date: Wed, 6 Jan 2010 22:52:56 +0200
Subject: [PATCH 6/9] mtd: common module for smartmedia/xD support
---
drivers/mtd/Kconfig | 9 ++
drivers/mtd/Makefile | 1 +
drivers/mtd/sm_common.c | 208 +++++++++++++++++++++++++++++++++++++++++++++++
drivers/mtd/sm_common.h | 31 +++++++
4 files changed, 249 insertions(+), 0 deletions(-)
create mode 100644 drivers/mtd/sm_common.c
create mode 100644 drivers/mtd/sm_common.h
diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig
index ecf90f5..ebeabd6 100644
--- a/drivers/mtd/Kconfig
+++ b/drivers/mtd/Kconfig
@@ -25,6 +25,15 @@ config MTD_DEBUG_VERBOSE
help
Determines the verbosity level of the MTD debugging messages.
+config MTD_NAND_SMARTMEDIA
+ boolean
+
+config MTD_SM_COMMON
+ depends on MTD_NAND
+ select MTD_NAND_SMARTMEDIA
+ tristate
+ default n
+
config MTD_TESTS
tristate "MTD tests support"
depends on m
diff --git a/drivers/mtd/Makefile b/drivers/mtd/Makefile
index 82d1e4d..02c5b17 100644
--- a/drivers/mtd/Makefile
+++ b/drivers/mtd/Makefile
@@ -7,6 +7,7 @@ obj-$(CONFIG_MTD) += mtd.o
mtd-y := mtdcore.o mtdsuper.o mtdbdi.o
mtd-$(CONFIG_MTD_PARTITIONS) += mtdpart.o
+obj-$(CONFIG_MTD_SM_COMMON) += sm_common.o
obj-$(CONFIG_MTD_CONCAT) += mtdconcat.o
obj-$(CONFIG_MTD_REDBOOT_PARTS) += redboot.o
obj-$(CONFIG_MTD_CMDLINE_PARTS) += cmdlinepart.o
diff --git a/drivers/mtd/sm_common.c b/drivers/mtd/sm_common.c
new file mode 100644
index 0000000..3d57f40
--- /dev/null
+++ b/drivers/mtd/sm_common.c
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2009 - Maxim Levitsky
+ * Common routines & support for xD format
+ *
+ * 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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/bitops.h>
+#include <linux/log2.h>
+#include "sm_common.h"
+
+#if 0
+static struct nand_ecclayout nand_oob_sm = {
+ .eccbytes = 6,
+ .eccpos = {8, 9, 10, 13, 14, 15},
+ .oobfree = {
+ {.offset = 0 , .length = 4}, /* reserved */
+ {.offset = 4 , .length = 2}, /* data & block status */
+ {.offset = 6 , .length = 2}, /* LBA1 */
+ {.offset = 11, .length = 2} /* LBA2 */
+ }
+};
+#endif
+
+static struct nand_ecclayout nand_oob_sm = {
+ .eccbytes = 0,
+ .oobfree = {
+ {.offset = 0 , .length = 16}, /* reserved */
+ }
+};
+
+static int sm_get_lba(u8 *lba)
+{
+ /* check fixed bits */
+ if ((lba[0] & 0xF8) != 0x10)
+ return -2;
+
+ /* check parity - endianess doesn't matter */
+ if (hweight16(*(u16*)lba) & 1)
+ return -2;
+
+ return (lba[1] >> 1) | ((lba[0] & 0x07) << 7);
+}
+
+
+/*
+ * Read LBA asscociated with block
+ * returns -1, if block is erased
+ * returns -2 if error happens
+ */
+int sm_read_lba(struct sm_oob *oob)
+{
+ static const u32 erased_pattern[4] =
+ { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF };
+
+ u16 lba_test;
+ int lba;
+ //int i;
+
+ /* First test for erased block */
+ if (!memcmp(oob, erased_pattern, sizeof(erased_pattern)))
+ return -1;
+
+ /* Now check is both copies of the LBA differ too much */
+ lba_test = *(u16*)oob->lba_copy1 ^ *(u16*)oob->lba_copy2;
+ if (lba_test && !is_power_of_2(lba_test))
+ return -2;
+
+
+ /* And read it */
+ if ((lba = sm_get_lba(oob->lba_copy1)) == -2)
+ lba = sm_get_lba(oob->lba_copy2);
+
+ return lba;
+}
+EXPORT_SYMBOL_GPL(sm_read_lba);
+
+void sm_write_lba(struct sm_oob *oob, u16 lba)
+{
+ u8 tmp[2];
+
+ WARN_ON(lba > 1000);
+
+ tmp[0] = 0x10 | ((lba >> 7) & 0x07);
+ tmp[1] = (lba << 1) & 0xFF;
+
+ if(hweight16(*(u16*)tmp) & 0x01)
+ tmp[1] |= 1;
+
+ oob->lba_copy1[0] = oob->lba_copy2[0] = tmp[0];
+ oob->lba_copy1[1] = oob->lba_copy2[1] = tmp[1];
+}
+EXPORT_SYMBOL_GPL(sm_write_lba);
+
+/* Test if whole block is valid, or at least is marked as such...*/
+int sm_block_valid(struct sm_oob *oob)
+{
+ /* test block status */
+ if (hweight16(oob->block_status) < 7)
+ return -EIO;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(sm_block_valid);
+
+
+/* Test if sector is valid */
+int sm_sector_valid(struct sm_oob *oob)
+{
+ /* test data status */
+ if (hweight16(oob->data_status) < 5)
+ return -EIO;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(sm_sector_valid);
+
+
+
+/* Nand interface helpers */
+static int sm_block_bad(struct mtd_info *mtd, loff_t ofs, int getchip)
+{
+ struct nand_chip *chip = (struct nand_chip *)mtd->priv;
+ struct mtd_oob_ops ops;
+ struct sm_oob oob;
+ int ret;
+
+ ops.mode = MTD_OOB_RAW;
+ ops.ooboffs = 0;
+ ops.ooblen = SM_OOB_SIZE;
+ ops.oobbuf = (void*)&oob;
+ ops.datbuf = NULL;
+
+ /* TODO: This doesn't take controller lock,
+ but we know that all xD cards are single chip. */
+ if (getchip)
+ chip->select_chip(mtd, 0);
+
+ ret = nand_do_read_oob(mtd, ofs, &ops);
+ if (ret < 0 || ops.oobretlen != SM_OOB_SIZE)
+ goto out;
+
+ ret = sm_block_valid(&oob) ? 1 : 0;
+out:
+ if (getchip)
+ chip->select_chip(mtd, -1);
+
+ if (ret)
+ printk(KERN_NOTICE "sm_common: bad sector at %i\n", (int)ofs);
+ return ret;
+}
+
+static int sm_block_markbad(struct mtd_info *mtd, loff_t ofs)
+{
+ struct mtd_oob_ops ops;
+ struct sm_oob oob;
+ int ret;
+
+ memset (&oob, -1, sizeof(oob));
+ oob.block_status = 0xF0;
+
+ ops.mode = MTD_OOB_RAW;
+ ops.ooboffs = 0;
+ ops.ooblen = SM_OOB_SIZE;
+ ops.oobbuf = (void*)&oob;
+ ops.datbuf = NULL;
+
+ ret = nand_do_write_oob(mtd, ofs, &ops);
+ if (ret < 0 || ops.oobretlen != SM_OOB_SIZE) {
+ printk(KERN_NOTICE "sm_common: can't mark sector at %i as bad\n",
+ (int)ofs);
+ return 1;
+ }
+
+ return 0;
+}
+
+int sm_register_device(struct mtd_info *mtd)
+{
+ struct nand_chip *chip = (struct nand_chip *)mtd->priv;
+
+ chip->options |= NAND_SKIP_BBTSCAN | NAND_NO_AUTOINCR;
+
+ chip->block_bad = sm_block_bad;
+ chip->block_markbad = sm_block_markbad;
+ chip->ecc.layout = &nand_oob_sm;
+
+ if (nand_scan(mtd, 1))
+ return -ENODEV;
+
+ /* TODO: support small page devices */
+ if (mtd->writesize != 512)
+ return -ENODEV;
+
+
+ if (add_mtd_device(mtd))
+ return -ENODEV;
+
+ return 0;
+}
+
+EXPORT_SYMBOL_GPL(sm_register_device);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Maxim Levitsky <[email protected]>");
+MODULE_DESCRIPTION("Common SmartMedia/xD functions");
\ No newline at end of file
diff --git a/drivers/mtd/sm_common.h b/drivers/mtd/sm_common.h
new file mode 100644
index 0000000..d546770
--- /dev/null
+++ b/drivers/mtd/sm_common.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2009 - Maxim Levitsky
+ * Common routines & support for SmartMedia/xD format
+ *
+ * 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.
+ */
+
+#include <linux/mtd/nand.h>
+
+struct sm_oob {
+ u32 reserved;
+ u8 data_status;
+ u8 block_status;
+ u8 lba_copy1[2];
+ u8 ecc2[3];
+ u8 lba_copy2[2];
+ u8 ecc1[3];
+} __attribute__((packed));
+
+
+#define SM_SECTOR_SIZE 512
+#define SM_OOB_SIZE 16
+#define SM_SMALL_PAGE 256
+
+extern int sm_register_device(struct mtd_info *mtd);
+extern int sm_read_lba(struct sm_oob *oob);
+extern void sm_write_lba(struct sm_oob *oob, u16 lba);
+extern int sm_block_valid(struct sm_oob *oob);
+extern int sm_sector_valid(struct sm_oob *oob);
\ No newline at end of file
--
1.6.3.3
>From 3385017902902c80ce2110e7c5ae02abf296b8ab Mon Sep 17 00:00:00 2001
From: Maxim Levitsky <[email protected]>
Date: Wed, 6 Jan 2010 22:53:00 +0200
Subject: [PATCH 7/9] NAND: add few workarounds for SmartMedia/xD chips.
* Add seperate ID table
* Add support for mask rom devices
* Workaround for write protect status
Signed-off-by: Maxim Levitsky <[email protected]>
---
drivers/mtd/nand/nand_base.c | 29 +++++++++++++++++++++--------
drivers/mtd/nand/nand_ids.c | 39 +++++++++++++++++++++++++++++++++++++++
drivers/mtd/sm_common.c | 2 +-
include/linux/mtd/nand.h | 11 +++++++++++
4 files changed, 72 insertions(+), 9 deletions(-)
diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c
index 787e751..887b779 100644
--- a/drivers/mtd/nand/nand_base.c
+++ b/drivers/mtd/nand/nand_base.c
@@ -398,9 +398,17 @@ static int nand_default_block_markbad(struct mtd_info *mtd, loff_t ofs)
static int nand_check_wp(struct mtd_info *mtd)
{
struct nand_chip *chip = mtd->priv;
+ int wp;
+
/* Check the WP bit */
chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
- return (chip->read_byte(mtd) & NAND_STATUS_WP) ? 0 : 1;
+ wp = (chip->read_byte(mtd) & NAND_STATUS_WP) ? 0 : 1;
+
+ /* broken xD cards report WP despite beeing writable */
+ if (chip->options & NAND_BROKEN_XD)
+ return 0;
+
+ return wp;
}
/**
@@ -2505,14 +2513,18 @@ static struct nand_flash_dev *nand_get_flash_type(struct mtd_info *mtd,
}
/* Lookup the flash id */
- for (i = 0; nand_flash_ids[i].name != NULL; i++) {
- if (dev_id == nand_flash_ids[i].id) {
- type = &nand_flash_ids[i];
+#ifdef CONFIG_MTD_NAND_SMARTMEDIA
+ if (chip->options & NAND_SMARTMEDIA)
+ type = nand_smartmedia_flash_ids;
+ else
+#endif
+ type = nand_flash_ids;
+
+ for (i = 0; type->name != NULL; type++)
+ if (dev_id == type->id)
break;
- }
- }
- if (!type)
+ if (!type->name)
return ERR_PTR(-ENODEV);
if (!mtd->name)
@@ -2868,7 +2880,8 @@ int nand_scan_tail(struct mtd_info *mtd)
/* Fill in remaining MTD driver data */
mtd->type = MTD_NANDFLASH;
- mtd->flags = MTD_CAP_NANDFLASH;
+ mtd->flags = chip->options & NAND_ROM ? MTD_CAP_ROM:
+ MTD_CAP_NANDFLASH;
mtd->erase = nand_erase;
mtd->point = NULL;
mtd->unpoint = NULL;
diff --git a/drivers/mtd/nand/nand_ids.c b/drivers/mtd/nand/nand_ids.c
index 69ee2c9..a24682d 100644
--- a/drivers/mtd/nand/nand_ids.c
+++ b/drivers/mtd/nand/nand_ids.c
@@ -127,6 +127,45 @@ struct nand_flash_dev nand_flash_ids[] = {
{NULL,}
};
+#ifdef CONFIG_MTD_NAND_SMARTMEDIA
+struct nand_flash_dev nand_smartmedia_flash_ids[] = {
+
+ /* SmartMedia */
+ {"SmartMedia 1MiB 5V", 0x6e, 256, 1, 0x1000, 0},
+ {"SmartMedia 1MiB 3,3V", 0xe8, 256, 1, 0x1000, 0},
+ {"SmartMedia 1MiB 3,3V", 0xec, 256, 1, 0x1000, 0},
+ {"SmartMedia 2MiB 3,3V", 0xea, 256, 2, 0x1000, 0},
+ {"SmartMedia 2MiB 5V", 0x64, 256, 2, 0x1000, 0},
+ {"SmartMedia 2MiB 3,3V ROM", 0x5d, 512, 2, 0x2000, NAND_ROM},
+ {"SmartMedia 4MiB 3,3V", 0xe3, 512, 4, 0x2000, 0},
+ {"SmartMedia 4MiB 3,3/5V", 0xe5, 512, 4, 0x2000, 0},
+ {"SmartMedia 4MiB 5V", 0x6b, 512, 4, 0x2000, 0},
+ {"SmartMedia 4MiB 3,3V ROM", 0xd5, 512, 4, 0x2000, NAND_ROM},
+ {"SmartMedia 8MiB 3,3V", 0xe6, 512, 8, 0x2000, 0},
+ {"SmartMedia 8MiB 3,3V ROM", 0xd6, 512, 8, 0x2000, NAND_ROM},
+
+ /* xD / SmartMedia */
+ {"SmartMedia/xD 16MiB 3,3V", 0x73, 512, 16, 0x4000, 0},
+ {"SmartMedia 16MiB 3,3V ROM", 0x57, 512, 16, 0x4000, NAND_ROM},
+ {"SmartMedia/xD 32MiB 3,3V", 0x75, 512, 32, 0x4000, 0},
+ {"SmartMedia 32MiB 3,3V ROM", 0x58, 512, 32, 0x4000, NAND_ROM},
+ {"SmartMedia/xD 64MiB 3,3V", 0x76, 512, 64, 0x4000, 0},
+ {"SmartMedia 64MiB 3,3V ROM", 0xd9, 512, 64, 0x4000, NAND_ROM},
+ {"SmartMedia/xD 128MiB 3,3V", 0x79, 512, 128, 0x4000, 0},
+ {"SmartMedia 128MiB 3,3V ROM", 0xda, 512, 128, 0x4000, NAND_ROM},
+ {"SmartMedia/xD 256MiB 3,3V", 0x71, 512, 256, 0x4000, NAND_BROKEN_XD},
+ {"SmartMedia 256MiB 3,3V ROM", 0x5b, 512, 256, 0x4000, NAND_ROM},
+
+ /* xD only */
+ {"xD 512MiB 3,3V", 0xDC, 512, 512, 0x4000, NAND_BROKEN_XD},
+ {"xD 1GiB 3,3V", 0xD3, 512, 1024, 0x4000,NAND_BROKEN_XD},
+ {"xD 2GiB 3,3V", 0xD5, 512, 2048, 0x4000,NAND_BROKEN_XD},
+
+ {NULL,}
+};
+EXPORT_SYMBOL(nand_smartmedia_flash_ids);
+#endif
+
/*
* Manufacturer ID list
*/
diff --git a/drivers/mtd/sm_common.c b/drivers/mtd/sm_common.c
index 3d57f40..022d8a0 100644
--- a/drivers/mtd/sm_common.c
+++ b/drivers/mtd/sm_common.c
@@ -181,7 +181,7 @@ int sm_register_device(struct mtd_info *mtd)
{
struct nand_chip *chip = (struct nand_chip *)mtd->priv;
- chip->options |= NAND_SKIP_BBTSCAN | NAND_NO_AUTOINCR;
+ chip->options |= NAND_SKIP_BBTSCAN | NAND_SMARTMEDIA | NAND_NO_AUTOINCR;
chip->block_bad = sm_block_bad;
chip->block_markbad = sm_block_markbad;
diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h
index 7c3ff57..13d8344 100644
--- a/include/linux/mtd/nand.h
+++ b/include/linux/mtd/nand.h
@@ -168,6 +168,11 @@ typedef enum {
/* Chip does not allow subpage writes */
#define NAND_NO_SUBPAGE_WRITE 0x00000200
+/* Device is one of 'new' xD cards that expose fake nand command set */
+#define NAND_BROKEN_XD 0x00000400
+
+/* Device behaves just like nand, but is readonly */
+#define NAND_ROM 0x00000800
/* Options valid for Samsung large page devices */
#define NAND_SAMSUNG_LP_OPTIONS \
@@ -194,6 +199,11 @@ typedef enum {
/* This option is defined if the board driver allocates its own buffers
(e.g. because it needs them DMA-coherent */
#define NAND_OWN_BUFFERS 0x00040000
+
+/* controller supports only xD/SmartMedia cards*/
+#define NAND_SMARTMEDIA 0x00080000
+
+
/* Options set by nand scan */
/* Nand scan has allocated controller struct */
#define NAND_CONTROLLER_ALLOC 0x80000000
@@ -468,6 +478,7 @@ struct nand_manufacturers {
};
extern struct nand_flash_dev nand_flash_ids[];
+extern struct nand_flash_dev nand_smartmedia_flash_ids[];
extern struct nand_manufacturers nand_manuf_ids[];
/**
--
1.6.3.3
>From d9e0e1a8cb54011ed2775aa82e71e1a5ba3b5880 Mon Sep 17 00:00:00 2001
From: Maxim Levitsky <[email protected]>
Date: Wed, 6 Jan 2010 23:06:24 +0200
Subject: [PATCH 8/9] mtd: SmartMedia/xD FTL
---
drivers/mtd/Kconfig | 12 +
drivers/mtd/Makefile | 1 +
drivers/mtd/sm_ftl.c | 1070 ++++++++++++++++++++++++++++++++++++++++++++++++++
drivers/mtd/sm_ftl.h | 79 ++++
4 files changed, 1162 insertions(+), 0 deletions(-)
create mode 100644 drivers/mtd/sm_ftl.c
create mode 100644 drivers/mtd/sm_ftl.h
diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig
index ebeabd6..e13bf41 100644
--- a/drivers/mtd/Kconfig
+++ b/drivers/mtd/Kconfig
@@ -313,6 +313,18 @@ config SSFDC
This enables read only access to SmartMedia formatted NAND
flash. You can mount it with FAT file system.
+
+config SM_FTL
+ tristate "SmartMedia/xD new translation layer"
+ depends on EXPERIMENTAL
+ select MTD_SM_COMMON
+ help
+ This enables new and very EXPERMENTAL support for SmartMedia/xD
+ FTL (Flash tanslation layer)
+ Write support isn't yet well tested, therefore this code IS likely to
+ eat your card, so please don't use it together with valuable data.
+ Use readonly driver (CONFIG_SSFDC) instead.
+
config MTD_OOPS
tristate "Log panic/oops to an MTD buffer"
depends on MTD
diff --git a/drivers/mtd/Makefile b/drivers/mtd/Makefile
index 02c5b17..02f6375 100644
--- a/drivers/mtd/Makefile
+++ b/drivers/mtd/Makefile
@@ -25,6 +25,7 @@ obj-$(CONFIG_NFTL) += nftl.o
obj-$(CONFIG_INFTL) += inftl.o
obj-$(CONFIG_RFD_FTL) += rfd_ftl.o
obj-$(CONFIG_SSFDC) += ssfdc.o
+obj-$(CONFIG_SM_FTL) += sm_ftl.o
obj-$(CONFIG_MTD_OOPS) += mtdoops.o
nftl-objs := nftlcore.o nftlmount.o
diff --git a/drivers/mtd/sm_ftl.c b/drivers/mtd/sm_ftl.c
new file mode 100644
index 0000000..41b52ac
--- /dev/null
+++ b/drivers/mtd/sm_ftl.c
@@ -0,0 +1,1070 @@
+/*
+ * Copyright (C) 2009 - Maxim Levitsky
+ * SmartMedia/xD translation layer
+ *
+ * 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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/random.h>
+#include <linux/hdreg.h>
+#include <linux/kthread.h>
+#include <linux/freezer.h>
+#include <asm/bitops.h>
+#include "sm_common.h"
+#include "sm_ftl.h"
+
+static u8 tmp_buffer[SM_SECTOR_SIZE];
+static int cache_size = 5;
+
+module_param(cache_size, int, S_IRUGO);
+MODULE_PARM_DESC(cache_size,
+ "Number of blocks to hold in the cache (5 default)");
+
+
+static void sm_erase_callback (struct erase_info *self);
+static void sm_erase_block(struct sm_ftl *ftl, int zone_num, s16 block,
+ int put_free);
+static void sm_mark_block_bad(struct sm_ftl *ftl, int zone_num, int block);
+static int sm_cache_flush_thread(void *data);
+
+
+static const struct chs_entry chs_table[] = {
+ { 1, 125, 4, 4 },
+ { 2, 125, 4, 8 },
+ { 4, 250, 4, 8 },
+ { 8, 250, 4, 16 },
+ { 16, 500, 4, 16 },
+ { 32, 500, 8, 16 },
+ { 64, 500, 8, 32 },
+ { 128, 500, 16, 32 },
+ { 256, 1000, 16, 32 },
+ { 512, 1015, 32, 63 },
+ { 1024, 985, 33, 63 },
+ { 2048, 985, 33, 63 },
+ { 0 },
+};
+
+/* Find out media parameters.
+ * This ideally has to be based on nand id, but for now device size is enough */
+int sm_get_media_info (struct sm_ftl *ftl, struct mtd_info *mtd)
+{
+ int i;
+ int size_in_megs = mtd->size / (1024 * 1024);
+ ftl->readonly = mtd->type == MTD_ROM;
+
+ /* Manual settings for very old devices */
+ ftl->zone_count = 1;
+ ftl->smallpagenand = 0;
+
+ switch (size_in_megs){
+ case 1:
+ /* 1 Mb flas/rom SmartMedia card (256 byte pages)*/
+ ftl->zone_size = 256;
+ ftl->max_lba = 250;
+ ftl->block_size = 8 * SM_SECTOR_SIZE;
+ ftl->smallpagenand = 1;
+
+ break;
+ case 2:
+ /* 2 Mb flash SmartMedia (256 byte pages)*/
+ if (!mtd->writesize == SM_SMALL_PAGE) {
+ ftl->zone_size = 512;
+ ftl->max_lba = 500;
+ ftl->block_size = 8 * SM_SECTOR_SIZE;
+ ftl->smallpagenand = 1;
+ /* 2 Mb rom SmartMedia */
+ } else {
+ ftl->zone_size = 256;
+ ftl->max_lba = 250;
+ ftl->block_size = 16 * SM_SECTOR_SIZE;
+ }
+ break;
+ case 4:
+ /* 4 Mb flash/rom SmartMedia device */
+ ftl->zone_size = 512;
+ ftl->max_lba = 500;
+ ftl->block_size = 16 * SM_SECTOR_SIZE;
+ break;
+ case 8:
+ /* 8 Mb flash/rom SmartMedia device */
+ ftl->zone_size = 1024;
+ ftl->max_lba = 1000;
+ ftl->block_size = 16 * SM_SECTOR_SIZE;
+ }
+
+ /* Minimum xD size is 16M, and thus all xD cards have standard zone
+ sizes. SmartMedia cards exist up to 128 Mb and have same layout*/
+ if (size_in_megs >= 16) {
+ ftl->zone_count = size_in_megs / 16;
+ ftl->zone_size = 1024;
+ ftl->max_lba = 1000;
+ ftl->block_size = 32 * SM_SECTOR_SIZE;
+ }
+
+ /* Test for proper write and erase sizes */
+ if (mtd->erasesize > ftl->block_size)
+ return -ENODEV;
+
+ if (mtd->writesize > SM_SECTOR_SIZE)
+ return -ENODEV;
+
+ /* For now, don't support small page nand */
+ if (ftl->smallpagenand)
+ return -ENODEV;
+
+ /* This shouldn't happen */
+ if (ftl->zone_count * ftl->zone_size * ftl->block_size != mtd->size)
+ return -ENODEV;
+
+ /* Find geometry information */
+ for (i = 0 ; i < ARRAY_SIZE(chs_table) ; i++)
+ if (chs_table[i].size == size_in_megs) {
+ ftl->cylinders = chs_table[i].cyl;
+ ftl->heads = chs_table[i].head;
+ ftl->sectors = chs_table[i].sec;
+ return 0;
+ }
+
+ ftl->cylinders = 985;
+ ftl->heads = 33;
+ ftl->sectors = 63;
+ return 0;
+}
+
+/* Make offset from parts */
+static loff_t sm_mkoffset(struct sm_ftl *ftl, int zone, int block, int boffset)
+{
+ WARN_ON(boffset & (SM_SECTOR_SIZE - 1));
+ WARN_ON(zone < 0 || zone >= ftl->zone_count);
+ WARN_ON(block >= ftl->zone_size);
+ WARN_ON(boffset > ftl->block_size);
+
+ if (block == -1)
+ return -1;
+
+ return (zone * ftl->zone_size + block) * ftl->block_size + boffset;
+}
+
+/* Breaks offset into parts */
+static void sm_break_offset(struct sm_ftl *ftl, loff_t offset,
+ int *zone, int *block, int *boffset)
+{
+ *boffset = offset % ftl->block_size;
+ offset /= ftl->block_size;
+ *block = offset % ftl->max_lba;
+ offset /= ftl->max_lba;
+
+ if (offset >= ftl->zone_count)
+ dbg("sm_break_offset: try to access a zone %lx",
+ (long unsigned int)offset);
+
+ *zone = offset >= ftl->zone_count ? -1 : offset;
+}
+
+/* Reads a sector + oob*/
+static int sm_read_sector(struct sm_ftl *ftl,
+ int zone, int block, int boffset,
+ u8* buffer, struct sm_oob *oob)
+{
+ struct mtd_oob_ops ops;
+ struct sm_oob tmp_oob;
+ struct mtd_info *mtd = ftl->trans->mtd;
+ int ret;
+ loff_t offset;
+
+ ops.len = SM_SECTOR_SIZE;
+ ops.datbuf = buffer;
+
+ if (!oob)
+ oob = &tmp_oob;
+
+ if (!mtd)
+ return -ENODEV;
+
+ /* TODO: raw mode doesn't check the ecc + reads to data buffer*/
+ /* This works only with empty oob layout */
+ ops.mode = MTD_OOB_PLACE;
+ ops.ooboffs = 0;
+ ops.ooblen = SM_OOB_SIZE;
+ ops.oobbuf = (void*)oob;
+
+ /* FTL can contain -1 entries that are by default filled with bits */
+ if (block == -1) {
+
+ if (buffer)
+ memset(buffer, 0xFF, SM_SECTOR_SIZE);
+ memset(oob, 0xFF, SM_OOB_SIZE);
+ return 0;
+ }
+
+ offset = sm_mkoffset(ftl, zone, block, boffset);
+ ret = mtd->read_oob(mtd, offset, &ops);
+
+ if (ret) {
+ return -EIO;
+ dbg("read of sector at 0x%lx failed with error %d",
+ (long unsigned int)offset, ret);
+ }
+
+ if (ops.oobretlen != SM_OOB_SIZE) {
+ dbg("read of sector at 0x%lx failed with wrong oob len %d",
+ (long unsigned int)offset, (int)ops.oobretlen);
+ return -EIO;
+ }
+
+ if (buffer && sm_sector_valid(oob)) {
+ dbg("read of sector at 0x%lxfailed because "
+ "it is marked as invalid",
+ (long unsigned int)offset);
+ return -EIO;
+ }
+
+ /* TODO: for small page flash, we need to check ecc here,
+ because nand subsystem really can't handle this... */
+
+ return 0;
+}
+
+/* Write a block using data and lba */
+static int sm_write_block (struct sm_ftl *ftl, u8 *buf,
+ int zone_num, int block, int lba)
+{
+ struct mtd_oob_ops ops;
+ struct mtd_info *mtd = ftl->trans->mtd;
+ int boffset;
+ loff_t offset;
+ int retry;
+
+ struct sm_oob oob;
+ memset(&oob, 0xFF, sizeof(oob));
+ sm_write_lba(&oob, lba);
+
+ if(!mtd)
+ return -ENODEV;
+
+ if (zone_num == 0 && (block == ftl->cis_block || block == 0)) {
+ dbg("attempted to write the CIS!");
+ return -EIO;
+ }
+
+ /* TODO: for small page flash, we need to compute & write ecc here,
+ because nand subsystem really can't handle this... */
+
+
+ ops.len = SM_SECTOR_SIZE;
+
+ /* TODO: raw mode doesn't check the ecc + reads to data buffer*/
+ /* This works only with empty oob layout */
+
+ ops.mode = MTD_OOB_PLACE;
+ ops.ooboffs = 0;
+ ops.ooblen = SM_OOB_SIZE;
+ ops.oobbuf = (void*)&oob;
+
+ /* Use write_oob here because some xD cards only accept writes that
+ contain both page and oob write. These cards most likely
+ do their own ftl */
+
+ offset = sm_mkoffset (ftl, zone_num, block, 0);
+
+restart:
+ for (boffset = 0; boffset < ftl->block_size;
+ boffset += SM_SECTOR_SIZE) {
+
+ ops.datbuf = buf + boffset;
+
+ if (!ftl->trans->mtd->write_oob(ftl->trans->mtd,
+ offset + boffset, &ops))
+ continue;
+
+ if (!retry) {
+ dbg("write of block %d in zone %d failed, erasing it",
+ block, zone_num);
+
+ /* If write fails. try to erase the block */
+ sm_erase_block(ftl, zone_num, block, 0);
+ retry = 1;
+ goto restart;
+ } else {
+ dbg("write of block %d in zone %d failed again"
+ ", marking as bad", block, zone_num);
+
+ sm_mark_block_bad(ftl, zone_num, block);
+ return -EIO;
+ }
+ }
+ return 0;
+}
+
+/*
+ * Erase a block within a zone
+ * If erase succedes, it updates free block fifo
+ */
+static void sm_erase_block(struct sm_ftl *ftl, int zone_num, s16 block,
+ int put_free)
+{
+ struct ftl_zone *zone = &ftl->zones[zone_num];
+ struct erase_info erase;
+ struct mtd_info *mtd = ftl->trans->mtd;
+
+ if(!mtd)
+ return;
+
+ erase.mtd = ftl->trans->mtd;
+ erase.callback = sm_erase_callback;
+ erase.addr = sm_mkoffset(ftl, zone_num, block, 0);
+ erase.len = ftl->block_size;
+ erase.priv = (u_long)ftl;
+
+ if (zone_num == 0 && (block == ftl->cis_block || block == 0)) {
+ dbg("attempted to erase the CIS!");
+ return;
+ }
+
+ if (ftl->trans->mtd->erase(ftl->trans->mtd, &erase)) {
+ dbg("erase of block %d in zone %d failed in mtd->erase call",
+ block, zone_num);
+ goto error;
+ }
+
+ wait_for_completion(&ftl->erase_completion);
+
+ if(ftl->erase_error) {
+ dbg("erase of block %d in zone %d failed after wait",
+ block, zone_num);
+ goto error;
+ }
+
+ if (put_free)
+ kfifo_put(zone->free_sectors, (const unsigned char *)&block, 2);
+ return;
+
+error:
+ sm_mark_block_bad(ftl, zone_num, block);
+ return;
+}
+
+static void sm_erase_callback (struct erase_info *self)
+{
+ struct sm_ftl *ftl = (struct sm_ftl *)self->priv;
+ ftl->erase_error = (self->state == MTD_ERASE_FAILED);
+ complete(&ftl->erase_completion);
+}
+
+
+/*
+ * Throughtly test that block is valid and belongs
+ * to specified LBA. Tries to erase it if not
+ */
+static int sm_check_block_lba (struct sm_ftl *ftl, int zone, int block, int lba)
+{
+ int boffset;
+ struct sm_oob oob;
+ int tmp;
+
+ for (boffset = 0; boffset < ftl->block_size;
+ boffset += SM_SECTOR_SIZE) {
+
+ if (sm_read_sector(ftl, zone, block, boffset, tmp_buffer,
+ &oob)) {
+ dbg("block check: fail in sector %d in zone %d",
+ block, zone);
+ goto erase;
+ }
+
+ if (sm_block_valid(&oob) || sm_sector_valid(&oob)) {
+ dbg("block check: block/sector status invalid"
+ " for sector %d in zone %d",
+ block, zone);
+ goto erase;
+ }
+
+ if ((tmp = sm_read_lba(&oob)) != lba) {
+ dbg("block check: block offset %d, we get "
+ "different LBA (%d), should get %d",
+ boffset, tmp, lba);
+ goto erase;
+ }
+ }
+ return 0;
+erase:
+ sm_erase_block(ftl, zone, block, 1);
+ return -EIO;
+}
+
+/* Mark whole block at offset 'offs' as bad.
+ We don't use mtd functions, because we know exectly how to do that
+*/
+static void sm_mark_block_bad(struct sm_ftl *ftl, int zone_num, int block) {
+
+ struct mtd_oob_ops ops;
+ struct sm_oob oob;
+ int boffset;
+ int offset = sm_mkoffset(ftl, zone_num, block, 0);
+ struct mtd_info *mtd = ftl->trans->mtd;
+
+ if(!mtd)
+ return;
+
+ dbg("marking block %d of zone %d as bad", block, zone_num);
+
+ memset (&oob, 0xFF, sizeof(oob));
+ oob.block_status = 0xF0;
+
+ ops.mode = MTD_OOB_RAW;
+ ops.ooboffs = 0;
+ ops.ooblen = SM_OOB_SIZE;
+ ops.oobbuf = (void*)&oob;
+ ops.datbuf = NULL;
+
+ /* We aren't checking the return value, because we don't care */
+ for (boffset = 0; boffset < ftl->block_size; boffset += SM_SECTOR_SIZE)
+ mtd->write_oob(mtd, offset + boffset, &ops);
+}
+
+/*
+ * Initialize FTL mapping for one zone
+ */
+struct ftl_zone* sm_initialize_zone(struct sm_ftl *ftl, int zone_num)
+{
+ struct sm_oob oob;
+ struct ftl_zone *zone;
+ u16 block;
+ int lba;
+ int i = 0;
+
+ if (zone_num >= ftl->zone_count || zone_num < 0) {
+ dbg("invalid zone (%d) was attempted to initialize", zone_num);
+ return (struct ftl_zone*)-ENODEV;
+ }
+
+ zone = &ftl->zones[zone_num];
+ if (zone->initialized)
+ return zone;
+
+ dbg("initializing zone %d", zone_num);
+
+ if (!(zone->lba_to_phys_table = kmalloc(ftl->max_lba * 2, GFP_KERNEL)))
+ return ERR_PTR(-ENOMEM);
+
+ zone->free_sectors = kfifo_alloc (ftl->zone_size * 2,
+ GFP_KERNEL, &ftl->fifo_lock);
+
+ if (!zone->free_sectors) {
+ kfree(zone->lba_to_phys_table);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ memset(zone->lba_to_phys_table, -1, ftl->max_lba * 2);
+
+ for (block = 0 ; block < ftl->zone_size ; block++) {
+
+ /* Skip blocks till the CIS (including) */
+ if (zone_num == 0 && block <= ftl->cis_block) {
+
+ if (block != ftl->cis_block)
+ dbg ("skipping block %d because"
+ " it is before the CIS (%d)",
+ block, ftl->cis_block);
+ continue;
+ }
+
+ /* Not much that we can do with blocks without
+ * even readable oob... - skip*/
+ /* Shouldn't happen though */
+ if (sm_read_sector(ftl, zone_num, block, 0, NULL, &oob)) {
+ dbg ("skipping block %d because it's "
+ "oob was unreadable", block);
+ continue;
+ }
+
+ /* Blocks with 0xFFs in the oob area are assumed free -
+ add to free table*/
+ lba = sm_read_lba(&oob);
+ if (lba == -1) {
+ kfifo_put(zone->free_sectors,
+ (unsigned char *)&block, 2);
+ continue;
+ }
+
+ /* Blocks that are marked as invalid aren't for sure usable */
+ /* If such block has correct LBA and no other block has it,
+ return errors on read */
+ if (sm_block_valid(&oob)) {
+ if (lba >= 0 && lba < ftl->max_lba)
+ zone->lba_to_phys_table[lba] = -2;
+ dbg ("skipping block %d because it was marked bad",
+ block);
+ continue;
+ }
+
+ /* Try to erase blocks that have invalid LBA,
+ but marked as valid */
+ if (lba >= ftl->max_lba || lba == -2) {
+ dbg ("erasing block %d because it has invalid LBA (%d)",
+ block, lba);
+
+ sm_erase_block(ftl, zone_num, block, 1);
+ continue;
+ }
+
+ /* If there is no collision,
+ just put the sector in the FTL table */
+ if(zone->lba_to_phys_table[lba] < 0) {
+ //dbg("LBA %04d -> PH %04d", lba, block);
+ zone->lba_to_phys_table[lba] = block;
+ continue;
+ }
+
+ dbg ("collision of LBA %d between blocks %d and %d in zone %d",
+ lba, zone->lba_to_phys_table[lba], block, zone_num);
+
+ /* Otherwise, carefully see if one of them is invalid*/
+ if (sm_check_block_lba(ftl, zone_num, block, lba))
+ continue;
+
+ if (sm_check_block_lba(ftl, zone_num,
+ zone->lba_to_phys_table[lba], lba))
+ continue;
+
+ /* Now both blocks are valid and share same LBA...
+ I guess only solution is to throw a dice.... */
+ dbg("erasing the later");
+ sm_erase_block(ftl, zone_num, block, 1);
+ }
+
+ dbg("zone initialized");
+ zone->initialized = 1;
+
+ /* No free sectors, means that the zone is heavily damaged, write won't
+ work, but it can still can be (partially) read */
+ if (!kfifo_len(zone->free_sectors)) {
+ dbg("no free blocks in zone %d", zone_num);
+ return zone;
+ }
+
+ return zone;
+
+ /* Randomize first block we write to */
+ get_random_bytes(&i, 2);
+ i %= (kfifo_len(zone->free_sectors) / 2);
+
+
+ while (i--) {
+ kfifo_get(zone->free_sectors, (unsigned char *)&block, 2);
+ kfifo_put(zone->free_sectors, (const unsigned char *)&block, 2);
+ }
+ return zone;
+}
+
+/* Write one cached block to hardware */
+int sm_cache_block_write(struct sm_ftl *ftl, struct cached_block *cache_entry)
+{
+ struct ftl_zone *zone = &ftl->zones[cache_entry->zone];
+
+ int sector_num;
+ u16 write_sector;
+ int zone_num = cache_entry->zone;
+ int block_num;
+
+ if (zone_num < 0) {
+ dbg("invalid zone num given.... %d", zone_num);
+ return -1;
+ }
+
+ block_num = zone->lba_to_phys_table[cache_entry->lba];
+
+
+ /* Read all unread areas of the cache block*/
+ for_each_bit(sector_num, &cache_entry->data_invalid_bitmap,
+ ftl->block_size / SM_SECTOR_SIZE) {
+
+
+ if (sm_read_sector(ftl,
+ zone_num, block_num, sector_num * SM_SECTOR_SIZE,
+ cache_entry->data + sector_num * SM_SECTOR_SIZE, NULL))
+ return -EIO;
+ }
+restart:
+ /* No spare blocks */
+ /* We could still continue by erasing the current block,
+ but for such worn out media it doesn't worth the trouble,
+ and the dangers */
+
+ if (!kfifo_len(zone->free_sectors)) {
+ dbg("no free sectors for write!");
+ return -EIO;
+ }
+
+ kfifo_get(zone->free_sectors, (unsigned char *)&write_sector, 2);
+
+ if (sm_write_block(ftl, cache_entry->data, zone_num, write_sector,
+ cache_entry->lba))
+ goto restart;
+
+ /* Update the FTL table */
+ zone->lba_to_phys_table[cache_entry->lba] = write_sector;
+
+ /* Write succesfull, so erase and free the old block */
+ if (block_num > 0)
+ sm_erase_block(ftl, zone_num, block_num, 1);
+ return 0;
+}
+
+
+/* Initialize new/used cache entry */
+static int sm_cache_block_init(struct sm_ftl *ftl,
+ struct cached_block *cache_entry)
+{
+ if (!cache_entry->data)
+ cache_entry->data = kmalloc(ftl->block_size, GFP_KERNEL);
+
+ if (!cache_entry->data)
+ return -ENOMEM;
+
+ cache_entry->data_invalid_bitmap = 0xFFFFFFFF;
+ cache_entry->lba = -1;
+ cache_entry->zone = -1;
+
+ return 0;
+}
+
+
+/* Flushes write cache, have to be run with ->cache_lock held */
+static int __sm_cache_flush(struct sm_ftl *ftl)
+{
+ struct cached_block *cache_entry = NULL, *tmp_entry;
+ struct mtd_info *mtd = ftl->trans->mtd;
+ int error;
+
+ if (ftl->readonly)
+ return -EROFS;
+
+ if (list_empty(&ftl->cache))
+ return 0;
+
+ if(!mtd)
+ return -ENODEV;
+
+ list_for_each_entry_safe(cache_entry, tmp_entry, &ftl->cache,
+ list_member) {
+
+ /* Write should never fail, unless media is worn out */
+ if (sm_cache_block_write(ftl, cache_entry)) {
+ dbg("sm_ftl: failed to write block %d at zone %d",
+ (int)cache_entry->lba, cache_entry->zone);
+ ftl->readonly = 1;
+ return -EIO;
+ }
+
+ list_del(&cache_entry->list_member);
+ list_add(&cache_entry->list_member, &ftl->free_cache);
+
+ if ((error = sm_cache_block_init(ftl, cache_entry)))
+ return error;
+ }
+ return 0;
+}
+
+
+/* Flushes the write cache */
+static int sm_cache_flush(struct sm_ftl *ftl)
+{
+ int retval;
+ mutex_lock(&ftl->cache_mutex);
+ retval = __sm_cache_flush(ftl);
+ mutex_unlock(&ftl->cache_mutex);
+ return retval;
+}
+
+/* Frees the write cache */
+static void sm_free_cache(struct sm_ftl *ftl)
+{
+ struct cached_block *cache_entry;
+
+ mutex_lock(&ftl->cache_mutex);
+ while(!list_empty(&ftl->free_cache)) {
+ cache_entry = list_first_entry(&ftl->free_cache,
+ struct cached_block, list_member);
+
+ kfree(cache_entry->data);
+ list_del(&cache_entry->list_member);
+ kfree(cache_entry);
+ }
+ mutex_unlock(&ftl->cache_mutex);
+}
+
+
+/* outside interface: open the device */
+static int sm_open(struct mtd_blktrans_dev *dev)
+{
+ struct sm_ftl *ftl = dev->priv;
+ ftl->flush_thread = kthread_run (sm_cache_flush_thread,
+ ftl, "smflush%d", dev->mtd->index);
+
+ if (IS_ERR(ftl->flush_thread))
+ return PTR_ERR(ftl->flush_thread);
+ return 0;
+}
+
+/* outside interface: read a sector */
+static int sm_read(struct mtd_blktrans_dev *dev,
+ unsigned long sect_no, char *buf)
+{
+ struct sm_ftl *ftl = dev->priv;
+ struct ftl_zone *zone;
+ struct cached_block *cache_entry = NULL;
+ int error = 0;
+ int cache_found = 0;
+
+ int zone_num, block, boffset;
+
+ sm_break_offset(ftl, sect_no << 9, &zone_num, &block, &boffset);
+
+ zone = sm_initialize_zone(ftl, zone_num);
+ if (IS_ERR (zone))
+ return PTR_ERR(zone);
+
+ mutex_lock(&ftl->cache_mutex);
+
+ /* Have to look at cache first */
+ list_for_each_entry(cache_entry, &ftl->cache, list_member)
+ if (cache_entry->zone == zone_num &&
+ cache_entry->lba == block &&
+
+ !test_bit(boffset / SM_SECTOR_SIZE,
+ &cache_entry->data_invalid_bitmap)) {
+
+ memcpy(buf, cache_entry->data + boffset, SM_SECTOR_SIZE);
+ goto unlock;
+ }
+
+
+ /* Translate the block and return if doesn't exist in the table */
+ block = zone->lba_to_phys_table[block];
+
+ if (block == -1) {
+ memset(buf, 0xFF, SM_SECTOR_SIZE);
+ goto unlock;
+ }
+
+ if (block == -2) {
+ dbg("read block %d of zone %d marked invalid in the ftl",
+ block, zone_num);
+ error = -EIO;
+ goto unlock;
+ }
+
+ /* Do the read. The below relies on proper ftl setup and underlying
+ driver to check at least the ecc
+ */
+ if (sm_read_sector(ftl, zone_num, block, boffset, buf, NULL)) {
+ error = -EIO;
+ goto unlock;
+ }
+
+ /* If we already have the cache entry, then add the data there, because
+ we will need it anyway..*/
+ if (cache_found) {
+ memcpy(cache_entry->data + boffset, buf, SM_SECTOR_SIZE);
+ clear_bit(boffset / SM_SECTOR_SIZE,
+ &cache_entry->data_invalid_bitmap);
+ }
+unlock:
+ mutex_unlock(&ftl->cache_mutex);
+ return error;
+}
+
+
+/* outside interface: write a sector */
+static int sm_write(struct mtd_blktrans_dev *dev,
+ unsigned long sec_no, char *buf)
+{
+ struct sm_ftl *ftl = dev->priv;
+ struct ftl_zone *zone;
+ struct cached_block *cache_entry = NULL;
+ int error;
+ int zone_num, block, boffset;
+ int cache_found = 0;
+
+ if (ftl->readonly)
+ return -EROFS;
+
+ sm_break_offset(ftl, sec_no << 9, &zone_num, &block, &boffset);
+
+ zone = sm_initialize_zone(ftl, zone_num);
+ if (IS_ERR(zone))
+ return PTR_ERR(zone);
+
+ /* Try to write the cache if possible */
+ mutex_lock(&ftl->cache_mutex);
+
+ /* Try to find existing cache entry */
+ list_for_each_entry(cache_entry, &ftl->cache, list_member)
+ if (cache_entry->zone == zone_num &&
+ cache_entry->lba == block) {
+ cache_found = 1;
+ break;
+ }
+
+ /* Entry not in the cache, create new cache entry */
+ if (!cache_found) {
+
+ /* Flush the cache if full */
+ if (list_empty(&ftl->free_cache))
+ if ((error = __sm_cache_flush(ftl)))
+ goto unlock;
+
+ BUG_ON(list_empty(&ftl->free_cache));
+
+ cache_entry = list_first_entry (&ftl->free_cache,
+ struct cached_block, list_member);
+
+ cache_entry->lba = block;
+ cache_entry->zone = zone_num;
+
+ list_del(&cache_entry->list_member);
+ list_add(&cache_entry->list_member, &ftl->cache);
+ }
+
+ /* And finally put data there */
+ memcpy(cache_entry->data + boffset, buf, SM_SECTOR_SIZE);
+ clear_bit(boffset / SM_SECTOR_SIZE, &cache_entry->data_invalid_bitmap);
+unlock:
+ mutex_unlock(&ftl->cache_mutex);
+ return error;
+}
+
+/* outside interface: flush everything */
+static int sm_flush(struct mtd_blktrans_dev *dev)
+{
+ struct sm_ftl *ftl = dev->priv;
+ return sm_cache_flush(ftl);
+}
+
+/* outside interface: last user has quit using the device,
+ also called on removal */
+static int sm_release (struct mtd_blktrans_dev *dev)
+{
+ struct sm_ftl *ftl = dev->priv;
+ sm_cache_flush(ftl);
+ kthread_stop(ftl->flush_thread);
+ return 0;
+}
+
+/* outside interface: get geometry */
+static int sm_getgeo(struct mtd_blktrans_dev *dev, struct hd_geometry *geo)
+{
+ struct sm_ftl *ftl = dev->priv;
+ geo->heads = ftl->heads;
+ geo->sectors = ftl->sectors;
+ geo->cylinders = ftl->cylinders;
+ return 0;
+}
+
+
+/* Periodic cache flush thread */
+static int sm_cache_flush_thread(void *data)
+{
+ struct sm_ftl *ftl = (struct sm_ftl *)data;
+
+ set_freezable();
+ while(!kthread_should_stop()) {
+
+ try_to_freeze();
+ set_current_state(TASK_INTERRUPTIBLE);
+ schedule_timeout(msecs_to_jiffies(500));
+ sm_cache_flush(ftl);
+ }
+
+ return 0;
+}
+
+static const u8 cis_signature[] = {
+ 0x01, 0x03, 0xD9, 0x01, 0xFF, 0x18, 0x02, 0xDF, 0x01, 0x20
+};
+
+/* Locate the CIS */
+static int sm_find_cis(struct sm_ftl *ftl)
+{
+ int block, boffset;
+ struct sm_oob oob;
+ int block_found = 0;
+
+
+ /* Scan for first valid block */
+ for (block = 0 ; block < ftl->zone_size - ftl->max_lba ; block++) {
+ if (sm_read_sector(ftl, 0, block, 0, NULL, &oob))
+ continue;
+
+ if (sm_block_valid(&oob))
+ continue;
+
+ block_found = 1;
+ break;
+ }
+
+ if (!block_found)
+ return -EIO;
+
+ /* Block might be still partially damaged, so scan for first valid
+ sector */
+ for (boffset = 0 ; boffset < ftl->block_size;
+ boffset += SM_SECTOR_SIZE) {
+
+ if (sm_read_sector(ftl, 0, block, boffset, tmp_buffer, &oob))
+ continue;
+
+ if (!memcmp (tmp_buffer, cis_signature, sizeof(cis_signature)))
+ goto found;
+
+ if (!memcmp (tmp_buffer + SM_SECTOR_SIZE / 2, cis_signature,
+ sizeof(cis_signature)))
+ goto found;
+ return -EIO;
+ }
+found:
+ ftl->cis_block = block;
+ dbg("CIS block found at offset %d", block * ftl->block_size + boffset);
+ return 0;
+}
+
+/* external interface: main initialization function */
+static void sm_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
+{
+ struct mtd_blktrans_dev *trans;
+ struct sm_ftl *ftl;
+ int i;
+ struct cached_block *cache_entry;
+
+
+ /* Allocate & initialize our private structure */
+ if (!(ftl = kzalloc (sizeof (struct sm_ftl), GFP_KERNEL)))
+ goto error1;
+
+ INIT_LIST_HEAD(&ftl->cache);
+ INIT_LIST_HEAD(&ftl->free_cache);
+ mutex_init(&ftl->cache_mutex);
+ init_completion(&ftl->erase_completion);
+
+ /* Read media information */
+ if (sm_get_media_info(ftl, mtd))
+ goto error2;
+
+ /* Allocate zone array, it will be initialized on demand */
+ ftl->zones = kzalloc (sizeof (struct ftl_zone) * ftl->zone_count,
+ GFP_KERNEL);
+ if (!ftl->zones)
+ goto error2;
+
+ /* Allocate write cache */
+ INIT_LIST_HEAD(&ftl->cache);
+ INIT_LIST_HEAD(&ftl->free_cache);
+
+ for (i = 0 ; i < cache_size ; i++) {
+ cache_entry = kzalloc (sizeof (struct cached_block),
+ GFP_KERNEL);
+ if (!cache_entry)
+ break;
+
+ if (sm_cache_block_init(ftl, cache_entry)) {
+ kfree(cache_entry);
+ break;
+ }
+ list_add(&cache_entry->list_member, &ftl->free_cache);
+ }
+
+ if (list_empty(&ftl->free_cache))
+ goto error3;
+
+ /* Allocate upper layer structure and initialize it */
+ if (!(trans = kzalloc (sizeof(struct mtd_blktrans_dev), GFP_KERNEL)))
+ goto error4;
+
+ ftl->trans = trans;
+ trans->priv = ftl;
+
+ trans->tr = tr;
+ trans->mtd = mtd;
+ trans->devnum = -1;
+ trans->size = (ftl->block_size * ftl->max_lba * ftl->zone_count) >> 9;
+ trans->readonly = ftl->readonly;
+
+ if (sm_find_cis(ftl))
+ goto error4;
+
+ /* Register device*/
+ if (add_mtd_blktrans_dev(trans))
+ goto error5;
+
+ dbg("Found %d MiB SmartMedia/xD card on %s",
+ (int)(mtd->size / (1024 * 1024)), mtd->name);
+
+ dbg("FTL layout:");
+ dbg("%d zones, each consists of %d blocks (+%d spares)",
+ ftl->zone_count, ftl->max_lba,
+ ftl->zone_size - ftl->max_lba);
+ dbg("each block consists of %d bytes",
+ ftl->block_size);
+
+ return;
+error5:
+ kfree(trans);
+error4:
+ sm_free_cache(ftl);
+error3:
+ kfree(ftl->zones);
+error2:
+ kfree(ftl);
+error1:
+ return;
+}
+
+/* main interface: device {surprise,} removal */
+static void sm_remove_dev (struct mtd_blktrans_dev *dev)
+{
+ struct sm_ftl *ftl = dev->priv;
+ dbg("removing the ftl device");
+ del_mtd_blktrans_dev(dev);
+ kfree(ftl->zones);
+ sm_free_cache(ftl);
+ kfree(ftl); /* WE free that here, but the ->release can still
+ be called after ..... fuck */
+}
+
+static struct mtd_blktrans_ops sm_ftl_ops = {
+ .name = "smblk",
+ .major = -1,
+ .part_bits = SM_FTL_PARTN_BITS,
+ .blksize = SM_SECTOR_SIZE,
+ .getgeo = sm_getgeo,
+ .readsect = sm_read,
+ .writesect = sm_write,
+ .add_mtd = sm_add_mtd,
+ .remove_dev = sm_remove_dev,
+ .open = sm_open,
+ .release = sm_release,
+ .flush = sm_flush,
+ .owner = THIS_MODULE,
+};
+
+static __init int sm_module_init(void)
+{
+ return register_mtd_blktrans(&sm_ftl_ops);
+}
+
+static void __exit sm_module_exit(void)
+{
+ deregister_mtd_blktrans(&sm_ftl_ops);
+}
+
+module_init(sm_module_init);
+module_exit(sm_module_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Maxim Levitsky <[email protected]>");
+MODULE_DESCRIPTION("Smartmedia/xD mtd translation layer");
\ No newline at end of file
diff --git a/drivers/mtd/sm_ftl.h b/drivers/mtd/sm_ftl.h
new file mode 100644
index 0000000..5edf372
--- /dev/null
+++ b/drivers/mtd/sm_ftl.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2009 - Maxim Levitsky
+ * SmartMedia/xD translation layer
+ *
+ * Based loosly on ssfdc.c which is
+ * (c) 2005 Eptar srl
+ * Author: Claudio Lanconelli <[email protected]>
+ *
+ * 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.
+ */
+
+#include <linux/mtd/nand.h>
+#include <linux/mtd/blktrans.h>
+#include <linux/list.h>
+#include <linux/kfifo.h>
+#include <linux/sched.h>
+#include <linux/completion.h>
+#include <linux/spinlock.h>
+
+
+struct ftl_zone {
+ int initialized;
+ s16 *lba_to_phys_table; /* LBA to physical table */
+ struct kfifo *free_sectors; /* queue of free sectors */
+};
+
+
+struct cached_block {
+ int zone;
+ unsigned long lba;
+ unsigned char *data;
+ long unsigned int data_invalid_bitmap;
+ struct list_head list_member;
+};
+
+
+struct sm_ftl {
+ struct mtd_blktrans_dev *trans;
+ struct ftl_zone *zones;
+ struct list_head cache;
+ struct list_head free_cache;
+ struct mutex cache_mutex;
+ struct completion erase_completion;
+ struct task_struct *flush_thread;
+ int erase_error;
+
+ int block_size; /* block size in bytes */
+ int zone_size; /* zone size in blocks */
+ int zone_count; /* number of zones */
+ int max_lba; /* maximum lba in a zone */
+ int smallpagenand; /* 256 bytes/page nand */
+
+ int readonly;
+
+ /* geometry stuff */
+ int heads;
+ int sectors;
+ int cylinders;
+
+ /*Misc */
+ int cis_block;
+
+ spinlock_t fifo_lock;
+};
+
+struct chs_entry {
+ unsigned long size;
+ unsigned short cyl;
+ unsigned char head;
+ unsigned char sec;
+};
+
+
+#define SM_FTL_PARTN_BITS 3
+
+#define dbg(format, ...) \
+ printk (KERN_ERR "sm_ftl" ": " format "\n", ## __VA_ARGS__)
--
1.6.3.3
>From b1150984957b7a2b429d40173891098a035c8947 Mon Sep 17 00:00:00 2001
From: Maxim Levitsky <[email protected]>
Date: Wed, 6 Jan 2010 23:06:28 +0200
Subject: [PATCH 9/9] mtd: add nand driver for ricoh xD/SmartMedia reader
---
MAINTAINERS | 6 +
drivers/mtd/nand/Kconfig | 11 +
drivers/mtd/nand/Makefile | 1 +
drivers/mtd/nand/r822.c | 1014 +++++++++++++++++++++++++++++++++++++++++++++
drivers/mtd/nand/r822.h | 155 +++++++
5 files changed, 1187 insertions(+), 0 deletions(-)
create mode 100644 drivers/mtd/nand/r822.c
create mode 100644 drivers/mtd/nand/r822.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 1c02e08..50a4006 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4466,6 +4466,12 @@ S: Maintained
F: Documentation/rfkill.txt
F: net/rfkill/
+RICOH SMARTMEDIA/XD DRIVER
+M: Maxim Levitsky <[email protected]>
+S: Maintained
+F: drivers/mtd/nand/r822.c
+F: drivers/mtd/nand/r822.h
+
RISCOM8 DRIVER
S: Orphan
F: Documentation/serial/riscom8.txt
diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig
index 2fda0b6..c71e282 100644
--- a/drivers/mtd/nand/Kconfig
+++ b/drivers/mtd/nand/Kconfig
@@ -106,6 +106,17 @@ config MTD_NAND_TS7250
config MTD_NAND_IDS
tristate
+config MTD_NAND_RICOH
+ tristate "Ricoh xD card reader"
+ default n
+ select MTD_SM_COMMON
+ help
+ Enable support for Ricoh xD card reader
+ You also need to enable ether
+ NAND SSFDC (SmartMedia) read only translation layer' or new
+ expermental, readwrite
+ 'SmartMedia/xD new translation layer'
+
config MTD_NAND_AU1550
tristate "Au1550/1200 NAND support"
depends on SOC_AU1200 || SOC_AU1550
diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile
index 6950d3d..7b4f500 100644
--- a/drivers/mtd/nand/Makefile
+++ b/drivers/mtd/nand/Makefile
@@ -42,5 +42,6 @@ obj-$(CONFIG_MTD_NAND_SOCRATES) += socrates_nand.o
obj-$(CONFIG_MTD_NAND_TXX9NDFMC) += txx9ndfmc.o
obj-$(CONFIG_MTD_NAND_W90P910) += w90p910_nand.o
obj-$(CONFIG_MTD_NAND_NOMADIK) += nomadik_nand.o
+obj-$(CONFIG_MTD_NAND_RICOH) += r822.o
nand-objs := nand_base.o nand_bbt.o
diff --git a/drivers/mtd/nand/r822.c b/drivers/mtd/nand/r822.c
new file mode 100644
index 0000000..a5e9abc
--- /dev/null
+++ b/drivers/mtd/nand/r822.c
@@ -0,0 +1,1014 @@
+/*
+ * Copyright (C) 2009 - Maxim Levitsky
+ * driver for Ricoh xD readers
+ *
+ * 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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/jiffies.h>
+#include <linux/workqueue.h>
+#include <linux/interrupt.h>
+#include <linux/pci_ids.h>
+#include <asm/byteorder.h>
+#include <linux/sched.h>
+#include "../sm_common.h"
+#include "r822.h"
+
+/* read register */
+static inline u8 r822_read_reg(struct r822_device *dev, int address)
+{
+ u8 reg = readb(dev->mmio + address);
+ return reg;
+}
+
+/* write register */
+static inline void r822_write_reg(struct r822_device *dev,
+ int address, u8 value)
+{
+ writeb(value, dev->mmio + address);
+}
+
+
+/* read dword sized register */
+static inline u32 r822_read_reg_dword(struct r822_device *dev, int address)
+{
+ u32 reg = le32_to_cpu(readl(dev->mmio + address));
+ return reg;
+}
+
+/* write dword sized register */
+static inline void r822_write_reg_dword(struct r822_device *dev,
+ int address, u32 value)
+{
+ writel(cpu_to_le32(value), dev->mmio + address);
+}
+
+/* returns pointer to our private structure */
+static inline struct r822_device *r822_get_dev(struct mtd_info *mtd)
+{
+ struct nand_chip *chip = (struct nand_chip *)mtd->priv;
+ return (struct r822_device *)chip->priv;
+}
+
+
+/* check if controller supports dma */
+static void r822_dma_test(struct r822_device *dev)
+{
+ dev->dma_usable = (r822_read_reg(dev, R822_DMA_CAP) &
+ (R822_DMA1 | R822_DMA2)) == (R822_DMA1 | R822_DMA2);
+
+ if (!dev->dma_usable)
+ dbg("Non dma capable device detected, dma disabled");
+}
+
+/*
+ * Enable dma. Enables ether first or second stage of the DMA,
+ * Expects dev->dma_dir and dev->dma_state be set
+ */
+static void r822_dma_enable(struct r822_device *dev)
+{
+ u8 dma_reg = dev->dma_dir ? R822_DMA_READ : 0;
+
+ if (dev->dma_state == DMA_INTERNAL)
+ dma_reg |= R822_DMA_INTERNAL;
+ else {
+ dma_reg |= R822_DMA_MEMORY;
+ r822_write_reg_dword(dev, R822_DMA_ADDR,
+ cpu_to_le32(dev->phys_dma_addr));
+ }
+
+ r822_write_reg(dev, R822_DMA_IRQ_STA,
+ r822_read_reg(dev, R822_DMA_IRQ_STA));
+
+ r822_write_reg(dev, R822_DMA_SETTINGS, dma_reg);
+ r822_write_reg(dev, R822_DMA_IRQ_ENABLE,
+ R822_DMA_IRQ_INTERNAL |
+ R822_DMA_IRQ_ERROR |
+ R822_DMA_IRQ_MEMORY);
+}
+
+/*
+ * Disable dma, called from the interrupt handler, which specifies
+ * success of the operation via 'error' argument
+ */
+static void r822_dma_done(struct r822_device *dev, int error)
+{
+ WARN_ON(dev->dma_stage == 0);
+
+ if (error)
+ dbg("dma: complete with error");
+
+ r822_write_reg(dev, R822_DMA_IRQ_STA, r822_read_reg(dev, R822_DMA_IRQ_STA));
+ r822_write_reg(dev, R822_DMA_SETTINGS, 0);
+ r822_write_reg(dev, R822_DMA_IRQ_ENABLE, 0);
+
+ dev->dma_error = error;
+ dev->dma_stage = 0;
+
+ if(dev->phys_dma_addr && dev->phys_dma_addr != dev->phys_bounce_buffer)
+ pci_unmap_single(dev->pci_dev, dev->phys_dma_addr, R822_DMA_LEN,
+ dev->dma_dir ? PCI_DMA_FROMDEVICE : PCI_DMA_TODEVICE);
+ complete(&dev->dma_done);
+}
+
+/*
+ * Wait, till dma is done, which includes both phases of it
+ */
+static int r822_dma_wait(struct r822_device *dev)
+{
+ long timeout = wait_for_completion_interruptible_timeout(&dev->dma_done,
+ msecs_to_jiffies(1000));
+ if (timeout < 0)
+ return -EAGAIN;
+ if (!timeout)
+ return -ETIMEDOUT;
+ return 0;
+}
+
+/*
+ * Read/Write one page using dma. Only pages can be read (512 bytes), oob
+ * can't be read here
+*/
+static void r822_do_dma (struct r822_device *dev, uint8_t *buf, int do_read)
+{
+ int bounce = 0;
+ unsigned long flags;
+ int error;
+
+ dev->dma_error = 0;
+
+ /* Set dma direction */
+ dev->dma_dir = do_read;
+ dev->dma_stage = 1;
+
+ /* Set intial dma state: for reading first fill on board buffer,
+ from device, for writes first fill the buffer from memory*/
+ dev->dma_state = do_read ? DMA_INTERNAL : DMA_MEMORY;
+
+ /* if incoming buffer is not page aligned, we should do bounce */
+ if ((unsigned long)buf & (PAGE_SIZE-1))
+ bounce = 1;
+
+ if (!bounce) {
+ dev->phys_dma_addr = pci_map_single(dev->pci_dev, (void*)buf,
+ R822_DMA_LEN,
+ (do_read ? PCI_DMA_FROMDEVICE : PCI_DMA_TODEVICE));
+
+ if (dev->phys_dma_addr == bad_dma_address)
+ bounce = 1;
+ }
+
+ if (bounce) {
+ dev->phys_dma_addr = dev->phys_bounce_buffer;
+
+ if (!do_read)
+ memcpy(dev->bounce_buffer, buf, R822_DMA_LEN);
+ }
+
+ /* Enable DMA */
+ spin_lock_irqsave(&dev->irqlock, flags);
+ r822_dma_enable(dev);
+ spin_unlock_irqrestore(&dev->irqlock, flags);
+
+ /* Wait till complete */
+ if((error = r822_dma_wait(dev))) {
+ r822_dma_done(dev, error);
+ return;
+ }
+
+ if(do_read && bounce)
+ memcpy((void*)buf, dev->bounce_buffer, R822_DMA_LEN);
+}
+
+/*
+ * Program data lines of the nand chip to send data to it
+ */
+void r822_write_buf (struct mtd_info *mtd, const uint8_t *buf, int len)
+{
+ struct r822_device *dev = r822_get_dev(mtd);
+ u32 reg;
+
+ if (dev->card_unstable)
+ return;
+
+ /* special case for whole sector read */
+ if (len == R822_DMA_LEN && dev->dma_usable) {
+ r822_do_dma(dev, (uint8_t *)buf, 0);
+ return;
+ }
+
+ /* write DWORD chinks - faster */
+ while(len) {
+ reg = buf[0] | buf[1] << 8 | buf[2] << 16 | buf[3] << 24;
+ r822_write_reg_dword(dev, R822_DATALINE, reg);
+ buf += 4;
+ len -= 4;
+
+ if (len %16 == 0)
+ udelay(20);
+ }
+
+ /* write rest */
+ while (len)
+ r822_write_reg(dev, R822_DATALINE, *buf++);
+}
+
+/*
+ * Read data lines of the nand chip to retrieve data
+ */
+void r822_read_buf (struct mtd_info *mtd, uint8_t *buf, int len)
+{
+ struct r822_device *dev = r822_get_dev(mtd);
+ u32 reg;
+
+ if (dev->card_unstable)
+ return;
+
+ /* special case for whole sector read */
+ if (len == R822_DMA_LEN && dev->dma_usable) {
+ r822_do_dma(dev, buf, 1);
+ return;
+ }
+
+ /* read in dword sized chunks */
+ while(len >= 4) {
+
+ reg = r822_read_reg_dword(dev, R822_DATALINE);
+ *buf++ = reg & 0xFF;
+ *buf++ = (reg >> 8) & 0xFF;
+ *buf++ = (reg >> 16) & 0xFF;
+ *buf++ = (reg >> 24) & 0xFF;
+ len -= 4;
+ }
+
+ /* read the reset by bytes */
+ while(len--)
+ *buf++ = r822_read_reg (dev, R822_DATALINE);
+}
+
+static uint8_t r822_read_byte(struct mtd_info *mtd) {
+ struct r822_device *dev = r822_get_dev(mtd);
+ return r822_read_reg(dev, R822_DATALINE);
+}
+
+
+/*
+ * Readback the buffer to verify it
+ */
+int r822_verify_buf(struct mtd_info *mtd, const uint8_t *buf, int len)
+{
+ struct r822_device *dev = r822_get_dev(mtd);
+
+ if (dev->card_unstable)
+ return 0;
+
+ if (len > SM_SECTOR_SIZE)
+ return 0;
+
+ r822_read_buf(mtd, dev->tmp_buffer, len);
+ return memcmp(buf, dev->tmp_buffer, len);
+}
+
+/*
+ * Control several chip lines & send commands
+ */
+void r822_cmdctl(struct mtd_info *mtd, int dat, unsigned int ctrl)
+{
+ struct r822_device *dev = r822_get_dev(mtd);
+
+ if (dev->card_unstable)
+ return;
+
+ if (ctrl & NAND_CTRL_CHANGE) {
+
+ dev->ctlreg &= ~(R822_CTL_DATA | R822_CTL_COMMAND |
+ R822_CTL_ON | R822_CTL_CARDENABLE);
+
+ if (ctrl & NAND_ALE)
+ dev->ctlreg |= R822_CTL_DATA;
+
+ if (ctrl & NAND_CLE)
+ dev->ctlreg |= R822_CTL_COMMAND;
+
+ if (ctrl & NAND_NCE)
+ dev->ctlreg |= (R822_CTL_CARDENABLE | R822_CTL_ON);
+ else
+ dev->ctlreg &= ~R822_CTL_WRITE;
+
+ /* when write is stareted, enable write access */
+ if (dat == NAND_CMD_ERASE1)
+ dev->ctlreg |= R822_CTL_WRITE;
+
+ r822_write_reg(dev, R822_CTL, dev->ctlreg);
+ }
+
+
+ /* HACK: NAND_CMD_SEQIN is called without NAND_CTRL_CHANGE, but we need
+ to set write mode */
+ if (dat == NAND_CMD_SEQIN && (dev->ctlreg & R822_CTL_COMMAND)) {
+ dev->ctlreg |= R822_CTL_WRITE;
+ r822_write_reg(dev, R822_CTL, dev->ctlreg);
+ }
+
+ if (dat != NAND_CMD_NONE)
+ r822_write_reg(dev, R822_DATALINE, dat);
+}
+
+/*
+ * Wait till card is ready... This has to be a busy loop because
+ * 'our <censored> controller' doesn't have an interrupt for that...
+ * based on nand_wait
+ */
+int r822_wait(struct mtd_info *mtd, struct nand_chip *chip)
+{
+ struct r822_device *dev = (struct r822_device *)chip->priv;
+
+ unsigned long timeout;
+ int status;
+
+ timeout = jiffies + (chip->state == FL_ERASING ?
+ msecs_to_jiffies(400) : msecs_to_jiffies(20));
+
+ while (time_before(jiffies, timeout)) {
+ if (chip->dev_ready(mtd))
+ break;
+ cond_resched();
+ }
+
+ chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
+ status = (int)chip->read_byte(mtd);
+
+ /* Unfortunelly, no way to send detailed error status... */
+ if (dev->dma_error) {
+ status |= NAND_STATUS_FAIL;
+ dev->dma_error = 0;
+ }
+
+ return status;
+}
+
+/*
+ * Check if card is ready
+ */
+
+int r822_ready(struct mtd_info *mtd)
+{
+ struct r822_device *dev = r822_get_dev(mtd);
+ return !(r822_read_reg(dev, R822_CARD_STA) & R822_CARD_STA_BUSY);
+}
+
+
+/*
+ * Set ECC engine mode
+*/
+
+void r822_ecc_hwctl (struct mtd_info *mtd, int mode)
+{
+ struct r822_device *dev = r822_get_dev(mtd);
+
+ if (dev->card_unstable)
+ return;
+
+ switch(mode) {
+ case NAND_ECC_READ:
+ case NAND_ECC_WRITE:
+ /* enable ecc generation/check*/
+ dev->ctlreg |= R822_CTL_ECC_ENABLE;
+
+ /* flush ecc buffer */
+ r822_write_reg(dev, R822_CTL, dev->ctlreg | R822_CTL_ECC_ACCESS);
+ r822_read_reg(dev, R822_DATALINE);
+ r822_write_reg(dev, R822_CTL, dev->ctlreg);
+ return;
+
+ case NAND_ECC_READSYN:
+ /* disable ecc generation */
+ dev->ctlreg &= ~R822_CTL_ECC_ENABLE;
+ r822_write_reg(dev, R822_CTL, dev->ctlreg);
+ }
+}
+
+/*
+ * Calculate ECC, only used for writes
+ */
+
+int r822_ecc_calculate(struct mtd_info *mtd,const uint8_t *dat,
+ uint8_t *ecc_code)
+{
+ struct r822_device *dev = r822_get_dev(mtd);
+ struct sm_oob *oob = (struct sm_oob *)ecc_code;
+ u32 ecc1, ecc2;
+
+ if (dev->card_unstable)
+ return 0;
+
+ dev->ctlreg &= ~R822_CTL_ECC_ENABLE;
+ r822_write_reg(dev, R822_CTL, dev->ctlreg | R822_CTL_ECC_ACCESS);
+
+ ecc1 = r822_read_reg_dword(dev, R822_DATALINE);
+ ecc2 = r822_read_reg_dword(dev, R822_DATALINE);
+
+ oob->ecc1[0] = (ecc1) & 0xFF;
+ oob->ecc1[1] = (ecc1 >> 8) & 0xFF;
+ oob->ecc1[2] = (ecc1 >> 16) & 0xFF;
+
+ oob->ecc2[0] = (ecc2) & 0xFF;
+ oob->ecc2[1] = (ecc2 >> 8) & 0xFF;
+ oob->ecc2[2] = (ecc2 >> 16) & 0xFF;
+
+ r822_write_reg(dev, R822_CTL, dev->ctlreg);
+ return 0;
+}
+
+/*
+ * Correct the data using ECC, hw did almost everything for us
+ */
+
+int r822_ecc_correct(struct mtd_info *mtd, uint8_t *dat,
+ uint8_t *read_ecc,uint8_t *calc_ecc)
+{
+ u16 ecc_reg;
+ u8 ecc_status, err_byte;
+ int i, error = 0;
+
+ struct r822_device *dev = r822_get_dev(mtd);
+
+ if (dev->card_unstable)
+ return 0;
+
+ r822_write_reg(dev, R822_CTL, dev->ctlreg | R822_CTL_ECC_ACCESS);
+ ecc_reg = r822_read_reg_dword(dev, R822_DATALINE);
+ r822_write_reg(dev, R822_CTL, dev->ctlreg);
+
+ for (i = 0 ; i <= 1 ; i++) {
+
+ ecc_status = (ecc_reg >> 8) & 0xFF;
+
+ /* ecc uncorrectable error */
+ if (ecc_status & R822_ECC_FAIL) {
+ dbg("ecc: unrecoverable error, in half %d", i);
+ error = -1;
+ goto exit;
+ }
+
+ /* correctable error */
+ if (ecc_status & R822_ECC_CORRECTABLE) {
+
+ err_byte = ecc_reg & 0xFF;
+
+ dbg("ecc: recoverable error, in half %d, byte %d, bit %d", i,
+ err_byte, ecc_status & R822_ECC_ERR_BIT_MSK);
+
+ dat[err_byte] ^= 1 << (ecc_status & R822_ECC_ERR_BIT_MSK);
+ error++;
+ }
+
+ dat += 256;
+ ecc_reg >>= 16;
+ }
+exit:
+ return error;
+}
+
+/*
+ * This is copy of nand_read_oob_std
+ * nand_read_oob_syndrome assumes we can send column address - we can't
+ */
+static int r822_read_oob(struct mtd_info *mtd, struct nand_chip *chip,
+ int page, int sndcmd)
+{
+ if (sndcmd) {
+ chip->cmdfunc(mtd, NAND_CMD_READOOB, 0, page);
+ sndcmd = 0;
+ }
+ chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
+ return sndcmd;
+}
+
+/*
+ * Start the hardware
+ */
+
+void r822_device_start(struct r822_device *dev)
+{
+ if (r822_read_reg(dev, R822_HW) & R822_HW_UNKNOWN) {
+ dbg("r822_device_start: probably recovering from HW error");
+ r822_write_reg(dev, R822_CTL, R822_CTL_RESET | R822_CTL_ON);
+ r822_write_reg_dword(dev, R822_HW, R822_HW_ENABLED);
+ } else {
+ r822_write_reg(dev, R822_HW, R822_HW_ENABLED);
+ r822_write_reg(dev, R822_CTL, R822_CTL_RESET | R822_CTL_ON);
+ r822_write_reg(dev, R822_CTL, 0);
+ }
+ msleep(200);
+}
+
+
+/*
+ * Shutdown the hardware
+ */
+
+void r822_device_shutdown(struct r822_device *dev)
+{
+ r822_write_reg(dev, R822_HW, 0);
+ r822_write_reg(dev, R822_CTL, R822_CTL_RESET);
+}
+
+/*
+ * Test if card is present
+ */
+
+void r822_card_update_present(struct r822_device *dev)
+{
+ unsigned long flags;
+ u8 reg;
+
+ spin_lock_irqsave(&dev->irqlock, flags);
+ reg = r822_read_reg(dev, R822_CARD_STA);
+ dev->card_detected = !!(reg & R822_CARD_STA_PRESENT);
+ spin_unlock_irqrestore(&dev->irqlock, flags);
+}
+
+/*
+ * Update card detection IRQ state according to current card state
+ * which is read in r822_card_update_present
+ */
+void r822_update_card_detect(struct r822_device *dev)
+{
+ int card_detect_reg = R822_CARD_IRQ_GENABLE;
+ card_detect_reg |= dev->card_detected ? R822_CARD_IRQ_REMOVE: R822_CARD_IRQ_INSERT;
+ r822_write_reg(dev, R822_CARD_IRQ_ENABLE, card_detect_reg);
+}
+
+
+/* Detect properties of card in slot */
+void r822_update_media_status(struct r822_device *dev)
+{
+ u8 reg;
+ unsigned long flags;
+ int readonly, sm;
+
+ spin_lock_irqsave(&dev->irqlock, flags);
+ if (!dev->card_detected) {
+ dbg("card removed");
+ spin_unlock_irqrestore(&dev->irqlock, flags);
+ return ;
+ }
+
+ readonly = r822_read_reg(dev, R822_CARD_STA) & R822_CARD_STA_RO;
+ reg = r822_read_reg(dev, R822_DMA_CAP);
+ sm = (reg & (R822_DMA1 | R822_DMA2)) && (reg & R822_SMBIT);
+
+ dbg("detected %s %s card in slot",
+ sm ? "SmartMedia" : "xD",
+ readonly ? "readonly" : "writeable");
+
+ dev->readonly = readonly;
+ spin_unlock_irqrestore(&dev->irqlock, flags);
+}
+
+/*
+ * Register the nand device
+ * Called when the card is detected
+ */
+int r822_register_nand_device(struct r822_device *dev)
+{
+ if (!(dev->mtd = kzalloc(sizeof(struct mtd_info), GFP_KERNEL)))
+ goto error1;
+
+ dev->mtd->owner = THIS_MODULE;
+ dev->mtd->priv = dev->chip;
+
+ if (dev->readonly)
+ dev->chip->options |= NAND_ROM;
+
+ r822_device_start(dev);
+ if (sm_register_device(dev->mtd))
+ goto error2;
+
+ dev->card_registred = 1;
+ return 0;
+error2:
+ kfree(dev->mtd);
+error1:
+ return -1;
+}
+
+/*
+ * Unregister the card
+ */
+
+void r822_unregister_nand_device(struct r822_device *dev)
+{
+ if (!dev->card_registred)
+ return;
+
+ nand_release(dev->mtd);
+ r822_device_shutdown(dev);
+ dev->card_registred = 0;
+ kfree(dev->mtd);
+ dev->mtd = NULL;
+}
+
+
+/* Card state updater */
+void r822_card_detect_work(struct work_struct *work)
+{
+ struct r822_device *dev =
+ container_of(work, struct r822_device, card_detect_work.work);
+
+ r822_card_update_present(dev);
+ dev->card_unstable = 0;
+
+ /* false alarm */
+ if (dev->card_detected == dev->card_registred) {
+ goto exit;
+ }
+
+ r822_update_media_status(dev);
+
+ /* no card present */
+ if (!dev->card_detected) {
+ r822_unregister_nand_device(dev);
+ goto exit;
+ }
+
+ /* card present from now */
+
+
+ if(!r822_register_nand_device(dev))
+ goto exit;
+ else
+ dev->card_detected = 0;
+exit:
+ r822_update_card_detect(dev);
+}
+
+
+/* disable IRQ generation */
+static void r822_disable_irqs(struct r822_device *dev)
+{
+ u8 reg;
+ reg = r822_read_reg(dev, R822_CARD_IRQ_ENABLE);
+ r822_write_reg(dev, R822_CARD_IRQ_ENABLE, reg & ~R822_CARD_IRQ_MASK);
+
+ reg = r822_read_reg(dev, R822_DMA_IRQ_ENABLE);
+ r822_write_reg(dev, R822_DMA_IRQ_ENABLE, reg & ~R822_DMA_IRQ_MASK);
+
+ reg = r822_read_reg(dev, R822_CARD_IRQ_STA);
+ r822_write_reg(dev, R822_CARD_IRQ_STA, reg);
+
+ reg = r822_read_reg(dev, R822_DMA_IRQ_STA);
+ r822_write_reg(dev, R822_DMA_IRQ_STA, reg);
+
+}
+
+/* Interrupt handler */
+static irqreturn_t r822_irq (int irq, void *data)
+{
+ struct r822_device *dev = (struct r822_device *)data;
+
+ u8 card_status, dma_status;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->irqlock, flags);
+
+ /* test card status interrupts */
+ card_status = r822_read_reg(dev, R822_CARD_IRQ_STA) & R822_CARD_IRQ_MASK;
+
+ if(card_status & (R822_CARD_IRQ_INSERT|R822_CARD_IRQ_REMOVE)) {
+
+ dev->card_detected = !!(card_status & R822_CARD_IRQ_INSERT);
+
+ /* we shouldn't recieve any interrupts if we wait for card
+ to settle */
+ WARN_ON(dev->card_unstable);
+
+ /* disable irqs while card is unstable */
+ /* this will timeout DMA if active, but better that garbage */
+ r822_disable_irqs(dev);
+
+ /* let, card state to settle a bit, and then do the work */
+ if(!dev->card_unstable) {
+ dev->card_unstable = 1;
+ queue_delayed_work(dev->card_workqueue,
+ &dev->card_detect_work, msecs_to_jiffies(100));
+ }
+
+ spin_unlock_irqrestore(&dev->irqlock, flags);
+ return IRQ_HANDLED;
+ }
+
+ card_status &= ~R822_CARD_IRQ_UNUSED1;
+ if (card_status)
+ dbg("card status = %02x", card_status);
+
+ /* test and ack dma interrupts */
+ dma_status = r822_read_reg(dev, R822_DMA_IRQ_STA) & R822_DMA_IRQ_MASK;
+ r822_write_reg(dev, R822_DMA_IRQ_STA, dma_status);
+
+ if(!dma_status) {
+ spin_unlock_irqrestore(&dev->irqlock, flags);
+ return IRQ_NONE;
+ }
+
+ if (dma_status & R822_DMA_IRQ_ERROR) {
+ dbg("recieved dma error IRQ");
+ r822_dma_done(dev, -EIO);
+ spin_unlock_irqrestore(&dev->irqlock, flags);
+ return IRQ_HANDLED;
+ }
+
+
+ /* recieved DMA interrupt out of nowhere? */
+ WARN_ON(dev->dma_stage == 0);
+
+
+ /* done device access */
+ if (dev->dma_state == DMA_INTERNAL && (dma_status & R822_DMA_IRQ_INTERNAL)) {
+ dev->dma_state = DMA_MEMORY;
+ dev->dma_stage++;
+ }
+
+ /* done memory DMA */
+ if (dev->dma_state == DMA_MEMORY && (dma_status & R822_DMA_IRQ_MEMORY)) {
+ dev->dma_state = DMA_INTERNAL;
+ dev->dma_stage++;
+ }
+
+ /* Enable 2nd half of dma dance */
+ if (dev->dma_stage == 2)
+ r822_dma_enable(dev);
+
+
+ /* Operation done */
+ if (dev->dma_stage == 3)
+ r822_dma_done(dev, 0);
+
+ spin_unlock_irqrestore(&dev->irqlock, flags);
+ return IRQ_HANDLED;
+}
+
+int r822_probe (struct pci_dev *pci_dev, const struct pci_device_id *id)
+{
+ int error;
+ struct nand_chip *chip;
+ struct r822_device *dev;
+
+ /* pci initialization */
+ if ((error = pci_enable_device(pci_dev)))
+ goto error1;
+
+ pci_set_master(pci_dev);
+
+ if ((error = pci_set_dma_mask(pci_dev, DMA_32BIT_MASK)))
+ goto error2;
+
+ if ((error = pci_request_regions(pci_dev, DRV_NAME)))
+ goto error3;
+
+ error = -ENOMEM;
+
+ /* init nand chip, but register it only on card insert */
+ if (!(chip = kzalloc(sizeof(struct nand_chip), GFP_KERNEL)))
+ goto error4;
+
+ /* commands */
+ chip->cmd_ctrl = r822_cmdctl;
+ chip->waitfunc = r822_wait;
+ chip->dev_ready = r822_ready;
+
+ /* I/O */
+ chip->read_byte = r822_read_byte;
+ chip->read_buf = r822_read_buf;
+ chip->write_buf = r822_write_buf;
+ chip->verify_buf = r822_verify_buf;
+
+ /* ecc */
+ chip->ecc.mode = NAND_ECC_HW_SYNDROME;
+ chip->ecc.size = R822_DMA_LEN;
+ chip->ecc.bytes = SM_OOB_SIZE;
+ chip->ecc.hwctl = r822_ecc_hwctl;
+ chip->ecc.calculate = r822_ecc_calculate;
+ chip->ecc.correct = r822_ecc_correct;
+
+ /* TODO: hack */
+ chip->ecc.read_oob = r822_read_oob;
+
+ /* init our device structure */
+ if (!(dev = kzalloc(sizeof(struct r822_device), GFP_KERNEL)))
+ goto error5;
+
+ chip->priv = dev;
+ dev->chip = chip;
+ dev->pci_dev = pci_dev;
+ pci_set_drvdata(pci_dev, dev);
+
+ dev->bounce_buffer = pci_alloc_consistent(pci_dev, R822_DMA_LEN,
+ &dev->phys_bounce_buffer);
+
+ if (!dev->bounce_buffer)
+ goto error6;
+
+ if (!(dev->mmio = pci_ioremap_bar (pci_dev, 0)))
+ goto error7;
+
+ if(!(dev->tmp_buffer = kzalloc(SM_SECTOR_SIZE, GFP_KERNEL)))
+ goto error8;
+
+ init_completion(&dev->dma_done);
+
+ if (!(dev->card_workqueue = create_freezeable_workqueue (DRV_NAME)))
+ goto error9;
+
+ INIT_DELAYED_WORK(&dev->card_detect_work, r822_card_detect_work);
+
+ /* shutdown everything - precation */
+ r822_device_shutdown(dev);
+ r822_disable_irqs(dev);
+ r822_dma_test(dev);
+
+ /*register irq handler*/
+ if (request_irq(pci_dev->irq, &r822_irq, IRQF_SHARED,
+ DRV_NAME, dev))
+ goto error10;
+
+ dev->irq = pci_dev->irq;
+ spin_lock_init(&dev->irqlock);
+
+ /* kick initial present test */
+ dev->card_detected = 0;
+ r822_card_update_present(dev);
+ queue_delayed_work(dev->card_workqueue,
+ &dev->card_detect_work, 0);
+
+ /* Load the FTL */
+ request_module_nowait("sm_ftl");
+
+ printk(KERN_NOTICE DRV_NAME ": driver loaded succesfully\n");
+ return 0;
+
+
+error10:
+ destroy_workqueue(dev->card_workqueue);
+error9:
+ kfree(dev->tmp_buffer);
+error8:
+ pci_iounmap(pci_dev, dev->mmio);
+
+error7:
+ pci_free_consistent(pci_dev, R822_DMA_LEN,
+ dev->bounce_buffer, dev->phys_bounce_buffer);
+error6:
+ kfree(dev);
+error5:
+ kfree(chip);
+error4:
+ pci_release_regions(pci_dev);
+error3:
+error2:
+ pci_disable_device(pci_dev);
+error1:
+ return error;
+}
+
+
+void r822_remove (struct pci_dev *pci_dev)
+{
+ struct r822_device *dev = pci_get_drvdata(pci_dev);
+
+ /* Stop detect workqueue - we are going to unregister the device anyway*/
+ cancel_delayed_work_sync(&dev->card_detect_work);
+ destroy_workqueue(dev->card_workqueue);
+
+ /* Unregister the device, this might make more IO */
+ r822_unregister_nand_device(dev);
+
+ /* Stop interrupts */
+ r822_disable_irqs(dev);
+ synchronize_irq(dev->irq);
+ free_irq(dev->irq, dev);
+
+ /* Cleanup */
+ kfree(dev->tmp_buffer);
+ pci_iounmap(pci_dev, dev->mmio);
+ pci_free_consistent(pci_dev, R822_DMA_LEN,
+ dev->bounce_buffer, dev->phys_bounce_buffer);
+ kfree(dev);
+ kfree(dev->chip);
+
+ /* Shutdown the PCI device */
+ pci_release_regions(pci_dev);
+ pci_disable_device(pci_dev);
+}
+
+void r822_shutdown (struct pci_dev *pci_dev)
+{
+ struct r822_device *dev = pci_get_drvdata(pci_dev);
+
+ cancel_delayed_work_sync(&dev->card_detect_work);
+ r822_disable_irqs(dev);
+ pci_disable_device(pci_dev);
+}
+
+int r822_suspend (struct device *device)
+{
+ struct r822_device *dev = pci_get_drvdata(to_pci_dev(device));
+
+ if (dev->ctlreg & R822_CTL_CARDENABLE)
+ return -EBUSY;
+
+ cancel_delayed_work_sync(&dev->card_detect_work);
+ r822_disable_irqs(dev);
+ r822_device_shutdown(dev);
+
+ /* If card was pulled off just during the suspend, which is very
+ unlikely, we will remove it on resume, it too late now
+ anyway... */
+ dev->card_unstable = 0;
+
+ pci_save_state(to_pci_dev(device));
+ pci_set_power_state(to_pci_dev(device), PCI_D3cold);
+
+
+ return 0;
+}
+
+int r822_resume (struct device *device)
+{
+ struct r822_device *dev = pci_get_drvdata(to_pci_dev(device));
+
+ pci_set_power_state(to_pci_dev(device), PCI_D0);
+ pci_restore_state(to_pci_dev(device));
+
+
+ r822_disable_irqs(dev);
+ r822_card_update_present(dev);
+ r822_device_shutdown(dev);
+
+
+ /* If card status changed, just do the work */
+ if (dev->card_detected != dev->card_registred) {
+ dbg("card was %s during low power state",
+ dev->card_detected ? "added" : "removed");
+
+ queue_delayed_work(dev->card_workqueue,
+ &dev->card_detect_work, 0);
+ return 0;
+ }
+
+ /* Otherwise, initialize the card */
+ if (dev->card_registred) {
+ r822_device_start(dev);
+ dev->chip->select_chip(dev->mtd, 0);
+ dev->chip->cmdfunc(dev->mtd, NAND_CMD_RESET, -1, -1);
+ dev->chip->cmdfunc(dev->mtd, NAND_CMD_READ0, -1, -1);
+ dev->chip->select_chip(dev->mtd, -1);
+ }
+
+ /* And start card detection IRQ */
+ r822_update_card_detect(dev);
+
+ return 0;
+}
+
+static const struct pci_device_id r822_pci_id_tbl[] = {
+
+ { PCI_VDEVICE(RICOH, PCI_DEVICE_ID_RICOH_RL5C852), },
+ { },
+};
+
+MODULE_DEVICE_TABLE(pci, r822_pci_id_tbl);
+
+SIMPLE_DEV_PM_OPS(r822_pm_ops, r822_suspend, r822_resume);
+
+
+static struct pci_driver r822_pci_driver = {
+ .name = DRV_NAME,
+ .id_table = r822_pci_id_tbl,
+ .probe = r822_probe,
+ .remove = r822_remove,
+ .shutdown = r822_shutdown,
+ .driver.pm = &r822_pm_ops,
+};
+
+static __init int r822_module_init(void)
+{
+ return pci_register_driver(&r822_pci_driver);
+}
+
+static void __exit r822_module_exit(void)
+{
+ pci_unregister_driver(&r822_pci_driver);
+}
+
+module_init(r822_module_init);
+module_exit(r822_module_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Maxim Levitsky <[email protected]>");
+MODULE_DESCRIPTION("Ricoh 85xx xD/smartmedia card reader driver");
diff --git a/drivers/mtd/nand/r822.h b/drivers/mtd/nand/r822.h
new file mode 100644
index 0000000..5b69629
--- /dev/null
+++ b/drivers/mtd/nand/r822.h
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2009 - Maxim Levitsky
+ * driver for Ricoh xD readers
+ *
+ * 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.
+ */
+
+#include <linux/pci.h>
+#include <linux/completion.h>
+#include <linux/workqueue.h>
+#include <linux/mtd/nand.h>
+#include <linux/spinlock.h>
+
+
+/* nand interface + ecc
+ byte write/read does one cycle on nand data lines.
+ dword write/read does 4 cycles
+ if R822_CTL_ECC_ACCESS is set in R822_CTL, then dword read reads
+ results of ecc correction, if DMA read was done before.
+ If write was done two dword reads read generated ecc checksums
+*/
+#define R822_DATALINE 0x00
+
+/* control register */
+#define R822_CTL 0x04
+#define R822_CTL_COMMAND 0x01 /* send command (#CLE)*/
+#define R822_CTL_DATA 0x02 /* read/write data (#ALE)*/
+#define R822_CTL_ON 0x04 /* only seem to controls the hd led, */
+ /* but has to be set on start...*/
+#define R822_CTL_RESET 0x08 /* unknown, set only on start once*/
+#define R822_CTL_CARDENABLE 0x10 /* probably (#CE) - always set*/
+#define R822_CTL_ECC_ENABLE 0x20 /* enable ecc engine */
+#define R822_CTL_ECC_ACCESS 0x40 /* read/write ecc via reg #0*/
+#define R822_CTL_WRITE 0x80 /* set when performing writes (#WP) */
+
+
+/* card detection status */
+#define R822_CARD_STA 0x05
+#define R822_CARD_STA_UNUSED1 0x01 /* unknown */
+#define R822_CARD_STA_RO 0x02 /* card is readonly -- test on #WP???*/
+#define R822_CARD_STA_PRESENT 0x04 /* card is present (#CD) */
+#define R822_CARD_STA_BUSY 0x80 /* card is busy - (#R/B) */
+
+/* card detection irq status*/
+#define R822_CARD_IRQ_STA 0x06 /* IRQ status */
+
+/* card detection irq enable */
+#define R822_CARD_IRQ_ENABLE 0x07 /* IRQ enable */
+
+#define R822_CARD_IRQ_UNUSED1 0x01 /* unknown */
+#define R822_CARD_IRQ_REMOVE 0x04 /* detect card removal */
+#define R822_CARD_IRQ_INSERT 0x08 /* detect card insert */
+#define R822_CARD_IRQ_UNUSED2 0x10 /* unknown */
+#define R822_CARD_IRQ_GENABLE 0x80 /* general enable */
+#define R822_CARD_IRQ_MASK 0x1D
+
+
+/* hardware enable */
+#define R822_HW 0x08
+#define R822_HW_ENABLED 0x01 /* hw enabled */
+#define R822_HW_UNKNOWN 0x80
+
+
+/* dma capabilities */
+#define R822_DMA_CAP 0x09
+#define R822_SMBIT 0x20 /* if set with bit #6 or bit #7, then */
+ /* hw is smartmedia */
+#define R822_DMA1 0x40 /* if set with bit #7, dma is supported */
+#define R822_DMA2 0x80 /* if set with bit #6, dma is supported */
+
+
+/* physical DMA address - 32 bit value*/
+#define R822_DMA_ADDR 0x0C
+
+
+/* dma settings */
+#define R822_DMA_SETTINGS 0x10
+#define R822_DMA_MEMORY 0x01 /* do real dma (memory <-> internal hw buffer) */
+#define R822_DMA_READ 0x02 /* 0 = write, 1 = read */
+#define R822_DMA_INTERNAL 0x04 /* transfer internal buffer from/to card */
+
+/* dma IRQ status */
+#define R822_DMA_IRQ_STA 0x14
+
+/* dma IRQ enable */
+#define R822_DMA_IRQ_ENABLE 0x18
+
+#define R822_DMA_IRQ_MEMORY 0x01 /* real dma done (memory <-> internal hw buffer) */
+#define R822_DMA_IRQ_ERROR 0x02 /* error did happen */
+#define R822_DMA_IRQ_INTERNAL 0x04 /* internal buffer was tranferred from/to card*/
+#define R822_DMA_IRQ_MASK 0x07 /* mask of all IRQ bits*/
+
+
+/* ECC syndrome format - read from reg #0 will return two copies of these for
+ each half of the page.
+ first byte is error byte location, and second, bit location + flags */
+#define R822_ECC_ERR_BIT_MSK 0x07 /* error bit location */
+#define R822_ECC_CORRECT 0x10 /* no errors - (guessed) */
+#define R822_ECC_CORRECTABLE 0x20 /* correctable error exist */
+#define R822_ECC_FAIL 0x40 /* non correctable error detected */
+
+#define R822_DMA_LEN 512
+
+#define DMA_INTERNAL 0
+#define DMA_MEMORY 1
+
+struct r822_device {
+ void __iomem *mmio; /* mmio */
+ struct mtd_info *mtd; /* mtd backpointer */
+ struct nand_chip *chip; /* nand chip backpointer */
+ struct pci_dev *pci_dev; /* pci backpointer */
+
+ /* dma area */
+ dma_addr_t phys_dma_addr; /* bus address of buffer*/
+ struct completion dma_done; /* data transfer done */
+
+ dma_addr_t phys_bounce_buffer; /* bus address of bounce buffer */
+ u8 *bounce_buffer; /* virtual address of bounce buffer */
+
+ int dma_dir; /* 1 = read, 0 = write */
+ int dma_stage; /* 0 - idle, 1 - first step,
+ 2 - second step */
+
+ int dma_state; /* 0 = internal, 1 = memory */
+ int dma_error; /* dma errors */
+ int dma_usable; /* is it possible to use dma */
+
+
+ /* card status area */
+ struct delayed_work card_detect_work;
+ struct workqueue_struct* card_workqueue;
+ int card_registred; /* card registered with mtd */
+ int card_detected; /* card detected in slot */
+ int card_unstable; /* whenever the card is inserted,
+ is not known yet */
+ int readonly; /* card is readonly */
+
+ /* misc */
+ spinlock_t irqlock; /* IRQ protecting lock */
+ void *tmp_buffer; /* temporary buffer */
+ u8 ctlreg; /* cached contents of control reg */
+ int irq; /* irq num */
+};
+
+#define DRV_NAME "r822xd"
+
+
+/* this will go to pci_ids.h */
+#define PCI_DEVICE_ID_RICOH_RL5C852 0x0852
+
+
+#define dbg(format, ...) \
+ printk (KERN_ERR DRV_NAME ": " format "\n", ## __VA_ARGS__)
--
1.6.3.3