public inbox for linux-mtd@lists.infradead.org
 help / color / mirror / Atom feed
* RFC: [PATCH 0/9 Integration of SmartMedia/xD into mtd subsystem
@ 2010-01-06 21:43 Maxim Levitsky
  2010-01-06 21:44 ` [PATCH 1/9] MTD: call remove notifiers before removing the device Maxim Levitsky
                   ` (8 more replies)
  0 siblings, 9 replies; 10+ messages in thread
From: Maxim Levitsky @ 2010-01-06 21:43 UTC (permalink / raw)
  To: linux-kernel; +Cc: joern, linux-mtd, Alex Dubov

Here is the result of my work of supporting an xD card reader that I
have on my notebook.

I had to fix few problems in mtd translation layer, add few workarounds.
This is by no way a final version of the patches, there is still lot of
cleanup to do.
The patches weren't tested with checkpatch.pl for example.

I can now read and write an xD card, and I have no crashes.
Still following issues remain:

1 - Write speed IS very SLOW, just about 200 Kbytes/s
In fact a reader in my printer gives me about 330 Kbytes/s, thus
partially card is to blame.
Also, hardware doesn't support interrupts to test when card is ready,
thus writes consume 100% of one cpu.

2 - I rely on mtd driver to have empty oob layout so I can read both oob
and data using ->read_oob  with MTD_OOB_PLACE.
I thinking to add new mode to allow to read whole oob + data and check
ecc, or I will have to do pointless copying of data from 'censored' oob
to normal structure. (This applies to FTL driver I also include in this
patchset)

3 - Using the mtd device directly with anything but supplied FTL or
SSFDC, on 'modern' xD cards just doesn't. These cards implement a fake
nand command set, thus don't have a real oob.
It was disappointing for me too.
I suspect that all 'Type M' cards are of this fake type, but older cards
are OK.


4 - Suspend/resume support works, but relies on all mtd users not to
suspend in middle of card access, bacause in this case its very
difficult/impossible to know card state.
Driver will refuse suspend in that case.
Access via all block devices is safe.


5 - And of course there are bugs, thus I warn you now that this driver
has high probability to erase data from your card.
Thus DON'T USE THIS DRIVER IF ANYTHING IMPORTANT IS STORED ON THE CARD.


6 - driver only detects PCI ID of my notebook, so it might not load on
compatable chips with different ID.


7 - patches developed against stable 2.6.32, and won't compile against
latest git due to changes in kfifo api.


Feedback is welcome, flames too :-)


Best regards,
Maxim Levitsky

