/*
 * MTD chip driver for pre-CFI Sharp flash chips
 *
 * Copyright 2000,2001 David A. Schleef <ds@schleef.org>
 *           2000,2001 Lineo, Inc.
 *
 * $Id: sharp.c,v 1.1.1.1 2002/03/18 15:11:54 jharrell Exp $
 *
 * Devices supported:
 *   LH28F016SCT Symmetrical block flash memory, 2Mx8
 *   LH28F008SCT Symmetrical block flash memory, 1Mx8
 *
 * Documentation:
 *   http://www.sharpmeg.com/datasheets/memic/flashcmp/
 *   http://www.sharpmeg.com/datasheets/memic/flashcmp/01symf/16m/016sctl9.pdf
 *   016sctl9.pdf
 *
 * Limitations:
 *   This driver only supports 4x1 arrangement of chips.
 *   Not tested on anything but PowerPC.
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/version.h>
#include <linux/types.h>
#include <linux/sched.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/mtd/map.h>
#include <linux/mtd/cfi.h>
#include <linux/delay.h>

#define CMD_RESET		0xffffffff
#define CMD_READ_ID		0x90909090
#define CMD_READ_STATUS		0x70707070
#define CMD_CLEAR_STATUS	0x50505050
#define CMD_BLOCK_ERASE_1	0x20202020
#define CMD_BLOCK_ERASE_2	0xd0d0d0d0
#define CMD_BYTE_WRITE		0x40404040
#define CMD_SUSPEND		0xb0b0b0b0
#define CMD_RESUME		0xd0d0d0d0
#define CMD_SET_BLOCK_LOCK_1	0x60606060
#define CMD_SET_BLOCK_LOCK_2	0x01010101
#define CMD_SET_MASTER_LOCK_1	0x60606060
#define CMD_SET_MASTER_LOCK_2	0xf1f1f1f1
#define CMD_CLEAR_BLOCK_LOCKS_1	0x60606060
#define CMD_CLEAR_BLOCK_LOCKS_2	0xd0d0d0d0

#define SR_READY		0x80808080 // 1 = ready
#define SR_ERASE_SUSPEND	0x40404040 // 1 = block erase suspended
#define SR_ERROR_ERASE		0x20202020 // 1 = error in block erase or clear lock bits
#define SR_ERROR_WRITE		0x10101010 // 1 = error in byte write or set lock bit
#define	SR_VPP			0x08080808 // 1 = Vpp is low
#define SR_WRITE_SUSPEND	0x04040404 // 1 = byte write suspended
#define SR_PROTECT		0x02020202 // 1 = lock bit set
#define SR_RESERVED		0x01010101

#define SR_ERRORS (SR_ERROR_ERASE|SR_ERROR_WRITE|SR_VPP|SR_PROTECT)

/* Configuration options */

#undef AUTOUNLOCK  /* automatically unlocks blocks before erasing */

struct mtd_info *sharp_probe(struct map_info *);

static int sharp_probe_map(struct map_info *map,struct mtd_info *mtd);

static int sharp_read(struct mtd_info *mtd, loff_t from, size_t len,
	size_t *retlen, u_char *buf);
static int sharp_write(struct mtd_info *mtd, loff_t from, size_t len,
	size_t *retlen, const u_char *buf);
static int sharp_erase(struct mtd_info *mtd, struct erase_info *instr);
static void sharp_sync(struct mtd_info *mtd);
static int sharp_suspend(struct mtd_info *mtd);
static void sharp_resume(struct mtd_info *mtd);
static void sharp_destroy(struct mtd_info *mtd);

static int sharp_write_oneword(struct map_info *map, struct flchip *chip,
	unsigned long adr, __u32 datum);
static int sharp_erase_oneblock(struct map_info *map, struct flchip *chip,
	unsigned long adr);
#ifdef AUTOUNLOCK
static void sharp_unlock_oneblock(struct map_info *map, struct flchip *chip,
	unsigned long adr);
#endif


struct sharp_info{
	struct flchip *chip;
	int bogus;
	int chipshift;
	int numchips;
	struct flchip chips[1];
};

struct mtd_info *sharp_probe(struct map_info *map);
static void sharp_destroy(struct mtd_info *mtd);

static struct mtd_chip_driver sharp_chipdrv = {
	probe: sharp_probe,
	destroy: sharp_destroy,
	name: "sharp",
	module: THIS_MODULE
};


struct mtd_info *sharp_probe(struct map_info *map)
{
	struct mtd_info *mtd = NULL;
	struct sharp_info *sharp = NULL;
	int width;

