| /* |
| * OpenBIOS - free your system! |
| * ( firmware/flash device driver for Linux ) |
| * |
| * programming.c - flash device programming and probing algorithms. |
| * |
| * This program is part of a free implementation of the IEEE 1275-1994 |
| * Standard for Boot (Initialization Configuration) Firmware. |
| * |
| * Copyright (C) 1998-2004 Stefan Reinauer |
| * |
| * 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; version 2 of the License. |
| * |
| * 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., 51 Franklin St, Fifth Floor, Boston, MA, 02110-1301 USA |
| * |
| */ |
| |
| // <-- C++ style comments are for experimental comments only. |
| // They will disappear as soon as I fixed all the stuff. |
| |
| /* #define DEBUG_PROBING */ |
| |
| #include <linux/config.h> |
| #include <linux/version.h> |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) && defined(MODVERSIONS) |
| #include <linux/modversions.h> |
| #endif |
| |
| #include <linux/pci.h> |
| #include <linux/errno.h> |
| #include <linux/types.h> |
| #include <linux/vmalloc.h> |
| #include <linux/delay.h> |
| #include <linux/spinlock.h> |
| #include <asm/io.h> |
| #include <asm/delay.h> |
| #include <asm/uaccess.h> |
| |
| #include "bios.h" |
| #include "pcisets.h" |
| #include "flashchips.h" |
| #include "programming.h" |
| |
| struct flashdevice flashdevices[BIOS_MAXDEV]; |
| int flashcount; |
| |
| /* |
| * ****************************************** |
| * |
| * flashchip handling |
| * |
| * ****************************************** |
| */ |
| |
| |
| void flash_command (unsigned char *addr, unsigned char command) |
| #if 1 |
| { |
| flash_writeb(addr, 0x5555, 0xaa); |
| flash_writeb(addr, 0x2AAA, 0x55); |
| flash_writeb(addr, 0x5555, command); |
| } |
| void fwh_flash_command(unsigned char *addr, unsigned char command) |
| #endif |
| { |
| flash_writeb(addr, 0x75555, 0xaa); |
| flash_writeb(addr, 0x72aaa, 0x55); |
| flash_writeb(addr, 0x75555, command); |
| } |
| |
| #define CFLASH flashdevices[flashcount] |
| int flash_probe_address(void *address) |
| { |
| int flashnum=0, manufnum=0, sectors=0; |
| unsigned short flash_id, testflash; |
| unsigned long flags; |
| #ifdef DEBUG_PROBING |
| printk( KERN_DEBUG "BIOS: Probing for flash chip @0x%08lx\n", (unsigned long) address); |
| #endif |
| |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) |
| save_flags(flags); |
| #endif |
| spin_lock_irqsave(&bios_lock, flags); |
| |
| testflash= (flash_readb(address, 0))+(flash_readb(address, 1)<<8); |
| |
| /* 1st method: Intel, Atmel listen to this.. */ |
| |
| flash_command(address, 0x90); |
| udelay(20); |
| |
| flash_id = (flash_readb(address, 0))+(flash_readb(address, 1)<<8); |
| |
| #ifdef DEBUG_PROBING |
| printk (KERN_DEBUG "BIOS: testflash[%04x] flash_id[%04x]\n", |
| testflash, flash_id); |
| #endif |
| |
| /* 2nd method: Winbond (I think this is Jedec standard) */ |
| |
| if (flash_id==testflash) { |
| #ifdef DEBUG_PROBING |
| printk (KERN_DEBUG "BIOS: Trying 2nd ID method.\n"); |
| #endif |
| flash_command(address, 0xf0); /* Reset */ |
| udelay(20); |
| |
| flash_command(address, 0x80); |
| flash_command(address, 0x60); |
| udelay(20); |
| |
| flash_id = (flash_readb(address, 0))+(flash_readb(address, 1)<<8); |
| #ifdef DEBUG_PROBING |
| printk (KERN_DEBUG "BIOS: testflash[%04x] flash_id[%04x]\n", |
| testflash, flash_id); |
| #endif |
| } |
| |
| /* 3rd Method: Some Winbonds seem to want this */ |
| |
| if (flash_id==testflash) { |
| #ifdef DEBUG_PROBING |
| printk (KERN_DEBUG "BIOS: Trying 3rd ID method.\n"); |
| #endif |
| flash_command(address, 0xf0); /* Reset again */ |
| udelay(20); |
| |
| flash_command(address, 0x80); |
| flash_command(address, 0x20); |
| udelay(20); |
| |
| flash_id = (flash_readb(address, 0))+(flash_readb(address, 1)<<8); |
| #ifdef DEBUG_PROBING |
| printk (KERN_DEBUG "BIOS: testflash[%04x] flash_id[%04x]\n", |
| testflash, flash_id); |
| #endif |
| } |
| |
| if (flash_id==0x7f7f && flash_readb(address, 0x100)==0x1c) { |
| /* We have an Eon flashchip. They keep their |
| * device id at 0x101 instead of 0x1 |
| */ |
| printk(KERN_INFO "BIOS: Eon flash device detected\n"); |
| flash_id=(flash_readb(address, 0x1))+(flash_readb(address, 0x101)<<8); |
| } |
| |
| flash_command(address, 0xf0); |
| udelay(20); |
| |
| spin_unlock_irqrestore(&bios_lock, flags); |
| |
| if (flash_id==testflash) return 0; /* Nothing found :-( */ |
| |
| while (flashchips[flashnum].id!=0) { |
| if (flash_id==flashchips[flashnum].id) |
| break; |
| flashnum++; |
| } |
| |
| while (manufacturers[manufnum].id!=0) { |
| if ((flash_id&0xff)==manufacturers[manufnum].id) |
| break; |
| manufnum++; |
| } |
| |
| if (flashchips[flashnum].id) { |
| while (flashchips[flashnum].sectors[sectors]<flashchips[flashnum].size) |
| sectors++; |
| } |
| |
| if (flashcount >= BIOS_MAXDEV) { |
| printk(KERN_DEBUG "BIOS: Too many flash devices found.\n"); |
| return -1; |
| } |
| |
| CFLASH.flashnum = flashnum; |
| CFLASH.manufnum = manufnum; |
| CFLASH.id = flash_id; |
| CFLASH.size = (flashchips[flashnum].size<<10); |
| CFLASH.sectors = sectors; |
| CFLASH.open_mode= 0; |
| CFLASH.open_cnt = 0; |
| |
| return 1; |
| } |
| |
| void flash_probe_area(unsigned long romaddr, unsigned long romsize, |
| int map_always) |
| { |
| unsigned long probeaddr; |
| unsigned char *mapped; |
| |
| mapped=ioremap(romaddr, romsize); |
| |
| devices[flashdevices[currflash].idx].activate(); |
| |
| probeaddr=(unsigned long)mapped; |
| |
| while ( probeaddr < (unsigned long)mapped + romsize - 0x5555 ) { |
| if ( flash_probe_address ((void *)probeaddr) != 1) { |
| probeaddr += 4*1024; |
| continue; |
| } |
| |
| CFLASH.offset = probeaddr-(unsigned long)mapped; |
| CFLASH.mapped = (unsigned long)mapped; |
| CFLASH.physical = romaddr+CFLASH.offset; |
| |
| printk( KERN_INFO "BIOS: flash device with size " |
| "%dk (ID 0x%04x) found.\n", |
| CFLASH.size >> 10, CFLASH.id); |
| |
| printk( KERN_INFO "BIOS: physical address " |
| "0x%08lx (va=0x%08lx+0x%lx).\n", |
| CFLASH.physical, (unsigned long)CFLASH.mapped, |
| CFLASH.offset); |
| |
| if (flashchips[CFLASH.flashnum].flags&f_fwh_compl) { |
| unsigned long t_lk; |
| unsigned int i=7; |
| printk(KERN_INFO "BIOS: FWH compliant " |
| "chip detected.\n"); |
| for (t_lk=0xffb80002; t_lk<=0xffbf0002; t_lk+=0x10000) |
| { |
| printk(KERN_INFO "Lock register %d " |
| "(0x%08lx): 0x%x\n", |
| i, t_lk, (unsigned int) |
| (readb(phys_to_virt(t_lk)))); |
| i--; |
| } |
| } |
| flashcount++; |
| currflash++; |
| #ifdef MULTIPLE_FLASH |
| probeaddr += flashdevices[flashcount-1].size; |
| flashdevices[flashcount].mapped=flashdevices[flashcount-1].mapped; |
| flashdevices[flashcount].data=flashdevices[flashcount-1].data; |
| continue; |
| #else |
| break; |
| #endif |
| } |
| |
| /* We might want to always map the memory |
| * region in certain cases |
| */ |
| |
| if (map_always) { |
| CFLASH.flashnum = 0; |
| CFLASH.manufnum = 0; |
| CFLASH.id = 0; |
| CFLASH.size = romsize; |
| CFLASH.sectors = 0; |
| CFLASH.open_mode= 0; |
| CFLASH.open_cnt = 0; |
| CFLASH.offset = 0; |
| CFLASH.mapped = (unsigned long)mapped; |
| CFLASH.physical = romaddr; |
| printk( KERN_INFO "BIOS: rom device with size " |
| "%dk registered.\n", CFLASH.size >> 10); |
| flashcount++; currflash++; |
| return; |
| } |
| |
| /* We found nothing in this area, so let's unmap it again */ |
| |
| if (flashcount && flashdevices[flashcount-1].mapped != (unsigned long)mapped) |
| iounmap(mapped); |
| |
| devices[flashdevices[currflash].idx].deactivate(); |
| } |
| |
| #undef CFLASH |
| |
| void flash_program (unsigned char *addr) |
| { |
| flash_command(addr, 0xa0); |
| } |
| |
| void flash_program_atmel (unsigned char *addr) |
| { |
| flash_command(addr, 0x80); |
| flash_command(addr, 0x20); |
| } |
| |
| int flash_erase (unsigned char *addr, unsigned int flashnum) |
| { |
| flash_command(addr, 0x80); |
| flash_command(addr, 0x10); |
| udelay(80); |
| return flash_ready_toggle(addr, 0); |
| } |
| |
| int flash_erase_sectors (unsigned char *addr, unsigned int flashnum, unsigned int startsec, unsigned int endsec) |
| { |
| unsigned int sector; |
| |
| if (!(flashchips[flashnum].flags & f_slow_sector_erase)) { |
| flash_command(addr, 0x80); |
| |
| if (flashchips[flashnum].flags&f_fwh_compl) { |
| flash_writeb(addr, 0x75555,0xaa); |
| flash_writeb(addr, 0x72aaa,0x55); |
| } else { |
| flash_writeb(addr, 0x5555,0xaa); |
| flash_writeb(addr, 0x2aaa,0x55); |
| } |
| |
| for (sector=startsec; sector <= endsec; sector++) { |
| flash_writeb (addr, flashchips[flashnum].sectors[sector]*1024, 0x30); |
| } |
| |
| udelay(150); // 80 max normally, wait 150usec to be sure |
| #if 0 |
| if (flashchips[flashnum].flags&f_fwh_compl) |
| #endif |
| return flash_ready_toggle(addr, flashchips[flashnum].sectors[sector-1]*1024); |
| #if 0 |
| else |
| return flash_ready_poll(addr, flashchips[flashnum].sectors[sector-1]*1024, 0xff); |
| #endif |
| } |
| |
| /* sectors must be sent the sector erase command for every sector */ |
| for (sector=startsec; sector <= endsec; sector++) { |
| flash_command(addr, 0x80); |
| if (flashchips[flashnum].flags&f_fwh_compl) { |
| flash_writeb(addr, 0x75555,0xaa); |
| flash_writeb(addr, 0x72aaa,0x55); |
| } else { |
| flash_writeb(addr, 0x5555,0xaa); |
| flash_writeb(addr, 0x2aaa,0x55); |
| } |
| |
| flash_writeb(addr, flashchips[flashnum].sectors[sector]*1024, 0x30); |
| udelay(150); |
| #if 0 |
| if (flashchips[flashnum].flags&f_fwh_compl) |
| #endif |
| flash_ready_toggle(addr, flashchips[flashnum].sectors[sector] *1024); |
| #if 0 |
| else |
| flash_ready_poll(addr, flashchips[flashnum].sectors[sector]*1024, 0xff); |
| #endif |
| } |
| |
| return 0; |
| |
| } |
| |
| /* waiting for the end of programming/erasure by using the toggle method. |
| * As long as there is a programming procedure going on, bit 6 of the last |
| * written byte is toggling it's state with each consecutive read. |
| * The toggling stops as soon as the procedure is completed. |
| * This function returns 0 if everything is ok, 1 if an error occurred |
| * while programming was in progress. |
| */ |
| |
| int flash_ready_toggle (unsigned char *addr, unsigned int offset) |
| { |
| unsigned long int timeout=0; |
| unsigned char oldflag, flag; |
| int loop=1; |
| |
| oldflag=flash_readb(addr, offset) & 0x40; |
| |
| while (loop && (timeout<0x7fffffff)) { |
| flag=flash_readb(addr, offset) & 0x40; |
| |
| if (flag == oldflag) |
| loop=0; |
| |
| oldflag=flag; |
| timeout++; |
| } |
| |
| if (loop) { |
| printk(KERN_DEBUG "BIOS: operation timed out (Toggle)\n"); |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| /* This functions is similar to the above one. While a programming |
| * procedure is going on, bit 7 of the last written data byte is |
| * inverted. When the procedure is completed, bit 7 contains the |
| * correct data value |
| */ |
| |
| int flash_ready_poll (unsigned char *addr, unsigned int offset, unsigned char data) |
| { |
| unsigned long int timeout=0; |
| unsigned char flag; |
| |
| flag=flash_readb(addr, offset); |
| |
| while ( ( flag & 0x80) != ( data & 0x80)) { |
| if ( ( flag & 0x80 ) == ( data & 0x80 ) ) { |
| #ifdef DBGTIMEOUT |
| printk(KERN_DEBUG "BIOS: Timeout value (EOT Polling) %ld\n",timeout); |
| #endif |
| return 0; |
| } |
| flag=flash_readb(addr, offset); |
| if (timeout++>12800) { // 10 times more than usual. |
| printk(KERN_ERR "BIOS: EOT Polling timed out at 0x%08x." |
| " Try again or increase max. timeout.\n",offset); |
| return 1; |
| } |
| if ((flag & 0x80) == ( data & 0x80)) { |
| flag=flash_readb(addr, offset); |
| } |
| } |
| #ifdef DBGTIMEOUT |
| printk(KERN_DEBUG "BIOS: Timeout value (EOT Polling) %ld\n",timeout); |
| #endif |
| |
| flag=flash_readb(addr, offset); |
| if ( ( flag & 0x80 ) == ( data & 0x80 ) ) return 0; else return 1; |
| } |
| |
| |
| |
| void iflash_program_byte (unsigned char *addr, unsigned int offset, unsigned char data) |
| { |
| unsigned long int timeout=0; |
| unsigned char flag; |
| |
| flash_writeb (addr, offset, 0x40); |
| flash_writeb (addr, offset, data); |
| |
| flash_writeb (addr, offset, 0x70); /* Read Status */ |
| do { |
| flag=flash_readb (addr, offset); |
| if (timeout++>100) { // usually 2 or 3 :-) |
| printk(KERN_ERR "BIOS: Intel programming timed out at" |
| "0x%08x. Try again or increase max. timeout.\n",offset); |
| return; |
| } |
| } while ((flag&0x80) != 0x80); |
| |
| #ifdef DBGTIMEOUT |
| printk (KERN_DEBUG"BIOS: Timeout value (Intel byte program) %ld\n",timeout); |
| #endif |
| |
| if (flag&0x18) { |
| flash_writeb (addr, offset, 0x50); /* Reset Status Register */ |
| printk (KERN_ERR "BIOS: Error occurred, please repeat write operation. (intel)\n"); |
| } |
| |
| flash_writeb (addr, offset, 0xff); |
| } |
| |
| |
| |
| int iflash_erase_sectors (unsigned char *addr, unsigned int flashnum, unsigned int startsec, unsigned int endsec) |
| { |
| unsigned long int timeout; |
| unsigned int sector, offset=0; |
| unsigned char flag; |
| |
| for (sector=startsec; sector<=endsec; sector++) { |
| offset=(flashchips[flashnum].sectors[sector]*1024); |
| flash_writeb (addr, offset, 0x20); |
| flash_writeb (addr, offset, 0xd0); |
| |
| flash_writeb (addr, offset, 0x70); /* Read Status */ |
| timeout=0; |
| do { |
| flag=flash_readb (addr, offset); |
| if (timeout++>1440000) { // usually 144000 |
| printk(KERN_ERR "BIOS: Intel sector erase timed out at 0x%08x. Try again or increase max. timeout.\n",offset); |
| return 1; |
| } |
| } while ((flag&0x80) != 0x80); |
| |
| #ifdef DBGTIMEOUT |
| printk (KERN_DEBUG "BIOS: Timeout value (Intel sector erase) %ld\n",timeout); |
| #endif |
| |
| if (flag&0x28) { |
| flash_writeb (addr, offset, 0x50); |
| flash_writeb (addr, offset, 0xff); |
| return 1; /* Error! */ |
| } |
| } |
| |
| flash_writeb (addr, offset, 0xff); |
| return 0; |
| } |
| |
| |
| |
| unsigned char flash_readb(unsigned char *addr, unsigned int offset) |
| { |
| #if defined(__alpha__) |
| if (flashdevices[currflash].data==(void *)0xfff80000) { |
| if (offset<0x80000) |
| outb(0x00,0x800); |
| else { |
| outb(0x01, 0x800); |
| offset-=0x80000; |
| } |
| } |
| #endif |
| return readb(addr+offset); |
| } |
| |
| |
| |
| void flash_writeb(unsigned char *addr, unsigned int offset, unsigned char data) |
| { |
| #if defined(__alpha__) |
| if (flashdevices[currflash].data==(void *)0xfff80000) { |
| if (offset<0x80000) |
| outb(0x00,0x800); |
| else { |
| outb(0x01, 0x800); |
| offset-=0x80000; |
| } |
| } |
| #endif |
| /* |
| printk(KERN_DEBUG "BIOS: writing 0x%02x to 0x%lx+0x%x\n", |
| data,bios,offset); |
| */ |
| writeb(data,addr+offset); |
| } |