/* 
 * Driver for MIU driven ATMEL Flash support
 * Author : Dean Jenkins (dean@libertesoftare.com)
 * Copyright 2006 Liberte Software Limited
 *
 * 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 file is the low-level driver to access serial Flash through the pnx833x MIU */

#include <config.h>

/*#undef DEBUG */
#include <common.h>

#ifdef CFG_MIU_SPI_FLASH

#include <asm/arch/regaccess.h>
#include <flash.h>

/************************************/
/* Exported functions for uboot API */
/************************************/

unsigned long flash_init (void);
int flash_erase (flash_info_t * info, int s_first, int s_last);
int write_buff (flash_info_t * info, uchar * src, ulong addr, ulong cnt);
void flash_print_info (flash_info_t * info);

#ifdef CFG_FLASH_PROTECTION
int flash_real_protect (flash_info_t * info, long sector, int prot);
#endif

/***************************/
/* Local MIU SPI functions */
/***************************/

static ulong read_device_id (ulong base);
static ulong flash_get_size (ulong base, int banknum);
static unsigned char read_status_register (ulong base);
static unsigned char do_page_compare (ulong base, ulong page_base, int buffer_num);
static void main_to_buffer (ulong base, ulong page_base, int buffer_num);
static int erase_flash_sector (ulong base, ulong base_sector);
static int write_page_to_flash (ulong base, ulong flash_base_page, ulong offset, ulong data_base, unsigned int length);

#ifdef CFG_FLASH_PROTECTION
static unsigned long read_sector_protection_register (ulong base);
#endif

/**********************/
/* Exported variables */
/**********************/

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


/************************/
/* Local file variables */
/************************/

#if ( CFG_MAX_FLASH_BANKS != 1 )
#error Only 1 bank of Flash supported, please check CFG_MAX_FLASH_BANKS
#endif

/* bank_base contains an array of base addresses for each bank of Flash */
static ulong bank_base[CFG_MAX_FLASH_BANKS] = { CFG_FLASH_BASE };

/* holds the initial SPI command */
/* this should be a slow continuous READ command */
static const ulong initial_config = 0x0000001El;	/* assumed RESET value */

/************************/
/* Local file types     */
/************************/

typedef unsigned long flash_sect_t;

/************************/
/* Local file #defines  */
/************************/

/* MIU Status register bit masks */
#define SR_PAGE_SIZE	0x01
#define SR_PROTECT	0x02
#define SR_FIXED	0x3C
#define SR_COMPARE	0x40
#define SR_READY_BUSY	0x80

#define COMP_OK		0
#define COMP_BAD	1
#define COMP_ERR	2

#define GOOD_PAGE	0
#define BAD_PAGE	1

#define BUSY_DELAY	400

/*****************************************************************************/
/* MIU SPI Functions for serial ATMEL flash                                  */
/*****************************************************************************/

/*
 * Function reads the manufacturer/device ID of the Serial Flash
 *
 * Inputs:
 *	base = base address of the serial Flash
 * Outputs:
 *	returns manufacturer/device ID
 */
static ulong read_device_id (ulong base)
{
	ulong device_id;

	debug( "  Current miu_sel0 = %08lX\n", PNX833X_MIU_SEL0 );
	debug( "  Current miu_sel0_timing = %08lX\n", PNX833X_MIU_SEL0_TIMING );
	debug( "  Original miu_config = %08lX\n", PNX833X_MIU_CONFIG );

	/* Do a check of the current MIU SPI command */
	if ( PNX833X_MIU_CONFIG != initial_config ) {
		/* current SPI command not a slow READ command */
		printf( "WARNING: MIU_CONFIG SPI command not as expected, defaulting to slow continuous READ command\n");
		PNX833X_MIU_CONFIG = initial_config;
	}

	debug( "  Sending SPI Device ID opcode...\n" );

	/* Send the Manufacturer/Device ID request OpCode 9FH */
	/* PNX833X_WRITE_SPI_CONFIG(opcode, data_enable, addr_enable, sync) */
	PNX833X_WRITE_SPI_CONFIG(0x9F, 1, 0, 0);

	/* read the device id from the base address of the serial flash */
	device_id = *((volatile unsigned long *) base);

	printf ("Serial Flash Device ID is %lX\n", device_id);

	/* Set the original settings back so that is back to a known state */
	PNX833X_MIU_CONFIG = initial_config;

	return device_id;
}


