/*
 * Copyright 2004 Freescale Semiconductor.
 * (C) Copyright 2003 Motorola Inc.
 *  Xianghua Xiao,(X.Xiao@motorola.com)
 *
 * (C) Copyright 2000, 2001
 *  Wolfgang Denk, DENX Software Engineering, wd@denx.de.
 *
 * (C) Copyright 2001, Stuart Hughes, Lineo Inc, stuarth@lineo.com
 * Add support the Sharp chips on the mpc8260ads.
 * I started with board/ip860/flash.c and made changes I found in
 * the MTD project by David Schleef.
 *
 * See file CREDITS for list of people who contributed to this
 * project.
 *
 * 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 <common.h>

#if !defined(CFG_NO_FLASH)

#undef FLASH_DEBUG
#ifdef FLASH_DEBUG
	#define debug(arg...) printf(arg)
#else
	#define debug(arg...)
#endif

flash_info_t	flash_info[CFG_MAX_FLASH_BANKS]; /* info for FLASH chips	*/

#if defined(CFG_ENV_IS_IN_FLASH)
# ifndef  CFG_ENV_ADDR
#  define CFG_ENV_ADDR	(CFG_FLASH_BASE + CFG_ENV_OFFSET)
# endif
# ifndef  CFG_ENV_SIZE
#  define CFG_ENV_SIZE	CFG_ENV_SECT_SIZE
# endif
# ifndef  CFG_ENV_SECT_SIZE
#  define CFG_ENV_SECT_SIZE  CFG_ENV_SIZE
# endif
#endif

/*-----------------------------------------------------------------------
 * Functions
 */
static ulong flash_get_size (vu_char *addr, flash_info_t *info);
static int write_char (flash_info_t *info, ulong dest, vu_char data);
static void flash_reset(flash_info_t *info);

/*-----------------------------------------------------------------------
 */

static void flash_reset(flash_info_t *info)
{
	vu_char  *base = (vu_char *)(info->start[0]);

	//add by jeff
	debug("start flash_reset_S1 %x\n", *base);

	/* Put FLASH back in read mode */
	if ((info->flash_id & FLASH_VENDMASK) == FLASH_MAN_INTEL)
	{
		*base = 0xFF;	/* Intel Read Mode */
		asm("msync");
	} else if ((info->flash_id & FLASH_VENDMASK) == FLASH_MAN_AMD)
	{
		*base = 0xF0;	/* AMD Read Mode */
		asm("msync");
	}

	//add by jeff
	debug("start flash_reset_return %x\n", *base);

}

