/* Copyright 2015 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

/* I2C module for Chrome EC */

#include "clock.h"
#include "common.h"
#include "console.h"
#include "gpio.h"
#include "hooks.h"
#include "i2c.h"
#include "registers.h"
#include "task.h"
#include "timer.h"
#include "util.h"

#define CPRINTS(format, args...) cprints(CC_I2C, format, ## args)

/* Default maximum time we allow for an I2C transfer */
#define I2C_TIMEOUT_DEFAULT_US (100 * MSEC)

enum enhanced_i2c_transfer_direct {
	TX_DIRECT,
	RX_DIRECT,
};

enum i2c_host_status {
	/* Host busy */
	HOSTA_HOBY = 0x01,
	/* Finish Interrupt */
	HOSTA_FINTR = 0x02,
	/* Device error */
	HOSTA_DVER = 0x04,
	/* Bus error */
	HOSTA_BSER = 0x08,
	/* Fail */
	HOSTA_FAIL = 0x10,
	/* Not response ACK */
	HOSTA_NACK = 0x20,
	/* Time-out error */
	HOSTA_TMOE = 0x40,
	/* Byte done status */
	HOSTA_BDS = 0x80,
	/* Error bit is set */
	HOSTA_ANY_ERROR = (HOSTA_DVER | HOSTA_BSER |
				HOSTA_FAIL | HOSTA_NACK | HOSTA_TMOE),
	/* W/C for next byte */
	HOSTA_NEXT_BYTE = HOSTA_BDS,
	/* W/C host status register */
	HOSTA_ALL_WC_BIT = (HOSTA_FINTR | HOSTA_ANY_ERROR | HOSTA_BDS),
};

enum enhanced_i2c_host_status {
	/* ACK receive */
	E_HOSTA_ACK = 0x01,
	/* Interrupt pending */
	E_HOSTA_INTP = 0x02,
	/* Read/Write */
	E_HOSTA_RW = 0x04,
	/* Time out error */
	E_HOSTA_TMOE = 0x08,
	/* Arbitration lost */
	E_HOSTA_ARB = 0x10,
	/* Bus busy */
	E_HOSTA_BB = 0x20,
	/* Address match */
	E_HOSTA_AM = 0x40,
	/* Byte done status */
	E_HOSTA_BDS = 0x80,
	/* time out or lost arbitration */
	E_HOSTA_ANY_ERROR = (E_HOSTA_TMOE | E_HOSTA_ARB),
	/* Byte transfer done and ACK receive */
	E_HOSTA_BDS_AND_ACK = (E_HOSTA_BDS | E_HOSTA_ACK),
};

enum enhanced_i2c_ctl {
	/* Hardware reset */
	E_HW_RST = 0x01,
	/* Stop */
	E_STOP = 0x02,
	/* Start & Repeat start */
	E_START = 0x04,
	/* Acknowledge */
	E_ACK = 0x08,
	/* State reset */
	E_STS_RST = 0x10,
	/* Mode select */
	E_MODE_SEL = 0x20,
	/* I2C interrupt enable */
	E_INT_EN = 0x40,
	/* 0 : Standard mode , 1 : Receive mode */
	E_RX_MODE = 0x80,
	/* State reset and hardware reset */
	E_STS_AND_HW_RST = (E_STS_RST | E_HW_RST),
	/* Generate start condition and transmit slave address */
	E_START_ID = (E_INT_EN | E_MODE_SEL | E_ACK | E_START | E_HW_RST),
	/* Generate stop condition */
	E_FINISH = (E_INT_EN | E_MODE_SEL | E_ACK | E_STOP | E_HW_RST),
};

enum i2c_reset_cause {
	I2C_RC_NO_IDLE_FOR_START = 1,
	I2C_RC_TIMEOUT,
};

struct i2c_ch_freq {
	int kpbs;
	uint8_t freq_set;
};

static const struct i2c_ch_freq i2c_freq_select[] = {
	{ 50,   1},
	{ 100,  2},
	{ 400,  3},
	{ 1000, 4},
};

struct i2c_pin {
	volatile uint8_t *pin_clk;
	volatile uint8_t *pin_data;
	volatile uint8_t *pin_clk_ctrl;
	volatile uint8_t *pin_data_ctrl;
	volatile uint8_t *mirror_clk;
	volatile uint8_t *mirror_data;
	uint8_t clk_mask;
	uint8_t data_mask;
};

