blob: 723d3b4b1dc1a52e8923f59a36111bf0ef5fbe23 [file] [log] [blame]
/*
* 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;
}