unsigned long flash_init (void)
{
	volatile immap_t     	*immap  = (immap_t *)CFG_IMMR;
        volatile ccsr_lbc_t 	*memctl = &immap->im_lbc;
        unsigned long size_lcs1 = 0;
	unsigned long size_lcs0 = 0;
        int i;

	//add by jeff
	debug("start flash_init_S1\n");

        /* Init: no FLASHes known */
        for (i=0; i<CFG_MAX_FLASH_BANKS; ++i)
        {
                flash_info[i].flash_id = FLASH_UNKNOWN;
        }

        /* Static FLASH Bank configuration here - FIXME XXX */

	/* CFG_FLASH_BASE always points to LCS1 instead of LCS0 */
        size_lcs0 = flash_get_size((vu_char *)CFG_FLASH_BASE1,
				   &flash_info[0]);

	//add by jeff
	debug("flash_init_S2, size_lcs0 = %x\n", size_lcs0);

        if (flash_info[0].flash_id == FLASH_UNKNOWN)
        {
                printf ("## Unknown FLASH on Bank 0 - Size = 0x%08lx = %ld MB\n",
                        size_lcs0, size_lcs0<<20);
        }
	//add by jeff
	debug("flash_init_S3, flash_id0 = %x\n", flash_info[0].flash_id);

        size_lcs1 = flash_get_size((vu_char *)CFG_FLASH_BASE, &flash_info[1]);

	//add by jeff
	debug("flash_init_S4,size_lcs1 = %x\n", size_lcs1);

        if (size_lcs1 > size_lcs0)
        {
            printf ("## ERROR: "
                    "Bank 1 (0x%08lx = %ld MB) > Bank 0 (0x%08lx = %ld MB)\n",
                     size_lcs1, size_lcs1<<20,
                     size_lcs0, size_lcs0<<20
            );
            flash_info[0].flash_id  = FLASH_UNKNOWN;
            flash_info[1].flash_id  = FLASH_UNKNOWN;
            flash_info[0].sector_count      = -1;
            flash_info[1].sector_count      = -1;
            flash_info[0].size              = 0;
            flash_info[1].size              = 0;
            return (0);
        }
        /* Remap FLASH according to real size */
        memctl->or0 = CFG_OR0_PRELIM;
        memctl->br0 = CFG_BR0_PRELIM;

        /* Re-do sizing to get full correct info */
        size_lcs0 = flash_get_size((vu_char *)(CFG_FLASH_BASE1), &flash_info[0]);

        if (size_lcs1)
        {
                memctl->or1 = CFG_OR1_PRELIM;
                memctl->br1 = CFG_BR1_PRELIM;

                /* Re-do sizing to get full correct info */
                size_lcs0 = flash_get_size((vu_char *)CFG_FLASH_BASE1,&flash_info[0]);
                size_lcs1 = flash_get_size((vu_char *)CFG_FLASH_BASE,&flash_info[1]);

        } else {
                memctl->br1 = 0;           /* invalidate bank */

                flash_info[1].flash_id = FLASH_UNKNOWN;
                flash_info[1].sector_count = -1;
        }

        flash_info[0].size = size_lcs0;
        flash_info[1].size = size_lcs1;


#if (CFG_MONITOR_BASE >= CFG_FLASH_BASE)

	//add by jeff
	debug("flash protect, monitor start= %x\n", CFG_MONITOR_BASE);
	debug("flash protect, monitor end  = %x\n", (CFG_MONITOR_BASE+monitor_flash_len-1));

        /* monitor protection ON by default */
        flash_protect(FLAG_PROTECT_SET,
                      CFG_MONITOR_BASE,
                      CFG_MONITOR_BASE+monitor_flash_len-1,
                      &flash_info[0]);
                      
#endif

#ifdef  CFG_ENV_IS_IN_FLASH

	//add by jeff
	debug("flash protect, ENV start= %x\n", CFG_ENV_ADDR);
	debug("flash protect, ENV end  = %x\n", (CFG_ENV_ADDR+CFG_ENV_SECT_SIZE-1));

        /* ENV protection ON by default */
        flash_protect(FLAG_PROTECT_SET,
                      CFG_ENV_ADDR,
                      CFG_ENV_ADDR+CFG_ENV_SECT_SIZE-1,
                      &flash_info[0]);
#endif

	//add by jeff
	debug("flash_init_return, total size = %x\n", (size_lcs0+size_lcs1));

        return (size_lcs0 + size_lcs1);
}


/*-----------------------------------------------------------------------
 */
void flash_print_info  (flash_info_t *info)
{
	int i;

	if (info->flash_id == FLASH_UNKNOWN)
	{
		printf ("missing or unknown FLASH type\n");
		return;
	}

	//add by jeff
	debug("flash_print_info_S1, flash_id = %x\n", info->flash_id);

	switch (info->flash_id & FLASH_VENDMASK)
	{
	case FLASH_MAN_INTEL:	printf ("Intel ");		break;
	case FLASH_MAN_SHARP:   printf ("Sharp ");		break;
	case FLASH_MAN_AMD:		printf ("AMD ");		break;
	case FLASH_MAN_MX:		printf ("Macronix ");		break;
	default:		printf ("Unknown Vendor ");	break;
	}

	switch (info->flash_id & FLASH_TYPEMASK)
	{
	case FLASH_28F016SV:	printf ("28F016SV (16 Mbit, 32 x 64k)\n");
				break;
	case FLASH_28F160S3:	printf ("28F160S3 (16 Mbit, 32 x 512K)\n");
				break;
	case FLASH_28F320S3:	printf ("28F320S3 (32 Mbit, 64 x 512K)\n");
				break;
	case FLASH_LH28F016SCT: printf ("28F016SC (16 Mbit, 32 x 64K)\n");
				break;
	case FLASH_28F640J3A:   printf ("28F640J3A (64 Mbit, 64 x 128K)\n");
				break;
   case FLASH_AM640U:      printf ("29LV641D (64 Mbit, uniform sectors)\n");
            break;
	case FLASH_AM320T:   	printf ("29LV320T (32 Mbit, 63 x 64K, 8 x 8K)\n");
				break;
	case FLASH_MXL640MT:   	printf ("29LV640T (64 Mbit, 127 x 64K, 8 x 8K)\n");
				break;

	default:		printf ("Unknown Chip Type\n");
				break;
	}

	printf ("  Size: %ld MB in %d Sectors\n",
		info->size >> 20, info->sector_count);

	printf ("  Sector Start Addresses:");
	for (i=0; i<info->sector_count; ++i)
	{
		if ((i % 5) == 0)
			printf ("\n   ");
			printf (" %08lX%s",
			info->start[i],
			info->protect[i] ? " (RO)" : "     "
		);
	}
	printf ("\n");
}