static const struct i2c_pin i2c_pin_regs[] = {
	{ &IT83XX_GPIO_GPCRB3, &IT83XX_GPIO_GPCRB4,
		&IT83XX_GPIO_GPDRB, &IT83XX_GPIO_GPDRB,
		&IT83XX_GPIO_GPDMRB, &IT83XX_GPIO_GPDMRB,
		0x08, 0x10},
	{ &IT83XX_GPIO_GPCRC1, &IT83XX_GPIO_GPCRC2,
		&IT83XX_GPIO_GPDRC, &IT83XX_GPIO_GPDRC,
		&IT83XX_GPIO_GPDMRC, &IT83XX_GPIO_GPDMRC,
		0x02, 0x04},
#ifdef CONFIG_IT83XX_SMCLK2_ON_GPC7
	{ &IT83XX_GPIO_GPCRC7, &IT83XX_GPIO_GPCRF7,
		&IT83XX_GPIO_GPDRC, &IT83XX_GPIO_GPDRF,
		&IT83XX_GPIO_GPDMRC, &IT83XX_GPIO_GPDMRF,
		0x80, 0x80},
#else
	{ &IT83XX_GPIO_GPCRF6, &IT83XX_GPIO_GPCRF7,
		&IT83XX_GPIO_GPDRF, &IT83XX_GPIO_GPDRF,
		&IT83XX_GPIO_GPDMRF, &IT83XX_GPIO_GPDMRF,
		0x40, 0x80},
#endif
	{ &IT83XX_GPIO_GPCRH1, &IT83XX_GPIO_GPCRH2,
		&IT83XX_GPIO_GPDRH, &IT83XX_GPIO_GPDRH,
		&IT83XX_GPIO_GPDMRH, &IT83XX_GPIO_GPDMRH,
		0x02, 0x04},
	{ &IT83XX_GPIO_GPCRE0, &IT83XX_GPIO_GPCRE7,
		&IT83XX_GPIO_GPDRE, &IT83XX_GPIO_GPDRE,
		&IT83XX_GPIO_GPDMRE, &IT83XX_GPIO_GPDMRE,
		0x01, 0x80},
	{ &IT83XX_GPIO_GPCRA4, &IT83XX_GPIO_GPCRA5,
		&IT83XX_GPIO_GPDRA, &IT83XX_GPIO_GPDRA,
		&IT83XX_GPIO_GPDMRA, &IT83XX_GPIO_GPDMRA,
		0x10, 0x20},
};

struct i2c_ctrl_t {
	uint8_t irq;
	enum clock_gate_offsets clock_gate;
	int reg_shift;
};

const struct i2c_ctrl_t i2c_ctrl_regs[] = {
	{IT83XX_IRQ_SMB_A, CGC_OFFSET_SMBA, -1},
	{IT83XX_IRQ_SMB_B, CGC_OFFSET_SMBB, -1},
	{IT83XX_IRQ_SMB_C, CGC_OFFSET_SMBC, -1},
	{IT83XX_IRQ_SMB_D, CGC_OFFSET_SMBD, 3},
	{IT83XX_IRQ_SMB_E, CGC_OFFSET_SMBE, 0},
	{IT83XX_IRQ_SMB_F, CGC_OFFSET_SMBF, 1},
};

enum i2c_ch_status {
	I2C_CH_NORMAL = 0,
	I2C_CH_REPEAT_START,
	I2C_CH_WAIT_READ,
	I2C_CH_WAIT_NEXT_XFER,
};

/* I2C port state data */
struct i2c_port_data {
	const uint8_t *out;  /* Output data pointer */
	int out_size;        /* Output data to transfer, in bytes */
	uint8_t *in;         /* Input data pointer */
	int in_size;         /* Input data to transfer, in bytes */
	int flags;           /* Flags (I2C_XFER_*) */
	int widx;            /* Index into output data */
	int ridx;            /* Index into input data */
	int err;             /* Error code, if any */
	uint8_t addr_8bit;   /* address of device */
	uint32_t timeout_us; /* Transaction timeout, or 0 to use default */
	uint8_t freq;        /* Frequency setting */

