/*!*****************************************************************************
*!
*!  Implements an interface for i2c compatible eeproms to run under Linux.
*!  Supports 2k, 8k(?) and 16k. Uses adaptive timing adjustments 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.
*!
*!        (c) 1999 Axis Communications AB, Lund, Sweden
*!*****************************************************************************/

#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/smp_lock.h>
#include <linux/wait.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. */
const 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;

#ifdef 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;
  
#ifdef 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)
{
  cycle_kernel_lock();
  if(iminor(inode) != EEPROM_MINOR_NR)
     return -ENXIO;
  if(imajor(inode) != 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 = file->f_pos;

  unsigned char page;

  if(p >= eeprom.size)  /* Address i 0 - (size-1) */
  {
    return -EFAULT;
  }
  
  wait_event_interruptible(eeprom.wait_q, !eeprom.busy);
  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%08X (%i) page: %i\n", eeprom_name, (int)p, (int)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;
}

/* 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 (!access_ok(VERIFY_READ, buf, count))
  {
    return -EFAULT;
  }

  wait_event_interruptible(eeprom.wait_q, !eeprom.busy);
  /* 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(KERN_INFO "%s: Write failed to address the eeprom: "
               "0x%08X (%i) \n", eeprom_name, (int)p, (int)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 && file->f_pos >= eeprom.size){
    return -ENOSPC;
  }
  file->f_pos += written;
  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);
