862 lines
25 KiB
C
862 lines
25 KiB
C
|
/* Copyright 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 "i2c_chip.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
|
||
|
|
||
|
/* Timeout for device should be available after reset (SMBus spec. unit:ms) */
|
||
|
#define I2C_MAX_TIMEOUT 35
|
||
|
/*
|
||
|
* Timeout for SCL held to low by slave device . (SMBus spec. unit:ms).
|
||
|
* Some I2C devices may violate this timing and clock stretch for longer.
|
||
|
* TODO: Consider increasing this timeout.
|
||
|
*/
|
||
|
#define I2C_MIN_TIMEOUT 25
|
||
|
|
||
|
/* Macro functions of I2C */
|
||
|
#define I2C_START(ctrl) SET_BIT(NPCX_SMBCTL1(ctrl), NPCX_SMBCTL1_START)
|
||
|
#define I2C_STOP(ctrl) SET_BIT(NPCX_SMBCTL1(ctrl), NPCX_SMBCTL1_STOP)
|
||
|
#define I2C_NACK(ctrl) SET_BIT(NPCX_SMBCTL1(ctrl), NPCX_SMBCTL1_ACK)
|
||
|
#define I2C_STALL(ctrl) SET_BIT(NPCX_SMBCTL1(ctrl), NPCX_SMBCTL1_STASTRE)
|
||
|
#define I2C_WRITE_BYTE(ctrl, data) (NPCX_SMBSDA(ctrl) = data)
|
||
|
#define I2C_READ_BYTE(ctrl, data) (data = NPCX_SMBSDA(ctrl))
|
||
|
|
||
|
/* 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 occurred on the bus
|
||
|
*/
|
||
|
enum smb_oper_state_t {
|
||
|
SMB_IDLE,
|
||
|
SMB_MASTER_START,
|
||
|
SMB_WRITE_OPER,
|
||
|
SMB_READ_OPER,
|
||
|
SMB_DUMMY_READ_OPER,
|
||
|
SMB_REPEAT_START,
|
||
|
SMB_WRITE_SUSPEND,
|
||
|
SMB_READ_SUSPEND,
|
||
|
};
|
||
|
|
||
|
/* I2C controller 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 */
|
||
|
uint16_t slave_addr_flags;/* 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 controller */
|
||
|
uint32_t timeout_us;/* Transaction timeout */
|
||
|
};
|
||
|
/* I2C controller state data array */
|
||
|
struct i2c_status i2c_stsobjs[I2C_CONTROLLER_COUNT];
|
||
|
|
||
|
/* I2C timing setting */
|
||
|
struct i2c_timing {
|
||
|
uint8_t clock; /* I2C source clock. (Unit: MHz)*/
|
||
|
uint8_t HLDT; /* I2C hold-time. (Unit: clocks) */
|
||
|
uint8_t k1; /* k1 = SCL low-time (Unit: clocks) */
|
||
|
uint8_t k2; /* k2 = SCL high-time (Unit: clocks) */
|
||
|
};
|
||
|
|
||
|
/* I2C timing setting array of 400K & 1M Hz */
|
||
|
static const struct i2c_timing i2c_400k_timings[] = {
|
||
|
{20, 7, 32, 22},
|
||
|
{15, 7, 24, 18},};
|
||
|
const unsigned int i2c_400k_timing_used = ARRAY_SIZE(i2c_400k_timings);
|
||
|
|
||
|
static const struct i2c_timing i2c_1m_timings[] = {
|
||
|
{20, 7, 16, 10},
|
||
|
{15, 7, 14, 10},};
|
||
|
const unsigned int i2c_1m_timing_used = ARRAY_SIZE(i2c_1m_timings);
|
||
|
|
||
|
/* IRQ for each port */
|
||
|
const uint32_t i2c_irqs[I2C_CONTROLLER_COUNT] = {
|
||
|
NPCX_IRQ_SMB1, NPCX_IRQ_SMB2, NPCX_IRQ_SMB3, NPCX_IRQ_SMB4,
|
||
|
#if defined(CHIP_FAMILY_NPCX7)
|
||
|
NPCX_IRQ_SMB5, NPCX_IRQ_SMB6, NPCX_IRQ_SMB7, NPCX_IRQ_SMB8,
|
||
|
#endif
|
||
|
};
|
||
|
BUILD_ASSERT(ARRAY_SIZE(i2c_irqs) == I2C_CONTROLLER_COUNT);
|
||
|
|
||
|
static void i2c_init_bus(int controller)
|
||
|
{
|
||
|
/* Enable module - before configuring CTL1 */
|
||
|
SET_BIT(NPCX_SMBCTL2(controller), NPCX_SMBCTL2_ENABLE);
|
||
|
|
||
|
/* Enable SMB interrupt and New Address Match interrupt source */
|
||
|
SET_BIT(NPCX_SMBCTL1(controller), NPCX_SMBCTL1_NMINTE);
|
||
|
SET_BIT(NPCX_SMBCTL1(controller), NPCX_SMBCTL1_INTEN);
|
||
|
}
|
||
|
|
||
|
int i2c_bus_busy(int controller)
|
||
|
{
|
||
|
return IS_BIT_SET(NPCX_SMBCST(controller), NPCX_SMBCST_BB) ? 1 : 0;
|
||
|
}
|
||
|
|
||
|
static int i2c_wait_stop_completed(int controller, int timeout)
|
||
|
{
|
||
|
if (timeout <= 0)
|
||
|
return EC_ERROR_INVAL;
|
||
|
|
||
|
/* Wait till STOP condition is generated. ie. I2C bus is idle. */
|
||
|
while (timeout > 0) {
|
||
|
if (!IS_BIT_SET(NPCX_SMBCTL1(controller), NPCX_SMBCTL1_STOP))
|
||
|
break;
|
||
|
if (--timeout > 0)
|
||
|
msleep(1);
|
||
|
}
|
||
|
|
||
|
if (timeout)
|
||
|
return EC_SUCCESS;
|
||
|
else
|
||
|
return EC_ERROR_TIMEOUT;
|
||
|
}
|
||
|
|
||
|
static void i2c_abort_data(int controller)
|
||
|
{
|
||
|
/* Clear NEGACK, STASTR and BER bits */
|
||
|
SET_BIT(NPCX_SMBST(controller), NPCX_SMBST_BER);
|
||
|
SET_BIT(NPCX_SMBST(controller), NPCX_SMBST_STASTR);
|
||
|
SET_BIT(NPCX_SMBST(controller), NPCX_SMBST_NEGACK);
|
||
|
|
||
|
/* Wait till STOP condition is generated */
|
||
|
if (i2c_wait_stop_completed(controller, I2C_MAX_TIMEOUT)
|
||
|
!= EC_SUCCESS) {
|
||
|
cprintf(CC_I2C, "Abort i2c %02x fail!\n", controller);
|
||
|
/* Clear BB (BUS BUSY) bit */
|
||
|
SET_BIT(NPCX_SMBCST(controller), NPCX_SMBCST_BB);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/* Clear BB (BUS BUSY) bit */
|
||
|
SET_BIT(NPCX_SMBCST(controller), NPCX_SMBCST_BB);
|
||
|
}
|
||
|
|
||
|
static int i2c_reset(int controller)
|
||
|
{
|
||
|
uint16_t timeout = I2C_MAX_TIMEOUT;
|
||
|
/* Disable the SMB module */
|
||
|
CLEAR_BIT(NPCX_SMBCTL2(controller), NPCX_SMBCTL2_ENABLE);
|
||
|
|
||
|
while (--timeout) {
|
||
|
/* WAIT FOR SCL & SDA IS HIGH */
|
||
|
if (IS_BIT_SET(NPCX_SMBCTL3(controller), NPCX_SMBCTL3_SCL_LVL)
|
||
|
&& IS_BIT_SET(NPCX_SMBCTL3(controller), NPCX_SMBCTL3_SDA_LVL))
|
||
|
break;
|
||
|
msleep(1);
|
||
|
}
|
||
|
|
||
|
if (timeout == 0) {
|
||
|
cprintf(CC_I2C, "Reset i2c %02x fail!\n", controller);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* Init the SMB module again */
|
||
|
i2c_init_bus(controller);
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
static void i2c_recovery(int controller, volatile struct i2c_status *p_status)
|
||
|
{
|
||
|
cprintf(CC_I2C,
|
||
|
"i2c %d recovery! error code is %d, current state is %d\n",
|
||
|
controller, p_status->err_code, p_status->oper_state);
|
||
|
|
||
|
/* Abort data, wait for STOP condition completed. */
|
||
|
i2c_abort_data(controller);
|
||
|
|
||
|
/* Reset i2c controller by re-enable i2c controller*/
|
||
|
if (!i2c_reset(controller))
|
||
|
return;
|
||
|
|
||
|
/* Restore to idle status */
|
||
|
p_status->oper_state = SMB_IDLE;
|
||
|
}
|
||
|
|
||
|
enum smb_error i2c_master_transaction(int controller)
|
||
|
{
|
||
|
/* Set i2c mode to object */
|
||
|
int events = 0;
|
||
|
volatile struct i2c_status *p_status = i2c_stsobjs + controller;
|
||
|
|
||
|
/* Assign current SMB status of controller */
|
||
|
if (p_status->oper_state == SMB_IDLE) {
|
||
|
/* New transaction */
|
||
|
p_status->oper_state = SMB_MASTER_START;
|
||
|
} else if (p_status->oper_state == SMB_WRITE_SUSPEND) {
|
||
|
if (p_status->sz_txbuf == 0) {
|
||
|
/* Read bytes from next transaction */
|
||
|
p_status->oper_state = SMB_REPEAT_START;
|
||
|
CPUTS("R");
|
||
|
} else {
|
||
|
/* Continue to write the other bytes */
|
||
|
p_status->oper_state = SMB_WRITE_OPER;
|
||
|
I2C_WRITE_BYTE(controller,
|
||
|
p_status->tx_buf[p_status->idx_buf++]);
|
||
|
CPRINTS("-W(%02x)",
|
||
|
p_status->tx_buf[p_status->idx_buf-1]);
|
||
|
}
|
||
|
} else if (p_status->oper_state == SMB_READ_SUSPEND) {
|
||
|
/*
|
||
|
* Do dummy read if read length is 1 and I2C_XFER_STOP is set
|
||
|
* simultaneously.
|
||
|
*/
|
||
|
if (p_status->sz_rxbuf == 1 &&
|
||
|
(p_status->flags & I2C_XFER_STOP)) {
|
||
|
/*
|
||
|
* Since SCL is released after reading last byte from
|
||
|
* previous transaction, adding a dummy byte for next
|
||
|
* transaction which let ec sets NACK bit in time is
|
||
|
* necessary. Or i2c master cannot generate STOP
|
||
|
* when the last byte is ACK during receiving.
|
||
|
*/
|
||
|
p_status->sz_rxbuf++;
|
||
|
p_status->oper_state = SMB_DUMMY_READ_OPER;
|
||
|
} else
|
||
|
/* Need to read the other bytes from next transaction */
|
||
|
p_status->oper_state = SMB_READ_OPER;
|
||
|
} else
|
||
|
cprintf(CC_I2C, "Unexpected i2c state machine! %d\n",
|
||
|
p_status->oper_state);
|
||
|
|
||
|
/* Generate a START condition */
|
||
|
if (p_status->oper_state == SMB_MASTER_START ||
|
||
|
p_status->oper_state == SMB_REPEAT_START) {
|
||
|
I2C_START(controller);
|
||
|
CPUTS("ST");
|
||
|
}
|
||
|
|
||
|
/* Enable event and error interrupts */
|
||
|
task_enable_irq(i2c_irqs[controller]);
|
||
|
|
||
|
/* Wait for transfer complete or timeout */
|
||
|
events = task_wait_event_mask(TASK_EVENT_I2C_IDLE,
|
||
|
p_status->timeout_us);
|
||
|
|
||
|
/* Disable event and error interrupts */
|
||
|
task_disable_irq(i2c_irqs[controller]);
|
||
|
|
||
|
/*
|
||
|
* If Stall-After-Start mode is still enabled since NACK or BUS error
|
||
|
* occurs, disable it.
|
||
|
*/
|
||
|
if (IS_BIT_SET(NPCX_SMBCTL1(controller), NPCX_SMBCTL1_STASTRE))
|
||
|
CLEAR_BIT(NPCX_SMBCTL1(controller), NPCX_SMBCTL1_STASTRE);
|
||
|
|
||
|
/* Handle bus timeout */
|
||
|
if ((events & TASK_EVENT_I2C_IDLE) == 0) {
|
||
|
p_status->err_code = SMB_TIMEOUT_ERROR;
|
||
|
/* Recovery I2C controller */
|
||
|
i2c_recovery(controller, p_status);
|
||
|
}
|
||
|
/* Recovery bus if we encounter bus error */
|
||
|
else if (p_status->err_code == SMB_BUS_ERROR)
|
||
|
i2c_recovery(controller, p_status);
|
||
|
|
||
|
/* Wait till STOP condition is generated for normal transaction */
|
||
|
if (p_status->err_code == SMB_OK && i2c_wait_stop_completed(controller,
|
||
|
I2C_MIN_TIMEOUT) != EC_SUCCESS) {
|
||
|
cprintf(CC_I2C,
|
||
|
"STOP fail! scl %02x is held by slave device!\n",
|
||
|
controller);
|
||
|
p_status->err_code = SMB_TIMEOUT_ERROR;
|
||
|
}
|
||
|
|
||
|
return p_status->err_code;
|
||
|
}
|
||
|
|
||
|
/* Issue stop condition if necessary and end transaction */
|
||
|
void i2c_done(int controller)
|
||
|
{
|
||
|
volatile struct i2c_status *p_status = i2c_stsobjs + controller;
|
||
|
|
||
|
/* need to STOP or not */
|
||
|
if (p_status->flags & I2C_XFER_STOP) {
|
||
|
/* Issue a STOP condition on the bus */
|
||
|
I2C_STOP(controller);
|
||
|
CPUTS("-SP");
|
||
|
/* Clear SDAST by writing dummy byte */
|
||
|
I2C_WRITE_BYTE(controller, 0xFF);
|
||
|
}
|
||
|
|
||
|
/* Set error code */
|
||
|
p_status->err_code = SMB_OK;
|
||
|
/* Set SMB status if we need stall bus */
|
||
|
p_status->oper_state = (p_status->flags & I2C_XFER_STOP)
|
||
|
? SMB_IDLE : SMB_WRITE_SUSPEND;
|
||
|
/*
|
||
|
* Disable interrupt for i2c master stall SCL
|
||
|
* and forbid SDAST generate interrupt
|
||
|
* until common layer start other transactions
|
||
|
*/
|
||
|
if (p_status->oper_state == SMB_WRITE_SUSPEND)
|
||
|
task_disable_irq(i2c_irqs[controller]);
|
||
|
|
||
|
/* Notify upper layer */
|
||
|
task_set_event(p_status->task_waiting,
|
||
|
TASK_EVENT_I2C_IDLE, 0);
|
||
|
CPUTS("-END");
|
||
|
}
|
||
|
|
||
|
static void i2c_handle_sda_irq(int controller)
|
||
|
{
|
||
|
volatile struct i2c_status *p_status = i2c_stsobjs + controller;
|
||
|
uint8_t addr_8bit = I2C_GET_ADDR(p_status->slave_addr_flags) << 1;
|
||
|
/* 1 Issue Start is successful ie. write address byte */
|
||
|
if (p_status->oper_state == SMB_MASTER_START
|
||
|
|| p_status->oper_state == SMB_REPEAT_START) {
|
||
|
/* Prepare address byte */
|
||
|
if (p_status->sz_txbuf == 0) {/* Receive mode */
|
||
|
p_status->oper_state = SMB_READ_OPER;
|
||
|
/*
|
||
|
* Receiving one or zero bytes - stall bus after START
|
||
|
* condition. If there's no slave devices on bus, FW
|
||
|
* needn't to set ACK bit.
|
||
|
*/
|
||
|
if (p_status->sz_rxbuf < 2)
|
||
|
I2C_STALL(controller);
|
||
|
|
||
|
/* Write the address to the bus R bit*/
|
||
|
I2C_WRITE_BYTE(controller, (addr_8bit | 0x1));
|
||
|
CPRINTS("-ARR-0x%02x", addr);
|
||
|
} else {/* Transmit mode */
|
||
|
p_status->oper_state = SMB_WRITE_OPER;
|
||
|
/* Write the address to the bus W bit*/
|
||
|
I2C_WRITE_BYTE(controller, addr_8bit);
|
||
|
CPRINTS("-ARW-0x%02x", addr);
|
||
|
}
|
||
|
/* 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)
|
||
|
i2c_done(controller);
|
||
|
/* need to restart & send slave address immediately */
|
||
|
else {
|
||
|
/*
|
||
|
* 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(controller);
|
||
|
CPUTS("-RST");
|
||
|
/*
|
||
|
* Receiving one byte only - set nack just
|
||
|
* before writing address byte
|
||
|
*/
|
||
|
if (p_status->sz_rxbuf == 1 &&
|
||
|
(p_status->flags & I2C_XFER_STOP)) {
|
||
|
I2C_NACK(controller);
|
||
|
CPUTS("-GNA");
|
||
|
}
|
||
|
/* Write the address to the bus R bit*/
|
||
|
I2C_WRITE_BYTE(controller,
|
||
|
(addr_8bit | 0x1));
|
||
|
CPUTS("-ARR");
|
||
|
}
|
||
|
}
|
||
|
/* write next byte (not last byte and not slave address */
|
||
|
else {
|
||
|
I2C_WRITE_BYTE(controller,
|
||
|
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 ||
|
||
|
p_status->oper_state == SMB_DUMMY_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(controller);
|
||
|
CPUTS("-SP");
|
||
|
} else {
|
||
|
/*
|
||
|
* Disable interrupt before i2c master read SDA
|
||
|
* reg (stall SCL) and forbid SDAST generate
|
||
|
* interrupt until starting other transactions
|
||
|
*/
|
||
|
task_disable_irq(i2c_irqs[controller]);
|
||
|
}
|
||
|
}
|
||
|
/* 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
|
||
|
*/
|
||
|
if (p_status->flags & I2C_XFER_STOP) {
|
||
|
I2C_NACK(controller);
|
||
|
CPUTS("-GNA");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Read data for SMBSDA */
|
||
|
I2C_READ_BYTE(controller, data);
|
||
|
CPRINTS("-R(%02x)", data);
|
||
|
|
||
|
/* Read to buf. Skip last byte if meet SMB_DUMMY_READ_OPER */
|
||
|
if (p_status->oper_state == SMB_DUMMY_READ_OPER &&
|
||
|
p_status->idx_buf == (p_status->sz_rxbuf - 1))
|
||
|
p_status->idx_buf++;
|
||
|
else
|
||
|
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 current status */
|
||
|
p_status->oper_state = (p_status->flags & I2C_XFER_STOP)
|
||
|
? SMB_IDLE : SMB_READ_SUSPEND;
|
||
|
/* Set error code */
|
||
|
p_status->err_code = SMB_OK;
|
||
|
/* Notify upper layer of missing data */
|
||
|
task_set_event(p_status->task_waiting,
|
||
|
TASK_EVENT_I2C_IDLE, 0);
|
||
|
CPUTS("-END");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void i2c_master_int_handler (int controller)
|
||
|
{
|
||
|
volatile struct i2c_status *p_status = i2c_stsobjs + controller;
|
||
|
|
||
|
/* Condition 1 : A Bus Error has been identified */
|
||
|
if (IS_BIT_SET(NPCX_SMBST(controller), NPCX_SMBST_BER)) {
|
||
|
uint8_t __attribute__((unused)) data;
|
||
|
/* Generate a STOP condition */
|
||
|
I2C_STOP(controller);
|
||
|
CPUTS("-SP");
|
||
|
/* Clear BER Bit */
|
||
|
SET_BIT(NPCX_SMBST(controller), NPCX_SMBST_BER);
|
||
|
/* Mask sure slave doesn't hold bus by dummy reading */
|
||
|
I2C_READ_BYTE(controller, data);
|
||
|
|
||
|
/* 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");
|
||
|
|
||
|
/*
|
||
|
* Disable smb's interrupts to forbid ec to enter ISR again
|
||
|
* before executing error recovery.
|
||
|
*/
|
||
|
task_disable_irq(i2c_irqs[controller]);
|
||
|
|
||
|
/* return for executing error recovery immediately */
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/* Condition 2: A negative acknowledge has occurred */
|
||
|
if (IS_BIT_SET(NPCX_SMBST(controller), NPCX_SMBST_NEGACK)) {
|
||
|
/* Generate a STOP condition */
|
||
|
I2C_STOP(controller);
|
||
|
CPUTS("-SP");
|
||
|
/* Clear NEGACK Bit */
|
||
|
SET_BIT(NPCX_SMBST(controller), 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: A Stall after START has occurred for READ-BYTE */
|
||
|
if (IS_BIT_SET(NPCX_SMBST(controller), NPCX_SMBST_STASTR)) {
|
||
|
CPUTS("-STL");
|
||
|
|
||
|
/* Disable Stall-After-Start mode first */
|
||
|
CLEAR_BIT(NPCX_SMBCTL1(controller), NPCX_SMBCTL1_STASTRE);
|
||
|
|
||
|
/*
|
||
|
* Generate stop condition and return success status since
|
||
|
* ACK received on zero-byte transaction.
|
||
|
*/
|
||
|
if (p_status->sz_rxbuf == 0)
|
||
|
i2c_done(controller);
|
||
|
/*
|
||
|
* Otherwise we have a one-byte transaction, so nack after
|
||
|
* receiving next byte, if requested.
|
||
|
*/
|
||
|
else if (p_status->flags & I2C_XFER_STOP)
|
||
|
I2C_NACK(controller);
|
||
|
|
||
|
/* Clear STASTR to release SCL after setting NACK/STOP bits */
|
||
|
SET_BIT(NPCX_SMBST(controller), NPCX_SMBST_STASTR);
|
||
|
}
|
||
|
|
||
|
/* Condition 4: SDA status is set - transmit or receive */
|
||
|
if (IS_BIT_SET(NPCX_SMBST(controller), NPCX_SMBST_SDAST)) {
|
||
|
i2c_handle_sda_irq(controller);
|
||
|
#if DEBUG_I2C
|
||
|
/* SDAST still issued with unexpected state machine */
|
||
|
if (IS_BIT_SET(NPCX_SMBST(controller), NPCX_SMBST_SDAST) &&
|
||
|
p_status->oper_state != SMB_WRITE_SUSPEND) {
|
||
|
cprints(CC_I2C, "i2c %d unknown state %d, error %d\n",
|
||
|
controller, p_status->oper_state, p_status->err_code);
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Handle an interrupt on the specified controller.
|
||
|
*
|
||
|
* @param controller I2C controller generating interrupt
|
||
|
*/
|
||
|
void handle_interrupt(int controller)
|
||
|
{
|
||
|
i2c_master_int_handler(controller);
|
||
|
}
|
||
|
|
||
|
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); }
|
||
|
#if defined(CHIP_FAMILY_NPCX7)
|
||
|
void i2c4_interrupt(void) { handle_interrupt(4); }
|
||
|
void i2c5_interrupt(void) { handle_interrupt(5); }
|
||
|
void i2c6_interrupt(void) { handle_interrupt(6); }
|
||
|
void i2c7_interrupt(void) { handle_interrupt(7); }
|
||
|
#endif
|
||
|
|
||
|
DECLARE_IRQ(NPCX_IRQ_SMB1, i2c0_interrupt, 4);
|
||
|
DECLARE_IRQ(NPCX_IRQ_SMB2, i2c1_interrupt, 4);
|
||
|
DECLARE_IRQ(NPCX_IRQ_SMB3, i2c2_interrupt, 4);
|
||
|
DECLARE_IRQ(NPCX_IRQ_SMB4, i2c3_interrupt, 4);
|
||
|
#if defined(CHIP_FAMILY_NPCX7)
|
||
|
DECLARE_IRQ(NPCX_IRQ_SMB5, i2c4_interrupt, 4);
|
||
|
DECLARE_IRQ(NPCX_IRQ_SMB6, i2c5_interrupt, 4);
|
||
|
DECLARE_IRQ(NPCX_IRQ_SMB7, i2c6_interrupt, 4);
|
||
|
DECLARE_IRQ(NPCX_IRQ_SMB8, i2c7_interrupt, 4);
|
||
|
#endif
|
||
|
|
||
|
/*****************************************************************************/
|
||
|
/* IC specific low-level driver */
|
||
|
|
||
|
void i2c_set_timeout(int port, uint32_t timeout)
|
||
|
{
|
||
|
int ctrl = i2c_port_to_controller(port);
|
||
|
|
||
|
/* Return if i2c_port_to_controller() returned an error */
|
||
|
if (ctrl < 0)
|
||
|
return;
|
||
|
|
||
|
/* Param is port, but timeout is stored by-controller. */
|
||
|
i2c_stsobjs[ctrl].timeout_us =
|
||
|
timeout ? timeout : I2C_TIMEOUT_DEFAULT_US;
|
||
|
}
|
||
|
|
||
|
int chip_i2c_xfer(const int port,
|
||
|
const uint16_t slave_addr_flags,
|
||
|
const uint8_t *out, int out_size,
|
||
|
uint8_t *in, int in_size, int flags)
|
||
|
{
|
||
|
volatile struct i2c_status *p_status;
|
||
|
int ctrl = i2c_port_to_controller(port);
|
||
|
|
||
|
/* Return error if i2c_port_to_controller() returned an error */
|
||
|
if (ctrl < 0)
|
||
|
return EC_ERROR_INVAL;
|
||
|
|
||
|
/* Skip unnecessary transaction */
|
||
|
if (out_size == 0 && in_size == 0)
|
||
|
return EC_SUCCESS;
|
||
|
|
||
|
p_status = i2c_stsobjs + ctrl;
|
||
|
|
||
|
/* Assign current task ID */
|
||
|
p_status->task_waiting = task_get_current();
|
||
|
|
||
|
/* Select port for multi-ports i2c controller */
|
||
|
i2c_select_port(port);
|
||
|
|
||
|
/* Copy data to controller 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;
|
||
|
p_status->slave_addr_flags = slave_addr_flags;
|
||
|
|
||
|
/* 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) &&
|
||
|
/* Ignore busy bus for repeated start */
|
||
|
p_status->oper_state != SMB_WRITE_SUSPEND &&
|
||
|
(i2c_bus_busy(ctrl)
|
||
|
|| (i2c_get_line_levels(port) != I2C_LINE_IDLE))) {
|
||
|
int ret;
|
||
|
|
||
|
/* Attempt to unwedge the i2c port */
|
||
|
ret = i2c_unwedge(port);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
p_status->err_code = SMB_BUS_BUSY;
|
||
|
/* recover i2c controller */
|
||
|
i2c_recovery(ctrl, p_status);
|
||
|
/* Select port again for recovery */
|
||
|
i2c_select_port(port);
|
||
|
}
|
||
|
|
||
|
CPUTS("\n");
|
||
|
|
||
|
/* Start master transaction */
|
||
|
i2c_master_transaction(ctrl);
|
||
|
|
||
|
/* Reset task ID */
|
||
|
p_status->task_waiting = TASK_ID_INVALID;
|
||
|
|
||
|
CPRINTS("-Err:0x%02x", 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.
|
||
|
* Please notice we cannot read voltage level from GPIO in M4 EC
|
||
|
*/
|
||
|
if (get_scl_from_i2c_port(port, &g) == EC_SUCCESS) {
|
||
|
if (i2c_is_raw_mode(port))
|
||
|
return gpio_get_level(g);
|
||
|
else
|
||
|
return IS_BIT_SET(NPCX_SMBCTL3(
|
||
|
i2c_port_to_controller(port)), NPCX_SMBCTL3_SCL_LVL);
|
||
|
}
|
||
|
|
||
|
/* 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.
|
||
|
* Please notice we cannot read voltage level from GPIO in M4 EC
|
||
|
*/
|
||
|
if (get_sda_from_i2c_port(port, &g) == EC_SUCCESS) {
|
||
|
if (i2c_is_raw_mode(port))
|
||
|
return gpio_get_level(g);
|
||
|
else
|
||
|
return IS_BIT_SET(NPCX_SMBCTL3(
|
||
|
i2c_port_to_controller(port)), NPCX_SMBCTL3_SDA_LVL);
|
||
|
}
|
||
|
|
||
|
|
||
|
/* If no SDA pin defined for this port, then return 1 to appear idle */
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
/*****************************************************************************/
|
||
|
/* Hooks */
|
||
|
static void i2c_freq_changed(void)
|
||
|
{
|
||
|
int freq, i, j;
|
||
|
|
||
|
for (i = 0; i < i2c_ports_used; i++) {
|
||
|
int bus_freq = i2c_ports[i].kbps;
|
||
|
int ctrl = i2c_port_to_controller(i2c_ports[i].port);
|
||
|
int scl_freq;
|
||
|
|
||
|
if (ctrl < 2)
|
||
|
#if defined(CHIP_FAMILY_NPCX7)
|
||
|
/* SMB0/1 use APB3 clock */
|
||
|
freq = clock_get_apb3_freq();
|
||
|
#else
|
||
|
/* SMB0/1 use core clock */
|
||
|
freq = clock_get_freq();
|
||
|
#endif
|
||
|
else
|
||
|
/* Other SMB controller use APB2 clock */
|
||
|
freq = clock_get_apb2_freq();
|
||
|
|
||
|
/*
|
||
|
* Set SCL frequency by formula:
|
||
|
* tSCL = 4 * SCLFRQ * tCLK
|
||
|
* fSCL = fCLK / (4*SCLFRQ)
|
||
|
* SCLFRQ = ceil(fCLK/(4*fSCL))
|
||
|
*/
|
||
|
scl_freq = DIV_ROUND_UP(freq, bus_freq*4000); /* Unit in bps */
|
||
|
|
||
|
/* Normal mode if i2c freq is under 100kHz */
|
||
|
if (bus_freq <= 100) {
|
||
|
/* Set divider value of SCL */
|
||
|
SET_FIELD(NPCX_SMBCTL2(ctrl), NPCX_SMBCTL2_SCLFRQ7_FIELD
|
||
|
, (scl_freq & 0x7F));
|
||
|
SET_FIELD(NPCX_SMBCTL3(ctrl), NPCX_SMBCTL3_SCLFRQ2_FIELD
|
||
|
, (scl_freq >> 7));
|
||
|
} else {
|
||
|
const struct i2c_timing *pTiming;
|
||
|
int i2c_timing_used;
|
||
|
|
||
|
/* use Fast Mode */
|
||
|
SET_BIT(NPCX_SMBCTL3(ctrl) , NPCX_SMBCTL3_400K);
|
||
|
/*
|
||
|
* Set SCLH(L)T and hold-time directly for best i2c
|
||
|
* timing condition for all source clocks. Please refer
|
||
|
* Section 7.5.9 "SMBus Timing - Fast Mode" for detail.
|
||
|
*/
|
||
|
if (bus_freq == 400) {
|
||
|
pTiming = i2c_400k_timings;
|
||
|
i2c_timing_used = i2c_400k_timing_used;
|
||
|
} else if (bus_freq == 1000) {
|
||
|
pTiming = i2c_1m_timings;
|
||
|
i2c_timing_used = i2c_1m_timing_used;
|
||
|
} else {
|
||
|
/* Set value from formula */
|
||
|
NPCX_SMBSCLLT(ctrl) = scl_freq;
|
||
|
NPCX_SMBSCLHT(ctrl) = scl_freq;
|
||
|
cprints(CC_I2C, "Warning: Use 400K or 1MHz "
|
||
|
"for better timing of I2c %d", ctrl);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
for (j = 0; j < i2c_timing_used; j++, pTiming++) {
|
||
|
if (pTiming->clock == (freq/SECOND)) {
|
||
|
/* Set SCLH(L)T and hold-time */
|
||
|
NPCX_SMBSCLLT(ctrl) = pTiming->k1/2;
|
||
|
NPCX_SMBSCLHT(ctrl) = pTiming->k2/2;
|
||
|
SET_FIELD(NPCX_SMBCTL4(ctrl),
|
||
|
NPCX_SMBCTL4_HLDT_FIELD, pTiming->HLDT);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (j == i2c_timing_used)
|
||
|
cprints(CC_I2C, "Error: Please make sure src "
|
||
|
"clock of i2c %d is supported", ctrl);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
DECLARE_HOOK(HOOK_FREQ_CHANGE, i2c_freq_changed, HOOK_PRIO_DEFAULT);
|
||
|
|
||
|
static void i2c_init(void)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
/* 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);
|
||
|
#if defined(CHIP_FAMILY_NPCX7)
|
||
|
clock_enable_peripheral(CGC_OFFSET_I2C2, CGC_I2C_MASK2,
|
||
|
CGC_MODE_RUN | CGC_MODE_SLEEP);
|
||
|
#endif
|
||
|
|
||
|
/* Set I2C freq */
|
||
|
i2c_freq_changed();
|
||
|
/*
|
||
|
* initialize smb status and register
|
||
|
*/
|
||
|
for (i = 0; i < i2c_ports_used; i++) {
|
||
|
volatile struct i2c_status *p_status;
|
||
|
int port = i2c_ports[i].port;
|
||
|
int ctrl = i2c_port_to_controller(port);
|
||
|
|
||
|
/* ignore the port if i2c_port_to_controller() failed */
|
||
|
if (ctrl < 0)
|
||
|
continue;
|
||
|
|
||
|
p_status = i2c_stsobjs + ctrl;
|
||
|
|
||
|
/* status init */
|
||
|
p_status->oper_state = SMB_IDLE;
|
||
|
|
||
|
/* Reset task ID */
|
||
|
p_status->task_waiting = TASK_ID_INVALID;
|
||
|
|
||
|
/* Use default timeout. */
|
||
|
i2c_set_timeout(port, 0);
|
||
|
|
||
|
/* Init the SMB module */
|
||
|
i2c_init_bus(ctrl);
|
||
|
}
|
||
|
}
|
||
|
DECLARE_HOOK(HOOK_INIT, i2c_init, HOOK_PRIO_INIT_I2C);
|
||
|
|