/*
 * Function reads the status register of the Serial Flash
 *
 * Inputs:
 *	base = base address of the serial Flash
 * Outputs:
 *	returns the whole status register
 */

/* MUST HAVE A DELAY BEFORE READING STATUS REGISTER OTHERWISE SPI CMD FAILS */
static unsigned char read_status_register (ulong base)
{
	unsigned char status_register;

	/* Read the device's status register */
	debugX( 2, "  Sending SPI SR opcode...\n" );

	/* Send the Status request OpCode D7H */
	/* PNX833X_WRITE_SPI_CONFIG(opcode, data_enable, addr_enable, sync) */
	PNX833X_WRITE_SPI_CONFIG(0xD7, 1, 0, 0);

	/* read the status register from the base address of the serial flash */
	status_register = *((volatile unsigned char *) base);

	debugX( 2, "  Status Register = %02X\n", status_register);

	/* Set the original settings back so that is back to a known state */
	PNX833X_MIU_CONFIG = initial_config;

	/* sanity, check fixed bits of the status register */
	if ((status_register & SR_FIXED) != SR_FIXED) {
		printf ("Error: Serial Flash Status Register fixed bit read error, %2X\n", status_register );
	}

	return status_register;
}


/*
 * Function does a buffer page compare with the main memory on the serial Flash
 *
 * Inputs:
 *	base = base address of the serial Flash
 *	page_base = page base address of the serial Flash
 *	buffer_num = 1 for buffer 1, 2 for buffer 2 select
 * Outputs:
 *	returns the result of the compare
 */
static unsigned char do_page_compare (ulong base, ulong page_base, int buffer_num)
{
	unsigned char compare_result;
	unsigned char compare_flag;

	debugX ( 2, "\nComparing buffer %d to page %08lX\n", buffer_num, page_base);

	/* do a buffer compare with main memory */

	if (buffer_num == 1) {
		/* Send the compare command to the Flash */
		debugX( 2, "  Sending SPI Compare to buffer 1 opcode...\n" );

		/* Send the buffer 1 to main memory compare command OpCode 60H */
		/* PNX833X_WRITE_SPI_CONFIG(opcode, data_enable, addr_enable, sync) */
		PNX833X_WRITE_SPI_CONFIG(0x60, 0, 1, 0);
	}
	else if (buffer_num == 2) {
		/* Send the compare command to the Flash */
		debugX( 2, "  Sending SPI Compare to buffer 2 opcode...\n" );

		/* Send the buffer 1 to main memory compare command OpCode 61H */
		/* PNX833X_WRITE_SPI_CONFIG(opcode, data_enable, addr_enable, sync) */
		PNX833X_WRITE_SPI_CONFIG(0x61, 0, 1, 0);
	}
	else {
		printf ("Error: buffer_num = %d and is out of range in do_page_compare()\n", buffer_num );
		return COMP_ERR;
	}

	/* Select the page to be compared */
	*((char *)page_base) = 0;	/* This does do something !! Bad compare results */

	/* MUST HAVE A DELAY BEFORE READING STATUS REGISTER OTHERWISE SPI CMD FAILS */

	udelay(100);

	/* Read the device's status register to get the busy state */
	while( (read_status_register (base) & SR_READY_BUSY) == 0 ) {
		udelay(BUSY_DELAY);
		debug (".");
	}

	debug("\n");

	/* read the compare flag */
	compare_flag = read_status_register (base) & SR_COMPARE;

	if ( compare_flag == SR_COMPARE ) {
		/* Compare failed */
		compare_result = COMP_BAD;
		printf ("\nPage compare FAILED\n");
	}
	else {
		/* Compare matched */
		compare_result = COMP_OK;
		debugX ( 2, "\nPage compare MATCHED\n");
	}

	/* Set the original settings back so that is back to a known state */
	PNX833X_MIU_CONFIG = initial_config;

	return compare_result;
}


/*
 * Function copies a page of main memory into in a buffer on the serial Flash
 *
 * Inputs:
 *	base = base address of the serial Flash
 *	page_base = page base address of the serial Flash
 *	buffer_num = 1 for buffer 1, 2 for buffer 2 select
 * Outputs:
 *	none
 */
