/* Driver for MIU driven ATMEL Flash support
 * Author : Dean Jenkins (Liberte Software Limited)
 * 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
 *
 */

/*                                                     
 * [DJ]
 * This file contains the MIU SPI Flash driver 
 */                                                    
#include <linux/init.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>

#include <pnx833x.h>

/*******************************/
/* Local MIU SPI configuration */
/*******************************/

/* define here the JFFS2 "sector erase size" in number of blocks */
#define JFFS2_ERASE_SIZE	(64l*1024l)	/* 64K */
#define BLOCKS_PER_SECTOR	(JFFS2_ERASE_SIZE/(8l*1024l))

#define FLASH_SIZE		0x00800000l	/* 8M Bytes */

#define CFG_MAX_FLASH_SECT	( FLASH_SIZE / JFFS2_ERASE_SIZE )
#define CFG_MAX_FLASH_BANKS	1
#define CFG_FLASH_BASE		0xB8000000

#define BUSY_DELAY		400

/* use kernel printk function for debug information */

#if ( CONFIG_MTD_MIU_SPI_DEBUG >= 1 )
#define debug(fmt,args...)	printk(KERN_DEBUG fmt,##args)
#else
#define debug(fmt,args...)
#endif

#if ( CONFIG_MTD_MIU_SPI_DEBUG >= 2 )
#define debugX(level,fmt,args...)	printk(KERN_DEBUG fmt,##args)
#else
#define debugX(level,fmt,args...)
#endif


/***********************/
/* Local MIU SPI types */
/***********************/

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 */
	unsigned char protect[CFG_MAX_FLASH_SECT]; /* sector protection status	*/
} flash_info_t;

typedef unsigned long flash_sect_t;

typedef unsigned char	uchar;

/************************************/
/* Exported functions for MTD API   */
/************************************/

static int miu_spi_read( struct mtd_info *mtd, loff_t from,
			size_t len, size_t *retlen, u_char *buf );
static int miu_spi_write(struct mtd_info *mtd,loff_t to,
			 size_t len, size_t *retlen, const u_char *buf );
static int miu_spi_erase(struct mtd_info *mtd, struct erase_info *instr);


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

/* low level 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, ulong block_count);
static int write_page_to_flash (ulong base, ulong flash_base_page, ulong offset, ulong data_base,
				 unsigned int length);

/* low level wrapper functions */
static unsigned long flash_init (flash_info_t **flash_info_ptr);
static int flash_erase (flash_info_t * info, int s_first, int s_last);
static int write_buff (flash_info_t * info, uchar * src, ulong addr, ulong cnt);


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



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

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

#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 from boot-up/reset */
/* this should be a read command */
static ulong initial_config = 0x0000001El;	/* assumed RESET value */


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

/* MIU #defines */

