mirror of
https://github.com/Telecominfraproject/OpenCellular.git
synced 2025-12-31 02:51:26 +00:00
BUG=None BRANCH=None TEST=Tested on Soraka ISH modified board, measured I2C speed for std, fast, fast plus mode. Change-Id: I0e07c3c73f5f0302fba41ad8e7f83e10e8f0af99 Signed-off-by: li feng <li1.feng@intel.com> Reviewed-on: https://chromium-review.googlesource.com/817899 Commit-Ready: Li1 Feng <li1.feng@intel.com> Tested-by: Li1 Feng <li1.feng@intel.com> Reviewed-by: Shawn N <shawnn@chromium.org>
460 lines
12 KiB
C
460 lines
12 KiB
C
/* Copyright 2016 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 port module for ISH */
|
|
|
|
#include "common.h"
|
|
#include "console.h"
|
|
#include "gpio.h"
|
|
#include "hooks.h"
|
|
#include "i2c.h"
|
|
#include "registers.h"
|
|
#include "ish_i2c.h"
|
|
#include "task.h"
|
|
#include "timer.h"
|
|
#include "hwtimer.h"
|
|
#include "util.h"
|
|
|
|
#define CPUTS(outstr) cputs(CC_I2C, outstr)
|
|
#define CPRINTS(format, args...) cprints(CC_I2C, format, ## args)
|
|
#define CPRINTF(format, args...) cprintf(CC_I2C, format, ## args)
|
|
|
|
#define I2C_FLAG_REPEATED_START_DISABLED 0
|
|
#define EVENT_FLAG_I2C_TIMEOUT TASK_EVENT_CUSTOM(1 << 1)
|
|
|
|
/*25MHz, 50MHz, 100MHz, 120MHz, 40MHz, 20MHz, 37MHz*/
|
|
static uint16_t default_hcnt_scl_100[] = {
|
|
4000, 4420, 4920, 4400, 4000, 4000, 4300
|
|
};
|
|
|
|
static uint16_t default_lcnt_scl_100[] = {
|
|
4720, 5180, 4990, 5333, 4700, 5200, 4950
|
|
};
|
|
|
|
static uint16_t default_hcnt_scl_400[] = {
|
|
600, 820, 1120, 1066, 600, 600, 450
|
|
};
|
|
|
|
static uint16_t default_lcnt_scl_400[] = {
|
|
1320, 1380, 1300, 1300, 1300, 1200, 1250
|
|
};
|
|
|
|
static uint16_t default_hcnt_scl_1000[] = {
|
|
260, 260, 260, 260, 260, 260, 260
|
|
};
|
|
|
|
static uint16_t default_lcnt_scl_1000[] = {
|
|
500, 500, 500, 500, 500, 500, 500
|
|
};
|
|
|
|
static uint16_t default_hcnt_scl_hs[] = { 160, 300, 160, 166, 175, 150, 162 };
|
|
static uint16_t default_lcnt_scl_hs[] = { 320, 340, 320, 325, 325, 300, 297 };
|
|
|
|
static uint8_t speed_val_arr[] = {
|
|
STD_SPEED_VAL, FAST_SPEED_VAL, FAST_PLUS_SPEED_VAL, HIGH_SPEED_VAL};
|
|
|
|
static uint8_t bus_freq[ISH_I2C_PORT_COUNT] = {
|
|
I2C_FREQ_120, I2C_FREQ_120, I2C_FREQ_120
|
|
};
|
|
|
|
static struct i2c_context i2c_ctxs[ISH_I2C_PORT_COUNT] = {
|
|
{
|
|
.bus = 0,
|
|
.base = (uint32_t *) ISH_I2C0_BASE,
|
|
.speed = I2C_SPEED_FAST,
|
|
.int_pin = ISH_I2C0_IRQ,
|
|
},
|
|
{
|
|
.bus = 1,
|
|
.base = (uint32_t *) ISH_I2C1_BASE,
|
|
.speed = I2C_SPEED_FAST,
|
|
.int_pin = ISH_I2C1_IRQ,
|
|
},
|
|
{
|
|
.bus = 2,
|
|
.base = (uint32_t *) ISH_I2C2_BASE,
|
|
.speed = I2C_SPEED_FAST,
|
|
.int_pin = ISH_I2C2_IRQ,
|
|
},
|
|
};
|
|
|
|
static struct i2c_bus_info board_config[ISH_I2C_PORT_COUNT] = {
|
|
{
|
|
.bus_id = 0,
|
|
.std_speed.sda_hold = DEFAULT_SDA_HOLD,
|
|
.fast_speed.sda_hold = DEFAULT_SDA_HOLD,
|
|
.fast_plus_speed.sda_hold = DEFAULT_SDA_HOLD,
|
|
.high_speed.sda_hold = DEFAULT_SDA_HOLD,
|
|
},
|
|
{
|
|
.bus_id = 1,
|
|
.std_speed.sda_hold = DEFAULT_SDA_HOLD,
|
|
.fast_speed.sda_hold = DEFAULT_SDA_HOLD,
|
|
.fast_plus_speed.sda_hold = DEFAULT_SDA_HOLD,
|
|
.high_speed.sda_hold = DEFAULT_SDA_HOLD,
|
|
},
|
|
{
|
|
.bus_id = 2,
|
|
.std_speed.sda_hold = DEFAULT_SDA_HOLD,
|
|
.fast_speed.sda_hold = DEFAULT_SDA_HOLD,
|
|
.fast_plus_speed.sda_hold = DEFAULT_SDA_HOLD,
|
|
.high_speed.sda_hold = DEFAULT_SDA_HOLD,
|
|
},
|
|
};
|
|
|
|
static inline void i2c_mmio_write(uint32_t *base, uint8_t offset,
|
|
uint32_t data)
|
|
{
|
|
REG32((uint32_t) ((uint8_t *)base + offset)) = data;
|
|
}
|
|
|
|
static inline uint32_t i2c_mmio_read(uint32_t *base, uint8_t offset)
|
|
{
|
|
return REG32((uint32_t) ((uint8_t *)base + offset));
|
|
}
|
|
|
|
static inline uint8_t i2c_read_byte(uint32_t *addr, uint8_t reg,
|
|
uint8_t offset)
|
|
{
|
|
uint32_t ret = i2c_mmio_read(addr, reg) >> offset;
|
|
|
|
return ret & 0xff;
|
|
}
|
|
|
|
static void i2c_intr_switch(uint32_t *base, int mode)
|
|
{
|
|
switch (mode) {
|
|
|
|
case ENABLE_WRITE_INT:
|
|
i2c_mmio_write(base, IC_INTR_MASK, IC_INTR_WRITE_MASK_VAL);
|
|
break;
|
|
|
|
case ENABLE_READ_INT:
|
|
i2c_mmio_write(base, IC_INTR_MASK, IC_INTR_READ_MASK_VAL);
|
|
break;
|
|
|
|
case DISABLE_INT:
|
|
i2c_mmio_write(base, IC_INTR_MASK, 0);
|
|
/* clear interrupts: TX_ABORT
|
|
* Because the DW_apb_i2c's TX FIFO is forced into a
|
|
* flushed/reset state whenever a TX_ABRT event occurs, it
|
|
* is necessary for software to release the DW_apb_i2c from
|
|
* this state by reading the IC_CLR_TX_ABRT register before
|
|
* attempting to write into the TX FIFO
|
|
*/
|
|
i2c_mmio_read(base, IC_CLR_TX_ABRT);
|
|
/* STOP_DET */
|
|
i2c_mmio_read(base, IC_CLR_STOP_DET);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void i2c_init_transaction(struct i2c_context *ctx,
|
|
uint8_t slave_addr, uint8_t flags)
|
|
{
|
|
uint32_t con_value;
|
|
uint32_t *base = ctx->base;
|
|
struct i2c_bus_info *bus_info = &board_config[ctx->bus];
|
|
uint32_t clk_in_val = clk_in[bus_freq[ctx->bus]];
|
|
|
|
/* Convert 8-bit slave addrees to 7-bit for driver expectation*/
|
|
slave_addr >>= 1;
|
|
|
|
/* disable interrupts */
|
|
i2c_intr_switch(base, DISABLE_INT);
|
|
|
|
i2c_mmio_write(base, IC_ENABLE, IC_ENABLE_DISABLE);
|
|
i2c_mmio_write(base, IC_TAR, (slave_addr << IC_TAR_OFFSET) |
|
|
TAR_SPECIAL_VAL | IC_10BITADDR_MASTER_VAL);
|
|
|
|
/* set Clock SCL Count */
|
|
switch (ctx->speed) {
|
|
|
|
case I2C_SPEED_STD:
|
|
i2c_mmio_write(base, IC_SS_SCL_HCNT,
|
|
NS_2_COUNTERS(bus_info->std_speed.hcnt,
|
|
clk_in_val));
|
|
i2c_mmio_write(base, IC_SS_SCL_LCNT,
|
|
NS_2_COUNTERS(bus_info->std_speed.lcnt,
|
|
clk_in_val));
|
|
i2c_mmio_write(base, IC_SDA_HOLD,
|
|
NS_2_COUNTERS(bus_info->std_speed.sda_hold,
|
|
clk_in_val));
|
|
break;
|
|
|
|
case I2C_SPEED_FAST:
|
|
i2c_mmio_write(base, IC_FS_SCL_HCNT,
|
|
NS_2_COUNTERS(bus_info->fast_speed.hcnt,
|
|
clk_in_val));
|
|
i2c_mmio_write(base, IC_FS_SCL_LCNT,
|
|
NS_2_COUNTERS(bus_info->fast_speed.lcnt,
|
|
clk_in_val));
|
|
i2c_mmio_write(base, IC_SDA_HOLD,
|
|
NS_2_COUNTERS(bus_info->fast_speed.sda_hold,
|
|
clk_in_val));
|
|
break;
|
|
|
|
|
|
case I2C_SPEED_FAST_PLUS:
|
|
i2c_mmio_write(base, IC_FS_SCL_HCNT,
|
|
NS_2_COUNTERS(bus_info->fast_plus_speed.hcnt,
|
|
clk_in_val));
|
|
i2c_mmio_write(base, IC_FS_SCL_LCNT,
|
|
NS_2_COUNTERS(bus_info->fast_plus_speed.lcnt,
|
|
clk_in_val));
|
|
i2c_mmio_write(base, IC_SDA_HOLD,
|
|
NS_2_COUNTERS(bus_info->fast_plus_speed.sda_hold,
|
|
clk_in_val));
|
|
break;
|
|
|
|
case I2C_SPEED_HIGH:
|
|
i2c_mmio_write(base, IC_HS_SCL_HCNT,
|
|
NS_2_COUNTERS(bus_info->high_speed.hcnt,
|
|
clk_in_val));
|
|
i2c_mmio_write(base, IC_HS_SCL_LCNT,
|
|
NS_2_COUNTERS(bus_info->high_speed.lcnt,
|
|
clk_in_val));
|
|
i2c_mmio_write(base, IC_SDA_HOLD,
|
|
NS_2_COUNTERS(bus_info->high_speed.sda_hold,
|
|
clk_in_val));
|
|
|
|
i2c_mmio_write(base, IC_FS_SCL_HCNT,
|
|
NS_2_COUNTERS(bus_info->fast_speed.hcnt,
|
|
clk_in_val));
|
|
i2c_mmio_write(base, IC_FS_SCL_LCNT,
|
|
NS_2_COUNTERS(bus_info->fast_speed.lcnt,
|
|
clk_in_val));
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* in SPT HW we need to sync between I2C clock and data signals */
|
|
con_value = i2c_mmio_read(base, IC_CON);
|
|
|
|
if (flags & I2C_FLAG_REPEATED_START_DISABLED)
|
|
con_value &= ~IC_RESTART_EN_VAL;
|
|
else
|
|
con_value |= IC_RESTART_EN_VAL;
|
|
|
|
i2c_mmio_write(base, IC_CON, con_value);
|
|
i2c_mmio_write(base, IC_FS_SPKLEN, spkln[bus_freq[ctx->bus]]);
|
|
i2c_mmio_write(base, IC_HS_SPKLEN, spkln[bus_freq[ctx->bus]]);
|
|
i2c_mmio_write(base, IC_ENABLE, IC_ENABLE_ENABLE);
|
|
}
|
|
|
|
static void i2c_write_buffer(uint32_t *base, uint8_t len,
|
|
const uint8_t *buffer, ssize_t *cur_index,
|
|
ssize_t total_len)
|
|
{
|
|
int i;
|
|
uint16_t out;
|
|
|
|
for (i = 0; i < len; i++) {
|
|
|
|
++(*cur_index);
|
|
out = (buffer[i] << DATA_CMD_DAT_OFFSET) | DATA_CMD_WRITE_VAL;
|
|
|
|
if (*cur_index == total_len)
|
|
out |= DATA_CMD_STOP_VAL;
|
|
|
|
i2c_mmio_write(base, IC_DATA_CMD, out);
|
|
}
|
|
}
|
|
|
|
static void i2c_write_read_commands(uint32_t *base, uint8_t len)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < len - 1; i++)
|
|
i2c_mmio_write(base, IC_DATA_CMD, DATA_CMD_READ_VAL);
|
|
|
|
i2c_mmio_write(base, IC_DATA_CMD,
|
|
DATA_CMD_READ_VAL | DATA_CMD_STOP_VAL);
|
|
}
|
|
|
|
int chip_i2c_xfer(int port, int slave_addr, const uint8_t *out, int out_size,
|
|
uint8_t *in, int in_size, int flags)
|
|
{
|
|
int i, is_read = 0;
|
|
ssize_t total_len;
|
|
uint64_t expire_ts;
|
|
struct i2c_context *ctx;
|
|
ssize_t curr_index = 0;
|
|
|
|
if (out_size == 0 && in_size == 0)
|
|
return EC_SUCCESS;
|
|
|
|
if (in_size > 0)
|
|
is_read = 1;
|
|
|
|
ctx = &i2c_ctxs[port];
|
|
ctx->error_flag = 0;
|
|
|
|
total_len = is_read ? (1 + in_size) : out_size;
|
|
|
|
i2c_init_transaction(ctx, slave_addr, flags);
|
|
|
|
/* Write device id */
|
|
i2c_write_buffer(ctx->base, 1, out, &curr_index, total_len);
|
|
|
|
/* Write W data */
|
|
i2c_write_buffer(ctx->base, (is_read ? 0 : out_size - 1),
|
|
(is_read ? NULL : out + 1),
|
|
&curr_index, total_len);
|
|
|
|
if (is_read) {
|
|
/* Write R commands */
|
|
i2c_write_read_commands(ctx->base, in_size);
|
|
|
|
/* Set rx_theshold */
|
|
i2c_mmio_write(ctx->base, IC_RX_TL, in_size - 1);
|
|
}
|
|
|
|
/* Enable interrupts */
|
|
i2c_intr_switch(ctx->base,
|
|
is_read ? ENABLE_READ_INT : ENABLE_WRITE_INT);
|
|
|
|
/* Wait for interrupt */
|
|
ctx->wait_task_id = task_get_current();
|
|
task_wait_event_mask(EVENT_FLAG_I2C_TIMEOUT, -1);
|
|
|
|
if ((ctx->interrupts & M_TX_ABRT) == 0) {
|
|
if (is_read) {
|
|
/* read data */
|
|
for (i = 0; i < in_size; i++)
|
|
in[i] = i2c_read_byte(ctx->base,
|
|
IC_DATA_CMD, 0);
|
|
}
|
|
|
|
} else {
|
|
ctx->error_flag = 1;
|
|
}
|
|
|
|
ctx->reason = 0;
|
|
ctx->interrupts = 0;
|
|
|
|
/* do not disable device before master is idle */
|
|
expire_ts = __hw_clock_source_read() + I2C_TSC_TIMEOUT;
|
|
|
|
while (i2c_mmio_read(ctx->base, IC_STATUS) &
|
|
(1 << IC_STATUS_MASTER_ACTIVITY)) {
|
|
|
|
if (__hw_clock_source_read() >= expire_ts) {
|
|
ctx->error_flag = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
i2c_mmio_write(ctx->base, IC_ENABLE, IC_ENABLE_DISABLE);
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
static void i2c_interrupt_handler(struct i2c_context *ctx)
|
|
{
|
|
/* check interrupts */
|
|
ctx->interrupts = i2c_mmio_read(ctx->base, IC_INTR_STAT);
|
|
ctx->reason = (uint16_t) i2c_mmio_read(ctx->base, IC_TX_ABRT_SOURCE);
|
|
|
|
/* disable interrupts */
|
|
i2c_intr_switch(ctx->base, DISABLE_INT);
|
|
task_set_event(ctx->wait_task_id, EVENT_FLAG_I2C_TIMEOUT, 0);
|
|
}
|
|
|
|
static void i2c_isr_bus0(void)
|
|
{
|
|
i2c_interrupt_handler(&i2c_ctxs[0]);
|
|
}
|
|
DECLARE_IRQ(ISH_I2C0_IRQ, i2c_isr_bus0);
|
|
|
|
static void i2c_isr_bus1(void)
|
|
{
|
|
i2c_interrupt_handler(&i2c_ctxs[1]);
|
|
}
|
|
DECLARE_IRQ(ISH_I2C1_IRQ, i2c_isr_bus1);
|
|
|
|
static void i2c_isr_bus2(void)
|
|
{
|
|
i2c_interrupt_handler(&i2c_ctxs[2]);
|
|
}
|
|
DECLARE_IRQ(ISH_I2C2_IRQ, i2c_isr_bus2);
|
|
|
|
static void i2c_config_speed(struct i2c_context *ctx, int kbps)
|
|
{
|
|
|
|
if (kbps > 1000)
|
|
ctx->speed = I2C_SPEED_HIGH;
|
|
else if (kbps > 400)
|
|
ctx->speed = I2C_SPEED_FAST_PLUS;
|
|
else if (kbps > 100)
|
|
ctx->speed = I2C_SPEED_FAST;
|
|
else
|
|
ctx->speed = I2C_SPEED_STD;
|
|
|
|
}
|
|
|
|
static void i2c_init_hardware(struct i2c_context *ctx)
|
|
{
|
|
uint32_t *base = ctx->base;
|
|
|
|
/* disable interrupts */
|
|
i2c_intr_switch(base, DISABLE_INT);
|
|
i2c_mmio_write(base, IC_ENABLE, IC_ENABLE_DISABLE);
|
|
i2c_mmio_write(base, IC_CON, (MASTER_MODE_VAL
|
|
| speed_val_arr[ctx->speed]
|
|
| IC_RESTART_EN_VAL
|
|
| IC_SLAVE_DISABLE_VAL));
|
|
|
|
i2c_mmio_write(base, IC_FS_SPKLEN, spkln[bus_freq[ctx->bus]]);
|
|
i2c_mmio_write(base, IC_HS_SPKLEN, spkln[bus_freq[ctx->bus]]);
|
|
|
|
/* get RX_FIFO and TX_FIFO depth */
|
|
ctx->max_rx_depth = i2c_read_byte(base, IC_COMP_PARAM_1,
|
|
RX_BUFFER_DEPTH_OFFSET) + 1;
|
|
ctx->max_tx_depth = i2c_read_byte(base, IC_COMP_PARAM_1,
|
|
TX_BUFFER_DEPTH_OFFSET) + 1;
|
|
}
|
|
|
|
static void i2c_initial_board_config(struct i2c_context *ctx)
|
|
{
|
|
uint8_t freq = bus_freq[ctx->bus];
|
|
struct i2c_bus_info *bus_info = &board_config[ctx->bus];
|
|
|
|
bus_info->std_speed.hcnt = default_hcnt_scl_100[freq];
|
|
bus_info->std_speed.lcnt = default_lcnt_scl_100[freq];
|
|
|
|
bus_info->fast_speed.hcnt = default_hcnt_scl_400[freq];
|
|
bus_info->fast_speed.lcnt = default_lcnt_scl_400[freq];
|
|
|
|
bus_info->fast_plus_speed.hcnt = default_hcnt_scl_1000[freq];
|
|
bus_info->fast_plus_speed.lcnt = default_lcnt_scl_1000[freq];
|
|
|
|
bus_info->high_speed.hcnt = default_hcnt_scl_hs[freq];
|
|
bus_info->high_speed.lcnt = default_lcnt_scl_hs[freq];
|
|
}
|
|
|
|
static void i2c_init(void)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < i2c_ports_used; i++) {
|
|
int port = i2c_ports[i].port;
|
|
i2c_initial_board_config(&i2c_ctxs[port]);
|
|
/* Config speed from i2c_ports[] defined in board.c */
|
|
i2c_config_speed(&i2c_ctxs[port], i2c_ports[i].kbps);
|
|
i2c_init_hardware(&i2c_ctxs[port]);
|
|
|
|
task_enable_irq((&i2c_ctxs[port])->int_pin);
|
|
}
|
|
|
|
CPRINTS("Done i2c_init");
|
|
}
|
|
DECLARE_HOOK(HOOK_INIT, i2c_init, HOOK_PRIO_INIT_I2C);
|