/* 2012-09-16: File added and changed by Sony Corporation */
/*
 * icx_i2c.c
 *
 * Copyright 2011 Sony Corporation.
 *
 * 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 <asm/errno.h>

#ifdef CONFIG_ICX

#include "reg.h"
#include "icx_io.h"
#include "icx_smu.h"
#include "icx_i2c.h"


#define	IIC_USE_INT			0

/***************/
/*@ Definition */
/***************/

/* IICACT */
#define IIC_IICACT_IICE0	0x0001

/* IICC0 */
#define IIC_IICC_SPT0		0x0001
#define IIC_IICC_STT0		0x0002
#define IIC_IICC_SPIE0		0x0010

/* IICSE0 */
#define IIC_IICSE_SPD0		0x0001
#define IIC_IICSE_ACKD0		0x0004
#define IIC_IICSE_MSTS0		0x0080

/* IICF0 */
#define IIC_IICF_IICBSY		0x0040
#define IIC_IICF_STCF		0x0080

#if IIC_USE_INT
/* INTC */
#define INTC_IT0_RAW_IIC2	0x00000080
#endif

/* timeout */
#define IIC_MAX_WAIT_BSY     100
#define IIC_MAX_WAIT_STCF	1000
#define IIC_MAX_WAIT_SPD0   1000
#if IIC_USE_INT
#define IIC_MAX_WAIT_SEND   1000
#else
#define IIC_MAX_WAIT_SEND    100
#endif

/* clock divisor */
#define SMU_IICSCLKDIV_IIC0	(SMU_PLLSEL_PLL3 | SMU_DIV(25))

/* macros */
#define I2C_WAIT_FOR_BB()    i2c_wait_for_state(IIC_IICF0,  IIC_IICF_IICBSY, IIC_MAX_WAIT_BSY,  0)
#define I2C_WAIT_FOR_START() i2c_wait_for_state(IIC_IICF0,  IIC_IICF_STCF,   IIC_MAX_WAIT_STCF, 0)
#define I2C_WAIT_FOR_STOP()  i2c_wait_for_state(IIC_IICSE0, IIC_IICSE_SPD0,  IIC_MAX_WAIT_SPD0, 1)


/***************/
/*@ Prototypes */
/***************/

static void i2c_power_on(void);
static void i2c_power_off(void);
static void i2c_config(i2c_config_t *config);
static int  i2c_send_data(unsigned char data);
static int  i2c_receive_data(unsigned char *data, unsigned char isack);
static int  i2c_wait_for_state(unsigned int reg, unsigned short mask, int limit, int cond);


/********************/
/*@ Global routines */
/********************/

/*-----------------------------------------------------------------------------
 * Function	: i2c_init
 * Abstruct : Initialize I2C.
 * Input    : config : pointer to I2C config structure
 * Return   : 0:success / -EINVAL
 * Note     : use only IIC0
 *----------------------------------------------------------------------------*/
int i2c_init(i2c_config_t *config) 
{
	if (config == NULL)
		return -EINVAL;

	/* pinsel/pull */
	outl(((inl(CHG_PINSEL_G064) & ~0x00000018) | 0x00000000), CHG_PINSEL_G064);
	outl(((inl(CHG_PULL8      ) & ~0x000FF000) | 0x000CC000), CHG_PULL8      );

	udelay(1000);

	/* set the divisor of system clock */
	outl(((inl(SMU_IICSCLKDIV) & ~0x0000FFFF) | SMU_IICSCLKDIV_IIC0), SMU_IICSCLKDIV);

	i2c_power_on();

	/* configuration */
	i2c_config(config);

#if IIC_USE_INT
	/* enable interrupt */
	outl(INTC_IT0_RAW_IIC2, INTC_IT0_IEN1);
#endif

	return 0;
}

/*-----------------------------------------------------------------------------
 * Function	: i2c_exit
 * Abstruct : Remove I2C.
 * Input    : none
 * Return   : none
 * Note     : use only IIC0
 *----------------------------------------------------------------------------*/
int i2c_exit(void)
{
#if IIC_USE_INT
	/* disable interrupt */
	outl(INTC_IT0_IEN1, INTC_IT0_IDS1);
#endif

	/* stop I2C */
	if (inh(IIC_IICACT0) & IIC_IICACT_IICE0) {
		outh(0, IIC_IICACT0);
		outh(0, IIC_IICC0);
		udelay(10000);
	}

	i2c_power_off();

	return 0;
}

/*-----------------------------------------------------------------------------
 * Function	: i2c_write
 * Abstruct : Write a data to I2C.
 * Input    : config : pointer to I2C config structure
 *          : reg : register address
 *          : data : data value
 * Return   : 0(success) / -EIO, -ETIMEDOUT 
 * Note     : use only IIC0
 *----------------------------------------------------------------------------*/