/*-----------------------------------------------------------------------
 */


/*-----------------------------------------------------------------------
 */

/*
 * The following code cannot be run from FLASH!
 */

static ulong flash_get_size (vu_char *addr, flash_info_t *info)
{
	short i;
	vu_char value0, value1;
	ulong base = (ulong)addr;

	/* Write "Intelligent Identifier" command: read Manufacturer ID */
	addr[0xAAA] = 0xAA;
	asm("msync");
	addr[0x555] = 0x55;
	asm("msync");
	addr[0xAAA] = 0x90;
	asm("msync");
	udelay(20);

#if 1
	value0 = addr[0];
#else
	value0 = AMD_MANUFACT;
#endif	
	asm("msync");

	//add by jeff
	debug("flash_get_size_S1, man id = %x\n", value0);

		info->flash_id = FLASH_MAN_MX;
	
#if 1
   value0 = addr[1];               /* device ID            */
	asm("msync");
   value1 = addr[2];               /* device ID            */
	asm("msync");
#else
	value0 = AMD_ID_LV640T;
	asm("msync");
#endif

	//add by jeff
	debug("flash_get_size_S2, device id,0x1 = %x\n", value0);
	debug("flash_get_size_S3, device id,0x2 = %x\n", value1);

        info->flash_id += FLASH_MXL640MT;
        info->sector_count = 135;
        info->size = 0x00800000 * (sizeof(unsigned char));

	/* set up sector start address table */
		for (i = 0; i < 127; i++)
		{
			info->start[i] = base + (i * 0x00010000);
		}
		debug("flash_get_size_S1, i = %x, base = %x\n", i, base);
		info->start[127] = base + 0x7f0000;
		info->start[128] = base + 0x7f2000;
		info->start[129] = base + 0x7f4000;
		info->start[130] = base + 0x7f6000;
		info->start[131] = base + 0x7f8000;
		info->start[132] = base + 0x7fA000;
		info->start[133] = base + 0x7fC000;
		info->start[134] = base + 0x7fE000;

	/* check for protected sectors */
	for (i = 0; i < info->sector_count; i++)
	{
		/* read sector protection at sector address, (A7 .. A0) = 0x02 */
		/* D0 = 1 if protected */
		addr = (vu_char *)(info->start[i]);
		info->protect[i] = addr[2] & 1;
	}



	/*
	 * Prevent writes to uninitialized FLASH.
	 */
	if (info->flash_id != FLASH_UNKNOWN)
	{
		addr = (vu_char *)info->start[0];
		*addr = 0xF0;	/* reset bank to read array mode */
		asm("msync");
	}

	return (info->size);
}


/*-----------------------------------------------------------------------
 */

int	flash_erase (flash_info_t *info, int s_first, int s_last)
{
	vu_char *addr;
	int flag, prot, sect;
	int amd = (info->flash_id & FLASH_VENDMASK) == FLASH_MAN_MX;
	ulong start, now, last;
	int rcode = 0;
	int temp = 0;

	//add by jeff
	debug("flash_erase_S1, s_first = %x\n", s_first);
	debug("flash_erase_S1, s_last = %x\n", s_last);


	if ((s_first < 0) || (s_first > s_last))
	{
		if (info->flash_id == FLASH_UNKNOWN)
		{
			printf ("- missing\n");
		} else {
			printf ("- no sectors to erase\n");
		}
		return 1;
	}

	switch (info->flash_id & FLASH_TYPEMASK)
	{
	case FLASH_28F320C3B:
	case FLASH_AM320T:
	case FLASH_AM640U:
	case FLASH_MXL640MT:
		break;
	case FLASH_UNKNOWN:
	default:
		printf ("Can't erase unknown flash type %08lx - aborted\n",
			info->flash_id);
		return 1;
	}

	prot = 0;
	for (sect=s_first; sect<=s_last; ++sect)
	{
		if (info->protect[sect])
		{
			prot++;
		}
	}

	if (prot)
	{
		printf ("- Warning: %d protected sectors will not be erased!\n",
			prot);
	} else {
		printf ("\n");
	}

	start = get_timer(0);
	last  = start;

	//add by jeff
	debug("flash_erase_S2\n");
	
	printf("Start erasing...");
	/* Start erase on unprotected sectors */
	for (sect = s_first; sect<=s_last && rcode == 0; sect++)
	{
		if (info->protect[sect] != 0)	/* protected, skip it */
			continue;

		/* Disable interrupts which might cause a timeout here */
		flag = disable_interrupts();

		addr = (vu_char *)(info->start[sect]);
		//add by jeff
		debug("flash_erase_S3, addr = %x\n", addr);

		if (amd)
		{
			vu_char *base;		/* first address in bank */
			base = (vu_char *)(info->start[0]);
			//add by jeff
			debug("flash_erase_S4, base = %x\n", base);

			base[0x0AAA] = 0xAA;	/* unlock */
			asm("msync");
			base[0x0555] = 0x55;	/* unlock */
			asm("msync");
			base[0x0AAA] = 0x80;	/* erase mode */
			asm("msync");
			base[0x0AAA] = 0xAA;	/* unlock */
			asm("msync");
			base[0x0555] = 0x55;	/* unlock */
			asm("msync");
			*addr = 0x30;	/* erase sector */
			asm("msync");
		}
		/* re-enable interrupts if necessary */
		if (flag)
			enable_interrupts();

		/* wait at least 50us for AMD, 80us for Intel.
		 * Let's wait 1 ms.
		 */
		udelay (300);
		//add by jeff
		debug("flash_erase_S5, addr = %x\n", addr);

		while ((*addr & 0x80) != 0x80)
		{
			if ((now = get_timer(start)) > CFG_FLASH_ERASE_TOUT)
			{
				printf ("Timeout\n");

				if (amd)
				{
					/* suspend erase	*/
					*addr = 0xB0;
					asm("msync");
				}

				flash_reset(info);	/* reset to read mode */
				rcode = 1;		/* failed */
				break;
			}
			udelay(600);
			temp++;
			/* show that we're waiting */
			if (temp%1000 ==0)
			{	/* every second */
				putc ('.');
			}
		}

		flash_reset(info);	/* reset to read mode	*/
	}

	printf ("done\n");
	return rcode;
}


