patch-2.4.5 linux/arch/cris/drivers/eeprom.c

Next file: linux/arch/cris/drivers/ethernet.c
Previous file: linux/arch/cris/drivers/ds1302.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.4.4/linux/arch/cris/drivers/eeprom.c linux/arch/cris/drivers/eeprom.c
@@ -0,0 +1,1161 @@
+/*!**************************************************************************
+*!
+*!  FILE NAME:      e100eeprom.c
+*!
+*!  DESCRIPTION:    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.
+*!
+*!  FUNCTIONS:
+*!
+*!  (Exported)
+*!              eeprom_init()
+*!
+*!  (Local)
+*!
+*!              eeprom_open()
+*!              eeprom_lseek()
+*!              eeprom_read()
+*!              eeprom_write()
+*!              eeprom_close()
+*!              eeprom_address()
+*!              eeprom_disable_write_protect()
+*!
+*!
+*!  $Id: eeprom.c,v 1.3 2001/03/19 16:04:46 markusl Exp $
+*!
+*!------------------------------------------------------------------------
+*!  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.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 FILES SECTION ******************************/
+
+#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 "i2c.h"
+
+/********************** CONSTANT AND MACRO SECTION *************************/
+#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
+
+#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)
+
+/********************** TYPE DEFINITION SECTION ****************************/
+
+/*
+ *  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 int 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;
+  int busy;
+  int retry_cnt_addr; /* Used to keep track of number of retries for
+                     adaptive timing adjustments */
+  int retry_cnt_read;
+
+  
+  
+};
+
+/********************** LOCAL FUNCTION DECLARATION SECTION *****************/
+
+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(unsigned long int addr,
+                            const char * buf, int count);
+static int eeprom_read_buf(unsigned long addr,
+                           char * buf, int count);
+
+static void eeprom_disable_write_protect(void);
+
+
+/********************** GLOBAL VARIABLE DECLARATION SECTION ****************/
+
+/********************** LOCAL VARIABLE DECLARATION SECTION *****************/
+
+/* 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
+};
+
+/********************** FUNCTION DEFINITION SECTION ************************/
+
+/*#**************************************************************************
+*#                                                            
+*# FUNCTION NAME: eeprom_init
+*#                                                            
+*# PARAMETERS   : none
+*#                                                            
+*# RETURNS      : 0 if OK
+*#                                                            
+*# SIDE EFFECTS : 
+*#                                                            
+*# DESCRIPTION  : eeprom init call. Probes for different eeprom models.
+*#                                                            
+*#---------------------------------------------------------------------------
+*# HISTORY                                                    
+*#                                                            
+*# DATE         NAME            CHANGES                       
+*# ----         ----            -------                       
+*# Aug 28 1999  Edgar Iglesias  Initial version               
+*# Sep 03 1999  Edgar Iglesias  Updated probing. Added forced values.
+*#
+*#**************************************************************************/
+
+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, "mem", &eeprom_fops))
+  {
+    printk("unable to get major %d for eeprom device\n", 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 = 4000;/*MAX_WRITEDELAY_US / EEPROM_RETRIES;*/
+  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 diferent 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("Failed to read in 2k mode!\n");  
+    }
+    
+    /* 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("16k detected in step 1\n");
+            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("read and write differs! Not 16kB\n");
+                  loc1[0] = ~loc1[0];
+                  
+                  if (eeprom_write_buf(LOC1, loc1, 1) == 1)
+                  {
+                    success = 1;
+                  }
+                  else
+                  {
+                    printk("eeprom: Restore 2k failed during probe EEPROM might be corrupt!\n");
+                    
+                  }
+                  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("Failed to write back 2k start!\n");
+                  }
+                  
+                  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("2k detected in step 2\n");
+                    loc1[0] = ~loc1[0];
+                    if (eeprom_write_buf(LOC1, loc1, 1) == 1)
+                    {
+                      success = 1;
+                    }
+                    else
+                    {
+                      printk("eeprom: Restore 2k failed during probe EEPROM might be corrupt!\n");
+                      
+                    }
+                    
+                    eeprom.size = EEPROM_2KB;     
+                  }
+                  else
+                  {
+                    printk("16k detected in step 2\n");
+                    loc1[0] = ~loc1[0];
+                    /* Data differs, assume 16k */
+                    /* Restore data */
+                    if (eeprom_write_buf(LOC1, loc1, 1) == 1)
+                    {
+                      success = 1;
+                    }
+                    else
+                    {
+                      printk("eeprom: Restore 16k failed during probe EEPROM might be corrupt!\n");
+                    }
+                    
+                    eeprom.size = EEPROM_16KB;
+                  }
+                }
+              }
+            }
+          } /* read LOC1 */
+        } /* address LOC1 */
+        if (!success)
+        {
+          printk("eeprom: Probing failed!, using 2KB!\n");
+          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("e100eeprom: " EETEXT " i2c compatible 2Kb eeprom.\n");
+     eeprom.sequential_write_pagesize = 16;
+     eeprom.select_cmd = 0xA0;
+     break;
+   case (EEPROM_8KB):
+     printk("e100eeprom: " EETEXT " i2c compatible 8Kb eeprom.\n");
+     eeprom.sequential_write_pagesize = 16;
+     eeprom.select_cmd = 0x80;
+     break;
+   case (EEPROM_16KB):
+     printk("e100eeprom: " EETEXT " i2c compatible 16Kb eeprom.\n");
+     eeprom.sequential_write_pagesize = 64;
+     eeprom.select_cmd = 0xA0;     
+     break;
+   default:
+     eeprom.size = 0;
+     printk("e100eeprom: Did not find a supported eeprom\n");
+     break;
+  }
+
+  
+
+  eeprom_disable_write_protect();
+
+  return 0;
+}
+
+/*#**************************************************************************
+*#                                                            
+*# FUNCTION NAME: eeprom_open
+*#                                                            
+*# PARAMETERS   : inode    : Pointer to the inode
+*#                file     : Pointer to the file
+*#                                                            
+*# RETURNS      : 0 if OK
+*#                                                            
+*# SIDE EFFECTS : 
+*#                                                            
+*# DESCRIPTION  : Opens the device.
+*#                                                            
+*#---------------------------------------------------------------------------
+*# HISTORY                                                    
+*#                                                            
+*# DATE         NAME            CHANGES                       
+*# ----         ----            -------                       
+*# Aug 28 1999  Edgar Iglesias  Initial version               
+*# Sep 03 1999  Edgar Iglesias  Removed users check.
+*#
+*#**************************************************************************/
+
+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;
+}
+
+/*#**************************************************************************
+*#                                                            
+*# FUNCTION NAME: eeprom_lseek
+*#                                                            
+*# PARAMETERS   : file     : Pointer to the file
+*#                offset   : The offset (in bytes)
+*#                orig     : look at the note
+*#                                                            
+*# RETURNS      : 0 if OK
+*#                                                            
+*# SIDE EFFECTS : 
+*#                                                            
+*# DESCRIPTION  : Changes the current file position.
+*#                                                            
+*#---------------------------------------------------------------------------
+*# HISTORY                                                    
+*#                                                            
+*# DATE         NAME            CHANGES                       
+*# ----         ----            -------                       
+*# Aug 28 1999  Edgar Iglesias  Initial version
+*# Sep 03 1999  Edgar Iglesias  Return -EOVERFLOW when beyond eeprom size.
+*#
+*#**************************************************************************/
+
+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 );
+}
+
+/*#**************************************************************************
+*#                                                            
+*# FUNCTION NAME: eeprom_read
+*#                                                            
+*# PARAMETERS   : inode    : Pointer to the inode
+*#                file     : Pointer to the file
+*#                buf      : Destination buffer
+*#                count    : max nr bytes to read
+*#                                                            
+*# RETURNS      : number of read bytes.
+*#                                                            
+*# SIDE EFFECTS : updates file->f_pos
+*#                                                            
+*# DESCRIPTION  : Reads data from eeprom.
+*#                                                            
+*#---------------------------------------------------------------------------
+*# HISTORY                                                    
+*#                                                            
+*# DATE         NAME            CHANGES                       
+*# ----         ----            -------                       
+*# Jan 17 2000  Johan Adolfsson Initial version
+*#
+*#**************************************************************************/
+static int eeprom_read_buf(unsigned long addr,
+                           char * buf, int count)
+{
+  struct file f;
+
+  f.f_pos = addr;
+  return eeprom_read(&f, buf, count, &addr);
+}
+
+
+
+/*#**************************************************************************
+*#                                                            
+*# FUNCTION NAME: eeprom_read
+*#                                                            
+*# PARAMETERS   : file     : Pointer to the file
+*#                buf      : Destination buffer
+*#                count    : max nr bytes to read
+*#                                                            
+*# RETURNS      : number of read bytes.
+*#                                                            
+*# SIDE EFFECTS : updates file->f_pos
+*#                                                            
+*# DESCRIPTION  : Reads data from eeprom.
+*#                                                            
+*#---------------------------------------------------------------------------
+*# HISTORY                                                    
+*#                                                            
+*# DATE         NAME            CHANGES                       
+*# ----         ----            -------                       
+*# Aug 28 1999  Edgar Iglesias  Initial version               
+*# Aug 31 1999  Edgar Iglesias  Made operation atomic with wait queues
+*# Sep 03 1999  Edgar Iglesias  Added bail-out stuff if we get interrupted
+*#                              in the spin-lock.
+*#
+*#**************************************************************************/
+
+static ssize_t eeprom_read(struct file * file, char * buf, size_t count, loff_t *off)
+{
+  int i, read=0;
+  unsigned long p = file->f_pos;
+
+  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("eeprom: Read failed to address the eeprom: "
+           "0x%08X (%i) page: %i\n", p, p, page);
+    i2c_stop();
+    
+    /* don't forget to wake them up */
+    eeprom.busy--;
+    wake_up_interruptible(&eeprom.wait_q);  
+    return -EFAULT;
+  }
+
+  if( (p + count) > eeprom.size)
+  {
+    /* 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)
+  {
+    file->f_pos += read;
+  }
+
+  eeprom.busy--;
+  wake_up_interruptible(&eeprom.wait_q);
+  return read;
+}
+
+/*#**************************************************************************
+*#                                                            
+*# FUNCTION NAME: eeprom_write_buf
+*#                                                            
+*# PARAMETERS   : addr     : Address to write to
+*#                buf      : Data buffer to write from
+*#                count    : number bytes to write
+*#                                                            
+*# RETURNS      : number of bytes actualy written.
+*#                                                            
+*# SIDE EFFECTS : None
+*#             
+*# DESCRIPTION  : Writes data to eeprom.
+*#                                                            
+*#---------------------------------------------------------------------------
+*# HISTORY                                                    
+*#                                                            
+*# DATE         NAME            CHANGES                       
+*# ----         ----            -------                       
+*# Jan 17 2000  Johan Adolfsson Initial vesion
+*#
+*#**************************************************************************/
+static int eeprom_write_buf(unsigned long int addr,
+                            const char * buf, int count)
+{
+  struct file f;
+
+  f.f_pos = addr;
+  
+  return eeprom_write(&f, buf, count, &addr);
+}
+
+
+/*#**************************************************************************
+*#                                                            
+*# FUNCTION NAME: eeprom_write
+*#                                                            
+*# PARAMETERS   : file     : Pointer to the file
+*#                buf      : Data buffer to write from
+*#                count    : number bytes to write
+*#                                                            
+*# RETURNS      : number of bytes actualy written.
+*#                                                            
+*# SIDE EFFECTS : updates file->f_pos
+*#             
+*# DESCRIPTION  : Writes data to eeprom.
+*#                                                            
+*#---------------------------------------------------------------------------
+*# HISTORY                                                    
+*#                                                            
+*# DATE         NAME            CHANGES                       
+*# ----         ----            -------                       
+*# Aug 28 1999  Edgar Iglesias  Initial version               
+*# Aug 31 1999  Edgar Iglesias  Made operation atomic with wait queues
+*# Sep 03 1999  Edgar Iglesias  Moved the actual reading to read_from_eeprom
+*# Sep 03 1999  Edgar Iglesias  Added bail-out stuff if we get interrupted
+*#                              in the spin-lock.
+*# May 18 2000  Edgar Iglesias  Make sure to end write cycle after every write.
+*#
+*#**************************************************************************/
+
+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;
+
+  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 = file->f_pos;
+   
+    
+    while( (written < count) && (p < eeprom.size))
+    {
+      /* address the eeprom */
+      if(!eeprom_address(p))
+      {
+        printk("eeprom: Write failed to address the eeprom: 0x%08X (%i) \n", 
+               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)
+          {
+            if (eeprom.usec_delay_step > 0)
+            {
+              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("eeprom: write error, retrying. %d\n", 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 && file->f_pos >= eeprom.size){
+    return -ENOSPC;
+  }
+  file->f_pos += written;
+  return written;
+}
+
+/*#**************************************************************************
+*#                                                            
+*# FUNCTION NAME: eeprom_close
+*#                                                            
+*# PARAMETERS   : inode    : Pointer to the inode
+*#                file     : Pointer to the file
+*#
+*# RETURNS      : nothing
+*#
+*# SIDE EFFECTS :
+*#
+*# DESCRIPTION  : Closes the device.
+*#
+*#---------------------------------------------------------------------------
+*# HISTORY                                                    
+*#
+*# DATE         NAME            CHANGES                       
+*# ----         ----            -------                       
+*# Aug 28 1999  Edgar Iglesias  Initial version               
+*# Sep 03 1999  Edgar Iglesias  Removed eeprom.users stuff.
+*#
+*#**************************************************************************/
+
+static int eeprom_close(struct inode * inode, struct file * file)
+{
+  /* do nothing for now */
+  return 0;
+}
+
+/*#**************************************************************************
+*#                                                            
+*# FUNCTION NAME: eeprom_address
+*#                                                            
+*# PARAMETERS   : addr     : Address to be given to eeprom
+*#
+*# RETURNS      : 1 if OK, 0 if failure.
+*#
+*# SIDE EFFECTS :
+*#
+*# DESCRIPTION  : Sets the current address of the eeprom.
+*#
+*#---------------------------------------------------------------------------
+*# HISTORY                                                    
+*#
+*# DATE         NAME            CHANGES                       
+*# ----         ----            -------                       
+*# Aug 28 1999  Edgar Iglesias  Initial version               
+*# Sep 03 1999  Edgar Iglesias  Corrected typo.
+*#
+*#**************************************************************************/
+
+static int eeprom_address(unsigned long addr)
+{
+  int i, j;
+  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;
+}
+
+/*#**************************************************************************
+*#                                                            
+*# FUNCTION NAME: read_from_eeprom
+*#                                                            
+*# PARAMETERS   : buf      : Destination buffer.
+*#                count    : Number of bytes to read.
+*#
+*# RETURNS      : number of read bytes or -EFAULT.
+*#
+*# SIDE EFFECTS :
+*#
+*# DESCRIPTION  : Reads from current adress.
+*#
+*#---------------------------------------------------------------------------
+*# HISTORY                                                    
+*#
+*# DATE         NAME            CHANGES                       
+*# ----         ----            -------                       
+*# Sep 03 1999  Edgar Iglesias  Initial version               
+*#
+*#**************************************************************************/
+
+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("eeprom: failed to read from eeprom\n");
+    i2c_stop();
+    
+    return -EFAULT;
+  }
+
+  while( (read < count))
+  {    
+    buf[read++] = i2c_inbyte();      
+
+    /*
+     *  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;
+}
+
+/*#**************************************************************************
+*#                                                            
+*# FUNCTION NAME: eeprom_disable_write_protect
+*#                                                            
+*# PARAMETERS   : None
+*#
+*# RETURNS      : Nothing
+*#
+*# SIDE EFFECTS :
+*#
+*# DESCRIPTION  : Disables write protection if applicable
+*#
+*#---------------------------------------------------------------------------
+*# HISTORY                                                    
+*#
+*# DATE         NAME            CHANGES                       
+*# ----         ----            -------                       
+*# Jan 14 2000  Johan Adolfsson Initial version (from PS pareerom.c)
+*#
+*#**************************************************************************/
+#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);
+
+/********************** END OF FILE e100eeprom.c *****************************/

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)