	enum i2c_ch_status i2ccs;
	/* Task waiting on port, or TASK_ID_INVALID if none. */
	volatile int task_waiting;
};
static struct i2c_port_data pdata[I2C_PORT_COUNT];

static int i2c_ch_reg_shift(int p)
{
	/*
	 * only enhanced port needs to be changed the parameter of registers
	 */
	ASSERT(p >= I2C_STANDARD_PORT_COUNT && p < I2C_PORT_COUNT);

	/*
	 * The registers of i2c enhanced ports are not sequential.
	 * This routine transfers the i2c port number to related
	 * parameter of registers.
	 *
	 * IT83xx chip : i2c enhanced ports - channel D,E,F
	 * channel D registers : 0x3680 ~ 0x36FF
	 * channel E registers : 0x3500 ~ 0x357F
	 * channel F registers : 0x3580 ~ 0x35FF
	 */
	return i2c_ctrl_regs[p].reg_shift;
}

static void i2c_reset(int p, int cause)
{
	int p_ch;

	if (p < I2C_STANDARD_PORT_COUNT) {
		/* bit1, kill current transaction. */
		IT83XX_SMB_HOCTL(p) = 0x2;
		IT83XX_SMB_HOCTL(p) = 0;
		/* W/C host status register */
		IT83XX_SMB_HOSTA(p) = HOSTA_ALL_WC_BIT;
	} else {
		/* Shift register */
		p_ch = i2c_ch_reg_shift(p);
		/* State reset and hardware reset */
		IT83XX_I2C_CTR(p_ch) = E_STS_AND_HW_RST;
	}
	CPRINTS("I2C ch%d reset cause %d", p, cause);
}

static void i2c_r_last_byte(int p)
{
	struct i2c_port_data *pd = pdata + p;

	/*
	 * bit5, The firmware shall write 1 to this bit
	 * when the next byte will be the last byte for i2c read.
	 */
	if ((pd->flags & I2C_XFER_STOP) && (pd->ridx == pd->in_size - 1))
		IT83XX_SMB_HOCTL(p) |= 0x20;
}

static void i2c_w2r_change_direction(int p)
{
	/* I2C switch direction */
	if (IT83XX_SMB_HOCTL2(p) & 0x08) {
		i2c_r_last_byte(p);
		IT83XX_SMB_HOSTA(p) = HOSTA_NEXT_BYTE;
	} else {
		/*
		 * bit2, I2C switch direction wait.
		 * bit3, I2C switch direction enable.
		 */
		IT83XX_SMB_HOCTL2(p) |= 0x0C;
		IT83XX_SMB_HOSTA(p) = HOSTA_NEXT_BYTE;
		i2c_r_last_byte(p);
		IT83XX_SMB_HOCTL2(p) &= ~0x04;
	}
}

static void i2c_pio_trans_data(int p, enum enhanced_i2c_transfer_direct direct,
			       uint8_t data, int first_byte)
{
	struct i2c_port_data *pd = pdata + p;
	int p_ch;
	int nack = 0;

	/* Shift register */
	p_ch = i2c_ch_reg_shift(p);

	if (first_byte) {
		/* First byte must be slave address. */
		IT83XX_I2C_DTR(p_ch) =
			data | (direct == RX_DIRECT ? BIT(0) : 0);
		/* start or repeat start signal. */
		IT83XX_I2C_CTR(p_ch) = E_START_ID;
	} else {
		if (direct == TX_DIRECT)
			/* Transmit data */
			IT83XX_I2C_DTR(p_ch) = data;
		else {
			/*
			 * Receive data.
			 * Last byte should be NACK in the end of read cycle
			 */
			if (((pd->ridx + 1) == pd->in_size) &&
				(pd->flags & I2C_XFER_STOP))
				nack = 1;
		}
		/* Set hardware reset to start next transmission */
		IT83XX_I2C_CTR(p_ch) =
			E_INT_EN | E_MODE_SEL | E_HW_RST | (nack ? 0 : E_ACK);
	}
}

