/*
 * Copyright 2010 Allied Telesis Labs
 *
 * See file CREDITS for list of people who contributed to this
 * project.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 * MA 02111-1307 USA
 */
#include <common.h>
#include <malloc.h>


#define MAX_I2C_RETRYS		10
#define I2C_DELAY		1  /* Should be at least the # of MHz of Tclk */
#undef	DEBUG_I2C

//#define DEBUG_I2C 1

#ifdef DEBUG_I2C
#define DP(x) x
#else
#define DP(x)
#endif

#define BIT2			0x00000004
#define BIT3			0x00000008
#define BIT4			0x00000010
#define BIT5			0x00000020
#define BIT6			0x00000040

#define REG_BASE		0xB8000000

#define RESET_REG_BITS(regOffset,bits) *(u8 *)(REG_BASE | regOffset) &= ~((u8)bits)


#define I2C_SLAVE_ADDRESS	0x200
#define I2C_DATA		0x201
#define I2C_CONTROL		0x202
#define I2C_STATUS_BAUD_RATE	0x203
#define I2C_SOFT_RESET		0x207

#define REG_WRITE(r, v) *(volatile u8 *)((u32)REG_BASE + r) = v
#define REG_READ(r, v) *(u8 *)v = *(volatile u8 *)((u32)REG_BASE + r)


static void i2c_stop(void);
static uchar i2c_start(void);

/* Assuming that there is only one master on the bus (us) */
//static int init = 0;

void i2c_init(int speed, int slaveaddr)
{
	unsigned int n, m, freq, margin, power;
	unsigned int actualFreq=0, actualN=0, actualM=0;
	unsigned int minMargin = 0xffffffff;
	unsigned int tclk = 150000000;
	u8 reset, status;
#ifdef DEBUG_I2C
	u8 control;
#endif

	DP(puts("i2c_init\n"));

	for(n = 0 ; n < 8 ; n++)
	{
		for(m = 0 ; m < 16 ; m++)
		{
			power = 2<<n; /* power = 2^(n+1) */
			freq = tclk/(10*(m+1)*power);
			if (speed > freq)
				margin = speed - freq;
			else
				margin = freq - speed;
			if(margin < minMargin)
			{
				minMargin   = margin;
				actualFreq  = freq;
				actualN	    = n;
				actualM	    = m;
			}
		}
	}

	DP(puts("setup i2c bus\n"));

	/* Setup bus */

	REG_READ(I2C_SOFT_RESET, &reset);
	reset |= 1;
	REG_WRITE(I2C_SOFT_RESET, reset);
	udelay(10);
	reset &= ~1;
	REG_WRITE(I2C_SOFT_RESET, reset);

	DP(puts("udelay...\n"));

	udelay(I2C_DELAY);

	DP(puts("set baudrate\n"));
	REG_WRITE(I2C_STATUS_BAUD_RATE, (actualM << 3) | actualN);
	REG_WRITE(I2C_SLAVE_ADDRESS, 0x20);
	while(1) {
		REG_READ(I2C_STATUS_BAUD_RATE, &status);
		if (status == 0xf8)
			break;
	}

	REG_WRITE(I2C_CONTROL, BIT2| BIT6);

#ifdef DEBUG_I2C
	DP(puts("read control, baudrate\n"));
	REG_READ(I2C_STATUS_BAUD_RATE, &status);
	REG_READ(I2C_CONTROL, &control);
	printf("i2c_init status %02x control %02x actualM %d actualN %d\n", status, control, actualM, actualN);
#endif
}

void i2c_init_rw(int speed, int slaveaddr)
{
	u8 control, status;

	DP(puts("setup i2c bus rw\n"));

	REG_READ(I2C_STATUS_BAUD_RATE, &status);
	REG_READ(I2C_CONTROL, &control);
	REG_WRITE(I2C_CONTROL, BIT2| BIT6);
#ifdef DEBUG_I2C
	printf("i2c_init status %02x control %02x\n", status, control);
#endif
}