static void main_to_buffer (ulong base, ulong page_base, int buffer_num)
{
	debugX ( 2, " Copying to buffer %d from page %08lX\n", buffer_num, page_base);

	/* copy a main memory page to a buffer */

	if (buffer_num == 1) {
		/* Send the transfer command to the Flash */
		debugX( 2, "  Sending SPI transfer to buffer 1 opcode...\n" );

		/* Send the main memory to buffer 1 transfer command OpCode 53H */
		/* PNX833X_WRITE_SPI_CONFIG(opcode, data_enable, addr_enable, sync) */
		PNX833X_WRITE_SPI_CONFIG(0x53, 0, 1, 0);
	}
	else if (buffer_num == 2) {
		/* Send the transfer command to the Flash */
		debugX( 2, "  Sending SPI transfer to buffer 2 opcode...\n" );

		/* Send the main memory to buffer 2 transfer command OpCode 55H */
		/* PNX833X_WRITE_SPI_CONFIG(opcode, data_enable, addr_enable, sync) */
		PNX833X_WRITE_SPI_CONFIG(0x55, 0, 1, 0);
	}
	else {
		printf ("Error: buffer_num = %d and is out of range in main_to_buffer()\n", buffer_num );
		return;
	}

	/* Select the page to be transfered */
	*((char *)page_base) = 0;

	/* MUST HAVE A DELAY BEFORE READING STATUS REGISTER OTHERWISE SPI CMD FAILS */

	udelay(100);

	/* Read the device's status register to get the busy state */
	while( (read_status_register (base) & SR_READY_BUSY) == 0 ) {
		udelay(BUSY_DELAY);
		debug (".");
	}

	debug("\n");

	/* Set the original settings back so that is back to a known state */
	PNX833X_MIU_CONFIG = initial_config;
}

#ifdef CFG_FLASH_PROTECTION
/*
 * Function reads the sector protection register of the Serial Flash
 * FIXME does not work
 * Inputs:
 *	base = base address of the serial Flash
 * Outputs:
 *	returns a long word as a bit map of the 
 *	status of the 32 sectors within the protection register
 */
static unsigned long read_sector_protection_register (ulong base)
{
	ulong sector_status;

	/* Read the device's sector protection register */
	debug( "  Sending SPI SPR opcode...\n" );

	/* Send the Status request OpCode 32H */
	/* PNX833X_WRITE_SPI_CONFIG(opcode, data_enable, addr_enable, sync) */
	PNX833X_WRITE_SPI_CONFIG(0x32, 1, 0, 0);

	/* DJ FIXME */
	/* Need to find out how to read all 32 bytes of the status protection register */

	/* read the sector status from the base address of the serial flash */
	sector_status = *((volatile unsigned long *) base);

	debug( "  Status Protection = %08lX\n", sector_status);

	/* Set the original settings back so that is back to a known state */
	PNX833X_MIU_CONFIG = initial_config;

	return sector_status;
}
#endif

/*
 * Function erases a sector of the Serial Flash
 *
 * Inputs:
 *	base = base address of the device
 *	base_sector = base address of the sector to erase in the Serial Flash
 * Outputs:
 *	returns the status
 */
