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
