summaryrefslogtreecommitdiff
path: root/chip/lm4/i2c.c
diff options
context:
space:
mode:
Diffstat (limited to 'chip/lm4/i2c.c')
-rw-r--r--chip/lm4/i2c.c413
1 files changed, 0 insertions, 413 deletions
diff --git a/chip/lm4/i2c.c b/chip/lm4/i2c.c
deleted file mode 100644
index 56084c38e6..0000000000
--- a/chip/lm4/i2c.c
+++ /dev/null
@@ -1,413 +0,0 @@
-/* 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 peripheral 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 peripheral 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 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_STRIP_FLAGS(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_bits(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 peripheral saw, so sleep
- * long enough that the peripheral will see the new
- * start condition below.
- */
- usleep(I2C_IDLE_US);
- }
-
- /* Set peripheral address for transmit */
- LM4_I2C_MSA(port) = (I2C_STRIP_FLAGS(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);
-
-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 controller, 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);
- }
-}
-
-/**
- * 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);
-}
-
-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);