summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--chip/g/build.mk1
-rw-r--r--chip/g/config_chip.h3
-rw-r--r--chip/g/i2cm.c482
-rw-r--r--include/i2c.h6
4 files changed, 492 insertions, 0 deletions
diff --git a/chip/g/build.mk b/chip/g/build.mk
index 095b1790aa..df028da65b 100644
--- a/chip/g/build.mk
+++ b/chip/g/build.mk
@@ -62,6 +62,7 @@ chip-$(CONFIG_RDD)+=rdd.o
chip-$(CONFIG_RBOX)+=rbox.o
chip-$(CONFIG_STREAM_USB)+=usb-stream.o
chip-$(CONFIG_STREAM_USART)+=usart.o
+chip-$(CONFIG_I2C_MASTER)+= i2cm.o
chip-$(CONFIG_I2C_SLAVE)+= i2cs.o
chip-$(CONFIG_LOW_POWER_IDLE)+=idle.o
diff --git a/chip/g/config_chip.h b/chip/g/config_chip.h
index ed909fc3e5..ad5f95cf4d 100644
--- a/chip/g/config_chip.h
+++ b/chip/g/config_chip.h
@@ -140,4 +140,7 @@
*/
#define CONFIG_CUSTOMIZED_RO
+/* Number of I2C ports */
+#define I2C_PORT_COUNT 2
+
#endif /* __CROS_EC_CONFIG_CHIP_H */
diff --git a/chip/g/i2cm.c b/chip/g/i2cm.c
new file mode 100644
index 0000000000..e63c4a7014
--- /dev/null
+++ b/chip/g/i2cm.c
@@ -0,0 +1,482 @@
+/* Copyright 2016 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.
+ */
+
+/*
+ * This is a driver for the I2C Master controller (i2cm) of the g chip.
+ *
+ * The g chip i2cm module supports 3 modes of operation, disabled, bit-banging,
+ * and instruction based. These modes are selected via the I2C_CTRL
+ * register. Selecting disabled mode can be used as a soft reset where the i2cm
+ * hw state machine is reset, but the register values remain unchanged. In
+ * bit-banging mode the signals SDA/SCL are controlled by the lower two bits of
+ * the INST register. I2C_INST[1:0] = SCL|SDA. In this mode the value of SDA is
+ * read every clock cycle.
+ *
+ * The main operation mode is instruction mode. A 32 bit instruction register
+ * (I2C_INST) is used to describe a sequence of operations. The I2C transaction
+ * is initiated when this register is written. The I2C module contains a status
+ * register which in real-time tracks the progress of the I2C sequence that was
+ * configured in the INST register. If enabled, an interrupt is generated when
+ * the transaction is completed. If not using interrupts then bit 24 (INTB) of
+ * the status register can be polled for 0. INTB is the inverse of the i2cm
+ * interrupt status.
+ *
+ * The i2cm module provides a 64 byte fifo (RWBYTES) for both write and read
+ * transactions. In addition there is a 4 byte fifo (FWBYTES) that can be used
+ * for writes, for the register write of portion of a read transaction. By
+ * default the pointer to RWBYTES fifo resets back 0 following each
+ * transaction.
+ *
+ * As mentioned, i2c transactions are configured via the I2C_INST register.
+ * A 2 byte register write would create the following bitmap to define the
+ * compound instruction for the transaction:
+ *
+ * I2C_INST_START = 1 -> send start bit
+ * I2C_INST_FWDEVADDR = 1 -> first send the slave device address
+ * I2C_INST_FWBYTESCOUNT = 3 -> 3 bytes in FWBYTES (register + 16 bit value)
+ * I2C_INST_FINALSTOP = 1 -> send stop bit
+ * I2C_INST_DEVADDRVAL = slave address
+ *
+ * I2C_FWBYTES[b7:b0] = out[0] -> register address
+ * I2C_FWBYTES[b15:b8] = out[1] -> first byte of value
+ * I2C_FWBYTES[b23:b16] = out[2] -> 2nd byte of value
+ *
+ * A 2 byte register read would create the following bitmap to define the
+ * compound instruction for the transaction:
+ *
+ * I2C_INST_START = 1 -> send start bit
+ * I2C_INST_FWDEVADDR = 1 -> first send the slave device address
+ * I2C_INST_FWBYTESCOUNT = 1 -> 1 byte in FWBYTES (register address)
+ * I2C_INST_REPEATEDSTART = 1 -> send start bit following write
+ * I2C_INST_RWDEVADDR = 1 -> send slave address in read mode
+ * I2C_INST_RWDEVADDR_RWB = 1 -> read bytes followin slave address
+ * I2C_INST_FINALNA = 1 -> ACK read bytes, NACK last byte read
+ * I2C_INST_FINALSTOP = 1 -> send stop bit
+ * I2C_INST_DEVADDRVAL = slave address
+ * I2C_FWBYTES[b7:b0] = out[0] -> register address byte
+ *
+ * Once trasnaction is complete:
+ * in[0] = I2C_RW0[b7:b0] -> copy first byte of read into destination
+ * in[1] = I2C_RW0[b15:b8] -> copy 2nd byte of read into destination
+ *
+ * Once the register I2C_INST is written with the instruction words constructed
+ * as shown, the transaction on the bus will commence. When I2C_INST is written,
+ * I2C_STATUS[b23:b0] is updated to reflect the transaction
+ * details and I2C_STATUS[b24] is set to 1. The transaction is complete when
+ * I2C_STATUS[b24] is 0. If interrupts are enabled, then an interrupt would be
+ * generated at this same point. The values of I2C_STATUS[b23:b0] are updated as
+ * the transaction progresses. Upon a completion of a successful transaction
+ * I2C_STATUS will be 0. If there was an error, the error details are contained
+ * in the upper bits of of I2C_STATUS, specifically [b31:b25].
+ */
+
+#include "common.h"
+#include "console.h"
+#include "gpio.h"
+#include "hooks.h"
+#include "i2c.h"
+#include "pmu.h"
+#include "registers.h"
+#include "system.h"
+#include "timer.h"
+
+#define CPRINTS(format, args...) cprints(CC_I2C, format, ## args)
+
+/*
+ * Limits for polling I2C transaction. The time limit of 25 msec is a
+ * conservative value for the worst case (68 byte transfer) at 100 kHz clock
+ * speed.
+ */
+#define I2CM_POLL_WAIT_US 25
+#define I2CM_MAX_POLL_ITERATIONS (25000 / I2CM_POLL_WAIT_US)
+
+/* Sizes for first write (FW) and read/write (RW) fifos */
+#define I2CM_FW_BYTES_MAX 4
+#define I2CM_RW_BYTES_MAX 64
+
+/* Macros to set bits/fields of the INST word for sequences*/
+#define INST_START GFIELD_MASK(I2C, INST, START)
+#define INST_STOP GFIELD_MASK(I2C, INST, FINALSTOP)
+#define INST_RPT_START GFIELD_MASK(I2C, INST, REPEATEDSTART)
+#define INST_FWDEVADDR GFIELD_MASK(I2C, INST, FWDEVADDR)
+#define INST_DEVADDRVAL(addr) (addr << GFIELD_LSB(I2C, INST, \
+ DEVADDRVAL))
+#define INST_RWDEVADDR GFIELD_MASK(I2C, INST, RWDEVADDR)
+#define INST_RWDEVADDR_RWB GFIELD_MASK(I2C, INST, RWDEVADDR_RWB)
+#define INST_NA GFIELD_MASK(I2C, INST, FINALNA)
+#define INST_RWBYTES(size) (size << GFIELD_LSB(I2C, INST, \
+ RWBYTESCOUNT))
+
+/* Mask for b31:INTB of STATUS register */
+#define I2CM_ERROR_MASK (~((1 << GFIELD_LSB(I2C, STATUS, INTB)) - 1))
+
+enum i2cm_control_mode {
+ i2c_mode_disabled = 0,
+ i2c_mode_bit_bang = 1,
+ i2c_mode_instruction = 2,
+ i2c_mode_reserved = 3,
+};
+
+#define I2C_NUM_PHASESTEPS 4
+struct i2c_xfer_mode {
+ uint8_t clk_div;
+ uint8_t phase_steps[I2C_NUM_PHASESTEPS];
+};
+
+/*
+ * TODO (crosbug.com/p/58355): For 100 and 400 kHz speed, phasestep0 has been
+ * adjusted longer that what should be required due to slow rise times on both
+ * Reef and Gru boards. In addition, the suggested values from the H1 chip spec
+ * were based off a 26 MHz clock. Have an ask to get suggested values for the
+ * actual 24 MHz bus speed.
+ */
+const struct i2c_xfer_mode i2c_timing[I2C_FREQ_COUNT] = {
+ /* 1000 kHz */
+ {
+ .clk_div = 1,
+ .phase_steps = {5, 5, 5, 11},
+ },
+ /* 400 kHz */
+ {
+ .clk_div = 1,
+ .phase_steps = {15, 12, 12, 21},
+ },
+ /* 100 kHz */
+ {
+ .clk_div = 10,
+ .phase_steps = {9, 6, 5, 4},
+ },
+};
+
+static void i2cm_config_xfer_mode(int port, enum i2c_freq freq)
+{
+ /* Set the control mode to disabled (soft reset) */
+ GWRITE_I(I2C, port, CTRL_MODE, i2c_mode_disabled);
+
+ /* Set the phasesteps register for the requested bus frequency */
+ GWRITE_FIELD_I(I2C, port, CTRL_PHASESTEPS, P0,
+ i2c_timing[freq].phase_steps[0]);
+ GWRITE_FIELD_I(I2C, port, CTRL_PHASESTEPS, P1,
+ i2c_timing[freq].phase_steps[1]);
+ GWRITE_FIELD_I(I2C, port, CTRL_PHASESTEPS, P2,
+ i2c_timing[freq].phase_steps[2]);
+ GWRITE_FIELD_I(I2C, port, CTRL_PHASESTEPS, P3,
+ i2c_timing[freq].phase_steps[3]);
+
+ /* Set the clock divide control register */
+ GWRITE_I(I2C, port, CTRL_CLKDIV, i2c_timing[freq].clk_div);
+ /* Ensure that INST register is reset */
+ GWRITE_I(I2C, port, INST, 0);
+ /* Set the control mode register to instruction */
+ GWRITE_I(I2C, port, CTRL_MODE, i2c_mode_instruction);
+}
+
+static void i2cm_set_fwbytes(int port, uint32_t *inst, const uint8_t *data,
+ int size)
+{
+ int i;
+ uint32_t fwbytes = 0;
+
+ /* Indicate that first write bytes field will be used */
+ *inst |= size << GFIELD_LSB(I2C, INST, FWBYTESCOUNT);
+
+ /* Now write data to FWBYTES register */
+ for (i = 0; i < size; i++)
+ fwbytes |= data[i] << (i * 8);
+ GWRITE_I(I2C, port, FW, fwbytes);
+}
+
+static void i2cm_write_rwbytes(int port, const uint8_t *out, int size)
+{
+ volatile uint32_t *rw_ptr;
+ int rw_count;
+ int i;
+
+ /* Calculate number of RW register writes required */
+ rw_count = (size + 3) >> 2;
+ /* Get pointer to RW0 register (start of fifo) */
+ rw_ptr = GREG32_ADDR_I(I2C, port, RW0);
+
+ /*
+ * Get write data from source buffer one byte at a time and write up to
+ * 4 bytes at a time in to the RW fifo.
+ */
+ for (i = 0; i < rw_count; i++) {
+ int byte_count;
+ int j;
+ uint32_t rw_data = 0;
+
+ byte_count = MIN(4, size);
+ for (j = 0; j < byte_count; j++)
+ rw_data |= *out++ << (j * 8);
+ size -= byte_count;
+ *rw_ptr++ = rw_data;
+ }
+}
+
+static void i2cm_read_rwbytes(int port, uint8_t *in, int size)
+{
+ int rw_count;
+ int i;
+ volatile uint32_t *rw_ptr;
+
+ /* Calculate number of RW register writes required */
+ rw_count = (size + 3) >> 2;
+ /* Get pointer to RW0 register (start of fifo) */
+ rw_ptr = GREG32_ADDR_I(I2C, port, RW0);
+
+ /*
+ * Read data from fifo up to 4 bytes at a time and copy into
+ * destination buffer 1 byte at a time.
+ */
+ for (i = 0; i < rw_count; i++) {
+ int byte_count;
+ int j;
+ uint32_t rw_data;
+
+ rw_data = *rw_ptr++;
+ byte_count = MIN(4, size);
+ for (j = 0; j < byte_count; j++) {
+ *in++ = rw_data;
+ rw_data >>= 8;
+ }
+ size -= byte_count;
+ }
+}
+
+static int i2cm_poll_for_complete(int port)
+{
+ int poll_count = 0;
+
+ while (poll_count < I2CM_MAX_POLL_ITERATIONS) {
+ /* Check if the sequence is complete */
+ if (!GREAD_FIELD_I(I2C, port, STATUS, INTB))
+ return EC_SUCCESS;
+ /* Not done yet, sleep */
+ usleep(I2CM_POLL_WAIT_US);
+ poll_count++;
+ };
+
+ return EC_ERROR_TIMEOUT;
+}
+
+static uint32_t i2cm_build_sequence(int port, int slave_addr,
+ const uint8_t *out, int out_size,
+ uint8_t *in, int in_size, int flags)
+{
+ int bytes_consumed;
+ uint32_t inst = 0;
+
+ if (flags & I2C_XFER_START)
+ inst |= INST_START;
+
+ /* Setup slave device address */
+ inst |= INST_DEVADDRVAL(slave_addr);
+
+ if (out_size) {
+ /* Send slave addr byte if this is start of I2C transaction */
+ if (flags & I2C_XFER_START)
+ inst |= INST_FWDEVADDR;
+ bytes_consumed = MIN(I2CM_FW_BYTES_MAX, out_size);
+ /* Setup first write bytes */
+ i2cm_set_fwbytes(port, &inst, out, bytes_consumed);
+ out_size -= bytes_consumed;
+ /* If write data remains, then put the rest in RW fifo */
+ if (out_size) {
+ out += bytes_consumed;
+ inst |= INST_RWBYTES(out_size);
+ i2cm_write_rwbytes(port, out, out_size);
+ }
+ }
+
+ if (in_size) {
+ /*
+ * If I2C_XFER_START is marked, then send slave address and
+ * indicate it's a read transaction.
+ */
+ if (flags & I2C_XFER_START) {
+ inst |= INST_RWDEVADDR;
+ inst |= INST_RWDEVADDR_RWB;
+ inst |= INST_RPT_START;
+ }
+ /* Setup number of bytes to read */
+ inst |= INST_RWBYTES(in_size);
+
+ /* NACK the last byte read */
+ if (flags & I2C_XFER_STOP)
+ inst |= INST_NA;
+ }
+
+ if (flags & I2C_XFER_STOP)
+ inst |= INST_STOP;
+
+ return inst;
+}
+
+static int i2cm_execute_sequence(int port, int slave_addr, const uint8_t *out,
+ int out_size, uint8_t *in, int in_size,
+ int flags)
+{
+ int rv;
+ uint32_t inst;
+
+ /* Build sequence instruciton */
+ inst = i2cm_build_sequence(port, slave_addr, out, out_size, in,
+ in_size, flags);
+ /* Start transaction */
+ GWRITE_I(I2C, port, INST, inst);
+
+ /* Wait for transaction to be complete */
+ rv = i2cm_poll_for_complete(port);
+ /* Handle timeout case */
+ if (rv)
+ return rv;
+
+ /* Check status value for errors */
+ if (GREAD_I(I2C, port, STATUS) & I2CM_ERROR_MASK) {
+ /* If failed, then clear INST register */
+ GWRITE_I(I2C, port, INST, 0);
+ return EC_ERROR_UNKNOWN;
+ }
+
+ return EC_SUCCESS;
+}
+
+
+/* Perform an i2c transaction. */
+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 rv;
+ int sequence_flags;
+ int num_out, num_in;
+
+ if (!in_size && !out_size)
+ /* Nothing to do */
+ return EC_SUCCESS;
+
+ /*
+ * Cr50 can do sequences of up to 64 write or read bytes. In addition it
+ * can accommodate up to 4 write bytes and up to 64 read bytes in a
+ * sequence set up. However, if the number of write bytes is > 4, then
+ * the write and read must be done in separate sequences.
+ */
+
+ while (out_size > I2CM_FW_BYTES_MAX) {
+ /* number of bytes that can handed in 1 sequence */
+ num_out = MIN(I2CM_RW_BYTES_MAX + I2CM_FW_BYTES_MAX, out_size);
+ sequence_flags = flags;
+ /* If more than 1 sequence remaining, mask stop bit flag */
+ if ((out_size - num_out) || in_size)
+ sequence_flags &= ~I2C_XFER_STOP;
+ /* Execute transaction */
+ rv = i2cm_execute_sequence(port, slave_addr, out, num_out, in,
+ 0, sequence_flags);
+ if (rv)
+ return rv;
+ /* Update counts and flags */
+ out += num_out;
+ out_size -= num_out;
+ flags &= ~sequence_flags;
+ }
+
+ /* At this point out_size <= 4 */
+ while (out_size || in_size) {
+ num_in = MIN(I2CM_RW_BYTES_MAX, in_size);
+ num_out = out_size;
+ sequence_flags = flags;
+ /* If more than 1 sequence remaining, mask stop bit flag */
+ if (in_size - num_in)
+ sequence_flags &= ~I2C_XFER_STOP;
+
+ rv = i2cm_execute_sequence(port, slave_addr, out, num_out, in,
+ num_in, sequence_flags);
+ if (rv)
+ return rv;
+
+ /* If bytes were read, copy to destination buffer */
+ if (num_in) {
+ i2cm_read_rwbytes(port, in, num_in);
+ in += num_in;
+ in_size -= num_in;
+ }
+ out_size = 0;
+ flags &= ~sequence_flags;
+ }
+
+ return EC_SUCCESS;
+}
+
+int i2c_raw_get_scl(int port)
+{
+ enum gpio_signal pin;
+
+ if (get_scl_from_i2c_port(port, &pin) == EC_SUCCESS)
+ return gpio_get_level(pin);
+
+ /* If no SCL pin defined for this port, then return 1 to appear idle. */
+ return 1;
+}
+
+int i2c_raw_get_sda(int port)
+{
+ enum gpio_signal pin;
+
+ if (get_sda_from_i2c_port(port, &pin) == EC_SUCCESS)
+ return gpio_get_level(pin);
+
+ /* If no SDA pin defined for this port, then return 1 to appear idle. */
+ return 1;
+}
+
+int i2c_get_line_levels(int port)
+{
+ return (i2c_raw_get_sda(port) ? I2C_LINE_SDA_HIGH : 0) |
+ (i2c_raw_get_scl(port) ? I2C_LINE_SCL_HIGH : 0);
+
+}
+
+static void i2cm_init_port(const struct i2c_port_t *p)
+{
+ enum i2c_freq freq;
+
+ /* Enable clock for I2C Master */
+ pmu_clock_en(p->port ? PERIPH_I2C1 : PERIPH_I2C0);
+
+ /* Set operation speed. */
+ switch (p->kbps) {
+ case 1000: /* Fast-mode Plus */
+ freq = I2C_FREQ_1000KHZ;
+ break;
+ case 400: /* Fast-mode */
+ freq = I2C_FREQ_400KHZ;
+ break;
+ case 100: /* Standard-mode */
+ freq = I2C_FREQ_100KHZ;
+ break;
+ default: /* unknown speed, default to 100kBps */
+ CPRINTS("I2C bad speed %d kBps. Defaulting to 100kbps.",
+ p->kbps);
+ freq = I2C_FREQ_100KHZ;
+ }
+
+ /* Configure the transfer clocks and mode */
+ i2cm_config_xfer_mode(p->port, freq);
+
+ CPRINTS("Initalized I2C port %d, freq = %d kHz", p->port, p->kbps);
+}
+
+/**
+ * Initialize the i2c module for all supported ports.
+ */
+void i2cm_init(void)
+{
+ const struct i2c_port_t *p = i2c_ports;
+ int i;
+
+ for (i = 0; i < i2c_ports_used; i++, p++)
+ i2cm_init_port(p);
+
+}
diff --git a/include/i2c.h b/include/i2c.h
index ddb0508828..1514ce8f1a 100644
--- a/include/i2c.h
+++ b/include/i2c.h
@@ -318,4 +318,10 @@ int i2c_get_protocol_info(struct host_cmd_handler_args *args);
void i2c_data_received(int port, uint8_t *buf, int len);
int i2c_set_response(int port, uint8_t *buf, int len);
+/**
+ * Initialize i2c master ports. This function can be called for cases where i2c
+ * ports are not initialized by default via a hook call.
+ */
+void i2cm_init(void);
+
#endif /* __CROS_EC_I2C_H */