Skip to content
Snippets Groups Projects
cfi_flash.c 32.8 KiB
Newer Older
  • Learn to ignore specific revisions
  • Wolfgang Denk's avatar
    Wolfgang Denk committed
     * (C) Copyright 2002-2004
    
     * Brad Kemp, Seranoa Networks, Brad.Kemp@seranoa.com
     *
     * Copyright (C) 2003 Arabella Software Ltd.
     * Yuli Barcohen <yuli@arabellasw.com>
     * Modified to work with AMD flashes
     *
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
     * Copyright (C) 2004
     * Ed Okerson
     * Modified to work with little-endian systems.
     *
    
     * 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
     *
     * History
     * 01/20/2004 - combined variants of original driver.
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
     * 01/22/2004 - Write performance enhancements for parallel chips (Tolunay)
     * 01/23/2004 - Support for x8/x16 chips (Rune Raknerud)
     * 01/27/2004 - Little endian support Ed Okerson
    
     *
     * Tested Architectures
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
     * Port Width  Chip Width    # of banks	   Flash Chip  Board
    
     * 32	       16	     1		   28F128J3    seranoa/eagle
     * 64	       16	     1		   28F128J3    seranoa/falcon
    
     */
    
    /* The DEBUG define must be before common to enable debugging */
    
    /* #define DEBUG	*/
    
    
    #include <common.h>
    #include <asm/processor.h>
    
    #include <asm/byteorder.h>
    
    #include <linux/byteorder/swab.h>
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    #ifdef	CFG_FLASH_CFI_DRIVER
    
    /*
     * This file implements a Common Flash Interface (CFI) driver for U-Boot.
     * The width of the port and the width of the chips are determined at initialization.
     * These widths are used to calculate the address for access CFI data structures.
     * It has been tested on an Intel Strataflash implementation and AMD 29F016D.
     *
     * References
     * JEDEC Standard JESD68 - Common Flash Interface (CFI)
     * JEDEC Standard JEP137-A Common Flash Interface (CFI) ID Codes
     * Intel Application Note 646 Common Flash Interface (CFI) and Command Sets
     * Intel 290667-008 3 Volt Intel StrataFlash Memory datasheet
     *
     * TODO
     *
     * Use Primary Extended Query table (PRI) and Alternate Algorithm Query
     * Table (ALT) to determine if protection is available
     *
     * Add support for other command sets Use the PRI and ALT to determine command set
     * Verify erase and program timeouts.
     */
    
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    #ifndef CFG_FLASH_BANKS_LIST
    #define CFG_FLASH_BANKS_LIST { CFG_FLASH_BASE }
    #endif
    
    
    #define FLASH_CMD_CFI			0x98
    #define FLASH_CMD_READ_ID		0x90
    #define FLASH_CMD_RESET			0xff
    #define FLASH_CMD_BLOCK_ERASE		0x20
    #define FLASH_CMD_ERASE_CONFIRM		0xD0
    #define FLASH_CMD_WRITE			0x40
    #define FLASH_CMD_PROTECT		0x60
    #define FLASH_CMD_PROTECT_SET		0x01
    #define FLASH_CMD_PROTECT_CLEAR		0xD0
    #define FLASH_CMD_CLEAR_STATUS		0x50
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    #define FLASH_CMD_WRITE_TO_BUFFER	0xE8
    #define FLASH_CMD_WRITE_BUFFER_CONFIRM	0xD0
    
    
    #define FLASH_STATUS_DONE		0x80
    #define FLASH_STATUS_ESS		0x40
    #define FLASH_STATUS_ECLBS		0x20
    #define FLASH_STATUS_PSLBS		0x10
    #define FLASH_STATUS_VPENS		0x08
    #define FLASH_STATUS_PSS		0x04
    #define FLASH_STATUS_DPS		0x02
    #define FLASH_STATUS_R			0x01
    #define FLASH_STATUS_PROTECT		0x01
    
    #define AMD_CMD_RESET			0xF0
    #define AMD_CMD_WRITE			0xA0
    #define AMD_CMD_ERASE_START		0x80
    #define AMD_CMD_ERASE_SECTOR		0x30
    
    #define AMD_CMD_UNLOCK_START		0xAA
    #define AMD_CMD_UNLOCK_ACK		0x55
    
    
    #define AMD_STATUS_TOGGLE		0x40
    #define AMD_STATUS_ERROR		0x20
    
    #define AMD_ADDR_ERASE_START		0x555
    #define AMD_ADDR_START			0x555
    #define AMD_ADDR_ACK			0x2AA
    
    
    #define FLASH_OFFSET_CFI		0x55
    #define FLASH_OFFSET_CFI_RESP		0x10
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    #define FLASH_OFFSET_PRIMARY_VENDOR	0x13
    
    #define FLASH_OFFSET_WTOUT		0x1F
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    #define FLASH_OFFSET_WBTOUT		0x20
    
    #define FLASH_OFFSET_ETOUT		0x21
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    #define FLASH_OFFSET_CETOUT		0x22
    
    #define FLASH_OFFSET_WMAX_TOUT		0x23
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    #define FLASH_OFFSET_WBMAX_TOUT		0x24
    
    #define FLASH_OFFSET_EMAX_TOUT		0x25
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    #define FLASH_OFFSET_CEMAX_TOUT		0x26
    
    #define FLASH_OFFSET_SIZE		0x27
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    #define FLASH_OFFSET_INTERFACE		0x28
    #define FLASH_OFFSET_BUFFER_SIZE	0x2A
    
    #define FLASH_OFFSET_NUM_ERASE_REGIONS	0x2C
    #define FLASH_OFFSET_ERASE_REGIONS	0x2D
    #define FLASH_OFFSET_PROTECT		0x02
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    #define FLASH_OFFSET_USER_PROTECTION	0x85
    #define FLASH_OFFSET_INTEL_PROTECTION	0x81
    
    
    
    #define FLASH_MAN_CFI			0x01000000
    
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    #define CFI_CMDSET_NONE		    0
    
    #define CFI_CMDSET_INTEL_EXTENDED   1
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    #define CFI_CMDSET_AMD_STANDARD	    2
    
    #define CFI_CMDSET_INTEL_STANDARD   3
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    #define CFI_CMDSET_AMD_EXTENDED	    4
    
    #define CFI_CMDSET_MITSU_STANDARD   256
    #define CFI_CMDSET_MITSU_EXTENDED   257
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    #define CFI_CMDSET_SST		    258
    
    
    
    typedef union {
    	unsigned char c;
    	unsigned short w;
    	unsigned long l;
    	unsigned long long ll;
    } cfiword_t;
    
    typedef union {
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	volatile unsigned char *cp;
    
    	volatile unsigned short *wp;
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	volatile unsigned long *lp;
    
    	volatile unsigned long long *llp;
    } cfiptr_t;
    
    #define NUM_ERASE_REGIONS 4
    
    static ulong bank_base[CFG_MAX_FLASH_BANKS] = CFG_FLASH_BANKS_LIST;
    
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    flash_info_t flash_info[CFG_MAX_FLASH_BANKS];	/* info for FLASH chips	  */
    
    
    /*-----------------------------------------------------------------------
     * Functions
     */
    
    typedef unsigned long flash_sect_t;
    
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    static void flash_add_byte (flash_info_t * info, cfiword_t * cword, uchar c);
    static void flash_make_cmd (flash_info_t * info, uchar cmd, void *cmdbuf);
    
    static void flash_write_cmd (flash_info_t * info, flash_sect_t sect, uint offset, uchar cmd);
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    static void flash_unlock_seq (flash_info_t * info, flash_sect_t sect);
    
    static int flash_isequal (flash_info_t * info, flash_sect_t sect, uint offset, uchar cmd);
    static int flash_isset (flash_info_t * info, flash_sect_t sect, uint offset, uchar cmd);
    static int flash_toggle (flash_info_t * info, flash_sect_t sect, uint offset, uchar cmd);
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    static int flash_detect_cfi (flash_info_t * info);
    
    static ulong flash_get_size (ulong base, int banknum);
    
    static int flash_write_cfiword (flash_info_t * info, ulong dest, cfiword_t cword);
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    static int flash_full_status_check (flash_info_t * info, flash_sect_t sector,
    				    ulong tout, char *prompt);
    
    #ifdef CFG_FLASH_USE_BUFFER_WRITE
    
    static int flash_write_cfibuffer (flash_info_t * info, ulong dest, uchar * cp, int len);
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    /*-----------------------------------------------------------------------
     * create an address based on the offset and the port width
     */
    
    inline uchar *flash_make_addr (flash_info_t * info, flash_sect_t sect, uint offset)
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    {
    	return ((uchar *) (info->start[sect] + (offset * info->portwidth)));
    }
    
    
    #ifdef DEBUG
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    /*-----------------------------------------------------------------------
     * Debug support
     */
    void print_longlong (char *str, unsigned long long data)
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    
    	cp = (unsigned char *) &data;
    	for (i = 0; i < 8; i++)
    		sprintf (&str[i * 2], "%2.2x", *cp++);
    }
    static void flash_printqry (flash_info_t * info, flash_sect_t sect)
    {
    	cfiptr_t cptr;
    	int x, y;
    
    	for (x = 0; x < 0x40; x += 16 / info->portwidth) {
    		cptr.cp =
    			flash_make_addr (info, sect,
    					 x + FLASH_OFFSET_CFI_RESP);
    		debug ("%p : ", cptr.cp);
    		for (y = 0; y < 16; y++) {
    			debug ("%2.2x ", cptr.cp[y]);
    		}
    		debug (" ");
    		for (y = 0; y < 16; y++) {
    			if (cptr.cp[y] >= 0x20 && cptr.cp[y] <= 0x7e) {
    				debug ("%c", cptr.cp[y]);
    			} else {
    				debug (".");
    			}
    		}
    		debug ("\n");
    	}
    
    }
    #endif
    
    
    /*-----------------------------------------------------------------------
     * read a character at a port width address
     */
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    inline uchar flash_read_uchar (flash_info_t * info, uint offset)
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    
    	cp = flash_make_addr (info, 0, offset);
    #if defined(__LITTLE_ENDIAN)
    	return (cp[0]);
    #else
    
    	return (cp[info->portwidth - 1]);
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    #endif
    
    }
    
    /*-----------------------------------------------------------------------
     * read a short word by swapping for ppc format.
     */
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    ushort flash_read_ushort (flash_info_t * info, flash_sect_t sect, uint offset)
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	uchar *addr;
    	ushort retval;
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    #ifdef DEBUG
    	int x;
    #endif
    	addr = flash_make_addr (info, sect, offset);
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    #ifdef DEBUG
    	debug ("ushort addr is at %p info->portwidth = %d\n", addr,
    	       info->portwidth);
    	for (x = 0; x < 2 * info->portwidth; x++) {
    		debug ("addr[%x] = 0x%x\n", x, addr[x]);
    	}
    #endif
    #if defined(__LITTLE_ENDIAN)
    	retval = ((addr[(info->portwidth)] << 8) | addr[0]);
    #else
    	retval = ((addr[(2 * info->portwidth) - 1] << 8) |
    		  addr[info->portwidth - 1]);
    #endif
    
    	debug ("retval = 0x%x\n", retval);
    	return retval;
    
    }
    
    /*-----------------------------------------------------------------------
     * read a long word by picking the least significant byte of each maiximum
     * port size word. Swap for ppc format.
     */
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    ulong flash_read_long (flash_info_t * info, flash_sect_t sect, uint offset)
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	uchar *addr;
    	ulong retval;
    
    #ifdef DEBUG
    	int x;
    #endif
    	addr = flash_make_addr (info, sect, offset);
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    #ifdef DEBUG
    	debug ("long addr is at %p info->portwidth = %d\n", addr,
    	       info->portwidth);
    	for (x = 0; x < 4 * info->portwidth; x++) {
    		debug ("addr[%x] = 0x%x\n", x, addr[x]);
    	}
    #endif
    #if defined(__LITTLE_ENDIAN)
    	retval = (addr[0] << 16) | (addr[(info->portwidth)] << 24) |
    
    		(addr[(2 * info->portwidth)]) | (addr[(3 * info->portwidth)] << 8);
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    #else
    	retval = (addr[(2 * info->portwidth) - 1] << 24) |
    		(addr[(info->portwidth) - 1] << 16) |
    		(addr[(4 * info->portwidth) - 1] << 8) |
    		addr[(3 * info->portwidth) - 1];
    #endif
    	return retval;
    
    }
    
    /*-----------------------------------------------------------------------
     */
    unsigned long flash_init (void)
    {
    	unsigned long size = 0;
    	int i;
    
    	/* Init: no FLASHes known */
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	for (i = 0; i < CFG_MAX_FLASH_BANKS; ++i) {
    
    		flash_info[i].flash_id = FLASH_UNKNOWN;
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    		size += flash_info[i].size = flash_get_size (bank_base[i], i);
    
    		if (flash_info[i].flash_id == FLASH_UNKNOWN) {
    
    			printf ("## Unknown FLASH on Bank %d - Size = 0x%08lx = %ld MB\n",
    				i, flash_info[i].size, flash_info[i].size << 20);
    
    		}
    	}
    
    	/* Monitor protection ON by default */
    #if (CFG_MONITOR_BASE >= CFG_FLASH_BASE)
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	flash_protect (FLAG_PROTECT_SET,
    		       CFG_MONITOR_BASE,
    		       CFG_MONITOR_BASE + CFG_MONITOR_LEN - 1,
    		       &flash_info[0]);
    
    #endif
    
    	return (size);
    }
    
    /*-----------------------------------------------------------------------
     */
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    int flash_erase (flash_info_t * info, int s_first, int s_last)
    
    {
    	int rcode = 0;
    	int prot;
    	flash_sect_t sect;
    
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	if (info->flash_id != FLASH_MAN_CFI) {
    
    		puts ("Can't erase unknown flash type - aborted\n");
    
    		return 1;
    	}
    	if ((s_first < 0) || (s_first > s_last)) {
    
    		puts ("- no sectors to erase\n");
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	for (sect = s_first; sect <= s_last; ++sect) {
    
    		if (info->protect[sect]) {
    			prot++;
    		}
    	}
    	if (prot) {
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    		printf ("- Warning: %d protected sectors will not be erased!\n", prot);
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	for (sect = s_first; sect <= s_last; sect++) {
    
    		if (info->protect[sect] == 0) { /* not protected */
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    			switch (info->vendor) {
    
    			case CFI_CMDSET_INTEL_STANDARD:
    			case CFI_CMDSET_INTEL_EXTENDED:
    
    				flash_write_cmd (info, sect, 0, FLASH_CMD_CLEAR_STATUS);
    				flash_write_cmd (info, sect, 0, FLASH_CMD_BLOCK_ERASE);
    				flash_write_cmd (info, sect, 0, FLASH_CMD_ERASE_CONFIRM);
    
    				break;
    			case CFI_CMDSET_AMD_STANDARD:
    			case CFI_CMDSET_AMD_EXTENDED:
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    				flash_unlock_seq (info, sect);
    
    				flash_write_cmd (info, sect, AMD_ADDR_ERASE_START,
    							AMD_CMD_ERASE_START);
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    				flash_unlock_seq (info, sect);
    
    				flash_write_cmd (info, sect, 0, AMD_CMD_ERASE_SECTOR);
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    				debug ("Unkown flash vendor %d\n",
    				       info->vendor);
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    			if (flash_full_status_check
    			    (info, sect, info->erase_blk_tout, "erase")) {
    
    				rcode = 1;
    			} else
    
    	puts (" done\n");
    
    	return rcode;
    }
    
    /*-----------------------------------------------------------------------
     */
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    void flash_print_info (flash_info_t * info)
    
    {
    	int i;
    
    	if (info->flash_id != FLASH_MAN_CFI) {
    
    		puts ("missing or unknown FLASH type\n");
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	printf ("CFI conformant FLASH (%d x %d)",
    		(info->portwidth << 3), (info->chipwidth << 3));
    
    	printf ("  Size: %ld MB in %d Sectors\n",
    		info->size >> 20, info->sector_count);
    
    	printf (" Erase timeout %ld ms, write timeout %ld ms, buffer write timeout %ld ms, buffer size %d\n",
    		info->erase_blk_tout,
    		info->write_tout,
    		info->buffer_write_tout,
    		info->buffer_size);
    
    	puts ("  Sector Start Addresses:");
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	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
    		 */
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    		if (i != (info->sector_count - 1))
    			size = info->start[i + 1] - info->start[i];
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    			size = info->start[0] + info->size - info->start[i];
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    		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
    		if ((i % 5) == 0)
    			printf ("\n   ");
    		printf (" %08lX%s",
    
    			info->start[i], info->protect[i] ? " (RO)  " : "     ");
    
    	return;
    }
    
    /*-----------------------------------------------------------------------
     * Copy memory to flash, returns:
     * 0 - OK
     * 1 - write timeout
     * 2 - Flash not erased
     */
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    int write_buff (flash_info_t * info, uchar * src, ulong addr, ulong cnt)
    
    {
    	ulong wp;
    	ulong cp;
    	int aln;
    	cfiword_t cword;
    	int i, rc;
    
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    #ifdef CFG_FLASH_USE_BUFFER_WRITE
    	int buffered_size;
    #endif
    	/* get lower aligned address */
    
    	/* get lower aligned address */
    	wp = (addr & ~(info->portwidth - 1));
    
    	/* handle unaligned start */
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	if ((aln = addr - wp) != 0) {
    
    		cword.l = 0;
    		cp = wp;
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    		for (i = 0; i < aln; ++i, ++cp)
    			flash_add_byte (info, &cword, (*(uchar *) cp));
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    		for (; (i < info->portwidth) && (cnt > 0); i++) {
    			flash_add_byte (info, &cword, *src++);
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    		for (; (cnt == 0) && (i < info->portwidth); ++i, ++cp)
    			flash_add_byte (info, &cword, (*(uchar *) cp));
    		if ((rc = flash_write_cfiword (info, wp, cword)) != 0)
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	/* handle the aligned part */
    
    #ifdef CFG_FLASH_USE_BUFFER_WRITE
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	buffered_size = (info->portwidth / info->chipwidth);
    	buffered_size *= info->buffer_size;
    	while (cnt >= info->portwidth) {
    		i = buffered_size > cnt ? cnt : buffered_size;
    		if ((rc = flash_write_cfibuffer (info, wp, src, i)) != ERR_OK)
    
    		i -= (i % info->portwidth);
    
    		wp += i;
    		src += i;
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    		cnt -= i;
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	while (cnt >= info->portwidth) {
    
    		cword.l = 0;
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    		for (i = 0; i < info->portwidth; i++) {
    			flash_add_byte (info, &cword, *src++);
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    		if ((rc = flash_write_cfiword (info, wp, cword)) != 0)
    
    			return rc;
    		wp += info->portwidth;
    		cnt -= info->portwidth;
    	}
    #endif /* CFG_FLASH_USE_BUFFER_WRITE */
    	if (cnt == 0) {
    		return (0);
    	}
    
    	/*
    	 * handle unaligned tail bytes
    	 */
    	cword.l = 0;
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	for (i = 0, cp = wp; (i < info->portwidth) && (cnt > 0); ++i, ++cp) {
    		flash_add_byte (info, &cword, *src++);
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	for (; i < info->portwidth; ++i, ++cp) {
    		flash_add_byte (info, &cword, (*(uchar *) cp));
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	return flash_write_cfiword (info, wp, cword);
    
    }
    
    /*-----------------------------------------------------------------------
     */
    #ifdef CFG_FLASH_PROTECTION
    
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    int flash_real_protect (flash_info_t * info, long sector, int prot)
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	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);
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    		flash_write_cmd (info, sector, 0, FLASH_CMD_PROTECT_CLEAR);
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	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 */
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    		if (prot == 0) {
    
    			flash_sect_t i;
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    
    			for (i = 0; i < info->sector_count; i++) {
    				if (info->protect[i])
    					flash_real_protect (info, i, 1);
    
    /*-----------------------------------------------------------------------
     * flash_read_user_serial - read the OneTimeProgramming cells
     */
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    void flash_read_user_serial (flash_info_t * info, void *buffer, int offset,
    			     int len)
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	uchar *src;
    	uchar *dst;
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	src = flash_make_addr (info, 0, FLASH_OFFSET_USER_PROTECTION);
    	flash_write_cmd (info, 0, 0, FLASH_CMD_READ_ID);
    	memcpy (dst, src + offset, len);
    	flash_write_cmd (info, 0, 0, FLASH_CMD_RESET);
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    
    
    /*
     * flash_read_factory_serial - read the device Id from the protection area
     */
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    void flash_read_factory_serial (flash_info_t * info, void *buffer, int offset,
    				int len)
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	uchar *src;
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	src = flash_make_addr (info, 0, FLASH_OFFSET_INTEL_PROTECTION);
    	flash_write_cmd (info, 0, 0, FLASH_CMD_READ_ID);
    	memcpy (buffer, src + offset, len);
    	flash_write_cmd (info, 0, 0, FLASH_CMD_RESET);
    
    }
    
    #endif /* CFG_FLASH_PROTECTION */
    
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    /*
     * flash_is_busy - check to see if the flash is busy
     * This routine checks the status of the chip and returns true if the chip is busy
     */
    static int flash_is_busy (flash_info_t * info, flash_sect_t sect)
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    
    	switch (info->vendor) {
    
    	case CFI_CMDSET_INTEL_STANDARD:
    	case CFI_CMDSET_INTEL_EXTENDED:
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    		retval = !flash_isset (info, sect, 0, FLASH_STATUS_DONE);
    
    		break;
    	case CFI_CMDSET_AMD_STANDARD:
    	case CFI_CMDSET_AMD_EXTENDED:
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    		retval = flash_toggle (info, sect, 0, AMD_STATUS_TOGGLE);
    
    		break;
    	default:
    		retval = 0;
    	}
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	debug ("flash_is_busy: %d\n", retval);
    
    	return retval;
    }
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    
    
    /*-----------------------------------------------------------------------
     *  wait for XSR.7 to be set. Time out with an error if it does not.
     *  This routine does not set the flash to read-array mode.
     */
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    static int flash_status_check (flash_info_t * info, flash_sect_t sector,
    			       ulong tout, char *prompt)
    
    {
    	ulong start;
    
    	/* Wait for command completion */
    	start = get_timer (0);
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	while (flash_is_busy (info, sector)) {
    		if (get_timer (start) > info->erase_blk_tout * CFG_HZ) {
    			printf ("Flash %s timeout at address %lx data %lx\n",
    				prompt, info->start[sector],
    				flash_read_long (info, sector, 0));
    			flash_write_cmd (info, sector, 0, info->cmd_reset);
    
    			return ERR_TIMOUT;
    		}
    	}
    	return ERR_OK;
    }
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    
    
    /*-----------------------------------------------------------------------
     * Wait for XSR.7 to be set, if it times out print an error, otherwise do a full status check.
     * This routine sets the flash to read-array mode.
     */
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    static int flash_full_status_check (flash_info_t * info, flash_sect_t sector,
    				    ulong tout, char *prompt)
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    
    	retcode = flash_status_check (info, sector, tout, prompt);
    	switch (info->vendor) {
    
    	case CFI_CMDSET_INTEL_EXTENDED:
    	case CFI_CMDSET_INTEL_STANDARD:
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    		if ((retcode != ERR_OK)
    		    && !flash_isequal (info, sector, 0, FLASH_STATUS_DONE)) {
    
    			retcode = ERR_INVAL;
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    			printf ("Flash %s error at address %lx\n", prompt,
    				info->start[sector]);
    
    			if (flash_isset (info, sector, 0, FLASH_STATUS_ECLBS | FLASH_STATUS_PSLBS)) {
    
    				puts ("Command Sequence Error.\n");
    
    			} else if (flash_isset (info, sector, 0, FLASH_STATUS_ECLBS)) {
    
    				puts ("Block Erase Error.\n");
    
    				retcode = ERR_NOT_ERASED;
    
    			} else if (flash_isset (info, sector, 0, FLASH_STATUS_PSLBS)) {
    
    				puts ("Locking Error\n");
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    			if (flash_isset (info, sector, 0, FLASH_STATUS_DPS)) {
    
    				puts ("Block locked.\n");
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    				retcode = ERR_PROTECTED;
    			}
    			if (flash_isset (info, sector, 0, FLASH_STATUS_VPENS))
    
    				puts ("Vpp Low Error.\n");
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    		flash_write_cmd (info, sector, 0, FLASH_CMD_RESET);
    
    		break;
    	default:
    		break;
    	}
    	return retcode;
    }
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    
    
    /*-----------------------------------------------------------------------
     */
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    static void flash_add_byte (flash_info_t * info, cfiword_t * cword, uchar c)
    
    #if defined(__LITTLE_ENDIAN)
    	unsigned short	w;
    	unsigned int	l;
    	unsigned long long ll;
    #endif
    
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	switch (info->portwidth) {
    
    	case FLASH_CFI_8BIT:
    		cword->c = c;
    		break;
    	case FLASH_CFI_16BIT:
    
    #if defined(__LITTLE_ENDIAN)
    		w = c;
    		w <<= 8;
    		cword->w = (cword->w >> 8) | w;
    #else
    
    		cword->w = (cword->w << 8) | c;
    
    		break;
    	case FLASH_CFI_32BIT:
    
    #if defined(__LITTLE_ENDIAN)
    		l = c;
    		l <<= 24;
    		cword->l = (cword->l >> 8) | l;
    #else
    
    		cword->l = (cword->l << 8) | c;
    
    		break;
    	case FLASH_CFI_64BIT:
    
    #if defined(__LITTLE_ENDIAN)
    		ll = c;
    		ll <<= 56;
    		cword->ll = (cword->ll >> 8) | ll;
    #else
    
    		cword->ll = (cword->ll << 8) | c;
    
    		break;
    	}
    }
    
    
    /*-----------------------------------------------------------------------
     * make a proper sized command based on the port and chip widths
     */
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    static void flash_make_cmd (flash_info_t * info, uchar cmd, void *cmdbuf)
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    
    #if defined(__LITTLE_ENDIAN)
    
    	ushort stmpw;
    	uint   stmpi;
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    #endif
    	uchar *cp = (uchar *) cmdbuf;
    
    	for (i = 0; i < info->portwidth; i++)
    		*cp++ = ((i + 1) % info->chipwidth) ? '\0' : cmd;
    #if defined(__LITTLE_ENDIAN)
    
    	switch (info->portwidth) {
    	case FLASH_CFI_8BIT:
    		break;
    	case FLASH_CFI_16BIT:
    		stmpw = *(ushort *) cmdbuf;
    		*(ushort *) cmdbuf = __swab16 (stmpw);
    		break;
    	case FLASH_CFI_32BIT:
    		stmpi = *(uint *) cmdbuf;
    		*(uint *) cmdbuf = __swab32 (stmpi);
    		break;
    	default:
    
    		puts ("WARNING: flash_make_cmd: unsuppported LittleEndian mode\n");
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	}
    #endif
    
    }
    
    /*
     * Write a proper sized command to the correct address
     */
    
    static void flash_write_cmd (flash_info_t * info, flash_sect_t sect, uint offset, uchar cmd)
    
    {
    
    	volatile cfiptr_t addr;
    	cfiword_t cword;
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    
    	addr.cp = flash_make_addr (info, sect, offset);
    	flash_make_cmd (info, cmd, &cword);
    	switch (info->portwidth) {
    
    	case FLASH_CFI_8BIT:
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    		debug ("fwc addr %p cmd %x %x 8bit x %d bit\n", addr.cp, cmd,
    		       cword.c, info->chipwidth << CFI_FLASH_SHIFT_WIDTH);
    
    		*addr.cp = cword.c;
    		break;
    	case FLASH_CFI_16BIT:
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    		debug ("fwc addr %p cmd %x %4.4x 16bit x %d bit\n", addr.wp,
    		       cmd, cword.w,
    
    		       info->chipwidth << CFI_FLASH_SHIFT_WIDTH);
    		*addr.wp = cword.w;
    		break;
    	case FLASH_CFI_32BIT:
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    		debug ("fwc addr %p cmd %x %8.8lx 32bit x %d bit\n", addr.lp,
    		       cmd, cword.l,
    
    		       info->chipwidth << CFI_FLASH_SHIFT_WIDTH);
    		*addr.lp = cword.l;
    		break;
    	case FLASH_CFI_64BIT:
    #ifdef DEBUG
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    		{
    
    			char str[20];
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    			print_longlong (str, cword.ll);
    
    			debug ("fwrite addr %p cmd %x %s 64 bit x %d bit\n",
    			       addr.llp, cmd, str,
    
    			       info->chipwidth << CFI_FLASH_SHIFT_WIDTH);
    		}
    #endif
    		*addr.llp = cword.ll;
    		break;
    	}
    }
    
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    static void flash_unlock_seq (flash_info_t * info, flash_sect_t sect)
    
    	flash_write_cmd (info, sect, AMD_ADDR_START, AMD_CMD_UNLOCK_START);
    	flash_write_cmd (info, sect, AMD_ADDR_ACK, AMD_CMD_UNLOCK_ACK);
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    
    
    /*-----------------------------------------------------------------------
     */
    
    static int flash_isequal (flash_info_t * info, flash_sect_t sect, uint offset, uchar cmd)
    
    {
    	cfiptr_t cptr;
    	cfiword_t cword;
    	int retval;
    
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	cptr.cp = flash_make_addr (info, sect, offset);
    	flash_make_cmd (info, cmd, &cword);
    
    	debug ("is= cmd %x(%c) addr %p ", cmd, cmd, cptr.cp);
    	switch (info->portwidth) {
    
    	case FLASH_CFI_8BIT:
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    		debug ("is= %x %x\n", cptr.cp[0], cword.c);
    
    		retval = (cptr.cp[0] == cword.c);
    		break;
    	case FLASH_CFI_16BIT:
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    		debug ("is= %4.4x %4.4x\n", cptr.wp[0], cword.w);
    
    		retval = (cptr.wp[0] == cword.w);
    		break;
    	case FLASH_CFI_32BIT:
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    		debug ("is= %8.8lx %8.8lx\n", cptr.lp[0], cword.l);
    
    		retval = (cptr.lp[0] == cword.l);
    		break;
    	case FLASH_CFI_64BIT:
    
    #ifdef DEBUG
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    		{
    
    			char str1[20];
    			char str2[20];
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    
    			print_longlong (str1, cptr.llp[0]);
    			print_longlong (str2, cword.ll);
    			debug ("is= %s %s\n", str1, str2);
    
    		}
    #endif
    		retval = (cptr.llp[0] == cword.ll);
    		break;
    	default:
    		retval = 0;
    		break;
    	}
    	return retval;
    }
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    
    
    /*-----------------------------------------------------------------------
     */
    
    static int flash_isset (flash_info_t * info, flash_sect_t sect, uint offset, uchar cmd)
    
    {
    	cfiptr_t cptr;
    	cfiword_t cword;
    	int retval;
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    
    	cptr.cp = flash_make_addr (info, sect, offset);
    	flash_make_cmd (info, cmd, &cword);
    	switch (info->portwidth) {
    
    	case FLASH_CFI_8BIT:
    		retval = ((cptr.cp[0] & cword.c) == cword.c);
    		break;
    	case FLASH_CFI_16BIT:
    		retval = ((cptr.wp[0] & cword.w) == cword.w);
    		break;
    	case FLASH_CFI_32BIT:
    		retval = ((cptr.lp[0] & cword.l) == cword.l);
    		break;
    	case FLASH_CFI_64BIT:
    		retval = ((cptr.llp[0] & cword.ll) == cword.ll);
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    		break;
    
    	default:
    		retval = 0;
    		break;
    	}
    	return retval;
    }
    
    /*-----------------------------------------------------------------------
     */
    
    static int flash_toggle (flash_info_t * info, flash_sect_t sect, uint offset, uchar cmd)
    
    {
    	cfiptr_t cptr;
    	cfiword_t cword;
    	int retval;
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    
    	cptr.cp = flash_make_addr (info, sect, offset);
    	flash_make_cmd (info, cmd, &cword);
    	switch (info->portwidth) {
    
    	case FLASH_CFI_8BIT:
    		retval = ((cptr.cp[0] & cword.c) != (cptr.cp[0] & cword.c));
    		break;
    	case FLASH_CFI_16BIT:
    		retval = ((cptr.wp[0] & cword.w) != (cptr.wp[0] & cword.w));
    		break;
    	case FLASH_CFI_32BIT:
    		retval = ((cptr.lp[0] & cword.l) != (cptr.lp[0] & cword.l));
    		break;
    	case FLASH_CFI_64BIT:
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    		retval = ((cptr.llp[0] & cword.ll) !=
    			  (cptr.llp[0] & cword.ll));
    
    		break;
    	default:
    		retval = 0;
    		break;
    	}
    	return retval;
    }
    
    /*-----------------------------------------------------------------------
     * detect if flash is compatible with the Common Flash Interface (CFI)
     * http://www.jedec.org/download/search/jesd68.pdf
     *
    */
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    static int flash_detect_cfi (flash_info_t * info)
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	debug ("flash detect cfi\n");
    
    	for (info->portwidth = FLASH_CFI_8BIT;
    	     info->portwidth <= FLASH_CFI_64BIT; info->portwidth <<= 1) {
    		for (info->chipwidth = FLASH_CFI_BY8;
    		     info->chipwidth <= info->portwidth;
    		     info->chipwidth <<= 1) {
    			flash_write_cmd (info, 0, 0, FLASH_CMD_RESET);
    
    			flash_write_cmd (info, 0, FLASH_OFFSET_CFI, FLASH_CMD_CFI);
    			if (flash_isequal (info, 0, FLASH_OFFSET_CFI_RESP, 'Q')
    			    && flash_isequal (info, 0, FLASH_OFFSET_CFI_RESP + 1, 'R')
    			    && flash_isequal (info, 0, FLASH_OFFSET_CFI_RESP + 2, 'Y')) {
    				info->interface = flash_read_ushort (info, 0, FLASH_OFFSET_INTERFACE);
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    				debug ("device interface is %d\n",
    				       info->interface);
    				debug ("found port %d chip %d ",
    				       info->portwidth, info->chipwidth);
    				debug ("port %d bits chip %d bits\n",
    
    				       info->portwidth << CFI_FLASH_SHIFT_WIDTH,
    				       info->chipwidth << CFI_FLASH_SHIFT_WIDTH);
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	debug ("not found\n");
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    
    
    /*
     * The following code cannot be run from FLASH!
     *
     */
    static ulong flash_get_size (ulong base, int banknum)
    {
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	flash_info_t *info = &flash_info[banknum];
    
    	int i, j;
    	flash_sect_t sect_cnt;
    	unsigned long sector;
    	unsigned long tmp;
    	int size_ratio;
    	uchar num_erase_regions;
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	int erase_region_size;
    	int erase_region_count;
    
    
    	info->start[0] = base;
    
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	if (flash_detect_cfi (info)) {
    
    		info->vendor = flash_read_ushort (info, 0, FLASH_OFFSET_PRIMARY_VENDOR);