int i2c_write(i2c_config_t *config, unsigned char reg, unsigned char data)
{
	int ret = 0;

	/* waiting on Bus Busy */
	if (I2C_WAIT_FOR_BB()) {
		if (!(inh(IIC_IICSE0) & IIC_IICSE_MSTS0))
			return -ETIMEDOUT;

		outh((inh(IIC_IICC0) | IIC_IICC_SPT0), IIC_IICC0);

		if (I2C_WAIT_FOR_BB()) {
			i2c_config(config);
			if (I2C_WAIT_FOR_BB())
				return -ETIMEDOUT;
		}
	}

	/* start condition */
#if IIC_USE_INT
	outl(INTC_IT0_RAW_IIC2, INTC_IT0_IIR);
#endif
	outh((inh(IIC_IICC0) | IIC_IICC_STT0), IIC_IICC0);
	if (I2C_WAIT_FOR_START())
		return -ETIMEDOUT;

	/* send slave address */
	if ((ret = i2c_send_data(config->slave_adr)) == 0) {
		/* send register address */
		if ((ret = i2c_send_data(reg)) == 0) {
			/* send a data */
			ret = i2c_send_data(data);
		}
	}

	/* stop condition */
	outh((inh(IIC_IICC0) | IIC_IICC_SPT0), IIC_IICC0);
	if (I2C_WAIT_FOR_STOP())
		return -ETIMEDOUT;

#if IIC_USE_INT
	outl(INTC_IT0_RAW_IIC2, INTC_IT0_IIR);
#endif

	return ret;
}

/*-----------------------------------------------------------------------------
 * Function	: i2c_read
 * Abstruct : Read a data from I2C.
 * Input    : config : pointer to I2C config structure
 *          : reg : register address
 *          : data : pointer to the data
 * Return   : 0(success) / -EIO, -ETIMEDOUT 
 * Note     : use only IIC0
 *----------------------------------------------------------------------------*/
int i2c_read(i2c_config_t *config, unsigned char reg, unsigned char *data, int num)
{
	int i;
	int ret = 0;

	/* waiting on Bus Busy */
	if (I2C_WAIT_FOR_BB()) {
		if (!(inh(IIC_IICSE0) & IIC_IICSE_MSTS0))
			return -ETIMEDOUT;

		outh((inh(IIC_IICC0) | IIC_IICC_SPT0), IIC_IICC0);

		if (I2C_WAIT_FOR_BB()) {
			i2c_config(config);
			if (I2C_WAIT_FOR_BB())
				return -ETIMEDOUT;
		}
	}

	/* start condition */
#if IIC_USE_INT
	outl(INTC_IT0_RAW_IIC2, INTC_IT0_IIR);
#endif
	outh((inh(IIC_IICC0) | IIC_IICC_STT0), IIC_IICC0);
	if (I2C_WAIT_FOR_START())
		return -ETIMEDOUT;

	/* send slave address */
	if ((ret = i2c_send_data(config->slave_adr)) == 0) {
		/* send register address */
		if ((ret = i2c_send_data(reg)) == 0) {

			/* start condition */
#if IIC_USE_INT
			outl(INTC_IT0_RAW_IIC2, INTC_IT0_IIR);
#endif
			outh((inh(IIC_IICC0) | IIC_IICC_STT0), IIC_IICC0);
			if (I2C_WAIT_FOR_START()) {
				ret = -ETIMEDOUT;
			}
			else {
				/* send slave address with read flag */
				if ((ret = i2c_send_data(config->slave_adr|0x01)) == 0) {
					/* receive a data */
					for (i = 0; i < num; i++) {
						if (i == (num - 1))
							ret = i2c_receive_data(&data[i], 0);
						else
							ret = i2c_receive_data(&data[i], 1);
					}
				}
			}
		}
	}

	/* stop condition */
	outh((inh(IIC_IICC0) | IIC_IICC_SPT0), IIC_IICC0);
	if (I2C_WAIT_FOR_STOP())
		return -ETIMEDOUT;

#if IIC_USE_INT
	outl(INTC_IT0_RAW_IIC2, INTC_IT0_IIR);
#endif

	return ret;
}


/*******************/
/*@ Local routines */
/*******************/

/*-----------------------------------------------------------------------------
 * Function	: i2c_power_on
 * Abstruct : Power on I2C.
 * Input    : none
 * Return   : none
 * Note     : use only IIC0
 *----------------------------------------------------------------------------*/
static void i2c_power_on(void)
{
	smu_clockgate_control(SMU_CLK_IIC0,   SMU_GCLKCTRL_OPEN);
	smu_clockgate_control(SMU_CLK_IIC0_S, SMU_GCLKCTRL_OPEN);

	smu_reset_control(SMU_RST_IIC0, SMU_RESETREQ_UNRESET);
}

/*-----------------------------------------------------------------------------
 * Function	: i2c_power_off
 * Abstruct : Power off I2C.
 * Input    : none
 * Return   : none
 * Note     : use only IIC0
 *----------------------------------------------------------------------------*/
