/*
 * This file is part of the coreboot project.
 *
 * Copyright 2015 Marvell Inc.
 *
 * 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; version 2 of the License.
 *
 * 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.
 */

#include <arch/io.h>
#include <arch/cpu.h>
#include <console/console.h>
#include <delay.h>
#include <device/i2c.h>
#include <soc/common.h>
#include <soc/i2c.h>
#include <soc/clock.h>
#include <helpers.h>

#undef MV_DEBUG
//#define MV_DEBUG
#ifdef MV_DEBUG
#define DB(x) x
#else
#define DB(x)
#endif
#define mv_os_printf(args...) printk(BIOS_INFO, args)

/* The TWSI interface supports both 7-bit and 10-bit addressing.            */
/* This enumerator describes addressing type.                               */
typedef enum _mv_twsi_addr_type {
	ADDR7_BIT, /* 7 bit address    */
	ADDR10_BIT /* 10 bit address   */
} MV_TWSI_ADDR_TYPE;

/* This structure describes TWSI address.                                   */
typedef struct _mv_twsi_addr {
	uint32_t address;       /* address          */
	MV_TWSI_ADDR_TYPE type; /* Address type     */
} MV_TWSI_ADDR;

/* This structure describes a TWSI slave.                                   */
typedef struct _mv_twsi_slave {
	MV_TWSI_ADDR slave_addr;
	int valid_offset; /* whether the slave has offset (i.e. Eeprom  etc.) */
	uint32_t offset;  /* offset in the slave. */
	int more_than256; /* whether the ofset is bigger then 256 */
} MV_TWSI_SLAVE;

/* This enumerator describes TWSI protocol commands.                        */
typedef enum _mv_twsi_cmd {
	MV_TWSI_WRITE, /* TWSI write command - 0 according to spec   */
	MV_TWSI_READ   /* TWSI read command  - 1 according to spec */
} MV_TWSI_CMD;

static void twsi_int_flg_clr(uint8_t chan_num);
static uint8_t twsi_main_int_get(uint8_t chan_num);
static void twsi_ack_bit_set(uint8_t chan_num);
static uint32_t twsi_sts_get(uint8_t chan_num);
static void twsi_reset(uint8_t chan_num);
static int twsi_addr7_bit_set(uint8_t chan_num,
			      uint32_t device_address,
			      MV_TWSI_CMD command);
static int twsi_addr10_bit_set(uint8_t chan_num,
			       uint32_t device_address,
			       MV_TWSI_CMD command);
static int twsi_data_transmit(uint8_t chan_num,
			      uint8_t *p_block,
			      uint32_t block_size);
static int twsi_data_receive(uint8_t chan_num,
			     uint8_t *p_block,
			     uint32_t block_size);
static int twsi_target_offs_set(uint8_t chan_num,
				uint32_t offset,
				uint8_t more_than256);
static int mv_twsi_start_bit_set(uint8_t chan_num);
static int mv_twsi_stop_bit_set(uint8_t chan_num);
static int mv_twsi_addr_set(uint8_t chan_num,
			    MV_TWSI_ADDR *twsi_addr,
			    MV_TWSI_CMD command);
static uint32_t mv_twsi_init(uint8_t chan_num,
			     uint32_t frequency,
			     uint32_t Tclk,
			     MV_TWSI_ADDR *twsi_addr,
			     uint8_t general_call_enable);
static int mv_twsi_read(uint8_t chan_num,
			MV_TWSI_SLAVE *twsi_slave,
			uint8_t *p_block,
			uint32_t block_size);
static int mv_twsi_write(uint8_t chan_num,
			 MV_TWSI_SLAVE *twsi_slave,
			 uint8_t *p_block,
			 uint32_t block_size);
static uint32_t who_am_i(void);
static int i2c_init(unsigned bus);
static void i2c_reset(unsigned bus);

static int m_initialized[MAX_I2C_NUM] = {0, 0};

static uint8_t twsi_timeout_chk(uint32_t timeout, const char *p_string)
{
	if (timeout >= TWSI_TIMEOUT_VALUE) {
		DB(mv_os_printf("%s", p_string));
		return MV_TRUE;
	}
	return MV_FALSE;
}

