/*
* inftlcore.c -- Linux driver for Inverse Flash Translation Layer (INFTL)
*
* Copyright © 2002, Greg Ungerer (gerg@snapgear.com)
*
* Based heavily on the nftlcore.c code which is:
* Copyright © 1999 Machine Vision Holdings, Inc.
* Copyright © 1999 David Woodhouse <dwmw2@infradead.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/kmod.h>
#include <linux/hdreg.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/nftl.h>
#include <linux/mtd/inftl.h>
#include <linux/mtd/nand.h>
#include <asm/uaccess.h>
#include <asm/errno.h>
#include <asm/io.h>
/*
* Maximum number of loops while examining next block, to have a
* chance to detect consistency problems (they should never happen
* because of the checks done in the mounting.
*/
#define MAX_LOOPS 10000
static void inftl_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
{
struct INFTLrecord *inftl;
unsigned long temp;
if (!mtd_type_is_nand(mtd) || mtd->size > UINT_MAX)
return;
/* OK, this is moderately ugly. But probably safe. Alternatives? */
if (memcmp(mtd->name, "DiskOnChip", 10))
return;
if (!mtd->_block_isbad) {
printk(KERN_ERR
"INFTL no longer supports the old DiskOnChip drivers loaded via docprobe.\n"
"Please use the new diskonchip driver under the NAND subsystem.\n");
return;
}
pr_debug("INFTL: add_mtd for %s\n", mtd->name);
inftl = kzalloc(sizeof(*inftl), GFP_KERNEL);
if (!inftl)
return;
inftl->mbd.mtd = mtd;
inftl->mbd.devnum = -1;
inftl->mbd.tr = tr;
if (INFTL_mount(inftl) < 0) {
printk(KERN_WARNING "INFTL: could not mount device\n");
kfree(inftl);
return;
}
/* OK, it's a new one. Set up all the data structures. */
/* Calculate geometry */
inftl->cylinders = 1024;
inftl->heads = 16;
temp = inftl->cylinders * inftl->heads;
inftl->sectors = inftl->mbd.size / temp;
if (inftl->mbd.size % temp) {
inftl->sectors++;
temp = inftl->cylinders * inftl->sectors;
inftl->heads = inftl->mbd.size / temp;
if (inftl->mbd.size % temp) {
inftl->heads++;
temp = inftl->heads * inftl->sectors;
inftl->cylinders = inftl->mbd.size / temp;
}
}
if (inftl->mbd.size != inftl->heads * inftl->cylinders * inftl->sectors) {
/*
Oh no we don't have
mbd.size == heads * cylinders * sectors
*/
printk(KERN_WARNING "INFTL: cannot calculate a geometry to "
"match size of 0x%lx.\n", inftl->mbd.size);
printk(KERN_WARNING "INFTL: using C:%d H:%d S:%d "
"(== 0x%lx sects)\n",
inftl->cylinders, inftl->heads , inftl->sectors,
(long)inftl->cylinders * (long)inftl->heads *
(long)inftl->sectors );
}
if (add_mtd_blktrans_dev(&inftl->mbd)) {
kfree(inftl->PUtable);
kfree(inftl->VUtable);
kfree(inftl);
return;
}
#ifdef PSYCHO_DEBUG
printk(KERN_INFO "INFTL: Found new inftl%c\n", inftl->mbd.devnum + 'a');
#endif
return;
}
static void inftl_remove_dev(struct mtd_blktrans_dev *dev)
{
struct INFTLrecord *inftl = (void *)dev;
pr_debug("INFTL: remove_dev (i=%d)\n", dev->devnum);
del_mtd_blktrans_dev(dev);
kfree(inftl->PUtable);
kfree(inftl->VUtable);
}
/*
* Actual INFTL access routines.
*/
/*
* Read oob data from flash
*/
int inftl_read_oob(struct mtd_info *mtd, loff_t offs, size_t len,
size_t *retlen, uint8_t *buf)
{
struct mtd_oob_ops ops;
int res;
ops.mode = MTD_OPS_PLACE_OOB;
ops.ooboffs = offs & (mtd->writesize - 1);
ops.ooblen = len;
ops.oobbuf = buf;
ops.datbuf = NULL;
res = mtd_read_oob(mtd, offs & ~(mtd->writesize - 1), &ops);
*retlen = ops.oobretlen;
return res;
}