static int erase_flash_sector (ulong base, ulong base_sector)
{
	int status = 0;

	/* DJ FIXME */
	/* need to allow for page size of 1056 */
	ulong verify_count = 1024l*256l/4l;	/* word count for page size = 1024 */
	volatile ulong * verify_ptr;
	int verify_result = 0;
	ulong block_no;

	/* do sector erase using multiple block erase as */
	/* sectors 0a and 0b are not the same size as the other sectors */
	/* therefore sector erase command behaves differently on 0a and 0b */

	for (block_no = 0; block_no < 32; block_no++) {

		/* Send the command to erase the Flash */
		debugX( 2, "  Sending SPI Erase block opcode...\n" );

		/* Send the Erase sector command OpCode 50H */
		/* PNX833X_WRITE_SPI_CONFIG(opcode, data_enable, addr_enable, sync) */
		PNX833X_WRITE_SPI_CONFIG(0x50, 0, 1, 0);

		*((char *)base_sector+block_no*8l*1024l) = 0;	/* Do the erase of this block */

		/* MUST HAVE A DELAY BEFORE READING STATUS REGISTER OTHERWISE SPI CMD FAILS */

		debug ("Erasing block %08lX\n", base_sector+block_no*8l*1024l);

		udelay(100);

		/* Read the device's status register to get the busy state */
		while( (read_status_register (base) & SR_READY_BUSY) == 0 ) {
			udelay(BUSY_DELAY);
			debug (".");
		}

		debug("\n");
	}

	/* Set the original settings back so that is back to a known state */
	PNX833X_MIU_CONFIG = initial_config;

	/* verify erase */
	verify_ptr = (ulong *)base_sector;	/* point to start of erased sector */

	while ( verify_count > 0 && verify_result == 0 ) {
	
		if ( *verify_ptr != 0xFFFFFFFFl ) {
			printf ("Error: Erase failed at %p value %08lX\n",
				verify_ptr, *verify_ptr);
			verify_result = 1;
		}

		verify_ptr++;
		verify_count--;
	}

	if ( verify_result == 0 ) {
		printf( "Sector Erase for %08lX verified OK\n", base_sector);
	}
	else {
		/* Indicate error occurred */
		status = 1;
	}

	return status;
}


/*
 * Function writes a page into the serial flash buffer 
 *
 * Inputs:
 *	base = base address of the device
 *	flash_base_page = address of the base of the page in the serial flash
 *	offset = offset relative to base of page for start address of new data
 *	data_base = address of the base of the data to be writen to flash
 *	length = length in words ( 4 bytes ) of the data to be written, must not exceed page length
 * Outputs:
 *	returns the status
 */
static int write_page_to_flash (ulong base, ulong flash_base_page, ulong offset,
				ulong data_base, unsigned int length)
{
	int status = GOOD_PAGE;
	volatile ulong *page_ptr;
	ulong *data_ptr;
	unsigned int loop;

	/* copy main memory page to buffer 1 */
	main_to_buffer (base, flash_base_page, 1);

	if (do_page_compare (base, flash_base_page, 1) != COMP_OK) {
		printf ("\nError: Failed to transfer main memory page into buffer 1 in write_page_to_flash()\n");
		status = BAD_PAGE;
	}

	if (status == GOOD_PAGE) {

		/* Send the command to write to the Flash buffer 1 */
		debugX( 2, "  Sending SPI Write to buffer 1 opcode...\n" );

		/* Send the Write to buffer 1 command OpCode 84H */
		/* PNX833X_WRITE_SPI_CONFIG(opcode, data_enable, addr_enable, sync) */
		PNX833X_WRITE_SPI_CONFIG(0x84, 1, 1, 0);

		debugX( 2, "  Writing to buffer 1 at address %08X\n", flash_base_page);
		debug( "Offset is %08X, Length is %08X (words)\n", offset, length);

		/* write to the buffer using 4 byte access */
		page_ptr = (ulong *)(flash_base_page + offset);
		data_ptr = (ulong *)data_base;

		for (loop = 0; loop < length; loop++) {

			*page_ptr = *data_ptr;	/* get data and write to buffer 1 */

			/* Move to next word location */
			page_ptr++;
			data_ptr++;
		}

		udelay(100);

		debug ("\nWrite to Flash\n");

		/* Send the command to write buffer 1 to the Flash */
		debugX( 2, "  Sending SPI buffer 1 wite to Flash opcode...\n" );

		/* Send the buffer 1 Write to flash with erase command OpCode 83H */
		/* PNX833X_WRITE_SPI_CONFIG(opcode, data_enable, addr_enable, sync) */
		PNX833X_WRITE_SPI_CONFIG(0x83, 0, 1, 0);

		/* specify main memory page to write to */
		*(char *)flash_base_page = 0;

		/* MUST HAVE A DELAY BEFORE READING STATUS REGISTER OTHERWISE SPI CMD FAILS */

		udelay(100);

		/* Read the device's status register to get the busy state */
		while( (read_status_register (base) & SR_READY_BUSY) == 0 ) {
			udelay(BUSY_DELAY);
			debug (".");
		}

		debug("\n");

		/* Now verify that page write was successful */
		if (do_page_compare (base, flash_base_page, 1) != COMP_OK) {
			printf ("Error: Failed to write buffer 1 to main memory page in write_page_to_flash()\n");
			status = BAD_PAGE;
		}
	}

	/* Set the original settings back so that is back to a known state */
	PNX833X_MIU_CONFIG = initial_config;

	return status;
}