/*******************************************************************************
* mv_twsi_start_bit_set - Set start bit on the bus
*
* DESCRIPTION:
*       This routine sets the start bit on the TWSI bus.
*       The routine first checks for interrupt flag condition, then it sets
*       the start bit  in the TWSI Control register.
*       If the interrupt flag condition check previously was set, the function
*       will clear it.
*       The function then wait for the start bit to be cleared by the HW.
*       Then it waits for the interrupt flag to be set and eventually, the
*       TWSI status is checked to be 0x8 or 0x10(repeated start bit).
*
* INPUT:
*       chan_num - TWSI channel.
*
* OUTPUT:
*       None.
*
* RETURN:
*       MV_OK if start bit was set successfuly on the bus.
*       MV_FAIL if start_bit not set or status does not indicate start
*       condition trasmitted.
*
*******************************************************************************/
static int mv_twsi_start_bit_set(uint8_t chan_num)
{
	uint8_t is_int_flag = MV_FALSE;
	uint32_t timeout, temp;

	DB(mv_os_printf("TWSI: mv_twsi_start_bit_set\n"));
	/* check Int flag */
	if (twsi_main_int_get(chan_num))
		is_int_flag = MV_TRUE;
	/* set start Bit */
	mrvl_reg_bit_set(TWSI_CONTROL_REG(chan_num), TWSI_CONTROL_START_BIT);

	/* in case that the int flag was set before i.e. repeated start bit */
	if (is_int_flag) {
		DB(mv_os_printf(
		    "TWSI: mv_twsi_start_bit_set repeated start Bit\n"));
		twsi_int_flg_clr(chan_num);
	}

	/* wait for interrupt */
	timeout = 0;
	while (!twsi_main_int_get(chan_num) && (timeout++ < TWSI_TIMEOUT_VALUE))
		;

	/* check for timeout */
	if (MV_TRUE ==
	    twsi_timeout_chk(timeout,
			     (const char *)"TWSI: Start Clear bit time_out.\n"))
		return MV_TIMEOUT;

	/* check that start bit went down */
	if ((mrvl_reg_read(TWSI_CONTROL_REG(chan_num)) &
	     TWSI_CONTROL_START_BIT) != 0) {
		mv_os_printf("TWSI: start bit didn't go down\n");
		return MV_FAIL;
	}

	/* check the status */
	temp = twsi_sts_get(chan_num);
	if ((TWSI_M_LOST_ARB_DUR_AD_OR_DATA_TRA == temp) ||
	    (TWSI_M_LOST_ARB_DUR_AD_TRA_GNL_CALL_AD_REC_ACK_TRA == temp)) {
		DB(mv_os_printf("TWSI: Lost Arb, status %x\n", temp));
		return MV_RETRY;
	} else if ((temp != TWSI_START_CON_TRA) &&
		   (temp != TWSI_REPEATED_START_CON_TRA)) {
		mv_os_printf("TWSI: status %x after Set Start Bit.\n", temp);
		return MV_FAIL;
	}

	return MV_OK;
}

/*******************************************************************************
* mv_twsi_stop_bit_set - Set stop bit on the bus
*
* DESCRIPTION:
*       This routine set the stop bit on the TWSI bus.
*       The function then wait for the stop bit to be cleared by the HW.
*       Finally the function checks for status of 0xF8.
*
* INPUT:
*	chan_num - TWSI channel
*
* OUTPUT:
*       None.
*
* RETURN:
*       MV_TRUE is stop bit was set successfuly on the bus.
*
*******************************************************************************/
static int mv_twsi_stop_bit_set(uint8_t chan_num)
{
	uint32_t timeout, temp;

	/* Generate stop bit */
	mrvl_reg_bit_set(TWSI_CONTROL_REG(chan_num), TWSI_CONTROL_STOP_BIT);

	twsi_int_flg_clr(chan_num);

	/* wait for stop bit to come down */
	timeout = 0;
	while (((mrvl_reg_read(TWSI_CONTROL_REG(chan_num)) &
		 TWSI_CONTROL_STOP_BIT) != 0) &&
	       (timeout++ < TWSI_TIMEOUT_VALUE))
		;

	/* check for timeout */
	if (MV_TRUE ==
	    twsi_timeout_chk(timeout,
			     (const char *)"TWSI: ERROR - Stop bit timeout\n"))
		return MV_TIMEOUT;

	/* check that the stop bit went down */
	if ((mrvl_reg_read(TWSI_CONTROL_REG(chan_num)) &
	     TWSI_CONTROL_STOP_BIT) != 0) {
		mv_os_printf(
		    "TWSI: ERROR - stop bit not went down\n");
		return MV_FAIL;
	}

	/* check the status */
	temp = twsi_sts_get(chan_num);
	if ((TWSI_M_LOST_ARB_DUR_AD_OR_DATA_TRA == temp) ||
	    (TWSI_M_LOST_ARB_DUR_AD_TRA_GNL_CALL_AD_REC_ACK_TRA == temp)) {
		DB(mv_os_printf("TWSI: Lost Arb, status %x\n", temp));
		return MV_RETRY;
	} else if (temp != TWSI_NO_REL_STS_INT_FLAG_IS_KEPT_0) {
		mv_os_printf(
		    "TWSI: ERROR - status %x after Stop Bit\n",
		    temp);
		return MV_FAIL;
	}

	return MV_OK;
}

/*******************************************************************************
* twsi_main_int_get - Get twsi bit from main Interrupt cause.
*
* DESCRIPTION:
*       This routine returns the twsi interrupt flag value.
*
* INPUT:
*       None.
*
* OUTPUT:
*       None.
*
* RETURN:
*       MV_TRUE is interrupt flag is set, MV_FALSE otherwise.
*
*******************************************************************************/
static uint32_t who_am_i(void)
{
	return (read_mpidr() & 0x1);
}

static uint8_t twsi_main_int_get(uint8_t chan_num)
{
	uint32_t temp;

	/* get the int flag bit */
	temp = mrvl_reg_read(MV_TWSI_CPU_MAIN_INT_CAUSE(chan_num, who_am_i()));
	if (temp & (1 << CPU_MAIN_INT_TWSI_OFFS(chan_num)))
		return MV_TRUE;

	return MV_FALSE;
}

/*******************************************************************************
* twsi_int_flg_clr - Clear Interrupt flag.
*
* DESCRIPTION:
*       This routine clears the interrupt flag. It does NOT poll the interrupt
*       to make sure the clear. After clearing the interrupt, it waits for at
*       least 1 miliseconds.
*
* INPUT:
*	chan_num - TWSI channel
*
* OUTPUT:
*       None.
*
* RETURN:
*       None.
*
*******************************************************************************/
static void twsi_int_flg_clr(uint8_t chan_num)
{
	/* wait for 1ms to prevent TWSI register write after write problems */
	mdelay(1);
	/* clear the int flag bit */
	mrvl_reg_bit_reset(
		TWSI_CONTROL_REG(chan_num), TWSI_CONTROL_INT_FLAG_SET);
	/* wait for 1 mili sec for the clear to take effect */
	mdelay(1);
}

