/*======================================================================

    drivers/mtd/armflash.c: ARM Flash Layout/Partitioning
  
    Copyright (C) 2000 ARM Limited
 
   $Id: armflash.c,v 1.2 2001/03/26 17:47:27 dwmw2 Exp $
 
   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
  
   This is access code for flashes using ARM's flash partitioning 
   standards.

   NOTES:  The write stuff is completely board specific and I don't think
   that this is the right thing to do.	 Better would be to use the
   CFI stuff.

======================================================================*/


#include <linux/config.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/ptrace.h>
#include <linux/malloc.h>
#include <linux/string.h>
#include <linux/timer.h>
#include <linux/major.h>
#include <linux/fs.h>
#include <linux/ioctl.h>
#include <linux/delay.h>
#include <linux/init.h>

#include <linux/mtd/mtd.h>
#include <linux/mtd/iflash.h>
#include <linux/mtd/map.h>

#ifdef CONFIG_ARCH_INTEGRATOR
#include <asm/arch/bits.h>
#include <asm/arch/sizes.h>
#include <asm/arch/platform.h>
#endif

#include <asm/hardware.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>

#define AFS_DEBUG 0
#define AFS_DEBUG_RW 0

/* data structures */
struct footer_struct {
	void *infoBase;			/* Address of first word of ImageFooter  */
	char *blockBase;		/* Start of area reserved by this footer */
	unsigned int signature;		/* 'Magic' number proves it's a footer   */
	unsigned int type;		/* Area type: ARM Image, SIB, customer   */
	unsigned int checksum;		/* Just this structure                   */
};

struct image_info_struct {
	unsigned int bootFlags;		/* Boot flags, compression etc.          */
	unsigned int imageNumber;	/* Unique number, selects for boot etc.  */
	char *loadAddress;		/* Address program should be loaded to   */
	unsigned int length;		/* Actual size of image                  */
	unsigned int address;		/* Image is executed from here           */
	char name[16];			/* Null terminated                       */
	char *headerBase;		/* Flash Address of any stripped header  */
	unsigned int header_length;	/* Length of header in memory            */
	unsigned int headerType;	/* AIF, RLF, s-record etc.               */
	unsigned int checksum;		/* Image checksum (inc. this struct)     */
};

struct afs_info {
	struct afs_info *next;		/* next in list                          */
	struct mtd_info *mtd;		/* Owning mtd_info block                 */
	struct mtd_info *dev_mtd;	/* Device MTD                            */
	u_int base;			/* offset                                */
	char *name;			/* Name of thing (set by AFU)            */
};

// board specific stuff - sorry, it should be in arch/arm/mach-*.
#ifdef CONFIG_ARCH_INTEGRATOR

#define FLASH_BASE	INTEGRATOR_FLASH_BASE
#define FLASH_SIZE	INTEGRATOR_FLASH_SIZE

#define FLASH_PART_SIZE 0x400000

static void afs_flash_unlock(void)
{
	volatile unsigned int *sc_ctrlc = (unsigned int *) (IO_ADDRESS(INTEGRATOR_SC_BASE) +
					     INTEGRATOR_SC_CTRLC_OFFSET);
	volatile unsigned int *ebi_csr1 = (unsigned int *) (IO_ADDRESS(INTEGRATOR_EBI_BASE) +
					     INTEGRATOR_EBI_CSR1_OFFSET);
#if AFS_DEBUG
	printk("AFS Flash: Debug: unlocking flash\n");
#endif

	// Set the write enable bit in system controller EBI register.
	*ebi_csr1 |= INTEGRATOR_EBI_WRITE_ENABLE;
	if (!(*ebi_csr1 & INTEGRATOR_EBI_WRITE_ENABLE)) {
		volatile unsigned int *ebi_lock = (unsigned int *) (IO_ADDRESS(INTEGRATOR_EBI_BASE) +
					     INTEGRATOR_EBI_LOCK_OFFSET);

		*ebi_lock = 0xA05F;
		*ebi_csr1 |= INTEGRATOR_EBI_WRITE_ENABLE;
		*ebi_lock = 0;
	}
	// Set the Flash programming bits in the system controller.
	*sc_ctrlc |= INTEGRATOR_SC_CTRL_nFLVPPEN | INTEGRATOR_SC_CTRL_nFLWP;
}