	mtd = kmalloc(sizeof(*mtd), GFP_KERNEL);
	if(!mtd)
		return NULL;

	sharp = kmalloc(sizeof(*sharp), GFP_KERNEL);
	if(!sharp)
		return NULL;

	memset(mtd, 0, sizeof(*mtd));

	width = sharp_probe_map(map,mtd);
	if(!width){
		kfree(mtd);
		kfree(sharp);
		return NULL;
	}

	mtd->priv = map;
	mtd->type = MTD_NORFLASH;
	mtd->erase = sharp_erase;
	mtd->read = sharp_read;
	mtd->write = sharp_write;
	mtd->sync = sharp_sync;
	mtd->suspend = sharp_suspend;
	mtd->resume = sharp_resume;
	mtd->flags = MTD_CAP_NORFLASH;
	mtd->name = map->name;

	memset(sharp, 0, sizeof(*sharp));
	sharp->chipshift = 23;
	sharp->numchips = 1;
	sharp->chips[0].start = 0;
	sharp->chips[0].state = FL_READY;
	sharp->chips[0].mutex = &sharp->chips[0]._spinlock;
	sharp->chips[0].word_write_time = 0;
	init_waitqueue_head(&sharp->chips[0].wq);
	spin_lock_init(&sharp->chips[0]._spinlock);

	map->fldrv = &sharp_chipdrv;
	map->fldrv_priv = sharp;

	MOD_INC_USE_COUNT;
	return mtd;
}

static int sharp_probe_map(struct map_info *map,struct mtd_info *mtd)
{
	unsigned long tmp;
	unsigned long base = 0;
	u32 read0, read4;
	int width = 4;

	tmp = map->read32(map, base+0);

	map->write32(map, CMD_READ_ID, base+0);

	read0=map->read32(map, base+0);
	read4=map->read32(map, base+4);
	if(read0 == 0x89898989){
		printk("Looks like sharp flash\n");
		switch(read4){
		case 0xaaaaaaaa:
		case 0xa0a0a0a0:
			/* aa - LH28F016SCT-L95 2Mx8, 32 64k blocks*/
			/* a0 - LH28F016SCT-Z4  2Mx8, 32 64k blocks*/
			mtd->erasesize = 0x10000 * width;
			mtd->size = 0x200000 * width;
			return width;
		case 0xa6a6a6a6:
			/* a6 - LH28F008SCT-L12 1Mx8, 16 64k blocks*/
			/* a6 - LH28F008SCR-L85 1Mx8, 16 64k blocks*/
			mtd->erasesize = 0x10000 * width;
			mtd->size = 0x100000 * width;
			return width;
#if 0
		case 0x00000000: /* unknown */
			/* XX - LH28F004SCT 512kx8, 8 64k blocks*/
			mtd->erasesize = 0x10000 * width;
			mtd->size = 0x80000 * width;
			return width;
#endif
		default:
			printk("Sort-of looks like sharp flash, 0x%08x 0x%08x\n",
				read0,read4);
		}
	}else if((map->read32(map, base+0) == CMD_READ_ID)){
		/* RAM, probably */
		printk("Looks like RAM\n");
		map->write32(map, tmp, base+0);
	}else{
		printk("Doesn't look like sharp flash, 0x%08x 0x%08x\n",
			read0,read4);
	}

	return 0;
}

/* This function returns with the chip->mutex lock held. */
static int sharp_wait(struct map_info *map, struct flchip *chip)
{
	__u16 status;
	unsigned long timeo = jiffies + HZ;
	DECLARE_WAITQUEUE(wait, current);
	int adr = 0;

retry:
	spin_lock_bh(chip->mutex);

	switch(chip->state){
	case FL_READY:
		map->write32(map,CMD_READ_STATUS,adr);
		chip->state = FL_STATUS;
	case FL_STATUS:
		status = map->read32(map,adr);
//printk("status=%08x\n",status);

		udelay(100);
		if((status & SR_READY)!=SR_READY){
//printk(".status=%08x\n",status);
			udelay(100);
		}
		break;
	default:
		printk("Waiting for chip\n");

		set_current_state(TASK_INTERRUPTIBLE);
		add_wait_queue(&chip->wq, &wait);

		spin_unlock_bh(chip->mutex);

		schedule();
		remove_wait_queue(&chip->wq, &wait);

		if(signal_pending(current))
			return -EINTR;

		timeo = jiffies + HZ;

		goto retry;
	}

	map->write32(map,CMD_RESET, adr);

	chip->state = FL_READY;

	return 0;
}

