2005-04-17 02:20:36 +04:00
/*
* Flash memory interface rev .5 driver for the Intel
* Flash chips used on the NetWinder .
*
* 20 / 08 / 2000 RMK use __ioremap to map flash into virtual memory
* make a few more places use " volatile "
* 22 / 05 / 2001 RMK - Lock read against write
* - merge printk level changes ( with mods ) from Alan Cox .
* - use * ppos as the file position , not file - > f_pos .
* - fix check for out of range pos and r / w size
*
* Please note that we are tampering with the only flash chip in the
* machine , which contains the bootup code . We therefore have the
* power to convert these machines into doorstops . . .
*/
# include <linux/module.h>
# include <linux/types.h>
# include <linux/fs.h>
# include <linux/errno.h>
# include <linux/mm.h>
# include <linux/delay.h>
# include <linux/proc_fs.h>
# include <linux/miscdevice.h>
# include <linux/spinlock.h>
# include <linux/rwsem.h>
# include <linux/init.h>
2006-03-23 14:00:27 +03:00
# include <linux/mutex.h>
2009-12-20 02:36:00 +03:00
# include <linux/jiffies.h>
2005-04-17 02:20:36 +04:00
# include <asm/hardware/dec21285.h>
# include <asm/io.h>
# include <asm/leds.h>
# include <asm/mach-types.h>
# include <asm/system.h>
# include <asm/uaccess.h>
/*****************************************************************************/
# include <asm/nwflash.h>
# define NWFLASH_VERSION "6.4"
2010-06-02 16:28:52 +04:00
static DEFINE_MUTEX ( flash_mutex ) ;
2005-04-17 02:20:36 +04:00
static void kick_open ( void ) ;
static int get_flash_id ( void ) ;
static int erase_block ( int nBlock ) ;
static int write_block ( unsigned long p , const char __user * buf , int count ) ;
# define KFLASH_SIZE 1024*1024 //1 Meg
# define KFLASH_SIZE4 4*1024*1024 //4 Meg
# define KFLASH_ID 0x89A6 //Intel flash
# define KFLASH_ID4 0xB0D4 //Intel flash 4Meg
static int flashdebug ; //if set - we will display progress msgs
static int gbWriteEnable ;
static int gbWriteBase64Enable ;
static volatile unsigned char * FLASH_BASE ;
static int gbFlashSize = KFLASH_SIZE ;
2006-03-23 14:00:27 +03:00
static DEFINE_MUTEX ( nwflash_mutex ) ;
2005-04-17 02:20:36 +04:00
static int get_flash_id ( void )
{
volatile unsigned int c1 , c2 ;
/*
* try to get flash chip ID
*/
kick_open ( ) ;
c2 = inb ( 0x80 ) ;
* ( volatile unsigned char * ) ( FLASH_BASE + 0x8000 ) = 0x90 ;
udelay ( 15 ) ;
c1 = * ( volatile unsigned char * ) FLASH_BASE ;
c2 = inb ( 0x80 ) ;
/*
* on 4 Meg flash the second byte is actually at offset 2. . .
*/
if ( c1 = = 0xB0 )
c2 = * ( volatile unsigned char * ) ( FLASH_BASE + 2 ) ;
else
c2 = * ( volatile unsigned char * ) ( FLASH_BASE + 1 ) ;
c2 + = ( c1 < < 8 ) ;
/*
* set it back to read mode
*/
* ( volatile unsigned char * ) ( FLASH_BASE + 0x8000 ) = 0xFF ;
if ( c2 = = KFLASH_ID4 )
gbFlashSize = KFLASH_SIZE4 ;
return c2 ;
}
2010-04-27 02:24:05 +04:00
static long flash_ioctl ( struct file * filep , unsigned int cmd , unsigned long arg )
2005-04-17 02:20:36 +04:00
{
2010-06-02 16:28:52 +04:00
mutex_lock ( & flash_mutex ) ;
2005-04-17 02:20:36 +04:00
switch ( cmd ) {
case CMD_WRITE_DISABLE :
gbWriteBase64Enable = 0 ;
gbWriteEnable = 0 ;
break ;
case CMD_WRITE_ENABLE :
gbWriteEnable = 1 ;
break ;
case CMD_WRITE_BASE64K_ENABLE :
gbWriteBase64Enable = 1 ;
break ;
default :
gbWriteBase64Enable = 0 ;
gbWriteEnable = 0 ;
2010-06-02 16:28:52 +04:00
mutex_unlock ( & flash_mutex ) ;
2005-04-17 02:20:36 +04:00
return - EINVAL ;
}
2010-06-02 16:28:52 +04:00
mutex_unlock ( & flash_mutex ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
static ssize_t flash_read ( struct file * file , char __user * buf , size_t size ,
loff_t * ppos )
{
2008-07-25 12:48:18 +04:00
ssize_t ret ;
2005-04-17 02:20:36 +04:00
if ( flashdebug )
2008-07-26 18:46:39 +04:00
printk ( KERN_DEBUG " flash_read: flash_read: offset=0x%llx, "
" buffer=%p, count=0x%zx. \n " , * ppos , buf , size ) ;
2008-07-25 12:48:18 +04:00
/*
* We now lock against reads and writes . - - rmk
*/
if ( mutex_lock_interruptible ( & nwflash_mutex ) )
return - ERESTARTSYS ;
2008-07-26 18:46:39 +04:00
ret = simple_read_from_buffer ( buf , size , ppos , ( void * ) FLASH_BASE , gbFlashSize ) ;
2008-07-25 12:48:18 +04:00
mutex_unlock ( & nwflash_mutex ) ;
2005-04-17 02:20:36 +04:00
return ret ;
}
static ssize_t flash_write ( struct file * file , const char __user * buf ,
size_t size , loff_t * ppos )
{
unsigned long p = * ppos ;
unsigned int count = size ;
int written ;
int nBlock , temp , rc ;
int i , j ;
if ( flashdebug )
printk ( " flash_write: offset=0x%lX, buffer=0x%p, count=0x%X. \n " ,
p , buf , count ) ;
if ( ! gbWriteEnable )
return - EINVAL ;
if ( p < 64 * 1024 & & ( ! gbWriteBase64Enable ) )
return - EINVAL ;
/*
* check for out of range pos or count
*/
if ( p > = gbFlashSize )
return count ? - ENXIO : 0 ;
if ( count > gbFlashSize - p )
count = gbFlashSize - p ;
if ( ! access_ok ( VERIFY_READ , buf , count ) )
return - EFAULT ;
/*
* We now lock against reads and writes . - - rmk
*/
2006-03-23 14:00:27 +03:00
if ( mutex_lock_interruptible ( & nwflash_mutex ) )
2005-04-17 02:20:36 +04:00
return - ERESTARTSYS ;
written = 0 ;
leds_event ( led_claim ) ;
leds_event ( led_green_on ) ;
nBlock = ( int ) p > > 16 ; //block # of 64K bytes
/*
* # of 64 K blocks to erase and write
*/
temp = ( ( int ) ( p + count ) > > 16 ) - nBlock + 1 ;
/*
* write ends at exactly 64 k boundary ?
*/
if ( ( ( int ) ( p + count ) & 0xFFFF ) = = 0 )
temp - = 1 ;
if ( flashdebug )
printk ( KERN_DEBUG " flash_write: writing %d block(s) "
" starting at %d. \n " , temp , nBlock ) ;
for ( ; temp ; temp - - , nBlock + + ) {
if ( flashdebug )
printk ( KERN_DEBUG " flash_write: erasing block %d. \n " , nBlock ) ;
/*
* first we have to erase the block ( s ) , where we will write . . .
*/
i = 0 ;
j = 0 ;
RetryBlock :
do {
rc = erase_block ( nBlock ) ;
i + + ;
} while ( rc & & i < 10 ) ;
if ( rc ) {
printk ( KERN_ERR " flash_write: erase error %x \n " , rc ) ;
break ;
}
if ( flashdebug )
printk ( KERN_DEBUG " flash_write: writing offset %lX, "
" from buf %p, bytes left %X. \n " , p , buf ,
count - written ) ;
/*
* write_block will limit write to space left in this block
*/
rc = write_block ( p , buf , count - written ) ;
j + + ;
/*
* if somehow write verify failed ? Can ' t happen ? ?
*/
if ( ! rc ) {
/*
* retry up to 10 times
*/
if ( j < 10 )
goto RetryBlock ;
else
/*
* else quit with error . . .
*/
rc = - 1 ;
}
if ( rc < 0 ) {
printk ( KERN_ERR " flash_write: write error %X \n " , rc ) ;
break ;
}
p + = rc ;
buf + = rc ;
written + = rc ;
* ppos + = rc ;
if ( flashdebug )
printk ( KERN_DEBUG " flash_write: written 0x%X bytes OK. \n " , written ) ;
}
/*
* restore reg on exit
*/
leds_event ( led_release ) ;
2006-03-23 14:00:27 +03:00
mutex_unlock ( & nwflash_mutex ) ;
2005-04-17 02:20:36 +04:00
return written ;
}
/*
* The memory devices use the full 32 / 64 bits of the offset , and so we cannot
* check against negative addresses : they are ok . The return value is weird ,
* though , in that case ( 0 ) .
*
* also note that seeking relative to the " end of file " isn ' t supported :
* it has no meaning , so it returns - EINVAL .
*/
static loff_t flash_llseek ( struct file * file , loff_t offset , int orig )
{
loff_t ret ;
2010-06-02 16:28:52 +04:00
mutex_lock ( & flash_mutex ) ;
2005-04-17 02:20:36 +04:00
if ( flashdebug )
printk ( KERN_DEBUG " flash_llseek: offset=0x%X, orig=0x%X. \n " ,
( unsigned int ) offset , orig ) ;
switch ( orig ) {
case 0 :
if ( offset < 0 ) {
ret = - EINVAL ;
break ;
}
if ( ( unsigned int ) offset > gbFlashSize ) {
ret = - EINVAL ;
break ;
}
file - > f_pos = ( unsigned int ) offset ;
ret = file - > f_pos ;
break ;
case 1 :
if ( ( file - > f_pos + offset ) > gbFlashSize ) {
ret = - EINVAL ;
break ;
}
if ( ( file - > f_pos + offset ) < 0 ) {
ret = - EINVAL ;
break ;
}
file - > f_pos + = offset ;
ret = file - > f_pos ;
break ;
default :
ret = - EINVAL ;
}
2010-06-02 16:28:52 +04:00
mutex_unlock ( & flash_mutex ) ;
2005-04-17 02:20:36 +04:00
return ret ;
}
/*
* assume that main Write routine did the parameter checking . . .
* so just go ahead and erase , what requested !
*/
static int erase_block ( int nBlock )
{
volatile unsigned int c1 ;
volatile unsigned char * pWritePtr ;
unsigned long timeout ;
int temp , temp1 ;
/*
* orange LED = = erase
*/
leds_event ( led_amber_on ) ;
/*
* reset footbridge to the correct offset 0 ( . . .0 . .3 )
*/
* CSR_ROMWRITEREG = 0 ;
/*
* dummy ROM read
*/
c1 = * ( volatile unsigned char * ) ( FLASH_BASE + 0x8000 ) ;
kick_open ( ) ;
/*
* reset status if old errors
*/
* ( volatile unsigned char * ) ( FLASH_BASE + 0x8000 ) = 0x50 ;
/*
* erase a block . . .
* aim at the middle of a current block . . .
*/
pWritePtr = ( unsigned char * ) ( ( unsigned int ) ( FLASH_BASE + 0x8000 + ( nBlock < < 16 ) ) ) ;
/*
* dummy read
*/
c1 = * pWritePtr ;
kick_open ( ) ;
/*
* erase
*/
* ( volatile unsigned char * ) pWritePtr = 0x20 ;
/*
* confirm
*/
* ( volatile unsigned char * ) pWritePtr = 0xD0 ;
/*
* wait 10 ms
*/
msleep ( 10 ) ;
/*
* wait while erasing in process ( up to 10 sec )
*/
timeout = jiffies + 10 * HZ ;
c1 = 0 ;
while ( ! ( c1 & 0x80 ) & & time_before ( jiffies , timeout ) ) {
msleep ( 10 ) ;
/*
* read any address
*/
c1 = * ( volatile unsigned char * ) ( pWritePtr ) ;
// printk("Flash_erase: status=%X.\n",c1);
}
/*
* set flash for normal read access
*/
kick_open ( ) ;
// *(volatile unsigned char*)(FLASH_BASE+0x8000) = 0xFF;
* ( volatile unsigned char * ) pWritePtr = 0xFF ; //back to normal operation
/*
* check if erase errors were reported
*/
if ( c1 & 0x20 ) {
printk ( KERN_ERR " flash_erase: err at %p \n " , pWritePtr ) ;
/*
* reset error
*/
* ( volatile unsigned char * ) ( FLASH_BASE + 0x8000 ) = 0x50 ;
return - 2 ;
}
/*
* just to make sure - verify if erased OK . . .
*/
msleep ( 10 ) ;
pWritePtr = ( unsigned char * ) ( ( unsigned int ) ( FLASH_BASE + ( nBlock < < 16 ) ) ) ;
for ( temp = 0 ; temp < 16 * 1024 ; temp + + , pWritePtr + = 4 ) {
if ( ( temp1 = * ( volatile unsigned int * ) pWritePtr ) ! = 0xFFFFFFFF ) {
printk ( KERN_ERR " flash_erase: verify err at %p = %X \n " ,
pWritePtr , temp1 ) ;
return - 1 ;
}
}
return 0 ;
}
/*
* write_block will limit number of bytes written to the space in this block
*/
static int write_block ( unsigned long p , const char __user * buf , int count )
{
volatile unsigned int c1 ;
volatile unsigned int c2 ;
unsigned char * pWritePtr ;
unsigned int uAddress ;
unsigned int offset ;
unsigned long timeout ;
unsigned long timeout1 ;
/*
* red LED = = write
*/
leds_event ( led_amber_off ) ;
leds_event ( led_red_on ) ;
pWritePtr = ( unsigned char * ) ( ( unsigned int ) ( FLASH_BASE + p ) ) ;
/*
* check if write will end in this block . . . .
*/
offset = p & 0xFFFF ;
if ( offset + count > 0x10000 )
count = 0x10000 - offset ;
/*
* wait up to 30 sec for this block
*/
timeout = jiffies + 30 * HZ ;
for ( offset = 0 ; offset < count ; offset + + , pWritePtr + + ) {
uAddress = ( unsigned int ) pWritePtr ;
uAddress & = 0xFFFFFFFC ;
if ( __get_user ( c2 , buf + offset ) )
return - EFAULT ;
WriteRetry :
/*
* dummy read
*/
c1 = * ( volatile unsigned char * ) ( FLASH_BASE + 0x8000 ) ;
/*
* kick open the write gate
*/
kick_open ( ) ;
/*
* program footbridge to the correct offset . . .0 . .3
*/
* CSR_ROMWRITEREG = ( unsigned int ) pWritePtr & 3 ;
/*
* write cmd
*/
* ( volatile unsigned char * ) ( uAddress ) = 0x40 ;
/*
* data to write
*/
* ( volatile unsigned char * ) ( uAddress ) = c2 ;
/*
* get status
*/
* ( volatile unsigned char * ) ( FLASH_BASE + 0x10000 ) = 0x70 ;
c1 = 0 ;
/*
* wait up to 1 sec for this byte
*/
timeout1 = jiffies + 1 * HZ ;
/*
* while not ready . . .
*/
while ( ! ( c1 & 0x80 ) & & time_before ( jiffies , timeout1 ) )
c1 = * ( volatile unsigned char * ) ( FLASH_BASE + 0x8000 ) ;
/*
* if timeout getting status
*/
if ( time_after_eq ( jiffies , timeout1 ) ) {
kick_open ( ) ;
/*
* reset err
*/
* ( volatile unsigned char * ) ( FLASH_BASE + 0x8000 ) = 0x50 ;
goto WriteRetry ;
}
/*
* switch on read access , as a default flash operation mode
*/
kick_open ( ) ;
/*
* read access
*/
* ( volatile unsigned char * ) ( FLASH_BASE + 0x8000 ) = 0xFF ;
/*
* if hardware reports an error writing , and not timeout -
* reset the chip and retry
*/
if ( c1 & 0x10 ) {
kick_open ( ) ;
/*
* reset err
*/
* ( volatile unsigned char * ) ( FLASH_BASE + 0x8000 ) = 0x50 ;
/*
* before timeout ?
*/
if ( time_before ( jiffies , timeout ) ) {
if ( flashdebug )
printk ( KERN_DEBUG " write_block: Retrying write at 0x%X)n " ,
pWritePtr - FLASH_BASE ) ;
/*
* no LED = = waiting
*/
leds_event ( led_amber_off ) ;
/*
* wait couple ms
*/
msleep ( 10 ) ;
/*
* red LED = = write
*/
leds_event ( led_red_on ) ;
goto WriteRetry ;
} else {
printk ( KERN_ERR " write_block: timeout at 0x%X \n " ,
pWritePtr - FLASH_BASE ) ;
/*
* return error - 2
*/
return - 2 ;
}
}
}
/*
* green LED = = read / verify
*/
leds_event ( led_amber_off ) ;
leds_event ( led_green_on ) ;
msleep ( 10 ) ;
pWritePtr = ( unsigned char * ) ( ( unsigned int ) ( FLASH_BASE + p ) ) ;
for ( offset = 0 ; offset < count ; offset + + ) {
char c , c1 ;
if ( __get_user ( c , buf ) )
return - EFAULT ;
buf + + ;
if ( ( c1 = * pWritePtr + + ) ! = c ) {
printk ( KERN_ERR " write_block: verify error at 0x%X (%02X!=%02X) \n " ,
pWritePtr - FLASH_BASE , c1 , c ) ;
return 0 ;
}
}
return count ;
}
static void kick_open ( void )
{
unsigned long flags ;
/*
* we want to write a bit pattern XXX1 to Xilinx to enable
* the write gate , which will be open for about the next 2 ms .
*/
2008-12-06 11:25:16 +03:00
spin_lock_irqsave ( & nw_gpio_lock , flags ) ;
nw_cpld_modify ( CPLD_FLASH_WR_ENABLE , CPLD_FLASH_WR_ENABLE ) ;
spin_unlock_irqrestore ( & nw_gpio_lock , flags ) ;
2005-04-17 02:20:36 +04:00
/*
* let the ISA bus to catch on . . .
*/
udelay ( 25 ) ;
}
2006-07-03 11:24:21 +04:00
static const struct file_operations flash_fops =
2005-04-17 02:20:36 +04:00
{
. owner = THIS_MODULE ,
. llseek = flash_llseek ,
. read = flash_read ,
. write = flash_write ,
2010-04-27 02:24:05 +04:00
. unlocked_ioctl = flash_ioctl ,
2005-04-17 02:20:36 +04:00
} ;
static struct miscdevice flash_miscdev =
{
FLASH_MINOR ,
" nwflash " ,
& flash_fops
} ;
static int __init nwflash_init ( void )
{
int ret = - ENODEV ;
if ( machine_is_netwinder ( ) ) {
int id ;
FLASH_BASE = ioremap ( DC21285_FLASH , KFLASH_SIZE4 ) ;
if ( ! FLASH_BASE )
goto out ;
id = get_flash_id ( ) ;
if ( ( id ! = KFLASH_ID ) & & ( id ! = KFLASH_ID4 ) ) {
ret = - ENXIO ;
iounmap ( ( void * ) FLASH_BASE ) ;
printk ( " Flash: incorrect ID 0x%04X. \n " , id ) ;
goto out ;
}
printk ( " Flash ROM driver v.%s, flash device ID 0x%04X, size %d Mb. \n " ,
NWFLASH_VERSION , id , gbFlashSize / ( 1024 * 1024 ) ) ;
ret = misc_register ( & flash_miscdev ) ;
if ( ret < 0 ) {
iounmap ( ( void * ) FLASH_BASE ) ;
}
}
out :
return ret ;
}
static void __exit nwflash_exit ( void )
{
misc_deregister ( & flash_miscdev ) ;
iounmap ( ( void * ) FLASH_BASE ) ;
}
MODULE_LICENSE ( " GPL " ) ;
module_param ( flashdebug , bool , 0644 ) ;
module_init ( nwflash_init ) ;
module_exit ( nwflash_exit ) ;