/*****************************************************************************/
/* I2C Bus interface initialisation and I2C Commands                         */
/* for PPC405GP		                                                     */
/* Author : AS HARNOIS                                                       */
/* Date   : 13.Dec.00                                                        */
/*****************************************************************************/

#include <ppcboot.h>
#include <ppc4xx.h>
#include <405gp_i2c.h>
#include <i2c.h>


#define IIC_OK		0
#define IIC_NOK		1
#define IIC_NOK_LA	2 /* Lost arbitration */
#define IIC_NOK_ICT	3 /* Incomplete transfer */
#define IIC_NOK_XFRA	4 /* Transfer aborted */
#define IIC_NOK_DATA	5 /* No data in buffer */
#define IIC_NOK_TOUT	6 /* Transfer timeout */

#define IIC_TIMEOUT 1 /* 1 seconde */

/* Ensure I/O operations complete */
/* __asm__ volatile("eieio"); */

void i2c_init(void)
{
  PPC405_SYS_INFO sysInfo;
  unsigned long freqOPB;
  int divisor;

  /* clear lo master address */
  out8(IIC_LMADR,0);
  __asm__ volatile("eieio");
  /* clear hi master address */
  out8(IIC_HMADR,0);
  __asm__ volatile("eieio");
  /* clear lo slave address */
  out8(IIC_LSADR,0);
  __asm__ volatile("eieio");
  /* clear hi slave address */
  out8(IIC_HSADR,0);
  __asm__ volatile("eieio");

  /* Reset status register */
  /* write 1 in SCMP IRQA to clear these fields */
  /* !!! ASH QpenBios says 0x08 */
  /*  out8(IIC_STS, 0x0C);  */
  out8(IIC_STS, 0x08);
  __asm__ volatile("eieio");
  /* write 1 in IRQP IRQD LA ICT XFRA to clear these fields */
  out8(IIC_EXTSTS, 0x8F);
  __asm__ volatile("eieio");


  /* Clock divide Register */
  /* get OPB frequency */
  get_sys_info (&sysInfo);
  freqOPB =  sysInfo.freqPLB / sysInfo.pllOpbDiv;
  /* set divisor according to freqOPB */
  divisor = freqOPB / 10000000;
  if (divisor == 0)
  	divisor = 1;
  out8(IIC_CLKDIV,divisor);
  __asm__ volatile("eieio");

  /* no interrupts */
  out8(IIC_INTRMSK,0);
  __asm__ volatile("eieio");
  /* clear transfer count */
  out8(IIC_XFRCNT,0);
  __asm__ volatile("eieio");

  /* clear extended control & stat */
  /* write 1 in SRC SRS SWC SWS to clear these fields */
  out8(IIC_XTCNTLSS,0xF0);
  __asm__ volatile("eieio");

  /* Mode Control Register
     Flush Slave/Master data buffer */
  out8(IIC_MDCNTL, IIC_MDCNTL_FSDB | IIC_MDCNTL_FMDB);
  __asm__ volatile("eieio");

  /* Ignore General Call, 100kHz, slave transfers are ignored,
     disable interrupts, exit unknown bus state, enable hold
     SCL
  */
  out8(IIC_MDCNTL, ((in8(IIC_MDCNTL))|IIC_MDCNTL_EUBS|IIC_MDCNTL_HSCL));
  __asm__ volatile("eieio");

  /* clear control reg */
  out8(IIC_CNTL,0x00);
  __asm__ volatile("eieio");

}


