diff mbox

[9/9] mtd: Add new SmartMedia/xD FTL

Message ID 1262963524.12577.23.camel@maxim-laptop
State New, archived
Headers show

Commit Message

Maxim Levitsky Jan. 8, 2010, 3:12 p.m. UTC
>From 743f723b6e7134cf9d99a158f3ac920c180d406a Mon Sep 17 00:00:00 2001
From: Maxim Levitsky <maximlevitsky@gmail.com>
Date: Fri, 8 Jan 2010 16:44:07 +0200
Subject: [PATCH 9/9] mtd: Add new SmartMedia/xD FTL

This implements new readwrite SmartMedia/xd FTL.

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

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

Signed-off-by: Maxim Levitsky <maximlevitsky@gmail.com>
---
 drivers/mtd/Kconfig  |   12 +
 drivers/mtd/Makefile |    1 +
 drivers/mtd/sm_ftl.c | 1043 ++++++++++++++++++++++++++++++++++++++++++++++++++
 drivers/mtd/sm_ftl.h |   75 ++++
 4 files changed, 1131 insertions(+), 0 deletions(-)
 create mode 100644 drivers/mtd/sm_ftl.c
 create mode 100644 drivers/mtd/sm_ftl.h

Comments

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

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

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

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

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

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

Best regards,
Maxim Levitsky
diff mbox

Patch

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