static void afs_flash_lock(void)
{
	volatile unsigned int *sc_ctrls = (unsigned int *) (IO_ADDRESS(INTEGRATOR_SC_BASE) +
					     INTEGRATOR_SC_CTRLS_OFFSET);
	volatile unsigned int *ebi_csr1 = (unsigned int *) (IO_ADDRESS(INTEGRATOR_EBI_BASE) +
					     INTEGRATOR_EBI_CSR1_OFFSET);

#if AFS_DEBUG
	printk("AFS Flash: Debug: locking flash\n");
#endif

	// Clear the write enable bit in system controller EBI register.
	*ebi_csr1 &= ~INTEGRATOR_EBI_WRITE_ENABLE;
	if (*ebi_csr1 & INTEGRATOR_EBI_WRITE_ENABLE) {
		volatile unsigned int *ebi_lock = (unsigned int *) (IO_ADDRESS(INTEGRATOR_EBI_BASE) +
					     INTEGRATOR_EBI_LOCK_OFFSET);

		*ebi_lock = 0xA05F;
		*ebi_csr1 &= ~INTEGRATOR_EBI_WRITE_ENABLE;
		*ebi_lock = 0;
	}
	// Clear the Flash programming bits in the system controller.
	*sc_ctrls |= INTEGRATOR_SC_CTRL_nFLVPPEN | INTEGRATOR_SC_CTRL_nFLWP;
}
#endif

#ifdef CONFIG_ARCH_P720T

#define FLASH_BASE		(0x04000000)
#define FLASH_SIZE		(64*1024*1024)

#define FLASH_PART_SIZE 	(4*1024*1024)
#define FLASH_BLOCK_SIZE	(128*1024)

static void afs_flash_unlock(void)
{
}

static void afs_flash_lock(void)
{
}
#endif

// AFS flash partitioning 
static int afs_flash_suspend(struct mtd_info *mtd)
{
	struct afs_info *afs = (struct afs_info *) mtd->priv;
	u_int status = 0;

#if AFS_DEBUG
	printk("AFS FLash: afs_flash_suspend() called\n");
#endif

	if (afs->dev_mtd->suspend)
		status = afs->dev_mtd->suspend(afs->dev_mtd);

	return status;
}

static void afs_flash_resume(struct mtd_info *mtd)
{
	struct afs_info *afs = (struct afs_info *) mtd->priv;

#if AFS_DEBUG
	printk("AFS FLash: afs_flash_resume() called\n");
#endif

	if (afs->dev_mtd->resume)
		afs->dev_mtd->resume(afs->dev_mtd);
}

static void afs_flash_sync(struct mtd_info *mtd)
{
	struct afs_info *afs = (struct afs_info *) mtd->priv;

#if AFS_DEBUG
	printk("AFS FLash: afs_flash_sync() called\n");
#endif

	afs->dev_mtd->sync(afs->dev_mtd);
}

static void afs_erase_callback(struct erase_info *ei)
{
	struct erase_info *eii = (struct erase_info *) ei->priv;

	if (eii->callback)
		eii->callback(eii);
}

static int afs_flash_erase(struct mtd_info *mtd, struct erase_info *instr)
{
	struct afs_info *afs = (struct afs_info *) mtd->priv;
	struct erase_info ei = *instr;
	u_int status;

	ei.mtd = afs->dev_mtd;
	ei.addr = afs->base + instr->addr;
	ei.callback = afs_erase_callback;
	ei.priv = (u_long) instr;
	ei.next = NULL;

	status = afs->dev_mtd->erase(afs->dev_mtd, &ei);

	return status;
}

static int afs_flash_read(struct mtd_info *mtd, loff_t from, size_t len,
			  size_t * retlen, u_char * buf)
{
	struct afs_info *afs = (struct afs_info *) mtd->priv;
	int ret;

	from += afs->base;

	afs_flash_unlock();
	ret = afs->dev_mtd->read(afs->dev_mtd, from, len, retlen, buf);
	afs_flash_lock();

	return ret;
}


static int afs_flash_write(struct mtd_info *mtd, loff_t to, size_t len,
			   size_t * retlen, const u_char * buf)
{
	struct afs_info *afs = (struct afs_info *) mtd->priv;
	int ret;

	to += afs->base;

//	afs_flash_unlock();
	ret = afs->dev_mtd->write(afs->dev_mtd, to, len, retlen, buf);
//	afs_flash_lock();

	return ret;
}

