Files
OpenCellular/common/i2c.c
Randall Spangler c0fbbaefed cleanup: comments in i2c modules
No code changes; just update comments with bug links

BUG=none
BRANCH=none
TEST=build all platforms

Change-Id: I8b845f9c43315b7db5a746a16c6618c3ee96979d
Signed-off-by: Randall Spangler <rspangler@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/174614
Reviewed-by: Bill Richardson <wfrichar@chromium.org>
2013-10-25 01:32:31 +00:00

471 lines
11 KiB
C

/* Copyright (c) 2013 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 cross-platform code for Chrome EC */
#include "clock.h"
#include "console.h"
#include "host_command.h"
#include "i2c.h"
#include "system.h"
#include "task.h"
#include "util.h"
#include "watchdog.h"
#define CPUTS(outstr) cputs(CC_I2C, outstr)
#define CPRINTF(format, args...) cprintf(CC_I2C, format, ## args)
static struct mutex port_mutex[I2C_PORT_COUNT];
void i2c_lock(int port, int lock)
{
if (lock) {
/* Don't allow deep sleep when I2C port is locked */
disable_sleep(SLEEP_MASK_I2C);
mutex_lock(port_mutex + port);
} else {
mutex_unlock(port_mutex + port);
/* Allow deep sleep again after I2C port is unlocked */
enable_sleep(SLEEP_MASK_I2C);
}
}
int i2c_read16(int port, int slave_addr, int offset, int *data)
{
int rv;
uint8_t reg, buf[2];
reg = offset & 0xff;
/* I2C read 16-bit word: transmit 8-bit offset, and read 16bits */
i2c_lock(port, 1);
rv = i2c_xfer(port, slave_addr, &reg, 1, buf, 2, I2C_XFER_SINGLE);
i2c_lock(port, 0);
if (rv)
return rv;
if (slave_addr & I2C_FLAG_BIG_ENDIAN)
*data = ((int)buf[0] << 8) | buf[1];
else
*data = ((int)buf[1] << 8) | buf[0];
return EC_SUCCESS;
}
int i2c_write16(int port, int slave_addr, int offset, int data)
{
int rv;
uint8_t buf[3];
buf[0] = offset & 0xff;
if (slave_addr & I2C_FLAG_BIG_ENDIAN) {
buf[1] = (data >> 8) & 0xff;
buf[2] = data & 0xff;
} else {
buf[1] = data & 0xff;
buf[2] = (data >> 8) & 0xff;
}
i2c_lock(port, 1);
rv = i2c_xfer(port, slave_addr, buf, 3, NULL, 0, I2C_XFER_SINGLE);
i2c_lock(port, 0);
return rv;
}
int i2c_read8(int port, int slave_addr, int offset, int *data)
{
int rv;
/* We use buf[1] here so it's aligned for DMA on STM32 */
uint8_t reg, buf[1];
reg = offset;
i2c_lock(port, 1);
rv = i2c_xfer(port, slave_addr, &reg, 1, buf, 1, I2C_XFER_SINGLE);
i2c_lock(port, 0);
if (!rv)
*data = buf[0];
return rv;
}
int i2c_write8(int port, int slave_addr, int offset, int data)
{
int rv;
uint8_t buf[2];
buf[0] = offset;
buf[1] = data;
i2c_lock(port, 1);
rv = i2c_xfer(port, slave_addr, buf, 2, 0, 0, I2C_XFER_SINGLE);
i2c_lock(port, 0);
return rv;
}
/*****************************************************************************/
/* Host commands */
/*
* TODO(crosbug.com/p/23570): remove separate read and write commands, as soon
* as ectool supports EC_CMD_I2C_PASSTHRU.
*/
static int i2c_command_read(struct host_cmd_handler_args *args)
{
const struct ec_params_i2c_read *p = args->params;
struct ec_response_i2c_read *r = args->response;
int data, rv = -1;
#ifdef CONFIG_I2C_PASSTHRU_RESTRICTED
if (system_is_locked())
return EC_RES_ACCESS_DENIED;
#endif
if (p->read_size == 16)
rv = i2c_read16(p->port, p->addr, p->offset, &data);
else if (p->read_size == 8)
rv = i2c_read8(p->port, p->addr, p->offset, &data);
if (rv)
return EC_RES_ERROR;
r->data = data;
args->response_size = sizeof(*r);
return EC_RES_SUCCESS;
}
DECLARE_HOST_COMMAND(EC_CMD_I2C_READ, i2c_command_read, EC_VER_MASK(0));
static int i2c_command_write(struct host_cmd_handler_args *args)
{
const struct ec_params_i2c_write *p = args->params;
int rv = -1;
#ifdef CONFIG_I2C_PASSTHRU_RESTRICTED
if (system_is_locked())
return EC_RES_ACCESS_DENIED;
#endif
if (p->write_size == 16)
rv = i2c_write16(p->port, p->addr, p->offset, p->data);
else if (p->write_size == 8)
rv = i2c_write8(p->port, p->addr, p->offset, p->data);
if (rv)
return EC_RES_ERROR;
return EC_RES_SUCCESS;
}
DECLARE_HOST_COMMAND(EC_CMD_I2C_WRITE, i2c_command_write, EC_VER_MASK(0));
#ifdef CONFIG_I2C_DEBUG_PASSTHRU
#define PTHRUPRINTF(format, args...) CPRINTF(format, ## args)
#else
#define PTHRUPRINTF(format, args...)
#endif
/**
* Perform the voluminous checking required for this message
*
* @param args Arguments
* @return 0 if OK, EC_RES_INVALID_PARAM on error
*/
static int check_i2c_params(const struct host_cmd_handler_args *args)
{
const struct ec_params_i2c_passthru *params = args->params;
const struct ec_params_i2c_passthru_msg *msg;
int read_len = 0, write_len = 0;
unsigned int size;
int msgnum;
if (args->params_size < sizeof(*params)) {
PTHRUPRINTF("[%T i2c passthru no params, params_size=%d, need at least %d]\n",
args->params_size, sizeof(*params));
return EC_RES_INVALID_PARAM;
}
size = sizeof(*params) + params->num_msgs * sizeof(*msg);
if (args->params_size < size) {
PTHRUPRINTF("[%T i2c passthru params_size=%d, need at least %d]\n",
args->params_size, size);
return EC_RES_INVALID_PARAM;
}
if (params->port >= I2C_PORT_COUNT) {
PTHRUPRINTF("[%T i2c passthru invalid port %d]\n",
params->port);
return EC_RES_INVALID_PARAM;
}
/* Loop and process messages */;
for (msgnum = 0, msg = params->msg; msgnum < params->num_msgs;
msgnum++, msg++) {
unsigned int addr_flags = msg->addr_flags;
/* Parse slave address if necessary */
if (addr_flags & EC_I2C_FLAG_10BIT) {
/* 10-bit addressing not supported yet */
PTHRUPRINTF("[%T i2c passthru no 10-bit addressing]\n");
return EC_RES_INVALID_PARAM;
}
PTHRUPRINTF("[%T i2c passthru port=%d, %s, addr=0x%02x, len=0x%02x]\n",
params->port,
addr_flags & EC_I2C_FLAG_READ ? "read" : "write",
addr_flags & EC_I2C_ADDR_MASK,
msg->len);
if (addr_flags & EC_I2C_FLAG_READ)
read_len += msg->len;
else
write_len += msg->len;
}
/* Check there is room for the data */
if (args->response_max <
sizeof(struct ec_response_i2c_passthru) + read_len) {
PTHRUPRINTF("[%T i2c passthru overflow1]\n");
return EC_RES_INVALID_PARAM;
}
/* Must have bytes to write */
if (args->params_size < size + write_len) {
PTHRUPRINTF("[%T i2c passthru overflow2]\n");
return EC_RES_INVALID_PARAM;
}
return EC_RES_SUCCESS;
}
static int i2c_command_passthru(struct host_cmd_handler_args *args)
{
const struct ec_params_i2c_passthru *params = args->params;
const struct ec_params_i2c_passthru_msg *msg;
struct ec_response_i2c_passthru *resp = args->response;
const uint8_t *out;
int in_len;
int ret;
#ifdef CONFIG_I2C_PASSTHRU_RESTRICTED
if (system_is_locked())
return EC_RES_ACCESS_DENIED;
#endif
ret = check_i2c_params(args);
if (ret)
return ret;
/* Loop and process messages */
resp->i2c_status = 0;
out = args->params + sizeof(*params) + params->num_msgs * sizeof(*msg);
in_len = 0;
i2c_lock(params->port, 1);
for (resp->num_msgs = 0, msg = params->msg;
resp->num_msgs < params->num_msgs;
resp->num_msgs++, msg++) {
/* EC uses 8-bit slave address */
unsigned int addr = (msg->addr_flags & EC_I2C_ADDR_MASK) << 1;
int xferflags = I2C_XFER_START;
int read_len = 0, write_len = 0;
int rv;
if (msg->addr_flags & EC_I2C_FLAG_READ)
read_len = msg->len;
else
write_len = msg->len;
/* Set stop bit for last message */
if (resp->num_msgs == params->num_msgs - 1)
xferflags |= I2C_XFER_STOP;
/* Transfer next message */
PTHRUPRINTF("[%T i2c passthru xfer port=%x, addr=%x, out=%p, write_len=%x, data=%p, read_len=%x, xferflags=%x]\n",
params->port, addr, out, write_len,
&resp->data[in_len], read_len, xferflags);
rv = i2c_xfer(params->port, addr, out, write_len,
&resp->data[in_len], read_len, xferflags);
if (rv) {
/* Driver will have sent a stop bit here */
if (rv == EC_ERROR_TIMEOUT)
resp->i2c_status = EC_I2C_STATUS_TIMEOUT;
else
resp->i2c_status = EC_I2C_STATUS_NAK;
break;
}
in_len += read_len;
out += write_len;
}
args->response_size = sizeof(*resp) + in_len;
/* Unlock port */
i2c_lock(params->port, 0);
/*
* Return success even if transfer failed so response is sent. Host
* will check message status to determine the transfer result.
*/
return EC_RES_SUCCESS;
}
DECLARE_HOST_COMMAND(EC_CMD_I2C_PASSTHRU, i2c_command_passthru, EC_VER_MASK(0));
/*****************************************************************************/
/* Console commands */
static void scan_bus(int port, const char *desc)
{
int a;
uint8_t tmp;
ccprintf("Scanning %d %s", port, desc);
/* Don't scan a busy port, since reads will just fail / time out */
a = i2c_get_line_levels(port);
if (a != I2C_LINE_IDLE) {
ccprintf(": port busy (SDA=%d, SCL=%d)\n",
(a & I2C_LINE_SDA_HIGH) ? 1 : 0,
(a & I2C_LINE_SCL_HIGH) ? 1 : 0);
return;
}
i2c_lock(port, 1);
for (a = 0; a < 0x100; a += 2) {
watchdog_reload(); /* Otherwise a full scan trips watchdog */
ccputs(".");
#ifdef CHIP_FAMILY_stm32f
/*
* TODO(crosbug.com/p/23569): The i2c_xfer() implementation on
* STM32F can't read a byte without writing one first. So
* write a byte and hope nothing bad happens. Remove this
* workaround when STM32F is fixed.
*/
tmp = 0;
if (!i2c_xfer(port, a, &tmp, 1, &tmp, 1, I2C_XFER_SINGLE))
#else
/* Do a single read */
if (!i2c_xfer(port, a, NULL, 0, &tmp, 1, I2C_XFER_SINGLE))
#endif
ccprintf("\n 0x%02x", a);
}
i2c_lock(port, 0);
ccputs("\n");
}
static int command_scan(int argc, char **argv)
{
int i;
for (i = 0; i < i2c_ports_used; i++)
scan_bus(i2c_ports[i].port, i2c_ports[i].name);
return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(i2cscan, command_scan,
NULL,
"Scan I2C ports for devices",
NULL);
static int command_i2cxfer(int argc, char **argv)
{
int rw = 0;
int port, slave_addr, offset;
int value = 0;
char *e;
int rv = 0;
if (argc < 5) {
ccputs("Usage: i2cxfer r/r16/w/w16 port addr offset [value]\n");
return EC_ERROR_UNKNOWN;
}
if (strcasecmp(argv[1], "r") == 0) {
rw = 0;
} else if (strcasecmp(argv[1], "r16") == 0) {
rw = 1;
} else if (strcasecmp(argv[1], "w") == 0) {
rw = 2;
} else if (strcasecmp(argv[1], "w16") == 0) {
rw = 3;
} else {
ccputs("Invalid rw mode : r / w / r16 / w16\n");
return EC_ERROR_INVAL;
}
port = strtoi(argv[2], &e, 0);
if (*e) {
ccputs("Invalid port\n");
return EC_ERROR_INVAL;
}
slave_addr = strtoi(argv[3], &e, 0);
if (*e) {
ccputs("Invalid slave_addr\n");
return EC_ERROR_INVAL;
}
offset = strtoi(argv[4], &e, 0);
if (*e) {
ccputs("Invalid addr\n");
return EC_ERROR_INVAL;
}
if (rw > 1) {
if (argc < 6) {
ccputs("No write value\n");
return EC_ERROR_INVAL;
}
value = strtoi(argv[5], &e, 0);
if (*e) {
ccputs("Invalid write value\n");
return EC_ERROR_INVAL;
}
}
switch (rw) {
case 0:
rv = i2c_read8(port, slave_addr, offset, &value);
break;
case 1:
rv = i2c_read16(port, slave_addr, offset, &value);
break;
case 2:
rv = i2c_write8(port, slave_addr, offset, value);
break;
case 3:
rv = i2c_write16(port, slave_addr, offset, value);
break;
}
if (rv) {
ccprintf("i2c command failed\n", rv);
return rv;
}
if (rw == 0)
ccprintf("0x%02x [%d]\n", value);
else if (rw == 1)
ccprintf("0x%04x [%d]\n", value);
ccputs("ok\n");
return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(i2cxfer, command_i2cxfer,
"r/r16/w/w16 port addr offset [value]",
"Read write I2C",
NULL);