/*******************************************************************************
* twsi_ack_bit_set - Set acknowledge bit on the bus
*
* DESCRIPTION:
*       This routine set the acknowledge bit on the TWSI bus.
*
* INPUT:
*       None.
*
* OUTPUT:
*       None.
*
* RETURN:
*       None.
*
*******************************************************************************/
static void twsi_ack_bit_set(uint8_t chan_num)
{
	/*Set the Ack bit */
	mrvl_reg_bit_set(TWSI_CONTROL_REG(chan_num), TWSI_CONTROL_ACK);
	/* Add delay of 1ms */
	mdelay(1);
}

/*******************************************************************************
* twsi_init - Initialize TWSI interface
*
* DESCRIPTION:
*       This routine:
*	-Reset the TWSI.
*	-Initialize the TWSI clock baud rate according to given frequency
*	 parameter based on Tclk frequency and enables TWSI slave.
*       -Set the ack bit.
*	-Assign the TWSI slave address according to the TWSI address Type.
*
* INPUT:
*	chan_num - TWSI channel
*       frequency - TWSI frequency in KHz. (up to 100_kHZ)
*
* OUTPUT:
*       None.
*
* RETURN:
*       Actual frequency.
*
*******************************************************************************/
static uint32_t mv_twsi_init(uint8_t chan_num,
			     uint32_t frequency,
			     uint32_t Tclk,
			     MV_TWSI_ADDR *p_twsi_addr,
			     uint8_t general_call_enable)
{
	uint32_t n, m, freq, margin, min_margin = 0xffffffff;
	uint32_t power;
	uint32_t actual_freq = 0, actual_n = 0, actual_m = 0, val;

	if (frequency > 100000)
		die("TWSI frequency is too high!");

	DB(mv_os_printf("TWSI: mv_twsi_init - Tclk = %d freq = %d\n", Tclk,
			frequency));
	/* Calucalte N and M for the TWSI clock baud rate */
	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);
			margin = ABS(frequency - freq);

			if ((freq <= frequency) && (margin < min_margin)) {
				min_margin = margin;
				actual_freq = freq;
				actual_n = n;
				actual_m = m;
			}
		}
	}
	DB(mv_os_printf("TWSI: mv_twsi_init - act_n %u act_m %u act_freq %u\n",
			actual_n, actual_m, actual_freq));
	/* Reset the TWSI logic */
	twsi_reset(chan_num);

	/* Set the baud rate */
	val = ((actual_m << TWSI_BAUD_RATE_M_OFFS) |
	       actual_n << TWSI_BAUD_RATE_N_OFFS);
	mrvl_reg_write(TWSI_STATUS_BAUDE_RATE_REG(chan_num), val);

	/* Enable the TWSI and slave */
	mrvl_reg_write(TWSI_CONTROL_REG(chan_num),
		       TWSI_CONTROL_ENA | TWSI_CONTROL_ACK);

	/* set the TWSI slave address */
	if (p_twsi_addr->type == ADDR10_BIT) {
		/* writing the 2 most significant bits of the 10 bit address */
		val = ((p_twsi_addr->address & TWSI_SLAVE_ADDR_10_BIT_MASK) >>
		       TWSI_SLAVE_ADDR_10_BIT_OFFS);
		/* bits 7:3 must be 0x11110 */
		val |= TWSI_SLAVE_ADDR_10_BIT_CONST;
		/* set GCE bit */
		if (general_call_enable)
			val |= TWSI_SLAVE_ADDR_GCE_ENA;
		/* write slave address */
		mrvl_reg_write(TWSI_SLAVE_ADDR_REG(chan_num), val);

		/* writing the 8 least significant bits of the 10 bit address */
		val = (p_twsi_addr->address << TWSI_EXTENDED_SLAVE_OFFS) &
		      TWSI_EXTENDED_SLAVE_MASK;
		mrvl_reg_write(TWSI_EXTENDED_SLAVE_ADDR_REG(chan_num), val);
	} else {
		/* set the 7 Bits address */
		mrvl_reg_write(TWSI_EXTENDED_SLAVE_ADDR_REG(chan_num), 0x0);
		val = (p_twsi_addr->address << TWSI_SLAVE_ADDR_7_BIT_OFFS) &
		      TWSI_SLAVE_ADDR_7_BIT_MASK;
		mrvl_reg_write(TWSI_SLAVE_ADDR_REG(chan_num), val);
	}

	/* unmask twsi int */
	mrvl_reg_bit_set(TWSI_CONTROL_REG(chan_num), TWSI_CONTROL_INT_ENA);

	/* unmask twsi int in Interrupt source control register */
	mrvl_reg_bit_set(CPU_INT_SOURCE_CONTROL_REG(
			CPU_MAIN_INT_CAUSE_TWSI(chan_num)), (
				1 << CPU_INT_SOURCE_CONTROL_IRQ_OFFS));

	/* Add delay of 1ms */
	mdelay(1);

	return actual_freq;
}

/*******************************************************************************
* twsi_sts_get - Get the TWSI status value.
*
* DESCRIPTION:
*       This routine returns the TWSI status value.
*
* INPUT:
*	chan_num - TWSI channel
*
* OUTPUT:
*       None.
*
* RETURN:
*       uint32_t - the TWSI status.
*
*******************************************************************************/
static uint32_t twsi_sts_get(uint8_t chan_num)
{
	return mrvl_reg_read(TWSI_STATUS_BAUDE_RATE_REG(chan_num));
}