/*****************************************************************************/
/* Exported Functions to uboot API for Flash operations                      */
/*****************************************************************************/

/*
 * Function initialises flash_info[] with flash device info
 *
 * Inputs:
 *	none
 * Outputs:
 *	returns the total size of all the Flash banks in bytes
 */
unsigned long flash_init (void)
{
	unsigned long size = 0;
	int i;

	debugX( 2, "flash_init called\n" );	

	/* Find a SPI Flash on the MIU CS = 0 */
	for (i = 0; i < CFG_MAX_FLASH_BANKS; ++i) {
		flash_info[i].flash_id = FLASH_UNKNOWN;
		size += flash_info[i].size = flash_get_size (bank_base[i], i);
		if (flash_info[i].flash_id == FLASH_UNKNOWN) {
#ifndef CFG_FLASH_QUIET_TEST
			printf ("## Unknown FLASH on Bank %d - Size = 0x%08lx = %ld MB\n",
				i+1, flash_info[i].size, flash_info[i].size << 20);
#endif /* CFG_FLASH_QUIET_TEST */
		}
	}

	return (size);
}


#if 0
/* For illustration only from include/flash.h */
typedef struct {
	ulong	size;			/* total bank size in bytes		*/
	ushort	sector_count;		/* number of erase units		*/
	ulong	flash_id;		/* combined device & manufacturer code	*/
	ulong	start[CFG_MAX_FLASH_SECT];   /* physical sector start addresses */
	uchar	protect[CFG_MAX_FLASH_SECT]; /* sector protection status	*/
} flash_info_t;
#endif

#define PAGE_1056 0
#define PAGE_1024 1

/*
 * Function fills flash_info[] with device info and returns the size
 *
 * Inputs:
 *	base = base address of the Flash bank
 *	banknum = bank number ( starts at zero put printed as 1 on the console )
 * Outputs:
 *	returns the size of banknum bank in bytes
 */
ulong flash_get_size (ulong base, int banknum)
{
	flash_info_t *info = &flash_info[banknum];

	int i;
	ulong device_id = 0;

	debugX( 2, "flash_get_size called\n" );

	info->size = 0;	/* Initialise size */

	device_id = read_device_id (base);

	/* Is this the expected device ? */
	if ( device_id == 0x1F280000l ) {
		/* device is AT45DB642 */
		int page_size = PAGE_1024;	/* 1056 or 1024 byte page size */

		/* 262144 bytes per sector for 1024 byte page size */
		/* 270336 bytes per sector for 1056 byte page size */
		ulong sector_size;	

		debug( "flash_get_size called\n" );

		printf ("Flash AT45DB642 detected on Bank #%d\n", banknum+1);

		/* Read the device's status register to get the page size */
		if( read_status_register (base) & SR_PAGE_SIZE ) {
			page_size = PAGE_1024;
		}
		else {
			page_size = PAGE_1056;
		}

		if ( page_size == PAGE_1056 ) {
			printf ("Flash page size is 1056 bytes\n");
			printf ("WARNING: Flash page size of 1056 bytes is NOT supported\n");
			printf ("WARNING: DO NOT use ERASE and WRITE commands otherwise corruption will occur\n");
			sector_size = 270336l;
		}
		else {
			printf ("Flash page size is 1024 bytes\n" );
			sector_size = 262144l;
		}

		info->size = 0x00800000l;	/* 64 MegBits */
		info->sector_count = 0x0020;	/* 32 sectors */
		info->flash_id = 0x1F280000l;	/* from data sheet */

		info->start[0] = base;

		/* DJ Fixme */
		/* Does sector 0a and 0b need special treatment ? */

		/* define the base address of each sector */
		for( i=1; i<info->sector_count; i++ ) {
			info->start[i] = info->start[i-1] + sector_size;
		}

		/* DJ FIXME */
		/* Need to get actual protection status out of the device */
		/* Assume for now that the physical device is unprotected */

/*		read_sector_protection_register (base); */

		/* indicate each sector is protected */
		/* this is an image and does not reflect the */
		/* device's real protect state at this moment */
		for( i=0; i<info->sector_count; i++ ) {
			info->protect[i] = 1;
		}

	}
	else {
		printf ("Sorry, serial Flash device ID %lX not supported on bank %d\n", device_id, banknum+1);
	}

	return info->size;
}

