blob: 0c9947c1cb25d4a34315b603d62a4f5274456304 [file] [log] [blame]
/* Copyright 2013-2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define _GNU_SOURCE /* for aspritnf */
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <stdbool.h>
#include <stdint.h>
#include <dirent.h>
#include <sys/param.h>
#include <string.h>
#include <sys/ioctl.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <ccan/list/list.h>
#include "opal-prd.h"
#include "module.h"
#include "i2c.h"
struct i2c_bus {
uint32_t chip_id;
uint8_t engine;
uint8_t port;
const char *devpath;
int fd;
struct list_node link;
};
static struct list_head bus_list = LIST_HEAD_INIT(bus_list);
static int i2c_get_dev(uint32_t chip, uint8_t eng, uint8_t port, uint16_t dev)
{
struct i2c_bus *b, *bus = NULL;
list_for_each(&bus_list, b, link) {
if (b->chip_id == chip && b->engine == eng && b->port == port) {
bus = b;
break;
}
}
if (!bus) {
pr_log(LOG_WARNING, "I2C: Bus %08x/%d/%d not found",
chip, eng, port);
return -1;
}
if (bus->fd < 0) {
bus->fd = open(bus->devpath, O_RDWR);
if (bus->fd < 0) {
pr_log(LOG_ERR, "I2C: Failed to open %s: %m",
bus->devpath);
return -1;
}
}
/* XXX We could use the I2C_SLAVE ioctl to check if the device
* is currently in use by a kernel driver...
*/
return bus->fd;
}
int i2c_read(uint32_t chip_id, uint8_t engine, uint8_t port,
uint16_t device, uint32_t offset_size, uint32_t offset,
uint32_t length, void* data)
{
struct i2c_rdwr_ioctl_data ioargs;
struct i2c_msg msgs[2];
uint8_t obuf[4];
int fd, i, midx = 0;
if (offset_size > 4) {
pr_log(LOG_ERR, "I2C: Invalid write offset_size %d",
offset_size);
return -1;
}
fd = i2c_get_dev(chip_id, engine, port, device);
if (fd == -1)
return -1;
/* If we have an offset, build a message for it */
if (offset_size) {
/* The offset has a variable size so let's handle this properly
* as it has to be laid out in memory MSB first
*/
for (i = 0; i < offset_size; i++)
obuf[i] = offset >> (8 * (offset_size - i - 1));
msgs[0].addr = device;
msgs[0].flags = 0;
msgs[0].buf = obuf;
msgs[0].len = offset_size;
midx = 1;
}
/* Build the message for the data portion */
msgs[midx].addr = device;
msgs[midx].flags = I2C_M_RD;
msgs[midx].buf = data;
msgs[midx].len = length;
midx++;
ioargs.msgs = msgs;
ioargs.nmsgs = midx;
if (ioctl(fd, I2C_RDWR, &ioargs) < 0) {
pr_log(LOG_ERR, "I2C: Read error: %m");
return -1;
}
pr_debug("I2C: Read from %08x:%d:%d@%02x+0x%x %d bytes ok",
chip_id, engine, port, device, offset_size ? offset : 0, length);
return 0;
}
int i2c_write(uint32_t chip_id, uint8_t engine, uint8_t port,
uint16_t device, uint32_t offset_size, uint32_t offset,
uint32_t length, void* data)
{
struct i2c_rdwr_ioctl_data ioargs;
struct i2c_msg msg;
int fd, size, i, rc;
uint8_t *buf;
if (offset_size > 4) {
pr_log(LOG_ERR, "I2C: Invalid write offset_size %d",
offset_size);
return -1;
}
fd = i2c_get_dev(chip_id, engine, port, device);
if (fd == -1)
return -1;
/* Not all kernel driver versions support breaking up a write into
* two components (offset, data), so we coalesce them first and
* issue a single write. The offset is laid out in BE format.
*/
size = offset_size + length;
buf = malloc(size);
if (!buf) {
pr_log(LOG_ERR, "I2C: Out of memory");
return -1;
}
/* The offset has a variable size so let's handle this properly
* as it has to be laid out in memory MSB first
*/
for (i = 0; i < offset_size; i++)
buf[i] = offset >> (8 * (offset_size - i - 1));
/* Copy the remaining data */
memcpy(buf + offset_size, data, length);
/* Build the message */
msg.addr = device;
msg.flags = 0;
msg.buf = buf;
msg.len = size;
ioargs.msgs = &msg;
ioargs.nmsgs = 1;
rc = ioctl(fd, I2C_RDWR, &ioargs);
free(buf);
if (rc < 0) {
pr_log(LOG_ERR, "I2C: Write error: %m");
return -1;
}
return 0;
}
static void i2c_add_bus(uint32_t chip, uint32_t engine, uint32_t port,
const char *devname)
{
struct i2c_bus *b = malloc(sizeof(struct i2c_bus));
char *dn;
if (asprintf(&dn, "/dev/%s", devname) < 0) {
pr_log(LOG_ERR, "I2C: Error creating devpath for %s: %m",
devname);
free(b);
return;
}
memset(b, 0, sizeof(*b));
b->chip_id = chip;
b->engine = engine;
b->port = port;
b->devpath = dn;
b->fd = -1;
list_add(&bus_list, &b->link);
}
void i2c_init(void)
{
#define SYSFS "/sys" /* XXX Find it ? */
DIR *devsdir;
struct dirent *devent;
char dpath[PATH_MAX];
char busname[256];
char *s;
FILE *f;
unsigned int chip, engine, port;
/* Ensure i2c-dev is loaded */
insert_module("i2c-dev");
/* Get directory of i2c char devs in sysfs */
devsdir = opendir(SYSFS "/class/i2c-dev");
if (!devsdir) {
pr_log(LOG_ERR, "I2C: Error opening "
SYSFS "/class/i2c-dev: %m");
return;
}
while ((devent = readdir(devsdir)) != NULL) {
if (!strcmp(devent->d_name, "."))
continue;
if (!strcmp(devent->d_name, ".."))
continue;
/* Get bus name */
sprintf(dpath, SYSFS "/class/i2c-dev/%s/name", devent->d_name);
f = fopen(dpath, "r");
if (!f) {
pr_log(LOG_NOTICE, "I2C: Can't open %s: %m, skipping.",
dpath);
continue;
}
s = fgets(busname, sizeof(busname), f);
fclose(f);
if (!s) {
pr_log(LOG_NOTICE, "Failed to read %s, skipping.",
dpath);
continue;
}
/* Is this a P8 or Centaur i2c bus ? No -> move on */
if (strncmp(s, "p8_", 3) == 0)
sscanf(s, "p8_%x_e%dp%d", &chip, &engine, &port);
else if (strncmp(s, "cen_", 4) == 0)
sscanf(s, "cen_%x_e%dp%d", &chip, &engine, &port);
else
continue;
pr_log(LOG_INFO, "I2C: Found Chip: %08x engine %d port %d",
chip, engine, port);
i2c_add_bus(chip, engine, port, devent->d_name);
}
closedir(devsdir);
}