/*******************************************************************************
* twsi_reset - Reset the TWSI.
*
* DESCRIPTION:
*       Resets the TWSI logic and sets all TWSI registers to their reset values.
*
* INPUT:
*      chan_num - TWSI channel
*
* OUTPUT:
*       None.
*
* RETURN:
*       None
*
*******************************************************************************/
static void twsi_reset(uint8_t chan_num)
{
	/* Reset the TWSI logic */
	mrvl_reg_write(TWSI_SOFT_RESET_REG(chan_num), 0);

	/* wait for 2 mili sec */
	mdelay(2);
}

/*******************************************************************************
* mv_twsi_addr_set - Set address on TWSI bus.
*
* DESCRIPTION:
*       This function Set address (7 or 10 Bit address) on the Twsi Bus.
*
* INPUT:
*	chan_num - TWSI channel
*       p_twsi_addr - twsi address.
*	command	 - read / write .
*
* OUTPUT:
*       None.
*
* RETURN:
*       MV_OK - if setting the address completed successfully.
*	MV_FAIL otherwmise.
*
*******************************************************************************/
static int mv_twsi_addr_set(uint8_t chan_num,
			    MV_TWSI_ADDR *p_twsi_addr,
			    MV_TWSI_CMD command)
{
	DB(mv_os_printf(
	    "TWSI: mv_twsi_addr7_bit_set addr %x , type %d, cmd is %s\n",
	    p_twsi_addr->address, p_twsi_addr->type,
	    ((command == MV_TWSI_WRITE) ? "Write" : "Read")));
	/* 10 Bit address */
	if (p_twsi_addr->type == ADDR10_BIT)
		return twsi_addr10_bit_set(chan_num, p_twsi_addr->address,
					   command);
	/* 7 Bit address */
	else
		return twsi_addr7_bit_set(chan_num, p_twsi_addr->address,
					  command);
}

/*******************************************************************************
* twsi_addr10_bit_set - Set 10 Bit address on TWSI bus.
*
* DESCRIPTION:
*       There are two address phases:
*       1) Write '11110' to data register bits [7:3] and 10-bit address MSB
*          (bits [9:8]) to data register bits [2:1] plus a write(0) or read(1)
*bit
*          to the Data register. Then it clears interrupt flag which drive
*          the address on the TWSI bus. The function then waits for interrupt
*          flag to be active and status 0x18 (write) or 0x40 (read) to be set.
*       2) write the rest of 10-bit address to data register and clears
*          interrupt flag which drive the address on the TWSI bus. The
*          function then waits for interrupt flag to be active and status
*          0xD0 (write) or 0xE0 (read) to be set.
*
* INPUT:
*	chan_num - TWSI channel
*       device_address - twsi address.
*	command	 - read / write .
*
* OUTPUT:
*       None.
*
* RETURN:
*       MV_OK - if setting the address completed successfully.
*	MV_FAIL otherwmise.
*
*******************************************************************************/
static int twsi_addr10_bit_set(uint8_t chan_num,
			       uint32_t device_address,
			       MV_TWSI_CMD command)
{
	uint32_t val, timeout;

	/* writing the 2 most significant bits of the 10 bit address */
	val = ((device_address & TWSI_DATA_ADDR_10_BIT_MASK) >>
	       TWSI_DATA_ADDR_10_BIT_OFFS);
	/* bits 7:3 must be 0x11110 */
	val |= TWSI_DATA_ADDR_10_BIT_CONST;
	/* set command */
	val |= command;
	mrvl_reg_write(TWSI_DATA_REG(chan_num), val);
	/* WA add a delay */
	mdelay(1);

	/* clear Int flag */
	twsi_int_flg_clr(chan_num);

	/* wait for Int to be Set */
	timeout = 0;
	while (!twsi_main_int_get(chan_num) && (timeout++ < TWSI_TIMEOUT_VALUE))
		;

	/* check for timeout */
	if (MV_TRUE ==
	    twsi_timeout_chk(
		timeout, (const char *)"TWSI: addr (10_bit) Int time_out.\n"))
		return MV_TIMEOUT;

	/* check the status */
	val = twsi_sts_get(chan_num);
	if ((TWSI_M_LOST_ARB_DUR_AD_OR_DATA_TRA == val) ||
	    (TWSI_M_LOST_ARB_DUR_AD_TRA_GNL_CALL_AD_REC_ACK_TRA == val)) {
		DB(mv_os_printf("TWSI: Lost Arb, status %x\n", val));
		return MV_RETRY;
	} else if (((val != TWSI_AD_PLS_RD_BIT_TRA_ACK_REC) &&
		    (command == MV_TWSI_READ)) ||
		   ((val != TWSI_AD_PLS_WR_BIT_TRA_ACK_REC) &&
		    (command == MV_TWSI_WRITE))) {
		mv_os_printf("TWSI: status %x 1st addr (10 Bit) in %s mode.\n",
			     val,
			     ((command == MV_TWSI_WRITE) ? "Write" : "Read"));
		return MV_FAIL;
	}

	/* set  8 LSB of the address */
	val = (device_address << TWSI_DATA_ADDR_7_BIT_OFFS) &
	      TWSI_DATA_ADDR_7_BIT_MASK;
	mrvl_reg_write(TWSI_DATA_REG(chan_num), val);

	/* clear Int flag */
	twsi_int_flg_clr(chan_num);

	/* wait for Int to be Set */
	timeout = 0;
	while (!twsi_main_int_get(chan_num) && (timeout++ < TWSI_TIMEOUT_VALUE))
		;

	/* check for timeout */
	if (MV_TRUE ==
	    twsi_timeout_chk(timeout,
			     (const char *)"TWSI: 2nd (10 Bit) Int tim_out.\n"))
		return MV_TIMEOUT;

	/* check the status */
	val = twsi_sts_get(chan_num);
	if ((TWSI_M_LOST_ARB_DUR_AD_OR_DATA_TRA == val) ||
	    (TWSI_M_LOST_ARB_DUR_AD_TRA_GNL_CALL_AD_REC_ACK_TRA == val)) {
		DB(mv_os_printf("TWSI: Lost Arb, status %x\n", val));
		return MV_RETRY;
	} else if (((val != TWSI_SEC_AD_PLS_RD_BIT_TRA_ACK_REC) &&
		    (command == MV_TWSI_READ)) ||
		   ((val != TWSI_SEC_AD_PLS_WR_BIT_TRA_ACK_REC) &&
		    (command == MV_TWSI_WRITE))) {
		mv_os_printf("TWSI: status %x 2nd addr(10 Bit) in %s mode.\n",
			     val,
			     ((command == MV_TWSI_WRITE) ? "Write" : "Read"));
		return MV_FAIL;
	}

	return MV_OK;
}

