Olpcflash.c
Jump to navigation
Jump to search
monmonpas
/* * 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); sscanf(tempstr,"%x",&iobase); 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; }