/*
 * Function prints the contents of 1 entry of the flash_info[]
 *
 * Inputs:
 *	info = pointer to the flash_info[] entry for the bank under investigation
 * Outputs:
 *	none
 */
void flash_print_info (flash_info_t * info)
{
	int i;

	debugX( 2, "flash_print_info called\n" );	

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

	printf (" flash_id = %lX\n", info->flash_id );	

	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) {
#ifdef CFG_FLASH_EMPTY_INFO
		int k;
		int size;
		int erased;
		volatile unsigned long *flash;

		/*
		 * Check if whole sector is erased
		 */
		if (i != (info->sector_count - 1))
			size = info->start[i + 1] - info->start[i];
		else
			size = info->start[0] + info->size - info->start[i];
		erased = 1;
		flash = (volatile unsigned long *) info->start[i];
		size = size >> 2;	/* divide by 4 for longword access */
		for (k = 0; k < size; k++) {
			if (*flash++ != 0xffffffff) {
				erased = 0;
				break;
			}
		}

		if ((i % 5) == 0)
			printf ("\n");
		/* print empty and read-only info */
		printf (" %08lX%s%s",
			info->start[i],
			erased ? " E" : "  ",
			info->protect[i] ? "RO " : "   ");
#else	/* ! CFG_FLASH_EMPTY_INFO */
		if ((i % 5) == 0)
			printf ("\n   ");
		printf (" %08lX%s",
			info->start[i], info->protect[i] ? " (RO)" : "     ");
#endif
	}
	printf ("\n");
	return;
}


/*
 * Function erases multiple sectors
 *
 * Inputs:
 *	info = pointer to the flash_info[] entry for the bank under investigation
 *	s_first = first sector number to erase
 *	s_last = last sector number to erase
 * Outputs:
 *	returns status = 0 for OK, else ERROR
 */
int flash_erase (flash_info_t * info, int s_first, int s_last)
{
	int prot;
	flash_sect_t sect;

	debugX( 2, "flash_erase called\n" );	
	debugX( 2, "erasing from sector %d to %d\n", s_first, s_last );	

	if (info->flash_id == FLASH_UNKNOWN) {
		printf ("Can't erase unknown flash type - aborted\n");
		return ERR_UNKNOWN_FLASH_TYPE;
	}
	if ((s_first < 0) || (s_first > s_last)) {
		printf ("- no sectors to erase\n");
		return ERR_NOT_ERASED;
	}

	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");
	}

	for (sect = s_first; sect <= s_last; sect++) {
		if (info->protect[sect] == 0) { /* not protected */

			if ( erase_flash_sector (info->start[0], info->start[sect]) != 0) {
				/* error occurred so exit */ 
				return ERR_NOT_ERASED;
			}
		}
	}

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


/*
 * Function writes to the flash buffer and then programs the sector
 *
 * Inputs:
 *	info = pointer to the flash_info[] entry for the bank under investigation
 *	src = pointer to the base location of the code to be copied 
 *	addr = base address of where the code is to be copied into the flash (buffer) 
 *	cnt = number of bytes to copy
 * Outputs:
 *	return status code = 0 for OK, else ERROR
 */