static int i2c_tran_write(int p)
{
	struct i2c_port_data *pd = pdata + p;

	if (pd->flags & I2C_XFER_START) {
		/* i2c enable */
		IT83XX_SMB_HOCTL2(p) = 0x13;
		/*
		 * bit0, Direction of the host transfer.
		 * bit[1:7}, Address of the targeted slave.
		 */
		IT83XX_SMB_TRASLA(p) = pd->addr_8bit;
		/* Send first byte */
		IT83XX_SMB_HOBDB(p) = *(pd->out++);
		pd->widx++;
		/* clear start flag */
		pd->flags &= ~I2C_XFER_START;
		/*
		 * bit0, Host interrupt enable.
		 * bit[2:4}, Extend command.
		 * bit6, start.
		 */
		IT83XX_SMB_HOCTL(p) = 0x5D;
	} else {
		/* Host has completed the transmission of a byte */
		if (IT83XX_SMB_HOSTA(p) & HOSTA_BDS) {
			if (pd->widx < pd->out_size) {
				/* Send next byte */
				IT83XX_SMB_HOBDB(p) = *(pd->out++);
				pd->widx++;
				/* W/C byte done for next byte */
				IT83XX_SMB_HOSTA(p) = HOSTA_NEXT_BYTE;
				if (pd->i2ccs == I2C_CH_REPEAT_START) {
					pd->i2ccs = I2C_CH_NORMAL;
					task_enable_irq(i2c_ctrl_regs[p].irq);
				}
			} else {
				/* done */
				pd->out_size = 0;
				if (pd->in_size > 0) {
					/* write to read */
					i2c_w2r_change_direction(p);
				} else {
					if (pd->flags & I2C_XFER_STOP) {
						/* set I2C_EN = 0 */
						IT83XX_SMB_HOCTL2(p) = 0x11;
						/* W/C byte done for finish */
						IT83XX_SMB_HOSTA(p) =
							HOSTA_NEXT_BYTE;
					} else {
						pd->i2ccs = I2C_CH_REPEAT_START;
						return 0;
					}
				}
			}
		}
	}
	return 1;
}

static int i2c_tran_read(int p)
{
	struct i2c_port_data *pd = pdata + p;

	if (pd->flags & I2C_XFER_START) {
		/* i2c enable */
		IT83XX_SMB_HOCTL2(p) = 0x13;
		/*
		 * bit0, Direction of the host transfer.
		 * bit[1:7}, Address of the targeted slave.
		 */
		IT83XX_SMB_TRASLA(p) = pd->addr_8bit | 0x01;
		/* clear start flag */
		pd->flags &= ~I2C_XFER_START;
		/*
		 * bit0, Host interrupt enable.
		 * bit[2:4}, Extend command.
		 * bit5, The firmware shall write 1 to this bit
		 *       when the next byte will be the last byte.
		 * bit6, start.
		 */
		if ((1 == pd->in_size) && (pd->flags & I2C_XFER_STOP))
			IT83XX_SMB_HOCTL(p) = 0x7D;
		else
			IT83XX_SMB_HOCTL(p) = 0x5D;
	} else {
		if ((pd->i2ccs == I2C_CH_REPEAT_START) ||
			(pd->i2ccs == I2C_CH_WAIT_READ)) {
			if (pd->i2ccs == I2C_CH_REPEAT_START) {
				/* write to read */
				i2c_w2r_change_direction(p);
			} else {
				/* For last byte */
				i2c_r_last_byte(p);
				/* W/C for next byte */
				IT83XX_SMB_HOSTA(p) = HOSTA_NEXT_BYTE;
			}
			pd->i2ccs = I2C_CH_NORMAL;
			task_enable_irq(i2c_ctrl_regs[p].irq);
		} else if (IT83XX_SMB_HOSTA(p) & HOSTA_BDS) {
			if (pd->ridx < pd->in_size) {
				/* To get received data. */
				*(pd->in++) = IT83XX_SMB_HOBDB(p);
				pd->ridx++;
				/* For last byte */
				i2c_r_last_byte(p);
				/* done */
				if (pd->ridx == pd->in_size) {
					pd->in_size = 0;
					if (pd->flags & I2C_XFER_STOP) {
						/* W/C for finish */
						IT83XX_SMB_HOSTA(p) =
							HOSTA_NEXT_BYTE;
					} else {
						pd->i2ccs = I2C_CH_WAIT_READ;
						return 0;
					}
				} else {
					/* W/C for next byte */
					IT83XX_SMB_HOSTA(p) = HOSTA_NEXT_BYTE;
				}
			}
		}
	}
	return 1;
}

