539 lines
14 KiB
C
539 lines
14 KiB
C
/*
|
|
* 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);
|
|
}
|