static __u8 afs_read8(struct map_info *map, unsigned long ofs)
{
	__u8 val = readb(ofs + map->map_priv_2);
#if  AFS_DEBUG_RW
	printk("AFS Flash (debug): afs_read8(0x%06lx) = 0x%02x\n", ofs, val);
#endif
	return val;
}

static __u16 afs_read16(struct map_info *map, unsigned long ofs)
{
	__u16 val = readw(ofs + map->map_priv_2);
#if  AFS_DEBUG_RW
	printk("AFS Flash (debug): afs_read16(0x%06lx) = 0x%04x\n", ofs, val);
#endif
	return val;
}

static __u32 afs_read32(struct map_info *map, unsigned long ofs)
{
	__u32 val = readl(ofs + map->map_priv_2);
#if  AFS_DEBUG_RW
	printk("AFS Flash (debug): afs_read32(0x%06lx) = 0x%08x\n", ofs, val);
#endif
	return val;
}

static void afs_copy_from(struct map_info *map, void *to, unsigned long from, ssize_t len)
{
#if  AFS_DEBUG_RW
	printk("AFS Flash (debug): afs_copy_from(0x%p, 0x%lx, %d)\n", to, from, len);
#endif
	memcpy(to, (void *) (from + map->map_priv_2), len);
}

static void afs_write8(struct map_info *map, __u8 d, unsigned long adr)
{
#if  AFS_DEBUG_RW
	printk("AFS Flash (debug): afs_write8(0x%x, 0x%lx)\n", d, adr);
#endif
	writeb(d, adr + map->map_priv_2);
}

static void afs_write16(struct map_info *map, __u16 d, unsigned long adr)
{
#if  AFS_DEBUG_RW
	printk("AFS Flash (debug): afs_write16(0x%x, 0x%lx)\n", d, adr);
#endif
	writew(d, adr + map->map_priv_2);
}

static void afs_write32(struct map_info *map, __u32 d, unsigned long adr)
{
#if  AFS_DEBUG_RW
	printk("AFS Flash (debug): afs_write32(0x%x, 0x%lx)\n", d, adr);
#endif
	writel(d, adr + map->map_priv_2);
}

static void afs_copy_to(struct map_info *map, unsigned long to, const void *from, ssize_t len)
{
#if  AFS_DEBUG_RW
	printk("AFS Flash (debug): afs_copy_to(0x%lx, 0x%px, %d)\n", to, from, len);
#endif
	memcpy((void *) (to + map->map_priv_2), from, len);
}

static struct map_info afs_map =
{
	name:		"AFS",
	read8:		afs_read8,
	read16:		afs_read16,
	read32:		afs_read32,
	copy_from:	afs_copy_from,
	write8:		afs_write8,
	write16:	afs_write16,
	write32:	afs_write32,
	copy_to:	afs_copy_to,
};