/*******************************************************************************
* twsi_addr7_bit_set - Set 7 Bit address on TWSI bus.
*
* DESCRIPTION:
*       This function writes 7 bit address plus a write or read bit to the
*       Data register. Then it clears interrupt flag which drive the address on
*       the TWSI bus. The function then waits for interrupt flag to be active
*       and status 0x18 (write) or 0x40 (read) to be set.
*
* INPUT:
*	chan_num - TWSI channel
*       device_address - twsi address.
*	command	 - read / write .
*
* OUTPUT:
*       None.
*
* RETURN:
*       MV_OK - if setting the address completed successfully.
*	MV_FAIL otherwmise.
*
*******************************************************************************/
static int twsi_addr7_bit_set(uint8_t chan_num,
			      uint32_t device_address,
			      MV_TWSI_CMD command)
{
	uint32_t val, timeout;

	/* set the address */
	val = (device_address << TWSI_DATA_ADDR_7_BIT_OFFS) &
	      TWSI_DATA_ADDR_7_BIT_MASK;
	/* set command */
	val |= command;
	mrvl_reg_write(TWSI_DATA_REG(chan_num), val);
	/* WA add a delay */
	mdelay(1);

	/* clear Int flag */
	twsi_int_flg_clr(chan_num);

	/* wait for Int to be Set */
	timeout = 0;
	while (!twsi_main_int_get(chan_num) && (timeout++ < TWSI_TIMEOUT_VALUE))
		;

	/* check for timeout */
	if (MV_TRUE ==
	    twsi_timeout_chk(
		timeout, (const char *)"TWSI: Addr (7 Bit) int time_out.\n"))
		return MV_TIMEOUT;

	/* check the status */
	val = twsi_sts_get(chan_num);
	if ((TWSI_M_LOST_ARB_DUR_AD_OR_DATA_TRA == val) ||
	    (TWSI_M_LOST_ARB_DUR_AD_TRA_GNL_CALL_AD_REC_ACK_TRA == val)) {
		DB(mv_os_printf("TWSI: Lost Arb, status %x\n", val));
		return MV_RETRY;
	} else if (((val != TWSI_AD_PLS_RD_BIT_TRA_ACK_REC) &&
		    (command == MV_TWSI_READ)) ||
		   ((val != TWSI_AD_PLS_WR_BIT_TRA_ACK_REC) &&
		    (command == MV_TWSI_WRITE))) {
		/* only in debug, since in boot we try to read the SPD of both
		   DRAM, and we don't
		   want error messeges in case DIMM doesn't exist. */
		DB(mv_os_printf(
		    "TWSI: status %x addr (7 Bit) in %s mode.\n", val,
		    ((command == MV_TWSI_WRITE) ? "Write" : "Read")));
		return MV_FAIL;
	}

	return MV_OK;
}

/*******************************************************************************
* twsi_data_write - Trnasmit a data block over TWSI bus.
*
* DESCRIPTION:
*       This function writes a given data block to TWSI bus in 8 bit
*       granularity.
*	first The function waits for interrupt flag to be active then
*       For each 8-bit data:
*        The function writes data to data register. It then clears
*        interrupt flag which drives the data on the TWSI bus.
*        The function then waits for interrupt flag to be active and status
*        0x28 to be set.
*
*
* INPUT:
*	chan_num - TWSI channel
*       p_block - Data block.
*	block_size - number of chars in p_block.
*
* OUTPUT:
*       None.
*
* RETURN:
*       MV_OK - if transmiting the block completed successfully,
*	MV_BAD_PARAM - if p_block is NULL,
*	MV_FAIL otherwmise.
*
*******************************************************************************/
static int twsi_data_transmit(uint8_t chan_num,
			      uint8_t *p_block,
			      uint32_t block_size)
{
	uint32_t timeout, temp, block_size_wr = block_size;

	if (NULL == p_block)
		return MV_BAD_PARAM;

	/* wait for Int to be Set */
	timeout = 0;
	while (!twsi_main_int_get(chan_num) && (timeout++ < TWSI_TIMEOUT_VALUE))
		;

	/* check for timeout */
	if (MV_TRUE ==
	    twsi_timeout_chk(timeout,
			     (const char *)"TWSI: Read Data Int time_out.\n"))
		return MV_TIMEOUT;

	while (block_size_wr) {
		/* write the data */
		mrvl_reg_write(TWSI_DATA_REG(chan_num), (uint32_t)*p_block);
		DB(mv_os_printf(
		    "TWSI: twsi_data_transmit place = %d write %x\n",
		    block_size - block_size_wr, *p_block));
		p_block++;
		block_size_wr--;

		twsi_int_flg_clr(chan_num);

		/* wait for Int to be Set */
		timeout = 0;
		while (!twsi_main_int_get(chan_num) &&
		       (timeout++ < TWSI_TIMEOUT_VALUE))
			;

		/* check for timeout */
		if (MV_TRUE == twsi_timeout_chk(
				   timeout, (const char *)"TWSI: time_out.\n"))
			return MV_TIMEOUT;

		/* check the status */
		temp = twsi_sts_get(chan_num);
		if ((TWSI_M_LOST_ARB_DUR_AD_OR_DATA_TRA == temp) ||
		    (TWSI_M_LOST_ARB_DUR_AD_TRA_GNL_CALL_AD_REC_ACK_TRA ==
		     temp)) {
			DB(mv_os_printf("TWSI: Lost Arb, status %x\n", temp));
			return MV_RETRY;
		} else if (temp != TWSI_M_TRAN_DATA_BYTE_ACK_REC) {
			mv_os_printf("TWSI: status %x in write trans\n", temp);
			return MV_FAIL;
		}
	}

	return MV_OK;
}

