blob: 03b48c28645beff4de4c364d27a075432fed7b03 [file] [log] [blame]
/* 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);