static void sharp_release(struct flchip *chip)
{
	wake_up(&chip->wq);
	spin_unlock_bh(chip->mutex);
}

static int sharp_read(struct mtd_info *mtd, loff_t from, size_t len,
	size_t *retlen, u_char *buf)
{
	struct map_info *map = mtd->priv;
	struct sharp_info *sharp = map->fldrv_priv;
	int chipnum;
	int ret = 0;
	int ofs = 0;

	chipnum = (from >> sharp->chipshift);
	ofs = from & ((1 << sharp->chipshift)-1);

	*retlen = 0;

	while(len){
		unsigned long thislen;

		if(chipnum>=sharp->numchips)
			break;

		thislen = len;
		if(ofs+thislen >= (1<<sharp->chipshift))
			thislen = (1<<sharp->chipshift) - ofs;

		ret = sharp_wait(map,&sharp->chips[chipnum]);
		if(ret<0)
			break;

		map->copy_from(map,buf,ofs,thislen);

		sharp_release(&sharp->chips[chipnum]);

		*retlen += thislen;
		len -= thislen;
		buf += thislen;

		ofs = 0;
		chipnum++;
	}
	return ret;
}

static int sharp_write(struct mtd_info *mtd, loff_t to, size_t len,
	size_t *retlen, const u_char *buf)
{
	struct map_info *map = mtd->priv;
	struct sharp_info *sharp = map->fldrv_priv;
	int ret = 0;
	int i,j;
	int chipnum;
	unsigned long ofs;
	union { u32 l; unsigned char uc[4]; } tbuf;

	*retlen = 0;

	while(len){
		tbuf.l = 0xffffffff;
		chipnum = to >> sharp->chipshift;
		ofs = to & ((1<<sharp->chipshift)-1);

		j=0;
		for(i=ofs&3;i<4 && len;i++){
			tbuf.uc[i] = *buf;
			buf++;
			to++;
			len--;
			j++;
		}
		sharp_write_oneword(map, &sharp->chips[chipnum], ofs&~3, tbuf.l);
		if(ret<0)
			return ret;
		(*retlen)+=j;
	}

	return 0;
}

static int sharp_write_oneword(struct map_info *map, struct flchip *chip,
	unsigned long adr, __u32 datum)
{
	int ret;
	int timeo;
	int try;
	int i;
	int status = 0;

	ret = sharp_wait(map,chip);

	for(try=0;try<10;try++){
		map->write32(map,CMD_BYTE_WRITE,adr);
		/* cpu_to_le32 -> hack to fix the writel be->le conversion */
		map->write32(map,cpu_to_le32(datum),adr);

		chip->state = FL_WRITING;

		timeo = jiffies + (HZ/2);

		map->write32(map,CMD_READ_STATUS,adr);
		for(i=0;i<100;i++){
			status = map->read32(map,adr);
			if((status & SR_READY)==SR_READY)
				break;
		}
		if(i==100){
			printk("sharp: timed out writing\n");
		}

		if(!(status&SR_ERRORS))
			break;

		printk("sharp: error writing byte at addr=%08lx status=%08x\n",adr,status);

		map->write32(map,CMD_CLEAR_STATUS,adr);
	}
	map->write32(map,CMD_RESET,adr);
	chip->state = FL_READY;

	wake_up(&chip->wq);
	spin_unlock_bh(chip->mutex);

	return 0;
}

static int sharp_erase(struct mtd_info *mtd, struct erase_info *instr)
{
	struct map_info *map = mtd->priv;
	struct sharp_info *sharp = map->fldrv_priv;
	unsigned long adr,len;
	int chipnum, ret=0;

//printk("sharp_erase()\n");
	if(instr->addr & (mtd->erasesize - 1))
		return -EINVAL;
	if(instr->len & (mtd->erasesize - 1))
		return -EINVAL;
	if(instr->len + instr->addr > mtd->size)
		return -EINVAL;

	chipnum = instr->addr >> sharp->chipshift;
	adr = instr->addr & ((1<<sharp->chipshift)-1);
	len = instr->len;

	while(len){
		ret = sharp_erase_oneblock(map, &sharp->chips[chipnum], adr);
		if(ret)return ret;

		adr += mtd->erasesize;
		len -= mtd->erasesize;
		if(adr >> sharp->chipshift){
			adr = 0;
			chipnum++;
			if(chipnum>=sharp->numchips)
				break;
		}
	}

	if(instr->callback)
		instr->callback(instr);

	return 0;
}

