Olpcflash.c

From OLPC
Jump to: navigation, search
/*
 * flash_rom.c: Flash programming utility
 *
 * Copyright 2000 Silicon Integrated System Corporation
 * Copyright 2004 Tyan Corp
 *	yhlu yhlu@tyan.com add exclude start and end option
 * Copyright 2005-2006 coresystems GmbH 
 *      Stefan Reinauer <stepan@coresystems.de> added rom layout
 *      support, and checking for suitable rom image, various fixes
 *      support for flashing the Technologic Systems 5300.
 * 
 *	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   1
#define VER_RELEASE 0

#define LINUXBIOS_START 0x10000

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_flash(void)
{
	erase_linuxbios_area();
}

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

	setaddr(addr);
	docmd(READ);
	wait_cmd_sent();
	data =  rdata();
	printf_debug("read 0x%x@0x%x\n", data, addr);
	return data;
}

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

	for (i = start; i <= stop; 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(unsigned char *buf, unsigned long start, unsigned long end){
	unsigned long i;

	printf("Writing...\r\n"); 
	for(i = start; i <= end; i++){
		if ((i % 0x10000) == 0)
			printf("Sector 0x%08x\r\n", i);
		if (write_flash_byte(i, *buf)) {
			return -1;
		}
		buf++;
	}

	return 0;
}

int verify_flash(unsigned char *buf, unsigned long start, unsigned long end)
{
	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 <= end; 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 char *buf;
	unsigned long size;
	FILE *image;
	int opt;
	int option_index = 0;
	int read_it = 0, 
	    write_it = 0, 
	    erase_it = 0, 
	    verify_it = 0;
	int ret = 0;

	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' },
		{ 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);
			iobase = strtol(tempstr,NULL,16);
			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) - (64*1024);
	buf = (unsigned char *) calloc(size, sizeof(char));
	
	// When I erased the flash and then exited the machine 
	// crashed.
	if (erase_it) {
		put_kbc_in_reset();
		disable_flash_write_protect();
		erase_flash();
		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();

		printf("Reading Flash...\r\n");
		read_flash(buf,LINUXBIOS_START,LINUXBIOS_START+size-1);

/*
		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_linuxbios_area();
		write_flash(buf,LINUXBIOS_START, LINUXBIOS_START+size-1);
		enable_flash_write_protect();
//		restore_kbc_run_mode();
	}

	if (verify_it) {
		put_kbc_in_reset();
		ret |= verify_flash(buf,LINUXBIOS_START,LINUXBIOS_START+size-1);
//		restore_kbc_run_mode();
	}

	if (buf) {
		free(buf);
	}
	return ret;
}