/* Flash mapping for Bifferboard, (c) bifferos@yahoo.co.uk 
 * Works with 1, 4 and 8MB flash versions
 */

#include <linux/delay.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/map.h>
#include <linux/mtd/partitions.h>

#define DRV "bifferboard-flash: "

static struct mtd_info *bb_mtd = NULL;

#define SIZE_BIFFBOOT 0x10000
#define SIZE_SECTOR   0x10000
#define SIZE_CONFIG   0x02000
#define SIZE_1MB 0x00100000
#define SIZE_4MB 0x00400000
#define SIZE_8MB 0x00800000

#define BASE_1MB 0xfff00000
#define BASE_4MB 0xffc00000
#define BASE_8MB 0xff800000

#define OFFS_KERNEL 0x6000
#define OFFS_CONFIG 0x4000

/*
 * Flash detection code.  This is needed because the cfi_probe doesn't probe
 * for the size correctly.  You actually have to know the flash size before you
 * call it, or so it seems.  It does work correctly if you map the entire flash
 * chip, but unsure of the implications for mapping more mem than exists.
 */

static unsigned char ReadFlash(void* base, u32 addr)
{
	unsigned char val = *(volatile unsigned char *)(base+addr);  
	udelay(1);
	return val;
}

static void WriteFlash(void* base, u32 addr, unsigned short data)
{
	*(volatile unsigned short *)(base+addr) = data;
}

static DEFINE_SPINLOCK(flash_lock);

static u32 bb_detect(void)
{
	u32 ret = 0;  
	ulong flags;
  
	/* for detection, map in just the 1MB device, 1st 64k is enough */
	void* base = ioremap_nocache(BASE_1MB, 0x10000);
    
	if (!base)
	{
		pr_err(DRV "Failed to map flash for probing\n");
		return 0;
	}
  
	spin_lock_irqsave(&flash_lock, flags);  
  
	/* put flash in auto-detect mode */
	WriteFlash(base, 0xaaaa, 0xaaaa);
	WriteFlash(base, 0x5554, 0x5555);
	WriteFlash(base, 0xaaaa, 0x9090);

	/* Read the auto-config data - 4 values in total */
	ret = ReadFlash(base, 0x0000);  ret <<= 8;
	ret |= ReadFlash(base, 0x0200);  ret <<= 8;
	ret |= ReadFlash(base, 0x0003);  ret <<= 8;
	ret |= ReadFlash(base, 0x0002);
  
	/* exit the autodetect state */
	WriteFlash(base, 0x0000, 0xf0f0);
  
	spin_unlock_irqrestore(&flash_lock, flags);
  
	/* unmap it, it'll be re-mapped based on the detection */
	iounmap(base);
  
	return ret;
}


static struct map_info bb_map;

/* Update the map, using the detected flash signature. */
static int bb_adjust_map(unsigned long sig, struct map_info* m)
{
	m->bankwidth = 2;
	switch (sig)
	{
		case 0x7f1c225b :
			m->name = "ENLV800B";
			m->phys = BASE_1MB;
			m->size = SIZE_1MB;
			break;
		case 0x7f1c22f9 :
			m->name = "ENLV320B";
			m->phys = BASE_4MB;
			m->size = SIZE_4MB;
			break;
		case 0x7f1c22cb :
			m->name = "ENLV640B";
			m->phys = BASE_8MB;
			m->size = SIZE_8MB;
			break;
		default:
			return -ENXIO;
	}
	return 0;
}


/* adjust partition sizes, prior to adding the partition
 * Values not specified by defines here are subject to replacement based on 
 * the real flash size. (0x00000000 being the exception, of course) */

static struct mtd_partition bb_parts[] = 
{
/* 0 */	{ name: "kernel",   offset: 0x00000000,         size: 0x000fa000 },
/* 1 */	{ name: "rootfs",   offset: MTDPART_OFS_APPEND, size: 0x002F0000 },
/* 2 */	{ name: "biffboot", offset: MTDPART_OFS_APPEND, size: MTDPART_SIZ_FULL, mask_flags: MTD_WRITEABLE },
};


/* returns the count of partitions to be used */
static ulong bb_adjust_partitions(struct map_info* m)
{
	/* Read the kernel offset value, 1036 bytes into the config block. */
	u16 km_units = *((u16*)(m->virt + OFFS_CONFIG + 1036));
	u32 kernelmax = 0x200000;  /* Biffboot 2.7 or earlier default */
	
	/* special-case 1MB devices, because there are fewer partitions */
	if (m->size == SIZE_1MB)
	{
		bb_parts[0].size   = SIZE_1MB - SIZE_BIFFBOOT;	/* kernel */
		bb_parts[1].name   = "biffboot";		/* biffboot */
		bb_parts[1].offset = MTDPART_OFS_APPEND;	/* biffboot */
		bb_parts[1].size   = MTDPART_SIZ_FULL;		/* biffboot */
		bb_parts[1].mask_flags = MTD_WRITEABLE;
		return 2;   /* because there's no rootfs now */
	}
	
	/* sanity check */
	if (km_units > ((m->size-SIZE_BIFFBOOT)/SIZE_SECTOR))
	{
		pr_err(DRV "config block has invalid kernelmax\n");
		return 0;
	}
	
	kernelmax = km_units * SIZE_SECTOR;
	
	/* Kernel */
	bb_parts[0].size = kernelmax;
	
	/* rootfs */
	bb_parts[1].size   = m->size - kernelmax - SIZE_BIFFBOOT;
		
	return 3;  /* 3 partitions */
}


static int __init init_bb_map(void)
{
	int ret;
	ulong signature = bb_detect();
	ulong part_len;
	
	if (!signature)
	{
		pr_err(DRV "Undetected flash chip");
		return -ENXIO;
	}
	
	ret = bb_adjust_map(signature, &bb_map);
	
	if (ret)
	{
		pr_err(DRV "Unrecognised flash chip (signature: 0x%lx)\n", signature);
		return ret;
	}
	
	bb_map.virt = ioremap_nocache(bb_map.phys, bb_map.size);
	if (!bb_map.virt) 
	{
		pr_err(DRV "ioremap\n");
		return -EIO;
	}
	
	bb_mtd = do_map_probe("cfi_probe", &bb_map);
	if (!bb_mtd)
	{
		/* cfi_probe fails here for 1MB devices */
		pr_err(DRV "cfi_probe\n");
		ret = -ENXIO;
		goto err;
	}

	part_len = bb_adjust_partitions(&bb_map);
	if (!part_len)
	{
		pr_err(DRV "no partitions established");
		ret = -ENXIO;
		goto err2;
	}
	
	bb_mtd->owner = THIS_MODULE;
	ret = add_mtd_partitions(bb_mtd, bb_parts, part_len);
	if (ret) 
	{
		pr_err(DRV "add_mtd_partitions\n");
		ret = -ENXIO;
		goto err2;
	}
	
	return 0;
	
err2:
	map_destroy(bb_mtd);
err:
	iounmap(bb_map.virt);

	return ret;
}

static void __exit exit_bb_map(void)
{
	del_mtd_partitions(bb_mtd);
	map_destroy(bb_mtd);	
	iounmap(bb_map.virt);
}

module_init(init_bb_map);
module_exit(exit_bb_map);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Bifferos <bifferos@yahoo.co.uk>");
MODULE_DESCRIPTION("MTD map driver for Bifferboard");