diff options
Diffstat (limited to 'chip/lm4/i2c.c')
-rw-r--r-- | chip/lm4/i2c.c | 413 |
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); |