int i2c_transfer(unsigned char command_is_reading, unsigned char address,
		 unsigned short size_to_transfer, unsigned char data[] )
{
  int bytes_transfered;
  int result;
  int status;
  int i, TimeReady;
  ulong freqOPB,freqProc;
  ulong TimeOut; /* _must_ be unsigned long for fast speeds */

  result = IIC_OK;
  bytes_transfered = 0;

  i2c_init();

  /* Check init */
  i=10;
  do{
    /* Get status */
    status = in8(IIC_STS);
    __asm__ volatile("eieio");
    i--;
  }while ((status & IIC_STS_PT) && (i>0));
  if (status & IIC_STS_PT)
    {
      result = IIC_NOK_TOUT;
      return(result);
    }

  /* 7-bit adressing */
  out8(IIC_HMADR,0);
  __asm__ volatile("eieio");
  out8(IIC_LMADR, address);
  __asm__ volatile("eieio");

  /* In worst case, you need to wait 4 OPB clocks */
  /* for data being ready on IIC bus */
  /* (because FIFO is 4 bytes depth) */
  freqOPB = get_OPB_freq();
  freqProc = get_gclk_freq();
  TimeReady = (4/(freqProc/freqOPB)) + 1;

  /* IIC time out => wait for TimeOut cycles */
  /* TimeOut /= 2; because waiting-loop is 2-instruction loop */
  /* Take care we get no overflow (266MHz = 0x0FDAD680) first divide */
  TimeOut = freqProc/2;
  TimeOut *= IIC_TIMEOUT*10;

  while ((bytes_transfered < size_to_transfer) && (result == IIC_OK))
    {
      /* Control register =
	 Normal transfer, 7-bits adressing, Transfer 1 byte, Normal start,
	 Transfer is a sequence of transfers
	 Write/Read, Start transfer */

      /* ACTION => Start - Transfer - Ack - pause */
      /* ACTION - LAST BYTE => Start - Transfer - Nack  - Stop */
      if (command_is_reading)
	{
	  /* issue read command */
	  if (bytes_transfered == size_to_transfer-1)
	    {
	    out8(IIC_CNTL, IIC_CNTL_READ | IIC_CNTL_PT);
	    __asm__ volatile("eieio");
	    }
	  else
	    {
	      out8(IIC_CNTL, IIC_CNTL_READ | IIC_CNTL_CHT | IIC_CNTL_PT);
	      __asm__ volatile("eieio");
	    }
	}
      else
	{
	  /* Set buffer */
	  out8(IIC_MDBUF,data[bytes_transfered]);
	  __asm__ volatile("eieio");
	  i=TimeReady;
	  do{
	    i--;
	  }while(i>0);
	  /* issue write command */
	  if (bytes_transfered == size_to_transfer-1)
	    {
	    out8(IIC_CNTL, IIC_CNTL_PT);
	    __asm__ volatile("eieio");
	    }
	  else
	    {
	    out8(IIC_CNTL, IIC_CNTL_CHT | IIC_CNTL_PT);
	    __asm__ volatile("eieio");
	    }
	}



      /* Transfer is in progress */
      i=TimeOut;
      do{
	/* Get status */
	status = in8(IIC_STS);
	__asm__ volatile("eieio");
	i--;
      }while ((status & IIC_STS_PT) && (i>0));

      if (status & IIC_STS_PT)
	{
	  result = IIC_NOK_TOUT;
	}
      else if (status & IIC_STS_ERR)
	{
	  result = IIC_NOK;
	  status = in8(IIC_EXTSTS);
	__asm__ volatile("eieio");
	  /* Lost arbitration? */
	  if (status & IIC_EXTSTS_LA)
	    result = IIC_NOK_LA;
	  /* Incomplete transfer? */
	  if (status & IIC_EXTSTS_ICT)
	    result = IIC_NOK_ICT;
	  /* Transfer aborted? */
	  if (status & IIC_EXTSTS_XFRA)
	    result = IIC_NOK_XFRA;
	}
      /* Command is reading => get buffer */
      if ((command_is_reading) && (result == IIC_OK))
	{
	  /* Are there data in buffer */
	  if (status & IIC_STS_MDBS)
	    {
	      i=TimeReady;
	      do{
		i--;
	      }while(i>0);
	      data[bytes_transfered] = in8(IIC_MDBUF);
	      __asm__ volatile("eieio");
	    }
	  else result = IIC_NOK_DATA;
	}
      bytes_transfered++;
    }
  return(result);
}


int i2c_receive(unsigned char address,
		 unsigned short size_to_expect, unsigned char datain[] )
{
  int status;
  status = i2c_transfer(1, address, size_to_expect, datain);
#ifdef CONFIG_WALNUT405
  if (status != 0) printf("\nI2C error => status = %d\n", status);
#endif
  return status;
}