static void enhanced_i2c_start(int p)
{
	/* Shift register */
	int p_ch = i2c_ch_reg_shift(p);

	/* State reset and hardware reset */
	IT83XX_I2C_CTR(p_ch) = E_STS_AND_HW_RST;
	/* Set i2c frequency */
	IT83XX_I2C_PSR(p_ch) = pdata[p].freq;
	IT83XX_I2C_HSPR(p_ch) = pdata[p].freq;
	/*
	 * Set time out register.
	 * I2C D/E/F clock/data low timeout.
	 */
	IT83XX_I2C_TOR(p_ch) = I2C_CLK_LOW_TIMEOUT;
	/* bit1: Enable enhanced i2c module */
	IT83XX_I2C_CTR1(p_ch) = BIT(1);
}

static int enhanced_i2c_tran_write(int p)
{
	struct i2c_port_data *pd = pdata + p;
	uint8_t out_data;
	int p_ch;

	/* Shift register */
	p_ch = i2c_ch_reg_shift(p);

	if (pd->flags & I2C_XFER_START) {
		/* Clear start bit */
		pd->flags &= ~I2C_XFER_START;
		enhanced_i2c_start(p);
		/* Send ID */
		i2c_pio_trans_data(p, TX_DIRECT, pd->addr_8bit, 1);
	} else {
		/* Host has completed the transmission of a byte */
		if (pd->widx < pd->out_size) {
			out_data = *(pd->out++);
			pd->widx++;

			/* Send Byte */
			i2c_pio_trans_data(p, TX_DIRECT, out_data, 0);
			if (pd->i2ccs == I2C_CH_WAIT_NEXT_XFER) {
				pd->i2ccs = I2C_CH_NORMAL;
				task_enable_irq(i2c_ctrl_regs[p].irq);
			}
		} else {
			/* done */
			pd->out_size = 0;
			if (pd->in_size > 0) {
				/* Write to read protocol */
				pd->i2ccs = I2C_CH_REPEAT_START;
				/* Repeat Start */
				i2c_pio_trans_data(p, RX_DIRECT,
						   pd->addr_8bit, 1);
			} else {
				if (pd->flags & I2C_XFER_STOP) {
					IT83XX_I2C_CTR(p_ch) = E_FINISH;
					/* wait for stop bit interrupt*/
					return 1;
				}
				/* Direct write with direct read */
				pd->i2ccs = I2C_CH_WAIT_NEXT_XFER;
				return 0;
			}
		}
	}
	return 1;
}

static int enhanced_i2c_tran_read(int p)
{
	struct i2c_port_data *pd = pdata + p;
	uint8_t in_data = 0;
	int p_ch;

	/* Shift register */
	p_ch = i2c_ch_reg_shift(p);

	if (pd->flags & I2C_XFER_START) {
		/* clear start flag */
		pd->flags &= ~I2C_XFER_START;
		enhanced_i2c_start(p);
		/* Direct read  */
		pd->i2ccs = I2C_CH_WAIT_READ;
		/* Send ID */
		i2c_pio_trans_data(p, RX_DIRECT, pd->addr_8bit, 1);
	} else {
		if (pd->i2ccs) {
			if (pd->i2ccs == I2C_CH_REPEAT_START) {
				pd->i2ccs = I2C_CH_NORMAL;
				/* Receive data */
				i2c_pio_trans_data(p, RX_DIRECT, in_data, 0);
			} else if (pd->i2ccs == I2C_CH_WAIT_READ) {
				pd->i2ccs = I2C_CH_NORMAL;
				/* Receive data */
				i2c_pio_trans_data(p, RX_DIRECT, in_data, 0);
				/* Turn on irq before next direct read */
				task_enable_irq(i2c_ctrl_regs[p].irq);
			} else {
				/* Write to read */
				pd->i2ccs = I2C_CH_WAIT_READ;
				/* Send ID */
				i2c_pio_trans_data(p, RX_DIRECT,
						   pd->addr_8bit, 1);
				task_enable_irq(i2c_ctrl_regs[p].irq);
			}
		} else {
			if (pd->ridx < pd->in_size) {
				/* read data */
				*(pd->in++) = IT83XX_I2C_DRR(p_ch);
				pd->ridx++;

				/* done */
				if (pd->ridx == pd->in_size) {
					pd->in_size = 0;
					if (pd->flags & I2C_XFER_STOP) {
						pd->i2ccs = I2C_CH_NORMAL;
						IT83XX_I2C_CTR(p_ch) = E_FINISH;
						/* wait for stop bit interrupt*/
						return 1;
					}
					/* End the transaction */
					pd->i2ccs = I2C_CH_WAIT_READ;
					return 0;
				}
				/* read next byte */
				i2c_pio_trans_data(p, RX_DIRECT, in_data, 0);
			}
		}
	}
	return 1;
}