int write_buff (flash_info_t * info, uchar * src, ulong addr, ulong cnt)
{
	ulong word_count;
	ulong source_addr;
	ulong dest_addr;
	ulong offset;
	int status = GOOD_PAGE;
	ulong copy_count;
	ulong verify_count;
	volatile ulong * verify_source_ptr;
	volatile ulong * verify_destination_ptr;
	int verify_result = 0;

	debugX( 2, "write_buff called\n" );	
	debug( "\nsource address = %p, flash address = %lX, number of bytes to copy = %lX (hex)\n", src, addr, cnt );	

	word_count = cnt >> 2;	/* convert to words ( 4 bytes ) */

	if (word_count << 2 != cnt) {
		/* Byte count is not ending on a word boundary so add 1 word */
		word_count++;
		printf ("Increasing byte count length to nearest word boundary\n");
	}

	verify_count = word_count;

	debug( "Number of words to copy is %lX\n", word_count);

	/* check that src is on a word boundary */
	if ((ulong)src & 0x00000003l) {
		printf ("Error: source address NOT on word boundary\n");
		return ERR_ALIGN;
	}

	source_addr = (ulong)src;	/* convert to words ( 4 bytes ) address */


	/* check that addr is on a word boundary */
	if (addr & 0x00000003l) {
		printf ("Error: destination address NOT on word boundary\n");
		return ERR_ALIGN;
	}

	/* Calculate offset into page, assumes 1024 page size */
	offset = (ulong)(addr & 0x000003ffl);

	/* convert to page base address */
	dest_addr = (ulong)(addr - offset);	/* convert to words ( 4 bytes ) address */

	debug( "offset = %08lX, dest base page = %08lX\n", offset, dest_addr );	

	/* work on a page by page basis */
	
	/* DJ FIXME */
	/* Assume page size is 1024, need to get actual page size */
	/* Destination address is on a page boundary */
	/* offset points to start of new data within the page */

	while ( word_count > 0 && status == GOOD_PAGE) {

		/* do a page copy using word count */
		if ( word_count > (1024 - offset)/4) {
			copy_count = (1024 - offset)/4;
		}
		else {
			copy_count = word_count;
		}

		status = write_page_to_flash (info->start[0], dest_addr, offset, source_addr, copy_count);

		/* Advance the page address to the next page ( in bytes )*/
		dest_addr += 1024;
		source_addr += 1024 - offset;

		/* offset only required on the first page so clear it */
		offset = 0;

		/* count down the words */
		word_count -= copy_count;
	}

	/* verify copy */
	verify_source_ptr = (ulong *)src;	/* point to start of source */
	verify_destination_ptr = (ulong *)addr;	/* point to start of destination */

	while ( verify_count > 0 && verify_result == 0 ) {
	
		if ( *verify_source_ptr != *verify_destination_ptr ) {
			printf ("Error: Copy failed at %p value %08lX, %p value %08lX\n",
				verify_source_ptr, *verify_source_ptr,
				verify_destination_ptr, *verify_destination_ptr);
			verify_result = 1;
		}

		verify_source_ptr++;
		verify_destination_ptr++;
		verify_count--;
	}

	if ( verify_result == 0 ) {
		printf( "\nCopy verified OK\n");
	}
	else {
		/* error occurred */
		return ERR_PROG_ERROR;
	}

	return ERR_OK;	/* All OK */
}


#ifdef CFG_FLASH_PROTECTION
#error Physical Flash device protection not supported, source code incomplete
/*
 * Function physically updates the sector protection state on the device
 *
 * Inputs:
 *	info = pointer to the flash_info[] entry for the bank under investigation
 *	sector = sector number within the bank specified by info
 *	prot = required protection state, 0 = set protection, 1 = clear protection for this sector
 * Outputs:
 *	returns status code = 0 for OK, else ERROR
 */
int flash_real_protect (flash_info_t * info, long sector, int prot)
{

	int retcode = 0;

	debugX( 2, "flash_real_protect called\n" );	

	if (prot) {
		debug( "set protection on sector %d\n", sector );	
	}
	else {
		debug( "clear protection on sector %d\n", sector );	
	}

	/* DJ Fixme */
	/* Physically set or clear the sector protection state on the device itself */

	/* Update the protection RAM mirror copy */
	info->protect[sector] = prot;

#if 0
	flash_write_cmd (info, sector, 0, FLASH_CMD_CLEAR_STATUS);
	flash_write_cmd (info, sector, 0, FLASH_CMD_PROTECT);
	if (prot)
		flash_write_cmd (info, sector, 0, FLASH_CMD_PROTECT_SET);
	else
		flash_write_cmd (info, sector, 0, FLASH_CMD_PROTECT_CLEAR);

	if ((retcode =
	     flash_full_status_check (info, sector, info->erase_blk_tout,
				      prot ? "protect" : "unprotect")) == 0) {

		info->protect[sector] = prot;
		/* Intel's unprotect unprotects all locking */
		if (prot == 0) {
			flash_sect_t i;

			for (i = 0; i < info->sector_count; i++) {
				if (info->protect[i])
					flash_real_protect (info, i, 1);
			}
		}
	}
#endif
	return retcode;
}
#endif


#endif