static int
i2c_wait_poll_iflg(void)
{
	u8 control;
	u32 retries = 2000;

	REG_READ(I2C_CONTROL, &control);

	while ((control & BIT3) == 0 && retries--) {
		REG_READ(I2C_CONTROL, &control);
	}

	return (retries == 0 ? -1 : 0);
}

static uchar i2c_start(void)
{
	u8 control, status;
	int ret;

	DP(puts("i2c_start\n"));

	/* Set the start bit */

	REG_READ(I2C_CONTROL, &control);
	control |= (0x1 << 5);
	control &= ~(0x1 << 3);
	REG_WRITE(I2C_CONTROL, control);

	ret = i2c_wait_poll_iflg();
	REG_READ(I2C_STATUS_BAUD_RATE, &status);
	if (ret != 0 || ((status & 0xff) != 0x08)) {
		REG_WRITE(I2C_CONTROL, (0x1 << 4)); /*stop*/
		DP(puts("i2c_start - error stop\n"));
		return (status);
	}

	return (0);
}

static uchar i2c_select_device(uchar dev_addr, uchar read, int alen)
{
	u8 status, data, bits = 7;
	int ret;

	DP(puts("i2c_select_device\n"));

	/* Output slave address */

	if (alen > 1) {
		bits = 10;
	}

	data = (dev_addr << 1);
	/* set the read bit */
	data |= read;
	REG_WRITE(I2C_DATA, data);

	RESET_REG_BITS(I2C_CONTROL, BIT3);
	/* assert the address */
	ret = i2c_wait_poll_iflg();
	REG_READ(I2C_STATUS_BAUD_RATE, &status);
	if (ret != 0 || (((status & 0xff) != 0x40) && ((status & 0xff) != 0x18))) {
		REG_WRITE(I2C_CONTROL, (0x1 << 4)); /*stop*/
		printf("failed to select device %02x error %02x\n", dev_addr, status & 0xff);
		return(status);
	}

	if (bits == 10) {
		printf("10 bit I2C addressing not yet implemented\n");
		return (0xff);
	}

	return (0);
}

static uchar i2c_get_data(uchar* return_data, int len) {

	u8 data, status = 0;
//	int count = 0;
	int ret;

	DP(puts("i2c_get_data\n"));

	while (len) {

		/* Get and return the data */

		RESET_REG_BITS(I2C_CONTROL, BIT3);

		ret = i2c_wait_poll_iflg();
		REG_READ(I2C_STATUS_BAUD_RATE, &status);
		if (ret != 0 || (status & 0xff) != 0x50) {
			REG_WRITE(I2C_CONTROL, BIT4); /*stop*/
			printf("failed to get_data ret %d status %02x\n", ret, status & 0xff);
			return 0;
		}
		REG_READ(I2C_DATA, &data);
		len--;
		*return_data = (uchar)data;
		return_data++;
	}
	RESET_REG_BITS(I2C_CONTROL, BIT2|BIT3);
	ret = i2c_wait_poll_iflg();
	REG_READ(I2C_STATUS_BAUD_RATE, &status);
	if (ret != 0 || (status & 0xff) != 0x58) {
		REG_WRITE(I2C_CONTROL, BIT4); /*stop*/
		printf("failed to get_data last data ret %d status %02x\n", ret, status & 0xff);
		return (status);
	}
	REG_WRITE(I2C_CONTROL, BIT4); /* stop */

	return (0);
}

static uchar i2c_write_data(unsigned int data, int len)
{
	u8 status, control;
	int ret;

	DP(puts("i2c_write_data\n"));

	if (len > 4)
		return -1;

	while (len) {
		/* Set and assert the data */

		REG_READ(I2C_CONTROL, &control);
		control &= ~(BIT3);
		REG_WRITE(I2C_DATA, (unsigned int)data);
		udelay(I2C_DELAY);
		REG_WRITE(I2C_CONTROL, control);
		REG_READ(I2C_STATUS_BAUD_RATE, &status);

		ret = i2c_wait_poll_iflg();
		REG_READ(I2C_STATUS_BAUD_RATE, &status);

		REG_READ(I2C_STATUS_BAUD_RATE, &status);
		if (ret != 0 || ((status & 0xff) != 0x28)) {
			REG_WRITE(I2C_CONTROL, (0x1 << 4)); /*stop*/
			printf("i2c_write_data error len %d data %08x status %02x\n", len, data, status);
			return (status);
		}
		len--;
	}

	return (0);
}