static int enhanced_i2c_error(int p)
{
	struct i2c_port_data *pd = pdata + p;
	/* Shift register */
	int p_ch = i2c_ch_reg_shift(p);
	int i2c_str = IT83XX_I2C_STR(p_ch);

	if (i2c_str & E_HOSTA_ANY_ERROR) {
		pd->err = i2c_str & E_HOSTA_ANY_ERROR;
	/* device does not respond ACK */
	} else if ((i2c_str & E_HOSTA_BDS_AND_ACK) == E_HOSTA_BDS) {
		if (IT83XX_I2C_CTR(p_ch) & E_ACK)
			pd->err = E_HOSTA_ACK;
	}

	return pd->err;
}

static int i2c_transaction(int p)
{
	struct i2c_port_data *pd = pdata + p;
	int p_ch;

	if (p < I2C_STANDARD_PORT_COUNT) {
		/* any error */
		if (IT83XX_SMB_HOSTA(p) & HOSTA_ANY_ERROR) {
			pd->err = (IT83XX_SMB_HOSTA(p) & HOSTA_ANY_ERROR);
		} else {
			/* i2c write */
			if (pd->out_size)
				return i2c_tran_write(p);
			/* i2c read */
			else if (pd->in_size)
				return i2c_tran_read(p);
			/* wait finish */
			if (!(IT83XX_SMB_HOSTA(p) & HOSTA_FINTR))
				return 1;
		}
		/* W/C */
		IT83XX_SMB_HOSTA(p) = HOSTA_ALL_WC_BIT;
		/* disable the SMBus host interface */
		IT83XX_SMB_HOCTL2(p) = 0x00;
	} else {
		/* no error */
		if (!(enhanced_i2c_error(p))) {
			/* i2c write */
			if (pd->out_size)
				return enhanced_i2c_tran_write(p);
			/* i2c read */
			else if (pd->in_size)
				return enhanced_i2c_tran_read(p);
		}
		p_ch = i2c_ch_reg_shift(p);
		IT83XX_I2C_CTR(p_ch) = E_STS_AND_HW_RST;
		IT83XX_I2C_CTR1(p_ch) = 0;
	}
	/* done doing work */
	return 0;
}

int i2c_is_busy(int port)
{
	int p_ch;

	if (port < I2C_STANDARD_PORT_COUNT)
		return (IT83XX_SMB_HOSTA(port) &
			(HOSTA_HOBY | HOSTA_ALL_WC_BIT));

	p_ch = i2c_ch_reg_shift(port);
	return (IT83XX_I2C_STR(p_ch) & E_HOSTA_BB);
}

int chip_i2c_xfer(int port, uint16_t slave_addr_flags,
		  const uint8_t *out, int out_size,
		  uint8_t *in, int in_size, int flags)
{
	struct i2c_port_data *pd = pdata + port;
	uint32_t events = 0;

	if (out_size == 0 && in_size == 0)
		return EC_SUCCESS;

	if (pd->i2ccs) {
		if ((flags & I2C_XFER_SINGLE) == I2C_XFER_SINGLE)
			flags &= ~I2C_XFER_START;
	}

	/* Copy data to port struct */
	pd->out = out;
	pd->out_size = out_size;
	pd->in = in;
	pd->in_size = in_size;
	pd->flags = flags;
	pd->widx = 0;
	pd->ridx = 0;
	pd->err = 0;
	pd->addr_8bit = I2C_GET_ADDR(slave_addr_flags) << 1;

	/* Make sure we're in a good state to start */
	if ((flags & I2C_XFER_START) && (i2c_is_busy(port)
		|| (i2c_get_line_levels(port) != I2C_LINE_IDLE))) {
		/* Attempt to unwedge the port. */
		i2c_unwedge(port);
		/* reset i2c port */
		i2c_reset(port, I2C_RC_NO_IDLE_FOR_START);
	}

	pd->task_waiting = task_get_current();
	if (pd->flags & I2C_XFER_START) {
		pd->i2ccs = I2C_CH_NORMAL;
		/* enable i2c interrupt */
		task_clear_pending_irq(i2c_ctrl_regs[port].irq);
		task_enable_irq(i2c_ctrl_regs[port].irq);
	}
	/* Start transaction */
	i2c_transaction(port);
	/* Wait for transfer complete or timeout */
	events = task_wait_event_mask(TASK_EVENT_I2C_IDLE, pd->timeout_us);
	/* disable i2c interrupt */
	task_disable_irq(i2c_ctrl_regs[port].irq);
	pd->task_waiting = TASK_ID_INVALID;

	/* Handle timeout */
	if (!(events & TASK_EVENT_I2C_IDLE)) {
		pd->err = EC_ERROR_TIMEOUT;
		/* reset i2c port */
		i2c_reset(port, I2C_RC_TIMEOUT);
	}

	/* reset i2c channel status */
	if (pd->err)
		pd->i2ccs = I2C_CH_NORMAL;

	return pd->err;
}

