blob: e9829458028a7473726d623f27cc887f89e30ea1 [file] [log] [blame]
/*
* Copyright 2012, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* i2c.c: I2C bus access via Linux I2C IOCTL interface.
*/
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <inttypes.h>
#include <string.h>
#include <dirent.h>
#include <sys/ioctl.h>
#include "mosys/alloc.h"
#include "mosys/globals.h"
#include "mosys/log.h"
#include "mosys/list.h"
#include "mosys/platform.h"
#include "intf/i2c.h"
#include "lib/math.h"
#include "lib/string.h"
/* these are exposed only to make testing easier */
#define I2C_DEV_ROOT "/dev"
#define I2C_SYS_ROOT "/sys/bus/i2c/devices"
/*
* Block writes need recovery delays for internal processing,
* according to e.g. the datasheets for Atmel EEPROMs.
* These are mildly pessimistic defaults.
*/
#define BLOCK_WRITE_DELAY 1000
#define BLOCK_WRITE_RETRIES 32
struct i2c_handle {
struct i2c_addr addr;
int fd;
} i2c_handles[I2C_HANDLE_MAX];
static int i2c_handle_num = 0;
/*
* i2c_open_dev - Open connection to I2C slave address
*
* @intf: platform interface
* @bus: I2C bus/adapter
* @address: I2C slave address
*
* returns handle for open I2C device
* returns <0 to indicate error
*/
static int i2c_open_dev(struct platform_intf *intf, int bus, int address)
{
char devf[512];
int handle, fd;
if (bus < 0 || bus > 32) {
lprintf(LOG_NOTICE, "Invalid I2C bus %d\n", bus);
return -1;
}
if (i2c_handle_num >= I2C_HANDLE_MAX) {
lprintf(LOG_NOTICE, "Out of I2C handles\n");
return -1;
}
for (handle = 0; handle < i2c_handle_num; handle++) {
if (i2c_handles[handle].addr.bus == bus &&
i2c_handles[handle].addr.addr == address &&
i2c_handles[handle].fd >= 0)
return handle;
}
snprintf(devf, sizeof(devf), "%s/i2c-%d",
intf->op->i2c->dev_root, bus);
fd = open(devf, O_RDWR);
if (fd < 0) {
lperror(LOG_DEBUG, "Unable to open I2C device %s", devf);
return -1;
}
#if defined (__linux__)
if (ioctl(fd, I2C_SLAVE, address) < 0) {
lperror(LOG_NOTICE, "Unable to set I2C slave address to 0x%02x",
address);
close(fd);
return -1;
}
#else
return -ENOSYS;
#endif
i2c_handles[i2c_handle_num].addr.bus = bus;
i2c_handles[i2c_handle_num].addr.addr = address;
i2c_handles[i2c_handle_num].fd = fd;
lprintf(LOG_DEBUG, "Opened I2C handle %d to %d-%02x (fd %d)\n",
i2c_handle_num, bus, address, fd);
return i2c_handle_num++;
}
/*
* i2c_close_dev - Close all open I2C handles
*
* @intf: platform interface
* @handle: I2C handle to close, -1 for all
*/
static void i2c_close_dev(struct platform_intf *intf)
{
int i;
// close all handles
for (i = 0; i < i2c_handle_num; i++) {
close(i2c_handles[i].fd);
i2c_handles[i].fd = -1;
i2c_handles[i].addr.bus = -1;
i2c_handles[i].addr.addr = -1;
}
i2c_handle_num = 0;
/* we store these as const in the structure */
free((char *)intf->op->i2c->sys_root);
free((char *)intf->op->i2c->dev_root);
}
static int i2c_transfer(struct platform_intf *intf, int bus, int address,
const void *outdata, int outsize,
const void *indata, int insize)
{
int ret = -1;
struct i2c_rdwr_ioctl_data data;
struct i2c_msg *msg = NULL;
int handle, fd;
/* open connection to i2c slave */
handle = i2c_open_dev(intf, bus, address);
if (handle < 0)
return -1;
fd = i2c_handles[handle].fd;
data.nmsgs = 0;
if (outsize) {
msg = mosys_realloc(msg, sizeof(*msg) * (data.nmsgs + 1));
msg[data.nmsgs].addr = address;
msg[data.nmsgs].flags = 0;
msg[data.nmsgs].len = outsize;
msg[data.nmsgs].buf = (char *)outdata;
data.nmsgs++;
}
if (insize) {
msg = mosys_realloc(msg, sizeof(*msg) * (data.nmsgs + 1));
msg[data.nmsgs].addr = address;
msg[data.nmsgs].flags = I2C_M_RD;
msg[data.nmsgs].len = insize;
msg[data.nmsgs].buf = (char *)indata;
data.nmsgs++;
}
data.msgs = msg;
/* send command to EC and read answer */
#if defined (__linux__)
/* ioctl returns negative errno, else the number of messages executed */
ret = ioctl(fd, I2C_RDWR, &data);
#else
ret = -ENOSYS;
#endif
if (ret < 0) {
lperror(LOG_ERR, "i2c transfer failed");
ret = -1;
} else if (ret != data.nmsgs) {
lprintf(LOG_ERR, "ioctl executed wrong number of messages\n");
ret = -1;
} else {
ret = 0;
}
free(msg);
return ret;
}
static int smbus_read_reg(struct platform_intf *intf, int bus,
int address, int reg, int length, void *data)
{
int handle, fd, i;
int32_t result;
static int read_words = 1;
/* limit to 256 bytes at a time */
if (length < 1 || length > 256) {
lprintf(LOG_NOTICE, "Invalid I2C read length: %d\n", length);
return -1;
}
lprintf(LOG_DEBUG,
"i2c_read_dev: Reading %d bytes from %d-%02x at %02x\n", length,
bus, address, reg);
/* open connection to i2c slave */
handle = i2c_open_dev(intf, bus, address);
if (handle < 0)
return -1;
fd = i2c_handles[handle].fd;
memset(data, 0, length);
i = 0;
while (i < length) {
if (read_words && (i < length - 1)) {
/* Do 2-byte reads whenever possible */
result = i2c_smbus_read_word_data(fd, reg + i);
if (result < 0) {
if (read_words) {
/* try again with byte read */
read_words = 0;
continue;
}
// COV_NF_START
lperror(LOG_NOTICE,
"Failed to read I2C register 0x%02x "
"from i2c-%d-%02x", reg + i, bus,
address);
break;
// COV_NF_END
}
memcpy(data + i, &result, 2);
i = i + 2;
} else {
/* Do an 1-byte read otherwise*/
result = i2c_smbus_read_byte_data(fd, reg + i);
if (result < 0) {
lperror(LOG_NOTICE,
"Failed to read I2C register 0x%02x "
"from i2c-%d-%02x", reg + i, bus,
address);
break;
}
memcpy(data + i, &result, 1);
i++;
}
}
return i;
}
/*
* We can't actually use i2c_smbus_read_block_data() because the driver
* doesn't know how to do the 2-byte address write. So we do the best we can
* by performing the address write once, then calling i2c_smbus_read_byte()
* repeatedly to keep the overhead to a minimum.
*/
static int smbus_read16_dev(struct platform_intf *intf, int bus,
int address, int reg, int length, void *data)
{
uint8_t hi, lo;
int fd, handle, i;
int32_t result;
uint8_t *dp = data;
memset(data, 0, length);
lprintf(LOG_DEBUG,
"i2c_read16_dev: Reading %d bytes from %d-%02x at %02x\n",
length, bus, address, reg);
// open connection to i2c slave
handle = i2c_open_dev(intf, bus, address);
if (handle < 0)
return -1;
fd = i2c_handles[handle].fd;
// Write eeprom offset
hi = reg >> 8;
lo = reg & 0xff;
result = i2c_smbus_write_byte_data(fd, hi, lo);
if (result < 0) {
lperror(LOG_NOTICE,
"Failed to write I2C register 0x%04x from i2c-%d-%02x",
reg, bus, address);
return -1;
}
// Read the block
for (i = 0; i < length; ++i) {
result = i2c_smbus_read_byte(fd);
if (result < 0) {
lperror(LOG_NOTICE,
"Failed to read I2C register 0x%04x "
"from i2c-%d-%02x", reg + i, bus, address);
return -1;
}
dp[i] = result;
}
return i;
}
static int smbus_read_raw(struct platform_intf *intf,
int bus, int address, int length, void *data)
{
int result, count;
int fd, handle;
uint8_t *dp = data;
lprintf(LOG_DEBUG,
"%s: Reading byte from %d-%02x\n",
__func__, bus, address);
/* open connection to i2c slave */
handle = i2c_open_dev(intf, bus, address);
if (handle < 0)
return -1;
fd = i2c_handles[handle].fd;
for (count = 0; count < length; count++) {
/* read byte */
result = i2c_smbus_read_byte(fd);
if (result < 0) {
lperror(LOG_NOTICE,
"%s: Failed to read from from i2c-%d-%02x",
__func__, bus, address);
break;
}
dp[count] = result;
}
return count;
}
static int smbus_write_reg(struct platform_intf *intf, int bus,
int address, int reg, int length, const void *data)
{
int handle, fd, i;
int32_t result;
const uint8_t *data_ptr = data;
if (length < 1 || length > 256) {
lprintf(LOG_NOTICE, "Invalid I2C write length: %d\n", length);
return -1;
}
lprintf(LOG_DEBUG,
"i2c_write_dev: Writing %d bytes to %d-%02x at %02x\n",
length, bus, address, reg);
/* open connection to i2c slave */
handle = i2c_open_dev(intf, bus, address);
if (handle < 0)
return -1;
fd = i2c_handles[handle].fd;
for (i = 0; i < length; i++) {
/* write one byte at a time */
result = i2c_smbus_write_byte_data(fd, reg + i, data_ptr[i]);
if (result < 0) {
lperror(LOG_NOTICE,
"Failed to write I2C register 0x%02x"
" from i2c-%d-%02x", reg, bus, address);
i = -1;
break;
}
}
return i;
}
/*
* The LSByte of the 16-bit register offset is sent as the first byte of
* the transfer buffer. Note the driver won't let us transfer more than
* 32 bytes, and since we're using one data byte as the low byte of the
* offset address, we have to perform the write in 31-byte chunks. Ugh.
*/
static int smbus_write16_buf(int handle, int reg, const uint8_t *dp, int len)
{
uint8_t buf[32];
int32_t result;
int fd = i2c_handles[handle].fd;
int bus = i2c_handles[handle].addr.bus;
int address = i2c_handles[handle].addr.addr;
int i;
memset(buf, 0, 32);
buf[0] = reg & 0xff; // LSByte of register offset
len = __min(len, 31);
memcpy(&buf[1], dp, len);
result = i2c_smbus_write_i2c_block_data(fd, reg >> 8, len+1, buf);
if (result < 0) {
lperror(LOG_NOTICE,
"Failed to write %d bytes "
"to I2C register 0x%04x from i2c-%d-%02x",
len, reg, bus, address);
return result;
}
// Spin waiting for device to recover.
for (i = 0; i < BLOCK_WRITE_RETRIES; ++i) {
usleep(BLOCK_WRITE_DELAY);
#if defined(__linux__)
if (ioctl(fd, I2C_SLAVE, address) == 0)
return result;
#else
return -ENOSYS;
#endif
}
lperror(LOG_NOTICE,
"i2c-%d-%02x didn't recover in %d usec\n",
bus, address,
BLOCK_WRITE_RETRIES * BLOCK_WRITE_DELAY);
return result;
}
static int smbus_write16_dev(struct platform_intf *intf,
int bus, int address, int reg,
int length, const void *data)
{
int handle, written = 0;
const uint8_t *data_ptr = (uint8_t *)data;
int buf_len, count;
// Open connection to this address
handle = i2c_open_dev(intf, bus, address);
if (handle < 0)
return -1;
if (length == 0) {
/* useful for setting internal address counters in devices */
written = smbus_write16_buf(handle, reg, NULL, 0);
}
while (length) {
buf_len = __min(length, 31);
count = smbus_write16_buf(handle, reg, data_ptr, buf_len);
if (count != buf_len)
return written;
length -= count;
written += count;
data_ptr += count;
reg += count;
if (length == 0)
return written;
buf_len = 1;
count = smbus_write16_buf(handle, reg, data_ptr, buf_len);
if (count != buf_len)
return written;
length -= buf_len;
written += buf_len;
data_ptr += buf_len;
reg += buf_len;
}
return written;
}
static int smbus_write_raw(struct platform_intf *intf,
int bus, int address, int length, void *data)
{
int handle, fd, count;
int32_t result;
const uint8_t *data_ptr = data;
lprintf(LOG_DEBUG,
"%s: Writing byte to %d-%02x\n",
__func__, bus, address);
/* open connection to i2c slave */
handle = i2c_open_dev(intf, bus, address);
if (handle < 0)
return -1;
fd = i2c_handles[handle].fd;
for (count = 0; count < length; count++) {
/* write byte */
result = i2c_smbus_write_byte(fd, data_ptr[count]);
if (result < 0) {
lperror(LOG_NOTICE,
"Failed to write byte to i2c-%d-%02x",
bus, address);
break;
}
}
return count;
}
static int i2c_find_driver(struct platform_intf *intf, const char *module)
{
char *path;
char s[80];
FILE *fp;
int len = strlen(module);
int ret = 0;
path = format_string("%s/proc/modules", mosys_get_root_prefix());
fp = fopen(path, "r");
free(path);
if (fp == NULL)
return 0;
len = __min(len, 80);
while (fgets(s, 80, fp) != NULL) {
if (!strncmp(s, module, len) && (len == strcspn(s, " "))) {
ret = 1;
break;
}
}
fclose(fp);
return ret;
}
static int i2c_match_bus_name(struct platform_intf *intf,
int bus, const char *name)
{
char *path;
char bus_name[64];
int fd;
int len = strlen(name);
int ret = 0;
path = format_string("%s/sys/class/i2c-dev/i2c-%d/name",
mosys_get_root_prefix(), bus);
fd = open(path, O_RDONLY);
free(path);
if (fd < 0)
return 0;
len = __min(len, 64);
memset(bus_name, 0, len);
if (read(fd, bus_name, len) == len) {
if (strncmp(bus_name, name, len) == 0)
ret = 1;
}
close(fd);
return ret;
}
/*
* i2c_find_dir - Find a /sys directory or directories for a device's
* description as printed in the /sys/bus/i2c/devices/<bus-addr>/name file.
* All matching descriptions are inserted into a linked list.
*/
static struct ll_node *i2c_find_sysfs_dir(struct platform_intf *intf,
const char *name)
{
char *path;
char s[32];
FILE *fp;
DIR *dp;
struct dirent *d;
struct ll_node *head = NULL;
int bus, addr;
int len = strlen(name);
if (!(dp = opendir(intf->op->i2c->sys_root))) {
lprintf(LOG_ERR, "Failed to open %s\n",
intf->op->i2c->sys_root);
return NULL;
}
while ((d = readdir(dp))) {
/* Scan for entries in the form of 0-0000 and filter out
* irrelevent entries like '.' and ".." */
if (!(sscanf(d->d_name, "%u-%x", &bus, &addr)))
continue;
/* Is this the type of device we want? */
path = format_string("%s/%u-%04x/name",
intf->op->i2c->sys_root, bus, addr);
fp = fopen(path, "r");
if (fp) {
fgets(s, sizeof(s), fp);
fclose(fp);
if (!strncmp(s, name, len)) { /* Found one! */
struct i2c_data *n;
n = mosys_malloc(sizeof(struct i2c_data));
/* Fill out the data structure */
n->addr.bus = bus;
n->addr.addr = addr;
n->dir = format_string("%s/%u-%04x",
intf->op->i2c->sys_root,
bus, addr);
head = list_insert_before(head, (void *)n);
}
} else {
lprintf(LOG_NOTICE, // COV_NF_LINE
"Failed to open %s\n", path);
}
free(path);
}
closedir(dp);
return head;
}
static int i2c_setup_dev(struct platform_intf *intf)
{
intf->op->i2c->sys_root
= format_string("%s/%s", mosys_get_root_prefix(), I2C_SYS_ROOT);
intf->op->i2c->dev_root
= format_string("%s/%s", mosys_get_root_prefix(), I2C_DEV_ROOT);
return 0;
}
/* I2C operations based on Linux /dev interface */
struct i2c_intf i2c_dev_intf = {
.setup = i2c_setup_dev,
.destroy = i2c_close_dev,
.i2c_transfer = i2c_transfer,
.smbus_read_reg = smbus_read_reg,
.smbus_write_reg = smbus_write_reg,
.smbus_read16 = smbus_read16_dev,
.smbus_write16 = smbus_write16_dev,
.smbus_read_raw = smbus_read_raw,
.smbus_write_raw = smbus_write_raw,
.find_driver = i2c_find_driver,
.find_sysfs_dir = i2c_find_sysfs_dir,
.match_bus = i2c_match_bus_name,
};