static void i2c_stop(void)
{
	REG_WRITE(I2C_CONTROL, BIT3 | BIT4);
	REG_WRITE(I2C_CONTROL, BIT4);

	udelay(I2C_DELAY * 10);
}


static uchar i2c_set_dev_offset(uchar dev_addr, unsigned int offset, int alen)
{
	u8 status;

	DP(puts("i2c_set_dev_offset\n"));

	status = i2c_select_device(dev_addr, 0, alen);
	if (status) {
		printf("Failed to select device setting offset: 0x%02x\n",
		       status);
		return status;
	}

	status = i2c_write_data(offset, 1);
	if (status) {
		printf("Failed to write data: 0x%02x\n", status);
		return status;
	}

	return (0);
}

int i2c_read(u8 dev_addr, uint offset, int alen, uchar* data, int len)
{
	u8 status = 0;

	DP(puts("i2c_read\n"));

	i2c_init_rw(CONFIG_SYS_I2C_SPEED, 0);

	status = i2c_start();

	if (status) {
		printf("Transaction start failed: 0x%02x\n", status);
		return status;
	}

	status = i2c_set_dev_offset(dev_addr, offset, 0);
	if (status) {
		printf("Failed to set offset: 0x%02x\n", status);
		return status;
	}
	
	i2c_stop();

	i2c_init_rw(CONFIG_SYS_I2C_SPEED, 0);

	status = i2c_start();
	if (status) {
		printf("Transaction restart failed: 0x%02x\n", status);
		return status;
	}

	status = i2c_select_device(dev_addr, 1, alen);
	if (status) {
		printf("Address not acknowledged: 0x%02x\n", status);
		return status;
	}

	status = i2c_get_data(data, len);
	if (status) {
		printf("Data not recieved: 0x%02x\n", status);
		return status;
	}
#ifdef DEBUG_I2C
	printf("I2C read addr %02x offset %02x status %d data %02x len %d\n", dev_addr, offset, status, data[0], len);
#endif

	return 0;
}

int i2c_write (u8 dev_addr, uint offset, int alen, u8 *data, int length)
{
	u8 status = 0;
	int i;

	DP(puts("i2c_write\n"));

	i2c_init_rw(CONFIG_SYS_I2C_SPEED, 0);

#ifdef DEBUG_I2C
	printf("Transaction I2C write addr %02x offset %02x\n", dev_addr, offset);
#endif

	status = i2c_start();
	if (status) {
		printf("Transaction start failed: 0x%02x\n", status);
		return status;
	}

	status = i2c_set_dev_offset(dev_addr, offset, 0);
	if (status) {
		printf("Failed to set offset: 0x%02x\n", status);
		return status;
	}

	for (i = 0; i < length; i++) {
		status = i2c_write_data((unsigned int)data[i], 1);
		if (status) {
			printf("Data not written: 0x%02x\n", status);
			break;
		}
	}

	i2c_stop();

	return !(i == length);
}

int i2c_probe (uchar chip)
{
	int tmp;

	/*
	 * Try to read the first location of the chip.  The underlying
	 * driver doesn't appear to support sending just the chip address
	 * and looking for an <ACK> back.
	 */
	DP(puts("i2c_probe\n"));
	udelay(10000);
	return i2c_read (chip, 0, 1, (uchar *)&tmp, 1);
}

uchar i2c_reg_read (uchar i2c_addr, uchar reg)
{
	u8 buf[1];

	DP(puts("i2c_reg_read\n"));
	i2c_read (i2c_addr, reg, 1, buf, 1);

	return (buf[0]);
}

void i2c_reg_write (uchar i2c_addr, uchar reg, uchar val)
{
	DP(puts("i2c_reg_write\n"));
	i2c_write (i2c_addr, reg, 1, &val, 1);
}
