2010-01-08 15:04:59

by Maxim Levitsky

[permalink] [raw]
Subject: RFC: [PATCH V2 0/9 Integration of SmartMedia/xD into mtd subsystem

Hi,

This is second version of my patchset.

Changes since V1:

-> Ported to latest kernel revision (2.6.33-rc3)
-> Resolved problem with oob layout
-> Resoved all checkpatch.pl issues
-> Tested that indeed all hardware devices of this type have
same PCI ID


Remaining issues:

* Write speed is of course still very low, but like I said, it might be
hardware problem.

* No support for 256 byte SmartMedia in the FTL. I don't have the
hardware to test that, and I think for now there is not much need for
that.
(It only applies to 1MB and 2MB SmartMedia devices anyway...)

* Like I said some XD cards are fake, thus have to be used with the FTL,
This is a hardware problem too.
If I knew which xD cards are affected, and had a way to know for sure
they have an FTL inside, I could implement a fake FTL, thus gain higher
write speed (and maybe solve that way issue #1)


Best regards,
Maxim Levitsky


2010-01-08 15:06:04

by Maxim Levitsky

[permalink] [raw]
Subject: [PATCH 1/9] MTD: call remove notifiers before removing the device

>From 1855da2a21882c803e5dfcfb556fff5bc6989c31 Mon Sep 17 00:00:00 2001
From: Maxim Levitsky <[email protected]>
Date: Fri, 8 Jan 2010 15:14:20 +0200
Subject: [PATCH 1/9] MTD: call remove notifiers before removing the device

This is first step in implementing proper hotplug support.
Now all users of the mtd device have a chance to put the mtd device
when they are notified to do so, and they have to do so to make hotplug work.

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 c356c0a..87ff83f 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


2010-01-08 15:06:45

by Maxim Levitsky

[permalink] [raw]
Subject: [PATCH 2/9] MTD: create lockless versions of {get,put}_mtd_device

>From 1531249dd2c0e33d727ed8cbd0719eb4e9af2266 Mon Sep 17 00:00:00 2001
From: Maxim Levitsky <[email protected]>
Date: Fri, 8 Jan 2010 15:51:22 +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.

Signed-off-by: Maxim Levitsky <[email protected]>
---
drivers/mtd/mtdcore.c | 61 ++++++++++++++++++++++++++++++-----------------
include/linux/mtd/mtd.h | 3 +-
2 files changed, 41 insertions(+), 23 deletions(-)

diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c
index 87ff83f..96b4a05 100644
--- a/drivers/mtd/mtdcore.c
+++ b/drivers/mtd/mtdcore.c
@@ -454,27 +454,38 @@ 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) {
+ ret = ERR_PTR(err);
+ goto out;
}

- ret->usecount++;
+ err = __get_mtd_device(ret);
+ if (err)
+ ret = ERR_PTR(err);
+out:
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) {
+
+ err = mtd->get_device(mtd);
+
+ if (err) {
+ module_put(mtd->owner);
+ return err;
+ }
+ }
+ mtd->usecount++;
+ return 0;
}