int i2c_raw_get_scl(int port)
{
	enum gpio_signal g;

	if (get_scl_from_i2c_port(port, &g) == EC_SUCCESS)
		return !!(*i2c_pin_regs[port].mirror_clk &
			i2c_pin_regs[port].clk_mask);

	/* If no SCL pin defined for this port, then return 1 to appear idle */
	return 1;
}

int i2c_raw_get_sda(int port)
{
	enum gpio_signal g;

	if (get_sda_from_i2c_port(port, &g) == EC_SUCCESS)
		return !!(*i2c_pin_regs[port].mirror_data &
			i2c_pin_regs[port].data_mask);

	/* If no SDA pin defined for this port, then return 1 to appear idle */
	return 1;
}

int i2c_get_line_levels(int port)
{
	int pin_sts = 0;

	if (port < I2C_STANDARD_PORT_COUNT)
		return IT83XX_SMB_SMBPCTL(port) & 0x03;

	if (*i2c_pin_regs[port].mirror_clk & i2c_pin_regs[port].clk_mask)
		pin_sts |= I2C_LINE_SCL_HIGH;
	if (*i2c_pin_regs[port].mirror_data & i2c_pin_regs[port].data_mask)
		pin_sts |= I2C_LINE_SDA_HIGH;

	return pin_sts;
}

void i2c_set_timeout(int port, uint32_t timeout)
{
	pdata[port].timeout_us = timeout ? timeout : I2C_TIMEOUT_DEFAULT_US;
}

void i2c_interrupt(int port)
{
	int id = pdata[port].task_waiting;

	/* Clear the interrupt status */
	task_clear_pending_irq(i2c_ctrl_regs[port].irq);

	/* If no task is waiting, just return */
	if (id == TASK_ID_INVALID)
		return;

	/* If done doing work, wake up the task waiting for the transfer */
	if (!i2c_transaction(port)) {
		task_disable_irq(i2c_ctrl_regs[port].irq);
		task_set_event(id, TASK_EVENT_I2C_IDLE, 0);
	}
}