static int sharp_do_wait_for_ready(struct map_info *map, struct flchip *chip,
	unsigned long adr)
{
	int ret;
	int timeo;
	int status;
	DECLARE_WAITQUEUE(wait, current);

	map->write32(map,CMD_READ_STATUS,adr);
	status = map->read32(map,adr);

	timeo = jiffies + HZ;

	while(jiffies<timeo){
		map->write32(map,CMD_READ_STATUS,adr);
		status = map->read32(map,adr);
		if((status & SR_READY)==SR_READY){
			ret = 0;
			goto out;
		}
		set_current_state(TASK_INTERRUPTIBLE);
		add_wait_queue(&chip->wq, &wait);

		//spin_unlock_bh(chip->mutex);

		schedule_timeout(1);
		schedule();
		remove_wait_queue(&chip->wq, &wait);

		//spin_lock_bh(chip->mutex);
		
		if (signal_pending(current)){
			ret = -EINTR;
			goto out;
		}
		
	}
	ret = -ETIME;
out:
	return ret;
}

static int sharp_erase_oneblock(struct map_info *map, struct flchip *chip,
	unsigned long adr)
{
	int ret;
	//int timeo;
	int status;
	//int i;

//printk("sharp_erase_oneblock()\n");

#ifdef AUTOUNLOCK
	/* This seems like a good place to do an unlock */
	sharp_unlock_oneblock(map,chip,adr);
#endif

	map->write32(map,CMD_BLOCK_ERASE_1,adr);
	map->write32(map,CMD_BLOCK_ERASE_2,adr);

	chip->state = FL_ERASING;

	ret = sharp_do_wait_for_ready(map,chip,adr);
	if(ret<0)return ret;

	map->write32(map,CMD_READ_STATUS,adr);
	status = map->read32(map,adr);

	if(!(status&SR_ERRORS)){
		map->write32(map,CMD_RESET,adr);
		chip->state = FL_READY;
		//spin_unlock_bh(chip->mutex);
		return 0;
	}

	printk("sharp: error erasing block at addr=%08lx status=%08x\n",adr,status);
	map->write32(map,CMD_CLEAR_STATUS,adr);

	//spin_unlock_bh(chip->mutex);

	return -EIO;
}

#ifdef AUTOUNLOCK
static void sharp_unlock_oneblock(struct map_info *map, struct flchip *chip,
	unsigned long adr)
{
	int i;
	int status;

	map->write32(map,CMD_CLEAR_BLOCK_LOCKS_1,adr);
	map->write32(map,CMD_CLEAR_BLOCK_LOCKS_2,adr);

	udelay(100);

	status = map->read32(map,adr);
	printk("status=%08x\n",status);

	for(i=0;i<1000;i++){
		//map->write32(map,CMD_READ_STATUS,adr);
		status = map->read32(map,adr);
		if((status & SR_READY)==SR_READY)
			break;
		udelay(100);
	}
	if(i==1000){
		printk("sharp: timed out unlocking block\n");
	}

	if(!(status&SR_ERRORS)){
		map->write32(map,CMD_RESET,adr);
		chip->state = FL_READY;
		return;
	}

	printk("sharp: error unlocking block at addr=%08lx status=%08x\n",adr,status);
	map->write32(map,CMD_CLEAR_STATUS,adr);
}
#endif

static void sharp_sync(struct mtd_info *mtd)
{
	//printk("sharp_sync()\n");
}

static int sharp_suspend(struct mtd_info *mtd)
{
	printk("sharp_suspend()\n");
	return -EINVAL;
}

static void sharp_resume(struct mtd_info *mtd)
{
	printk("sharp_resume()\n");
	
}

static void sharp_destroy(struct mtd_info *mtd)
{
	printk("sharp_destroy()\n");

}

int __init sharp_probe_init(void)
{
	printk("MTD Sharp chip driver <ds@lineo.com>\n");

	register_mtd_chip_driver(&sharp_chipdrv);

	return 0;
}

static void __exit sharp_probe_exit(void)
{
	unregister_mtd_chip_driver(&sharp_chipdrv);
}

module_init(sharp_probe_init);
module_exit(sharp_probe_exit);


MODULE_LICENSE("GPL");
MODULE_AUTHOR("David Schleef <ds@schleef.org>");
MODULE_DESCRIPTION("Old MTD chip driver for pre-CFI Sharp flash chips");