/*-----------------------------------------------------------------------
 * Copy memory to flash, returns:
 * 0 - OK
 * 1 - write timeout
 * 2 - Flash not erased
 */
int write_buff (flash_info_t *info, uchar *src, ulong addr, ulong cnt)
{
	int rc;
	int wrap=0;
	
//	printf("*src = %x\n",src);
	if (info->flash_id == FLASH_UNKNOWN)
	{
		return 4;
	}
	printf("\b\b\b\b");
	while (cnt > 0) {
		if ((rc = write_char(info, addr, *src)) != 0)
		{
			printf("return code = %d\n", rc);
			return rc;
		}
		addr++;
		src++;
		cnt--;
		if (cnt%6000==0)
		{
			printf(".");
			wrap++;
			if (wrap>60)
			{
				printf("\n             ");
				wrap = 0;
			}
		}
	}
	printf("\n");
	return cnt;
}


static int write_char (flash_info_t *info, ulong dest, vu_char data)
{
    ulong start;
    int flag;
    int res = 0;	/* result, assume success	*/
    vu_char *base;		/* first address in flash bank	*/
	 vu_char temp;
	 int time = 0;
    /* Check if Flash is (sufficiently) erased */
    if ((*((vu_char *)dest) & data) != data)
    {
		printf("\nfirst read, at address = %x\n", dest);
		printf("*temp = %x, data = %x\n", temp, data);
		printf("*dest = %x, data = %x\n", *((vu_char *)dest), data);
		return (2);
    }

	
    base = (vu_char *)(info->start[0]);
	//add by jeff
//	debug("write_char_S1, dest = %x, data = %x\n", dest, data);

    /* Disable interrupts which might cause a timeout here */
    flag = disable_interrupts();

    base[0x0AAA] = 0xAA;	/* unlock */
    asm("msync");
    base[0x0555] = 0x55;	/* unlock */
    asm("msync");
    base[0x0AAA] = 0xA0;	/* selects program mode */
    asm("msync");

    *((vu_char *)dest) = data;		/* start programming the data	*/
    asm("msync");

    /* re-enable interrupts if necessary */
    if (flag)
		enable_interrupts();

    start = get_timer (0);

    /* data polling for D7 */
    temp = *((vu_char *)dest);
    asm("msync");
    while ((res == 0) && ((temp & 0x80) != (data & 0x80)))
    {
    	time++;
    	if (time > CFG_FLASH_WRITE_TOUT * 10000)
    	{
	   	*((vu_char *)dest) = 0xF0;	/* reset bank */
    	   asm("msync");
	    	printf ("Write out = %d\n",time);
	    	res = 1;
		}
		temp = *((vu_char *)dest);
	   asm("msync");
    }
	 if ((*((vu_char *)dest)) != data)
		udelay(30);		

		//printf("addr %x = %x, data = %x\n",dest, temp, data);
	//add by jeff
//	debug("write_char_S1, read dest = %x, data = %x\n", dest, *dest);
//	res = 1000;
   return (res);
}

#endif /* !CFG_NO_FLASH */
