| /* 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 chip_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; |
| } |
| |
| /*****************************************************************************/ |
| /* 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); |
| |
| /* Set I2C freq */ |
| i2c_freq_changed(); |
| /* |
| * 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); |