summaryrefslogtreecommitdiff
path: root/chip
diff options
context:
space:
mode:
authorScott Worley <scott.worley@microchip.corp-partner.google.com>2017-12-21 14:36:03 -0500
committerchrome-bot <chrome-bot@chromium.org>2018-01-02 15:48:23 -0800
commitf4700dac616a3ac42acf34b149ab741f9f5bcdbc (patch)
tree4fed004951c60ac2a8832050edfbb938026028c8 /chip
parenta92647b9ce517fc28f924c2abc60dec3d1bdc98e (diff)
downloadchrome-ec-f4700dac616a3ac42acf34b149ab741f9f5bcdbc.tar.gz
ec_chip_mchp: Add I2C controller
Add Microchip MEC17xx family I2C controller. Controller supports master/slave but is used as master only. BRANCH=none BUG= TEST=Review only. Change-Id: I66c130943de6574a5f4acff56b88fca69bbb2663 Signed-off-by: Scott Worley <scott.worley@microchip.corp-partner.google.com> Reviewed-on: https://chromium-review.googlesource.com/840646 Commit-Ready: Randall Spangler <rspangler@chromium.org> Tested-by: Randall Spangler <rspangler@chromium.org> Reviewed-by: Randall Spangler <rspangler@chromium.org>
Diffstat (limited to 'chip')
-rw-r--r--chip/mchp/i2c.c612
1 files changed, 612 insertions, 0 deletions
diff --git a/chip/mchp/i2c.c b/chip/mchp/i2c.c
new file mode 100644
index 0000000000..055db7b2ef
--- /dev/null
+++ b/chip/mchp/i2c.c
@@ -0,0 +1,612 @@
+/* Copyright 2017 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 MCHP MEC
+ * TODO handle chip variants
+ */
+
+#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"
+#include "tfdp_chip.h"
+
+#define CPUTS(outstr) cputs(CC_I2C, outstr)
+#define CPRINTS(format, args...) cprints(CC_I2C, format, ## args)
+
+#define I2C_CLOCK 16000000 /* 16 MHz */
+
+/* Status */
+#define STS_NBB (1 << 0) /* Bus busy */
+#define STS_LAB (1 << 1) /* Arbitration lost */
+#define STS_LRB (1 << 3) /* Last received bit */
+#define STS_BER (1 << 4) /* Bus error */
+#define STS_PIN (1 << 7) /* Pending interrupt */
+
+/* Control */
+#define CTRL_ACK (1 << 0) /* Acknowledge */
+#define CTRL_STO (1 << 1) /* STOP */
+#define CTRL_STA (1 << 2) /* START */
+#define CTRL_ENI (1 << 3) /* Enable interrupt */
+#define CTRL_ESO (1 << 6) /* Enable serial output */
+#define CTRL_PIN (1 << 7) /* Pending interrupt not */
+
+/* Completion */
+#define COMP_IDLE (1 << 29) /* i2c bus is idle */
+#define COMP_RW_BITS_MASK 0x3C /* R/W bits mask */
+
+/* Maximum transfer of a SMBUS block transfer */
+#define SMBUS_MAX_BLOCK_SIZE 32
+
+/*
+ * Amount of time to blocking wait for i2c bus to finish. After this
+ * blocking timeout, if the bus is still not finished, then allow other
+ * tasks to run.
+ * Note: this is just long enough for a 400kHz bus to finish transmitting
+ * one byte assuming the bus isn't being held.
+ */
+#define I2C_WAIT_BLOCKING_TIMEOUT_US 25
+
+enum i2c_transaction_state {
+ /* Stop condition was sent in previous transaction */
+ I2C_TRANSACTION_STOPPED,
+ /* Stop condition was not sent in previous transaction */
+ I2C_TRANSACTION_OPEN,
+};
+
+/* I2C controller state data
+ * NOTE: I2C_CONTROLLER_COUNT is defined at board level.
+ */
+static struct {
+ /* Transaction timeout, or 0 to use default. */
+ uint32_t timeout_us;
+ /* Task waiting on port, or TASK_ID_INVALID if none. */
+ /*
+ * MCHP Remove volatile.
+ * ISR only reads.
+ * Non-ISR only writes when interrupt is disabled.
+ */
+ task_id_t task_waiting;
+ enum i2c_transaction_state transaction_state;
+} cdata[I2C_CONTROLLER_COUNT];
+
+/*
+ * Map port number to port name in datasheet, for debug prints.
+ * Refer to registers.h
+ * MCHP_I2C0_n defines
+ */
+static const char __attribute__((unused))
+*i2c_port_names[MCHP_I2C_PORT_COUNT] = {
+ "0_0",
+ "N/A", /* MCHP I2C Port 1 pins not present */
+ "0_2",
+ "1_3",
+ "2_4",
+ "3_5",
+};
+
+static const uint16_t i2c_controller_pcr[MCHP_I2C_CTRL_MAX] = {
+ MCHP_PCR_I2C0,
+ MCHP_PCR_I2C1,
+ MCHP_PCR_I2C2,
+ MCHP_PCR_I2C3
+};
+
+static void i2c_ctrl_slp_en(int controller, int sleep_en)
+{
+ if ((controller < 0) || (controller > MCHP_I2C_CTRL_MAX))
+ return;
+
+ if (sleep_en)
+ MCHP_PCR_SLP_EN_DEV(i2c_controller_pcr[controller]);
+ else
+ MCHP_PCR_SLP_DIS_DEV(i2c_controller_pcr[controller]);
+}
+
+static void configure_controller_speed(int controller, int kbps)
+{
+ int t_low, t_high;
+ const int period = I2C_CLOCK / 1000 / kbps;
+
+ /* Clear PCR sleep enable for controller */
+ i2c_ctrl_slp_en(controller, 0);
+
+ /*
+ * Refer to NXP UM10204 for minimum timing requirement of T_Low and
+ * T_High.
+ * http://www.nxp.com/documents/user_manual/UM10204.pdf
+ */
+ if (kbps > 400) {
+ /* Fast mode plus */
+ t_low = t_high = period / 2 - 1;
+ MCHP_I2C_DATA_TIM(controller) = 0x06060601;
+ MCHP_I2C_DATA_TIM_2(controller) = 0x06;
+ } else if (kbps > 100) {
+ /* Fast mode */
+ /* By spec, clk low period is 1.3us min */
+ t_low = MAX((int)(I2C_CLOCK * 1.3 / 1000000), period / 2 - 1);
+ t_high = period - t_low - 2;
+ MCHP_I2C_DATA_TIM(controller) = 0x040a0a01;
+ MCHP_I2C_DATA_TIM_2(controller) = 0x0a;
+ } else {
+ /* Standard mode */
+ t_low = t_high = period / 2 - 1;
+ MCHP_I2C_DATA_TIM(controller) = 0x0c4d5006;
+ MCHP_I2C_DATA_TIM_2(controller) = 0x4d;
+ }
+
+ /* Clock periods is one greater than the contents of these fields */
+ MCHP_I2C_BUS_CLK(controller) = ((t_high & 0xff) << 8) |
+ (t_low & 0xff);
+}
+
+/*
+ * NOTE: direct mode interrupts do not need GIRQn bit
+ * set in aggregator block enable register.
+ */
+static void enable_controller_irq(int controller)
+{
+ MCHP_INT_ENABLE(MCHP_I2C_GIRQ) =
+ MCHP_I2C_GIRQ_BIT(controller);
+ task_enable_irq(MCHP_IRQ_I2C_0 + controller);
+}
+
+static void disable_controller_irq(int controller)
+{
+ MCHP_INT_DISABLE(MCHP_I2C_GIRQ) =
+ MCHP_I2C_GIRQ_BIT(controller);
+ task_disable_irq(MCHP_IRQ_I2C_0 + controller);
+}
+
+static void configure_controller(int controller, int port, int kbps)
+{
+ uint32_t cfg;
+
+ /* update port select field b[3:0] */
+ cfg = MCHP_I2C_CONFIG(controller) & ~0xf;
+ MCHP_I2C_CONFIG(controller) = cfg + (port & 0xf);
+
+ MCHP_I2C_CTRL(controller) = CTRL_PIN;
+ MCHP_I2C_OWN_ADDR(controller) = board_i2c_slave_addrs(controller);
+ configure_controller_speed(controller, kbps);
+ MCHP_I2C_CTRL(controller) = CTRL_PIN | CTRL_ESO |
+ CTRL_ACK | CTRL_ENI;
+ MCHP_I2C_CONFIG(controller) |= 1 << 10; /* ENAB */
+
+ /* Enable interrupt */
+ MCHP_I2C_CONFIG(controller) |= 1 << 29; /* ENIDI */
+ enable_controller_irq(controller);
+}
+
+static void reset_controller(int controller)
+{
+ int i;
+
+ MCHP_I2C_CONFIG(controller) |= 1 << 9;
+ udelay(100);
+ MCHP_I2C_CONFIG(controller) &= ~(1 << 9);
+
+ for (i = 0; i < i2c_ports_used; ++i)
+ if (controller == i2c_port_to_controller(i2c_ports[i].port)) {
+ configure_controller(controller, i2c_ports[i].port,
+ i2c_ports[i].kbps);
+ cdata[controller].transaction_state =
+ I2C_TRANSACTION_STOPPED;
+ break;
+ }
+}
+
+static int wait_for_interrupt(int controller, int timeout)
+{
+ int event;
+
+ if (timeout <= 0)
+ return EC_ERROR_TIMEOUT;
+
+ cdata[controller].task_waiting = task_get_current();
+ enable_controller_irq(controller);
+
+ /* Wait until I2C interrupt or timeout. */
+ event = task_wait_event_mask(TASK_EVENT_I2C_IDLE, timeout);
+
+ disable_controller_irq(controller);
+ cdata[controller].task_waiting = TASK_ID_INVALID;
+
+ return (event & TASK_EVENT_TIMER) ? EC_ERROR_TIMEOUT : EC_SUCCESS;
+}
+
+static int wait_idle(int controller)
+{
+ uint8_t sts = MCHP_I2C_STATUS(controller);
+ uint64_t block_timeout = get_time().val + I2C_WAIT_BLOCKING_TIMEOUT_US;
+ uint64_t task_timeout = block_timeout + cdata[controller].timeout_us;
+ int rv = 0;
+
+ while (!(sts & STS_NBB)) {
+ if (rv)
+ return rv;
+ if (get_time().val > block_timeout)
+ rv = wait_for_interrupt(controller,
+ task_timeout - get_time().val);
+ sts = MCHP_I2C_STATUS(controller);
+ }
+
+ if (sts & (STS_BER | STS_LAB))
+ return EC_ERROR_UNKNOWN;
+ return EC_SUCCESS;
+}
+
+static int wait_byte_done(int controller)
+{
+ uint8_t sts = MCHP_I2C_STATUS(controller);
+ uint64_t block_timeout = get_time().val + I2C_WAIT_BLOCKING_TIMEOUT_US;
+ uint64_t task_timeout = block_timeout + cdata[controller].timeout_us;
+ int rv = 0;
+
+ while (sts & STS_PIN) {
+ if (rv)
+ return rv;
+ if (get_time().val > block_timeout)
+ rv = wait_for_interrupt(controller,
+ task_timeout - get_time().val);
+ sts = MCHP_I2C_STATUS(controller);
+ }
+
+ return sts & STS_LRB;
+}
+
+/*
+ * MEC1701H allows mapping any I2C port to any controller.
+ * port is a zero based number. Call i2c_port_to_controller
+ * to look up controller the specified port.
+ */
+static void select_port(int port)
+{
+ uint32_t port_sel = (uint32_t)(port & 0x0f);
+ int controller = i2c_port_to_controller(port);
+
+ uint32_t cfg = MCHP_I2C_CONFIG(controller) & ~0xf;
+
+ MCHP_I2C_CONFIG(controller) = cfg | port_sel;
+
+}
+
+static inline int get_line_level(int controller)
+{
+ int ret, ctrl;
+ /*
+ * We need to enable BB (Bit Bang) mode in order to read line level
+ * properly, otherwise line levels return always idle (0x60).
+ */
+ ctrl = MCHP_I2C_BB_CTRL(controller);
+ MCHP_I2C_BB_CTRL(controller) |= 1;
+ ret = (MCHP_I2C_BB_CTRL(controller) >> 5) & 0x3;
+ MCHP_I2C_BB_CTRL(controller) = ctrl;
+ return ret;
+}
+
+static inline void push_in_buf(uint8_t **in, uint8_t val, int skip)
+{
+ if (!skip) {
+ **in = val;
+ (*in)++;
+ }
+}
+
+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;
+ int controller;
+ int send_start = flags & I2C_XFER_START;
+ int send_stop = flags & I2C_XFER_STOP;
+ int skip = 0;
+ int bytes_to_read;
+ uint8_t reg;
+ int ret_done;
+
+ if (out_size == 0 && in_size == 0)
+ return EC_SUCCESS;
+
+ select_port(port);
+ controller = i2c_port_to_controller(port);
+
+ if (send_start &&
+ cdata[controller].transaction_state == I2C_TRANSACTION_STOPPED)
+ wait_idle(controller);
+
+ reg = MCHP_I2C_STATUS(controller);
+ if (send_start &&
+ cdata[controller].transaction_state == I2C_TRANSACTION_STOPPED &&
+ (((reg & (STS_BER | STS_LAB)) || !(reg & STS_NBB)) ||
+ (get_line_level(controller)
+ != I2C_LINE_IDLE))) {
+ CPRINTS("MEC1701 i2c%s bad status 0x%02x, SCL=%d, SDA=%d",
+ i2c_port_names[port], reg,
+ get_line_level(controller) & I2C_LINE_SCL_HIGH,
+ get_line_level(controller) & I2C_LINE_SDA_HIGH);
+
+ /* Attempt to unwedge the port. */
+ i2c_unwedge(port);
+
+ /* Bus error, bus busy, or arbitration lost. Try reset. */
+ reset_controller(controller);
+ select_port(port);
+
+ /*
+ * We don't know what edges the slave saw, so sleep long enough
+ * that the slave will see the new start condition below.
+ */
+ usleep(1000);
+ }
+
+ if (out_size) {
+ if (send_start) {
+ MCHP_I2C_DATA(controller) = (uint8_t)slave_addr;
+
+ /* Clock out the slave address, sending START bit */
+ MCHP_I2C_CTRL(controller) = CTRL_PIN | CTRL_ESO |
+ CTRL_ENI | CTRL_ACK |
+ CTRL_STA;
+ cdata[controller].transaction_state =
+ I2C_TRANSACTION_OPEN;
+ }
+
+ for (i = 0; i < out_size; ++i) {
+ ret_done = wait_byte_done(controller);
+ if (ret_done)
+ goto err_chip_i2c_xfer;
+
+ MCHP_I2C_DATA(controller) = out[i];
+ }
+ ret_done = wait_byte_done(controller);
+ if (ret_done)
+ goto err_chip_i2c_xfer;
+
+ /*
+ * Send STOP bit if the stop flag is on, and caller
+ * doesn't expect to receive data.
+ */
+ if (send_stop && in_size == 0) {
+ MCHP_I2C_CTRL(controller) = CTRL_PIN | CTRL_ESO |
+ CTRL_STO | CTRL_ACK;
+ cdata[controller].transaction_state =
+ I2C_TRANSACTION_STOPPED;
+ }
+ }
+
+ if (in_size) {
+ /* Resend start bit when changing direction */
+ if (out_size || send_start) {
+ /* Repeated start case */
+ if (cdata[controller].transaction_state ==
+ I2C_TRANSACTION_OPEN)
+ MCHP_I2C_CTRL(controller) = CTRL_ESO |
+ CTRL_STA |
+ CTRL_ACK |
+ CTRL_ENI;
+
+ MCHP_I2C_DATA(controller) = (uint8_t)slave_addr
+ | 0x01;
+
+ /* New transaction case, clock out slave address. */
+ if (cdata[controller].transaction_state ==
+ I2C_TRANSACTION_STOPPED)
+ MCHP_I2C_CTRL(controller) = CTRL_ESO |
+ CTRL_STA |
+ CTRL_ACK |
+ CTRL_ENI |
+ CTRL_PIN;
+
+ cdata[controller].transaction_state =
+ I2C_TRANSACTION_OPEN;
+
+ /* Skip over the dummy byte */
+ skip = 1;
+ in_size++;
+ }
+
+ /* Special flags need to be set for last two bytes */
+ bytes_to_read = send_stop ? in_size - 2 : in_size;
+
+ for (i = 0; i < bytes_to_read; ++i) {
+ ret_done = wait_byte_done(controller);
+ if (ret_done)
+ goto err_chip_i2c_xfer;
+
+ push_in_buf(&in, MCHP_I2C_DATA(controller), skip);
+ skip = 0;
+ }
+ ret_done = wait_byte_done(controller);
+ if (ret_done)
+ goto err_chip_i2c_xfer;
+
+ if (send_stop) {
+ /*
+ * De-assert ACK bit before reading the next to last
+ * byte, so that the last byte is NACK'ed.
+ */
+ MCHP_I2C_CTRL(controller) = CTRL_ESO | CTRL_ENI;
+ push_in_buf(&in, MCHP_I2C_DATA(controller), skip);
+ ret_done = wait_byte_done(controller);
+ if (ret_done)
+ goto err_chip_i2c_xfer;
+
+ /* Send STOP */
+ MCHP_I2C_CTRL(controller) =
+ CTRL_PIN | CTRL_ESO | CTRL_ACK | CTRL_STO;
+
+ cdata[controller].transaction_state =
+ I2C_TRANSACTION_STOPPED;
+
+ /*
+ * We need to know our stop point two bytes in
+ * advance. If we don't know soon enough, we need
+ * to do an extra dummy read (to last_addr + 1) to
+ * issue the stop.
+ */
+ push_in_buf(&in, MCHP_I2C_DATA(controller),
+ in_size == 1);
+ }
+ }
+
+ /* Check for error conditions */
+ if (MCHP_I2C_STATUS(controller) & (STS_LAB | STS_BER))
+ return EC_ERROR_UNKNOWN;
+
+ return EC_SUCCESS;
+err_chip_i2c_xfer:
+ /* Send STOP and return error */
+ MCHP_I2C_CTRL(controller) = CTRL_PIN | CTRL_ESO |
+ CTRL_STO | CTRL_ACK;
+ cdata[controller].transaction_state = I2C_TRANSACTION_STOPPED;
+ if (ret_done == STS_LRB) {
+ return EC_ERROR_BUSY;
+ }
+ else if (ret_done == EC_ERROR_TIMEOUT) {
+ /*
+ * If our transaction timed out then our i2c controller
+ * may be wedged without showing any other outward signs
+ * of failure. Reset the controller so that future
+ * transactions have a chance of success.
+ */
+ reset_controller(controller);
+ return EC_ERROR_TIMEOUT;
+ }
+ else {
+ return EC_ERROR_UNKNOWN;
+ }
+}
+
+int i2c_raw_get_scl(int port)
+{
+ enum gpio_signal g;
+
+ /* 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;
+
+ return gpio_get_level(g);
+}
+
+int i2c_raw_get_sda(int port)
+{
+ enum gpio_signal g;
+
+ /* 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;
+
+ return gpio_get_level(g);
+}
+
+/*
+ * Caller is responsible for locking the port.
+ */
+int i2c_get_line_levels(int port)
+{
+ int rv;
+
+ select_port(port);
+ rv = get_line_level(i2c_port_to_controller(port));
+ return rv;
+}
+
+/*
+ * I2C port must be a zero based number.
+ * MCHP I2C can map any port to any of the 4 controllers.
+ * Call board level function as board designs may choose
+ * to wire up and group ports differently.
+ */
+int i2c_port_to_controller(int port)
+{
+ return board_i2c_p2c(port);
+}
+
+void i2c_set_timeout(int port, uint32_t timeout)
+{
+ /* Param is port, but timeout is stored by-controller. */
+ cdata[i2c_port_to_controller(port)].timeout_us =
+ timeout ? timeout : I2C_TIMEOUT_DEFAULT_US;
+}
+
+/*
+ * Initialize I2C controllers specified by the board configuration.
+ * If multiple ports are mapped to the same controller choose the
+ * lowest speed.
+ */
+static void i2c_init(void)
+{
+ int i;
+ int controller, kbps;
+ int controller_kbps[MCHP_I2C_CTRL_MAX];
+
+ for (i = 0; i < MCHP_I2C_CTRL_MAX; i++)
+ controller_kbps[i] = 0;
+
+ /* Configure GPIOs */
+ gpio_config_module(MODULE_I2C, 1);
+
+ for (i = 0; i < i2c_ports_used; ++i) {
+ controller = i2c_port_to_controller(i2c_ports[i].port);
+
+ kbps = i2c_ports[i].kbps;
+ if (controller_kbps[controller] &&
+ (controller_kbps[controller] != kbps)) {
+ CPRINTS("i2c_init(): controller %d"
+ " speed conflict: %d != %d",
+ controller, kbps,
+ controller_kbps[controller]);
+ kbps = MIN(kbps, controller_kbps[controller]);
+ }
+ controller_kbps[controller] = kbps;
+
+ configure_controller(controller, i2c_ports[i].port,
+ controller_kbps[controller]);
+
+ cdata[controller].task_waiting = TASK_ID_INVALID;
+ cdata[controller].transaction_state = I2C_TRANSACTION_STOPPED;
+
+ /* Use default timeout. */
+ i2c_set_timeout(i2c_ports[i].port, 0);
+ }
+}
+DECLARE_HOOK(HOOK_INIT, i2c_init, HOOK_PRIO_INIT_I2C);
+
+static void handle_interrupt(int controller)
+{
+ int id = cdata[controller].task_waiting;
+
+ /* Clear the interrupt status */
+ MCHP_I2C_COMPLETE(controller) &= (COMP_RW_BITS_MASK | COMP_IDLE);
+
+ MCHP_INT_SOURCE(MCHP_I2C_GIRQ) = MCHP_I2C_GIRQ_BIT(controller);
+
+ /*
+ * Write to control register interferes with I2C transaction.
+ * Instead, let's disable IRQ from the core until the next time
+ * we want to wait for STS_PIN/STS_NBB.
+ */
+ disable_controller_irq(controller);
+
+ /* Wake up the task which was waiting on the I2C interrupt, if any. */
+ if (id != TASK_ID_INVALID)
+ 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); }
+
+DECLARE_IRQ(MCHP_IRQ_I2C_0, i2c0_interrupt, 2);
+DECLARE_IRQ(MCHP_IRQ_I2C_1, i2c1_interrupt, 2);
+DECLARE_IRQ(MCHP_IRQ_I2C_2, i2c2_interrupt, 2);
+DECLARE_IRQ(MCHP_IRQ_I2C_3, i2c3_interrupt, 2);