static void i2c_freq_changed(void)
{
	int i, f, clk_div, psr, freq;
	int p_ch;

	/*
	 * Standard I2C Channels
	 */
	for (i = 0; i < i2c_ports_used; i++) {
		freq = i2c_ports[i].kbps;
		if (i2c_ports[i].port < I2C_STANDARD_PORT_COUNT) {
			for (f = ARRAY_SIZE(i2c_freq_select) - 1; f >= 0; f--) {
				if (freq >= i2c_freq_select[f].kpbs) {
					IT83XX_SMB_SCLKTS(i2c_ports[i].port) =
						i2c_freq_select[f].freq_set;
					break;
				}
			}
		} else {
			p_ch = i2c_ch_reg_shift(i2c_ports[i].port);
			/*
			 * Let psr(Prescale) = IT83XX_I2C_PSR(p_ch)
			 * Then, 1 SCL cycle = 2 x (psr + 2) x SMBus clock cycle
			 * SMBus clock = PLL_CLOCK / clk_div
			 * SMBus clock cycle = 1 / SMBus clock
			 * 1 SCL cycle = 1 / (1000 x freq)
			 * 1 / (1000 x freq) =
			 *          2 x (psr + 2) x (1 / (PLL_CLOCK / clk_div))
			 * psr = ((PLL_CLOCK / clk_div) x
			 *          (1 / (1000 x freq)) x (1 / 2)) - 2
			 */
			if (freq) {
				/* Get SMBus clock divide value */
				clk_div = (IT83XX_ECPM_SCDCR2 & 0x0F) + 1;
				/* Calculate PSR value */
				psr = (PLL_CLOCK /
					(clk_div * (2 * 1000 * freq))) - 2;
				/* Set psr value under 0xFD */
				if (psr > 0xFD)
					psr = 0xFD;

				/* Set I2C Speed */
				IT83XX_I2C_PSR(p_ch) = (psr & 0xFF);
				IT83XX_I2C_HSPR(p_ch) = (psr & 0xFF);

				/* Backup */
				pdata[i2c_ports[i].port].freq = (psr & 0xFF);
			}
		}
	}
	/* This field defines the SMCLK0/1/2 clock/data low timeout. */
	IT83XX_SMB_25MS = I2C_CLK_LOW_TIMEOUT;
}
DECLARE_HOOK(HOOK_FREQ_CHANGE, i2c_freq_changed, HOOK_PRIO_DEFAULT);

static void i2c_init(void)
{
	int i, p, p_ch;

	/* Configure GPIOs */
	gpio_config_module(MODULE_I2C, 1);

#ifdef CONFIG_IT83XX_SMCLK2_ON_GPC7
	/* bit7, 0: SMCLK2 is located on GPF6, 1: SMCLK2 is located on GPC7 */
	IT83XX_GPIO_GRC7 |= 0x80;
#endif

	/* Enable I2C function. */
	for (i = 0; i < i2c_ports_used; i++) {
		/* I2c port mapping. */
		p = i2c_ports[i].port;

		clock_enable_peripheral(i2c_ctrl_regs[p].clock_gate, 0, 0);

		if (p < I2C_STANDARD_PORT_COUNT) {
			/*
			 * bit0, The SMBus host interface is enabled.
			 * bit1, Enable to communicate with I2C device
			 *        and support I2C-compatible cycles.
			 * bit4, This bit controls the reset mechanism
			 *        of SMBus master to handle the SMDAT
			 *        line low if 25ms reg timeout.
			 */
			IT83XX_SMB_HOCTL2(p) = 0x11;
			/*
			 * bit1, Kill SMBus host transaction.
			 * bit0, Enable the interrupt for the master interface.
			 */
			IT83XX_SMB_HOCTL(p) = 0x03;
			IT83XX_SMB_HOCTL(p) = 0x01;
			/* W/C host status register */
			IT83XX_SMB_HOSTA(p) = HOSTA_ALL_WC_BIT;
			IT83XX_SMB_HOCTL2(p) = 0x00;
		} else {
			/* Shift register */
			p_ch = i2c_ch_reg_shift(p);
			switch (p) {
			case IT83XX_I2C_CH_D:
				#ifndef CONFIG_UART_HOST
				/* Enable SMBus D channel */
				IT83XX_GPIO_GRC2 |= 0x20;
				#endif
				break;
			case IT83XX_I2C_CH_E:
				/* Enable SMBus E channel */
				IT83XX_GCTRL_PMER1 |= 0x01;
				break;
			case IT83XX_I2C_CH_F:
				/* Enable SMBus F channel */
				IT83XX_GCTRL_PMER1 |= 0x02;
				break;
			}
			/* Software reset */
			IT83XX_I2C_DHTR(p_ch) |= 0x80;
			IT83XX_I2C_DHTR(p_ch) &= 0x7F;
			/* State reset and hardware reset */
			IT83XX_I2C_CTR(p_ch) = E_STS_AND_HW_RST;
			/* bit1, Module enable */
			IT83XX_I2C_CTR1(p_ch) = 0;
		}
		pdata[i].task_waiting = TASK_ID_INVALID;
	}

	i2c_freq_changed();

	for (i = 0; i < I2C_PORT_COUNT; i++) {
		/* Use default timeout */
		i2c_set_timeout(i, 0);
	}
}
DECLARE_HOOK(HOOK_INIT, i2c_init, HOOK_PRIO_INIT_I2C);