^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH 1/9] MTD: call remove notifiers before removing the device
  2010-01-06 21:43 RFC: [PATCH 0/9 Integration of SmartMedia/xD into mtd subsystem Maxim Levitsky
@ 2010-01-06 21:44 ` Maxim Levitsky
  2010-01-06 21:45 ` [PATCH 2/9] MTD: create lockless versions of {get,put}_mtd_device Maxim Levitsky
                   ` (7 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Maxim Levitsky @ 2010-01-06 21:44 UTC (permalink / raw)
  To: linux-kernel; +Cc: joern, linux-mtd, Alex Dubov

>From d8cb6b03919de5ab1587cd4340c684f2c468aec7 Mon Sep 17 00:00:00 2001
From: Maxim Levitsky <maximlevitsky@gmail.com>
Date: Tue, 5 Jan 2010 22:29:31 +0200
Subject: [PATCH 1/9] MTD: call remove notifiers before removing the device, so users
 have chance to free the mtd device

Signed-off-by: Maxim Levitsky <maximlevitsky@gmail.com>
---
 drivers/mtd/mtdcore.c |   21 +++++++++++----------
 1 files changed, 11 insertions(+), 10 deletions(-)

diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c
index 467a4f1..738e329 100644
--- a/drivers/mtd/mtdcore.c
+++ b/drivers/mtd/mtdcore.c
@@ -341,31 +341,32 @@ int add_mtd_device(struct mtd_info *mtd)
 int del_mtd_device (struct mtd_info *mtd)
 {
 	int ret;
+	struct mtd_notifier *not;
 
 	mutex_lock(&mtd_table_mutex);
 
 	if (mtd_table[mtd->index] != mtd) {
 		ret = -ENODEV;
-	} else if (mtd->usecount) {
+		goto out_error;
+	}
+
+	/* No need to get a refcount on the module containing
+		the notifier, since we hold the mtd_table_mutex */
+	list_for_each_entry(not, &mtd_notifiers, list)
+		not->remove(mtd);
+
+	if (mtd->usecount) {
 		printk(KERN_NOTICE "Removing MTD device #%d (%s) with use count %d\n",
 		       mtd->index, mtd->name, mtd->usecount);
 		ret = -EBUSY;
 	} else {
-		struct mtd_notifier *not;
-
 		device_unregister(&mtd->dev);
-
-		/* No need to get a refcount on the module containing
-		   the notifier, since we hold the mtd_table_mutex */
-		list_for_each_entry(not, &mtd_notifiers, list)
-			not->remove(mtd);
-
 		mtd_table[mtd->index] = NULL;
-
 		module_put(THIS_MODULE);
 		ret = 0;
 	}
 
+out_error:
 	mutex_unlock(&mtd_table_mutex);
 	return ret;
 }
-- 
1.6.3.3

^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 2/9] MTD: create lockless versions of {get,put}_mtd_device
  2010-01-06 21:43 RFC: [PATCH 0/9 Integration of SmartMedia/xD into mtd subsystem Maxim Levitsky
  2010-01-06 21:44 ` [PATCH 1/9] MTD: call remove notifiers before removing the device Maxim Levitsky
@ 2010-01-06 21:45 ` Maxim Levitsky
  2010-01-06 21:46 ` [PATCH 3/9] MTD: blkdevs: major cleanups Maxim Levitsky
                   ` (6 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Maxim Levitsky @ 2010-01-06 21:45 UTC (permalink / raw)
  To: linux-kernel; +Cc: joern, linux-mtd, Alex Dubov

>From 10333e0ed6dc7b176f2dfce04f08572f9e1b04ce Mon Sep 17 00:00:00 2001
From: Maxim Levitsky <maximlevitsky@gmail.com>
Date: Tue, 5 Jan 2010 22:29:31 +0200
Subject: [PATCH 2/9] MTD: create lockless versions of {get,put}_mtd_device

This will be used to resolve deadlock in block translation layer
---
 drivers/mtd/mtdcore.c   |   55 +++++++++++++++++++++++++++-------------------
 include/linux/mtd/mtd.h |    3 +-
 2 files changed, 34 insertions(+), 24 deletions(-)

diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c
index 738e329..e9daf87 100644
--- a/drivers/mtd/mtdcore.c
+++ b/drivers/mtd/mtdcore.c
@@ -454,27 +454,30 @@ struct mtd_info *get_mtd_device(struct mtd_info *mtd, int num)
 			ret = NULL;
 	}
 
-	if (!ret)
-		goto out_unlock;
-
-	if (!try_module_get(ret->owner))
-		goto out_unlock;
-
-	if (ret->get_device) {
-		err = ret->get_device(ret);
-		if (err)
-			goto out_put;
-	}
+	if (!ret || (err = __get_mtd_device(ret)))
+		ret = ERR_PTR(err);
 
-	ret->usecount++;
 	mutex_unlock(&mtd_table_mutex);
 	return ret;
+}
 
-out_put:
-	module_put(ret->owner);
-out_unlock:
-	mutex_unlock(&mtd_table_mutex);
-	return ERR_PTR(err);
+
+int __get_mtd_device(struct mtd_info *mtd)
+{
+	int err;
+
+	if (!try_module_get(mtd->owner))
+		return -ENODEV;
+
+	if (mtd->get_device) {
+
+		if ((err = mtd->get_device(mtd))) {
+			module_put(mtd->owner);
+			return err;
+		}
+	}
+	mtd->usecount++;
+	return 0;
 }
 
 /**
@@ -525,18 +528,22 @@ out_unlock:
 
 void put_mtd_device(struct mtd_info *mtd)
 {
-	int c;
-
 	mutex_lock(&mtd_table_mutex);
-	c = --mtd->usecount;
-	if (mtd->put_device)
-		mtd->put_device(mtd);
+	__put_mtd_device(mtd);
 	mutex_unlock(&mtd_table_mutex);
-	BUG_ON(c < 0);
 
 	module_put(mtd->owner);
 }
 
+void __put_mtd_device(struct mtd_info *mtd)
+{
+	--mtd->usecount;
+	BUG_ON(mtd->usecount < 0);
+
+	if (mtd->put_device)
+		mtd->put_device(mtd);
+}
+
 /* default_mtd_writev - default mtd writev method for MTD devices that
  *			don't implement their own
  */
@@ -570,7 +577,9 @@ EXPORT_SYMBOL_GPL(add_mtd_device);
 EXPORT_SYMBOL_GPL(del_mtd_device);
 EXPORT_SYMBOL_GPL(get_mtd_device);
 EXPORT_SYMBOL_GPL(get_mtd_device_nm);
+EXPORT_SYMBOL_GPL(__get_mtd_device);
 EXPORT_SYMBOL_GPL(put_mtd_device);
+EXPORT_SYMBOL_GPL(__put_mtd_device);
 EXPORT_SYMBOL_GPL(register_mtd_user);
 EXPORT_SYMBOL_GPL(unregister_mtd_user);
 EXPORT_SYMBOL_GPL(default_mtd_writev);
diff --git a/include/linux/mtd/mtd.h b/include/linux/mtd/mtd.h
index 0f32a9b..662d747 100644
--- a/include/linux/mtd/mtd.h
+++ b/include/linux/mtd/mtd.h
@@ -290,8 +290,9 @@ extern int add_mtd_device(struct mtd_info *mtd);
 extern int del_mtd_device (struct mtd_info *mtd);
 
 extern struct mtd_info *get_mtd_device(struct mtd_info *mtd, int num);
+extern int __get_mtd_device(struct mtd_info *mtd);
+extern void __put_mtd_device(struct mtd_info *mtd);
 extern struct mtd_info *get_mtd_device_nm(const char *name);
-
 extern void put_mtd_device(struct mtd_info *mtd);
 

-- 
1.6.3.3

^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 3/9] MTD: blkdevs: major cleanups
  2010-01-06 21:43 RFC: [PATCH 0/9 Integration of SmartMedia/xD into mtd subsystem Maxim Levitsky
  2010-01-06 21:44 ` [PATCH 1/9] MTD: call remove notifiers before removing the device Maxim Levitsky
  2010-01-06 21:45 ` [PATCH 2/9] MTD: create lockless versions of {get,put}_mtd_device Maxim Levitsky
@ 2010-01-06 21:46 ` Maxim Levitsky
  2010-01-06 21:48 ` [PATCH 4/9] mtd-make mtdtrans thread suspend friendly Maxim Levitsky
                   ` (5 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Maxim Levitsky @ 2010-01-06 21:46 UTC (permalink / raw)
  To: linux-kernel; +Cc: joern, linux-mtd, Alex Dubov

>From ebc405ef4e2bcf14870e1b17fb3e356f1a20980c Mon Sep 17 00:00:00 2001
From: Maxim Levitsky <maximlevitsky@gmail.com>
Date: Tue, 5 Jan 2010 23:25:12 +0200
Subject: [PATCH 3/9] MTD: blkdevs: major cleanups.

* Make disk queue and thread per mtd device
* Handle the case of mtd device diappearence correctly

Signed-off-by: Maxim Levitsky <maximlevitsky@gmail.com>
---
 drivers/mtd/ftl.c            |    1 -
 drivers/mtd/inftlcore.c      |    1 -
 drivers/mtd/mtd_blkdevs.c    |  237 ++++++++++++++++++++++++++---------------
 drivers/mtd/mtdblock.c       |    1 -
 drivers/mtd/mtdblock_ro.c    |    1 -
 drivers/mtd/mtdcore.c        |    3 +-
 drivers/mtd/nftlcore.c       |    1 -
 drivers/mtd/rfd_ftl.c        |    1 -
 drivers/mtd/ssfdc.c          |    1 -
 include/linux/mtd/blktrans.h |   13 ++-
 10 files changed, 160 insertions(+), 100 deletions(-)

diff --git a/drivers/mtd/ftl.c b/drivers/mtd/ftl.c
index e56d6b4..62da9eb 100644
--- a/drivers/mtd/ftl.c
+++ b/drivers/mtd/ftl.c
@@ -1082,7 +1082,6 @@ static void ftl_remove_dev(struct mtd_blktrans_dev *dev)
 {
 	del_mtd_blktrans_dev(dev);
 	ftl_freepart((partition_t *)dev);
-	kfree(dev);
 }
 
 static struct mtd_blktrans_ops ftl_tr = {
diff --git a/drivers/mtd/inftlcore.c b/drivers/mtd/inftlcore.c
index 8aca552..015a7fe 100755
--- a/drivers/mtd/inftlcore.c
+++ b/drivers/mtd/inftlcore.c
@@ -139,7 +139,6 @@ static void inftl_remove_dev(struct mtd_blktrans_dev *dev)
 
 	kfree(inftl->PUtable);
 	kfree(inftl->VUtable);
-	kfree(inftl);
 }
 
 /*
diff --git a/drivers/mtd/mtd_blkdevs.c b/drivers/mtd/mtd_blkdevs.c
index 8ca17a3..6af4673 100644
--- a/drivers/mtd/mtd_blkdevs.c
+++ b/drivers/mtd/mtd_blkdevs.c
@@ -26,11 +26,6 @@
 
 static LIST_HEAD(blktrans_majors);
 
-struct mtd_blkcore_priv {
-	struct task_struct *thread;
-	struct request_queue *rq;
-	spinlock_t queue_lock;
-};
 
 static int do_blktrans_request(struct mtd_blktrans_ops *tr,
 			       struct mtd_blktrans_dev *dev,
@@ -78,8 +73,8 @@ static int do_blktrans_request(struct mtd_blktrans_ops *tr,
 
 static int mtd_blktrans_thread(void *arg)
 {
-	struct mtd_blktrans_ops *tr = arg;
-	struct request_queue *rq = tr->blkcore_priv->rq;
+	struct mtd_blktrans_dev *dev = arg;
+	struct request_queue *rq = dev->rq;
 	struct request *req = NULL;
 
 	/* we might get involved when memory gets low, so use PF_MEMALLOC */
@@ -88,7 +83,6 @@ static int mtd_blktrans_thread(void *arg)
 	spin_lock_irq(rq->queue_lock);
 
 	while (!kthread_should_stop()) {
-		struct mtd_blktrans_dev *dev;
 		int res;
 
 		if (!req && !(req = blk_fetch_request(rq))) {
@@ -99,13 +93,10 @@ static int mtd_blktrans_thread(void *arg)
 			continue;
 		}
 
-		dev = req->rq_disk->private_data;
-		tr = dev->tr;
-
 		spin_unlock_irq(rq->queue_lock);
 
 		mutex_lock(&dev->lock);
-		res = do_blktrans_request(tr, dev, req);
+		res = do_blktrans_request(dev->tr, dev, req);
 		mutex_unlock(&dev->lock);
 
 		spin_lock_irq(rq->queue_lock);
@@ -124,8 +115,14 @@ static int mtd_blktrans_thread(void *arg)
 
 static void mtd_blktrans_request(struct request_queue *rq)
 {
-	struct mtd_blktrans_ops *tr = rq->queuedata;
-	wake_up_process(tr->blkcore_priv->thread);
+	struct mtd_blktrans_dev *dev = rq->queuedata;
+	struct request *req = NULL;
+
+	if (dev->deleted)
+		while ((req = blk_fetch_request(rq)) != NULL)
+			__blk_end_request_all(req, -ENODEV);
+	else
+		wake_up_process(dev->thread);
 }
 

@@ -133,72 +130,108 @@ static int blktrans_open(struct block_device *bdev, fmode_t mode)
 {
 	struct mtd_blktrans_dev *dev = bdev->bd_disk->private_data;
 	struct mtd_blktrans_ops *tr = dev->tr;
-	int ret = -ENODEV;
+	int ret = 0;
+
+	mutex_lock(&dev->lock);
+	if (dev->open++)
+		goto out;
 
-	if (!get_mtd_device(NULL, dev->mtd->index))
+	if (dev->deleted)
 		goto out;
 
+	ret = -ENODEV;
 	if (!try_module_get(tr->owner))
-		goto out_tr;
+		goto out;
 
-	/* FIXME: Locking. A hot pluggable device can go away
-	   (del_mtd_device can be called for it) without its module
-	   being unloaded. */
-	dev->mtd->usecount++;
+	if (__get_mtd_device(dev->mtd)) {
+		module_put(tr->owner);
+		goto out;
+	}
 
 	ret = 0;
 	if (tr->open && (ret = tr->open(dev))) {
-		dev->mtd->usecount--;
-		put_mtd_device(dev->mtd);
-	out_tr:
 		module_put(tr->owner);
+		__put_mtd_device(dev->mtd);
+		goto out;
 	}
  out:
+	mutex_unlock(&dev->lock);
 	return ret;
 }
 
+
 static int blktrans_release(struct gendisk *disk, fmode_t mode)
 {
 	struct mtd_blktrans_dev *dev = disk->private_data;
 	struct mtd_blktrans_ops *tr = dev->tr;
 	int ret = 0;
 
-	if (tr->release)
-		ret = tr->release(dev);
+	mutex_lock(&dev->lock);
+	dev->open--;
+	if (dev->open)
+		goto out;
 
-	if (!ret) {
-		dev->mtd->usecount--;
-		put_mtd_device(dev->mtd);
+	/* Free the private data */
+	if (dev->deleted) {
 		module_put(tr->owner);
+		mutex_unlock(&dev->lock);
+		kfree(dev);
+		return 0;
 	}
 
+	ret = tr->release ? tr->release(dev) : 0;
+	module_put(tr->owner);
+
+	if(dev->mtd)
+		__put_mtd_device(dev->mtd);
+out:
+	mutex_unlock(&dev->lock);
 	return ret;
 }
 
 static int blktrans_getgeo(struct block_device *bdev, struct hd_geometry *geo)
 {
 	struct mtd_blktrans_dev *dev = bdev->bd_disk->private_data;
+	int error;
+
+	mutex_lock(&dev->lock);
 
+	error = -ENODEV;
+	if (dev->deleted)
+		goto out;
+
+	error = -ENOTTY;
 	if (dev->tr->getgeo)
-		return dev->tr->getgeo(dev, geo);
-	return -ENOTTY;
+		error = dev->tr->getgeo(dev, geo);
+out:
+	mutex_unlock(&dev->lock);
+	return error;
 }
 
 static int blktrans_ioctl(struct block_device *bdev, fmode_t mode,
 			      unsigned int cmd, unsigned long arg)
 {
 	struct mtd_blktrans_dev *dev = bdev->bd_disk->private_data;
-	struct mtd_blktrans_ops *tr = dev->tr;
+	int error = -ENODEV;
+
+	mutex_lock(&dev->lock);
+
+	if (dev->deleted)
+		goto out;
+
+	error = -ENOTTY;
 
 	switch (cmd) {
 	case BLKFLSBUF:
-		if (tr->flush)
-			return tr->flush(dev);
-		/* The core code did the work, we had nothing to do. */
-		return 0;
+		if (dev->tr->flush)
+			error = dev->tr->flush(dev);
+		break;
 	default:
-		return -ENOTTY;
+		break;
 	}
+out:
+	mutex_unlock(&dev->lock);
+	return error;
 }
 
 static const struct block_device_operations mtd_blktrans_ops = {
@@ -215,6 +248,7 @@ int add_mtd_blktrans_dev(struct mtd_blktrans_dev *new)
 	struct mtd_blktrans_dev *d;
 	int last_devnum = -1;
 	struct gendisk *gd;
+	int ret;
 
 	if (mutex_trylock(&mtd_table_mutex)) {
 		mutex_unlock(&mtd_table_mutex);
@@ -240,12 +274,13 @@ int add_mtd_blktrans_dev(struct mtd_blktrans_dev *new)
 		}
 		last_devnum = d->devnum;
 	}
+
+	ret=  -EBUSY;
 	if (new->devnum == -1)
 		new->devnum = last_devnum+1;
 
-	if ((new->devnum << tr->part_bits) > 256) {
-		return -EBUSY;
-	}
+	if ((new->devnum << tr->part_bits) > 256)
+		goto error1;
 
 	list_add_tail(&new->list, &tr->devs);
  added:
@@ -253,11 +288,16 @@ int add_mtd_blktrans_dev(struct mtd_blktrans_dev *new)
 	if (!tr->writesect)
 		new->readonly = 1;
 
+
+	/* Create gendisk */
+	ret = -ENOMEM;
 	gd = alloc_disk(1 << tr->part_bits);
-	if (!gd) {
-		list_del(&new->list);
-		return -ENOMEM;
-	}
+
+	if (!gd)
+		goto error2;
+
+	new->disk = gd;
+	gd->private_data = new;
 	gd->major = tr->major;
 	gd->first_minor = (new->devnum) << tr->part_bits;
 	gd->fops = &mtd_blktrans_ops;
@@ -275,25 +315,58 @@ int add_mtd_blktrans_dev(struct mtd_blktrans_dev *new)
 		snprintf(gd->disk_name, sizeof(gd->disk_name),
 			 "%s%d", tr->name, new->devnum);
 
-	/* 2.5 has capacity in units of 512 bytes while still
-	   having BLOCK_SIZE_BITS set to 10. Just to keep us amused. */
 	set_capacity(gd, (new->size * tr->blksize) >> 9);
 
-	gd->private_data = new;
-	new->blkcore_priv = gd;
-	gd->queue = tr->blkcore_priv->rq;
+
+	/* Create the request queue */
+	spin_lock_init(&new->queue_lock);
+	new->rq = blk_init_queue(mtd_blktrans_request, &new->queue_lock);
+
+	if (!new->rq)
+		goto error3;
+
+	new->rq->queuedata = new;
+	blk_queue_logical_block_size(new->rq, tr->blksize);
+
+	if (tr->discard)
+		queue_flag_set_unlocked(QUEUE_FLAG_DISCARD,
+					new->rq);
+
+	gd->queue = new->rq;
+
+	/* Create processing thread */
+	/* TODO: workqueue ? */
+	new->thread = kthread_run(mtd_blktrans_thread, new,
+			"%s%d", tr->name, new->mtd->index);
+	if (IS_ERR(new->thread)) {
+		ret = PTR_ERR(new->thread);
+		goto error4;
+	}
+
 	gd->driverfs_dev = &new->mtd->dev;
 
 	if (new->readonly)
 		set_disk_ro(gd, 1);
 
+	new->open = 0;
 	add_disk(gd);
 
 	return 0;
+error4:
+	blk_cleanup_queue(new->rq);
+error3:
+	put_disk(new->disk);
+error2:
+	list_del(&new->list);
+error1:
+	kfree(new);
+	return ret;
 }
 
 int del_mtd_blktrans_dev(struct mtd_blktrans_dev *old)
 {
+	unsigned long flags;
+
 	if (mutex_trylock(&mtd_table_mutex)) {
 		mutex_unlock(&mtd_table_mutex);
 		BUG();
@@ -301,9 +374,35 @@ int del_mtd_blktrans_dev(struct mtd_blktrans_dev *old)
 
 	list_del(&old->list);
 
-	del_gendisk(old->blkcore_priv);
-	put_disk(old->blkcore_priv);
+	/* stop new requests to arrive */
+	del_gendisk(old->disk);
+
+
+	/* flush current requests */
+	spin_lock_irqsave(&old->queue_lock, flags);
+	old->deleted = 1;
+	blk_start_queue(old->rq);
+	spin_unlock_irqrestore(&old->queue_lock, flags);
+
+
+	/* Stop the thread */
+	kthread_stop(old->thread);
 
+	/* Tell trans driver to release the device */
+	mutex_lock(&old->lock);
+
+	if (old->open) {
+		if (old->tr->release)
+			old->tr->release(old);
+		__put_mtd_device(old->mtd);
+	}
+
+	/* From now on, no calls into trans can be made */
+	/* Mtd device will be gone real soon now */
+	old->mtd = NULL;
+	mutex_unlock(&old->lock);
+
+	blk_cleanup_queue(old->rq);
 	return 0;
 }
 
@@ -344,9 +443,6 @@ int register_mtd_blktrans(struct mtd_blktrans_ops *tr)
 	if (!blktrans_notifier.list.next)
 		register_mtd_user(&blktrans_notifier);
 
-	tr->blkcore_priv = kzalloc(sizeof(*tr->blkcore_priv), GFP_KERNEL);
-	if (!tr->blkcore_priv)
-		return -ENOMEM;
 
 	mutex_lock(&mtd_table_mutex);
 
@@ -354,39 +450,12 @@ int register_mtd_blktrans(struct mtd_blktrans_ops *tr)
 	if (ret) {
 		printk(KERN_WARNING "Unable to register %s block device on major %d: %d\n",
 		       tr->name, tr->major, ret);
-		kfree(tr->blkcore_priv);
 		mutex_unlock(&mtd_table_mutex);
 		return ret;
 	}
-	spin_lock_init(&tr->blkcore_priv->queue_lock);
-
-	tr->blkcore_priv->rq = blk_init_queue(mtd_blktrans_request, &tr->blkcore_priv->queue_lock);
-	if (!tr->blkcore_priv->rq) {
-		unregister_blkdev(tr->major, tr->name);
-		kfree(tr->blkcore_priv);
-		mutex_unlock(&mtd_table_mutex);
-		return -ENOMEM;
-	}
-
-	tr->blkcore_priv->rq->queuedata = tr;
-	blk_queue_logical_block_size(tr->blkcore_priv->rq, tr->blksize);
-	if (tr->discard)
-		queue_flag_set_unlocked(QUEUE_FLAG_DISCARD,
-					tr->blkcore_priv->rq);
 
 	tr->blkshift = ffs(tr->blksize) - 1;
 
-	tr->blkcore_priv->thread = kthread_run(mtd_blktrans_thread, tr,
-			"%sd", tr->name);
-	if (IS_ERR(tr->blkcore_priv->thread)) {
-		int ret = PTR_ERR(tr->blkcore_priv->thread);
-		blk_cleanup_queue(tr->blkcore_priv->rq);
-		unregister_blkdev(tr->major, tr->name);
-		kfree(tr->blkcore_priv);
-		mutex_unlock(&mtd_table_mutex);
-		return ret;
-	}
-
 	INIT_LIST_HEAD(&tr->devs);
 	list_add(&tr->list, &blktrans_majors);
 
@@ -406,8 +475,6 @@ int deregister_mtd_blktrans(struct mtd_blktrans_ops *tr)
 
 	mutex_lock(&mtd_table_mutex);
 
-	/* Clean up the kernel thread */
-	kthread_stop(tr->blkcore_priv->thread);
 
 	/* Remove it from the list of active majors */
 	list_del(&tr->list);
@@ -415,13 +482,9 @@ int deregister_mtd_blktrans(struct mtd_blktrans_ops *tr)
 	list_for_each_entry_safe(dev, next, &tr->devs, list)
 		tr->remove_dev(dev);
 
-	blk_cleanup_queue(tr->blkcore_priv->rq);
 	unregister_blkdev(tr->major, tr->name);
-
 	mutex_unlock(&mtd_table_mutex);
 
-	kfree(tr->blkcore_priv);
-
 	BUG_ON(!list_empty(&tr->devs));
 	return 0;
 }
diff --git a/drivers/mtd/mtdblock.c b/drivers/mtd/mtdblock.c
index 9f41b1a..d8322cc 100644
--- a/drivers/mtd/mtdblock.c
+++ b/drivers/mtd/mtdblock.c
@@ -368,7 +368,6 @@ static void mtdblock_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
 static void mtdblock_remove_dev(struct mtd_blktrans_dev *dev)
 {
 	del_mtd_blktrans_dev(dev);
-	kfree(dev);
 }
 
 static struct mtd_blktrans_ops mtdblock_tr = {
diff --git a/drivers/mtd/mtdblock_ro.c b/drivers/mtd/mtdblock_ro.c
index 852165f..54ff288 100644
--- a/drivers/mtd/mtdblock_ro.c
+++ b/drivers/mtd/mtdblock_ro.c
@@ -49,7 +49,6 @@ static void mtdblock_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
 static void mtdblock_remove_dev(struct mtd_blktrans_dev *dev)
 {
 	del_mtd_blktrans_dev(dev);
-	kfree(dev);
 }
 
 static struct mtd_blktrans_ops mtdblock_tr = {
diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c
index e9daf87..8c9c020 100644
--- a/drivers/mtd/mtdcore.c
+++ b/drivers/mtd/mtdcore.c
@@ -532,7 +532,6 @@ void put_mtd_device(struct mtd_info *mtd)
 	__put_mtd_device(mtd);
 	mutex_unlock(&mtd_table_mutex);
 
-	module_put(mtd->owner);
 }
 
 void __put_mtd_device(struct mtd_info *mtd)
@@ -542,6 +541,8 @@ void __put_mtd_device(struct mtd_info *mtd)
 
 	if (mtd->put_device)
 		mtd->put_device(mtd);
+
+	module_put(mtd->owner);
 }
 
 /* default_mtd_writev - default mtd writev method for MTD devices that
diff --git a/drivers/mtd/nftlcore.c b/drivers/mtd/nftlcore.c
index 1002e18..a4578bf 100644
--- a/drivers/mtd/nftlcore.c
+++ b/drivers/mtd/nftlcore.c
@@ -126,7 +126,6 @@ static void nftl_remove_dev(struct mtd_blktrans_dev *dev)
 	del_mtd_blktrans_dev(dev);
 	kfree(nftl->ReplUnitTable);
 	kfree(nftl->EUNtable);
-	kfree(nftl);
 }
 
 /*
diff --git a/drivers/mtd/rfd_ftl.c b/drivers/mtd/rfd_ftl.c
index d2aa9c4..63b83c0 100644
--- a/drivers/mtd/rfd_ftl.c
+++ b/drivers/mtd/rfd_ftl.c
@@ -817,7 +817,6 @@ static void rfd_ftl_remove_dev(struct mtd_blktrans_dev *dev)
 	vfree(part->sector_map);
 	kfree(part->header_cache);
 	kfree(part->blocks);
-	kfree(part);
 }
 
 static struct mtd_blktrans_ops rfd_ftl_tr = {
diff --git a/drivers/mtd/ssfdc.c b/drivers/mtd/ssfdc.c
index 3f67e00..81c4ecd 100644
--- a/drivers/mtd/ssfdc.c
+++ b/drivers/mtd/ssfdc.c
@@ -375,7 +375,6 @@ static void ssfdcr_remove_dev(struct mtd_blktrans_dev *dev)
 
 	del_mtd_blktrans_dev(dev);
 	kfree(ssfdc->logic_block_map);
-	kfree(ssfdc);
 }
 
 static int ssfdcr_readsect(struct mtd_blktrans_dev *dev,
diff --git a/include/linux/mtd/blktrans.h b/include/linux/mtd/blktrans.h
index 8b4aa05..054b053 100644
--- a/include/linux/mtd/blktrans.h
+++ b/include/linux/mtd/blktrans.h
@@ -24,10 +24,15 @@ struct mtd_blktrans_dev {
 	int devnum;
 	unsigned long size;
 	int readonly;
-	void *blkcore_priv; /* gendisk in 2.5, devfs_handle in 2.4 */
-};
+	int deleted;
+	int open;
 
-struct blkcore_priv; /* Differs for 2.4 and 2.5 kernels; private */
+	struct gendisk *disk;
+	struct task_struct *thread;
+	struct request_queue *rq;
+	spinlock_t queue_lock;
+	void *priv;
+};
 
 struct mtd_blktrans_ops {
 	char *name;
@@ -60,8 +65,6 @@ struct mtd_blktrans_ops {
 	struct list_head devs;
 	struct list_head list;
 	struct module *owner;
-
-	struct mtd_blkcore_priv *blkcore_priv;
 };
 
 extern int register_mtd_blktrans(struct mtd_blktrans_ops *tr);
-- 
1.6.3.3

^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 4/9] mtd-make mtdtrans thread suspend friendly
  2010-01-06 21:43 RFC: [PATCH 0/9 Integration of SmartMedia/xD into mtd subsystem Maxim Levitsky
                   ` (2 preceding siblings ...)
  2010-01-06 21:46 ` [PATCH 3/9] MTD: blkdevs: major cleanups Maxim Levitsky
@ 2010-01-06 21:48 ` Maxim Levitsky
  2010-01-06 21:49 ` [PATCH 5/9] NAND: export nand_do_read_oob and nand_do_write_oob Maxim Levitsky
                   ` (4 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Maxim Levitsky @ 2010-01-06 21:48 UTC (permalink / raw)
  To: linux-kernel; +Cc: joern, linux-mtd, Alex Dubov

>From 23892d5e002d63b7980742fa5445443547fcf8cf Mon Sep 17 00:00:00 2001
From: Maxim Levitsky <maximlevitsky@gmail.com>
Date: Tue, 5 Jan 2010 23:25:19 +0200
Subject: [PATCH 4/9] mtd-make mtdtrans thread suspend friendly

Signed-of-by: Maxim Levitsky <maximlevitsky@gmail.com>
---
 drivers/mtd/mtd_blkdevs.c |   15 +++++++--------
 1 files changed, 7 insertions(+), 8 deletions(-)

diff --git a/drivers/mtd/mtd_blkdevs.c b/drivers/mtd/mtd_blkdevs.c
index 6af4673..7070d35 100644
--- a/drivers/mtd/mtd_blkdevs.c
+++ b/drivers/mtd/mtd_blkdevs.c
@@ -20,6 +20,7 @@
 #include <linux/init.h>
 #include <linux/mutex.h>
 #include <linux/kthread.h>
+#include <linux/freezer.h>
 #include <asm/uaccess.h>
 
 #include "mtdcore.h"
@@ -79,37 +80,35 @@ static int mtd_blktrans_thread(void *arg)
 
 	/* we might get involved when memory gets low, so use PF_MEMALLOC */
 	current->flags |= PF_MEMALLOC;
-
-	spin_lock_irq(rq->queue_lock);
+	set_freezable();
 
 	while (!kthread_should_stop()) {
 		int res;
 
+		try_to_freeze();
+
+		spin_lock_irq(rq->queue_lock);
 		if (!req && !(req = blk_fetch_request(rq))) {
 			set_current_state(TASK_INTERRUPTIBLE);
 			spin_unlock_irq(rq->queue_lock);
 			schedule();
-			spin_lock_irq(rq->queue_lock);
 			continue;
 		}
-
 		spin_unlock_irq(rq->queue_lock);
 
+
 		mutex_lock(&dev->lock);
 		res = do_blktrans_request(dev->tr, dev, req);
 		mutex_unlock(&dev->lock);
 
 		spin_lock_irq(rq->queue_lock);
-
 		if (!__blk_end_request_cur(req, res))
 			req = NULL;
+		spin_unlock_irq(rq->queue_lock);
 	}
 
 	if (req)
 		__blk_end_request_all(req, -EIO);
-
-	spin_unlock_irq(rq->queue_lock);
-
 	return 0;
 }
 
-- 
1.6.3.3

^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 5/9] NAND: export nand_do_read_oob and nand_do_write_oob
  2010-01-06 21:43 RFC: [PATCH 0/9 Integration of SmartMedia/xD into mtd subsystem Maxim Levitsky
                   ` (3 preceding siblings ...)
  2010-01-06 21:48 ` [PATCH 4/9] mtd-make mtdtrans thread suspend friendly Maxim Levitsky
@ 2010-01-06 21:49 ` Maxim Levitsky
  2010-01-06 21:51 ` [PATCH 6/9] mtd: common module for smartmedia/xD support Maxim Levitsky
                   ` (3 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Maxim Levitsky @ 2010-01-06 21:49 UTC (permalink / raw)
  To: linux-kernel; +Cc: joern, linux-mtd, Alex Dubov

>From 42a44a48f270f45e44595b6f3906df4a6cf28ba5 Mon Sep 17 00:00:00 2001
From: Maxim Levitsky <maximlevitsky@gmail.com>
Date: Tue, 5 Jan 2010 23:25:20 +0200
Subject: [PATCH 5/9] NAND: export nand_do_read_oob and nand_do_write_oob

Drivers might need sane way to read/write oob area, but can't
use mtd interface due to locking

Signed-off-by: Maxim Levitsky <maximlevitsky@gmail.com>
---
 drivers/mtd/nand/nand_base.c |   10 +++++-----
 include/linux/mtd/nand.h     |    7 +++++++
 2 files changed, 12 insertions(+), 5 deletions(-)

diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c
index 2957cc7..787e751 100644
--- a/drivers/mtd/nand/nand_base.c
+++ b/drivers/mtd/nand/nand_base.c
@@ -99,9 +99,6 @@ static struct nand_ecclayout nand_oob_128 = {
 static int nand_get_device(struct nand_chip *chip, struct mtd_info *mtd,
 			   int new_state);
 
-static int nand_do_write_oob(struct mtd_info *mtd, loff_t to,
-			     struct mtd_oob_ops *ops);
-
 /*
  * For devices which display every fart in the system on a separate LED. Is
  * compiled away when LED support is disabled.
@@ -1458,7 +1455,7 @@ static int nand_write_oob_syndrome(struct mtd_info *mtd,
  *
  * NAND read out-of-band data from the spare area
  */
-static int nand_do_read_oob(struct mtd_info *mtd, loff_t from,
+int nand_do_read_oob(struct mtd_info *mtd, loff_t from,
 			    struct mtd_oob_ops *ops)
 {
 	int page, realpage, chipnr, sndcmd = 1;
@@ -1993,7 +1990,7 @@ static int nand_write(struct mtd_info *mtd, loff_t to, size_t len,
  *
  * NAND write out-of-band
  */
-static int nand_do_write_oob(struct mtd_info *mtd, loff_t to,
+int nand_do_write_oob(struct mtd_info *mtd, loff_t to,
 			     struct mtd_oob_ops *ops)
 {
 	int chipnr, page, status, len;
@@ -2962,6 +2959,9 @@ EXPORT_SYMBOL_GPL(nand_scan);
 EXPORT_SYMBOL_GPL(nand_scan_ident);
 EXPORT_SYMBOL_GPL(nand_scan_tail);
 EXPORT_SYMBOL_GPL(nand_release);
+EXPORT_SYMBOL_GPL(nand_do_read_oob);
+EXPORT_SYMBOL_GPL(nand_do_write_oob);
+
 
 static int __init nand_base_init(void)
 {
diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h
index 7a232a9..7c3ff57 100644
--- a/include/linux/mtd/nand.h
+++ b/include/linux/mtd/nand.h
@@ -548,6 +548,13 @@ extern int nand_erase_nand(struct mtd_info *mtd, struct erase_info *instr,
 extern int nand_do_read(struct mtd_info *mtd, loff_t from, size_t len,
 			size_t * retlen, uint8_t * buf);
 
+extern int nand_do_read_oob(struct mtd_info *mtd, loff_t from,
+			    struct mtd_oob_ops *ops);
+
+extern int nand_do_write_oob(struct mtd_info *mtd, loff_t to,
+			     struct mtd_oob_ops *ops);
+
+
 /*
 * Constants for oob configuration
 */
-- 
1.6.3.3

^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 6/9] mtd: common module for smartmedia/xD support
  2010-01-06 21:43 RFC: [PATCH 0/9 Integration of SmartMedia/xD into mtd subsystem Maxim Levitsky
                   ` (4 preceding siblings ...)
  2010-01-06 21:49 ` [PATCH 5/9] NAND: export nand_do_read_oob and nand_do_write_oob Maxim Levitsky
@ 2010-01-06 21:51 ` Maxim Levitsky
  2010-01-06 21:52 ` [PATCH 7/9] NAND: add few workarounds for SmartMedia/xD chips Maxim Levitsky
                   ` (2 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Maxim Levitsky @ 2010-01-06 21:51 UTC (permalink / raw)
  To: linux-kernel; +Cc: joern, linux-mtd, Alex Dubov

>From 06cb6f7fa6d9c708ca0a208b33c6e3b5969aab44 Mon Sep 17 00:00:00 2001
From: Maxim Levitsky <maximlevitsky@gmail.com>
Date: Wed, 6 Jan 2010 22:52:56 +0200
Subject: [PATCH 6/9] mtd: common module for smartmedia/xD support

---
 drivers/mtd/Kconfig     |    9 ++
 drivers/mtd/Makefile    |    1 +
 drivers/mtd/sm_common.c |  208 +++++++++++++++++++++++++++++++++++++++++++++++
 drivers/mtd/sm_common.h |   31 +++++++
 4 files changed, 249 insertions(+), 0 deletions(-)
 create mode 100644 drivers/mtd/sm_common.c
 create mode 100644 drivers/mtd/sm_common.h

diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig
index ecf90f5..ebeabd6 100644
--- a/drivers/mtd/Kconfig
+++ b/drivers/mtd/Kconfig
@@ -25,6 +25,15 @@ config MTD_DEBUG_VERBOSE
 	help
 	  Determines the verbosity level of the MTD debugging messages.
 
+config MTD_NAND_SMARTMEDIA
+	boolean
+
+config MTD_SM_COMMON
+	depends on MTD_NAND
+	select MTD_NAND_SMARTMEDIA
+	tristate
+	default n
+
 config MTD_TESTS
 	tristate "MTD tests support"
 	depends on m
diff --git a/drivers/mtd/Makefile b/drivers/mtd/Makefile
index 82d1e4d..02c5b17 100644
--- a/drivers/mtd/Makefile
+++ b/drivers/mtd/Makefile
@@ -7,6 +7,7 @@ obj-$(CONFIG_MTD)		+= mtd.o
 mtd-y				:= mtdcore.o mtdsuper.o mtdbdi.o
 mtd-$(CONFIG_MTD_PARTITIONS)	+= mtdpart.o
 
+obj-$(CONFIG_MTD_SM_COMMON) 	+= sm_common.o
 obj-$(CONFIG_MTD_CONCAT)	+= mtdconcat.o
 obj-$(CONFIG_MTD_REDBOOT_PARTS) += redboot.o
 obj-$(CONFIG_MTD_CMDLINE_PARTS) += cmdlinepart.o
diff --git a/drivers/mtd/sm_common.c b/drivers/mtd/sm_common.c
new file mode 100644
index 0000000..3d57f40
--- /dev/null
+++ b/drivers/mtd/sm_common.c
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2009 - Maxim Levitsky
+ * Common routines & support for xD format
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/bitops.h>
+#include <linux/log2.h>
+#include "sm_common.h"
+
+#if 0
+static struct nand_ecclayout nand_oob_sm = {
+	.eccbytes = 6,
+	.eccpos = {8, 9, 10, 13, 14, 15},
+	.oobfree = {
+		{.offset = 0 , .length = 4}, /* reserved */
+		{.offset = 4 , .length = 2}, /* data & block status */
+		{.offset = 6 , .length = 2}, /* LBA1 */
+		{.offset = 11, .length = 2}  /* LBA2 */
+	}
+};
+#endif
+
+static struct nand_ecclayout nand_oob_sm = {
+	.eccbytes = 0,
+	.oobfree = {
+		{.offset = 0 , .length = 16}, /* reserved */
+	}
+};
+
+static int sm_get_lba(u8 *lba)
+{
+	/* check fixed bits */
+	if ((lba[0] & 0xF8) != 0x10)
+		return -2;
+
+	/* check parity - endianess doesn't matter */
+	if (hweight16(*(u16*)lba) & 1)
+		return -2;
+
+	return (lba[1] >> 1) | ((lba[0] & 0x07) << 7);
+}
+
+
+/*
+ * Read LBA asscociated with block
+ * returns -1, if block is erased
+ * returns -2 if error happens
+ */
+int sm_read_lba(struct sm_oob *oob)
+{
+	static const u32 erased_pattern[4] =
+		{ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF };
+
+	u16 lba_test;
+	int lba;
+	//int i;
+
+	/* First test for erased block */
+	if (!memcmp(oob, erased_pattern, sizeof(erased_pattern)))
+		return -1;
+
+	/* Now check is both copies of the LBA differ too much */
+	lba_test = *(u16*)oob->lba_copy1 ^ *(u16*)oob->lba_copy2;
+	if (lba_test && !is_power_of_2(lba_test))
+		return -2;
+
+
+	/* And read it */
+	if ((lba = sm_get_lba(oob->lba_copy1)) == -2)
+		lba = sm_get_lba(oob->lba_copy2);
+
+	return lba;
+}
+EXPORT_SYMBOL_GPL(sm_read_lba);
+
+void sm_write_lba(struct sm_oob *oob, u16 lba)
+{
+	u8 tmp[2];
+
+	WARN_ON(lba > 1000);
+
+	tmp[0] = 0x10 | ((lba >> 7) & 0x07);
+	tmp[1] = (lba << 1) & 0xFF;
+
+	if(hweight16(*(u16*)tmp) & 0x01)
+		tmp[1] |= 1;
+
+	oob->lba_copy1[0] = oob->lba_copy2[0] = tmp[0];
+	oob->lba_copy1[1] = oob->lba_copy2[1] = tmp[1];
+}
+EXPORT_SYMBOL_GPL(sm_write_lba);
+
+/* Test if whole block is valid, or at least is marked as such...*/
+int sm_block_valid(struct sm_oob *oob)
+{
+	/* test block status */
+	if (hweight16(oob->block_status) < 7)
+		return -EIO;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(sm_block_valid);
+
+
+/* Test if sector is valid */
+int sm_sector_valid(struct sm_oob *oob)
+{
+	/* test data status */
+	if (hweight16(oob->data_status) < 5)
+		return -EIO;
+	return 0;
+}
+EXPORT_SYMBOL_GPL(sm_sector_valid);
+
+
+
+/* Nand interface helpers */
+static int sm_block_bad(struct mtd_info *mtd, loff_t ofs, int getchip)
+{
+	struct nand_chip *chip = (struct nand_chip *)mtd->priv;
+	struct mtd_oob_ops ops;
+	struct sm_oob oob;
+	int ret;
+
+	ops.mode = MTD_OOB_RAW;
+	ops.ooboffs = 0;
+	ops.ooblen = SM_OOB_SIZE;
+	ops.oobbuf = (void*)&oob;
+	ops.datbuf = NULL;
+
+	/* TODO: This doesn't take controller lock,
+		but we know that all xD cards are single chip. */
+	if (getchip)
+		chip->select_chip(mtd, 0);
+
+	ret = nand_do_read_oob(mtd, ofs, &ops);
+	if (ret < 0 || ops.oobretlen != SM_OOB_SIZE)
+		goto out;
+
+	ret = sm_block_valid(&oob) ? 1 : 0;
+out:
+	if (getchip)
+		chip->select_chip(mtd, -1);
+
+	if (ret)
+		printk(KERN_NOTICE "sm_common: bad sector at %i\n", (int)ofs);
+	return ret;
+}
+
+static int sm_block_markbad(struct mtd_info *mtd, loff_t ofs)
+{
+	struct mtd_oob_ops ops;
+	struct sm_oob oob;
+	int ret;
+
+	memset (&oob, -1, sizeof(oob));
+	oob.block_status = 0xF0;
+
+	ops.mode = MTD_OOB_RAW;
+	ops.ooboffs = 0;
+	ops.ooblen = SM_OOB_SIZE;
+	ops.oobbuf = (void*)&oob;
+	ops.datbuf = NULL;
+
+	ret = nand_do_write_oob(mtd, ofs, &ops);
+	if (ret < 0 || ops.oobretlen != SM_OOB_SIZE) {
+		printk(KERN_NOTICE "sm_common: can't mark sector at %i as bad\n",
+					(int)ofs);
+		return 1;
+	}
+
+	return 0;
+}
+
+int sm_register_device(struct mtd_info *mtd)
+{
+	struct nand_chip *chip = (struct nand_chip *)mtd->priv;
+
+	chip->options |= NAND_SKIP_BBTSCAN | NAND_NO_AUTOINCR;
+
+	chip->block_bad = sm_block_bad;
+	chip->block_markbad = sm_block_markbad;
+	chip->ecc.layout = &nand_oob_sm;
+
+	if (nand_scan(mtd, 1))
+		return -ENODEV;
+
+	/* TODO: support small page devices */
+	if (mtd->writesize != 512)
+		return -ENODEV;
+
+
+	if (add_mtd_device(mtd))
+		return -ENODEV;
+
+	return 0;
+}
+
+EXPORT_SYMBOL_GPL(sm_register_device);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Maxim Levitsky <maximlevitsky@gmail.com>");
+MODULE_DESCRIPTION("Common SmartMedia/xD functions");
\ No newline at end of file
diff --git a/drivers/mtd/sm_common.h b/drivers/mtd/sm_common.h
new file mode 100644
index 0000000..d546770
--- /dev/null
+++ b/drivers/mtd/sm_common.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2009 - Maxim Levitsky
+ * Common routines & support for SmartMedia/xD format
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/mtd/nand.h>
+
+struct sm_oob {
+	u32 reserved;
+	u8  data_status;
+	u8  block_status;
+	u8 lba_copy1[2];
+	u8  ecc2[3];
+	u8 lba_copy2[2];
+	u8  ecc1[3];
+} __attribute__((packed));
+
+
+#define SM_SECTOR_SIZE	512
+#define SM_OOB_SIZE	16
+#define SM_SMALL_PAGE 256
+
+extern int sm_register_device(struct mtd_info *mtd);
+extern int sm_read_lba(struct sm_oob *oob);
+extern void sm_write_lba(struct sm_oob *oob, u16 lba);
+extern int sm_block_valid(struct sm_oob *oob);
+extern int sm_sector_valid(struct sm_oob *oob);
\ No newline at end of file
-- 
1.6.3.3

^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 7/9] NAND: add few workarounds for SmartMedia/xD chips.
  2010-01-06 21:43 RFC: [PATCH 0/9 Integration of SmartMedia/xD into mtd subsystem Maxim Levitsky
                   ` (5 preceding siblings ...)
  2010-01-06 21:51 ` [PATCH 6/9] mtd: common module for smartmedia/xD support Maxim Levitsky
@ 2010-01-06 21:52 ` Maxim Levitsky
  2010-01-06 21:53 ` [PATCH 8/9] mtd: SmartMedia/xD FTL Maxim Levitsky
  2010-01-06 21:54 ` [PATCH 9/9] mtd: add nand driver for ricoh xD/SmartMedia reader Maxim Levitsky
  8 siblings, 0 replies; 10+ messages in thread
From: Maxim Levitsky @ 2010-01-06 21:52 UTC (permalink / raw)
  To: linux-kernel; +Cc: joern, linux-mtd, Alex Dubov

>From 3385017902902c80ce2110e7c5ae02abf296b8ab Mon Sep 17 00:00:00 2001
From: Maxim Levitsky <maximlevitsky@gmail.com>
Date: Wed, 6 Jan 2010 22:53:00 +0200
Subject: [PATCH 7/9] NAND: add few workarounds for SmartMedia/xD chips.

* Add seperate ID table
* Add support for mask rom devices
* Workaround for write protect status

Signed-off-by: Maxim Levitsky <maximlevitsky@gmail.com>
---
 drivers/mtd/nand/nand_base.c |   29 +++++++++++++++++++++--------
 drivers/mtd/nand/nand_ids.c  |   39 +++++++++++++++++++++++++++++++++++++++
 drivers/mtd/sm_common.c      |    2 +-
 include/linux/mtd/nand.h     |   11 +++++++++++
 4 files changed, 72 insertions(+), 9 deletions(-)

diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c
index 787e751..887b779 100644
--- a/drivers/mtd/nand/nand_base.c
+++ b/drivers/mtd/nand/nand_base.c
@@ -398,9 +398,17 @@ static int nand_default_block_markbad(struct mtd_info *mtd, loff_t ofs)
 static int nand_check_wp(struct mtd_info *mtd)
 {
 	struct nand_chip *chip = mtd->priv;
+	int wp;
+
 	/* Check the WP bit */
 	chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
-	return (chip->read_byte(mtd) & NAND_STATUS_WP) ? 0 : 1;
+	wp = (chip->read_byte(mtd) & NAND_STATUS_WP) ? 0 : 1;
+
+	/* broken xD cards report WP despite beeing writable */
+	if (chip->options & NAND_BROKEN_XD)
+		return 0;
+
+	return wp;
 }
 
 /**
@@ -2505,14 +2513,18 @@ static struct nand_flash_dev *nand_get_flash_type(struct mtd_info *mtd,
 	}
 
 	/* Lookup the flash id */
-	for (i = 0; nand_flash_ids[i].name != NULL; i++) {
-		if (dev_id == nand_flash_ids[i].id) {
-			type =  &nand_flash_ids[i];
+#ifdef CONFIG_MTD_NAND_SMARTMEDIA
+	if (chip->options & NAND_SMARTMEDIA)
+		type = nand_smartmedia_flash_ids;
+	else
+#endif
+		type = nand_flash_ids;
+
+	for (i = 0; type->name != NULL; type++)
+		if (dev_id == type->id)
 			break;
-		}
-	}
 
-	if (!type)
+	if (!type->name)
 		return ERR_PTR(-ENODEV);
 
 	if (!mtd->name)
@@ -2868,7 +2880,8 @@ int nand_scan_tail(struct mtd_info *mtd)
 
 	/* Fill in remaining MTD driver data */
 	mtd->type = MTD_NANDFLASH;
-	mtd->flags = MTD_CAP_NANDFLASH;
+	mtd->flags = chip->options & NAND_ROM ? MTD_CAP_ROM:
+							MTD_CAP_NANDFLASH;
 	mtd->erase = nand_erase;
 	mtd->point = NULL;
 	mtd->unpoint = NULL;
diff --git a/drivers/mtd/nand/nand_ids.c b/drivers/mtd/nand/nand_ids.c
index 69ee2c9..a24682d 100644
--- a/drivers/mtd/nand/nand_ids.c
+++ b/drivers/mtd/nand/nand_ids.c
@@ -127,6 +127,45 @@ struct nand_flash_dev nand_flash_ids[] = {
 	{NULL,}
 };
 
+#ifdef CONFIG_MTD_NAND_SMARTMEDIA
+struct nand_flash_dev nand_smartmedia_flash_ids[] = {
+
+	/* SmartMedia */
+	{"SmartMedia 1MiB 5V",		0x6e, 256, 1, 0x1000, 0},
+	{"SmartMedia 1MiB 3,3V",	0xe8, 256, 1, 0x1000, 0},
+	{"SmartMedia 1MiB 3,3V",	0xec, 256, 1, 0x1000, 0},
+	{"SmartMedia 2MiB 3,3V",	0xea, 256, 2, 0x1000, 0},
+	{"SmartMedia 2MiB 5V",		0x64, 256, 2, 0x1000, 0},
+	{"SmartMedia 2MiB 3,3V ROM",	0x5d, 512, 2, 0x2000, NAND_ROM},
+	{"SmartMedia 4MiB 3,3V",	0xe3, 512, 4, 0x2000, 0},
+	{"SmartMedia 4MiB 3,3/5V",	0xe5, 512, 4, 0x2000, 0},
+	{"SmartMedia 4MiB 5V",		0x6b, 512, 4, 0x2000, 0},
+	{"SmartMedia 4MiB 3,3V ROM",	0xd5, 512, 4, 0x2000, NAND_ROM},
+	{"SmartMedia 8MiB 3,3V",	0xe6, 512, 8, 0x2000, 0},
+	{"SmartMedia 8MiB 3,3V ROM",	0xd6, 512, 8, 0x2000, NAND_ROM},
+
+	/* xD / SmartMedia */
+	{"SmartMedia/xD 16MiB 3,3V",	0x73, 512, 16, 0x4000, 0},
+	{"SmartMedia 16MiB 3,3V ROM",	0x57, 512, 16, 0x4000, NAND_ROM},
+	{"SmartMedia/xD 32MiB 3,3V",	0x75, 512, 32, 0x4000, 0},
+	{"SmartMedia 32MiB 3,3V ROM",	0x58, 512, 32, 0x4000, NAND_ROM},
+	{"SmartMedia/xD 64MiB 3,3V",	0x76, 512, 64, 0x4000, 0},
+	{"SmartMedia 64MiB 3,3V ROM",	0xd9, 512, 64, 0x4000, NAND_ROM},
+	{"SmartMedia/xD 128MiB 3,3V",	0x79, 512, 128, 0x4000, 0},
+	{"SmartMedia 128MiB 3,3V ROM",	0xda, 512, 128, 0x4000, NAND_ROM},
+	{"SmartMedia/xD 256MiB 3,3V",	0x71, 512, 256, 0x4000, NAND_BROKEN_XD},
+	{"SmartMedia 256MiB 3,3V ROM",	0x5b, 512, 256, 0x4000, NAND_ROM},
+
+	/* xD only */
+	{"xD 512MiB 3,3V",		0xDC, 512, 512, 0x4000, NAND_BROKEN_XD},
+	{"xD 1GiB 3,3V",		0xD3, 512, 1024, 0x4000,NAND_BROKEN_XD},
+	{"xD 2GiB 3,3V",		0xD5, 512, 2048, 0x4000,NAND_BROKEN_XD},
+
+	{NULL,}
+};
+EXPORT_SYMBOL(nand_smartmedia_flash_ids);
+#endif
+
 /*
 *	Manufacturer ID list
 */
diff --git a/drivers/mtd/sm_common.c b/drivers/mtd/sm_common.c
index 3d57f40..022d8a0 100644
--- a/drivers/mtd/sm_common.c
+++ b/drivers/mtd/sm_common.c
@@ -181,7 +181,7 @@ int sm_register_device(struct mtd_info *mtd)
 {
 	struct nand_chip *chip = (struct nand_chip *)mtd->priv;
 
-	chip->options |= NAND_SKIP_BBTSCAN | NAND_NO_AUTOINCR;
+	chip->options |= NAND_SKIP_BBTSCAN | NAND_SMARTMEDIA | NAND_NO_AUTOINCR;
 
 	chip->block_bad = sm_block_bad;
 	chip->block_markbad = sm_block_markbad;
diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h
index 7c3ff57..13d8344 100644
--- a/include/linux/mtd/nand.h
+++ b/include/linux/mtd/nand.h
@@ -168,6 +168,11 @@ typedef enum {
 /* Chip does not allow subpage writes */
 #define NAND_NO_SUBPAGE_WRITE	0x00000200
 
+/* Device is one of 'new' xD cards that expose fake nand command set */
+#define NAND_BROKEN_XD		0x00000400
+
+/* Device behaves just like nand, but is readonly */
+#define NAND_ROM		0x00000800
 
 /* Options valid for Samsung large page devices */
 #define NAND_SAMSUNG_LP_OPTIONS \
@@ -194,6 +199,11 @@ typedef enum {
 /* This option is defined if the board driver allocates its own buffers
    (e.g. because it needs them DMA-coherent */
 #define NAND_OWN_BUFFERS	0x00040000
+
+/* controller supports only xD/SmartMedia cards*/
+#define NAND_SMARTMEDIA		0x00080000
+
+
 /* Options set by nand scan */
 /* Nand scan has allocated controller struct */
 #define NAND_CONTROLLER_ALLOC	0x80000000
@@ -468,6 +478,7 @@ struct nand_manufacturers {
 };
 
 extern struct nand_flash_dev nand_flash_ids[];
+extern struct nand_flash_dev nand_smartmedia_flash_ids[];
 extern struct nand_manufacturers nand_manuf_ids[];
 
 /**
-- 
1.6.3.3

^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 8/9] mtd: SmartMedia/xD FTL
  2010-01-06 21:43 RFC: [PATCH 0/9 Integration of SmartMedia/xD into mtd subsystem Maxim Levitsky
                   ` (6 preceding siblings ...)
  2010-01-06 21:52 ` [PATCH 7/9] NAND: add few workarounds for SmartMedia/xD chips Maxim Levitsky
@ 2010-01-06 21:53 ` Maxim Levitsky
  2010-01-06 21:54 ` [PATCH 9/9] mtd: add nand driver for ricoh xD/SmartMedia reader Maxim Levitsky
  8 siblings, 0 replies; 10+ messages in thread
From: Maxim Levitsky @ 2010-01-06 21:53 UTC (permalink / raw)
  To: linux-kernel; +Cc: joern, linux-mtd, Alex Dubov

>From d9e0e1a8cb54011ed2775aa82e71e1a5ba3b5880 Mon Sep 17 00:00:00 2001
From: Maxim Levitsky <maximlevitsky@gmail.com>
Date: Wed, 6 Jan 2010 23:06:24 +0200
Subject: [PATCH 8/9] mtd: SmartMedia/xD FTL

---
 drivers/mtd/Kconfig  |   12 +
 drivers/mtd/Makefile |    1 +
 drivers/mtd/sm_ftl.c | 1070 ++++++++++++++++++++++++++++++++++++++++++++++++++
 drivers/mtd/sm_ftl.h |   79 ++++
 4 files changed, 1162 insertions(+), 0 deletions(-)
 create mode 100644 drivers/mtd/sm_ftl.c
 create mode 100644 drivers/mtd/sm_ftl.h

diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig
index ebeabd6..e13bf41 100644
--- a/drivers/mtd/Kconfig
+++ b/drivers/mtd/Kconfig
@@ -313,6 +313,18 @@ config SSFDC
 	  This enables read only access to SmartMedia formatted NAND
 	  flash. You can mount it with FAT file system.
 
+
+config SM_FTL
+	tristate "SmartMedia/xD new translation layer"
+	depends on EXPERIMENTAL
+	select MTD_SM_COMMON
+	help
+	  This enables new and very EXPERMENTAL support for SmartMedia/xD
+	  FTL (Flash tanslation layer)
+	  Write support isn't yet well tested, therefore this code IS likely to
+	  eat your card, so please don't use it together with valuable data.
+	  Use readonly driver (CONFIG_SSFDC) instead.
+
 config MTD_OOPS
 	tristate "Log panic/oops to an MTD buffer"
 	depends on MTD
diff --git a/drivers/mtd/Makefile b/drivers/mtd/Makefile
index 02c5b17..02f6375 100644
--- a/drivers/mtd/Makefile
+++ b/drivers/mtd/Makefile
@@ -25,6 +25,7 @@ obj-$(CONFIG_NFTL)		+= nftl.o
 obj-$(CONFIG_INFTL)		+= inftl.o
 obj-$(CONFIG_RFD_FTL)		+= rfd_ftl.o
 obj-$(CONFIG_SSFDC)		+= ssfdc.o
+obj-$(CONFIG_SM_FTL)		+= sm_ftl.o
 obj-$(CONFIG_MTD_OOPS)		+= mtdoops.o
 
 nftl-objs		:= nftlcore.o nftlmount.o
diff --git a/drivers/mtd/sm_ftl.c b/drivers/mtd/sm_ftl.c
new file mode 100644
index 0000000..41b52ac
--- /dev/null
+++ b/drivers/mtd/sm_ftl.c
@@ -0,0 +1,1070 @@
+/*
+ * Copyright (C) 2009 - Maxim Levitsky
+ * SmartMedia/xD translation layer
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/random.h>
+#include <linux/hdreg.h>
+#include <linux/kthread.h>
+#include <linux/freezer.h>
+#include <asm/bitops.h>
+#include "sm_common.h"
+#include "sm_ftl.h"
+
+static u8 tmp_buffer[SM_SECTOR_SIZE];
+static int cache_size = 5;
+
+module_param(cache_size, int, S_IRUGO);
+MODULE_PARM_DESC(cache_size,
+		"Number of blocks to hold in the cache (5 default)");
+
+
+static void sm_erase_callback (struct erase_info *self);
+static void sm_erase_block(struct sm_ftl *ftl, int zone_num, s16 block,
+								int put_free);
+static void sm_mark_block_bad(struct sm_ftl *ftl, int zone_num, int block);
+static int sm_cache_flush_thread(void *data);
+
+
+static const struct chs_entry chs_table[] = {
+	{ 1,    125,  4,  4  },
+	{ 2,    125,  4,  8  },
+	{ 4,    250,  4,  8  },
+	{ 8,    250,  4,  16 },
+	{ 16,   500,  4,  16 },
+	{ 32,   500,  8,  16 },
+	{ 64,   500,  8,  32 },
+	{ 128,  500,  16, 32 },
+	{ 256,  1000, 16, 32 },
+	{ 512,  1015, 32, 63 },
+	{ 1024, 985,  33, 63 },
+	{ 2048, 985,  33, 63 },
+	{ 0 },
+};
+
+/* Find out media parameters.
+ * This ideally has to be based on nand id, but for now device size is enough */
+int sm_get_media_info (struct sm_ftl *ftl, struct mtd_info *mtd)
+{
+	int i;
+	int size_in_megs = mtd->size / (1024 * 1024);
+	ftl->readonly = mtd->type == MTD_ROM;
+
+	/* Manual settings for very old devices */
+	ftl->zone_count = 1;
+	ftl->smallpagenand = 0;
+
+	switch (size_in_megs){
+	case 1:
+		/* 1 Mb flas/rom SmartMedia card (256 byte pages)*/
+		ftl->zone_size = 256;
+		ftl->max_lba = 250;
+		ftl->block_size = 8 * SM_SECTOR_SIZE;
+		ftl->smallpagenand = 1;
+
+		break;
+	case 2:
+		/* 2 Mb flash SmartMedia (256 byte pages)*/
+		if (!mtd->writesize == SM_SMALL_PAGE) {
+			ftl->zone_size = 512;
+			ftl->max_lba = 500;
+			ftl->block_size = 8 * SM_SECTOR_SIZE;
+			ftl->smallpagenand = 1;
+		/* 2 Mb rom SmartMedia */
+		} else {
+			ftl->zone_size = 256;
+			ftl->max_lba = 250;
+			ftl->block_size = 16 * SM_SECTOR_SIZE;
+		}
+		break;
+	case 4:
+		/* 4 Mb flash/rom SmartMedia device */
+		ftl->zone_size = 512;
+		ftl->max_lba = 500;
+		ftl->block_size = 16 * SM_SECTOR_SIZE;
+		break;
+	case 8:
+		/* 8 Mb flash/rom SmartMedia device */
+		ftl->zone_size = 1024;
+		ftl->max_lba = 1000;
+		ftl->block_size = 16 * SM_SECTOR_SIZE;
+	}
+
+	/* Minimum xD size is 16M, and thus all xD cards have standard zone
+	   sizes. SmartMedia cards exist up to 128 Mb and have same layout*/
+	if (size_in_megs >= 16) {
+		ftl->zone_count = size_in_megs / 16;
+		ftl->zone_size = 1024;
+		ftl->max_lba = 1000;
+		ftl->block_size = 32 * SM_SECTOR_SIZE;
+	}
+
+	/* Test for proper write and erase sizes */
+	if (mtd->erasesize > ftl->block_size)
+		return -ENODEV;
+
+	if (mtd->writesize > SM_SECTOR_SIZE)
+		return -ENODEV;
+
+	/* For now, don't support small page nand */
+	if (ftl->smallpagenand)
+		return -ENODEV;
+
+	/* This shouldn't happen */
+	if (ftl->zone_count * ftl->zone_size * ftl->block_size != mtd->size)
+		return -ENODEV;
+
+	/* Find geometry information */
+	for (i = 0 ; i < ARRAY_SIZE(chs_table) ; i++)
+		 if (chs_table[i].size == size_in_megs) {
+			ftl->cylinders = chs_table[i].cyl;
+			ftl->heads = chs_table[i].head;
+			ftl->sectors = chs_table[i].sec;
+			return 0;
+		}
+
+	ftl->cylinders = 985;
+	ftl->heads =  33;
+	ftl->sectors = 63;
+	return 0;
+}
+
+/* Make offset from parts */
+static loff_t sm_mkoffset(struct sm_ftl *ftl, int zone, int block, int boffset)
+{
+	WARN_ON(boffset & (SM_SECTOR_SIZE - 1));
+	WARN_ON(zone < 0 || zone >= ftl->zone_count);
+	WARN_ON(block >= ftl->zone_size);
+	WARN_ON(boffset > ftl->block_size);
+
+	if (block == -1)
+		return -1;
+
+	return (zone * ftl->zone_size + block) * ftl->block_size + boffset;
+}
+
+/* Breaks offset into parts */
+static void sm_break_offset(struct sm_ftl *ftl, loff_t offset,
+					int *zone, int *block, int *boffset)
+{
+	*boffset = offset % ftl->block_size;
+	offset /= ftl->block_size;
+	*block = offset % ftl->max_lba;
+	offset /= ftl->max_lba;
+
+	if (offset >= ftl->zone_count)
+		dbg("sm_break_offset: try to access a zone %lx",
+						(long unsigned int)offset);
+
+	*zone = offset >= ftl->zone_count ? -1 : offset;
+}
+
+/* Reads a sector + oob*/
+static int sm_read_sector(struct sm_ftl *ftl,
+		int zone, int block, int boffset,
+				u8* buffer, struct sm_oob *oob)
+{
+	struct mtd_oob_ops ops;
+	struct sm_oob tmp_oob;
+	struct mtd_info *mtd = ftl->trans->mtd;
+	int ret;
+	loff_t offset;
+
+	ops.len = SM_SECTOR_SIZE;
+	ops.datbuf = buffer;
+
+	if (!oob)
+		oob = &tmp_oob;
+
+	if (!mtd)
+		return -ENODEV;
+
+	/* TODO: raw mode doesn't check the ecc + reads to data buffer*/
+	/* This works only with empty oob layout */
+	ops.mode = MTD_OOB_PLACE; 
+	ops.ooboffs = 0;
+	ops.ooblen = SM_OOB_SIZE;
+	ops.oobbuf = (void*)oob;
+
+	/* FTL can contain -1 entries that are by default filled with bits */
+	if (block == -1) {
+
+		if (buffer)
+			memset(buffer, 0xFF, SM_SECTOR_SIZE);
+		memset(oob, 0xFF, SM_OOB_SIZE);
+		return 0;
+	}
+
+	offset = sm_mkoffset(ftl, zone, block, boffset);
+	ret = mtd->read_oob(mtd, offset, &ops);
+
+	if (ret) {
+		return -EIO;
+		dbg("read of sector at 0x%lx failed with error %d",
+					(long unsigned int)offset, ret);
+	}
+
+	if (ops.oobretlen != SM_OOB_SIZE) {
+		dbg("read of sector at 0x%lx failed with wrong oob len %d",
+				(long unsigned int)offset, (int)ops.oobretlen);
+		return -EIO;
+	}
+
+	if (buffer && sm_sector_valid(oob)) {
+		dbg("read of sector at 0x%lxfailed because "
+			"it is marked as invalid",
+						(long unsigned int)offset);
+		return -EIO;
+	}
+
+	/* TODO: for small page flash, we need to check ecc here,
+		because nand subsystem really can't handle this... */
+
+	return 0;	
+}
+
+/* Write a block using data and lba */
+static int sm_write_block (struct sm_ftl *ftl, u8 *buf,
+					int zone_num, int block, int lba)
+{
+	struct mtd_oob_ops ops;
+	struct mtd_info *mtd = ftl->trans->mtd;
+	int boffset;
+	loff_t offset;
+	int retry;
+
+	struct sm_oob oob;
+	memset(&oob, 0xFF, sizeof(oob));
+	sm_write_lba(&oob, lba);
+
+	if(!mtd)
+		return -ENODEV;
+
+	if (zone_num == 0 && (block == ftl->cis_block || block == 0)) {
+		dbg("attempted to write the CIS!");
+		return -EIO;
+	}
+
+	/* TODO: for small page flash, we need to compute & write ecc here,
+		 because nand subsystem really can't handle this... */
+
+
+	ops.len = SM_SECTOR_SIZE;
+
+	/* TODO: raw mode doesn't check the ecc + reads to data buffer*/
+	/* This works only with empty oob layout */
+
+	ops.mode = MTD_OOB_PLACE;
+	ops.ooboffs = 0;
+	ops.ooblen = SM_OOB_SIZE;
+	ops.oobbuf = (void*)&oob;
+
+	/* Use write_oob here because some xD cards only accept writes that
+		contain both page and oob write. These cards most likely
+		do their own ftl */
+
+	offset = sm_mkoffset (ftl, zone_num, block, 0);
+
+restart:
+	for (boffset = 0; boffset < ftl->block_size;
+				boffset += SM_SECTOR_SIZE) {
+
+		ops.datbuf = buf + boffset;
+
+		if (!ftl->trans->mtd->write_oob(ftl->trans->mtd,
+						offset + boffset, &ops))
+			continue;
+
+		if (!retry) {
+			dbg("write of block %d in zone %d failed, erasing it",
+							block, zone_num);
+
+			/* If write fails. try to erase the block */
+			sm_erase_block(ftl, zone_num, block, 0);
+			retry = 1;
+			goto restart;
+		} else {
+			dbg("write of block %d in zone %d failed again"
+				", marking as bad", block, zone_num);
+
+			sm_mark_block_bad(ftl, zone_num, block);
+			return -EIO;
+		}
+	}
+	return 0;
+}
+
+/*
+ * Erase a block within a zone
+ * If erase succedes, it updates free block fifo
+ */
+static void sm_erase_block(struct sm_ftl *ftl, int zone_num, s16 block,
+								int put_free)
+{
+	struct ftl_zone *zone = &ftl->zones[zone_num];
+	struct erase_info erase;
+	struct mtd_info *mtd = ftl->trans->mtd;
+
+	if(!mtd)
+		return;
+
+	erase.mtd = ftl->trans->mtd;
+	erase.callback = sm_erase_callback;
+	erase.addr = sm_mkoffset(ftl, zone_num, block, 0);
+	erase.len = ftl->block_size;
+	erase.priv = (u_long)ftl;
+
+	if (zone_num == 0 && (block == ftl->cis_block || block == 0)) {
+		dbg("attempted to erase the CIS!");
+		return;
+	}
+
+	if (ftl->trans->mtd->erase(ftl->trans->mtd, &erase)) {
+		dbg("erase of block %d in zone %d failed in mtd->erase call",
+			block, zone_num);
+		goto error;
+	}
+
+	wait_for_completion(&ftl->erase_completion);
+
+	if(ftl->erase_error) {
+		dbg("erase of block %d in zone %d failed after wait",
+			block, zone_num);
+		goto error;
+	}
+	
+	if (put_free)
+		kfifo_put(zone->free_sectors, (const unsigned char *)&block, 2);
+	return;
+
+error:
+	sm_mark_block_bad(ftl, zone_num, block);
+	return;
+}
+
+static void sm_erase_callback (struct erase_info *self)
+{
+	struct sm_ftl *ftl = (struct sm_ftl *)self->priv;
+	ftl->erase_error = (self->state == MTD_ERASE_FAILED);
+	complete(&ftl->erase_completion);
+}
+
+
+/*
+ * Throughtly test that block is valid and belongs
+ * to specified LBA. Tries to erase it if not
+ */
+static int sm_check_block_lba (struct sm_ftl *ftl, int zone, int block, int lba)
+{
+	int boffset;
+	struct sm_oob oob;
+	int tmp;
+
+	for (boffset = 0; boffset < ftl->block_size;
+					boffset += SM_SECTOR_SIZE) {
+
+		if (sm_read_sector(ftl, zone, block, boffset, tmp_buffer,
+								&oob)) {
+			dbg("block check: fail in sector %d in zone %d",
+				block, zone);
+			goto erase;
+		}
+
+		if (sm_block_valid(&oob) || sm_sector_valid(&oob)) {
+			dbg("block check: block/sector status invalid"
+				" for sector %d in zone %d",
+				block, zone);
+			goto erase;
+		}
+
+		if ((tmp = sm_read_lba(&oob)) != lba) {
+			dbg("block check: block offset %d, we get "
+				"different LBA (%d), should get %d",
+				boffset, tmp, lba);
+			goto erase;
+		}
+	}
+	return 0;
+erase:
+	sm_erase_block(ftl, zone, block, 1);
+	return -EIO;
+}
+
+/* Mark whole block at offset 'offs' as bad.
+   We don't use mtd functions, because we know exectly how to do that
+*/
+static void sm_mark_block_bad(struct sm_ftl *ftl, int zone_num, int block) {
+
+	struct mtd_oob_ops ops;
+	struct sm_oob oob;
+	int boffset;
+	int offset = sm_mkoffset(ftl, zone_num, block, 0);
+	struct mtd_info *mtd = ftl->trans->mtd;
+
+	if(!mtd)
+		return;
+
+	dbg("marking block %d of zone %d as bad", block, zone_num);
+
+	memset (&oob, 0xFF, sizeof(oob));
+	oob.block_status = 0xF0;
+
+	ops.mode = MTD_OOB_RAW;
+	ops.ooboffs = 0;
+	ops.ooblen = SM_OOB_SIZE;
+	ops.oobbuf = (void*)&oob;
+	ops.datbuf = NULL;
+
+	/* We aren't checking the return value, because we don't care */
+	for (boffset = 0; boffset < ftl->block_size; boffset += SM_SECTOR_SIZE)
+		mtd->write_oob(mtd, offset + boffset, &ops);
+}
+
+/*
+ * Initialize FTL mapping for one zone
+ */
+struct ftl_zone* sm_initialize_zone(struct sm_ftl *ftl, int zone_num)
+{
+	struct sm_oob oob;
+	struct ftl_zone *zone;
+	u16 block;
+	int lba;
+	int i = 0;
+
+	if (zone_num >= ftl->zone_count || zone_num < 0) {
+		dbg("invalid zone (%d) was attempted to initialize", zone_num);
+		return (struct ftl_zone*)-ENODEV;
+	}
+
+	zone = &ftl->zones[zone_num];
+	if (zone->initialized)
+		return zone;
+
+	dbg("initializing zone %d", zone_num);
+
+	if (!(zone->lba_to_phys_table = kmalloc(ftl->max_lba * 2, GFP_KERNEL)))
+		return ERR_PTR(-ENOMEM);
+
+	zone->free_sectors = kfifo_alloc (ftl->zone_size * 2,
+		GFP_KERNEL, &ftl->fifo_lock);
+
+	if (!zone->free_sectors) {
+		kfree(zone->lba_to_phys_table);
+		return ERR_PTR(-ENOMEM);
+	}
+
+	memset(zone->lba_to_phys_table, -1, ftl->max_lba * 2);
+
+	for (block = 0 ; block < ftl->zone_size ; block++) {
+
+		/* Skip blocks till the CIS (including) */
+		if (zone_num == 0 && block <= ftl->cis_block) {
+
+			if (block != ftl->cis_block)
+				dbg ("skipping block %d because"
+					" it is before the CIS (%d)",
+						block, ftl->cis_block);
+			continue;
+		}
+
+		/* Not much that we can do with blocks without
+		 * even readable oob... - skip*/
+		/* Shouldn't happen though */
+		if (sm_read_sector(ftl, zone_num, block, 0, NULL, &oob)) {
+			dbg ("skipping block %d because it's "
+				"oob was unreadable", block);
+			continue;
+		}
+
+		/* Blocks with 0xFFs in the oob area are assumed free -
+			add to free table*/
+		lba = sm_read_lba(&oob);
+		if (lba == -1) {
+			kfifo_put(zone->free_sectors,
+				(unsigned char *)&block, 2);
+			continue;
+		}
+
+		/* Blocks that are marked as invalid aren't for sure usable */
+		/* If such block has correct LBA and no other block has it,
+			return errors on read */
+		if (sm_block_valid(&oob)) {
+			if (lba >= 0 && lba < ftl->max_lba)
+				zone->lba_to_phys_table[lba] = -2;
+			dbg ("skipping block %d because it was marked bad",
+									block);
+			continue;
+		}
+
+		/* Try to erase blocks that have invalid LBA,
+			but marked as valid */
+		if (lba >= ftl->max_lba || lba == -2) {
+			dbg ("erasing block %d because it has invalid LBA (%d)",
+				 block, lba);
+
+			sm_erase_block(ftl, zone_num, block, 1);
+			continue;
+		}
+
+		/* If there is no collision,
+			just put the sector in the FTL table */
+		if(zone->lba_to_phys_table[lba] < 0) {
+			//dbg("LBA %04d -> PH %04d", lba, block);
+			zone->lba_to_phys_table[lba] = block;
+			continue;
+		}
+
+		dbg ("collision of LBA %d between blocks %d and %d in zone %d",
+			lba, zone->lba_to_phys_table[lba], block, zone_num);
+
+		/* Otherwise, carefully see if one of them is invalid*/
+		if (sm_check_block_lba(ftl, zone_num, block, lba))
+			continue;
+
+		if (sm_check_block_lba(ftl, zone_num,
+				zone->lba_to_phys_table[lba], lba))
+			continue;
+
+		/* Now both blocks are valid and share same LBA...
+		   I guess only solution is to throw a dice.... */
+		dbg("erasing the later");
+		sm_erase_block(ftl, zone_num, block, 1);
+	}
+
+	dbg("zone initialized");
+	zone->initialized = 1;
+
+	/* No free sectors, means that the zone is heavily damaged, write won't
+		work, but it can still can be (partially) read */
+	if (!kfifo_len(zone->free_sectors)) {
+		dbg("no free blocks in zone %d", zone_num);
+		return zone;
+	}
+
+	return zone;
+
+	/* Randomize first block we write to */
+	get_random_bytes(&i, 2);
+	i %= (kfifo_len(zone->free_sectors) / 2);
+
+
+	while (i--) {
+		kfifo_get(zone->free_sectors, (unsigned char *)&block, 2);
+		kfifo_put(zone->free_sectors, (const unsigned char *)&block, 2);
+	}
+	return zone;
+}
+
+/* Write one cached block to hardware */
+int sm_cache_block_write(struct sm_ftl *ftl, struct cached_block *cache_entry)
+{
+	struct ftl_zone *zone = &ftl->zones[cache_entry->zone];
+
+	int sector_num;
+	u16 write_sector;
+	int zone_num = cache_entry->zone;
+	int block_num;
+
+	if (zone_num < 0) {
+		dbg("invalid zone num given.... %d", zone_num);
+		return -1;
+	}
+
+	block_num = zone->lba_to_phys_table[cache_entry->lba];
+
+
+	/* Read all unread areas of the cache block*/
+	for_each_bit(sector_num, &cache_entry->data_invalid_bitmap,
+		ftl->block_size / SM_SECTOR_SIZE) {
+
+
+		if (sm_read_sector(ftl,
+			zone_num, block_num, sector_num * SM_SECTOR_SIZE,
+			cache_entry->data + sector_num * SM_SECTOR_SIZE, NULL))
+			return -EIO;
+	}
+restart:
+	/* No spare blocks */
+	/* We could still continue by erasing the current block,
+		but for such worn out media it doesn't worth the trouble,
+			and the dangers */
+
+	if (!kfifo_len(zone->free_sectors)) {
+		dbg("no free sectors for write!");
+		return -EIO;
+	}
+
+	kfifo_get(zone->free_sectors, (unsigned char *)&write_sector, 2);
+
+	if (sm_write_block(ftl, cache_entry->data, zone_num, write_sector,
+							cache_entry->lba))
+			goto restart;
+
+	/* Update the FTL table */
+	zone->lba_to_phys_table[cache_entry->lba] = write_sector;
+
+	/* Write succesfull, so erase and free the old block */
+	if (block_num > 0)
+		sm_erase_block(ftl, zone_num, block_num, 1);
+	return 0;
+}
+
+
+/* Initialize new/used cache entry */
+static int sm_cache_block_init(struct sm_ftl *ftl,
+				struct cached_block *cache_entry)
+{
+	if (!cache_entry->data)
+		cache_entry->data = kmalloc(ftl->block_size, GFP_KERNEL);
+
+	if (!cache_entry->data)
+		return -ENOMEM;
+
+	cache_entry->data_invalid_bitmap = 0xFFFFFFFF;
+	cache_entry->lba = -1;
+	cache_entry->zone = -1;
+	
+	return 0;
+}
+
+
+/* Flushes write cache, have to be run with ->cache_lock held */
+static int __sm_cache_flush(struct sm_ftl *ftl)
+{
+	struct cached_block *cache_entry = NULL, *tmp_entry;
+	struct mtd_info *mtd = ftl->trans->mtd;
+	int error;
+
+	if (ftl->readonly)
+		return  -EROFS;
+
+	if (list_empty(&ftl->cache))
+		return 0;
+
+	if(!mtd)
+		return -ENODEV;
+
+	list_for_each_entry_safe(cache_entry, tmp_entry, &ftl->cache,
+								list_member) {
+
+		/* Write should never fail, unless media is worn out */
+		if (sm_cache_block_write(ftl, cache_entry)) {
+			dbg("sm_ftl: failed to write block %d at zone %d",
+				(int)cache_entry->lba, cache_entry->zone);
+			ftl->readonly = 1;
+			return -EIO;
+		}
+
+		list_del(&cache_entry->list_member);
+		list_add(&cache_entry->list_member, &ftl->free_cache);
+
+		if ((error = sm_cache_block_init(ftl, cache_entry)))
+			return error;
+	}
+	return 0;
+}
+
+
+/* Flushes the write cache */
+static int sm_cache_flush(struct sm_ftl *ftl)
+{
+	int retval;
+	mutex_lock(&ftl->cache_mutex);
+	retval = __sm_cache_flush(ftl);
+	mutex_unlock(&ftl->cache_mutex);
+	return retval;
+}
+
+/* Frees the write cache */
+static void sm_free_cache(struct sm_ftl *ftl)
+{
+	struct cached_block *cache_entry;
+
+	mutex_lock(&ftl->cache_mutex);
+	while(!list_empty(&ftl->free_cache)) {
+		cache_entry = list_first_entry(&ftl->free_cache,
+				struct cached_block, list_member);
+
+		kfree(cache_entry->data);
+		list_del(&cache_entry->list_member);
+		kfree(cache_entry);
+	}
+	mutex_unlock(&ftl->cache_mutex);
+}
+
+
+/* outside interface: open the device */
+static int sm_open(struct mtd_blktrans_dev *dev)
+{
+	struct sm_ftl *ftl = dev->priv;
+	ftl->flush_thread = kthread_run (sm_cache_flush_thread,
+				ftl, "smflush%d", dev->mtd->index);
+
+	if (IS_ERR(ftl->flush_thread))
+		return PTR_ERR(ftl->flush_thread);
+	return 0;
+}
+
+/* outside interface: read a sector */
+static int sm_read(struct mtd_blktrans_dev *dev,
+				unsigned long sect_no, char *buf)
+{
+	struct sm_ftl *ftl = dev->priv;
+	struct ftl_zone *zone;
+	struct cached_block *cache_entry = NULL;
+	int error = 0;
+	int cache_found = 0;
+
+	int zone_num, block, boffset;
+
+	sm_break_offset(ftl, sect_no << 9, &zone_num, &block, &boffset);
+
+	zone = sm_initialize_zone(ftl, zone_num);
+	if (IS_ERR (zone))
+		return PTR_ERR(zone);
+
+	mutex_lock(&ftl->cache_mutex);
+
+	/* Have to look at cache first */
+	list_for_each_entry(cache_entry, &ftl->cache, list_member)
+		if (cache_entry->zone == zone_num &&
+			cache_entry->lba == block &&
+
+			!test_bit(boffset / SM_SECTOR_SIZE,
+				&cache_entry->data_invalid_bitmap)) {
+
+			memcpy(buf, cache_entry->data + boffset, SM_SECTOR_SIZE);
+			goto unlock;
+		}
+
+
+	/* Translate the block and return if doesn't exist in the table */
+	block = zone->lba_to_phys_table[block];
+
+	if (block == -1) {
+		memset(buf, 0xFF, SM_SECTOR_SIZE);
+		goto unlock;
+	}
+
+	if (block == -2) {
+		dbg("read block %d of zone %d marked invalid in the ftl",
+							block, zone_num);
+		error = -EIO;
+		goto unlock;
+	}
+
+	/* Do the read. The below relies on proper ftl setup and underlying
+	  driver to check at least the ecc
+	*/
+	if (sm_read_sector(ftl, zone_num, block, boffset, buf, NULL)) {
+		error = -EIO;
+		goto unlock;
+	}
+
+	/* If we already have the cache entry, then add the data there, because
+		we will need it anyway..*/
+	if (cache_found) {
+		memcpy(cache_entry->data + boffset, buf, SM_SECTOR_SIZE);
+		clear_bit(boffset / SM_SECTOR_SIZE,
+				&cache_entry->data_invalid_bitmap);
+	}
+unlock:
+	mutex_unlock(&ftl->cache_mutex);
+	return error;
+}
+
+
+/* outside interface: write a sector */
+static int sm_write(struct mtd_blktrans_dev *dev,
+				unsigned long sec_no, char *buf)
+{
+	struct sm_ftl *ftl = dev->priv;
+	struct ftl_zone *zone;
+	struct cached_block *cache_entry = NULL;
+	int error;
+	int zone_num, block, boffset;
+	int cache_found = 0;
+
+	if (ftl->readonly)
+		return -EROFS;
+
+	sm_break_offset(ftl, sec_no << 9, &zone_num, &block, &boffset);
+
+	zone = sm_initialize_zone(ftl, zone_num);
+	if (IS_ERR(zone))
+		return PTR_ERR(zone);
+
+	/* Try to write the cache if possible */
+	mutex_lock(&ftl->cache_mutex);
+
+	/* Try to find existing cache entry */
+	list_for_each_entry(cache_entry, &ftl->cache, list_member)
+		if (cache_entry->zone == zone_num &&
+				cache_entry->lba == block) {
+			cache_found = 1;
+			break;
+		}
+
+	/* Entry not in the cache, create new cache entry */
+	if (!cache_found) {
+
+		/* Flush the cache if full */
+		if (list_empty(&ftl->free_cache))
+			if ((error = __sm_cache_flush(ftl)))
+				goto unlock;
+
+		BUG_ON(list_empty(&ftl->free_cache));
+
+		cache_entry = list_first_entry (&ftl->free_cache,
+			struct cached_block, list_member);
+
+		cache_entry->lba = block;
+		cache_entry->zone = zone_num;
+
+		list_del(&cache_entry->list_member);
+		list_add(&cache_entry->list_member, &ftl->cache);
+	}
+
+	/* And finally put data there */
+	memcpy(cache_entry->data + boffset, buf, SM_SECTOR_SIZE);
+	clear_bit(boffset / SM_SECTOR_SIZE, &cache_entry->data_invalid_bitmap);
+unlock:
+	mutex_unlock(&ftl->cache_mutex);
+	return error;
+}
+
+/* outside interface: flush everything */
+static int sm_flush(struct mtd_blktrans_dev *dev)
+{
+	struct sm_ftl *ftl = dev->priv;
+	return sm_cache_flush(ftl);
+}
+
+/* outside interface: last user has quit using the device,
+						also called on removal */
+static int sm_release (struct mtd_blktrans_dev *dev)
+{
+	struct sm_ftl *ftl = dev->priv;
+	sm_cache_flush(ftl);
+	kthread_stop(ftl->flush_thread);
+	return 0;
+}
+
+/* outside interface: get geometry */
+static int sm_getgeo(struct mtd_blktrans_dev *dev, struct hd_geometry *geo)
+{
+	struct sm_ftl *ftl = dev->priv;
+	geo->heads = ftl->heads;
+	geo->sectors = ftl->sectors;
+	geo->cylinders = ftl->cylinders;
+	return 0;
+}
+
+
+/* Periodic cache flush thread */
+static int sm_cache_flush_thread(void *data)
+{
+	struct sm_ftl *ftl = (struct sm_ftl *)data;
+
+	set_freezable();
+	while(!kthread_should_stop()) {
+
+		try_to_freeze();
+		set_current_state(TASK_INTERRUPTIBLE);
+		schedule_timeout(msecs_to_jiffies(500));
+		sm_cache_flush(ftl);
+	}
+
+	return 0;
+}
+
+static const u8 cis_signature[] = {
+	0x01, 0x03, 0xD9, 0x01, 0xFF, 0x18, 0x02, 0xDF, 0x01, 0x20
+};
+
+/* Locate the CIS */
+static int sm_find_cis(struct sm_ftl *ftl)
+{
+	int block, boffset;
+	struct sm_oob oob;
+	int block_found = 0;
+
+
+	/* Scan for first valid block */
+	for (block = 0 ; block < ftl->zone_size - ftl->max_lba ; block++) {
+		if (sm_read_sector(ftl, 0, block, 0, NULL, &oob))
+			continue;
+
+		if (sm_block_valid(&oob))
+			continue;
+
+		block_found = 1;
+		break;
+	}
+
+	if (!block_found)
+		return -EIO;
+
+	/* Block might be still partially damaged, so scan for first valid
+		sector */
+	for (boffset = 0 ; boffset < ftl->block_size;
+					boffset += SM_SECTOR_SIZE) {
+
+		if (sm_read_sector(ftl, 0, block, boffset, tmp_buffer, &oob))
+			continue;
+
+		if (!memcmp (tmp_buffer, cis_signature, sizeof(cis_signature)))
+			goto found;
+
+		if (!memcmp (tmp_buffer + SM_SECTOR_SIZE / 2, cis_signature,
+							sizeof(cis_signature)))
+			goto found;
+		return -EIO;
+	}
+found:
+	ftl->cis_block = block;
+	dbg("CIS block found at offset %d", block * ftl->block_size + boffset);
+	return 0;
+}
+
+/* external interface: main initialization function */
+static void sm_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
+{
+	struct mtd_blktrans_dev *trans;
+	struct sm_ftl *ftl;
+	int i;
+	struct cached_block *cache_entry;
+
+
+	/* Allocate & initialize our private structure */
+	if (!(ftl = kzalloc (sizeof (struct sm_ftl), GFP_KERNEL)))
+		goto error1;
+
+	INIT_LIST_HEAD(&ftl->cache);
+	INIT_LIST_HEAD(&ftl->free_cache);
+	mutex_init(&ftl->cache_mutex);
+	init_completion(&ftl->erase_completion);
+
+	/* Read media information */
+	if (sm_get_media_info(ftl, mtd))
+		goto error2;
+
+	/* Allocate zone array, it will be initialized on demand */
+	ftl->zones = kzalloc (sizeof (struct ftl_zone) * ftl->zone_count,
+								GFP_KERNEL);
+	if (!ftl->zones)
+		goto error2;
+
+	/* Allocate write cache */
+	INIT_LIST_HEAD(&ftl->cache);
+	INIT_LIST_HEAD(&ftl->free_cache);
+
+	for (i = 0 ; i < cache_size ; i++) {
+		cache_entry = kzalloc (sizeof (struct cached_block),
+								GFP_KERNEL);
+		if (!cache_entry)
+			break;
+
+		if (sm_cache_block_init(ftl, cache_entry)) {
+			kfree(cache_entry);
+			break;
+		}
+		list_add(&cache_entry->list_member, &ftl->free_cache);
+	}
+
+	if (list_empty(&ftl->free_cache))
+		goto error3;
+
+	/* Allocate upper layer structure and initialize it */
+	if (!(trans = kzalloc (sizeof(struct mtd_blktrans_dev), GFP_KERNEL)))
+		goto error4;
+
+	ftl->trans = trans;
+	trans->priv = ftl;
+
+	trans->tr = tr;
+	trans->mtd = mtd;
+	trans->devnum = -1;
+	trans->size = (ftl->block_size * ftl->max_lba * ftl->zone_count) >> 9;
+	trans->readonly = ftl->readonly;
+
+	if (sm_find_cis(ftl))
+		goto error4;
+
+	/* Register device*/
+	if (add_mtd_blktrans_dev(trans))
+		goto error5;
+
+	dbg("Found %d MiB SmartMedia/xD card on %s",
+		(int)(mtd->size / (1024 * 1024)), mtd->name);
+
+	dbg("FTL layout:");
+	dbg("%d zones, each consists of %d blocks (+%d spares)",
+		ftl->zone_count, ftl->max_lba,
+		ftl->zone_size - ftl->max_lba);
+	dbg("each block consists of %d bytes",
+		ftl->block_size);
+
+	return;
+error5:
+	kfree(trans);
+error4:
+	sm_free_cache(ftl);
+error3:
+	kfree(ftl->zones);
+error2:
+	kfree(ftl);
+error1:
+	return;
+}
+
+/* main interface: device {surprise,} removal */
+static void sm_remove_dev (struct mtd_blktrans_dev *dev)
+{
+	struct sm_ftl *ftl = dev->priv;
+	dbg("removing the ftl device");
+	del_mtd_blktrans_dev(dev);
+	kfree(ftl->zones);
+	sm_free_cache(ftl);
+	kfree(ftl); /* WE free that here, but the ->release can still
+			be called after ..... fuck */
+}
+
+static struct mtd_blktrans_ops sm_ftl_ops = {
+	.name		= "smblk",
+	.major		= -1,
+	.part_bits	= SM_FTL_PARTN_BITS,
+	.blksize	= SM_SECTOR_SIZE,
+	.getgeo		= sm_getgeo,
+	.readsect	= sm_read,
+	.writesect	= sm_write,
+	.add_mtd	= sm_add_mtd,
+	.remove_dev	= sm_remove_dev,
+	.open 		= sm_open,
+	.release	= sm_release,
+	.flush		= sm_flush,
+	.owner		= THIS_MODULE,
+};
+
+static __init int sm_module_init(void)
+{
+	return register_mtd_blktrans(&sm_ftl_ops);
+}
+
+static void __exit sm_module_exit(void)
+{
+	deregister_mtd_blktrans(&sm_ftl_ops);
+}
+
+module_init(sm_module_init);
+module_exit(sm_module_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Maxim Levitsky <maximlevitsky@gmail.com>");
+MODULE_DESCRIPTION("Smartmedia/xD mtd translation layer");
\ No newline at end of file
diff --git a/drivers/mtd/sm_ftl.h b/drivers/mtd/sm_ftl.h
new file mode 100644
index 0000000..5edf372
--- /dev/null
+++ b/drivers/mtd/sm_ftl.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2009 - Maxim Levitsky
+ * SmartMedia/xD translation layer
+ *
+ * Based loosly on ssfdc.c which is
+ *  (c) 2005 Eptar srl
+ *  Author: Claudio Lanconelli <lanconelli.claudio@eptar.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/mtd/nand.h>
+#include <linux/mtd/blktrans.h>
+#include <linux/list.h>
+#include <linux/kfifo.h>
+#include <linux/sched.h>
+#include <linux/completion.h>
+#include <linux/spinlock.h>
+
+
+struct ftl_zone {
+	int initialized;
+	s16 *lba_to_phys_table;		/* LBA to physical table */
+	struct kfifo *free_sectors;	/* queue of free sectors */
+};
+
+
+struct cached_block {
+	int zone;
+	unsigned long lba;
+	unsigned char *data;
+	long unsigned int data_invalid_bitmap;
+	struct list_head list_member;
+};
+	
+
+struct sm_ftl {
+	struct mtd_blktrans_dev *trans;
+	struct ftl_zone *zones;
+	struct list_head cache;
+	struct list_head free_cache;
+	struct mutex cache_mutex;
+	struct completion erase_completion;
+	struct task_struct *flush_thread;
+	int erase_error;
+
+	int block_size;		/* block size in bytes */
+	int zone_size;		/* zone size in blocks */
+	int zone_count;		/* number of zones */
+	int max_lba;		/* maximum lba in a zone */
+	int smallpagenand;	/* 256 bytes/page nand */
+
+	int readonly;
+
+	/* geometry stuff */
+	int heads;
+	int sectors;
+	int cylinders;
+
+	/*Misc */
+	int cis_block;
+
+	spinlock_t fifo_lock;
+};
+
+struct chs_entry {
+	unsigned long size;
+	unsigned short cyl;
+	unsigned char head;
+	unsigned char sec;
+};
+
+
+#define SM_FTL_PARTN_BITS	3
+
+#define dbg(format, ...) \
+	printk (KERN_ERR "sm_ftl" ": " format "\n", ## __VA_ARGS__)
-- 
1.6.3.3

^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 9/9] mtd: add nand driver for ricoh xD/SmartMedia reader
  2010-01-06 21:43 RFC: [PATCH 0/9 Integration of SmartMedia/xD into mtd subsystem Maxim Levitsky
                   ` (7 preceding siblings ...)
  2010-01-06 21:53 ` [PATCH 8/9] mtd: SmartMedia/xD FTL Maxim Levitsky
@ 2010-01-06 21:54 ` Maxim Levitsky
  8 siblings, 0 replies; 10+ messages in thread
From: Maxim Levitsky @ 2010-01-06 21:54 UTC (permalink / raw)
  To: linux-kernel; +Cc: joern, linux-mtd, Alex Dubov

>From b1150984957b7a2b429d40173891098a035c8947 Mon Sep 17 00:00:00 2001
From: Maxim Levitsky <maximlevitsky@gmail.com>
Date: Wed, 6 Jan 2010 23:06:28 +0200
Subject: [PATCH 9/9] mtd: add nand driver for ricoh xD/SmartMedia reader

---
 MAINTAINERS               |    6 +
 drivers/mtd/nand/Kconfig  |   11 +
 drivers/mtd/nand/Makefile |    1 +
 drivers/mtd/nand/r822.c   | 1014 +++++++++++++++++++++++++++++++++++++++++++++
 drivers/mtd/nand/r822.h   |  155 +++++++
 5 files changed, 1187 insertions(+), 0 deletions(-)
 create mode 100644 drivers/mtd/nand/r822.c
 create mode 100644 drivers/mtd/nand/r822.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 1c02e08..50a4006 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4466,6 +4466,12 @@ S:	Maintained
 F:	Documentation/rfkill.txt
 F:	net/rfkill/
 
+RICOH SMARTMEDIA/XD DRIVER
+M:	Maxim Levitsky <maximlevitsky@gmail.com>
+S:	Maintained
+F:	drivers/mtd/nand/r822.c
+F:	drivers/mtd/nand/r822.h
+
 RISCOM8 DRIVER
 S:	Orphan
 F:	Documentation/serial/riscom8.txt
diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig
index 2fda0b6..c71e282 100644
--- a/drivers/mtd/nand/Kconfig
+++ b/drivers/mtd/nand/Kconfig
@@ -106,6 +106,17 @@ config MTD_NAND_TS7250
 config MTD_NAND_IDS
 	tristate
 
+config MTD_NAND_RICOH
+	tristate "Ricoh xD card reader"
+	default n
+	select MTD_SM_COMMON
+	help
+	  Enable support for Ricoh xD card reader
+	  You also need to enable ether
+	  NAND SSFDC (SmartMedia) read only translation layer' or new
+	  expermental, readwrite
+	  'SmartMedia/xD new translation layer'
+
 config MTD_NAND_AU1550
 	tristate "Au1550/1200 NAND support"
 	depends on SOC_AU1200 || SOC_AU1550
diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile
index 6950d3d..7b4f500 100644
--- a/drivers/mtd/nand/Makefile
+++ b/drivers/mtd/nand/Makefile
@@ -42,5 +42,6 @@ obj-$(CONFIG_MTD_NAND_SOCRATES)		+= socrates_nand.o
 obj-$(CONFIG_MTD_NAND_TXX9NDFMC)	+= txx9ndfmc.o
 obj-$(CONFIG_MTD_NAND_W90P910)		+= w90p910_nand.o
 obj-$(CONFIG_MTD_NAND_NOMADIK)		+= nomadik_nand.o
+obj-$(CONFIG_MTD_NAND_RICOH)		+= r822.o
 
 nand-objs := nand_base.o nand_bbt.o
diff --git a/drivers/mtd/nand/r822.c b/drivers/mtd/nand/r822.c
new file mode 100644
index 0000000..a5e9abc
--- /dev/null
+++ b/drivers/mtd/nand/r822.c
@@ -0,0 +1,1014 @@
+/*
+ * Copyright (C) 2009 - Maxim Levitsky
+ * driver for Ricoh xD readers
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/jiffies.h>
+#include <linux/workqueue.h>
+#include <linux/interrupt.h>
+#include <linux/pci_ids.h>
+#include <asm/byteorder.h>
+#include <linux/sched.h>
+#include "../sm_common.h"
+#include "r822.h"
+
+/* read register */
+static inline u8 r822_read_reg(struct r822_device *dev, int address)
+{
+	u8 reg = readb(dev->mmio + address);
+	return reg;
+}
+
+/* write register */
+static inline void r822_write_reg(struct r822_device *dev,
+						int address, u8 value)
+{
+	writeb(value, dev->mmio + address);
+}
+
+
+/* read dword sized register */
+static inline u32 r822_read_reg_dword(struct r822_device *dev, int address)
+{
+	u32 reg = le32_to_cpu(readl(dev->mmio + address));
+	return reg;
+}
+
+/* write dword sized register */
+static inline void r822_write_reg_dword(struct r822_device *dev,
+							int address, u32 value)
+{
+	writel(cpu_to_le32(value), dev->mmio + address);
+}
+
+/* returns pointer to our private structure */
+static inline struct r822_device *r822_get_dev(struct mtd_info *mtd)
+{
+	struct nand_chip *chip = (struct nand_chip *)mtd->priv;
+	return (struct r822_device *)chip->priv;
+}
+
+
+/* check if controller supports dma */
+static void r822_dma_test(struct r822_device *dev)
+{
+	dev->dma_usable = (r822_read_reg(dev, R822_DMA_CAP) &
+		(R822_DMA1 | R822_DMA2)) == (R822_DMA1 | R822_DMA2);
+
+	if (!dev->dma_usable)
+		dbg("Non dma capable device detected, dma disabled");
+}
+
+/* 
+ * Enable dma. Enables ether first or second stage of the DMA,
+ * Expects dev->dma_dir and dev->dma_state be set 
+ */
+static void r822_dma_enable(struct r822_device *dev)
+{
+	u8 dma_reg = dev->dma_dir ? R822_DMA_READ : 0;
+
+	if (dev->dma_state == DMA_INTERNAL)
+		dma_reg |= R822_DMA_INTERNAL;
+	else {
+		dma_reg |= R822_DMA_MEMORY;
+		r822_write_reg_dword(dev, R822_DMA_ADDR,
+			cpu_to_le32(dev->phys_dma_addr));
+	}
+
+	r822_write_reg(dev, R822_DMA_IRQ_STA,
+			r822_read_reg(dev, R822_DMA_IRQ_STA));
+
+	r822_write_reg(dev, R822_DMA_SETTINGS, dma_reg);
+	r822_write_reg(dev, R822_DMA_IRQ_ENABLE,
+		R822_DMA_IRQ_INTERNAL |
+		R822_DMA_IRQ_ERROR |
+		R822_DMA_IRQ_MEMORY);
+}
+
+/*
+ * Disable dma, called from the interrupt handler, which specifies
+ * success of the operation via 'error' argument
+ */
+static void r822_dma_done(struct r822_device *dev, int error)
+{
+	WARN_ON(dev->dma_stage == 0);
+
+	if (error)
+		dbg("dma: complete with error");
+
+	r822_write_reg(dev, R822_DMA_IRQ_STA, r822_read_reg(dev, R822_DMA_IRQ_STA));
+	r822_write_reg(dev, R822_DMA_SETTINGS, 0);
+	r822_write_reg(dev, R822_DMA_IRQ_ENABLE, 0);
+
+	dev->dma_error = error;
+	dev->dma_stage = 0;
+
+	if(dev->phys_dma_addr && dev->phys_dma_addr != dev->phys_bounce_buffer)
+		pci_unmap_single(dev->pci_dev, dev->phys_dma_addr, R822_DMA_LEN,
+			dev->dma_dir ? PCI_DMA_FROMDEVICE : PCI_DMA_TODEVICE);
+	complete(&dev->dma_done);
+}
+
+/*
+ * Wait, till dma is done, which includes both phases of it
+ */
+static int r822_dma_wait(struct r822_device *dev)
+{
+	long timeout = wait_for_completion_interruptible_timeout(&dev->dma_done,
+				msecs_to_jiffies(1000));
+	if (timeout < 0)
+		return -EAGAIN;
+	if (!timeout)
+		return -ETIMEDOUT;
+	return 0;
+}
+
+/*
+ * Read/Write one page using dma. Only pages can be read (512 bytes), oob
+ * can't be read here
+*/
+static void r822_do_dma (struct r822_device *dev, uint8_t *buf, int do_read)
+{
+	int bounce = 0;
+	unsigned long flags;
+	int error;
+
+	dev->dma_error = 0;
+
+	/* Set dma direction */
+	dev->dma_dir = do_read;
+	dev->dma_stage = 1;
+
+	/* Set intial dma state: for reading first fill on board buffer,
+	  from device, for writes first fill the buffer  from memory*/
+	dev->dma_state = do_read ? DMA_INTERNAL : DMA_MEMORY;
+
+	/* if incoming buffer is not page aligned, we should do bounce */
+	if ((unsigned long)buf & (PAGE_SIZE-1))
+		bounce = 1;
+
+	if (!bounce) {
+		dev->phys_dma_addr = pci_map_single(dev->pci_dev, (void*)buf,
+			R822_DMA_LEN,
+			(do_read ? PCI_DMA_FROMDEVICE : PCI_DMA_TODEVICE));
+
+		if (dev->phys_dma_addr == bad_dma_address)
+			bounce = 1;
+	}
+
+	if (bounce) {
+		dev->phys_dma_addr = dev->phys_bounce_buffer;
+
+		if (!do_read)
+			memcpy(dev->bounce_buffer, buf, R822_DMA_LEN);
+	}
+
+	/* Enable DMA */
+	spin_lock_irqsave(&dev->irqlock, flags);
+	r822_dma_enable(dev);
+	spin_unlock_irqrestore(&dev->irqlock, flags);
+
+	/* Wait till complete */
+	if((error = r822_dma_wait(dev))) {
+		r822_dma_done(dev, error);
+		return;
+	}
+
+	if(do_read && bounce)
+		memcpy((void*)buf, dev->bounce_buffer, R822_DMA_LEN);
+}
+
+/* 
+ * Program data lines of the nand chip to send data to it
+ */
+void r822_write_buf (struct mtd_info *mtd, const uint8_t *buf, int len)
+{
+	struct r822_device *dev = r822_get_dev(mtd);
+	u32 reg;
+
+	if (dev->card_unstable)
+		return;
+
+	/* special case for whole sector read */
+	if (len == R822_DMA_LEN && dev->dma_usable) {
+		r822_do_dma(dev, (uint8_t *)buf, 0);
+		return;
+	}
+
+	/* write DWORD chinks - faster */
+	while(len) {
+		reg = buf[0] | buf[1] << 8 | buf[2] << 16 | buf[3] << 24;
+		r822_write_reg_dword(dev, R822_DATALINE, reg);
+		buf += 4;
+		len -= 4;
+
+		if (len %16 == 0)
+			udelay(20);
+	}
+
+	/* write rest */
+	while (len)
+		r822_write_reg(dev, R822_DATALINE, *buf++);
+}
+
+/* 
+ * Read data lines of the nand chip to retrieve data
+ */
+void r822_read_buf (struct mtd_info *mtd, uint8_t *buf, int len)
+{
+	struct r822_device *dev = r822_get_dev(mtd);
+	u32 reg;
+
+	if (dev->card_unstable)
+		return;
+
+	/* special case for whole sector read */
+	if (len == R822_DMA_LEN && dev->dma_usable) {
+		r822_do_dma(dev, buf, 1);
+		return;
+	}
+
+	/* read in dword sized chunks */
+	while(len >= 4) {
+
+		reg = r822_read_reg_dword(dev, R822_DATALINE);
+		*buf++ = reg & 0xFF;
+		*buf++ = (reg >> 8) & 0xFF;
+		*buf++ = (reg >> 16) & 0xFF;
+		*buf++ = (reg >> 24) & 0xFF;
+		len -= 4;
+	}
+
+	/* read the reset by bytes */
+	while(len--)
+		*buf++ = r822_read_reg (dev, R822_DATALINE);
+}
+
+static uint8_t r822_read_byte(struct mtd_info *mtd) {
+	struct r822_device *dev = r822_get_dev(mtd);
+	return r822_read_reg(dev, R822_DATALINE);
+}
+
+
+/* 
+ * Readback the buffer to verify it
+ */
+int r822_verify_buf(struct mtd_info *mtd, const uint8_t *buf, int len)
+{
+	struct r822_device *dev = r822_get_dev(mtd);
+
+	if (dev->card_unstable)
+		return 0;
+
+	if (len > SM_SECTOR_SIZE)
+		return 0;
+
+	r822_read_buf(mtd, dev->tmp_buffer, len);
+	return memcmp(buf, dev->tmp_buffer, len);
+}
+
+/*
+ * Control several chip lines & send commands
+ */
+void r822_cmdctl(struct mtd_info *mtd, int dat, unsigned int ctrl)
+{
+	struct r822_device *dev = r822_get_dev(mtd);
+
+	if (dev->card_unstable)
+		return;
+
+	if (ctrl & NAND_CTRL_CHANGE) {
+
+		dev->ctlreg &= ~(R822_CTL_DATA | R822_CTL_COMMAND |
+				 R822_CTL_ON | R822_CTL_CARDENABLE);
+
+		if (ctrl & NAND_ALE)
+			dev->ctlreg |= R822_CTL_DATA;
+
+		if (ctrl & NAND_CLE)
+			dev->ctlreg |= R822_CTL_COMMAND;
+
+		if (ctrl & NAND_NCE)
+			dev->ctlreg |= (R822_CTL_CARDENABLE | R822_CTL_ON);
+		else
+			dev->ctlreg &= ~R822_CTL_WRITE;
+
+		/* when write is stareted, enable write access */
+		if (dat == NAND_CMD_ERASE1)
+			dev->ctlreg |= R822_CTL_WRITE;
+
+		r822_write_reg(dev, R822_CTL, dev->ctlreg);
+	}
+
+
+	 /* HACK: NAND_CMD_SEQIN is called without NAND_CTRL_CHANGE, but we need
+		to set write mode */
+	if (dat == NAND_CMD_SEQIN && (dev->ctlreg & R822_CTL_COMMAND)) {
+		dev->ctlreg |= R822_CTL_WRITE;
+		r822_write_reg(dev, R822_CTL, dev->ctlreg);
+	}	
+
+	if (dat != NAND_CMD_NONE)
+		r822_write_reg(dev, R822_DATALINE, dat);
+}
+
+/*
+ * Wait till card is ready... This has to be a busy loop because
+ * 'our <censored> controller' doesn't have an interrupt for that...
+ * based on nand_wait
+ */
+int r822_wait(struct mtd_info *mtd, struct nand_chip *chip)
+{
+	struct r822_device *dev = (struct r822_device *)chip->priv;
+
+	unsigned long timeout;
+	int status;
+
+	timeout = jiffies + (chip->state == FL_ERASING ?
+		msecs_to_jiffies(400) : msecs_to_jiffies(20));
+
+	while (time_before(jiffies, timeout)) {
+		if (chip->dev_ready(mtd))
+			break;
+		cond_resched();
+	}
+
+	chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
+	status = (int)chip->read_byte(mtd);
+
+	/* Unfortunelly, no way to send detailed error status... */
+	if (dev->dma_error) {
+		status |= NAND_STATUS_FAIL;
+		dev->dma_error = 0;
+	}
+
+	return status;
+}
+
+/*
+ * Check if card is ready
+ */
+
+int r822_ready(struct mtd_info *mtd)
+{
+	struct r822_device *dev = r822_get_dev(mtd);
+	return !(r822_read_reg(dev, R822_CARD_STA) & R822_CARD_STA_BUSY);
+}
+
+
+/*
+ * Set ECC engine mode
+*/
+
+void r822_ecc_hwctl (struct mtd_info *mtd, int mode)
+{
+	struct r822_device *dev = r822_get_dev(mtd);
+
+	if (dev->card_unstable)
+		return;
+
+	switch(mode) {
+	case NAND_ECC_READ:
+	case NAND_ECC_WRITE:
+		/* enable ecc generation/check*/
+		dev->ctlreg |= R822_CTL_ECC_ENABLE;
+
+		/* flush ecc buffer */
+		r822_write_reg(dev, R822_CTL, dev->ctlreg | R822_CTL_ECC_ACCESS);
+		r822_read_reg(dev, R822_DATALINE);
+		r822_write_reg(dev, R822_CTL, dev->ctlreg);
+		return;
+
+	case NAND_ECC_READSYN:
+		/* disable ecc generation */
+		dev->ctlreg &= ~R822_CTL_ECC_ENABLE;
+		r822_write_reg(dev, R822_CTL, dev->ctlreg);
+	}
+}
+
+/*
+ * Calculate ECC, only used for writes
+ */
+
+int r822_ecc_calculate(struct mtd_info *mtd,const uint8_t *dat,
+							uint8_t *ecc_code)
+{
+	struct r822_device *dev = r822_get_dev(mtd);
+	struct sm_oob *oob = (struct sm_oob *)ecc_code;
+	u32 ecc1, ecc2;
+
+	if (dev->card_unstable)
+		return 0;
+
+	dev->ctlreg &= ~R822_CTL_ECC_ENABLE;
+	r822_write_reg(dev, R822_CTL, dev->ctlreg | R822_CTL_ECC_ACCESS);
+
+	ecc1 = r822_read_reg_dword(dev, R822_DATALINE);
+	ecc2 = r822_read_reg_dword(dev, R822_DATALINE);
+
+	oob->ecc1[0] = (ecc1) & 0xFF;
+	oob->ecc1[1] = (ecc1 >> 8) & 0xFF;
+	oob->ecc1[2] = (ecc1 >> 16) & 0xFF;
+
+	oob->ecc2[0] = (ecc2) & 0xFF;
+	oob->ecc2[1] = (ecc2 >> 8) & 0xFF;
+	oob->ecc2[2] = (ecc2 >> 16) & 0xFF;
+
+	r822_write_reg(dev, R822_CTL, dev->ctlreg);
+	return 0;
+}
+
+/*
+ * Correct the data using ECC, hw did almost everything for us
+ */
+
+int r822_ecc_correct(struct mtd_info *mtd, uint8_t *dat,
+				uint8_t *read_ecc,uint8_t *calc_ecc)
+{
+	u16 ecc_reg;
+	u8 ecc_status, err_byte;
+	int i, error = 0;
+
+	struct r822_device *dev = r822_get_dev(mtd);
+
+	if (dev->card_unstable)
+		return 0;
+
+	r822_write_reg(dev, R822_CTL, dev->ctlreg | R822_CTL_ECC_ACCESS);
+	ecc_reg = r822_read_reg_dword(dev, R822_DATALINE);
+	r822_write_reg(dev, R822_CTL, dev->ctlreg);
+
+	for (i = 0 ; i <= 1 ; i++) {
+
+		ecc_status = (ecc_reg >> 8) & 0xFF;
+
+		/* ecc uncorrectable error */
+		if (ecc_status & R822_ECC_FAIL) {
+			dbg("ecc: unrecoverable error, in half %d", i);
+			error = -1;
+			goto exit;
+		}
+
+		/* correctable error */
+		if (ecc_status & R822_ECC_CORRECTABLE) {
+
+			err_byte = ecc_reg & 0xFF;
+
+			dbg("ecc: recoverable error, in half %d, byte %d, bit %d", i,
+				err_byte, ecc_status & R822_ECC_ERR_BIT_MSK);
+
+			dat[err_byte] ^= 1 << (ecc_status & R822_ECC_ERR_BIT_MSK);
+			error++;
+		}
+
+		dat += 256;
+		ecc_reg >>= 16;
+	}
+exit:
+	return error;
+}
+
+/*
+ * This is copy of nand_read_oob_std
+ * nand_read_oob_syndrome assumes we can send column address - we can't
+ */
+static int r822_read_oob(struct mtd_info *mtd, struct nand_chip *chip,
+			     int page, int sndcmd)
+{
+	if (sndcmd) {
+		chip->cmdfunc(mtd, NAND_CMD_READOOB, 0, page);
+		sndcmd = 0;
+	}
+	chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
+	return sndcmd;
+}
+
+/*
+ * Start the hardware
+ */
+
+void r822_device_start(struct r822_device *dev)
+{
+	if (r822_read_reg(dev, R822_HW) & R822_HW_UNKNOWN) {
+		dbg("r822_device_start: probably recovering from HW error");
+		r822_write_reg(dev, R822_CTL, R822_CTL_RESET | R822_CTL_ON);
+		r822_write_reg_dword(dev, R822_HW, R822_HW_ENABLED);
+	} else {
+		r822_write_reg(dev, R822_HW, R822_HW_ENABLED);
+		r822_write_reg(dev, R822_CTL, R822_CTL_RESET | R822_CTL_ON);
+		r822_write_reg(dev, R822_CTL, 0);
+	}
+	msleep(200);
+}
+
+
+/*
+ * Shutdown the hardware
+ */
+
+void r822_device_shutdown(struct r822_device *dev)
+{
+	r822_write_reg(dev, R822_HW, 0);
+	r822_write_reg(dev, R822_CTL, R822_CTL_RESET);
+}
+
+/*
+ * Test if card is present
+ */
+
+void r822_card_update_present(struct r822_device *dev)
+{
+	unsigned long flags;
+	u8 reg;
+
+	spin_lock_irqsave(&dev->irqlock, flags);
+	reg = r822_read_reg(dev, R822_CARD_STA);
+	dev->card_detected = !!(reg & R822_CARD_STA_PRESENT);
+	spin_unlock_irqrestore(&dev->irqlock, flags);
+}
+
+/* 
+ * Update card detection IRQ state according to current card state
+ * which is read in r822_card_update_present
+ */
+void r822_update_card_detect(struct r822_device *dev)
+{
+	int card_detect_reg = R822_CARD_IRQ_GENABLE;
+	card_detect_reg |= dev->card_detected ? R822_CARD_IRQ_REMOVE: R822_CARD_IRQ_INSERT;
+	r822_write_reg(dev, R822_CARD_IRQ_ENABLE, card_detect_reg);
+}
+
+
+/* Detect properties of card in slot */
+void r822_update_media_status(struct r822_device *dev)
+{
+	u8 reg;
+	unsigned long flags;
+	int readonly, sm;
+
+	spin_lock_irqsave(&dev->irqlock, flags);
+	if (!dev->card_detected) {
+		dbg("card removed");
+		spin_unlock_irqrestore(&dev->irqlock, flags);
+		return ;
+	}
+
+	readonly  = r822_read_reg(dev, R822_CARD_STA) & R822_CARD_STA_RO;
+	reg = r822_read_reg(dev, R822_DMA_CAP);
+	sm = (reg & (R822_DMA1 | R822_DMA2)) && (reg & R822_SMBIT);
+
+	dbg("detected %s %s card in slot",
+		sm ? "SmartMedia" : "xD",
+		readonly ? "readonly" : "writeable");
+
+	dev->readonly = readonly;
+	spin_unlock_irqrestore(&dev->irqlock, flags);
+}
+
+/*
+ * Register the nand device
+ * Called when the card is detected
+ */
+int r822_register_nand_device(struct r822_device *dev)
+{
+	if (!(dev->mtd = kzalloc(sizeof(struct mtd_info), GFP_KERNEL)))
+		goto error1;
+
+	dev->mtd->owner = THIS_MODULE;
+	dev->mtd->priv = dev->chip;
+
+	if (dev->readonly)
+		dev->chip->options |= NAND_ROM;
+
+	r822_device_start(dev);
+	if (sm_register_device(dev->mtd))
+		goto error2;
+
+	dev->card_registred = 1;
+	return 0;
+error2:
+	kfree(dev->mtd);
+error1:
+	return -1;
+}
+
+/*
+ * Unregister the card
+ */
+
+void r822_unregister_nand_device(struct r822_device *dev)
+{
+	if (!dev->card_registred)
+		return;
+
+	nand_release(dev->mtd);
+	r822_device_shutdown(dev);
+	dev->card_registred = 0;
+	kfree(dev->mtd);
+	dev->mtd = NULL;
+}
+
+
+/* Card state updater */
+void r822_card_detect_work(struct work_struct *work)
+{
+	struct r822_device *dev =
+		container_of(work, struct r822_device, card_detect_work.work);
+
+	r822_card_update_present(dev);
+	dev->card_unstable = 0;
+
+	/* false alarm */
+	if (dev->card_detected == dev->card_registred) {
+		goto exit;
+	}
+
+	r822_update_media_status(dev);
+
+	/* no card present */
+	if (!dev->card_detected) {
+		r822_unregister_nand_device(dev);
+		goto exit;
+	}
+
+	/* card present from now */
+
+
+	if(!r822_register_nand_device(dev))
+		goto exit;
+	else
+		dev->card_detected = 0;
+exit:
+	r822_update_card_detect(dev);
+}
+
+
+/* disable IRQ generation */
+static void r822_disable_irqs(struct r822_device *dev)
+{
+	u8 reg;
+	reg = r822_read_reg(dev, R822_CARD_IRQ_ENABLE);
+	r822_write_reg(dev, R822_CARD_IRQ_ENABLE, reg & ~R822_CARD_IRQ_MASK);
+
+	reg = r822_read_reg(dev, R822_DMA_IRQ_ENABLE);
+	r822_write_reg(dev, R822_DMA_IRQ_ENABLE, reg & ~R822_DMA_IRQ_MASK);
+
+	reg = r822_read_reg(dev, R822_CARD_IRQ_STA);
+	r822_write_reg(dev, R822_CARD_IRQ_STA, reg);
+
+	reg = r822_read_reg(dev, R822_DMA_IRQ_STA);
+	r822_write_reg(dev, R822_DMA_IRQ_STA, reg);
+
+}
+
+/* Interrupt handler */
+static irqreturn_t r822_irq (int irq, void *data)
+{
+	struct r822_device *dev = (struct r822_device *)data;
+
+	u8 card_status, dma_status;
+	unsigned long flags;
+
+	spin_lock_irqsave(&dev->irqlock, flags);
+
+	/* test card status interrupts */
+	card_status = r822_read_reg(dev, R822_CARD_IRQ_STA) & R822_CARD_IRQ_MASK;
+
+	if(card_status & (R822_CARD_IRQ_INSERT|R822_CARD_IRQ_REMOVE)) {
+
+		dev->card_detected = !!(card_status & R822_CARD_IRQ_INSERT);
+
+		/* we shouldn't recieve any interrupts if we wait for card
+			to settle */
+		WARN_ON(dev->card_unstable);
+
+		/* disable irqs while card is unstable */
+		/* this will timeout DMA if active, but better that garbage */
+		r822_disable_irqs(dev);
+
+		/* let, card state to settle a bit, and then do the work */
+		if(!dev->card_unstable) {
+			dev->card_unstable = 1;
+			queue_delayed_work(dev->card_workqueue,
+				&dev->card_detect_work, msecs_to_jiffies(100));
+		}
+
+		spin_unlock_irqrestore(&dev->irqlock, flags);
+		return IRQ_HANDLED;
+	}
+
+	card_status &= ~R822_CARD_IRQ_UNUSED1;
+	if (card_status)
+		dbg("card status = %02x", card_status);
+
+	/* test and ack dma interrupts */
+	dma_status = r822_read_reg(dev, R822_DMA_IRQ_STA) & R822_DMA_IRQ_MASK;
+	r822_write_reg(dev, R822_DMA_IRQ_STA, dma_status);
+
+	if(!dma_status) {
+		spin_unlock_irqrestore(&dev->irqlock, flags);
+		return IRQ_NONE;
+	}
+
+	if (dma_status & R822_DMA_IRQ_ERROR) {
+		dbg("recieved dma error IRQ");
+		r822_dma_done(dev, -EIO);
+		spin_unlock_irqrestore(&dev->irqlock, flags);
+		return IRQ_HANDLED;
+	}
+
+
+	/* recieved DMA interrupt out of nowhere? */
+	WARN_ON(dev->dma_stage == 0);
+
+
+	/* done device access */
+	if (dev->dma_state == DMA_INTERNAL && (dma_status & R822_DMA_IRQ_INTERNAL)) {
+		dev->dma_state = DMA_MEMORY;
+		dev->dma_stage++;
+	}
+
+	/* done memory DMA */
+	if (dev->dma_state == DMA_MEMORY && (dma_status & R822_DMA_IRQ_MEMORY)) {
+		dev->dma_state = DMA_INTERNAL;
+		dev->dma_stage++;
+	}
+
+	/* Enable 2nd half of dma dance */
+	if (dev->dma_stage == 2)
+		r822_dma_enable(dev);
+
+
+	/* Operation done */
+	if (dev->dma_stage == 3)
+		r822_dma_done(dev, 0);
+
+	spin_unlock_irqrestore(&dev->irqlock, flags);
+	return IRQ_HANDLED;
+}
+
+int  r822_probe (struct pci_dev *pci_dev, const struct pci_device_id *id)
+{
+	int error;
+	struct nand_chip *chip;
+	struct r822_device *dev;
+
+	/* pci initialization */
+	if ((error = pci_enable_device(pci_dev)))
+		goto error1;
+
+	pci_set_master(pci_dev);
+
+	if ((error = pci_set_dma_mask(pci_dev, DMA_32BIT_MASK)))
+		goto error2;
+
+	if ((error = pci_request_regions(pci_dev, DRV_NAME)))
+		goto error3;
+
+	error = -ENOMEM;
+
+	/* init nand chip, but register it only on card insert */
+	if (!(chip = kzalloc(sizeof(struct nand_chip), GFP_KERNEL)))
+		goto error4;
+
+	/* commands */
+	chip->cmd_ctrl = r822_cmdctl;
+	chip->waitfunc = r822_wait;
+	chip->dev_ready = r822_ready;
+
+	/* I/O */
+	chip->read_byte = r822_read_byte;
+	chip->read_buf = r822_read_buf;
+	chip->write_buf = r822_write_buf;
+	chip->verify_buf = r822_verify_buf;
+
+	/* ecc */
+	chip->ecc.mode = NAND_ECC_HW_SYNDROME;
+	chip->ecc.size = R822_DMA_LEN;
+	chip->ecc.bytes = SM_OOB_SIZE;
+	chip->ecc.hwctl = r822_ecc_hwctl;
+	chip->ecc.calculate = r822_ecc_calculate;
+	chip->ecc.correct = r822_ecc_correct;
+
+	/* TODO: hack */
+	chip->ecc.read_oob = r822_read_oob;
+
+	/* init our device structure */
+	if (!(dev = kzalloc(sizeof(struct r822_device), GFP_KERNEL)))
+		goto error5;
+
+	chip->priv = dev;
+	dev->chip = chip;
+	dev->pci_dev = pci_dev;
+	pci_set_drvdata(pci_dev, dev);
+
+	dev->bounce_buffer = pci_alloc_consistent(pci_dev, R822_DMA_LEN,
+		&dev->phys_bounce_buffer);
+
+	if (!dev->bounce_buffer)
+		goto error6;
+
+	if (!(dev->mmio = pci_ioremap_bar (pci_dev, 0)))
+		goto error7;
+
+	if(!(dev->tmp_buffer = kzalloc(SM_SECTOR_SIZE, GFP_KERNEL)))
+		goto error8;
+
+	init_completion(&dev->dma_done);
+
+	if (!(dev->card_workqueue = create_freezeable_workqueue (DRV_NAME)))
+		goto error9;
+
+	INIT_DELAYED_WORK(&dev->card_detect_work, r822_card_detect_work);
+
+	/* shutdown everything - precation */
+	r822_device_shutdown(dev);
+	r822_disable_irqs(dev);
+	r822_dma_test(dev);
+
+	/*register irq handler*/
+	if (request_irq(pci_dev->irq, &r822_irq, IRQF_SHARED,
+			  DRV_NAME, dev))
+		goto error10;
+
+	dev->irq = pci_dev->irq;
+	spin_lock_init(&dev->irqlock);
+
+	/* kick initial present test */
+	dev->card_detected = 0;
+	r822_card_update_present(dev);
+	queue_delayed_work(dev->card_workqueue,
+		&dev->card_detect_work, 0);
+
+	/* Load the FTL */
+	request_module_nowait("sm_ftl");
+
+	printk(KERN_NOTICE DRV_NAME ": driver loaded succesfully\n");
+	return 0;
+
+
+error10:
+	destroy_workqueue(dev->card_workqueue);
+error9:
+	kfree(dev->tmp_buffer);
+error8:
+	pci_iounmap(pci_dev, dev->mmio);
+
+error7:
+	pci_free_consistent(pci_dev, R822_DMA_LEN,
+		dev->bounce_buffer, dev->phys_bounce_buffer);
+error6:
+	kfree(dev);
+error5:
+	kfree(chip);
+error4:
+	pci_release_regions(pci_dev);
+error3:
+error2:
+	pci_disable_device(pci_dev);
+error1:
+	return error;
+}
+
+
+void r822_remove (struct pci_dev *pci_dev)
+{
+	struct r822_device *dev = pci_get_drvdata(pci_dev);
+
+	/* Stop detect workqueue - we are going to unregister the device anyway*/
+	cancel_delayed_work_sync(&dev->card_detect_work);
+	destroy_workqueue(dev->card_workqueue);
+
+	/* Unregister the device, this might make more IO */
+	r822_unregister_nand_device(dev);
+
+	/* Stop interrupts */
+	r822_disable_irqs(dev);
+	synchronize_irq(dev->irq);
+	free_irq(dev->irq, dev);
+
+	/* Cleanup */
+	kfree(dev->tmp_buffer);
+	pci_iounmap(pci_dev, dev->mmio);
+	pci_free_consistent(pci_dev, R822_DMA_LEN,
+		dev->bounce_buffer, dev->phys_bounce_buffer);
+	kfree(dev);
+	kfree(dev->chip);
+
+	/* Shutdown the PCI device */
+	pci_release_regions(pci_dev);
+	pci_disable_device(pci_dev);
+}
+
+void r822_shutdown (struct pci_dev *pci_dev)
+{
+	struct r822_device *dev = pci_get_drvdata(pci_dev);
+
+	cancel_delayed_work_sync(&dev->card_detect_work);
+	r822_disable_irqs(dev);
+	pci_disable_device(pci_dev);
+}
+
+int r822_suspend (struct device *device)
+{
+	struct r822_device *dev = pci_get_drvdata(to_pci_dev(device));
+
+	if (dev->ctlreg & R822_CTL_CARDENABLE)
+		return -EBUSY;
+
+	cancel_delayed_work_sync(&dev->card_detect_work);
+	r822_disable_irqs(dev);
+	r822_device_shutdown(dev);
+
+	/* If card was pulled off just during the suspend, which is very
+		unlikely, we will remove it on resume, it too late now
+		anyway... */
+	dev->card_unstable = 0;
+
+	pci_save_state(to_pci_dev(device));
+	pci_set_power_state(to_pci_dev(device), PCI_D3cold);
+
+
+	return 0;
+}
+
+int r822_resume (struct device *device)
+{
+	struct r822_device *dev = pci_get_drvdata(to_pci_dev(device));
+
+	pci_set_power_state(to_pci_dev(device), PCI_D0);
+	pci_restore_state(to_pci_dev(device));
+
+
+	r822_disable_irqs(dev);
+	r822_card_update_present(dev);
+	r822_device_shutdown(dev);
+
+
+	/* If card status changed, just do the work */
+	if (dev->card_detected != dev->card_registred) {
+		dbg("card was %s during low power state",
+			dev->card_detected ? "added" : "removed");
+
+		queue_delayed_work(dev->card_workqueue,
+		&dev->card_detect_work, 0);
+		return 0;
+	}
+
+	/* Otherwise, initialize the card */
+	if (dev->card_registred) {
+		r822_device_start(dev);
+		dev->chip->select_chip(dev->mtd, 0);
+		dev->chip->cmdfunc(dev->mtd, NAND_CMD_RESET, -1, -1);
+		dev->chip->cmdfunc(dev->mtd, NAND_CMD_READ0, -1, -1);
+		dev->chip->select_chip(dev->mtd, -1);
+	}
+
+	/* And start card detection IRQ */
+	r822_update_card_detect(dev);
+
+	return 0;
+}
+
+static const struct pci_device_id r822_pci_id_tbl[] = {
+
+	{ PCI_VDEVICE(RICOH, PCI_DEVICE_ID_RICOH_RL5C852), },
+	{ },
+};
+
+MODULE_DEVICE_TABLE(pci, r822_pci_id_tbl);
+
+SIMPLE_DEV_PM_OPS(r822_pm_ops, r822_suspend, r822_resume);
+
+
+static struct pci_driver r822_pci_driver = {
+	.name		= DRV_NAME,
+	.id_table	= r822_pci_id_tbl,
+	.probe		= r822_probe,
+	.remove		= r822_remove,
+	.shutdown	= r822_shutdown,
+	.driver.pm	= &r822_pm_ops,
+};
+
+static __init int r822_module_init(void)
+{
+	return pci_register_driver(&r822_pci_driver);
+}
+
+static void __exit r822_module_exit(void)
+{
+	pci_unregister_driver(&r822_pci_driver);
+}
+
+module_init(r822_module_init);
+module_exit(r822_module_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Maxim Levitsky <maximlevitsky@gmail.com>");
+MODULE_DESCRIPTION("Ricoh 85xx xD/smartmedia card reader driver");
diff --git a/drivers/mtd/nand/r822.h b/drivers/mtd/nand/r822.h
new file mode 100644
index 0000000..5b69629
--- /dev/null
+++ b/drivers/mtd/nand/r822.h
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2009 - Maxim Levitsky
+ * driver for Ricoh xD readers
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/pci.h>
+#include <linux/completion.h>
+#include <linux/workqueue.h>
+#include <linux/mtd/nand.h>
+#include <linux/spinlock.h>
+
+
+/* nand interface + ecc
+   byte write/read does one cycle on nand data lines.
+   dword write/read does 4 cycles
+   if R822_CTL_ECC_ACCESS is set in R822_CTL, then dword read reads
+   results of ecc correction, if DMA read was done before.
+   If write was done two dword reads read generated ecc checksums
+*/
+#define	R822_DATALINE		0x00
+
+/* control register */
+#define R822_CTL			0x04
+#define R822_CTL_COMMAND 	0x01	/* send command (#CLE)*/
+#define R822_CTL_DATA		0x02	/* read/write data (#ALE)*/
+#define R822_CTL_ON		0x04	/* only seem to controls the hd led, */
+					/* but has to be set on start...*/
+#define R822_CTL_RESET		0x08	/* unknown, set only on start once*/
+#define R822_CTL_CARDENABLE	0x10	/* probably (#CE) - always set*/
+#define R822_CTL_ECC_ENABLE	0x20	/* enable ecc engine */
+#define R822_CTL_ECC_ACCESS	0x40	/* read/write ecc via reg #0*/
+#define R822_CTL_WRITE		0x80	/* set when performing writes (#WP) */
+
+
+/* card detection status */
+#define R822_CARD_STA		0x05
+#define R822_CARD_STA_UNUSED1	0x01	/* unknown */
+#define R822_CARD_STA_RO		0x02	/* card is readonly -- test on #WP???*/
+#define R822_CARD_STA_PRESENT	0x04	/* card is present (#CD) */
+#define R822_CARD_STA_BUSY	0x80	/* card is busy - (#R/B) */
+
+/* card detection irq status*/
+#define R822_CARD_IRQ_STA	0x06	/* IRQ status */
+
+/* card detection irq enable */
+#define R822_CARD_IRQ_ENABLE	0x07	/* IRQ enable */
+
+#define R822_CARD_IRQ_UNUSED1	0x01	/* unknown */
+#define R822_CARD_IRQ_REMOVE	0x04	/* detect card removal */
+#define R822_CARD_IRQ_INSERT	0x08	/* detect card insert */
+#define R822_CARD_IRQ_UNUSED2	0x10	/* unknown */
+#define R822_CARD_IRQ_GENABLE	0x80	/* general enable */
+#define R822_CARD_IRQ_MASK	0x1D
+
+
+/* hardware enable */
+#define R822_HW			0x08
+#define R822_HW_ENABLED		0x01	/* hw enabled */
+#define R822_HW_UNKNOWN		0x80
+
+
+/* dma capabilities */
+#define R822_DMA_CAP		0x09
+#define R822_SMBIT		0x20	/* if set with bit #6 or bit #7, then */
+					/* hw is smartmedia */
+#define R822_DMA1		0x40	/* if set with bit #7, dma is supported */
+#define R822_DMA2		0x80	/* if set with bit #6, dma is supported */
+
+
+/* physical DMA address - 32 bit value*/
+#define R822_DMA_ADDR		0x0C
+
+
+/* dma settings */
+#define R822_DMA_SETTINGS	0x10
+#define R822_DMA_MEMORY		0x01	/* do real dma (memory <-> internal hw buffer) */
+#define R822_DMA_READ		0x02	/* 0 = write, 1 = read */
+#define R822_DMA_INTERNAL	0x04	/* transfer internal buffer from/to card */
+
+/* dma IRQ status */
+#define R822_DMA_IRQ_STA		0x14
+
+/* dma IRQ enable */
+#define R822_DMA_IRQ_ENABLE	0x18
+
+#define R822_DMA_IRQ_MEMORY	0x01	/* real dma done (memory <-> internal hw buffer) */
+#define R822_DMA_IRQ_ERROR	0x02	/* error did happen */
+#define R822_DMA_IRQ_INTERNAL	0x04	/* internal buffer was tranferred from/to card*/
+#define R822_DMA_IRQ_MASK	0x07	/* mask of all IRQ bits*/
+
+
+/* ECC syndrome format - read from reg #0 will return two copies of these for
+   each half of the page.
+   first byte is error byte location, and second, bit location + flags */
+#define R822_ECC_ERR_BIT_MSK	0x07	/* error bit location */
+#define R822_ECC_CORRECT		0x10	/* no errors - (guessed) */
+#define R822_ECC_CORRECTABLE	0x20	/* correctable error exist */
+#define R822_ECC_FAIL		0x40	/* non correctable error detected */
+
+#define R822_DMA_LEN		512
+
+#define DMA_INTERNAL	0
+#define DMA_MEMORY	1
+
+struct r822_device {
+	void __iomem *mmio;		/* mmio */
+	struct mtd_info *mtd;		/* mtd backpointer */
+	struct nand_chip *chip;		/* nand chip backpointer */
+	struct pci_dev *pci_dev;	/* pci backpointer */
+
+	/* dma area */
+	dma_addr_t phys_dma_addr;	/* bus address of buffer*/
+	struct completion dma_done;	/* data transfer done */
+
+	dma_addr_t phys_bounce_buffer;	/* bus address of bounce buffer */
+	u8 *bounce_buffer;		/* virtual address of bounce buffer */
+
+	int dma_dir;			/* 1 = read, 0 = write */
+	int dma_stage;			/* 0 - idle, 1 - first step,
+					   2 - second step */
+
+	int dma_state;			/* 0 = internal, 1 = memory */
+	int dma_error;			/* dma errors */
+	int dma_usable;			/* is it possible to use dma */
+
+
+	/* card status area */
+	struct delayed_work card_detect_work;
+	struct workqueue_struct* card_workqueue;
+	int card_registred;		/* card registered with mtd */
+	int card_detected;		/* card detected in slot */
+	int card_unstable;		/* whenever the card is inserted,
+					   is not known yet */
+	int readonly;			/* card is readonly */
+
+	/* misc */
+	spinlock_t irqlock;		/* IRQ protecting lock */
+	void *tmp_buffer;		/* temporary buffer */
+	u8 ctlreg;			/* cached contents of control reg */
+	int irq;			/* irq num */
+};
+
+#define DRV_NAME "r822xd"
+
+
+/* this will go to pci_ids.h */
+#define PCI_DEVICE_ID_RICOH_RL5C852	0x0852
+
+
+#define dbg(format, ...) \
+	printk (KERN_ERR DRV_NAME ": " format "\n", ## __VA_ARGS__)
-- 
1.6.3.3

^ permalink raw reply related	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2010-01-06 21:54 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2010-01-06 21:43 RFC: [PATCH 0/9 Integration of SmartMedia/xD into mtd subsystem Maxim Levitsky
2010-01-06 21:44 ` [PATCH 1/9] MTD: call remove notifiers before removing the device Maxim Levitsky
2010-01-06 21:45 ` [PATCH 2/9] MTD: create lockless versions of {get,put}_mtd_device Maxim Levitsky
2010-01-06 21:46 ` [PATCH 3/9] MTD: blkdevs: major cleanups Maxim Levitsky
2010-01-06 21:48 ` [PATCH 4/9] mtd-make mtdtrans thread suspend friendly Maxim Levitsky
2010-01-06 21:49 ` [PATCH 5/9] NAND: export nand_do_read_oob and nand_do_write_oob Maxim Levitsky
2010-01-06 21:51 ` [PATCH 6/9] mtd: common module for smartmedia/xD support Maxim Levitsky
2010-01-06 21:52 ` [PATCH 7/9] NAND: add few workarounds for SmartMedia/xD chips Maxim Levitsky
2010-01-06 21:53 ` [PATCH 8/9] mtd: SmartMedia/xD FTL Maxim Levitsky
2010-01-06 21:54 ` [PATCH 9/9] mtd: add nand driver for ricoh xD/SmartMedia reader Maxim Levitsky

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox