New.

- Full page sized writes.
 Part now programs in ~45 seconds just like the DOS code.
 Writing is now faster than reading. :)

- Brick mode
 Allows you to read and write the entire part rather than just 0x10000-0xfffff.

Brick mode showed up earlier than planned out of self-preservation.  I
had a stupid one off thinko that wrote a page of garbage to  address
0x100000.

Fearing that the part just wraps and I had trashed my EC code, I
implemented brick mode.  All the while  hoping that we didn't have a
power glitch since I didn't have the unit plugged up to a UPS.

Using brick mode and the Insyde image file sent to me earlier I was
able to ascertain that indeed I had trashed my EC code.  Then using
brick mode I was able to restore my EC code from the image file.

So I guess in this instance it was unbrick mode.

This tells me that even if we figured out the reset issue with putting
the KBC back into run mode it would be a Really Bad Idea to do this.
In this instance the KBC would have started running trashed code.

I also verified that as expected the USB keyboard works fine while the
KBC is in reset.

Still TODO:

- Add image CRC32 checksum support and verification checks to make
sure that the image file really is a LinuxBIOS image.

- Do reads in blocks larger than 1 byte so it goes much faster.

Mitch: I'll update the wiki tomorrow night. Promise.

--
Richard A. Smith
/*
 * olpcflash.c: SPI Flash programming utility for OLPC.
 *
 * Copyright 2000 Silicon Integrated System Corporation
 * Copyright 2004 Tyan Corp
 *		yhlu [EMAIL PROTECTED] add exclude start and end option
 * Copyright 2005-2006 coresystems GmbH 
 *      Stefan Reinauer <[EMAIL PROTECTED]> added rom layout
 *      support, and checking for suitable rom image, various fixes
 *      support for flashing the Technologic Systems 5300.
 * Copyright 2006  Ron Minniich <[email protected]>
 *      Initial support for EnE KB3920 EC and the spansion
 *      25FL008A 8Mibit SPI part on the OLPC
 * Copyright 2006 Richard A. Smith <[EMAIL PROTECTED]> 
 *      Added lots of additional sutff to make writing to spansion part
 *      work correctly with the OLPC EnE EC.
 * 
 *	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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include <errno.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <getopt.h>
#include <sys/io.h>

#define printf_debug(x...) { if(verbose) printf(x); }
#define printf_super_debug(x...) { if(verbose > 1) printf(x); }

#define VER_MAJOR   0
#define VER_MINOR   2
#define VER_RELEASE 0

#define LINUXBIOS_START 0x10000
#define PAGE_SIZE	256

enum {
	GPIO5 = 0xfc2a,
	SPIA0 = 0xfea8, 
	SPIA1, 
	SPIA2, 
	SPIDAT,
	SPICMD, 
	SPICFG,
	SPIDATR
};

enum {
	DUMMY,
	WRITESTATUS = 1,
	BYTEPROGRAM, 
	READ,
	WRITEDISABLE,
	READSTATUS,	
	WRITEENABLE,
	HIGHSPEEDREAD = 0xb,
	SECTORERASESST = 0x20, 
	ENABLEWRITESTATUSSST = 0x50,
	BLOCKERASESST = 0x52,
	CHIPERASESST = 0x60,
	CHIPERASEPCM = 0xc7, /* also nexflash */
	SECTORERASEPCM = 0xd7,
	BLOCKERASEPCM = 0xd8, /* also nexflash, and spansion */
};

enum {
	SPIBUSY = 2, 
	SPIFIRMWAREMODE = 1 << 4,
	SPICMDWE = 8,
	SPIFLASHREADCE = 1 << 6
};

enum {
	WIP = 1 << 0
};

char *chip_to_probe = NULL;

int exclude_start_page, exclude_end_page;
int force=0, verbose=0;

/* this is the defaut index and data IO base address for
 * EC register access.
 * setup by the EC code.
 */

unsigned short iobase = 0x381;

// count to a billion. Time it. If it's < 1 sec, count to 10B, etc.
unsigned long micro = 1;

void myusec_delay(int time)
{
	volatile unsigned long i;
	for (i = 0; i < time * micro; i++);
}

void myusec_calibrate_delay()
{
	int count = 1000;
	unsigned long timeusec;
	struct timeval start, end;
	int ok = 0;

	printf_debug("Setting up microsecond timing loop\n");
	while (!ok) {
		gettimeofday(&start, 0);
		myusec_delay(count);
		gettimeofday(&end, 0);
		timeusec = 1000000 * (end.tv_sec - start.tv_sec) +
		    (end.tv_usec - start.tv_usec);
		count *= 2;
		if (timeusec < 1000000 / 4)
			continue;
		ok = 1;
	}

	// compute one microsecond. That will be count / time
	micro = count / timeusec;

	printf_debug("%ldM loops per second\n", (unsigned long) micro);
}


void
setecindex(unsigned short index){
	unsigned char hi = index>>8;
	unsigned char lo = index;

	outb(hi, iobase);
	outb(lo, iobase+1);
	printf_super_debug("%s: set 0x%x to 0x%x, 0x%x to 0x%x\n", __FUNCTION__, 
		iobase, hi, iobase+1, lo);
}

unsigned char 
getecdata(void){
	unsigned char data;
	data = inb(iobase+2);
	printf_super_debug("%s: read 0x%x from 0x%x\n", __FUNCTION__, data, iobase+2);
	return data;
}

void
putecdata(unsigned char data){
	outb(data, iobase+2);
	printf_super_debug("%s: wrote 0x%x to 0x%x\n", __FUNCTION__, data, iobase+2);
}

unsigned char 
readecdata(unsigned short index){
	setecindex(index);
	return getecdata();
}

void
writeecdata(unsigned short index, unsigned char data){
	setecindex(index);
	putecdata(data);
}

void setaddr(unsigned long addr){
	unsigned char data;
	data = addr;
	writeecdata(SPIA0, data);
	data = addr >> 8;
	writeecdata(SPIA1, data);
	data = addr >> 16;
	writeecdata(SPIA2, data);
}

unsigned char 
rdata(){
	unsigned char data;
	data = readecdata(SPIDAT);
	return data;
}

void
wdata(unsigned char data){
	writeecdata(SPIDAT, data);
}

unsigned char
cmd(void){
	return readecdata(SPICMD);
}

void
docmd(unsigned char cmd){
	writeecdata(SPICMD, cmd);
	printf_super_debug("docmd: cmd 0x%x\n", cmd);
}

void
wait_cmd_sent(void){
	int trycount = 0;
	myusec_delay(10);
//	docmd(READSTATUS);
//	myusec_delay(1000);

	while (readecdata(SPICFG) & SPIBUSY){
		trycount++;
		myusec_delay(10);
		if (trycount > 100000){ /* 1 second! */
			printf("wait_sent: Err: waited for > 1 second\n");
			trycount = 0;
		}
	}
}	

/* 
 * The EnE code has lots of small delays inbetween
 * many of the actions.  Route all this through 
 * one function so I can play with how long they
 * need to be.
 */
void short_delay(void)
{
	// EnE code did 4 pci reads of the base address
	// which should be around 800nS
	// 2 uS should cover it in case I'm wrong
	myusec_delay(2);
}

/*
 * Firmware mode allows you raw control over the SPI bus
 * the spansion part is not supported by the EC in 
 * "hardware" mode.
 * in this mode bytes written to the SPICMD register
 * are clocked out the bus.
 * This also asserts SPICS#
 */
void start_SPI_firmware_mode_access(void)
{
	writeecdata(SPICFG,0x18);
}

void end_SPI_firmware_mode_access(void)
{
	writeecdata(SPICFG,0x08);
}

/*
 * You must do this prior to _every_ command that
 * writes data to the part.  The write enable
 * latch resets after write commands complete.
 */
void
send_write_enable(void) {
	start_SPI_firmware_mode_access();
	short_delay();
	docmd(WRITEENABLE);
	wait_cmd_sent();
	end_SPI_firmware_mode_access();
}

void 
send_addr(unsigned long addr)
{
	unsigned char data;

	data = addr >> 16 & 0xff;
	docmd(data);
	wait_cmd_sent();

	data = addr >> 8 & 0xff;
	docmd(data);
	wait_cmd_sent();

	data = addr & 0xff;
	docmd(data);
}

void 
enable_flash_cmd(void)
{
	writeecdata(SPICFG, SPICMDWE|readecdata(SPICFG));
}

void 
enable_flash_write_protect(void){
	unsigned char val;
	val = readecdata(GPIO5);
	val &= ~0x80;
	writeecdata(GPIO5,val);
}

void 
disable_flash_write_protect(void){
	unsigned char val;
	val = readecdata(GPIO5);
	val |= 0x80;
	writeecdata(GPIO5,val);
}

/*
 * This appears to be necessary.  If you watch the lines with 
 *	scope you will see that there is constant activity on the SPI
 *	bus to the part.  Trying to write to the port while all that
 * is going on is sure to muck things up.
 * Putting this into reset stops all that 
 * activity.

 * Plus Ray Tseng said so.

 * It appears to work in read mode without this though.
 */
void put_kbc_in_reset(void){
	unsigned char val;
	unsigned long timeout = 500000;

	outb(0xd8,0x66);
	while((inb(0x66) & 0x02) && (timeout>0)) {
		timeout--;
	}
	val = readecdata(0xff14);
	val |= 0x01;
	writeecdata(0xff14,val);
}

void restore_kbc_run_mode(void){
	unsigned char val;

	val = readecdata(0xff14);
	val &= ~0x01;
	writeecdata(0xff14,val);
}

unsigned char read_status_register(void)
{
	unsigned char data=0;
	start_SPI_firmware_mode_access();
	short_delay();
	docmd(READSTATUS);
	wait_cmd_sent();
	docmd(DUMMY);
	wait_cmd_sent();
	data =  rdata();
	end_SPI_firmware_mode_access();
	return data;
}

// Staus reg writes; erases and programs all need to 
// check this status bit.
int wait_write_done(void)
{
	int trycount = 0;

	while (read_status_register() & WIP){
		trycount++;
		myusec_delay(10);
		// For the spansion part datasheet claims that 
		// the only thing that takes longer than 500mS is 
		// bulk erase and we don't ever want to use that
		// command
		if (trycount > 100000){ /* 1 second! */
			printf("wait_write_done: Err: waited for > 1 second\n");
			trycount = 0;
			return -1;
		}
	}

	return 0;
}	
 
int erase_sector(unsigned long addr)
{
	send_write_enable();
	short_delay();

	start_SPI_firmware_mode_access();
	short_delay();

	docmd(BLOCKERASEPCM);
	wait_cmd_sent();

	send_addr(addr);
	wait_cmd_sent();

	end_SPI_firmware_mode_access();

	return wait_write_done();
}

/*
 Erase from sectors 0x10000 to 0xf0000
*/
int erase_linuxbios_area(void) 
{
	unsigned long addr;

	for (addr = 0x10000;addr < 0xfffff;addr+=0x10000) {
		printf("Erasing Sector: 0x%08x\r\n",addr);
		erase_sector(addr);
	}
	return 0;
}

int erase_EC_area(void) 
{
	unsigned long addr = 0;
	printf("Erasing Sector: 0x%08x\r\n",addr);
	erase_sector(addr);
	return 0;
}

int erase_flash(int mode)
{
	if (mode == 1) {
		erase_EC_area();
	}
	erase_linuxbios_area();
	return 0;
}

unsigned char 
read_flash_byte(unsigned long addr) {
	unsigned char data;

	setaddr(addr);
	docmd(READ);
	wait_cmd_sent();
	data =  rdata();
	printf_debug("read [EMAIL PROTECTED]", data, addr);
	return data;
}

int
read_flash(unsigned char *buf, unsigned long start, unsigned long size){
	unsigned long i;

	printf("Reading %d bytes from %x\r\n",size,start);
	for (i = start; i < start+size; i++) {
		if ((i % 0x10000) == 0)
			printf("Sector 0x%08x\r\n", i);
		*buf = read_flash_byte(i);
		buf++;
	}
	return 0;
}

int
write_flash_byte(unsigned long addr, unsigned char data) {
	unsigned char verify;
	unsigned char addr_byte;
	
	send_write_enable();
	short_delay();

	start_SPI_firmware_mode_access();
	short_delay();

	docmd(BYTEPROGRAM);
	wait_cmd_sent();

	send_addr(addr);
	wait_cmd_sent();

	docmd(data);
	wait_cmd_sent();

	end_SPI_firmware_mode_access();

	wait_write_done();
/*
	verify =  read_flash_byte(addr);
	if (verify != data) {
		printf("addr 0x%x, want 0x%x, got 0x%x\n", 
				addr, data, verify);
		return -1;
	}
*/
	return 0;
}

int write_flash_page(unsigned char *buf, unsigned long addr, unsigned long size) {
	unsigned char verify;
	unsigned char addr_byte;
	
	send_write_enable();
	short_delay();

	start_SPI_firmware_mode_access();
	short_delay();

	docmd(BYTEPROGRAM);
	wait_cmd_sent();

	send_addr(addr);
	wait_cmd_sent();

	while (size > 0) {
		docmd(*buf);
		wait_cmd_sent();
		size--;
		buf++;
	}

	end_SPI_firmware_mode_access();

	wait_write_done();
/*
	verify =  read_flash_byte(addr);
	if (verify != data) {
		printf("addr 0x%x, want 0x%x, got 0x%x\n", 
				addr, data, verify);
		return -1;
	}
*/
	return 0;
}

int
write_flash(unsigned char *buf, unsigned long start, unsigned long size){
	unsigned long p=0;
	unsigned long pages=0;
	unsigned long short_page=0;
	
	pages = (size+(PAGE_SIZE-1))/PAGE_SIZE;
	short_page = size-(pages*PAGE_SIZE);

	printf("Writing %d bytes starting at address 0x%x\r\n",size,start);

	for (p=0;p<pages;p++) {
		if ((start % 0x10000) == 0) printf("Sector 0x%08x\r\n", start);
		printf_debug("Page %d/%d\r",p+1,pages);

		if (write_flash_page(buf,start,PAGE_SIZE)) {
			return -1;
		}

		buf+=PAGE_SIZE;
		start+=PAGE_SIZE;
	}
	printf_debug("\r\n");

	if (short_page) {
		printf_debug("Short Page of %d bytes\r\n",short_page);	
		printf_debug("Address %d\r\n",start);	

		if (write_flash_page(buf,start,short_page)) {
			return -1;
		}
	}

	return 0;
}

int verify_flash(unsigned char *buf, unsigned long start, unsigned long size)
{
	unsigned long idx;

	printf("Verifying flash\n");
	
	if(verbose) printf("address: 0x00000000\b\b\b\b\b\b\b\b\b\b");
	
	for (idx = start; idx < start+size; idx++) {
		if (verbose && ( (idx & 0xfff) == 0xfff ))
			printf("0x%08x", idx);

		if ((idx % 0x10000) == 0)
			printf("Sector 0x%08x\r\n", idx);

		if (read_flash_byte(idx) != *(buf)) {
			if (verbose) {
				printf("0x%08x ", idx);
			}
			printf("- FAILED\n");
			return 1;
		}
		buf++;

		if (verbose && ( (idx & 0xfff) == 0xfff ))
			printf("\b\b\b\b\b\b\b\b\b\b");
	}
	if (verbose) 
		printf("\b\b\b\b\b\b\b\b\b\b ");
	
	printf("- VERIFIED         \n");
	return 0;
}


void usage(const char *name)
{
	printf("%s Ver: %d.%d.%d\n",name,VER_MAJOR,VER_MINOR,VER_RELEASE);
	printf("usage: %s [-rwv] [-V] [file]\n", name);
	printf("   -r | --read:   read flash and save into file\n"
	       "   -w | --write:  write file into flash (default when file is specified)\n"
	       "   -v | --verify: verify flash against file\n"
	       "   -E | --erase: Erase LinuxBIOS area (0x10000-0xfffff)\n"
	       "   -V | --verbose: more verbose output"
           "   -h | --help: This message\n\n");
	exit(1);
}