static void i2c_power_off(void)
{
	smu_clockgate_control(SMU_CLK_IIC0,   SMU_GCLKCTRL_OPEN);
	smu_clockgate_control(SMU_CLK_IIC0_S, SMU_GCLKCTRL_OPEN);

	smu_reset_control(SMU_RST_IIC0, SMU_RESETREQ_RESET);

	smu_clockgate_control(SMU_CLK_IIC0_S, SMU_GCLKCTRL_CLOSE);
	smu_clockgate_control(SMU_CLK_IIC0_S, SMU_GCLKCTRL_CLOSE);
}

/*-----------------------------------------------------------------------------
 * Function	: i2c_config
 * Abstruct : I2C configuration.
 * Input    : config : pointer to I2C config structure
 * Return   : none
 * Note     : use only IIC0
 *----------------------------------------------------------------------------*/
static void i2c_config(i2c_config_t *config)
{
	/* configuration */
	if (inh(IIC_IICACT0) & IIC_IICACT_IICE0) {
		outh(0, IIC_IICACT0);
		outh(0, IIC_IICC0);
		udelay(10000);
	}

	outh(config->clock, IIC_IICCL0);
	outh(config->flag,  IIC_IICF0);
	outh((config->ctrl | IIC_IICC_SPIE0), IIC_IICC0);
	outh(IIC_IICACT_IICE0, IIC_IICACT0);
}

/*-----------------------------------------------------------------------------
 * Function	: i2c_send_data
 * Abstruct : Send a data
 * Input    : data : data value
 * Return   : 0 : success / -EIO, -ETIMEDOUT
 * Note     : use only IIC0
 *----------------------------------------------------------------------------*/
static int i2c_send_data(unsigned char data)
{
	int timeout;

#if IIC_USE_INT
	/* clear the interrupt bit */
	outl(INTC_IT0_RAW_IIC2, INTC_IT0_IIR);
#endif

	/* write a data */
	outh(data, IIC_IIC0);

#if IIC_USE_INT
	/* wait for interrupt status */
	for (timeout = IIC_MAX_WAIT_SEND; timeout > 0; timeout--) {
		if (inl(INTC_IT0_RAW1) & INTC_IT0_RAW_IIC2)
			break;
	}

	if (timeout <= 0)
		return -ETIMEDOUT;
#else
	udelay(IIC_MAX_WAIT_SEND);
#endif

	/* check ACK */
	if (!(inh(IIC_IICSE0) & IIC_IICSE_ACKD0))
		return -EIO;

#if IIC_USE_INT
	/* clear the interrupt bit */
	outl(INTC_IT0_RAW_IIC2, INTC_IT0_IIR);
#endif

	return 0;
}

/*-----------------------------------------------------------------------------
 * Function : i2c_receive_data
 * Abstruct : receive a data
 * Input    : data : pointer to data 
 * Return   : 0(success) / -EIO, -ETIMEDOUT 
 * Note     : use only IIC0
 *----------------------------------------------------------------------------*/
static int i2c_receive_data(unsigned char *data, unsigned char isack)
{
	int            timeout;
	unsigned short regval;

#if IIC_USE_INT
	/* clear the interrupt bit */
	outl(INTC_IT0_RAW_IIC2, INTC_IT0_IIR);
#endif

	/* receive setting */
	regval = inh(IIC_IICC0);
	if (isack) {
		regval &= ~IIC_IICC0_WTIM0;
		regval |= IIC_IICC0_ACKE0;
	}
	else {
		regval |= IIC_IICC0_WTIM0;
		regval &= ~IIC_IICC0_ACKE0;
	}
	outh(regval, IIC_IICC0);
	outh((regval | IIC_IICC0_WREL0), IIC_IICC0);

#if IIC_USE_INT
	/* wait for interrupt status */
	for (timeout = IIC_MAX_WAIT_SEND; timeout > 0; timeout--) {
		if (inl(INTC_IT0_RAW1) & INTC_IT0_RAW_IIC2)
			break;
	}

	if (timeout <= 0)
		return -ETIMEDOUT;
#else
	udelay(IIC_MAX_WAIT_SEND);
#endif

	/* read a data */
	*data = inh(IIC_IIC0);

	outh((inh(IIC_IICC0) & ~IIC_IICC0_ACKE0), IIC_IICC0);

#if IIC_USE_INT
	/* clear the interrupt bit */
	outl(INTC_IT0_RAW_IIC2, INTC_IT0_IIR);
#endif

	return 0;
}

/*-----------------------------------------------------------------------------
 * Function : i2c_receive_data
 * Abstruct : Wait for changing a status
 * Input    : reg : I2C register address
 *          : mask  : state bit
 *          : limit : timeout
 *          : cond  : 0 or 1
 * Return   : 0 : changed a status / 1 : timeout
 * Note     : use only IIC0
 *----------------------------------------------------------------------------*/
static int i2c_wait_for_state(unsigned int reg, unsigned short mask, int limit, int cond)
{
	int timeout = limit;

	/* waiting on Bus Busy */
	cond = cond ? 0 : mask;
	while (((inh(reg) & mask) == cond) && timeout--);

	if (timeout <= 0) 
		return 1;

	return 0;
}

#endif /* CONFIG_ICX */