static int __init afs_cfi_init(u_int base, u_int size)
{
	struct mtd_info *mtd;
	u_int mask, off, idx;

	/*
	 * look for CFI based flash parts fitted to this board
	 */
	afs_map.size       = size;
	afs_map.buswidth   = 4;
	afs_map.map_priv_1 = 0;
	afs_map.map_priv_2 = (unsigned long) base;

	/*
	 * Also, the CFI layer automatically works out what size
	 * of chips we have, and does the necessary identification
	 * for us automatically.
	 *
	 * Note that we don't want to keep the flash unlocked for
	 * more time than necessary.
	 */
	afs_flash_unlock();
	mtd = do_cfi_probe(&afs_map);
	afs_flash_lock();

	if (!mtd)
		return -ENODEV;

	mtd->module = THIS_MODULE;

	/*
	 * This is the address mask; we use this to mask off out of
	 * range address bits.
	 */
	mask = afs_map.size - 1;

	/*
	 * Identify the partitions
	 */
	for (idx = off = 0; off < mtd->size; off += FLASH_BLOCK_SIZE) {
		struct afs_info *afs_part;
		struct mtd_info *my_mtd;
		struct footer_struct fs;
		struct image_info_struct iis;
		u_int ptr, length;
		size_t sz;
		int ret;

		/*
		 * Read the footer
		 */
		afs_flash_unlock();
		ret = mtd->read(mtd, off + FLASH_BLOCK_SIZE - sizeof(fs),
				sizeof(fs), &sz, (u_char *) &fs);
		afs_flash_lock();

		if (ret) {
			printk(KERN_ERR "error reading flash at offset 0x%x: %d\n",
				off + FLASH_BLOCK_SIZE - sizeof(fs), ret);
			break;
		}

		/*
		 * Does it contain the magic number?
		 */
		if (fs.signature != 0xa0ffff9f)
			continue;

		/*
		 * Read the image info block
		 */
		ptr = (u_int) fs.infoBase & mask;
		if (ptr >= mtd->size)
			continue;

		memset(&iis, 0, sizeof(iis));
		if (ptr) {
			afs_flash_unlock();
			ret = mtd->read(mtd, ptr, sizeof(iis), &sz, (u_char *) &iis);
			afs_flash_lock();
		}

		if (ret) {
			printk(KERN_ERR "error reading flash at offset 0x%x: %d\n",
				ptr, ret);
			break;
		}

		ptr = (u_int) fs.blockBase & mask;
		if (ptr > mtd->size || ptr > off)
			continue;

		length = FLASH_BLOCK_SIZE + off - ptr;

		afs_part = kmalloc(sizeof(struct afs_info) +
				   sizeof(struct mtd_info) + 16,
				   GFP_KERNEL);
		if (!afs_part)
			return -ENOMEM;

		memset(afs_part, 0, sizeof(struct afs_info) + sizeof(struct mtd_info));

		my_mtd = (struct mtd_info *) (afs_part + 1);

		afs_part->dev_mtd = mtd;
		afs_part->base    = ptr;
		afs_part->mtd     = my_mtd;
		afs_part->name    = (char *) (my_mtd + 1);

		my_mtd->type      = mtd->type;
		my_mtd->flags     = mtd->flags;
		my_mtd->size      = length;
		my_mtd->erasesize = mtd->erasesize;
		my_mtd->oobblock  = mtd->oobblock;
		my_mtd->oobsize   = mtd->oobsize;
		my_mtd->ecctype   = mtd->ecctype;
		my_mtd->eccsize   = mtd->eccsize;
		my_mtd->name      = "ARM Firmware Suite, Flash Partition";
		my_mtd->bank_size = mtd->bank_size;
		my_mtd->module    = THIS_MODULE;
		my_mtd->erase     = afs_flash_erase;
		my_mtd->point     = NULL;
		my_mtd->unpoint   = NULL;
		my_mtd->read      = afs_flash_read;
		my_mtd->write     = afs_flash_write;
		my_mtd->read_ecc  = mtd->read_ecc;
		my_mtd->write_ecc = mtd->write_ecc;
		my_mtd->read_oob  = mtd->read_oob;
		my_mtd->write_oob = mtd->write_oob;
		my_mtd->sync      = afs_flash_sync;
		my_mtd->suspend   = afs_flash_suspend;
		my_mtd->resume    = afs_flash_resume;
		my_mtd->priv      = afs_part;

		memcpy(afs_part->name, iis.name, 16);

		if (add_mtd_device(afs_part->mtd)) {
			printk(KERN_ERR "AFS: MTD device registration failed!\n");
			kfree(afs_part);
			return -EAGAIN;
		}
		printk("  mtd%d: at 0x%08x, %5dKB, %8d, %s\n",
		     idx, ptr, length / 1024, iis.imageNumber, iis.name);

		idx += 1;
	}

	return 0;
}

#if defined (MODULE) && LINUX_VERSION_CODE < 0x20300
#define init_armflash init_module
#define cleanup_armflash cleanup_module
#endif

int __init init_armflash(void)
{
	unsigned int flash_base;

	printk("ARM Flash Layout (V1.0)\n");

	flash_base = (unsigned int)__ioremap(FLASH_BASE, FLASH_SIZE, 0);
	return afs_cfi_init(flash_base, FLASH_SIZE);
}

static void __init cleanup_armflash(void)
{
	afs_flash_lock();
	iounmap((void *)afs_map.map_priv_2);
}

#if LINUX_VERSION_CODE > 0x20300
module_init(init_armflash);
module_exit(cleanup_armflash);
#endif