int main(int argc, char *argv[])
{
	unsigned long size;
	unsigned long start_addr=LINUXBIOS_START;
	FILE *image;
	int opt;
	int option_index = 0;
	int read_it = 0, 
	    write_it = 0, 
	    erase_it = 0, 
	    verify_it = 0;
	int brick_mode = 0;
	int ret = 0;
	int i=0;
	unsigned char buf[1024*1024];

	static struct option long_options[]= {
		{ "read", 0, 0, 'r' },
		{ "write", 0, 0, 'w' },
		{ "erase", 0, 0, 'E' },
		{ "verify", 0, 0, 'v' },
		{ "iobase", 1, 0, 'i' },
		{ "verbose", 0, 0, 'V' },
		{ "help", 0, 0, 'h' },
		{ "brick",0, 0, 'B' },
		{ 0, 0, 0, 0 }
	};
	
	char *filename = NULL;
    unsigned int exclude_start_position=0, exclude_end_position=0; // [x,y)
	char *tempstr=NULL, *tempstr2=NULL;

	if (iopl(3) < 0){
		perror("iop(3)");
		exit(1);
	}

	setbuf(stdout, NULL);
	while ((opt = getopt_long(argc, argv, "rwvVEfc:s:e:m:l:i:h", long_options,
					&option_index)) != EOF) {
		switch (opt) {
		case 'r':
			read_it = 1;
			break;
		case 'w':
			write_it = 1;
			break;
		case 'v':
			verify_it = 1;
			break;
		case 'V':
			verbose++;
			break;
		case 'E':
			erase_it = 1;
			break;
		case 'i':
			tempstr = strdup(optarg);
			sscanf(tempstr,"%x",&iobase);
			break;
		case 'B':
			brick_mode = 1;
			break;
		case 'h':
		default:
			usage(argv[0]);
			break;
		}
	}

	if (argc > 1) {
		/* Yes, print them. */
		int i;
		printf_debug ("The arguments are:\n");
		for (i = 1; i < argc; ++i)
			printf_debug ("%s\n", argv[i]);
	}

	if (read_it && write_it) {
		printf("-r and -w are mutually exclusive\n");
		usage(argv[0]);
	}

	if (optind < argc)
		filename = argv[optind++];

	printf("Calibrating delay loop... ");
	myusec_calibrate_delay();
	printf("ok\n");


	if (!filename && !erase_it) {
		// FIXME: Do we really want this feature implicitly?
		printf("Doing nothing\n");
		return 0;
	}

	enable_flash_cmd();

	size = (1024*1024);
	start_addr = 0;

	if (brick_mode != 1) {
		size -= (64*1024);
		start_addr = LINUXBIOS_START;
	}

	for (i=0;i<size;i++) {
		buf[i] = 0xff;
	}

	if (erase_it) {
		put_kbc_in_reset();
		disable_flash_write_protect();
		erase_flash(brick_mode);
		enable_flash_write_protect();
//		restore_kbc_run_mode();
		exit(0);		
	} else if (read_it) {
		if ((image = fopen(filename, "w")) == NULL) {
			perror(filename);
			exit(1);
		}
		put_kbc_in_reset();

		read_flash(buf,start_addr,size);
/*
		if (exclude_end_position - exclude_start_position > 0)
			memset(buf+exclude_start_position, 0,
			       exclude_end_position-exclude_start_position);
*/
		fwrite(buf, sizeof(char), size, image);
		fclose(image);
		printf("done\n");
//		restore_kbc_run_mode();
	} else {
		if ((image = fopen(filename, "r")) == NULL) {
			perror(filename);
			exit(1);
		}
		printf("Loading 0x%x bytes from %s\r\n",size,filename);
		fread(buf, sizeof(char), size, image);
//		show_id(buf, size);
		fclose(image);
	}

	if (write_it) {
		put_kbc_in_reset();
		disable_flash_write_protect();
		erase_flash(brick_mode);
		write_flash(buf,start_addr, size);
		enable_flash_write_protect();
//		restore_kbc_run_mode();
	}

	if (verify_it) {
		put_kbc_in_reset();
		ret |= verify_flash(buf,start_addr,size);
//		restore_kbc_run_mode();
	}

	return ret;
}

_______________________________________________
Devel mailing list
[email protected]
http://mailman.laptop.org/mailman/listinfo/devel

Reply via email to