int i2c_send(unsigned char address,
		 unsigned short size_to_send, unsigned char dataout[] )
{
  int status;
  status = i2c_transfer(0, address, size_to_send, dataout);
#ifdef CONFIG_WALNUT405
  if (status != 0) printf("\nI2C error => status = %d\n", status);
#endif
  return status;
}


int i2c_read (uchar *addr, int alen, uchar *buffer, int len)
{
	int rcode = 0;

	if ((alen < 1) || (alen > 3)) {
		printf ("I2C read: addr len %d not supported\n", alen);
		return 1;
	}

	if (i2c_send (addr[0] << 1, alen-1, addr+1) != 0) rcode = 1;
	if (i2c_receive ((addr[0] << 1) | 0x01, len, buffer) != 0) rcode = 1;
	return rcode;
}

#ifndef CFG_EEPROM_PAGE_WRITE_ENABLE

int i2c_write (uchar *addr, int alen, uchar *buffer, int len)
{
	uchar xbuf[3];
	unsigned short paddr;

	if (alen == 2) {
		/* write with ack polling */
		while (len-- > 0) {
			xbuf[0] = addr[1]++; /* increase write offset */
			xbuf[1] = *buffer++;
			/* single write + ack polling */
			while (i2c_send (addr[0] << 1, 2, xbuf) != 0) {
				udelay(100);
			}
		}
		return 0;
	}

	paddr=(addr[1]<<8)+addr[2];

	if (alen == 3) {
		/* write with ack polling */
		while (len-- > 0) {
			xbuf[0] = (uchar)(paddr>>8);	/* High addr */
			xbuf[1] = (uchar)(paddr&0xff);	/* Low  addr */
			paddr++;
			xbuf[2] = *buffer++;
			/* single write + ack polling */
			while (i2c_send (addr[0] << 1, 3, xbuf) != 0) {
				udelay(100);
			}
		}
		return 0;
	}

	printf ("I2C write: addr len %d not supported\n", alen);
	return 1;
}

#else /* CFG_EEPROM_PAGE_WRITE_ENABLE */

int i2c_write (uchar *addr, int alen, uchar *buffer, int len)
{
	/* buffer for one page + addresses */
	uchar wbuffer[(1<<CFG_EEPROM_PAGE_WRITE_BITS)+2];

	int i;

	if (alen == 2) {
		/* fill in address first */
 		wbuffer[0] = addr[1];
		for (i=1; i < len+1; i++) {
			wbuffer[i] = *buffer++; /* copy data */
		}
		/* write page + ack polling */
		while (i2c_send (addr[0] << 1, len+1, wbuffer) != 0) {
			udelay(100);
		}
		return 0;
	}
	if (alen == 3) {
		/* fill in the address first */
		wbuffer[0] = addr[1];
		wbuffer[1] = addr[2];
		for (i=2; i < len+2; i++) {
			wbuffer[i] = *buffer++; /* copy data */
		}
		/* write page + ack polling */
		while (i2c_send (addr[0] << 1, len+2, wbuffer) != 0) {
			udelay(100);
		}
		return 0;
	}
	printf ("I2C write: addr len %d not supported\n", alen);
	return 1;
}
#endif /* CFG_EEPROM_PAGE_WRITE_ENABLE */


int i2c_write_page (uchar *addr, int alen, uchar *data, int dlen, int dsize)
{
	uchar	xbuf[7];
	int	idx, i;

	/* Validate parameters. */
	if (alen > 3) {
	    printf("I2C write: addr len %d not supported\n", alen);
	    return 1;
	}
	if (dsize > 4) {
	    printf ("I2C write: data size %d not supported\n", dsize);
	    return 1;
	}

	/* write with ack polling */
	while (dlen > 0) {
	    /* build up an output buffer */
	    idx = 0;

	    /* move the extra address bytes to buffer */
	    for (i = 1; i < alen; i++)
		xbuf[idx++] = addr[i];

	    /* move the data to output buffer */
	    for (i = 0; ((i < dsize) && (dlen-- > 0)); i++)
		xbuf[idx++] = *data++;

	    /* single write + ack polling */
	    while ((i2c_send(addr[0] << 1, idx, xbuf)) != 0) {
		udelay(100);
	    }
	}

	return 0;
}