/*******************************************************************************
* twsi_data_receive - Receive data block from TWSI bus.
*
* DESCRIPTION:
*       This function receive data block from TWSI bus in 8bit granularity
*       into p_block buffer.
*	first The function waits for interrupt flag to be active then
*       For each 8-bit data:
*        It clears the interrupt flag which allows the next data to be
*        received from TWSI bus.
*	 The function waits for interrupt flag to be active,
*	 and status reg is 0x50.
*	 Then the function reads data from data register, and copies it to
*	 the given buffer.
*
* INPUT:
*	chan_num - TWSI channel
*       block_size - number of bytes to read.
*
* OUTPUT:
*       p_block - Data block.
*
* RETURN:
*       MV_OK - if receive transaction completed successfully,
*	MV_BAD_PARAM - if p_block is NULL,
*	MV_FAIL otherwmise.
*
*******************************************************************************/
static int twsi_data_receive(uint8_t chan_num,
			     uint8_t *p_block,
			     uint32_t block_size)
{
	uint32_t timeout, temp, block_size_rd = block_size;

	if (NULL == p_block)
		return MV_BAD_PARAM;

	/* wait for Int to be Set */
	timeout = 0;
	while (!twsi_main_int_get(chan_num) && (timeout++ < TWSI_TIMEOUT_VALUE))
		;

	/* check for timeout */
	if (MV_TRUE ==
	    twsi_timeout_chk(timeout,
			     (const char *)"TWSI: Read Data int Time out .\n"))
		return MV_TIMEOUT;

	while (block_size_rd) {
		if (block_size_rd == 1)
			/* clear ack and Int flag */
			mrvl_reg_bit_reset(
				TWSI_CONTROL_REG(chan_num), TWSI_CONTROL_ACK);

		twsi_int_flg_clr(chan_num);
		/* wait for Int to be Set */
		timeout = 0;
		while ((!twsi_main_int_get(chan_num)) &&
		       (timeout++ < TWSI_TIMEOUT_VALUE))
			;

		/* check for timeout */
		if (MV_TRUE ==
		    twsi_timeout_chk(timeout, (const char *)"TWSI: Timeout.\n"))
			return MV_TIMEOUT;

		/* check the status */
		temp = twsi_sts_get(chan_num);
		if ((TWSI_M_LOST_ARB_DUR_AD_OR_DATA_TRA == temp) ||
		    (TWSI_M_LOST_ARB_DUR_AD_TRA_GNL_CALL_AD_REC_ACK_TRA ==
		     temp)) {
			DB(mv_os_printf("TWSI: Lost Arb, status %x\n", temp));
			return MV_RETRY;
		} else if ((temp != TWSI_M_REC_RD_DATA_ACK_TRA) &&
			   (block_size_rd != 1)) {
			mv_os_printf("TWSI: status %x in read trans\n", temp);
			return MV_FAIL;
		} else if ((temp != TWSI_M_REC_RD_DATA_ACK_NOT_TRA) &&
			   (block_size_rd == 1)) {
			mv_os_printf("TWSI: status %x in Rd Terminate\n", temp);
			return MV_FAIL;
		}

		/* read the data */
		*p_block = (uint8_t)mrvl_reg_read(TWSI_DATA_REG(chan_num));
		DB(mv_os_printf("TWSI: twsi_data_receive  place %d read %x\n",
				block_size - block_size_rd, *p_block));
		p_block++;
		block_size_rd--;
	}

	return MV_OK;
}

/*******************************************************************************
* twsi_target_offs_set - Set TWST target offset on TWSI bus.
*
* DESCRIPTION:
*       The function support TWSI targets that have inside address space (for
*       example EEPROMs). The function:
*       1) Convert the given offset into p_block and size.
*		in case the offset should be set to a TWSI slave which support
*		more then 256 bytes offset, the offset setting will be done
*		in 2 transactions.
*       2) Use twsi_data_transmit to place those on the bus.
*
* INPUT:
*	chan_num - TWSI channel
*       offset - offset to be set on the EEPROM device.
*	more_than256 - whether the EEPROM device support more then 256 byte
*offset.
*
* OUTPUT:
*       None.
*
* RETURN:
*       MV_OK - if setting the offset completed successfully.
*	MV_FAIL otherwmise.
*
*******************************************************************************/
static int twsi_target_offs_set(uint8_t chan_num,
				uint32_t offset,
				uint8_t more_than256)
{
	uint8_t off_block[2];
	uint32_t off_size;

	if (more_than256 == MV_TRUE) {
		off_block[0] = (offset >> 8) & 0xff;
		off_block[1] = offset & 0xff;
		off_size = 2;
	} else {
		off_block[0] = offset & 0xff;
		off_size = 1;
	}
	DB(mv_os_printf(
	    "TWSI: twsi_target_offs_set off_size = %x addr1 = %x addr2 = %x\n",
	    off_size, off_block[0], off_block[1]));
	return twsi_data_transmit(chan_num, off_block, off_size);
}