/**
@@ -525,18 +536,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 +585,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


2010-01-08 15:07:28

by Maxim Levitsky

[permalink] [raw]
Subject: [PATCH 3/9] MTD: blkdevs: make hotplug work

>From 427c68fd6cb0fd228dbf8eebc832f63799b119ca Mon Sep 17 00:00:00 2001
From: Maxim Levitsky <[email protected]>
Date: Fri, 8 Jan 2010 15:52:12 +0200
Subject: [PATCH 3/9] MTD: blkdevs: make hotplug work

This changes the blkdev common module for translation layers
to survive when underlying mtd device disappears.

To do so the following conceptual changes were made:

* disk queue and thread are now one per mtd device
This was it is easy to flush and destroy the queue

* the struct mtd_blktrans_dev will now be freed automaticly when last user
of the device quits.
All existing translation layers are adjusted

* ->open and release function of the translation layer will never be called
twise or more in the row.
This makes code simplier.
Also the ->release will be called just before mtd device disappears
This and above is the only visable changes on the outside.

Tested with mtdblock, ssfdc and my own sm_ftl on top of physicly hotplugable
nand card.

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 c82e09b..04a875f 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,
@@ -80,14 +75,13 @@ 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;

spin_lock_irq(rq->queue_lock);

while (!kthread_should_stop()) {
- struct mtd_blktrans_dev *dev;
int res;

if (!req && !(req = blk_fetch_request(rq))) {
@@ -98,13 +92,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);
@@ -123,8 +114,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);
}


@@ -132,72 +129,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 = {
@@ -214,6 +247,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);
@@ -239,12 +273,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:
@@ -252,11 +287,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;
@@ -274,25 +314,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();
@@ -300,9 +373,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;
}

@@ -343,9 +442,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);

@@ -353,39 +449,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)) {
- 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);

@@ -405,8 +474,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);
@@ -414,13 +481,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 96b4a05..0e86208 100644
--- a/drivers/mtd/mtdcore.c
+++ b/drivers/mtd/mtdcore.c
@@ -540,7 +540,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)
@@ -550,6 +549,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


2010-01-08 15:08:22

by Maxim Levitsky

[permalink] [raw]
Subject: [PATCH 4/9] MTD: make mtdtrans thread freezable.

>From e8648ebdc31d65556fccb340e4c16edc43017cf1 Mon Sep 17 00:00:00 2001
From: Maxim Levitsky <[email protected]>
Date: Fri, 8 Jan 2010 15:52:18 +0200
Subject: [PATCH 4/9] MTD: make mtdtrans thread freezable.

This makes the mtd blktrans thread enter the freezer in between accesses
to nand device.
This will ensure that we aren't suspending the system in the middle
of page read/write and even worse erase, which is a bad idea.

Signed-off-by: Maxim Levitsky <[email protected]>
---
drivers/mtd/mtd_blkdevs.c | 14 +++++++-------
1 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/drivers/mtd/mtd_blkdevs.c b/drivers/mtd/mtd_blkdevs.c
index 04a875f..db996d6 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,36 +80,35 @@ static int mtd_blktrans_thread(void *arg)
struct request_queue *rq = dev->rq;
struct request *req = NULL;

- 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


2010-01-08 15:09:06

by Maxim Levitsky

[permalink] [raw]
Subject: [PATCH 5/9] MTD: nand subsystem, export nand_do_read_oob and nand_do_write_oob

>From 5dbd1e5f7b5a4fe76053e25e744327156f015c6f Mon Sep 17 00:00:00 2001
From: Maxim Levitsky <[email protected]>
Date: Fri, 8 Jan 2010 15:54:42 +0200
Subject: [PATCH 5/9] MTD: nand subsystem, 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 | 5 +++++
2 files changed, 10 insertions(+), 5 deletions(-)

diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c
index 8f2958f..8e31245 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.
@@ -1536,7 +1533,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;
@@ -1620,6 +1617,7 @@ static int nand_do_read_oob(struct mtd_info *mtd, loff_t from,
ops->oobretlen = ops->ooblen;
return 0;
}
+EXPORT_SYMBOL_GPL(nand_do_read_oob);

/**
* nand_read_oob - [MTD Interface] NAND read data and/or out-of-band
@@ -2110,7 +2108,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;
@@ -2181,6 +2179,7 @@ static int nand_do_write_oob(struct mtd_info *mtd, loff_t to,

return 0;
}
+EXPORT_SYMBOL_GPL(nand_do_write_oob);

/**
* nand_write_oob - [MTD Interface] NAND write data and/or out-of-band
@@ -3082,6 +3081,7 @@ EXPORT_SYMBOL_GPL(nand_scan_ident);
EXPORT_SYMBOL_GPL(nand_scan_tail);
EXPORT_SYMBOL_GPL(nand_release);

+
static int __init nand_base_init(void)
{
led_trigger_register_simple("nand-disk", &nand_led_trigger);
diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h
index ccab9df..75575fb 100644
--- a/include/linux/mtd/nand.h
+++ b/include/linux/mtd/nand.h
@@ -469,6 +469,11 @@ 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);
/**
* struct platform_nand_chip - chip level device structure
* @nr_chips: max. number of chips to scan for
--
1.6.3.3


2010-01-08 15:09:47

by Maxim Levitsky

[permalink] [raw]
Subject: [PATCH 6/9] mtd: common module for smartmedia/xD support

>From 7297f3c08c9cd19b5cca4073afc225e1e4e293fb Mon Sep 17 00:00:00 2001
From: Maxim Levitsky <[email protected]>
Date: Fri, 8 Jan 2010 16:00:56 +0200
Subject: [PATCH 6/9] mtd: common module for smartmedia/xD support
This small module implements few helpers that are usefull
for nand drivers for SmartMedia/xD card readers
and for SmartMedia translation layer.

Signed-off-by: Maxim Levitsky <[email protected]>
---
drivers/mtd/Kconfig | 9 ++
drivers/mtd/Makefile | 1 +
drivers/mtd/sm_common.c | 227 +++++++++++++++++++++++++++++++++++++++++++++++
drivers/mtd/sm_common.h | 50 ++++++++++
4 files changed, 287 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..45b577f
--- /dev/null
+++ b/drivers/mtd/sm_common.c
@@ -0,0 +1,227 @@
+/*
+ * 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"
+
+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 */
+ }
+};
+
+static struct nand_ecclayout nand_oob_sm_smallpage = {
+ .eccbytes = 0,
+ .oobfree = {
+ {.offset = 0 , .length = 8},
+ }
+};
+
+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;
+
+ /* First test for erased block */
+ if (!memcmp(oob, erased_pattern, sizeof(struct sm_oob)))
+ 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 */
+ lba = sm_get_lba(oob->lba_copy1);
+
+ if (lba == -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_AUTO;
+ ops.ooboffs = 0;
+ ops.ooblen = sizeof(struct sm_oob);
+ 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 != sizeof(struct sm_oob))
+ goto out;
+
+ ret = (sm_block_valid(&oob) || sm_sector_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_AUTO;
+ ops.ooboffs = 0;
+ ops.ooblen = sizeof(struct sm_oob);
+ ops.oobbuf = (void *)&oob;
+ ops.datbuf = NULL;
+
+ ret = nand_do_write_oob(mtd, ofs, &ops);
+ if (ret < 0 || ops.oobretlen != sizeof(struct sm_oob)) {
+ printk(KERN_NOTICE "sm_common: can't mark sector at %i as bad\n",
+ (int)ofs);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int sm_block_bad_dummy(struct mtd_info *mtd, loff_t ofs, int getchip)
+{
+ return 0;
+}
+
+static int sm_block_markbad_dummy(struct mtd_info *mtd, loff_t ofs)
+{
+ return -EIO;
+}
+
+int sm_register_device(struct mtd_info *mtd)
+{
+ struct nand_chip *chip = (struct nand_chip *)mtd->priv;
+ int ret;
+
+ chip->options |= NAND_SKIP_BBTSCAN | NAND_NO_AUTOINCR;
+
+ /* Scan for card properties */
+ ret = nand_scan_ident(mtd, 1);
+
+ if (ret)
+ return ret;
+
+ /* Set oob handling functions. */
+ if (mtd->writesize == SM_SECTOR_SIZE) {
+ chip->block_bad = sm_block_bad;
+ chip->block_markbad = sm_block_markbad;
+ chip->ecc.layout = &nand_oob_sm;
+
+ /* SmartMedia on small page nand has page depedent oob layout,
+ thus let FTL do that hard job */
+ } else if (mtd->writesize == SM_SMALL_PAGE) {
+ chip->block_bad = sm_block_bad_dummy;
+ chip->block_markbad = sm_block_markbad_dummy;
+ chip->ecc.layout = &nand_oob_sm_smallpage;
+ } else
+ return -ENODEV;
+
+ ret = nand_scan_tail(mtd);
+ if (ret)
+ return ret;
+
+ ret = add_mtd_device(mtd);
+
+ if (ret)
+ return ret;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(sm_register_device);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Maxim Levitsky <[email protected]>");
+MODULE_DESCRIPTION("Common SmartMedia/xD functions");
diff --git a/drivers/mtd/sm_common.h b/drivers/mtd/sm_common.h
new file mode 100644
index 0000000..42d94b2
--- /dev/null
+++ b/drivers/mtd/sm_common.h
@@ -0,0 +1,50 @@
+/*
+ * 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>
+
+/* Full oob structure as written on the flash */
+struct sm_oob_raw {
+ u32 reserved;
+ u8 data_status;
+ u8 block_status;
+ u8 lba_copy1[2];
+ u8 ecc2[3];
+ u8 lba_copy2[2];
+ u8 ecc1[3];
+} __attribute__((packed));
+
+/* oob structure without ecc fields, used by external interfaces */
+struct sm_oob {
+ u32 reserved;
+ u8 data_status;
+ u8 block_status;
+ u8 lba_copy1[2];
+ u8 lba_copy2[2];
+} __attribute__((packed));
+
+
+/* one sector is always 512 bytes, but it can consist of two nand pages */
+#define SM_SECTOR_SIZE 512
+
+/* minumum size of the page */
+#define SM_SMALL_PAGE 256
+
+/* oob area is also 16 bytes, but might be from two pages */
+#define SM_OOB_SIZE 16
+
+/* This is maximum zone size, and all devices that have more that one zone
+ have this size */
+#define SM_MAX_ZONE_SIZE 1024
+
+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);
--
1.6.3.3


2010-01-08 15:10:32

by Maxim Levitsky

[permalink] [raw]
Subject: [PATCH 7/9] MTD: add few workarounds to nand system for SmartMedia/xD chips.

>From 4ade18c0eff30b0a5aa7360651448ec4efff761f Mon Sep 17 00:00:00 2001
From: Maxim Levitsky <[email protected]>
Date: Fri, 8 Jan 2010 16:03:10 +0200
Subject: [PATCH 7/9] MTD: add few workarounds to nand system for SmartMedia/xD chips.

* Add an option NAND_SMARTMEDIA that can be set by nand driver
If set, it will cause separate ID table to be used, which includes
mask rom devices and new xD cards

* Workaround for wrong write protect status on some xD cards

Signed-off-by: Maxim Levitsky <[email protected]>
---
drivers/mtd/nand/nand_base.c | 29 +++++++++++++++++++++--------
drivers/mtd/nand/nand_ids.c | 38 ++++++++++++++++++++++++++++++++++++++
drivers/mtd/sm_common.c | 2 +-
include/linux/mtd/nand.h | 11 +++++++++++
4 files changed, 71 insertions(+), 9 deletions(-)

diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c
index 8e31245..a4a9284 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;
}

/**
@@ -2624,14 +2632,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)
@@ -2988,7 +3000,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..7ac7538 100644
--- a/drivers/mtd/nand/nand_ids.c
+++ b/drivers/mtd/nand/nand_ids.c
@@ -127,6 +127,44 @@ 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 45b577f..6234482 100644
--- a/drivers/mtd/sm_common.c
+++ b/drivers/mtd/sm_common.c
@@ -186,7 +186,7 @@ int sm_register_device(struct mtd_info *mtd)
struct nand_chip *chip = (struct nand_chip *)mtd->priv;
int ret;

- chip->options |= NAND_SKIP_BBTSCAN | NAND_NO_AUTOINCR;
+ chip->options |= NAND_SKIP_BBTSCAN | NAND_SMARTMEDIA | NAND_NO_AUTOINCR;

/* Scan for card properties */
ret = nand_scan_ident(mtd, 1);
diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h
index 75575fb..a3670c1 100644
--- a/include/linux/mtd/nand.h
+++ b/include/linux/mtd/nand.h
@@ -170,6 +170,12 @@ 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 \
(NAND_NO_PADDING | NAND_CACHEPRG | NAND_COPYBACK)
@@ -195,9 +201,13 @@ 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
+
/* Chip may not exist, so silence any errors in scan */
#define NAND_SCAN_SILENT_NODEV 0x00080000

+/* controller supports only xD/SmartMedia cards*/
+#define NAND_SMARTMEDIA 0x00100000
+
/* Options set by nand scan */
/* Nand scan has allocated controller struct */
#define NAND_CONTROLLER_ALLOC 0x80000000
@@ -458,6 +468,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[];

extern int nand_scan_bbt(struct mtd_info *mtd, struct nand_bbt_descr *bd);
--
1.6.3.3


2010-01-08 15:11:18

by Maxim Levitsky

[permalink] [raw]
Subject: [PATCH 8/9] mtd: Add nand driver for ricoh xD/SmartMedia reader

>From 8ba5765479d06c2b2cd5d54ad62bd4c4406dac85 Mon Sep 17 00:00:00 2001
From: Maxim Levitsky <[email protected]>
Date: Fri, 8 Jan 2010 16:26:41 +0200
Subject: [PATCH 8/9] mtd: Add nand driver for ricoh xD/SmartMedia reader

This adds a driver for Ricoh xD card reader that has
0x0852 PCI ID

Since the reader is a part of larger mulifunction chip, it
is hard to determine the correct model name. Might be R5C852.

Driver is complete, but bewere of the fact that some
(probably only type M) xD cards are 'fake' which means that
they have an on board CPU and expose emulated nand command set
Thus they demand to have proper values written in the oob area,
and therefore only useful with SmartMedia FTL.

Signed-off-by: Maxim Levitsky <[email protected]>
---
MAINTAINERS | 6 +
drivers/mtd/nand/Kconfig | 11 +
drivers/mtd/nand/Makefile | 1 +
drivers/mtd/nand/r822.c | 1051 +++++++++++++++++++++++++++++++++++++++++++++
drivers/mtd/nand/r822.h | 155 +++++++
5 files changed, 1224 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 8f0109b..81c451b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4585,6 +4585,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 677cd53..59bfabb 100644
--- a/drivers/mtd/nand/Kconfig
+++ b/drivers/mtd/nand/Kconfig
@@ -104,6 +104,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 1407bd1..986ab9c 100644
--- a/drivers/mtd/nand/Makefile
+++ b/drivers/mtd/nand/Makefile
@@ -42,5 +42,6 @@ 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_BCM_UMI) += bcm_umi_nand.o nand_bcm_umi.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..9dbb578
--- /dev/null
+++ b/drivers/mtd/nand/r822.c
@@ -0,0 +1,1051 @@
+/*
+ * 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 == DMA_ERROR_CODE)
+ 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 */
+ error = r822_dma_wait(dev)
+
+ if (error) {
+ 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_raw *oob = (struct sm_oob_raw *)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)
+{
+ dev->mtd = kzalloc(sizeof(struct mtd_info), GFP_KERNEL);
+
+ if (!dev->mtd)
+ 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 */
+ error = pci_enable_device(pci_dev);
+
+ if (error)
+ goto error1;
+
+ pci_set_master(pci_dev);
+
+ error = pci_set_dma_mask(pci_dev, DMA_32BIT_MASK);
+ if (error)
+ goto error2;
+
+ error = pci_request_regions(pci_dev, DRV_NAME);
+
+ if (error)
+ goto error3;
+
+ error = -ENOMEM;
+
+ /* init nand chip, but register it only on card insert */
+ chip = kzalloc(sizeof(struct nand_chip), GFP_KERNEL);
+
+ if (!chip)
+ 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 */
+ dev = kzalloc(sizeof(struct r822_device), GFP_KERNEL);
+
+ if (!dev)
+ 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;
+
+
+ error = -ENODEV;
+ dev->mmio = pci_ioremap_bar(pci_dev, 0);
+
+ if (!dev->mmio)
+ goto error7;
+
+ error = -ENOMEM;
+ dev->tmp_buffer = kzalloc(SM_SECTOR_SIZE, GFP_KERNEL);
+
+ if (!dev->tmp_buffer)
+ goto error8;
+
+ init_completion(&dev->dma_done);
+
+ dev->card_workqueue = create_freezeable_workqueue(DRV_NAME);
+
+ if (!dev->card_workqueue)
+ 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*/
+ error = -ENODEV;
+ 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..1b41769
--- /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 w/bit #7, dma is supported */
+#define R822_DMA2 0x80 /* if set w/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 /* (memory <-> internal hw buffer) */
+#define R822_DMA_READ 0x02 /* 0 = write, 1 = read */
+#define R822_DMA_INTERNAL 0x04 /* (internal hw buffer <-> 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 /* (memory <-> internal hw buffer) */
+#define R822_DMA_IRQ_ERROR 0x02 /* error did happen */
+#define R822_DMA_IRQ_INTERNAL 0x04 /* (internal hw buffer <-> 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


2010-01-08 15:12:14

by Maxim Levitsky

[permalink] [raw]
Subject: [PATCH 9/9] mtd: Add new SmartMedia/xD FTL

>From 743f723b6e7134cf9d99a158f3ac920c180d406a Mon Sep 17 00:00:00 2001
From: Maxim Levitsky <[email protected]>
Date: Fri, 8 Jan 2010 16:44:07 +0200
Subject: [PATCH 9/9] mtd: Add new SmartMedia/xD FTL

This implements new readwrite SmartMedia/xd FTL.

It depends on nand driver to define proper oob layout that excludes
all ecc areas and nothing more.

Support for very old 256 byte/page devices is not yet enabled/complete.
For these devices, all ecc handling will be done inside this FTL
due to wierd oob layout.

Signed-off-by: Maxim Levitsky <[email protected]>
---
drivers/mtd/Kconfig | 12 +
drivers/mtd/Makefile | 1 +
drivers/mtd/sm_ftl.c | 1043 ++++++++++++++++++++++++++++++++++++++++++++++++++
drivers/mtd/sm_ftl.h | 75 ++++
4 files changed, 1131 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..b72c30b
--- /dev/null
+++ b/drivers/mtd/sm_ftl.c
@@ -0,0 +1,1043 @@
+/*
+ * 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 <linux/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;
+
+ if (mtd->oobavail < sizeof(struct sm_oob))
+ 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 * SM_MAX_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;
+
+ ops.mode = MTD_OOB_AUTO;
+ ops.ooboffs = 0;
+ ops.ooblen = sizeof(struct sm_oob);
+ 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, sizeof(struct sm_oob));
+ 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 != sizeof(struct sm_oob)) {
+ 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;
+ }
+
+ 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 (zone_num == 0 && (block == ftl->cis_block || block == 0)) {
+ dbg("attempted to write the CIS!");
+ return -EIO;
+ }
+
+
+ ops.len = SM_SECTOR_SIZE;
+
+ ops.mode = MTD_OOB_AUTO;
+ ops.ooboffs = 0;
+ ops.ooblen = sizeof(struct sm_oob);
+ 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;
+
+ 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_in(&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;
+ }
+
+ tmp = sm_read_lba(&oob);
+
+ if (tmp != 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.
+ */
+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;
+
+ 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_AUTO;
+ ops.ooboffs = 0;
+ ops.ooblen = sizeof(struct sm_oob);
+ 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;
+
+ BUG_ON(zone_num >= ftl->zone_count);
+ zone = &ftl->zones[zone_num];
+ if (zone->initialized)
+ return zone;
+
+ dbg("initializing zone %d", zone_num);
+
+ zone->lba_to_phys_table = kmalloc(ftl->max_lba * 2, GFP_KERNEL);
+
+ if (!zone->lba_to_phys_table)
+ return ERR_PTR(-ENOMEM);
+
+ if (kfifo_alloc(&zone->free_sectors, ftl->zone_size * 2, GFP_KERNEL)) {
+ 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_in(&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_out(&zone->free_sectors, (unsigned char *)&block, 2);
+ kfifo_in(&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;
+
+ int sector_num;
+ u16 write_sector;
+ int zone_num = cache_entry->zone;
+ int block_num;
+
+ BUG_ON(cache_entry->zone < 0);
+ zone = &ftl->zones[cache_entry->zone];
+ 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_out(&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;
+
+ 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);
+
+ error = sm_cache_block_init(ftl, cache_entry);
+ if (error)
+ 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;
+
+ /* Try to write the cache if possible */
+ mutex_lock(&ftl->cache_mutex);
+
+ 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 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)) {
+
+ error = __sm_cache_flush(ftl);
+
+ if (error)
+ 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 */
+ ftl = kzalloc(sizeof(struct sm_ftl), GFP_KERNEL);
+ if (!ftl)
+ 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 */
+ trans = kzalloc(sizeof(struct mtd_blktrans_dev), GFP_KERNEL);
+ if (!trans)
+ 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");
diff --git a/drivers/mtd/sm_ftl.h b/drivers/mtd/sm_ftl.h
new file mode 100644
index 0000000..d86d00e
--- /dev/null
+++ b/drivers/mtd/sm_ftl.h
@@ -0,0 +1,75 @@
+/*
+ * 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;
+};
+
+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


2010-01-08 15:27:32

by Maxim Levitsky

[permalink] [raw]
Subject: Re: [PATCH 2/9] MTD: create lockless versions of {get,put}_mtd_device

On Fri, 2010-01-08 at 16:21 +0100, Jörn Engel wrote:
> On Fri, 8 January 2010 17:06:38 +0200, Maxim Levitsky wrote:
> >
> > + err = __get_mtd_device(ret);
>
> Function is called here, afaics not declared and defined...
>
> > [...]
> > +int __get_mtd_device(struct mtd_info *mtd)
>
> ...further below. Didn't gcc complain about this? Or am I missing
> something?
>
> Jörn
>

Yep, you miss this:

+extern int __get_mtd_device(struct mtd_info *mtd);
+extern void __put_mtd_device(struct mtd_info *mtd);

I put that into the header, so these functions can be used outside.

When I compiled the kernel, there were no warnings.

Best regards,
Maxim Levitsky

2010-01-08 15:29:55

by Jörn Engel

[permalink] [raw]
Subject: Re: [PATCH 9/9] mtd: Add new SmartMedia/xD FTL

On Fri, 8 January 2010 17:12:04 +0200, Maxim Levitsky wrote:
>
> This implements new readwrite SmartMedia/xd FTL.
>
> It depends on nand driver to define proper oob layout that excludes
> all ecc areas and nothing more.
>
> Support for very old 256 byte/page devices is not yet enabled/complete.
> For these devices, all ecc handling will be done inside this FTL
> due to wierd oob layout.
>
> Signed-off-by: Maxim Levitsky <[email protected]>
> ---
> drivers/mtd/Kconfig | 12 +
> drivers/mtd/Makefile | 1 +
> drivers/mtd/sm_ftl.c | 1043 ++++++++++++++++++++++++++++++++++++++++++++++++++
> drivers/mtd/sm_ftl.h | 75 ++++

sm_ftl is certainly nicer than ssfdc.c - at least I can decrypt the
acronym. But I am missing a reason why ssfdc is not sufficient and we
need yet another implementation of the same ftl.

Or maybe ssfdc is sufficient or barely sufficient and just needs a small
patch?

Jörn

--
He that composes himself is wiser than he that composes a book.
-- B. Franklin

2010-01-08 15:31:24

by Jörn Engel

[permalink] [raw]
Subject: Re: [PATCH 2/9] MTD: create lockless versions of {get,put}_mtd_device

On Fri, 8 January 2010 17:27:24 +0200, Maxim Levitsky wrote:
>
> Yep, you miss this:
>
> +extern int __get_mtd_device(struct mtd_info *mtd);
> +extern void __put_mtd_device(struct mtd_info *mtd);
>
> I put that into the header, so these functions can be used outside.
>
> When I compiled the kernel, there were no warnings.

Maxim: 1
Joern: 0

Jörn

--
But this is not to say that the main benefit of Linux and other GPL
software is lower-cost. Control is the main benefit--cost is secondary.
-- Bruce Perens

2010-01-08 15:35:39

by Maxim Levitsky

[permalink] [raw]
Subject: Re: [PATCH 9/9] mtd: Add new SmartMedia/xD FTL

On Fri, 2010-01-08 at 16:29 +0100, Jörn Engel wrote:
> On Fri, 8 January 2010 17:12:04 +0200, Maxim Levitsky wrote:
> >
> > This implements new readwrite SmartMedia/xd FTL.
> >
> > It depends on nand driver to define proper oob layout that excludes
> > all ecc areas and nothing more.
> >
> > Support for very old 256 byte/page devices is not yet enabled/complete.
> > For these devices, all ecc handling will be done inside this FTL
> > due to wierd oob layout.
> >
> > Signed-off-by: Maxim Levitsky <[email protected]>
> > ---
> > drivers/mtd/Kconfig | 12 +
> > drivers/mtd/Makefile | 1 +
> > drivers/mtd/sm_ftl.c | 1043 ++++++++++++++++++++++++++++++++++++++++++++++++++
> > drivers/mtd/sm_ftl.h | 75 ++++
>
> sm_ftl is certainly nicer than ssfdc.c - at least I can decrypt the
> acronym. But I am missing a reason why ssfdc is not sufficient and we
> need yet another implementation of the same ftl.
ssfdc is readonly, and bulk of the hard work is the write part.
Also ssfdc creates one big table by reading oob of whole device and this
takes about 1 second here.
I instead defer this by doing it one per zone.

Ideally ssfdc can be deprecated/removed, since my driver is its
superset.
However if one needs only R/O support he can use the smaller and safer
ssfdc.c

I also though about patching the ssfdc.c, but really there will be
nothing old left after doing that.

Best regards,
Maxim Levitsky

2010-01-08 15:39:05

by Jörn Engel

[permalink] [raw]
Subject: Re: [PATCH 2/9] MTD: create lockless versions of {get,put}_mtd_device

On Fri, 8 January 2010 17:06:38 +0200, Maxim Levitsky wrote:
>
> + err = __get_mtd_device(ret);

Function is called here, afaics not declared and defined...

> [...]
> +int __get_mtd_device(struct mtd_info *mtd)

...further below. Didn't gcc complain about this? Or am I missing
something?

Jörn

--
Luck is when opportunity meets good preparation.
-- Chinese proverb

2010-01-08 16:04:14

by Jörn Engel

[permalink] [raw]
Subject: Re: RFC: [PATCH V2 0/9 Integration of SmartMedia/xD into mtd subsystem

On Fri, 8 January 2010 17:04:52 +0200, Maxim Levitsky wrote:
>
> * Write speed is of course still very low, but like I said, it might be
> hardware problem.

Iirc the alauda could do about 400kB/s. Speed didn't change noticeably
between 16M and 256M cards. My conclusion back then was to blame the
alauda chip. If you get even less, the problem likely isn't in the
cards but in the reader and/or your driver.

> * No support for 256 byte SmartMedia in the FTL. I don't have the
> hardware to test that, and I think for now there is not much need for
> that.
> (It only applies to 1MB and 2MB SmartMedia devices anyway...)

Fair enough. If either of the two people who still use such cards
cares... ;)

Jörn

--
I've never met a human being who would want to read 17,000 pages of
documentation, and if there was, I'd kill him to get him out of the
gene pool.
-- Joseph Costello

2010-01-08 17:40:35

by Maxim Levitsky

[permalink] [raw]
Subject: Re: RFC: [PATCH V2 0/9 Integration of SmartMedia/xD into mtd subsystem

On Fri, 2010-01-08 at 17:04 +0100, Jörn Engel wrote:
> On Fri, 8 January 2010 17:04:52 +0200, Maxim Levitsky wrote:
> >
> > * Write speed is of course still very low, but like I said, it might be
> > hardware problem.
>
> Iirc the alauda could do about 400kB/s. Speed didn't change noticeably
> between 16M and 256M cards. My conclusion back then was to blame the
> alauda chip. If you get even less, the problem likely isn't in the
> cards but in the reader and/or your driver.
Who knows...
Like I said, I get around 330 Kbytes/s in card reader integrated in
printer. It is still much higher that what I get with this driver....

I will figure that out sooner or later.

Btw, your cards aren't Type M, right?
However I use here a Type M card, and its likely to include an FTL
inside, thus, it might explain the low speeds I get there partially...

Best regards,
Maxim Levitsky

2010-01-09 00:26:11

by Jörn Engel

[permalink] [raw]
Subject: Re: RFC: [PATCH V2 0/9 Integration of SmartMedia/xD into mtd subsystem

On Fri, 8 January 2010 19:40:29 +0200, Maxim Levitsky wrote:
>
> Btw, your cards aren't Type M, right?
> However I use here a Type M card, and its likely to include an FTL
> inside, thus, it might explain the low speeds I get there partially...

No, mine were the old ones before marketing invented the two types.

Jörn

--
Fancy algorithms are buggier than simple ones, and they're much harder
to implement. Use simple algorithms as well as simple data structures.
-- Rob Pike

2010-01-11 22:24:38

by Maxim Levitsky

[permalink] [raw]
Subject: Re: RFC: [PATCH V2 0/9 Integration of SmartMedia/xD into mtd subsystem

On Sat, 2010-01-09 at 01:26 +0100, Jörn Engel wrote:
> On Fri, 8 January 2010 19:40:29 +0200, Maxim Levitsky wrote:
> >
> > Btw, your cards aren't Type M, right?
> > However I use here a Type M card, and its likely to include an FTL
> > inside, thus, it might explain the low speeds I get there partially...
>
> No, mine were the old ones before marketing invented the two types.
>
> Jörn
>

I finally located the source of slowdown.
It appears to be caused by too large caching I did (5 erase blocks).
If I cache one block, write speed nears 1 MB/s.
As a bonus that makes the FTL much simplified.

Another small flaw that currently exists in my FTL is that it ignores
the underlying badblock support from mtd driver and requeres it to
expose the two bad block bytes in the oob layout, I think I can avoid
that after all.

Other that that, everything is just perfect.

I post updated patchset soon.

Best regards,
Maxim Levitsky