coreboot-libre-fam15h-rdimm/3rdparty/chromeec/chip/lm4/i2c.c

413 lines
10 KiB
C
Raw Permalink Normal View History

2024-03-04 11:14:53 +01:00
/* 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);