413 lines
10 KiB
C
413 lines
10 KiB
C
|
/* Copyright 2013 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 Chrome EC */
|
||
|
|
||
|
#include "atomic.h"
|
||
|
#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 CPUTS(outstr) cputs(CC_I2C, outstr)
|
||
|
#define CPRINTS(format, args...) cprints(CC_I2C, format, ## args)
|
||
|
|
||
|
/* Flags for writes to MCS */
|
||
|
#define LM4_I2C_MCS_RUN BIT(0)
|
||
|
#define LM4_I2C_MCS_START BIT(1)
|
||
|
#define LM4_I2C_MCS_STOP BIT(2)
|
||
|
#define LM4_I2C_MCS_ACK BIT(3)
|
||
|
#define LM4_I2C_MCS_HS BIT(4)
|
||
|
#define LM4_I2C_MCS_QCMD BIT(5)
|
||
|
|
||
|
/* Flags for reads from MCS */
|
||
|
#define LM4_I2C_MCS_BUSY BIT(0)
|
||
|
#define LM4_I2C_MCS_ERROR BIT(1)
|
||
|
#define LM4_I2C_MCS_ADRACK BIT(2)
|
||
|
#define LM4_I2C_MCS_DATACK BIT(3)
|
||
|
#define LM4_I2C_MCS_ARBLST BIT(4)
|
||
|
#define LM4_I2C_MCS_IDLE BIT(5)
|
||
|
#define LM4_I2C_MCS_BUSBSY BIT(6)
|
||
|
#define LM4_I2C_MCS_CLKTO BIT(7)
|
||
|
|
||
|
/*
|
||
|
* Minimum delay between resetting the port or sending a stop condition, and
|
||
|
* when the port can be expected to be back in an idle state (and the slave
|
||
|
* has had long enough to see the start/stop condition edges).
|
||
|
*
|
||
|
* 500 us = 50 clocks at 100 KHz bus speed. This has been experimentally
|
||
|
* determined to be enough.
|
||
|
*/
|
||
|
#define I2C_IDLE_US 500
|
||
|
|
||
|
/* IRQ for each port */
|
||
|
static const uint32_t i2c_irqs[] = {LM4_IRQ_I2C0, LM4_IRQ_I2C1, LM4_IRQ_I2C2,
|
||
|
LM4_IRQ_I2C3, LM4_IRQ_I2C4, LM4_IRQ_I2C5};
|
||
|
BUILD_ASSERT(ARRAY_SIZE(i2c_irqs) == I2C_PORT_COUNT);
|
||
|
|
||
|
/* 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 idx; /* Index into input/output data */
|
||
|
int err; /* Error code, if any */
|
||
|
uint32_t timeout_us; /* Transaction timeout, or 0 to use default */
|
||
|
|
||
|
/* Task waiting on port, or TASK_ID_INVALID if none. */
|
||
|
volatile int task_waiting;
|
||
|
};
|
||
|
static struct i2c_port_data pdata[I2C_PORT_COUNT];
|
||
|
|
||
|
int i2c_is_busy(int port)
|
||
|
{
|
||
|
return LM4_I2C_MCS(port) & LM4_I2C_MCS_BUSBSY;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* I2C transfer engine.
|
||
|
*
|
||
|
* @return Zero when done with transfer (ready to wake task).
|
||
|
*
|
||
|
* MCS sequence on multi-byte write:
|
||
|
* 0x3 0x1 0x1 ... 0x1 0x5
|
||
|
* Single byte write:
|
||
|
* 0x7
|
||
|
*
|
||
|
* MCS receive sequence on multi-byte read:
|
||
|
* 0xb 0x9 0x9 ... 0x9 0x5
|
||
|
* Single byte read:
|
||
|
* 0x7
|
||
|
*/
|
||
|
int i2c_do_work(int port)
|
||
|
{
|
||
|
struct i2c_port_data *pd = pdata + port;
|
||
|
uint32_t reg_mcs = LM4_I2C_MCS_RUN;
|
||
|
|
||
|
if (pd->flags & I2C_XFER_START) {
|
||
|
/* Set start bit on first byte */
|
||
|
reg_mcs |= LM4_I2C_MCS_START;
|
||
|
pd->flags &= ~I2C_XFER_START;
|
||
|
} else if (LM4_I2C_MCS(port) & (LM4_I2C_MCS_CLKTO | LM4_I2C_MCS_ARBLST |
|
||
|
LM4_I2C_MCS_ERROR)) {
|
||
|
/*
|
||
|
* Error after starting; abort transfer. Ignore errors at
|
||
|
* start because arbitration and timeout errors are taken care
|
||
|
* of in chip_i2c_xfer(), and slave ack failures will
|
||
|
* automatically clear once we send a start condition.
|
||
|
*/
|
||
|
pd->err = EC_ERROR_UNKNOWN;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if (pd->out_size) {
|
||
|
/* Send next byte of output */
|
||
|
LM4_I2C_MDR(port) = *(pd->out++);
|
||
|
pd->idx++;
|
||
|
|
||
|
/* Handle starting to send last byte */
|
||
|
if (pd->idx == pd->out_size) {
|
||
|
|
||
|
/* Done with output after this */
|
||
|
pd->out_size = 0;
|
||
|
pd->idx = 0;
|
||
|
|
||
|
/* Resend start bit when changing direction */
|
||
|
pd->flags |= I2C_XFER_START;
|
||
|
|
||
|
/*
|
||
|
* Send stop bit after last byte if the stop flag is
|
||
|
* on, and caller doesn't expect to receive data.
|
||
|
*/
|
||
|
if ((pd->flags & I2C_XFER_STOP) && pd->in_size == 0)
|
||
|
reg_mcs |= LM4_I2C_MCS_STOP;
|
||
|
}
|
||
|
|
||
|
LM4_I2C_MCS(port) = reg_mcs;
|
||
|
return 1;
|
||
|
|
||
|
} else if (pd->in_size) {
|
||
|
if (pd->idx) {
|
||
|
/* Copy the byte we just read */
|
||
|
*(pd->in++) = LM4_I2C_MDR(port) & 0xff;
|
||
|
} else {
|
||
|
/* Starting receive; switch to receive address */
|
||
|
LM4_I2C_MSA(port) |= 0x01;
|
||
|
}
|
||
|
|
||
|
if (pd->idx < pd->in_size) {
|
||
|
/* More data to read */
|
||
|
pd->idx++;
|
||
|
|
||
|
/* ACK all bytes except the last one */
|
||
|
if ((pd->flags & I2C_XFER_STOP) &&
|
||
|
pd->idx == pd->in_size)
|
||
|
reg_mcs |= LM4_I2C_MCS_STOP;
|
||
|
else
|
||
|
reg_mcs |= LM4_I2C_MCS_ACK;
|
||
|
|
||
|
LM4_I2C_MCS(port) = reg_mcs;
|
||
|
return 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* If we're still here, done with transfer */
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int chip_i2c_xfer(const int port, const 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 reg_mcs = LM4_I2C_MCS(port);
|
||
|
int events = 0;
|
||
|
|
||
|
if (out_size == 0 && in_size == 0)
|
||
|
return EC_SUCCESS;
|
||
|
|
||
|
/* 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->idx = 0;
|
||
|
pd->err = 0;
|
||
|
|
||
|
/* Make sure we're in a good state to start */
|
||
|
if ((flags & I2C_XFER_START) &&
|
||
|
((reg_mcs & (LM4_I2C_MCS_CLKTO | LM4_I2C_MCS_ARBLST)) ||
|
||
|
(i2c_get_line_levels(port) != I2C_LINE_IDLE))) {
|
||
|
uint32_t tpr = LM4_I2C_MTPR(port);
|
||
|
|
||
|
CPRINTS("I2C%d Addr:%02X bad status 0x%02x, SCL=%d, SDA=%d",
|
||
|
port,
|
||
|
I2C_GET_ADDR(slave_addr_flags),
|
||
|
reg_mcs,
|
||
|
i2c_get_line_levels(port) & I2C_LINE_SCL_HIGH,
|
||
|
i2c_get_line_levels(port) & I2C_LINE_SDA_HIGH);
|
||
|
|
||
|
/* Attempt to unwedge the port. */
|
||
|
i2c_unwedge(port);
|
||
|
|
||
|
/* Clock timeout or arbitration lost. Reset port to clear. */
|
||
|
atomic_or(LM4_SYSTEM_SRI2C_ADDR, BIT(port));
|
||
|
clock_wait_cycles(3);
|
||
|
atomic_clear(LM4_SYSTEM_SRI2C_ADDR, BIT(port));
|
||
|
clock_wait_cycles(3);
|
||
|
|
||
|
/* Restore settings */
|
||
|
LM4_I2C_MCR(port) = 0x10;
|
||
|
LM4_I2C_MTPR(port) = tpr;
|
||
|
|
||
|
/*
|
||
|
* We don't know what edges the slave saw, so sleep long enough
|
||
|
* that the slave will see the new start condition below.
|
||
|
*/
|
||
|
usleep(I2C_IDLE_US);
|
||
|
}
|
||
|
|
||
|
/* Set slave address for transmit */
|
||
|
LM4_I2C_MSA(port) = (I2C_GET_ADDR(slave_addr_flags) << 1) & 0xff;
|
||
|
|
||
|
/* Enable interrupts */
|
||
|
pd->task_waiting = task_get_current();
|
||
|
LM4_I2C_MICR(port) = 0x03;
|
||
|
LM4_I2C_MIMR(port) = 0x03;
|
||
|
|
||
|
/* Kick the port interrupt handler to start the transfer */
|
||
|
task_trigger_irq(i2c_irqs[port]);
|
||
|
|
||
|
/* Wait for transfer complete or timeout */
|
||
|
events = task_wait_event_mask(TASK_EVENT_I2C_IDLE, pd->timeout_us);
|
||
|
|
||
|
/* Disable interrupts */
|
||
|
LM4_I2C_MIMR(port) = 0x00;
|
||
|
pd->task_waiting = TASK_ID_INVALID;
|
||
|
|
||
|
/* Handle timeout */
|
||
|
if (events & TASK_EVENT_TIMER)
|
||
|
pd->err = EC_ERROR_TIMEOUT;
|
||
|
|
||
|
if (pd->err) {
|
||
|
/* Force port back idle */
|
||
|
LM4_I2C_MCS(port) = LM4_I2C_MCS_STOP;
|
||
|
usleep(I2C_IDLE_US);
|
||
|
}
|
||
|
|
||
|
return pd->err;
|
||
|
}
|
||
|
|
||
|
int i2c_raw_get_scl(int port)
|
||
|
{
|
||
|
enum gpio_signal g;
|
||
|
int ret;
|
||
|
|
||
|
/* If no SCL pin defined for this port, then return 1 to appear idle. */
|
||
|
if (get_scl_from_i2c_port(port, &g) != EC_SUCCESS)
|
||
|
return 1;
|
||
|
|
||
|
/* If we are driving the pin low, it must be low. */
|
||
|
if (gpio_get_level(g) == 0)
|
||
|
return 0;
|
||
|
|
||
|
/*
|
||
|
* Otherwise, we need to toggle it to an input to read the true pin
|
||
|
* state.
|
||
|
*/
|
||
|
gpio_set_flags(g, GPIO_INPUT);
|
||
|
ret = gpio_get_level(g);
|
||
|
gpio_set_flags(g, GPIO_ODR_HIGH);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
int i2c_raw_get_sda(int port)
|
||
|
{
|
||
|
enum gpio_signal g;
|
||
|
int ret;
|
||
|
|
||
|
/* If no SDA pin defined for this port, then return 1 to appear idle. */
|
||
|
if (get_sda_from_i2c_port(port, &g) != EC_SUCCESS)
|
||
|
return 1;
|
||
|
|
||
|
/* If we are driving the pin low, it must be low. */
|
||
|
if (gpio_get_level(g) == 0)
|
||
|
return 0;
|
||
|
|
||
|
/*
|
||
|
* Otherwise, we need to toggle it to an input to read the true pin
|
||
|
* state.
|
||
|
*/
|
||
|
gpio_set_flags(g, GPIO_INPUT);
|
||
|
ret = gpio_get_level(g);
|
||
|
gpio_set_flags(g, GPIO_ODR_HIGH);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
int i2c_get_line_levels(int port)
|
||
|
{
|
||
|
/* Conveniently, MBMON bit BIT(1) is SDA and BIT(0) is SCL. */
|
||
|
return LM4_I2C_MBMON(port) & 0x03;
|
||
|
}
|
||
|
|
||
|
void i2c_set_timeout(int port, uint32_t timeout)
|
||
|
{
|
||
|
pdata[port].timeout_us = timeout ? timeout : I2C_TIMEOUT_DEFAULT_US;
|
||
|
}
|
||
|
|
||
|
/*****************************************************************************/
|
||
|
/* Hooks */
|
||
|
|
||
|
static void i2c_freq_changed(void)
|
||
|
{
|
||
|
int freq = clock_get_freq();
|
||
|
int i;
|
||
|
|
||
|
for (i = 0; i < i2c_ports_used; i++) {
|
||
|
/*
|
||
|
* From datasheet:
|
||
|
* SCL_PRD = 2 * (1 + TPR) * (SCL_LP + SCL_HP) * CLK_PRD
|
||
|
*
|
||
|
* so:
|
||
|
* TPR = SCL_PRD / (2 * (SCL_LP + SCL_HP) * CLK_PRD) - 1
|
||
|
*
|
||
|
* converting from period to frequency:
|
||
|
* TPR = CLK_FREQ / (SCL_FREQ * 2 * (SCL_LP + SCL_HP)) - 1
|
||
|
*/
|
||
|
const int d = 2 * (6 + 4) * (i2c_ports[i].kbps * 1000);
|
||
|
|
||
|
/* Round TPR up, so desired kbps is an upper bound */
|
||
|
const int tpr = (freq + d - 1) / d - 1;
|
||
|
|
||
|
#ifdef PRINT_I2C_SPEEDS
|
||
|
const int f = freq / (2 * (1 + tpr) * (6 + 4));
|
||
|
CPRINTS("I2C%d clk=%d tpr=%d freq=%d",
|
||
|
i2c_ports[i].port, freq, tpr, f);
|
||
|
#endif
|
||
|
|
||
|
LM4_I2C_MTPR(i2c_ports[i].port) = tpr;
|
||
|
}
|
||
|
}
|
||
|
DECLARE_HOOK(HOOK_FREQ_CHANGE, i2c_freq_changed, HOOK_PRIO_DEFAULT);
|
||
|
|
||
|
static void i2c_init(void)
|
||
|
{
|
||
|
uint32_t mask = 0;
|
||
|
int i;
|
||
|
|
||
|
/* Enable I2C modules in run and sleep modes. */
|
||
|
for (i = 0; i < i2c_ports_used; i++)
|
||
|
mask |= 1 << i2c_ports[i].port;
|
||
|
|
||
|
clock_enable_peripheral(CGC_OFFSET_I2C, mask,
|
||
|
CGC_MODE_RUN | CGC_MODE_SLEEP);
|
||
|
|
||
|
/* Configure GPIOs */
|
||
|
gpio_config_module(MODULE_I2C, 1);
|
||
|
|
||
|
/* Initialize ports as master, with interrupts enabled */
|
||
|
for (i = 0; i < i2c_ports_used; i++)
|
||
|
LM4_I2C_MCR(i2c_ports[i].port) = 0x10;
|
||
|
|
||
|
/* Set initial clock frequency */
|
||
|
i2c_freq_changed();
|
||
|
|
||
|
/* Enable IRQs; no tasks are waiting on ports */
|
||
|
for (i = 0; i < I2C_PORT_COUNT; i++) {
|
||
|
pdata[i].task_waiting = TASK_ID_INVALID;
|
||
|
task_enable_irq(i2c_irqs[i]);
|
||
|
|
||
|
/* Use default timeout */
|
||
|
i2c_set_timeout(i, 0);
|
||
|
}
|
||
|
}
|
||
|
DECLARE_HOOK(HOOK_INIT, i2c_init, HOOK_PRIO_INIT_I2C);
|
||
|
|
||
|
/**
|
||
|
* Handle an interrupt on the specified port.
|
||
|
*
|
||
|
* @param port I2C port generating interrupt
|
||
|
*/
|
||
|
static void handle_interrupt(int port)
|
||
|
{
|
||
|
int id = pdata[port].task_waiting;
|
||
|
|
||
|
/* Clear the interrupt status */
|
||
|
LM4_I2C_MICR(port) = LM4_I2C_MMIS(port);
|
||
|
|
||
|
/* 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_do_work(port))
|
||
|
task_set_event(id, TASK_EVENT_I2C_IDLE, 0);
|
||
|
}
|
||
|
|
||
|
void i2c0_interrupt(void) { handle_interrupt(0); }
|
||
|
void i2c1_interrupt(void) { handle_interrupt(1); }
|
||
|
void i2c2_interrupt(void) { handle_interrupt(2); }
|
||
|
void i2c3_interrupt(void) { handle_interrupt(3); }
|
||
|
void i2c4_interrupt(void) { handle_interrupt(4); }
|
||
|
void i2c5_interrupt(void) { handle_interrupt(5); }
|
||
|
|
||
|
DECLARE_IRQ(LM4_IRQ_I2C0, i2c0_interrupt, 2);
|
||
|
DECLARE_IRQ(LM4_IRQ_I2C1, i2c1_interrupt, 2);
|
||
|
DECLARE_IRQ(LM4_IRQ_I2C2, i2c2_interrupt, 2);
|
||
|
DECLARE_IRQ(LM4_IRQ_I2C3, i2c3_interrupt, 2);
|
||
|
DECLARE_IRQ(LM4_IRQ_I2C4, i2c4_interrupt, 2);
|
||
|
DECLARE_IRQ(LM4_IRQ_I2C5, i2c5_interrupt, 2);
|