summaryrefslogtreecommitdiff
path: root/chip/npcx/i2c.c
diff options
context:
space:
mode:
Diffstat (limited to 'chip/npcx/i2c.c')
-rw-r--r--chip/npcx/i2c.c592
1 files changed, 592 insertions, 0 deletions
diff --git a/chip/npcx/i2c.c b/chip/npcx/i2c.c
new file mode 100644
index 0000000000..1e71ed6c7b
--- /dev/null
+++ b/chip/npcx/i2c.c
@@ -0,0 +1,592 @@
+/* Copyright (c) 2014 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 "clock.h"
+#include "clock_chip.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"
+
+#if !(DEBUG_I2C)
+#define CPUTS(...)
+#define CPRINTS(...)
+#else
+#define CPUTS(outstr) cputs(CC_I2C, outstr)
+#define CPRINTS(format, args...) cprints(CC_I2C, format, ## args)
+#endif
+
+/* Data abort timeout unit:ms*/
+#define I2C_ABORT_TIMEOUT 10000
+/* Maximum time we allow for an I2C transfer */
+#define I2C_TIMEOUT_US (100*MSEC)
+/* Marco functions of I2C */
+#define I2C_START(port) SET_BIT(NPCX_SMBCTL1(port), NPCX_SMBCTL1_START)
+#define I2C_STOP(port) SET_BIT(NPCX_SMBCTL1(port), NPCX_SMBCTL1_STOP)
+#define I2C_NACK(port) SET_BIT(NPCX_SMBCTL1(port), NPCX_SMBCTL1_ACK)
+#define I2C_WRITE_BYTE(port, data) (NPCX_SMBSDA(port) = data)
+#define I2C_READ_BYTE(port, data) (data = NPCX_SMBSDA(port))
+
+/* Error values that functions can return */
+enum smb_error {
+ SMB_OK = 0, /* No error */
+ SMB_CH_OCCUPIED, /* Channel is already occupied */
+ SMB_MEM_POOL_INIT_ERROR, /* Memory pool initialization error */
+ SMB_BUS_FREQ_ERROR, /* SMbus freq was not valid */
+ SMB_INVLAID_REGVALUE, /* Invalid SMbus register value */
+ SMB_UNEXIST_CH_ERROR, /* Channel does not exist */
+ SMB_NO_SUPPORT_PTL, /* Not support SMBus Protocol */
+ SMB_BUS_ERROR, /* Encounter bus error */
+ SMB_MASTER_NO_ADDRESS_MATCH,/* No slave address match (Master Mode)*/
+ SMB_READ_DATA_ERROR, /* Read data for SDA error */
+ SMB_READ_OVERFLOW_ERROR, /* Read data over than we predict */
+ SMB_TIMEOUT_ERROR, /* Timeout expired */
+ SMB_MODULE_ISBUSY, /* Module is occupied by other device */
+ SMB_BUS_BUSY, /* SMBus is occupied by other device */
+};
+
+/*
+ * Internal SMBus Interface driver states values, which reflect events
+ * which occured on the bus
+ */
+enum smb_oper_state_t {
+ SMB_IDLE,
+ SMB_MASTER_START,
+ SMB_WRITE_OPER,
+ SMB_READ_OPER,
+ SMB_REPEAT_START,
+ SMB_WAIT_REPEAT_START,
+};
+
+
+/* IRQ for each port */
+static const uint32_t i2c_irqs[I2C_PORT_COUNT] = {
+ NPCX_IRQ_SMB1, NPCX_IRQ_SMB2, NPCX_IRQ_SMB3, NPCX_IRQ_SMB4};
+BUILD_ASSERT(ARRAY_SIZE(i2c_irqs) == I2C_PORT_COUNT);
+
+/* I2C port state data */
+struct i2c_status {
+ int flags; /* Flags (I2C_XFER_*) */
+ const uint8_t *tx_buf; /* Entry pointer of transmit buffer */
+ uint8_t *rx_buf; /* Entry pointer of receive buffer */
+ uint16_t sz_txbuf; /* Size of Tx buffer in bytes */
+ uint16_t sz_rxbuf; /* Size of rx buffer in bytes */
+ uint16_t idx_buf; /* Current index of Tx/Rx buffer */
+ uint8_t slave_addr;/* target slave address */
+ enum smb_oper_state_t oper_state;/* smbus operation state */
+ enum smb_error err_code; /* Error code */
+ int task_waiting; /* Task waiting on port */
+};
+/* I2C port state data array */
+static struct i2c_status i2c_stsobjs[I2C_PORT_COUNT];
+
+int i2c_bus_busy(int port)
+{
+ return IS_BIT_SET(NPCX_SMBCST(port), NPCX_SMBCST_BB) ? 1 : 0;
+}
+
+void i2c_abort_data(int port)
+{
+ uint16_t timeout = I2C_ABORT_TIMEOUT;
+
+ /* Generate a STOP condition */
+ I2C_STOP(port);
+
+ /* Clear NEGACK, STASTR and BER bits */
+ SET_BIT(NPCX_SMBST(port), NPCX_SMBST_BER);
+ SET_BIT(NPCX_SMBST(port), NPCX_SMBST_STASTR);
+ /*
+ * In Master mode, NEGACK should be cleared only
+ * after generating STOP
+ */
+ SET_BIT(NPCX_SMBST(port), NPCX_SMBST_NEGACK);
+
+ /* Wait till STOP condition is generated */
+ while (--timeout) {
+ msleep(1);
+ if (!IS_BIT_SET(NPCX_SMBCTL1(port), NPCX_SMBCTL1_STOP))
+ break;
+ }
+
+ /* Clear BB (BUS BUSY) bit */
+ SET_BIT(NPCX_SMBCST(port), NPCX_SMBCST_BB);
+}
+
+void i2c_reset(int port)
+{
+ uint16_t timeout = I2C_ABORT_TIMEOUT;
+ /* Disable the SMB module */
+ CLEAR_BIT(NPCX_SMBCTL2(port), NPCX_SMBCTL2_ENABLE);
+
+ while (--timeout) {
+ msleep(1);
+ /* WAIT FOR SCL & SDA IS HIGH */
+ if (IS_BIT_SET(NPCX_SMBCTL3(port), NPCX_SMBCTL3_SCL_LVL) &&
+ IS_BIT_SET(NPCX_SMBCTL3(port), NPCX_SMBCTL3_SDA_LVL))
+ break;
+ }
+
+ /* Enable the SMB module */
+ SET_BIT(NPCX_SMBCTL2(port), NPCX_SMBCTL2_ENABLE);
+}
+
+void i2c_recovery(int port)
+{
+ /* Abort data, generating STOP condition */
+ i2c_abort_data(port);
+
+ /* Reset i2c port by re-enable i2c port*/
+ i2c_reset(port);
+}
+
+enum smb_error i2c_master_transaction(int port)
+{
+ /* Set i2c mode to object */
+ int events = 0;
+ volatile struct i2c_status *p_status = i2c_stsobjs + port;
+
+ if (p_status->oper_state == SMB_IDLE) {
+ p_status->oper_state = SMB_MASTER_START;
+ } else if (p_status->oper_state == SMB_WAIT_REPEAT_START) {
+ p_status->oper_state = SMB_REPEAT_START;
+ CPUTS("R");
+ }
+
+ /* Generate a START condition */
+ I2C_START(port);
+ CPUTS("ST");
+
+ /* Wait for transfer complete or timeout */
+ events = task_wait_event_mask(TASK_EVENT_I2C_IDLE, I2C_TIMEOUT_US);
+ /* Handle timeout */
+ if ((events & TASK_EVENT_I2C_IDLE) == 0) {
+ /* Recovery I2C port */
+ i2c_recovery(port);
+ p_status->err_code = SMB_TIMEOUT_ERROR;
+ }
+
+ /*
+ * In slave write operation, NACK is OK, otherwise it is a problem
+ */
+ else if (p_status->err_code == SMB_BUS_ERROR ||
+ p_status->err_code == SMB_MASTER_NO_ADDRESS_MATCH){
+ i2c_recovery(port);
+ }
+
+ return p_status->err_code;
+}
+
+inline void i2c_handle_sda_irq(int port)
+{
+ volatile struct i2c_status *p_status = i2c_stsobjs + port;
+ /* 1 Issue Start is successful ie. write address byte */
+ if (p_status->oper_state == SMB_MASTER_START
+ || p_status->oper_state == SMB_REPEAT_START) {
+ uint8_t addr = p_status->slave_addr;
+ /* Prepare address byte */
+ if (p_status->sz_txbuf == 0) {/* Receive mode */
+ p_status->oper_state = SMB_READ_OPER;
+ /*
+ * Receiving one byte only - set nack just
+ * before writing address byte
+ */
+ if (p_status->sz_rxbuf == 1)
+ I2C_NACK(port);
+
+ /* Write the address to the bus R bit*/
+ I2C_WRITE_BYTE(port, (addr | 0x1));
+ CPUTS("-ARR");
+ } else {/* Transmit mode */
+ p_status->oper_state = SMB_WRITE_OPER;
+ /* Write the address to the bus W bit*/
+ I2C_WRITE_BYTE(port, addr);
+ CPUTS("-ARW");
+ }
+ /* Completed handling START condition */
+ return;
+ }
+ /* 2 Handle master write operation */
+ else if (p_status->oper_state == SMB_WRITE_OPER) {
+ /* all bytes have been written, in a pure write operation */
+ if (p_status->idx_buf == p_status->sz_txbuf) {
+ /* no more message */
+ if (p_status->sz_rxbuf == 0) {
+ /* need to STOP or not */
+ if (p_status->flags & I2C_XFER_STOP) {
+ /* Issue a STOP condition on the bus */
+ I2C_STOP(port);
+ CPUTS("-SP");
+ }
+ /* Clear SDA Status bit by writing dummy byte */
+ I2C_WRITE_BYTE(port, 0xFF);
+ /* Set error code */
+ p_status->err_code = SMB_OK;
+ /* Notify upper layer */
+ p_status->oper_state
+ = (p_status->flags & I2C_XFER_STOP)
+ ? SMB_IDLE : SMB_WAIT_REPEAT_START;
+ task_set_event(p_status->task_waiting,
+ TASK_EVENT_I2C_IDLE, 0);
+ CPUTS("-END");
+ }
+ /* need to restart & send slave address immediately */
+ else {
+ uint8_t addr_byte = p_status->slave_addr;
+ /*
+ * Prepare address byte
+ * and start to receive bytes
+ */
+ p_status->oper_state = SMB_READ_OPER;
+ /* Reset index of buffer */
+ p_status->idx_buf = 0;
+
+ /*
+ * Generate (Repeated) Start
+ * upon next write to SDA
+ */
+ I2C_START(port);
+ CPUTS("-RST");
+ /*
+ * Receiving one byte only - set nack just
+ * before writing address byte
+ */
+ if (p_status->sz_rxbuf == 1) {
+ I2C_NACK(port);
+ CPUTS("-GNA");
+ }
+ /* Write the address to the bus R bit*/
+ I2C_WRITE_BYTE(port, (addr_byte | 0x1));
+ CPUTS("-ARR");
+ }
+ }
+ /* write next byte (not last byte and not slave address */
+ else {
+ I2C_WRITE_BYTE(port,
+ p_status->tx_buf[p_status->idx_buf++]);
+ CPRINTS("-W(%02x)",
+ p_status->tx_buf[p_status->idx_buf-1]);
+ }
+ }
+ /* 3 Handle master read operation (read or after a write operation) */
+ else if (p_status->oper_state == SMB_READ_OPER) {
+ uint8_t data;
+ /* last byte is about to be read - end of transaction */
+ if (p_status->idx_buf == (p_status->sz_rxbuf - 1)) {
+ /* need to STOP or not */
+ if (p_status->flags & I2C_XFER_STOP) {
+ /* Stop should set before reading last byte */
+ I2C_STOP(port);
+ CPUTS("-SP");
+ }
+ }
+ /* Check if byte-before-last is about to be read */
+ else if (p_status->idx_buf == (p_status->sz_rxbuf - 2)) {
+ /*
+ * Set nack before reading byte-before-last,
+ * so that nack will be generated after receive
+ * of last byte
+ */
+ I2C_NACK(port);
+ CPUTS("-GNA");
+ }
+
+ /* Read data for SMBSDA */
+ I2C_READ_BYTE(port, data);
+ CPRINTS("-R(%02x)", data);
+ /* Read to buffer */
+ p_status->rx_buf[p_status->idx_buf++] = data;
+
+ /* last byte is read - end of transaction */
+ if (p_status->idx_buf == p_status->sz_rxbuf) {
+ /* Set error code */
+ p_status->err_code = SMB_OK;
+ /* Notify upper layer of missing data */
+ p_status->oper_state = (p_status->flags & I2C_XFER_STOP)
+ ? SMB_IDLE : SMB_WAIT_REPEAT_START;
+ task_set_event(p_status->task_waiting,
+ TASK_EVENT_I2C_IDLE, 0);
+ CPUTS("-END");
+ }
+ }
+}
+
+void i2c_master_int_handler (int port)
+{
+ volatile struct i2c_status *p_status = i2c_stsobjs + port;
+ /* Condition 1 : A Bus Error has been identified */
+ if (IS_BIT_SET(NPCX_SMBST(port), NPCX_SMBST_BER)) {
+ /* Clear BER Bit */
+ SET_BIT(NPCX_SMBST(port), NPCX_SMBST_BER);
+ /* Set error code */
+ p_status->err_code = SMB_BUS_ERROR;
+ /* Notify upper layer */
+ p_status->oper_state = SMB_IDLE;
+ task_set_event(p_status->task_waiting, TASK_EVENT_I2C_IDLE, 0);
+ CPUTS("-BER");
+ }
+
+ /* Condition 2: A negative acknowledge has occurred */
+ if (IS_BIT_SET(NPCX_SMBST(port), NPCX_SMBST_NEGACK)) {
+ /* Clear NEGACK Bit */
+ SET_BIT(NPCX_SMBST(port), NPCX_SMBST_NEGACK);
+ /* Set error code */
+ p_status->err_code = SMB_MASTER_NO_ADDRESS_MATCH;
+ /* Notify upper layer */
+ p_status->oper_state = SMB_IDLE;
+ task_set_event(p_status->task_waiting, TASK_EVENT_I2C_IDLE, 0);
+ CPUTS("-NA");
+ }
+
+ /* Condition 3: SDA status is set - transmit or receive */
+ if (IS_BIT_SET(NPCX_SMBST(port), NPCX_SMBST_SDAST))
+ i2c_handle_sda_irq(port);
+}
+
+/**
+ * Handle an interrupt on the specified port.
+ *
+ * @param port I2C port generating interrupt
+ */
+void handle_interrupt(int port)
+{
+ i2c_master_int_handler(port);
+}
+
+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(NPCX_IRQ_SMB1, i2c0_interrupt, 2);
+DECLARE_IRQ(NPCX_IRQ_SMB2, i2c1_interrupt, 2);
+DECLARE_IRQ(NPCX_IRQ_SMB3, i2c2_interrupt, 2);
+DECLARE_IRQ(NPCX_IRQ_SMB4, i2c3_interrupt, 2);
+
+/*****************************************************************************/
+/* IC specific low-level driver */
+
+int i2c_xfer(int port, int slave_addr, const uint8_t *out, int out_size,
+ uint8_t *in, int in_size, int flags)
+{
+ volatile struct i2c_status *p_status = i2c_stsobjs + port;
+
+ if (port < 0 || port >= i2c_ports_used)
+ return EC_ERROR_INVAL;
+
+ if (out_size == 0 && in_size == 0)
+ return EC_SUCCESS;
+
+ /* Copy data to port struct */
+ p_status->flags = flags;
+ p_status->tx_buf = out;
+ p_status->sz_txbuf = out_size;
+ p_status->rx_buf = in;
+ p_status->sz_rxbuf = in_size;
+#if I2C_7BITS_ADDR
+ /* Set slave address from 7-bits to 8-bits */
+ p_status->slave_addr = (slave_addr<<1);
+#else
+ /* Set slave address (8-bits) */
+ p_status->slave_addr = slave_addr;
+#endif
+ /* Reset index & error */
+ p_status->idx_buf = 0;
+ p_status->err_code = SMB_OK;
+
+
+ /* Make sure we're in a good state to start */
+ if ((flags & I2C_XFER_START) && (i2c_bus_busy(port)
+ || (i2c_get_line_levels(port) != I2C_LINE_IDLE))) {
+
+ /* Attempt to unwedge the port. */
+ i2c_unwedge(port);
+ /* recovery i2c port */
+ i2c_recovery(port);
+ }
+
+ /* Enable SMB interrupt and New Address Match interrupt source */
+ SET_BIT(NPCX_SMBCTL1(port), NPCX_SMBCTL1_NMINTE);
+ SET_BIT(NPCX_SMBCTL1(port), NPCX_SMBCTL1_INTEN);
+
+ CPUTS("\n");
+
+ /* Assign current task ID */
+ p_status->task_waiting = task_get_current();
+
+ /* Start master transaction */
+ i2c_master_transaction(port);
+
+ /* Reset task ID */
+ p_status->task_waiting = TASK_ID_INVALID;
+
+ /* Disable SMB interrupt and New Address Match interrupt source */
+ CLEAR_BIT(NPCX_SMBCTL1(port), NPCX_SMBCTL1_NMINTE);
+ CLEAR_BIT(NPCX_SMBCTL1(port), NPCX_SMBCTL1_INTEN);
+
+ CPRINTS("-Err:0x%02x\n", p_status->err_code);
+
+ return (p_status->err_code == SMB_OK) ? EC_SUCCESS : EC_ERROR_UNKNOWN;
+}
+
+/**
+ * Return raw I/O line levels (I2C_LINE_*) for a port when port is in alternate
+ * function mode.
+ *
+ * @param port Port to check
+ * @return State of SCL/SDA bit 0/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);
+}
+
+int i2c_raw_get_scl(int port)
+{
+ enum gpio_signal g;
+
+ /* Check do we support this port of i2c and return gpio number of scl */
+ if (get_scl_from_i2c_port(port, &g) == EC_SUCCESS)
+#if !(I2C_LEVEL_SUPPORT)
+ return gpio_get_level(g);
+#else
+ return IS_BIT_SET(NPCX_SMBCTL3(port), NPCX_SMBCTL3_SCL_LVL);
+#endif
+
+ /* 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 g;
+ /* Check do we support this port of i2c and return gpio number of scl */
+ if (get_sda_from_i2c_port(port, &g) == EC_SUCCESS)
+#if !(I2C_LEVEL_SUPPORT)
+ return gpio_get_level(g);
+#else
+ return IS_BIT_SET(NPCX_SMBCTL3(port), NPCX_SMBCTL3_SDA_LVL);
+#endif
+
+ /* If no SDA pin defined for this port, then return 1 to appear idle */
+ return 1;
+}
+
+int i2c_read_string(int port, int slave_addr, int offset, uint8_t *data,
+ int len)
+{
+ int rv;
+ uint8_t reg, block_length;
+
+ /* Check block protocol size */
+ if ((len <= 0) || (len > 32))
+ return EC_ERROR_INVAL;
+
+ i2c_lock(port, 1);
+ reg = offset;
+
+ /*
+ * Send device reg space offset, and read back block length. Keep this
+ * session open without a stop.
+ */
+ rv = i2c_xfer(port, slave_addr, &reg, 1, &block_length, 1,
+ I2C_XFER_START);
+ if (rv)
+ goto exit;
+
+ if (len && block_length > (len - 1))
+ block_length = len - 1;
+
+ rv = i2c_xfer(port, slave_addr, 0, 0, data, block_length,
+ I2C_XFER_STOP);
+ data[block_length] = 0;
+
+exit:
+ i2c_lock(port, 0);
+
+ return rv;
+}
+
+/*****************************************************************************/
+/* Hooks */
+static void i2c_freq_changed(void)
+{
+ /* I2C is under APB2 */
+ int freq;
+ int port;
+
+ for (port = 0; port < i2c_ports_used; port++) {
+ int bus_freq = i2c_ports[port].kbps;
+ int scl_time;
+
+ /* SMB0/1 use core clock & SMB2/3 use apb2 clock */
+ if (port < 2)
+ freq = clock_get_freq();
+ else
+ freq = clock_get_apb2_freq();
+
+ /* use Fast Mode */
+ SET_BIT(NPCX_SMBCTL3(port) , NPCX_SMBCTL3_400K);
+
+ /*
+ * Set SCLLT/SCLHT:
+ * tSCLL = 2 * SCLLT7-0 * tCLK
+ * tSCLH = 2 * SCLHT7-0 * tCLK
+ * (tSCLL+tSCLH) = 4 * SCLH(L)T * tCLK if tSCLL == tSCLH
+ * SCLH(L)T = T(SCL)/4/T(CLK) = FREQ(CLK)/4/FREQ(SCL)
+ */
+ scl_time = (freq/1000) / (bus_freq * 4); /* bus_freq is KHz */
+
+ /* set SCL High/Low time */
+ NPCX_SMBSCLLT(port) = scl_time;
+ NPCX_SMBSCLHT(port) = scl_time;
+ }
+}
+DECLARE_HOOK(HOOK_FREQ_CHANGE, i2c_freq_changed, HOOK_PRIO_DEFAULT);
+
+static void i2c_init(void)
+{
+ int port = 0;
+ /* Configure pins from GPIOs to I2Cs */
+ gpio_config_module(MODULE_I2C, 1);
+
+ /* Enable clock for I2C peripheral */
+ clock_enable_peripheral(CGC_OFFSET_I2C, CGC_I2C_MASK,
+ CGC_MODE_RUN | CGC_MODE_SLEEP);
+ /*
+ * initialize smb status and register
+ */
+ for (port = 0; port < i2c_ports_used; port++) {
+ volatile struct i2c_status *p_status = i2c_stsobjs + port;
+ /* Configure pull-up for SMB interface pins */
+#ifndef SMB_SUPPORT18V
+ /* Enable 3.3V pull-up */
+ SET_BIT(NPCX_DEVPU0, port);
+#else
+ /* Set GPIO Pin voltage judgment to 1.8V */
+ SET_BIT(NPCX_LV_GPIO_CTL1, port+1);
+#endif
+
+ /* Enable module - before configuring CTL1 */
+ SET_BIT(NPCX_SMBCTL2(port), NPCX_SMBCTL2_ENABLE);
+
+ /* status init */
+ p_status->oper_state = SMB_IDLE;
+
+ /* Reset task ID */
+ p_status->task_waiting = TASK_ID_INVALID;
+
+ /* Enable event and error interrupts */
+ task_enable_irq(i2c_irqs[port]);
+ }
+}
+DECLARE_HOOK(HOOK_INIT, i2c_init, HOOK_PRIO_DEFAULT);