/*******************************************************************************
* mv_twsi_read - Read data block from a TWSI Slave.
*
* DESCRIPTION:
*       The function calls the following functions:
*       -) mv_twsi_start_bit_set();
*	if (EEPROM device)
*	-) mv_twsi_addr_set(w);
*	-) twsi_target_offs_set();
*	-) mv_twsi_start_bit_set();
*	-) mv_twsi_addr_set(r);
*	-) twsi_data_receive();
*	-) mv_twsi_stop_bit_set();
*
* INPUT:
*	chan_num - TWSI channel
*	p_twsi_slave - Twsi Slave structure.
*	block_size - number of bytes to read.
*
* OUTPUT:
*	p_block - Data block.
*
* RETURN:
*	MV_OK - if EEPROM read transaction completed successfully,
*	MV_BAD_PARAM - if p_block is NULL,
*	MV_FAIL otherwmise.
*
*******************************************************************************/
static int mv_twsi_read(uint8_t chan_num,
			MV_TWSI_SLAVE *p_twsi_slave,
			uint8_t *p_block,
			uint32_t block_size)
{
	int rc;
	int ret = MV_FAIL;
	uint32_t counter = 0;

	if ((NULL == p_block) || (NULL == p_twsi_slave))
		return MV_BAD_PARAM;

	do {
		/* wait for 1 mili sec for the clear to take effect */
		if (counter > 0)
			mdelay(1);
		ret = mv_twsi_start_bit_set(chan_num);

		if (MV_RETRY == ret)
			continue;
		else if (MV_OK != ret) {
			mv_twsi_stop_bit_set(chan_num);
			DB(mv_os_printf(
			    "mv_twsi_read:mv_twsi_start_bit_set failed\n"));
			return MV_FAIL;
		}

		DB(mv_os_printf(
		    "TWSI: mv_twsi_eeprom_read after mv_twsi_start_bit_set\n"));

		/* in case offset exsist (i.e. eeprom ) */
		if (MV_TRUE == p_twsi_slave->valid_offset) {
			rc = mv_twsi_addr_set(chan_num,
					      &(p_twsi_slave->slave_addr),
					      MV_TWSI_WRITE);
			if (MV_RETRY == rc)
				continue;
			else if (MV_OK != rc) {
				mv_twsi_stop_bit_set(chan_num);
				DB(mv_os_printf(
				    "mv_twsi_addr_set(%d,0x%x,%d) rc=%d\n",
				    chan_num,
				    (uint32_t) &(p_twsi_slave->slave_addr),
				    MV_TWSI_WRITE, rc));
				return MV_FAIL;
			}

			ret =
			    twsi_target_offs_set(chan_num, p_twsi_slave->offset,
						 p_twsi_slave->more_than256);
			if (MV_RETRY == ret)
				continue;
			else if (MV_OK != ret) {
				mv_twsi_stop_bit_set(chan_num);
				DB(mv_os_printf(
				    "TWSI: twsi_target_offs_set Failed\n"));
				return MV_FAIL;
			}
			DB(mv_os_printf("TWSI: after twsi_target_offs_set\n"));
			ret = mv_twsi_start_bit_set(chan_num);
			if (MV_RETRY == ret)
				continue;
			else if (MV_OK != ret) {
				mv_twsi_stop_bit_set(chan_num);
				DB(mv_os_printf(
				    "TWSI: mv_twsi_start_bit_set failed\n"));
				return MV_FAIL;
			}
			DB(mv_os_printf("TWSI: after mv_twsi_start_bit_set\n"));
		}
		ret = mv_twsi_addr_set(chan_num, &(p_twsi_slave->slave_addr),
				       MV_TWSI_READ);
		if (MV_RETRY == ret)
			continue;
		else if (MV_OK != ret) {
			mv_twsi_stop_bit_set(chan_num);
			DB(mv_os_printf(
			    "mv_twsi_read: mv_twsi_addr_set 2 Failed\n"));
			return MV_FAIL;
		}
		DB(mv_os_printf(
		    "TWSI: mv_twsi_eeprom_read after mv_twsi_addr_set\n"));

		ret = twsi_data_receive(chan_num, p_block, block_size);
		if (MV_RETRY == ret)
			continue;
		else if (MV_OK != ret) {
			mv_twsi_stop_bit_set(chan_num);
			DB(mv_os_printf(
			    "mv_twsi_read: twsi_data_receive Failed\n"));
			return MV_FAIL;
		}
		DB(mv_os_printf(
		    "TWSI: mv_twsi_eeprom_read after twsi_data_receive\n"));

		ret = mv_twsi_stop_bit_set(chan_num);
		if (MV_RETRY == ret)
			continue;
		else if (MV_OK != ret) {
			DB(mv_os_printf(
			    "mv_twsi_read: mv_twsi_stop_bit_set 3 Failed\n"));
			return MV_FAIL;
		}
		counter++;
	} while ((MV_RETRY == ret) && (counter < MAX_RETRY_CNT));

	if (counter == MAX_RETRY_CNT)
		DB(mv_os_printf("mv_twsi_write: Retry Expire\n"));

	twsi_ack_bit_set(chan_num);

	DB(mv_os_printf(
	    "TWSI: mv_twsi_eeprom_read after mv_twsi_stop_bit_set\n"));

	return MV_OK;
}

