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