blob: 285c818e625498a9fc930b9fa2d1e06d84befcea [file] [log] [blame]
/******************************************************************************
*!
*! Implements an interface for i2c compatible eeproms to run under linux.
*! Supports 2k, 8k(?) and 16k. Uses adaptive timing adjustents by
*! Johan.Adolfsson@axis.com
*!
*! Probing results:
*! 8k or not is detected (the assumes 2k or 16k)
*! 2k or 16k detected using test reads and writes.
*!
*!------------------------------------------------------------------------
*! HISTORY
*!
*! DATE NAME CHANGES
*! ---- ---- -------
*! Aug 28 1999 Edgar Iglesias Initial Version
*! Aug 31 1999 Edgar Iglesias Allow simultaneous users.
*! Sep 03 1999 Edgar Iglesias Updated probe.
*! Sep 03 1999 Edgar Iglesias Added bail-out stuff if we get interrupted
*! in the spin-lock.
*!
*! $Log: eeprom.c,v $
*! Revision 1.12 2003/04/09 08:31:14 pkj
*! Typo correction (taken from Linux 2.5).
*!
*! Revision 1.11 2003/02/12 20:43:46 johana
*! Previous checkin removed beginning of comment.
*!
*! Revision 1.10 2003/02/10 07:18:20 starvik
*! Removed misplaced ;
*!
*! Revision 1.9 2003/01/22 06:54:46 starvik
*! Fixed warnings issued by GCC 3.2.1
*!
*! Revision 1.8 2001/06/15 13:24:29 jonashg
*! * Added verification of pointers from userspace in read and write.
*! * Made busy counter volatile.
*! * Added define for inital write delay.
*! * Removed warnings by using loff_t instead of unsigned long.
*!
*! Revision 1.7 2001/06/14 15:26:54 jonashg
*! Removed test because condition is always true.
*!
*! Revision 1.6 2001/06/14 15:18:20 jonashg
*! Kb -> kB (makes quite a difference if you don't know if you have 2k or 16k).
*!
*! Revision 1.5 2001/06/14 14:39:51 jonashg
*! Forgot to use name when registering the driver.
*!
*! Revision 1.4 2001/06/14 14:35:47 jonashg
*! * Gave driver a name and used it in printk's.
*! * Cleanup.
*!
*! Revision 1.3 2001/03/19 16:04:46 markusl
*! Fixed init of fops struct
*!
*! Revision 1.2 2001/03/19 10:35:07 markusl
*! 2.4 port of eeprom driver
*!
*! Revision 1.8 2000/05/18 10:42:25 edgar
*! Make sure to end write cycle on _every_ write
*!
*! Revision 1.7 2000/01/17 17:41:01 johana
*! Adjusted probing and return -ENOSPC when writing outside EEPROM
*!
*! Revision 1.6 2000/01/17 15:50:36 johana
*! Added adaptive timing adjustments and fixed autoprobing for 2k and 16k(?)
*! EEPROMs
*!
*! Revision 1.5 1999/09/03 15:07:37 edgar
*! Added bail-out check to the spinlock
*!
*! Revision 1.4 1999/09/03 12:11:17 bjornw
*! Proper atomicity (need to use spinlocks, not if's). users -> busy.
*!
*!
*! (c) 1999 Axis Communications AB, Lund, Sweden
*!*****************************************************************************/
#include <linux/config.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include "i2c.h"
#define D(x)
/* If we should use adaptive timing or not: */
//#define EEPROM_ADAPTIVE_TIMING
#define EEPROM_MAJOR_NR 122 /* use a LOCAL/EXPERIMENTAL major for now */
#define EEPROM_MINOR_NR 0
/* Empirical sane initial value of the delay, the value will be adapted to
* what the chip needs when using EEPROM_ADAPTIVE_TIMING.
*/
#define INITIAL_WRITEDELAY_US 4000
#define MAX_WRITEDELAY_US 10000 /* 10 ms according to spec for 2KB EEPROM */
/* This one defines how many times to try when eeprom fails. */
#define EEPROM_RETRIES 10
#define EEPROM_2KB (2 * 1024)
/*#define EEPROM_4KB (4 * 1024)*/ /* Exists but not used in Axis products */
#define EEPROM_8KB (8 * 1024 - 1 ) /* Last byte has write protection bit */
#define EEPROM_16KB (16 * 1024)
#define i2c_delay(x) udelay(x)
/*
* This structure describes the attached eeprom chip.
* The values are probed for.
*/
struct eeprom_type
{
unsigned long size;
unsigned long sequential_write_pagesize;
unsigned char select_cmd;
unsigned long usec_delay_writecycles; /* Min time between write cycles
(up to 10ms for some models) */
unsigned long usec_delay_step; /* For adaptive algorithm */
int adapt_state; /* 1 = To high , 0 = Even, -1 = To low */
/* this one is to keep the read/write operations atomic */
wait_queue_head_t wait_q;
volatile int busy;
int retry_cnt_addr; /* Used to keep track of number of retries for
adaptive timing adjustments */
int retry_cnt_read;
};
static int eeprom_open(struct inode * inode, struct file * file);
static loff_t eeprom_lseek(struct file * file, loff_t offset, int orig);
static ssize_t eeprom_read(struct file * file, char * buf, size_t count,
loff_t *off);
static ssize_t eeprom_write(struct file * file, const char * buf, size_t count,
loff_t *off);
static int eeprom_close(struct inode * inode, struct file * file);
static int eeprom_address(unsigned long addr);
static int read_from_eeprom(char * buf, int count);
static int eeprom_write_buf(loff_t addr, const char * buf, int count);
static int eeprom_read_buf(loff_t addr, char * buf, int count);
static void eeprom_disable_write_protect(void);
static const char eeprom_name[] = "eeprom";
/* chip description */
static struct eeprom_type eeprom;
/* This is the exported file-operations structure for this device. */
struct file_operations eeprom_fops =
{
llseek: eeprom_lseek,
read: eeprom_read,
write: eeprom_write,
open: eeprom_open,
release: eeprom_close
};
/* eeprom init call. Probes for different eeprom models. */
int __init eeprom_init(void)
{
init_waitqueue_head(&eeprom.wait_q);
eeprom.busy = 0;
#if CONFIG_ETRAX_I2C_EEPROM_PROBE
#define EETEXT "Found"
#else
#define EETEXT "Assuming"
#endif
if (register_chrdev(EEPROM_MAJOR_NR, eeprom_name, &eeprom_fops))
{
printk(KERN_INFO "%s: unable to get major %d for eeprom device\n",
eeprom_name, EEPROM_MAJOR_NR);
return -1;
}
printk("EEPROM char device v0.3, (c) 2000 Axis Communications AB\n");
/*
* Note: Most of this probing method was taken from the printserver (5470e)
* codebase. It did not contain a way of finding the 16kB chips
* (M24128 or variants). The method used here might not work
* for all models. If you encounter problems the easiest way
* is probably to define your model within #ifdef's, and hard-
* code it.
*/
eeprom.size = 0;
eeprom.usec_delay_writecycles = INITIAL_WRITEDELAY_US;
eeprom.usec_delay_step = 128;
eeprom.adapt_state = 0;
#if CONFIG_ETRAX_I2C_EEPROM_PROBE
i2c_start();
i2c_outbyte(0x80);
if(!i2c_getack())
{
/* It's not 8k.. */
int success = 0;
unsigned char buf_2k_start[16];
/* Im not sure this will work... :) */
/* assume 2kB, if failure go for 16kB */
/* Test with 16kB settings.. */
/* If it's a 2kB EEPROM and we address it outside it's range
* it will mirror the address space:
* 1. We read two locations (that are mirrored),
* if the content differs * it's a 16kB EEPROM.
* 2. if it doesn't differ - write different value to one of the locations,
* check the other - if content still is the same it's a 2k EEPROM,
* restore original data.
*/
#define LOC1 8
#define LOC2 (0x1fb) /*1fb, 3ed, 5df, 7d1 */
/* 2k settings */
i2c_stop();
eeprom.size = EEPROM_2KB;
eeprom.select_cmd = 0xA0;
eeprom.sequential_write_pagesize = 16;
if( eeprom_read_buf( 0, buf_2k_start, 16 ) == 16 )
{
D(printk("2k start: '%16.16s'\n", buf_2k_start));
}
else
{
printk(KERN_INFO "%s: Failed to read in 2k mode!\n", eeprom_name);
}
/* 16k settings */
eeprom.size = EEPROM_16KB;
eeprom.select_cmd = 0xA0;
eeprom.sequential_write_pagesize = 64;
{
unsigned char loc1[4], loc2[4], tmp[4];
if( eeprom_read_buf(LOC2, loc2, 4) == 4)
{
if( eeprom_read_buf(LOC1, loc1, 4) == 4)
{
D(printk("0 loc1: (%i) '%4.4s' loc2 (%i) '%4.4s'\n",
LOC1, loc1, LOC2, loc2));
#if 0
if (memcmp(loc1, loc2, 4) != 0 )
{
/* It's 16k */
printk(KERN_INFO "%s: 16k detected in step 1\n", eeprom_name);
eeprom.size = EEPROM_16KB;
success = 1;
}
else
#endif
{
/* Do step 2 check */
/* Invert value */
loc1[0] = ~loc1[0];
if (eeprom_write_buf(LOC1, loc1, 1) == 1)
{
/* If 2k EEPROM this write will actually write 10 bytes
* from pos 0
*/
D(printk("1 loc1: (%i) '%4.4s' loc2 (%i) '%4.4s'\n",
LOC1, loc1, LOC2, loc2));
if( eeprom_read_buf(LOC1, tmp, 4) == 4)
{
D(printk("2 loc1: (%i) '%4.4s' tmp '%4.4s'\n",
LOC1, loc1, tmp));
if (memcmp(loc1, tmp, 4) != 0 )
{
printk(KERN_INFO "%s: read and write differs! Not 16kB\n",
eeprom_name);
loc1[0] = ~loc1[0];
if (eeprom_write_buf(LOC1, loc1, 1) == 1)
{
success = 1;
}
else
{
printk(KERN_INFO "%s: Restore 2k failed during probe,"
" EEPROM might be corrupt!\n", eeprom_name);
}
i2c_stop();
/* Go to 2k mode and write original data */
eeprom.size = EEPROM_2KB;
eeprom.select_cmd = 0xA0;
eeprom.sequential_write_pagesize = 16;
if( eeprom_write_buf(0, buf_2k_start, 16) == 16)
{
}
else
{
printk(KERN_INFO "%s: Failed to write back 2k start!\n",
eeprom_name);
}
eeprom.size = EEPROM_2KB;
}
}
if(!success)
{
if( eeprom_read_buf(LOC2, loc2, 1) == 1)
{
D(printk("0 loc1: (%i) '%4.4s' loc2 (%i) '%4.4s'\n",
LOC1, loc1, LOC2, loc2));
if (memcmp(loc1, loc2, 4) == 0 )
{
/* Data the same, must be mirrored -> 2k */
/* Restore data */
printk(KERN_INFO "%s: 2k detected in step 2\n", eeprom_name);
loc1[0] = ~loc1[0];
if (eeprom_write_buf(LOC1, loc1, 1) == 1)
{
success = 1;
}
else
{
printk(KERN_INFO "%s: Restore 2k failed during probe,"
" EEPROM might be corrupt!\n", eeprom_name);
}
eeprom.size = EEPROM_2KB;
}
else
{
printk(KERN_INFO "%s: 16k detected in step 2\n",
eeprom_name);
loc1[0] = ~loc1[0];
/* Data differs, assume 16k */
/* Restore data */
if (eeprom_write_buf(LOC1, loc1, 1) == 1)
{
success = 1;
}
else
{
printk(KERN_INFO "%s: Restore 16k failed during probe,"
" EEPROM might be corrupt!\n", eeprom_name);
}
eeprom.size = EEPROM_16KB;
}
}
}
}
} /* read LOC1 */
} /* address LOC1 */
if (!success)
{
printk(KERN_INFO "%s: Probing failed!, using 2KB!\n", eeprom_name);
eeprom.size = EEPROM_2KB;
}
} /* read */
}
}
else
{
i2c_outbyte(0x00);
if(!i2c_getack())
{
/* No 8k */
eeprom.size = EEPROM_2KB;
}
else
{
i2c_start();
i2c_outbyte(0x81);
if (!i2c_getack())
{
eeprom.size = EEPROM_2KB;
}
else
{
/* It's a 8kB */
i2c_inbyte();
eeprom.size = EEPROM_8KB;
}
}
}
i2c_stop();
#elif defined(CONFIG_ETRAX_I2C_EEPROM_16KB)
eeprom.size = EEPROM_16KB;
#elif defined(CONFIG_ETRAX_I2C_EEPROM_8KB)
eeprom.size = EEPROM_8KB;
#elif defined(CONFIG_ETRAX_I2C_EEPROM_2KB)
eeprom.size = EEPROM_2KB;
#endif
switch(eeprom.size)
{
case (EEPROM_2KB):
printk("%s: " EETEXT " i2c compatible 2kB eeprom.\n", eeprom_name);
eeprom.sequential_write_pagesize = 16;
eeprom.select_cmd = 0xA0;
break;
case (EEPROM_8KB):
printk("%s: " EETEXT " i2c compatible 8kB eeprom.\n", eeprom_name);
eeprom.sequential_write_pagesize = 16;
eeprom.select_cmd = 0x80;
break;
case (EEPROM_16KB):
printk("%s: " EETEXT " i2c compatible 16kB eeprom.\n", eeprom_name);
eeprom.sequential_write_pagesize = 64;
eeprom.select_cmd = 0xA0;
break;
default:
eeprom.size = 0;
printk("%s: Did not find a supported eeprom\n", eeprom_name);
break;
}
eeprom_disable_write_protect();
return 0;
}
/* Opens the device. */
static int eeprom_open(struct inode * inode, struct file * file)
{
if(MINOR(inode->i_rdev) != EEPROM_MINOR_NR)
return -ENXIO;
if(MAJOR(inode->i_rdev) != EEPROM_MAJOR_NR)
return -ENXIO;
if( eeprom.size > 0 )
{
/* OK */
return 0;
}
/* No EEprom found */
return -EFAULT;
}
/* Changes the current file position. */
static loff_t eeprom_lseek(struct file * file, loff_t offset, int orig)
{
/*
* orig 0: position from begning of eeprom
* orig 1: relative from current position
* orig 2: position from last eeprom address
*/
switch (orig)
{
case 0:
file->f_pos = offset;
break;
case 1:
file->f_pos += offset;
break;
case 2:
file->f_pos = eeprom.size - offset;
break;
default:
return -EINVAL;
}
/* truncate position */
if (file->f_pos < 0)
{
file->f_pos = 0;
return(-EOVERFLOW);
}
if (file->f_pos >= eeprom.size)
{
file->f_pos = eeprom.size - 1;
return(-EOVERFLOW);
}
return ( file->f_pos );
}
/* Reads data from eeprom. */
static int eeprom_read_buf(loff_t addr, char * buf, int count)
{
struct file f;
f.f_pos = addr;
return eeprom_read(&f, buf, count, &addr);
}
/* Reads data from eeprom. */
static ssize_t eeprom_read(struct file * file, char * buf, size_t count, loff_t *off)
{
int read=0;
unsigned long p = *off;
unsigned char page;
if(p >= eeprom.size) /* Address i 0 - (size-1) */
{
return -EFAULT;
}
while(eeprom.busy)
{
interruptible_sleep_on(&eeprom.wait_q);
/* bail out if we get interrupted */
if (signal_pending(current))
return -EINTR;
}
eeprom.busy++;
page = (unsigned char) (p >> 8);
if(!eeprom_address(p))
{
printk(KERN_INFO "%s: Read failed to address the eeprom: "
"0x%08lX (%li) page: %i\n", eeprom_name, p, p, page);
i2c_stop();
/* don't forget to wake them up */
eeprom.busy--;
wake_up_interruptible(&eeprom.wait_q);
return -EFAULT;
}
if(count > eeprom.size - p)
{
/* truncate count */
count = eeprom.size - p;
}
/* stop dummy write op and initiate the read op */
i2c_start();
/* special case for small eeproms */
if(eeprom.size < EEPROM_16KB)
{
i2c_outbyte( eeprom.select_cmd | 1 | (page << 1) );
}
/* go on with the actual read */
read = read_from_eeprom( buf, count);
if(read > 0)
{
*off = p + read;
}
eeprom.busy--;
wake_up_interruptible(&eeprom.wait_q);
return read;
}
/* Writes data to eeprom. */
static int eeprom_write_buf(loff_t addr, const char * buf, int count)
{
struct file f;
f.f_pos = addr;
return eeprom_write(&f, buf, count, &addr);
}
/* Writes data to eeprom. */
static ssize_t eeprom_write(struct file * file, const char * buf, size_t count,
loff_t *off)
{
int i, written, restart=1;
unsigned long p;
if (verify_area(VERIFY_READ, buf, count))
{
return -EFAULT;
}
while(eeprom.busy)
{
interruptible_sleep_on(&eeprom.wait_q);
/* bail out if we get interrupted */
if (signal_pending(current))
return -EINTR;
}
eeprom.busy++;
for(i = 0; (i < EEPROM_RETRIES) && (restart > 0); i++)
{
restart = 0;
written = 0;
p = *off;
while( (written < count) && (p < eeprom.size))
{
/* address the eeprom */
if(!eeprom_address(p))
{
printk(KERN_INFO "%s: Write failed to address the eeprom: "
"0x%08lX (%li) \n", eeprom_name, p, p);
i2c_stop();
/* don't forget to wake them up */
eeprom.busy--;
wake_up_interruptible(&eeprom.wait_q);
return -EFAULT;
}
#ifdef EEPROM_ADAPTIVE_TIMING
/* Adaptive algorithm to adjust timing */
if (eeprom.retry_cnt_addr > 0)
{
/* To Low now */
D(printk(">D=%i d=%i\n",
eeprom.usec_delay_writecycles, eeprom.usec_delay_step));
if (eeprom.usec_delay_step < 4)
{
eeprom.usec_delay_step++;
eeprom.usec_delay_writecycles += eeprom.usec_delay_step;
}
else
{
if (eeprom.adapt_state > 0)
{
/* To Low before */
eeprom.usec_delay_step *= 2;
if (eeprom.usec_delay_step > 2)
{
eeprom.usec_delay_step--;
}
eeprom.usec_delay_writecycles += eeprom.usec_delay_step;
}
else if (eeprom.adapt_state < 0)
{
/* To High before (toggle dir) */
eeprom.usec_delay_writecycles += eeprom.usec_delay_step;
if (eeprom.usec_delay_step > 1)
{
eeprom.usec_delay_step /= 2;
eeprom.usec_delay_step--;
}
}
}
eeprom.adapt_state = 1;
}
else
{
/* To High (or good) now */
D(printk("<D=%i d=%i\n",
eeprom.usec_delay_writecycles, eeprom.usec_delay_step));
if (eeprom.adapt_state < 0)
{
/* To High before */
if (eeprom.usec_delay_step > 1)
{
eeprom.usec_delay_step *= 2;
eeprom.usec_delay_step--;
if (eeprom.usec_delay_writecycles > eeprom.usec_delay_step)
{
eeprom.usec_delay_writecycles -= eeprom.usec_delay_step;
}
}
}
else if (eeprom.adapt_state > 0)
{
/* To Low before (toggle dir) */
if (eeprom.usec_delay_writecycles > eeprom.usec_delay_step)
{
eeprom.usec_delay_writecycles -= eeprom.usec_delay_step;
}
if (eeprom.usec_delay_step > 1)
{
eeprom.usec_delay_step /= 2;
eeprom.usec_delay_step--;
}
eeprom.adapt_state = -1;
}
if (eeprom.adapt_state > -100)
{
eeprom.adapt_state--;
}
else
{
/* Restart adaption */
D(printk("#Restart\n"));
eeprom.usec_delay_step++;
}
}
#endif /* EEPROM_ADAPTIVE_TIMING */
/* write until we hit a page boundary or count */
do
{
i2c_outbyte(buf[written]);
if(!i2c_getack())
{
restart=1;
printk(KERN_INFO "%s: write error, retrying. %d\n", eeprom_name, i);
i2c_stop();
break;
}
written++;
p++;
} while( written < count && ( p % eeprom.sequential_write_pagesize ));
/* end write cycle */
i2c_stop();
i2c_delay(eeprom.usec_delay_writecycles);
} /* while */
} /* for */
eeprom.busy--;
wake_up_interruptible(&eeprom.wait_q);
if (written == 0 && p >= eeprom.size){
return -ENOSPC;
}
*off = p;
return written;
}
/* Closes the device. */
static int eeprom_close(struct inode * inode, struct file * file)
{
/* do nothing for now */
return 0;
}
/* Sets the current address of the eeprom. */
static int eeprom_address(unsigned long addr)
{
int i;
unsigned char page, offset;
page = (unsigned char) (addr >> 8);
offset = (unsigned char) addr;
for(i = 0; i < EEPROM_RETRIES; i++)
{
/* start a dummy write for addressing */
i2c_start();
if(eeprom.size == EEPROM_16KB)
{
i2c_outbyte( eeprom.select_cmd );
i2c_getack();
i2c_outbyte(page);
}
else
{
i2c_outbyte( eeprom.select_cmd | (page << 1) );
}
if(!i2c_getack())
{
/* retry */
i2c_stop();
/* Must have a delay here.. 500 works, >50, 100->works 5th time*/
i2c_delay(MAX_WRITEDELAY_US / EEPROM_RETRIES * i);
/* The chip needs up to 10 ms from write stop to next start */
}
else
{
i2c_outbyte(offset);
if(!i2c_getack())
{
/* retry */
i2c_stop();
}
else
break;
}
}
eeprom.retry_cnt_addr = i;
D(printk("%i\n", eeprom.retry_cnt_addr));
if(eeprom.retry_cnt_addr == EEPROM_RETRIES)
{
/* failed */
return 0;
}
return 1;
}
/* Reads from current address. */
static int read_from_eeprom(char * buf, int count)
{
int i, read=0;
for(i = 0; i < EEPROM_RETRIES; i++)
{
if(eeprom.size == EEPROM_16KB)
{
i2c_outbyte( eeprom.select_cmd | 1 );
}
if(i2c_getack())
{
break;
}
}
if(i == EEPROM_RETRIES)
{
printk(KERN_INFO "%s: failed to read from eeprom\n", eeprom_name);
i2c_stop();
return -EFAULT;
}
while( (read < count))
{
if (put_user(i2c_inbyte(), &buf[read++]))
{
i2c_stop();
return -EFAULT;
}
/*
* make sure we don't ack last byte or you will get very strange
* results!
*/
if(read < count)
{
i2c_sendack();
}
}
/* stop the operation */
i2c_stop();
return read;
}
/* Disables write protection if applicable. */
#define DBP_SAVE(x)
#define ax_printf printk
static void eeprom_disable_write_protect(void)
{
/* Disable write protect */
if (eeprom.size == EEPROM_8KB)
{
/* Step 1 Set WEL = 1 (write 00000010 to address 1FFFh */
i2c_start();
i2c_outbyte(0xbe);
if(!i2c_getack())
{
DBP_SAVE(ax_printf("Get ack returns false\n"));
}
i2c_outbyte(0xFF);
if(!i2c_getack())
{
DBP_SAVE(ax_printf("Get ack returns false 2\n"));
}
i2c_outbyte(0x02);
if(!i2c_getack())
{
DBP_SAVE(ax_printf("Get ack returns false 3\n"));
}
i2c_stop();
i2c_delay(1000);
/* Step 2 Set RWEL = 1 (write 00000110 to address 1FFFh */
i2c_start();
i2c_outbyte(0xbe);
if(!i2c_getack())
{
DBP_SAVE(ax_printf("Get ack returns false 55\n"));
}
i2c_outbyte(0xFF);
if(!i2c_getack())
{
DBP_SAVE(ax_printf("Get ack returns false 52\n"));
}
i2c_outbyte(0x06);
if(!i2c_getack())
{
DBP_SAVE(ax_printf("Get ack returns false 53\n"));
}
i2c_stop();
/* Step 3 Set BP1, BP0, and/or WPEN bits (write 00000110 to address 1FFFh */
i2c_start();
i2c_outbyte(0xbe);
if(!i2c_getack())
{
DBP_SAVE(ax_printf("Get ack returns false 56\n"));
}
i2c_outbyte(0xFF);
if(!i2c_getack())
{
DBP_SAVE(ax_printf("Get ack returns false 57\n"));
}
i2c_outbyte(0x06);
if(!i2c_getack())
{
DBP_SAVE(ax_printf("Get ack returns false 58\n"));
}
i2c_stop();
/* Write protect disabled */
}
}
module_init(eeprom_init);