/*******************************************************************************
* mv_twsi_write - Write data block to a TWSI Slave.
*
* DESCRIPTION:
*       The function calls the following functions:
*       -) mv_twsi_start_bit_set();
*       -) mv_twsi_addr_set();
*	-)if (EEPROM device)
*	-) twsi_target_offs_set();
*       -) twsi_data_transmit();
*       -) mv_twsi_stop_bit_set();
*
* INPUT:
*	chan_num - TWSI channel
*	eeprom_address - eeprom address.
*       block_size - number of bytes to write.
*	p_block - Data block.
*
* OUTPUT:
*	None
*
* RETURN:
*       MV_OK - if EEPROM read transaction completed successfully.
*	MV_BAD_PARAM - if p_block is NULL,
*	MV_FAIL otherwmise.
*
* NOTE: Part of the EEPROM, required that the offset will be aligned to the
*	max write burst supported.
*******************************************************************************/
static int mv_twsi_write(uint8_t chan_num,
			 MV_TWSI_SLAVE *p_twsi_slave,
			 uint8_t *p_block,
			 uint32_t block_size)
{
	int ret = MV_FAIL;
	uint32_t counter = 0;

	if ((NULL == p_block) || (NULL == p_twsi_slave))
		return MV_BAD_PARAM;

	do {
		if (counter >
		    0) /* wait for 1 mili sec for the clear to take effect */
			mdelay(1);
		ret = mv_twsi_start_bit_set(chan_num);

		if (MV_RETRY == ret)
			continue;

		else if (MV_OK != ret) {
			mv_twsi_stop_bit_set(chan_num);
			DB(mv_os_printf(
			    "mv_twsi_write: mv_twsi_start_bit_set failed\n"));
			return MV_FAIL;
		}

		ret = mv_twsi_addr_set(chan_num, &(p_twsi_slave->slave_addr),
				       MV_TWSI_WRITE);
		if (MV_RETRY == ret)
			continue;
		else if (MV_OK != ret) {
			mv_twsi_stop_bit_set(chan_num);
			DB(mv_os_printf(
			    "mv_twsi_write: mv_twsi_addr_set failed\n"));
			return MV_FAIL;
		}

		/* in case offset exsist (i.e. eeprom ) */
		if (MV_TRUE == p_twsi_slave->valid_offset) {
			ret =
			    twsi_target_offs_set(chan_num, p_twsi_slave->offset,
						 p_twsi_slave->more_than256);
			if (MV_RETRY == ret)
				continue;
			else if (MV_OK != ret) {
				mv_twsi_stop_bit_set(chan_num);
				DB(mv_os_printf(
				    "TWSI: twsi_target_offs_set failed\n"));
				return MV_FAIL;
			}
		}

		ret = twsi_data_transmit(chan_num, p_block, block_size);
		if (MV_RETRY == ret)
			continue;
		else if (MV_OK != ret) {
			mv_twsi_stop_bit_set(chan_num);
			DB(mv_os_printf(
			    "mv_twsi_write: twsi_data_transmit failed\n"));
			return MV_FAIL;
		}
		ret = mv_twsi_stop_bit_set(chan_num);
		if (MV_RETRY == ret)
			continue;
		else if (MV_OK != ret) {
			DB(mv_os_printf(
			    "mv_twsi_write: failed to set stopbit\n"));
			return MV_FAIL;
		}
		counter++;
	} while ((MV_RETRY == ret) && (counter < MAX_RETRY_CNT));

	if (counter == MAX_RETRY_CNT)
		DB(mv_os_printf("mv_twsi_write: Retry Expire\n"));

	return MV_OK;
}

static int i2c_init(unsigned bus)
{
	if (bus >= MAX_I2C_NUM)
		return 1;

	if (!m_initialized[bus]) {
		/* TWSI init */
		MV_TWSI_ADDR slave;

		slave.type = ADDR7_BIT;
		slave.address = 0;
		mv_twsi_init(bus, TWSI_SPEED, mv_tclk_get(), &slave, 0);
		m_initialized[bus] = 1;
	}

	return 0;
}

static void i2c_reset(unsigned bus)
{
	if (bus < MAX_I2C_NUM)
		m_initialized[bus] = 0;
}

int platform_i2c_transfer(unsigned bus, struct i2c_seg *segments, int seg_count)
{
	struct i2c_seg *seg = segments;
	int ret = 0;
	MV_TWSI_SLAVE twsi_slave;

	if (i2c_init(bus))
		return 1;

	while (!ret && seg_count--) {
		twsi_slave.slave_addr.address = seg->chip;
		twsi_slave.slave_addr.type = ADDR7_BIT;
		twsi_slave.more_than256 = MV_FALSE;
		twsi_slave.valid_offset = MV_FALSE;
		if (seg->read)
			ret =
			    mv_twsi_read(bus, &twsi_slave, seg->buf, seg->len);
		else
			ret =
			    mv_twsi_write(bus, &twsi_slave, seg->buf, seg->len);
		seg++;
	}

	if (ret) {
		i2c_reset(bus);
		DB(mv_os_printf("mv_twsi_read/mv_twsi_write failed\n"));
		return 1;
	}

	return 0;
}