/* 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 BAD_LENGTH	2

#define FLASH_UNKNOWN	0

/*
 * 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;

	/* save the initial SPI command, it is a slow read command */
	initial_config = 0x0000001E;

	debug( "  Original miu_sel0 = %08lX\n", PNX833X_MIU_SEL0 );
	debug( "  Original miu_config = %08lX\n", PNX833X_MIU_CONFIG_SPI );

	/* Enabling burst mode with 128 byte burst and not(PCP/2) clock */
	/* Note: burst mode is required for correct device operation */
	PNX833X_MIU_SEL0 = 0x44E80;

	debug( "  New miu_sel0 = %08lX\n", PNX833X_MIU_SEL0 );
	debug( "  New miu_config = %08lX\n", initial_config );

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

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

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

	printk (KERN_ALERT "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_SPI = 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_CONFIG_SPI(opcode, data_enable, addr_enable, sync) */
	PNX833X_WRITE_CONFIG_SPI(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_SPI = initial_config;

	/* sanity, check fixed bits of the status register */
	if ((status_register & SR_FIXED) != SR_FIXED) {
		printk (KERN_ALERT "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, "Comparing 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_CONFIG_SPI(opcode, data_enable, addr_enable, sync) */
		PNX833X_WRITE_CONFIG_SPI(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_CONFIG_SPI(opcode, data_enable, addr_enable, sync) */
		PNX833X_WRITE_CONFIG_SPI(0x61, 0, 1, 0);
	}
	else {
		printk (KERN_ALERT "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;

	/* 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;
		printk (KERN_ALERT "Page compare FAILED\n");
	}
	else {
		/* Compare matched */
		compare_result = COMP_OK;
		debugX ( 2, "Page compare MATCHED\n");
	}

	/* Set the original settings back so that is back to a known state */
	PNX833X_MIU_CONFIG_SPI = 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_CONFIG_SPI(opcode, data_enable, addr_enable, sync) */
		PNX833X_WRITE_CONFIG_SPI(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_CONFIG_SPI(opcode, data_enable, addr_enable, sync) */
		PNX833X_WRITE_CONFIG_SPI(0x55, 0, 1, 0);
	}
	else {
		printk (KERN_ALERT "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_SPI = initial_config;
}


/*
 * Function erases a sector of the Serial Flash
 * Modified to be able to use a "JFFS2 erase sector size" based on the erase block size
 *
 * Inputs:
 *	base = base address of the device
 *	base_sector = base address of the first block to erase in the Serial Flash
 *			for sector erase base_sector should be on a sector boundary
 *			for block erase base_sector should be on a block boundary
 * Outputs:
 *	returns the status
 */
static int erase_flash_sector (ulong base, ulong base_sector, ulong block_count)
{
	int status = 0;

	/* DJ FIXME */
	/* need to allow for page size of 1056 */
	/* 1 sector has 32 blocks, 1 block has 8 pages, each page is 1024 bytes */
	ulong verify_count = (block_count * 8l * 1024l)/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 < block_count; 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_CONFIG_SPI(opcode, data_enable, addr_enable, sync) */
		PNX833X_WRITE_CONFIG_SPI(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 */

		/* Make a delay before reading status register by printing something */
		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_SPI = 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 ) {
			printk (KERN_ALERT "Error: Erase failed at %p value %08lX\n",
				verify_ptr, *verify_ptr);
			verify_result = 1;
		}

		verify_ptr++;
		verify_count--;
	}

	if ( verify_result == 0 ) {
		debug ("Sector Erase for %08lX verified OK\n", base_sector);
	}
	else {
		/* 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 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 *word_page_ptr;
	ulong *word_data_ptr;
	volatile uchar *byte_page_ptr;
	uchar *byte_data_ptr;
	unsigned int loop;
	ulong src_byte_offset;
	ulong dest_byte_offset;

	/* 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) {
		printk (KERN_ALERT "\nError: Failed to transfer main memory page into buffer 1 in write_page_to_flash()\n");
		status = BAD_PAGE;
	}

	/* check whether the length exceeds the page size */
	if((length + offset) > 1024) {
		printk (KERN_ALERT "Error: write_page_to_flash: length %04X + offset %08lX exceeds page size of 1024\n",
			length, offset);
		status = BAD_LENGTH;
	}

	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_CONFIG_SPI(opcode, data_enable, addr_enable, sync) */
		PNX833X_WRITE_CONFIG_SPI(0x84, 1, 1, 0);

		debugX( 2, "  Writing to buffer 1 at address %08lX\n", flash_base_page);

		/* first check the misalignment of the source and destination addresses */
		src_byte_offset = data_base & 0x3l;
		dest_byte_offset = (flash_base_page + offset) & 0x3l;

		if ((src_byte_offset == 0) &&
			(dest_byte_offset == 0) &&
			((length & 0x3l) == 0)) {

			/* if source and dest on word boundaries and */
			/* length is a whole word then use word access */

			/* Note further optimisation is possible if */
			/* both source and dest are on the same word misalignment */
			/* special treatment required for first word copy */

			/* Note further optimisation is possible if */
			/* the length is not a whole number of words */
			/* special treatment required for last word copy */

			debug("write_page_to_flash: WORD copy used\n");

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

			for (loop = 0; loop < (length/4); loop++) {
				*word_page_ptr++ = *word_data_ptr++;	/* get data and write to buffer 1 */
			}
		}
		else {
			/* if source and destination have different word misalignments */
			/* must use byte copy */

			debug("write_page_to_flash: BYTE copy used\n");

			/* write to the buffer using byte access */
			byte_page_ptr = (uchar *)(flash_base_page + offset);
			byte_data_ptr = (uchar *)data_base;

			for (loop = 0; loop < length; loop++) {
				*byte_page_ptr++ = *byte_data_ptr++;	/* get data and write to buffer 1 */
			}
		}

		udelay(100);

		debug ("Write 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_CONFIG_SPI(opcode, data_enable, addr_enable, sync) */
		PNX833X_WRITE_CONFIG_SPI(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) {
			printk (KERN_ALERT "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_SPI = initial_config;

	return status;
}


/*****************************************************************************/
/* Wrapper Functions for low level for Flash operations                      */
/*****************************************************************************/

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

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

	/* return a pointer to the flash_info array */
	*flash_info_ptr = &flash_info[0];

	/* 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);
	}

	return (size);
}


/*
 * 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 */

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

		printk (KERN_ALERT "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 ) {
			/* handle page size of 1024 bytes */
			printk (KERN_ALERT "Flash page size is 1024 bytes\n" );

			sector_size = JFFS2_ERASE_SIZE;	/* 1024 page size only */

			info->size = FLASH_SIZE;

			/* Define the number of erasable units */
			info->sector_count = info->size / JFFS2_ERASE_SIZE ;

			info->flash_id = 0x1F280000l;	/* from data sheet */

			info->start[0] = base;

			/* don't care about sector 0a and 0b special treatment */
			/* as sector erase is NOT used, block erase is used. */

			/* 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); */

			/* FIXME make all sectors UNPROTECTED in this driver */
			/* indicate each sector is unprotected */
			/* 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] = 0;
			}
		}
		else {
			/* handle page size of 1056 bytes */
			printk (KERN_ALERT "Sorry, Flash page size is 1056 bytes and is NOT supported\n");
		}
	}
	else {
		printk (KERN_ALERT "Sorry, serial Flash device ID %lX not supported on bank %d\n", device_id, banknum+1);
	}

	return info->size;
}


/*
 * 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 rcode = 0 for OK, else ERROR
 */
static int flash_erase (flash_info_t * info, int s_first, int s_last)
{
	int rcode = 0;
	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) {
		printk (KERN_ALERT "Can't erase unknown flash type - aborted\n");
		return 1;
	}
	if ((s_first < 0) || (s_first > s_last)) {
		printk (KERN_ALERT "- no sectors to erase\n");
		return 1;
	}

	prot = 0;
	for (sect = s_first; sect <= s_last; ++sect) {
		if (info->protect[sect]) {
			prot++;
		}
	}
	if (prot) {
		debug ("- Warning: %d protected sectors will not be erased!\n", prot);
	}

	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], BLOCKS_PER_SECTOR) != 0) {
				/* error occurred so exit */ 
				return 1;
			}
		}
	}

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


/*
 * 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
 */
static int write_buff (flash_info_t * info, uchar * src, ulong addr, ulong cnt)
{
	ulong byte_count;
	ulong source_addr;
	ulong dest_addr;
	ulong offset;
	int status = GOOD_PAGE;
	ulong copy_count;
	ulong verify_count;
	volatile uchar *verify_source_ptr;
	volatile uchar *verify_destination_ptr;
	int verify_result = 0;

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

	byte_count = cnt;

	verify_count = byte_count;

	debug( "Number of bytes to copy is %lX\n", byte_count);

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

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

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

	debugX( 2, "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 */

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

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

		/* count down the bytes for next loop */
		byte_count -= copy_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;
	}

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

	while ( verify_count > 0 && verify_result == 0 ) {
	
		if ( *verify_source_ptr != *verify_destination_ptr ) {
			printk (KERN_ALERT "Error: write_buff failed at %p value %02X, %p value %02X, count %08lX\n",
				verify_source_ptr, *verify_source_ptr,
				verify_destination_ptr, *verify_destination_ptr,
				verify_count);
			verify_result = 1;
		}

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

	if ( verify_result == 0 ) {
		debug ( "Copy verified OK\n");
	}
	else {
		/* error occurred */
		return 1;
	}

	return 0;	/* All OK */
}


/*****************************************************************************/
/* Exported Functions and structures for MTD API for Flash operations        */
/*****************************************************************************/

/* Atomic state variable */

typedef enum {
	DRIVER_INIT=0,
	DRIVER_READY,
	DRIVER_READING,
	DRIVER_WRITING,
	DRIVER_ERASING
} driver_state_t;

/* driver_state is an atomic variable to detect and prevent */
/* re-entrancy of driver operations */
static atomic_t atomic_driver_state = ATOMIC_INIT( DRIVER_INIT );

/* entry counter */
static atomic_t atomic_entry_counter = ATOMIC_INIT( 0 );

/* exit counter, set to 1 to avoid use of overflow detection routines */
/* exit counter indicates the next expected entry number */
static atomic_t atomic_exit_counter = ATOMIC_INIT( 1 );

/* declare a wait queue */
DECLARE_WAIT_QUEUE_HEAD(wait_event);

static struct mtd_partition atmel_45_patitions[] = {
	{
		name:	"uboot",
		offset:	0x00000000,
		size:	0x00040000,
	},
	{
		name:	"uboot-env",
		offset:	0x00040000,
		size:	0x00040000,
	},
	{
		name:	"uImage",
		offset:	0x00080000,
		size:	0x00200000,
	},
	{
		name:	"spare",
		offset:	0x00280000,
		size:	0x00080000,
	},
	{
		name:	"JFFS2",
		offset:	0x00300000,
		size:	0x00500000,
	}
};

#define NUM_STATIC_PARTITIONS 		ARRAY_SIZE(atmel_45_patitions)

/* use Kernel command line mtdparts cmdlinepart function */
const char *part_probes[] = { "cmdlinepart", NULL,  };

static struct mtd_info *mtd_info_ptr = NULL;

/*
 * Function interfaces to the MTD API to allow Flash erase
 *
 * Inputs:
 *	mtd_info = pointer to mtd info struct, pass all device details through here
 *	instr = pointer to erase_info struct
 * Outputs:
 *	return status code
 */
static int miu_spi_erase(struct mtd_info *mtd, struct erase_info *instr)
{
	flash_info_t *flash_info_array;
	int start = 0;
	int end = 0;
	int sector;
	int good_sector;
	int ret_code = 0;
	driver_state_t current_state;
	int current_entry;

	debug("miu_spi_erase called\n");

	/* each access has a unique counter value */
	/* accesses must be done in correct sequence */
	current_entry = atomic_add_return(1, &atomic_entry_counter);

	debug("ENTRY ERASE# Entry: %d, Exit: %d\n", current_entry, atomic_read( &atomic_exit_counter ));

	/* wait until previous request has exited */
	wait_event(wait_event, (current_entry) == atomic_read( &atomic_exit_counter ));

	/* Interlock, protected by only one process running from wait queue */

	/* get the driver state */
	current_state = atomic_read( &atomic_driver_state );

	if (current_state != DRIVER_READY ) {
		printk(KERN_ALERT "Error: miu_spi_erase: trying to ERASE in wrong state %d\n",
			current_state);
		BUG();
	}

	/* driver is READY so change to ERASE state */
	atomic_set( &atomic_driver_state, DRIVER_WRITING );

	/* Start address must align on block boundary */
	if (instr->addr & (mtd->erasesize - 1)) 
	{
		printk (KERN_ALERT "miu_spi_erase: Address %08lX not on sector boundary\n", (ulong)instr->addr);
		ret_code = -EINVAL;
		goto exit_error;
	}

	/* Length must align on block boundary */
	if (instr->len & (mtd->erasesize - 1)) 
	{
		printk (KERN_ALERT "miu_spi_erase: Length %08lX not sector aligned\n", (ulong)instr->len);
		ret_code = -EINVAL;
		goto exit_error;
	}

	/* Do not allow erase past end of device */
	if ((instr->len + instr->addr) > mtd->size) 
	{
		printk (KERN_ALERT "miu_spi_erase: requested erase goes past end of device\n");
		ret_code = -EINVAL;
		goto exit_error;
	}

	/* retrieve a pointer to the device info array */
	flash_info_array = (flash_info_t *)mtd->priv;

	/* Determine the sector numbers to erase */
	good_sector = 0;

	for (sector = 0; sector < flash_info_array[0].sector_count; sector++) {
		if ( flash_info_array[0].start[sector] == instr->addr + CFG_FLASH_BASE ) {
			start = sector;
			good_sector = 1;
			break;
		}
	}

	if ( good_sector == 0 ) {
		printk (KERN_ALERT "miu_spi_erase: start sector not found in device info, , address %08lX\n",
			(ulong)instr->addr);
		ret_code = -EINVAL;
		goto exit_error;
	}

	good_sector = 0;

	for (sector = start; sector < flash_info_array[0].sector_count; sector++) {
		if (flash_info_array[0].start[sector] + JFFS2_ERASE_SIZE == instr->addr + CFG_FLASH_BASE + instr->len) {
			end = sector;
			good_sector = 1;
			break;
		}
	}

	if ( good_sector == 0 ) {
		printk (KERN_ALERT "miu_spi_erase: end sector not found in device info, address %08lX, length %08lX\n",
			(ulong)instr->addr, (ulong)instr->len);
		ret_code = -EINVAL;
		goto exit_error;
	}

	debug("Erasing %d sectors from start address %08lX\n", end-start+1, (ulong) instr->addr);

	instr->state = MTD_ERASE_DONE;

	/* do the erase */
	if (flash_erase (flash_info_array, start, end) != 0) {
		ret_code = -EIO;
		goto exit_error;
	}

	/* erase was successful so call the call back function */
	if (instr->callback) {
		instr->callback (instr);
	}

exit_error:
	/* driver is finished ERASING so change to READY state */
	atomic_set( &atomic_driver_state, DRIVER_READY );

	/* count the exits */
	atomic_add(1,&atomic_exit_counter);

	/* Interlock end */

        /* wake up the waiting requests */
        wake_up(&wait_event);

	debug("EXIT ERASE# Entry: %d, Exit: %d\n", current_entry, atomic_read( &atomic_exit_counter ));

	return ret_code;
}


/*
 * Function interfaces to the MTD API to allow Flash writes
 * uses auto erase so not separate erase command required
 *
 * Inputs:
 *	mtd = pointer to mtd info struct, pass all device details through here
 *	to = is relative address to base of device in long long type
 *	len = length in bytes in unsigned int type
 *	buf = pointer to input buffer
 * Outputs:
 *	retlen = unsigned int pointer to return number of bytes copied
 *	return status code
 */
static int miu_spi_write(struct mtd_info *mtd,loff_t to,
			 size_t len, size_t *retlen, const u_char *buf )
{
	flash_info_t *flash_info_array;
	int result;
	int ret_code = 0;
	driver_state_t current_state;
	int current_entry;

	debug("miu_spi_write called\n");

	debug("From is %llX, len is %X\n", to, len);

	/* each access has a unique counter value */
	/* accesses must be done in correct sequence */
	current_entry = atomic_add_return(1, &atomic_entry_counter);

	debug("ENTRY WRITE# Entry: %d, Exit: %d\n", current_entry, atomic_read( &atomic_exit_counter ));

	/* wait until previous request has exited */
	wait_event(wait_event, (current_entry) == atomic_read( &atomic_exit_counter ));

	/* Interlock, protected by only one process running from wait queue */

	/* get the driver state */
	current_state = atomic_read( &atomic_driver_state );

	if (current_state != DRIVER_READY ) {
		printk(KERN_ALERT "Error: miu_spi_write: trying to WRITE in wrong state %d\n",
			current_state);
		BUG();
	}

	/* driver is READY so change to WRITE state */
	atomic_set( &atomic_driver_state, DRIVER_WRITING );

	/* retrieve a pointer to the device info array */
	flash_info_array = (flash_info_t *)mtd->priv;

	debugX(2, "Device id = %lX\n", flash_info_array[0].flash_id);

	/* translate address to base of Flash */
	to += CFG_FLASH_BASE;

	/* write with auto erase to Flash */
	result = write_buff (flash_info_array, (uchar *)buf, to, len);

	if (result != 0) {
		len = 0;		/* Indicate failed to write bytes */
		ret_code = -EIO;	/* Indicate IO error */
	}

	*retlen = len;

	/* driver is finished WRITE so change to READY state */
	atomic_set( &atomic_driver_state, DRIVER_READY );

	/* count the exits */
	atomic_add(1,&atomic_exit_counter);

	/* Interlock end */

        /* wake up the waiting requests */
        wake_up(&wait_event);

	debug("EXIT WRITE# Entry: %d, Exit: %d\n", current_entry, atomic_read( &atomic_exit_counter ));

	return ret_code;
}


/*
 * Function interfaces to the MTD API to allow Flash reads
 *
 * Inputs:
 *	mtd = pointer to mtd info struct, pass all device details through here
 *	from = is relative address to base of device in long long type
 *	len = length in bytes in unsigned int type
 *	buf = pointer to output buffer
 * Outputs:
 *	retlen = unsigned int pointer to return number of bytes copied
 *	return status code
 */
static int miu_spi_read( struct mtd_info *mtd, loff_t from,
			size_t len, size_t *retlen, u_char *buf )
{
	volatile unsigned char * flash_ptr;
	driver_state_t current_state;
	int current_entry;

	debug("miu_spi_read called\n");

	debug("From is %llX, len is %X\n", from, len);

	/* each access has a unique counter value */
	/* accesses must be done in correct sequence */
	current_entry = atomic_add_return(1, &atomic_entry_counter);

	debug("ENTRY READ# Entry: %d, Exit: %d\n", current_entry, atomic_read( &atomic_exit_counter ));

	/* wait until previous request has exited */
	wait_event(wait_event, (current_entry) == atomic_read( &atomic_exit_counter ));

	/* Interlock, protected by only one process running from wait queue */

	/* get the driver state */
	current_state = atomic_read( &atomic_driver_state );

	if (current_state != DRIVER_READY ) {
		printk(KERN_ALERT "Error: miu_spi_read: trying to READ in wrong state %d\n",
			current_state);
		BUG();
	}

	/* driver is READY so change to READ state */
	atomic_set( &atomic_driver_state, DRIVER_READING );

	/* read from the MIU SPI Flash device */
	/* assumes SPI opcode is already set to continuous read mode */

	/* translate address to base of Flash */
	from += CFG_FLASH_BASE;

	/* set a pointer into flash */
	flash_ptr = (volatile unsigned char *)((ulong)from);

	/* record the number of bytes copied */
	*retlen = len;

	/* read data from the flash */
	for ( ; len > 0; len-- ) {
		*buf++ = *flash_ptr++;
	}

	/* driver is finished READING so change to READY state */
	atomic_set( &atomic_driver_state, DRIVER_READY );

	/* count the exits */
	atomic_add(1,&atomic_exit_counter);

	/* Interlock end */

        /* wake up the waiting requests */
        wake_up(&wait_event);

	debug("EXIT READ# Entry: %d, Exit: %d\n", current_entry, atomic_read( &atomic_exit_counter ));

	return 0;
}


static int __init miu_spi_init(void)
{
	flash_info_t *flash_info_array;
	int mtd_parts_nb = 0;
	const char *part_type = NULL;
	struct mtd_partition *mtd_parts = 0;
	int rcode = 0;
	atomic_t *atomic_driver_state_ptr = &atomic_driver_state;
	driver_state_t current_state;

	printk(KERN_ALERT "Detecting MIU SPI flash...\n");

	/* get the driver state */
	current_state = atomic_read( atomic_driver_state_ptr );

	if (current_state != DRIVER_INIT ) {
		printk(KERN_ALERT "Error: miu_spi_init: trying to initialise driver in wrong state %d\n",
			current_state);
		BUG();
	}

	if (mtd_info_ptr != NULL) {
		printk(KERN_ALERT "Warning: flash_info_ptr not NULL\n");
	}

	/* returns a pointer to the flash_info array */
	if (flash_init ( &flash_info_array ) > 0) {
		/* Flash was found so register partitions */
		mtd_info_ptr = kmalloc( sizeof(struct mtd_info), GFP_KERNEL);

		if (mtd_info_ptr != NULL) {
			/* now fill in the device details */

			/* zero out the structure ready for populating */
			memset((char *)mtd_info_ptr, 0, sizeof(struct mtd_info));

			/* Indicate the name */
			if (flash_info_array[0].flash_id == 0x1F280000l) {
				/* name needs to match Kernel command line mtdparts mtd-id */
				mtd_info_ptr->name = "Flash";
			}
			else {
				mtd_info_ptr->name = "Name of FLASH part unknown";
			}

			/* Indicate the smallest erasable size */
			mtd_info_ptr->erasesize = JFFS2_ERASE_SIZE;

			/* Indicate the smallest writable size */
			mtd_info_ptr->writesize = 1;

			/* Indicate the total size of the part */
			mtd_info_ptr->size = flash_info_array[0].size;

			/* Indicate the type of Flash part */
			mtd_info_ptr->type = MTD_DATAFLASH;

			/* Indicate the Flash part's capabilities */
			/* MTD_CAP_NORFLASH = MTD_CLEAR_BITS|MTD_ERASEABLE */
			mtd_info_ptr->flags = MTD_CAP_NORFLASH;

			/* Indicate that this module owns the structure */
			mtd_info_ptr->owner = THIS_MODULE;

			/* Indicate the Flash's read function */
			mtd_info_ptr->read = miu_spi_read;

			/* Indicate the Flash's write function */
			mtd_info_ptr->write = miu_spi_write;

			/* Indicate the Flash's erase function */
			mtd_info_ptr->erase = miu_spi_erase;

			/* Indicate the Flash's sync function */
			mtd_info_ptr->sync = NULL;

			/* perhaps should use these for XIP ? */
			mtd_info_ptr->point = NULL;
			mtd_info_ptr->unpoint = NULL;

			/* not supported */
			mtd_info_ptr->lock = NULL;
			mtd_info_ptr->unlock = NULL;
			mtd_info_ptr->suspend = NULL;
			mtd_info_ptr->resume = NULL;

			/* indicate this is bank #1 */
			mtd_info_ptr->index=0;

			/* save a pointer to the device info */
			mtd_info_ptr->priv = flash_info_array;

#ifdef CONFIG_MTD_CMDLINE_PARTS
			/* get the dynamic mtdparts Kernel command line settings */
			/* returns the number of partitions or negative if error occurred */
			mtd_parts_nb = parse_mtd_partitions(mtd_info_ptr, part_probes,
					&mtd_parts, 0);

			if (mtd_parts_nb < 0) {
				/* Note: -EINVAL returned when no mtdparts found */
				debug ("mtdparts command parsing returned error code %d\n",
					mtd_parts_nb);
				/* error occurred so indicate no mtdparts */
				mtd_parts_nb = 0;
			}

			if (mtd_parts_nb > 0) {
				part_type = "Kernel command line mtdparts";
			}
#endif

			if (mtd_parts_nb == 0)
			{
				/* if no command line settings then use static settings */
				mtd_parts = (struct mtd_partition*)atmel_45_patitions;
				mtd_parts_nb = (int) NUM_STATIC_PARTITIONS;
				part_type = "static";
			}
	
			printk(KERN_NOTICE "Using %s partition definition\n", part_type);

			/* register mtd device with partition info */
			rcode = add_mtd_partitions(mtd_info_ptr, mtd_parts, mtd_parts_nb);

			if (rcode == 0) {
				/* driver is intialised OK so change to ready state */
				atomic_set( atomic_driver_state_ptr, DRIVER_READY );
			}

			return rcode;
		}
		else {
			printk(KERN_ALERT "Unable to allocate memory for MTD MIU SPI Serial Flash Device\n");
			return -ENOMEM;
		}
	}
	else {
		printk(KERN_NOTICE "Error: No MIU SPI Flash detected\n");
	}

	return 0;
}

#ifdef MODULE
static void __exit miu_spi_exit(void)
{
	int ret;

	printk(KERN_ALERT "removing MIU SPI Flash driver...\n");

	if (mtd_info_ptr == NULL) {
		printk(KERN_ALERT "Error: flash_info_ptr NULL when exiting\n");
	}

/* either del device ( if no partitions ) or del partitions but don't do both */
#if 0
	/* Unregister the device */
	printk(KERN_ALERT "MIU SPI Flash device unregistering...\n");
 	ret = del_mtd_device (mtd_info_ptr);
	printk(KERN_ALERT "...result is %d\n", ret);
#endif

	printk(KERN_ALERT "MIU SPI Flash device deleting partitions...\n");
	ret = del_mtd_partitions(mtd_info_ptr);
	printk(KERN_ALERT "...result is %d\n", ret);

	/* Free the MTD device structure */
	printk(KERN_ALERT "MIU SPI Flash drive freeing memory...\n");
	kfree (mtd_info_ptr);

	printk(KERN_ALERT "MIU SPI Flash driver removed OK\n");
}

module_exit(miu_spi_exit);
#endif

module_init(miu_spi_init);

MODULE_LICENSE ("GPL");
MODULE_AUTHOR ("dean.jenkins@libertesoftware.com");
MODULE_DESCRIPTION ("MIU SPI Flash driver");
