aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenjamin Herrenschmidt <benh@kernel.crashing.org>2014-10-28 15:57:51 +1100
committerBenjamin Herrenschmidt <benh@kernel.crashing.org>2014-10-28 15:57:51 +1100
commit931ffa22751ba143f0269ba95dd9bf83def4a3fa (patch)
tree1cd824f303ab807686741fb6ffdc42fa6d575520
parent5d254c78374a1092318785a1fcdda1eca54eb761 (diff)
parent62be0cb831e9ad497b77ae828e40a407cf97b72c (diff)
downloadskiboot-931ffa22751ba143f0269ba95dd9bf83def4a3fa.zip
skiboot-931ffa22751ba143f0269ba95dd9bf83def4a3fa.tar.gz
skiboot-931ffa22751ba143f0269ba95dd9bf83def4a3fa.tar.bz2
Merge remote-tracking branch 'ltcgit/master'
-rw-r--r--core/init.c177
-rw-r--r--core/test/run-mem_region_init.c5
-rw-r--r--hw/Makefile.inc2
-rw-r--r--hw/lpc.c9
-rw-r--r--hw/p8-i2c.c1111
-rw-r--r--hw/psi.c3
-rw-r--r--include/i2c.h53
-rw-r--r--include/lpc.h3
-rw-r--r--include/opal.h3
9 files changed, 1360 insertions, 6 deletions
diff --git a/core/init.c b/core/init.c
index 811662a..4350764 100644
--- a/core/init.c
+++ b/core/init.c
@@ -33,6 +33,7 @@
#include <device.h>
#include <pci.h>
#include <lpc.h>
+#include <i2c.h>
#include <chip.h>
#include <interrupts.h>
#include <mem_region.h>
@@ -400,10 +401,103 @@ void __noreturn load_and_boot_kernel(bool is_reboot)
start_kernel(kernel_entry, fdt, mem_top);
}
+struct i2c_device {
+ const char *label;
+ uint8_t dev_addr;
+};
+
+
+static struct dt_node *dt_create_i2c_master(struct dt_node *n, uint32_t eng_id)
+{
+ struct dt_node *i2cm;
+
+ /* Each master registers set is of length 0x20 */
+ i2cm = dt_new_addr(n, "i2cm", 0xa0000 + eng_id * 0x20);
+ if (!i2cm)
+ return NULL;
+
+ dt_add_property_string(i2cm, "compatible",
+ "ibm,power8-i2cm");
+ dt_add_property_cells(i2cm, "reg", 0xa0000 + eng_id * 0x20,
+ 0x20);
+ dt_add_property_cells(i2cm, "bus-speed-khz", 400);
+ dt_add_property_cells(i2cm, "local-bus-freq-mhz", 50);
+
+ return i2cm;
+}
+
+static struct dt_node *dt_create_i2c_bus(struct dt_node *i2cm, const char *compat,
+ uint32_t port_id)
+{
+ static struct dt_node *port;
+ static uint32_t bus_id = 0;
+
+ port = dt_new_addr(i2cm, "i2c-bus", port_id);
+ if (!port)
+ return NULL;
+
+ dt_add_property_string(port, "compatible", compat);
+ dt_add_property_cells(port, "port-id", port_id);
+ dt_add_property_cells(port, "bus-id", ++bus_id);
+
+ return port;
+}
+
+static void dt_create_i2c_devices(struct dt_node *port,
+ struct i2c_device *devices, uint32_t count)
+{
+ struct i2c_device *dev;
+ uint32_t i, size = 0;
+ char *names, *s;
+ uint8_t *addr;
+
+ for (i = 0, dev = devices; i < count; i++, dev++)
+ size = size + strlen(dev->label) + 1;
+
+ names = zalloc(size);
+ if (!names) {
+ prerror("Failed to allocate device names\n");
+ return;
+ }
+
+ addr = zalloc(count * sizeof(*addr));
+ if (!addr) {
+ prerror("Failed to allocate device addresses");
+ free(names);
+ return;
+ }
+
+ s = names;
+ for (i = 0, dev = devices; i < count; i++, dev++) {
+ strcpy(s, dev->label);
+ s = s + strlen(dev->label) + 1;
+
+ addr[i] = dev->dev_addr;
+ }
+
+ dt_add_property(port, "device-type", names, size);
+ dt_add_property(port, "device-addr", addr,
+ count * sizeof(*addr));
+ free(addr);
+ free(names);
+}
+
static void dt_fixups(void)
{
- struct dt_node *n;
+ struct dt_node *n, *i2cm, *port;
struct dt_node *primary_lpc = NULL;
+ uint32_t chip_id;
+ struct i2c_device i2c_pci_slots_0[] = {
+ {"slot-C10-C11", 0x32},
+ {"slot-C6-C7", 0x35},
+ {"slot-C8-C9", 0x36},
+ {"slot-C12", 0x39}
+ };
+
+ struct i2c_device i2c_pci_slots_1[] = {
+ {"slot-C4-C5", 0x39},
+ {"slot-C2-C3", 0x3a}
+ };
/* lpc node missing #address/size cells. Also pick one as
* primary for now (TBD: How to convey that from HB)
@@ -422,10 +516,87 @@ static void dt_fixups(void)
if (primary_lpc && !dt_has_node_property(primary_lpc, "primary", NULL))
dt_add_property(primary_lpc, "primary", NULL, 0);
- /* Missing "scom-controller" */
dt_for_each_compatible(dt_root, n, "ibm,xscom") {
+ /* Missing "scom-controller" */
if (!dt_has_node_property(n, "scom-controller", NULL))
dt_add_property(n, "scom-controller", NULL, 0);
+
+ /*
+ * DCM Chip Engine bus/port
+ * -----------------------------
+ * 0 0 0 0, 1
+ * 0 0 1 0
+ *
+ * 0 1 0 0
+ *
+ * 1 0 0 0, 1
+ * 1 0 1 0
+ *
+ * 1 1 0 0
+ */
+ chip_id = dt_get_chip_id(n);
+ switch (chip_id) {
+ case 0x00:
+ /* Engine: 0, port: 0/1 */
+ i2cm = dt_create_i2c_master(n, 0);
+ if (!i2cm)
+ continue;
+
+ port = dt_create_i2c_bus(i2cm, "ibm,opal-vpd", 0);
+ port = dt_create_i2c_bus(i2cm, "ibm,opal-vpd", 1);
+
+ /* Engine: 1, port: 0 */
+ i2cm = dt_create_i2c_master(n, 1);
+ if (!i2cm)
+ continue;
+
+ port = dt_create_i2c_bus(i2cm, "ibm,opal-maxim-mex", 0);
+ if (!port)
+ continue;
+
+ dt_create_i2c_devices(port, i2c_pci_slots_0,
+ ARRAY_SIZE(i2c_pci_slots_0));
+ break;
+ case 0x01:
+ /* Engine: 1, port: 0 */
+ i2cm = dt_create_i2c_master(n, 0);
+ if (!i2cm)
+ continue;
+
+ port = dt_create_i2c_bus(i2cm, "ibm,opal-vpd", 0);
+ break;
+ case 0x10:
+ /* Engine: 0, port: 0/1 */
+ i2cm = dt_create_i2c_master(n, 0);
+ if (!i2cm)
+ continue;
+
+ port = dt_create_i2c_bus(i2cm, "ibm,opal-vpd", 0);
+ port = dt_create_i2c_bus(i2cm, "ibm,opal-vpd", 1);
+
+ /* Engine: 1, port: 0 */
+ i2cm = dt_create_i2c_master(n, 1);
+ if (!i2cm)
+ continue;
+
+ port = dt_create_i2c_bus(i2cm, "ibm,opal-maxim-mex", 0);
+ if (!port)
+ continue;
+
+ dt_create_i2c_devices(port, i2c_pci_slots_1,
+ ARRAY_SIZE(i2c_pci_slots_1));
+ break;
+ case 0x11:
+ /* Engine: 1, port: 0 */
+ i2cm = dt_create_i2c_master(n, 0);
+ if (!i2cm)
+ continue;
+
+ port = dt_create_i2c_bus(i2cm, "ibm,opal-vpd", 0);
+ break;
+ default:
+ break;
+ }
}
}
@@ -551,6 +722,8 @@ void __noreturn main_cpu_entry(const void *fdt, u32 master_cpu)
*/
lpc_init();
+ p8_i2c_init();
+
/*
* Now, we init our memory map from the device-tree, and immediately
* reserve areas which we know might contain data coming from
diff --git a/core/test/run-mem_region_init.c b/core/test/run-mem_region_init.c
index 75c04b9..65643ec 100644
--- a/core/test/run-mem_region_init.c
+++ b/core/test/run-mem_region_init.c
@@ -83,7 +83,9 @@ static void add_mem_node(uint64_t start, uint64_t len)
{
struct dt_node *mem;
u64 reg[2];
- char name[sizeof("memory@") + STR_MAX_CHARS(reg[0])];
+ char *name= (char*)malloc(sizeof("memory@") + STR_MAX_CHARS(reg[0]));
+
+ assert(name);
/* reg contains start and length */
reg[0] = cpu_to_be64(start);
@@ -95,6 +97,7 @@ static void add_mem_node(uint64_t start, uint64_t len)
assert(mem);
dt_add_property_string(mem, "device_type", "memory");
dt_add_property(mem, "reg", reg, sizeof(reg));
+ free(name);
}
void add_chip_dev_associativity(struct dt_node *dev __attribute__((unused)))
diff --git a/hw/Makefile.inc b/hw/Makefile.inc
index 3f372b1..83125be 100644
--- a/hw/Makefile.inc
+++ b/hw/Makefile.inc
@@ -4,7 +4,7 @@ SUBDIRS += hw
HW_OBJS = xscom.o chiptod.o gx.o cec.o lpc.o lpc-uart.o psi.o
HW_OBJS += homer.o slw.o occ.o nx.o fsi-master.o centaur.o
HW_OBJS += p7ioc.o p7ioc-inits.o p7ioc-phb.o p5ioc2.o p5ioc2-phb.o
-HW_OBJS += phb3.o sfc-ctrl.o fake-rtc.o bt.o
+HW_OBJS += phb3.o sfc-ctrl.o fake-rtc.o bt.o p8-i2c.o
HW=hw/built-in.o
include $(SRC)/hw/fsp/Makefile.inc
diff --git a/hw/lpc.c b/hw/lpc.c
index 313c8d2..975edd6 100644
--- a/hw/lpc.c
+++ b/hw/lpc.c
@@ -20,6 +20,7 @@
#include <lock.h>
#include <chip.h>
#include <lpc.h>
+#include <i2c.h>
#include <timebase.h>
#include <fsp-elog.h>
@@ -454,6 +455,14 @@ bool lpc_present(void)
return lpc_default_chip_id >= 0;
}
+void lpc_interrupt(void)
+{
+ /* i2c interrupts are routed through lpc */
+ p8_i2c_interrupt();
+
+ /* Handle the lpc interrupt source */
+}
+
void lpc_init(void)
{
struct dt_node *xn;
diff --git a/hw/p8-i2c.c b/hw/p8-i2c.c
new file mode 100644
index 0000000..5dc81d3
--- /dev/null
+++ b/hw/p8-i2c.c
@@ -0,0 +1,1111 @@
+/* Copyright 2013-2014 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.
+ */
+
+#include <fsp.h>
+#include <opal.h>
+#include <lock.h>
+#include <chip.h>
+#include <i2c.h>
+#include <xscom.h>
+#include <timebase.h>
+#include <opal-msg.h>
+
+#define USEC_PER_SEC 1000000
+#define USEC_PER_MSEC 1000
+#define I2C_WRITE_DELAY_MS 5 /* 5 msecs */
+#define MAX_POLL_COUNT(x) ((I2C_WRITE_DELAY_MS * USEC_PER_MSEC)/(x))
+#define I2C_FIFO_HI_LVL 4
+#define I2C_FIFO_LO_LVL 4
+#define I2C_PAGE_WRITE_SIZE (0x1u << 8)
+#define I2C_PAGE_WRITE_MASK (I2C_PAGE_WRITE_SIZE - 1)
+
+/*
+ * I2C registers set.
+ * Below is the offset of registers from base which is stored in the
+ * 'struct p8_i2c_master'
+ */
+
+/* I2C FIFO register */
+#define I2C_FIFO_REG 0x4
+#define I2C_FIFO_MASK PPC_BITMASK(0, 7)
+#define I2C_FIFO_LSH PPC_BITLSHIFT(7)
+
+/* I2C command register */
+#define I2C_CMD_REG 0x5
+#define I2C_CMD_WITH_START PPC_BIT(0)
+#define I2C_CMD_WITH_ADDR PPC_BIT(1)
+#define I2C_CMD_READ_CONT PPC_BIT(2)
+#define I2C_CMD_WITH_STOP PPC_BIT(3)
+#define I2C_CMD_DEV_ADDR_MASK PPC_BITMASK(8, 14)
+#define I2C_CMD_DEV_ADDR_LSH PPC_BITLSHIFT(14)
+#define I2C_CMD_READ_NOT_WRITE PPC_BIT(15)
+#define I2C_CMD_LEN_BYTES_MASK PPC_BITMASK(16, 31)
+#define I2C_CMD_LEN_BYTES_LSH PPC_BITLSHIFT(31)
+#define I2C_MAX_TFR_LEN 0xffffull
+
+/* I2C mode register */
+#define I2C_MODE_REG 0x6
+#define I2C_MODE_BIT_RATE_DIV_MASK PPC_BITMASK(0, 15)
+#define I2C_MODE_BIT_RATE_DIV_LSH PPC_BITLSHIFT(15)
+#define I2C_MODE_PORT_NUM_MASK PPC_BITMASK(16, 21)
+#define I2C_MODE_PORT_NUM_LSH PPC_BITLSHIFT(21)
+#define I2C_MODE_ENHANCED PPC_BIT(28)
+#define I2C_MODE_DIAGNOSTIC PPC_BIT(29)
+#define I2C_MODE_PACING_ALLOW PPC_BIT(30)
+#define I2C_MODE_WRAP PPC_BIT(31)
+
+/* I2C watermark register */
+#define I2C_WATERMARK_REG 0x7
+#define I2C_WATERMARK_HIGH_MASK PPC_BITMASK(16, 19)
+#define I2C_WATERMARK_HIGH_LSH PPC_BITLSHIFT(19)
+#define I2C_WATERMARK_LOW_MASK PPC_BITMASK(24, 27)
+#define I2C_WATERMARK_LOW_LSH PPC_BITLSHIFT(27)
+
+/* I2C interrupt mask, condition and interrupt registers */
+#define I2C_INTR_MASK_REG 0x8
+#define I2C_INTR_COND_REG 0x9
+#define I2C_INTR_REG 0xa
+#define I2C_INTR_ALL_MASK PPC_BITMASK(16, 31)
+#define I2C_INTR_ALL_LSH PPC_BITLSHIFT(31)
+#define I2C_INTR_INVALID_CMD PPC_BIT(16)
+#define I2C_INTR_LBUS_PARITY_ERR PPC_BIT(17)
+#define I2C_INTR_BKEND_OVERRUN_ERR PPC_BIT(18)
+#define I2C_INTR_BKEND_ACCESS_ERR PPC_BIT(19)
+#define I2C_INTR_ARBT_LOST_ERR PPC_BIT(20)
+#define I2C_INTR_NACK_RCVD_ERR PPC_BIT(21)
+#define I2C_INTR_DATA_REQ PPC_BIT(22)
+#define I2C_INTR_CMD_COMP PPC_BIT(23)
+#define I2C_INTR_STOP_ERR PPC_BIT(24)
+#define I2C_INTR_I2C_BUSY PPC_BIT(25)
+#define I2C_INTR_NOT_I2C_BUSY PPC_BIT(26)
+#define I2C_INTR_SCL_EQ_1 PPC_BIT(28)
+#define I2C_INTR_SCL_EQ_0 PPC_BIT(29)
+#define I2C_INTR_SDA_EQ_1 PPC_BIT(30)
+#define I2C_INTR_SDA_EQ_0 PPC_BIT(31)
+
+/* I2C status register */
+#define I2C_RESET_I2C_REG 0xb
+#define I2C_STAT_REG 0xb
+#define I2C_STAT_INVALID_CMD PPC_BIT(0)
+#define I2C_STAT_LBUS_PARITY_ERR PPC_BIT(1)
+#define I2C_STAT_BKEND_OVERRUN_ERR PPC_BIT(2)
+#define I2C_STAT_BKEND_ACCESS_ERR PPC_BIT(3)
+#define I2C_STAT_ARBT_LOST_ERR PPC_BIT(4)
+#define I2C_STAT_NACK_RCVD_ERR PPC_BIT(5)
+#define I2C_STAT_DATA_REQ PPC_BIT(6)
+#define I2C_STAT_CMD_COMP PPC_BIT(7)
+#define I2C_STAT_STOP_ERR PPC_BIT(8)
+#define I2C_STAT_UPPER_THRS_MASK PPC_BITMASK(9, 15)
+#define I2C_STAT_UPPER_THRS_LSH PPC_BITLSHIFT(15)
+#define I2C_STAT_ANY_I2C_INTR PPC_BIT(16)
+#define I2C_STAT_PORT_HISTORY_BUSY PPC_BIT(19)
+#define I2C_STAT_SCL_INPUT_LEVEL PPC_BIT(20)
+#define I2C_STAT_SDA_INPUT_LEVEL PPC_BIT(21)
+#define I2C_STAT_PORT_BUSY PPC_BIT(22)
+#define I2C_STAT_INTERFACE_BUSY PPC_BIT(23)
+#define I2C_STAT_FIFO_ENTRY_COUNT_MASK PPC_BITMASK(24, 31)
+#define I2C_STAT_FIFO_ENTRY_COUNT_LSH PPC_BITLSHIFT(31)
+
+#define I2C_STAT_ANY_ERR (I2C_STAT_INVALID_CMD | I2C_STAT_LBUS_PARITY_ERR | \
+ I2C_STAT_BKEND_OVERRUN_ERR | \
+ I2C_STAT_BKEND_ACCESS_ERR | I2C_STAT_ARBT_LOST_ERR | \
+ I2C_STAT_NACK_RCVD_ERR | I2C_STAT_STOP_ERR)
+
+/* I2C extended status register */
+#define I2C_EXTD_STAT_REG 0xc
+#define I2C_EXTD_STAT_FIFO_SIZE_MASK PPC_BITMASK(0, 7)
+#define I2C_EXTD_STAT_FIFO_SIZE_LSH PPC_BITLSHIFT(7)
+#define I2C_EXTD_STAT_MSM_CURSTATE_MASK PPC_BITMASK(11, 15)
+#define I2C_EXTD_STAT_MSM_CURSTATE_LSH PPC_BITLSHIFT(15)
+#define I2C_EXTD_STAT_SCL_IN_SYNC PPC_BIT(16)
+#define I2C_EXTD_STAT_SDA_IN_SYNC PPC_BIT(17)
+#define I2C_EXTD_STAT_S_SCL PPC_BIT(18)
+#define I2C_EXTD_STAT_S_SDA PPC_BIT(19)
+#define I2C_EXTD_STAT_M_SCL PPC_BIT(20)
+#define I2C_EXTD_STAT_M_SDA PPC_BIT(21)
+#define I2C_EXTD_STAT_HIGH_WATER PPC_BIT(22)
+#define I2C_EXTD_STAT_LOW_WATER PPC_BIT(23)
+#define I2C_EXTD_STAT_I2C_BUSY PPC_BIT(24)
+#define I2C_EXTD_STAT_SELF_BUSY PPC_BIT(25)
+#define I2C_EXTD_STAT_I2C_VERSION_MASK PPC_BITMASK(27, 31)
+#define I2C_EXTD_STAT_I2C_VERSION_LSH PPC_BITLSHIFT(31)
+
+/* I2C residual front end/back end length */
+#define I2C_RESIDUAL_LEN_REG 0xd
+#define I2C_RESIDUAL_FRONT_END_MASK PPC_BITMASK(0, 15)
+#define I2C_RESIDUAL_FRONT_END_LSH PPC_BITLSHIFT(15)
+#define I2C_RESIDUAL_BACK_END_MASK PPC_BITMASK(16, 31)
+#define I2C_RESIDUAL_BACK_END_LSH PPC_BITLSHIFT(31)
+
+struct p8_i2cm_state {
+ enum request_state {
+ REQ_LIST_EMPTY = 0x1, /* i2cm request queue is empty */
+ REQ_BEGIN = 0x2, /* Fresh request pushed on the bus */
+ REQ_SM_OFFSET = 0x4, /* SMBUS offset writing in progress */
+ REQ_DATA = 0x8, /* Device data read/write in progress */
+ REQ_DATA_CONT = 0x10, /* Data request with no stop for data */
+ REQ_WR_COMPLETE = 0x20, /* Write request on the device completed,
+ * waiting for the device to settle down
+ */
+ } req_state;
+ uint32_t bytes_sent;
+};
+
+struct p8_i2c_master {
+ struct p8_i2cm_state state; /* Request state of i2cm */
+ struct lock lock; /* Lock to guard the members */
+ uint64_t poll_timer; /* Poll timer expiration */
+ uint64_t poll_interval; /* Polling interval in usec */
+ uint64_t poll_count; /* Max poll attempts */
+ uint64_t write_delay; /* Wait for the slave device to
+ * settle down after write */
+ uint64_t xscom_base; /* xscom base of i2cm */
+ uint32_t bit_rate_div; /* Divisor to set bus speed*/
+ uint32_t fifo_size; /* Maximum size of FIFO */
+ uint32_t chip_id; /* Chip the i2cm sits on */
+ struct list_head req_list; /* Request queue head */
+};
+
+struct p8_i2c_master_port {
+ struct i2c_bus bus; /* Abstract bus struct for the client */
+ struct p8_i2c_master *common;
+ uint32_t bus_id;
+ uint32_t port_num;
+};
+
+struct p8_i2c_request {
+ struct i2c_request req;
+ uint32_t port_num;
+};
+
+struct opal_p8_i2c_data {
+ struct i2c_bus *bus;
+ uint64_t token;
+};
+
+LIST_HEAD(i2c_bus_list);
+static int p8_i2c_start_request(struct p8_i2c_master *master);
+static void p8_i2c_complete_request(struct p8_i2c_master *master, int rc);
+static int p8_i2c_prog_mode(struct p8_i2c_master *master, bool reset,
+ bool enhanced_mode);
+static int p8_i2c_prog_watermark(struct p8_i2c_master *master);
+
+static int p8_i2c_fifo_to_buf(struct p8_i2c_master *master,
+ struct i2c_request *req, uint32_t count)
+{
+ uint8_t *buf = (uint8_t *)req->rw_buf + master->state.bytes_sent;
+ uint64_t fifo;
+ uint32_t i;
+ int rc = 0;
+
+ for (i = 0; i < count; i++, buf++) {
+ rc = xscom_read(master->chip_id, master->xscom_base +
+ I2C_FIFO_REG, &fifo);
+ if (rc) {
+ prlog(PR_ERR, "I2C: Failed to read the fifo\n");
+ break;
+ }
+
+ *buf = GETFIELD(I2C_FIFO, fifo);
+ }
+
+ master->state.bytes_sent += i;
+
+ return rc;
+}
+
+static int p8_i2c_buf_to_fifo(struct p8_i2c_master *master,
+ struct i2c_request *req, uint32_t count)
+{
+ uint64_t fifo = 0x0ull;
+ uint32_t offset, i;
+ uint8_t *buf;
+ int rc = 0;
+
+ if (master->state.req_state & REQ_SM_OFFSET) {
+ offset = req->offset + master->state.bytes_sent;
+ buf = (uint8_t *)&offset;
+ /* MSB address byte is followed by the LSB byte */
+ buf += (sizeof(offset) - req->offset_bytes);
+ for (i = 0; i < req->offset_bytes; i++, buf++, count--) {
+ fifo = SETFIELD(I2C_FIFO, fifo, *buf);
+ rc = xscom_write(master->chip_id, master->xscom_base +
+ I2C_FIFO_REG, fifo);
+ if (rc) {
+ prlog(PR_ERR, "I2C:Failed to write the fifo\n");
+ return -1;
+ }
+ }
+
+ /*
+ * SMBUS_WRITE is combined offset and data write with same START
+ * condition, update the state as the next call to this function
+ * for the same command sequence should not write the 'offset'
+ * again.
+ * SMBUS_READ is seperate START condition for 'offset write' and
+ * data read, so state gets updated when we issue the following
+ * START condition for data read.
+ */
+ if (req->op == SMBUS_WRITE)
+ master->state.req_state &= ~REQ_SM_OFFSET;
+ }
+
+ buf = (uint8_t *)req->rw_buf + master->state.bytes_sent;
+ for (i = 0; i < count; i++, buf++) {
+ fifo = SETFIELD(I2C_FIFO, fifo, *buf);
+ rc = xscom_write(master->chip_id, master->xscom_base +
+ I2C_FIFO_REG, fifo);
+ if (rc) {
+ prlog(PR_ERR, "I2C: Failed to write the fifo\n");
+ break;
+ }
+ }
+
+ master->state.bytes_sent += i;
+
+ return rc;
+}
+
+static int p8_i2c_get_fifo_left(struct p8_i2c_master *master, uint32_t fifo_count,
+ uint32_t *fifo_left)
+{
+ uint32_t res_be;
+ uint64_t res;
+ int rc;
+
+ *fifo_left = master->fifo_size - fifo_count;
+ rc = xscom_read(master->chip_id, master->xscom_base +
+ I2C_RESIDUAL_LEN_REG, &res);
+ if (rc) {
+ prlog(PR_ERR, "I2C: Failed to read RESIDUAL_LEN\n");
+ return -1;
+ }
+
+ res_be = GETFIELD(I2C_RESIDUAL_BACK_END, res);
+ if (res_be < *fifo_left)
+ *fifo_left = res_be;
+
+ return 0;
+}
+
+static void p8_i2c_status_error(struct p8_i2c_master *master, uint64_t status)
+{
+ int rc;
+
+ prlog(PR_WARNING, "Error occured STATUS_REG:0x%016llx\n", status);
+ /* Reset the i2c engine */
+ rc = xscom_write(master->chip_id, master->xscom_base +
+ I2C_RESET_I2C_REG, 0);
+ if (rc) {
+ prlog(PR_ERR, "I2C: Failed to reset the i2c engine\n");
+ goto exit;
+ }
+
+ /* Reprogram the watermark register */
+ rc = p8_i2c_prog_watermark(master);
+ if (rc)
+ goto exit;
+
+ /* Don't bother issuing a STOP command, just get rid of the current
+ * request and start off with the fresh one in the list
+ */
+ if (status & (I2C_STAT_LBUS_PARITY_ERR | I2C_STAT_ARBT_LOST_ERR |
+ I2C_STAT_STOP_ERR)) {
+ /* Reprogram the mode register */
+ rc = p8_i2c_prog_mode(master, true, false);
+ if (rc)
+ goto exit;
+
+ p8_i2c_complete_request(master, OPAL_HARDWARE);
+
+ /*
+ * Reset the bus by issuing a STOP command to slave, when
+ * command completion happens for this STOP the current request
+ * will get popped out and fresh request will be pushed on the
+ * bus.
+ * TODO Should we give couple retries to the current request in
+ * case of NACK received error before eventually doing a STOP
+ * reset to the bus.
+ */
+ } else {
+ /* Reprogram the mode register with 'enhanced bit' set */
+ rc = p8_i2c_prog_mode(master, true, true);
+ if (rc)
+ goto exit;
+
+ master->state.req_state = REQ_BEGIN;
+ rc = xscom_write(master->chip_id, master->xscom_base +
+ I2C_CMD_REG, I2C_CMD_WITH_STOP);
+ if (rc) {
+ prlog(PR_ERR, "I2C:Failed to issue the STOP\n");
+ goto exit;
+ }
+ }
+
+ return;
+
+exit:
+ p8_i2c_complete_request(master, rc);
+}
+
+static void p8_i2c_status_data_request(struct p8_i2c_master *master, uint64_t status)
+{
+ struct i2c_request *req = list_top(&master->req_list,
+ struct i2c_request, link);
+ uint32_t fifo_count, fifo_left;
+ int rc;
+
+ fifo_count = GETFIELD(I2C_STAT_FIFO_ENTRY_COUNT, status);
+
+ switch (req->op) {
+ case I2C_READ:
+ rc = p8_i2c_fifo_to_buf(master, req, fifo_count);
+ break;
+ case SMBUS_READ:
+ if (master->state.req_state & REQ_SM_OFFSET) {
+ rc = p8_i2c_get_fifo_left(master, fifo_count,
+ &fifo_left);
+ if (rc)
+ break;
+ rc = p8_i2c_buf_to_fifo(master, req, fifo_left);
+ } else {
+ rc = p8_i2c_fifo_to_buf(master, req, fifo_count);
+ }
+ break;
+ case I2C_WRITE:
+ case SMBUS_WRITE:
+ rc = p8_i2c_get_fifo_left(master, fifo_count, &fifo_left);
+ if (rc)
+ break;
+
+ rc = p8_i2c_buf_to_fifo(master, req, fifo_left);
+ break;
+ default:
+ rc = -1;
+ break;
+ }
+
+ if (rc)
+ prlog(PR_INFO, "I2C: i2c operation '%d' failed\n", req->op);
+
+ /* Enable the interrupts */
+ rc = xscom_write(master->chip_id, master->xscom_base +
+ I2C_INTR_COND_REG, I2C_STAT_ANY_ERR >> 16 |
+ I2C_INTR_CMD_COMP | I2C_INTR_DATA_REQ);
+ if (rc)
+ prlog(PR_ERR, "I2C: Failed to enable the interrupts\n");
+}
+
+static void p8_i2c_status_cmd_completion(struct p8_i2c_master *master)
+{
+ struct i2c_request *req = list_top(&master->req_list,
+ struct i2c_request, link);
+ struct p8_i2cm_state *state = &master->state;
+ int rc;
+
+ /* Continue with the same request in the list */
+ if (state->req_state & REQ_SM_OFFSET ||
+ state->req_state & REQ_DATA_CONT) {
+ /* Reset the poll variables */
+ master->poll_timer = 0;
+ master->poll_count = MAX_POLL_COUNT(master->poll_interval);
+
+ /*
+ * It takes maximum of 5 msecs time for the device to get ready
+ * for any later operations after WRITE command, though the core
+ * is ready to issue the next
+ */
+ if (req->op == I2C_WRITE || req->op == SMBUS_WRITE) {
+ master->write_delay = mftb() +
+ msecs_to_tb(I2C_WRITE_DELAY_MS);
+ state->req_state |= REQ_WR_COMPLETE;
+ } else {
+ rc = p8_i2c_start_request(master);
+ if (rc)
+ p8_i2c_complete_request(master, rc);
+ }
+
+ /* Completed the current request, remove it from the list and start
+ * off with the the fresh one
+ */
+ } else {
+ state->bytes_sent = 0;
+ if (req->op == I2C_WRITE || req->op == SMBUS_WRITE) {
+ master->write_delay = mftb() +
+ msecs_to_tb(I2C_WRITE_DELAY_MS);
+ state->req_state = REQ_WR_COMPLETE;
+ }
+
+ p8_i2c_complete_request(master, OPAL_SUCCESS);
+ }
+ /* Don't require to explicitly enable the interrupts as call to
+ * p8_i2c_start_request() will do
+ */
+}
+
+static void __p8_i2c_poll(struct p8_i2c_master *master)
+{
+ uint64_t status;
+ int rc;
+
+ rc = xscom_read(master->chip_id, master->xscom_base + I2C_STAT_REG,
+ &status);
+ if (rc) {
+ prlog(PR_ERR, "I2C: Failed to read the STAT_REG\n");
+ return;
+ }
+
+ if (!(status & (I2C_STAT_ANY_ERR | I2C_STAT_DATA_REQ |
+ I2C_STAT_CMD_COMP)))
+ return;
+
+ /* Mask the interrupts for this engine */
+ rc = xscom_write(master->chip_id, master->xscom_base + I2C_INTR_REG,
+ ~I2C_INTR_ALL_MASK);
+ if (rc) {
+ prlog(PR_ERR, "I2C: Failed to disable the interrupts\n");
+ return;
+ }
+
+
+ if (status & I2C_STAT_ANY_ERR)
+ p8_i2c_status_error(master, status);
+ else if (status & I2C_STAT_DATA_REQ)
+ p8_i2c_status_data_request(master, status);
+
+ /* Both front end & back end data transfer are complete */
+ else if (status & I2C_STAT_CMD_COMP)
+ p8_i2c_status_cmd_completion(master);
+}
+
+static void p8_i2c_complete_request(struct p8_i2c_master *master, int ret)
+{
+ struct p8_i2cm_state *state = &master->state;
+ struct i2c_request *req;
+ int rc;
+
+ /* Reset the poll variables */
+ master->poll_timer = 0;
+ master->poll_count = MAX_POLL_COUNT(master->poll_interval);
+
+ /* Delete the top request completed */
+ req = list_top(&master->req_list, struct i2c_request, link);
+ list_del(&req->link);
+
+ unlock(&master->lock);
+ if (req->completion)
+ req->completion(ret, req);
+
+ lock(&master->lock);
+ if (state->req_state & REQ_WR_COMPLETE) {
+ if (tb_compare(mftb(), master->write_delay) == TB_ABEFOREB) {
+ prlog(PR_INFO, "I2C: Waiting for the write delay\n");
+ return;
+ } else {
+ state->req_state |= ~REQ_WR_COMPLETE;
+ }
+ }
+
+ /* Push the fresh request from the list */
+ do {
+ req = list_top(&master->req_list, struct i2c_request, link);
+ if (!req) {
+ state->req_state = REQ_LIST_EMPTY;
+ return;
+ }
+
+ state->req_state = REQ_BEGIN;
+ rc = p8_i2c_start_request(master);
+ if (rc) {
+ list_del(&req->link);
+
+ unlock(&master->lock);
+ if (req->completion)
+ req->completion(rc, req);
+ lock(&master->lock);
+ }
+ } while (rc);
+}
+
+static int p8_i2c_prog_mode(struct p8_i2c_master *master, bool reset,
+ bool enhanced_mode)
+{
+ struct i2c_request *req = list_top(&master->req_list,
+ struct i2c_request, link);
+ struct p8_i2c_request *request = container_of(req, struct p8_i2c_request,
+ req);
+ uint64_t mode;
+ int rc;
+
+ rc = xscom_read(master->chip_id, master->xscom_base +
+ I2C_MODE_REG, &mode);
+ if (rc) {
+ prlog(PR_ERR, "I2C: Failed to read the MODE_REG\n");
+ return OPAL_HARDWARE;
+ }
+
+ mode = SETFIELD(I2C_MODE_PORT_NUM, mode, request->port_num);
+ if (reset) {
+ mode = SETFIELD(I2C_MODE_BIT_RATE_DIV, mode,
+ master->bit_rate_div);
+ if (enhanced_mode)
+ mode |= I2C_MODE_ENHANCED;
+ }
+
+ rc = xscom_write(master->chip_id, master->xscom_base + I2C_MODE_REG,
+ mode);
+ if (rc) {
+ prlog(PR_ERR, "I2C: Failed to write the MODE_REG\n");
+ return OPAL_HARDWARE;
+ }
+
+ return 0;
+}
+
+static int p8_i2c_prog_watermark(struct p8_i2c_master *master)
+{
+ uint64_t watermark;
+ int rc;
+
+ rc = xscom_read(master->chip_id, master->xscom_base + I2C_WATERMARK_REG,
+ &watermark);
+ if (rc) {
+ prlog(PR_ERR, "I2C: Failed to read the WATERMARK_REG\n");
+ return OPAL_HARDWARE;
+ }
+
+ /* Set the high/low watermark */
+ watermark = SETFIELD(I2C_WATERMARK_HIGH, watermark, I2C_FIFO_HI_LVL);
+ watermark = SETFIELD(I2C_WATERMARK_LOW, watermark, I2C_FIFO_LO_LVL);
+ rc = xscom_write(master->chip_id, master->xscom_base +
+ I2C_WATERMARK_REG, watermark);
+ if (rc) {
+ prlog(PR_ERR, "I2C: Failed to set high/low watermark level\n");
+ return OPAL_HARDWARE;
+ }
+
+ return 0;
+}
+
+/*
+ * START + (dev_addr + READ) + STOP
+ * Need to do multiple START if data requested > I2C_MAX_TFR_LEN, with
+ * 'Read continue' set and STOP condition only for the last one
+ */
+static void p8_i2c_read_cmd(struct p8_i2c_master *master, uint64_t *cmd)
+{
+ struct i2c_request *req = list_top(&master->req_list,
+ struct i2c_request, link);
+ struct p8_i2cm_state *state = &master->state;
+ uint32_t data_bytes_left;
+
+ *cmd |= I2C_CMD_READ_NOT_WRITE;
+ data_bytes_left = req->rw_len - state->bytes_sent;
+ if (data_bytes_left & ~I2C_MAX_TFR_LEN) {
+ *cmd |= I2C_CMD_READ_CONT;
+ *cmd = SETFIELD(I2C_CMD_LEN_BYTES, *cmd, I2C_MAX_TFR_LEN);
+ state->req_state = REQ_DATA_CONT;
+ } else {
+ *cmd |= I2C_CMD_WITH_STOP;
+ *cmd = SETFIELD(I2C_CMD_LEN_BYTES, *cmd, data_bytes_left);
+ state->req_state = REQ_DATA;
+ }
+}
+
+/* START + (dev_addr + WRITE) + STOP */
+static void p8_i2c_write_cmd(struct p8_i2c_master *master, uint64_t *cmd)
+{
+ struct i2c_request *req = list_top(&master->req_list,
+ struct i2c_request, link);
+
+ *cmd |= I2C_CMD_WITH_STOP;
+ *cmd = SETFIELD(I2C_CMD_LEN_BYTES, *cmd,
+ req->rw_len & I2C_MAX_TFR_LEN);
+ master->state.req_state = REQ_DATA;
+}
+
+/*
+ * Repeat START/address phase with no STOP in between
+ * START + (dev_addr + WRITE) + offset +
+ * START + (dev_addr + READ) + data(n) + STOP
+ */
+static void p8_smbus_read_cmd(struct p8_i2c_master *master, uint64_t *cmd)
+{
+ struct i2c_request *req = list_top(&master->req_list,
+ struct i2c_request, link);
+ struct p8_i2cm_state *state = &master->state;
+ uint32_t data_bytes_left;
+
+ if (state->req_state & REQ_BEGIN) {
+ *cmd = SETFIELD(I2C_CMD_LEN_BYTES, *cmd,
+ req->offset_bytes);
+ state->req_state = REQ_SM_OFFSET;
+ } else {
+ *cmd |= I2C_CMD_READ_NOT_WRITE;
+ data_bytes_left = req->rw_len - state->bytes_sent;
+ if (data_bytes_left & ~I2C_MAX_TFR_LEN) {
+ *cmd |= I2C_CMD_READ_CONT;
+ *cmd = SETFIELD(I2C_CMD_LEN_BYTES, *cmd,
+ I2C_MAX_TFR_LEN);
+ state->req_state = REQ_DATA_CONT;
+ } else {
+ *cmd |= I2C_CMD_WITH_STOP;
+ *cmd = SETFIELD(I2C_CMD_LEN_BYTES, *cmd,
+ data_bytes_left);
+ state->req_state = REQ_DATA;
+ }
+ }
+
+}
+
+/*
+ * Single START/addr/STOP phase for both the offset and data
+ * START + (dev_addr + WRITE) + offset + data(n) + STOP
+ */
+static void p8_smbus_write_cmd(struct p8_i2c_master *master, uint64_t *cmd)
+{
+ struct i2c_request *req = list_top(&master->req_list,
+ struct i2c_request, link);
+ struct p8_i2cm_state *state = &master->state;
+ uint32_t data_bytes_left, page_bytes_left;
+
+ *cmd |= I2C_CMD_WITH_STOP;
+ /*
+ * Slave devices where the internal device offset could be more
+ * than 1 byte, only the lower address byte gets incremented
+ * and not the higher address byte during data writes, when the
+ * internal address reaches the page boundary (256 bytes), the
+ * following byte is placed at the beginning of the same page.
+ * So, a write request of the manner of touching multiple
+ * pages is sliced into multiple requests, each sending maximum
+ * of 1 page data to the device using repeated START-STOP.
+ */
+ page_bytes_left = I2C_PAGE_WRITE_SIZE -
+ ((req->offset + state->bytes_sent) &
+ I2C_PAGE_WRITE_MASK);
+ data_bytes_left = req->rw_len - state->bytes_sent;
+ if (page_bytes_left < data_bytes_left) {
+ *cmd = SETFIELD(I2C_CMD_LEN_BYTES, *cmd,
+ page_bytes_left + req->offset_bytes);
+ state->req_state = REQ_SM_OFFSET | REQ_DATA_CONT;
+ } else {
+ *cmd = SETFIELD(I2C_CMD_LEN_BYTES, *cmd,
+ data_bytes_left + req->offset_bytes);
+ state->req_state = REQ_SM_OFFSET | REQ_DATA;
+ }
+}
+
+static int p8_i2c_start_request(struct p8_i2c_master *master)
+{
+ struct i2c_request *req = list_top(&master->req_list,
+ struct i2c_request, link);
+ uint64_t cmd;
+ int rc;
+
+ /*
+ * Setting the port-id in mode register is required only if the request
+ * is being pushed on the bus first time and *not* if it's repeated
+ * START condition
+ */
+ if (master->state.req_state & REQ_BEGIN) {
+ rc = p8_i2c_prog_mode(master, false, false);
+ if (rc)
+ return rc;
+ }
+
+ /* Enable the interrupts */
+ rc = xscom_write(master->chip_id, master->xscom_base +
+ I2C_INTR_COND_REG, I2C_STAT_ANY_ERR >> 16 |
+ I2C_INTR_CMD_COMP | I2C_INTR_DATA_REQ);
+ if (rc) {
+ prlog(PR_ERR, "I2C: Failed to enable the interrupts\n");
+ return OPAL_HARDWARE;
+ }
+
+ /* Set up the command register */
+ cmd = 0x0ull;
+ cmd |= (I2C_CMD_WITH_START | I2C_CMD_WITH_ADDR);
+ cmd = SETFIELD(I2C_CMD_DEV_ADDR, cmd, req->dev_addr);
+
+ switch (req->op) {
+ case I2C_READ:
+ p8_i2c_read_cmd(master, &cmd);
+ break;
+ case I2C_WRITE:
+ p8_i2c_write_cmd(master, &cmd);
+ break;
+ case SMBUS_READ:
+ p8_smbus_read_cmd(master, &cmd);
+ break;
+ case SMBUS_WRITE:
+ p8_smbus_write_cmd(master, &cmd);
+ break;
+ default:
+ return OPAL_PARAMETER;
+ }
+
+ rc = xscom_write(master->chip_id, master->xscom_base + I2C_CMD_REG,
+ cmd);
+ if (rc) {
+ prlog(PR_ERR, "I2C: Failed to write the CMD_REG\n");
+ return OPAL_HARDWARE;
+ }
+
+ return OPAL_SUCCESS;
+}
+
+static int p8_i2c_queue_request(struct i2c_bus *bus, struct i2c_request *req)
+{
+ struct p8_i2c_master_port *port = container_of(bus, struct p8_i2c_master_port,
+ bus);
+ struct p8_i2c_master *master = port->common;
+ int rc = 0;
+
+ /* Parameter check */
+ if (req->offset_bytes > sizeof(req->offset)) {
+ prlog(PR_ERR, "I2C: Invalid parameters passed\n");
+ return OPAL_PARAMETER;
+ }
+
+ lock(&master->lock);
+ list_add_tail(&master->req_list, &req->link);
+
+ /* If the list is empty, start this request otherwise some request is
+ * already in progress on this master.
+ */
+ if (master->state.req_state & REQ_LIST_EMPTY) {
+ master->state.req_state = REQ_BEGIN;
+ rc = p8_i2c_start_request(master);
+ if (rc)
+ p8_i2c_complete_request(master, rc);
+ }
+ unlock(&master->lock);
+
+ return rc;
+}
+
+static struct i2c_request *p8_i2c_alloc_request(struct i2c_bus *bus)
+{
+ struct p8_i2c_master_port *port = container_of(bus, struct p8_i2c_master_port,
+ bus);
+ struct p8_i2c_request *request;
+
+ request = zalloc(sizeof(*request));
+ if (!request) {
+ prlog(PR_ERR, "I2C: Failed to allocate i2c request\n");
+ return NULL;
+ }
+
+ request->port_num = port->port_num;
+
+ return &request->req;
+}
+
+static void p8_i2c_dealloc_request(struct i2c_request *req)
+{
+ struct p8_i2c_request *request = container_of(req, struct p8_i2c_request,
+ req);
+ free(request);
+}
+
+static inline uint32_t p8_i2c_get_bit_rate_divisor(uint32_t lb_freq_mhz,
+ uint32_t bus_speed)
+{
+ uint64_t lb_freq = lb_freq_mhz * 1000;
+
+ return (((lb_freq / bus_speed) - 1) / 4);
+}
+
+static inline uint64_t p8_i2c_get_poll_interval(uint32_t bus_speed)
+{
+ /* Polling Interval = 8 * (1/bus_speed) * (1/10) -> convert to uSec */
+ return ((8 * USEC_PER_SEC) / (10 * bus_speed * 1000));
+}
+
+static void p8_i2c_compare_poll_timer(struct p8_i2c_master *master)
+{
+ uint64_t now = mftb();
+
+ if (master->poll_timer == 0 ||
+ tb_compare(now, master->poll_timer) == TB_AAFTERB ||
+ tb_compare(now, master->poll_timer) == TB_AEQUALB) {
+ if (0 == master->poll_count--) {
+ prlog(PR_WARNING, "I2C: Operation timed out\n");
+ p8_i2c_complete_request(master, OPAL_HARDWARE);
+
+ return;
+ }
+
+ master->poll_timer = now + usecs_to_tb(master->poll_interval);
+ __p8_i2c_poll(master);
+ }
+}
+
+static void p8_i2c_poll_each_master(bool interrupt)
+{
+ struct p8_i2c_master *master = NULL;
+ struct p8_i2c_master_port *port;
+ struct i2c_bus *bus;
+ int rc;
+
+ list_for_each(&i2c_bus_list, bus, link) {
+ port = container_of(bus, struct p8_i2c_master_port, bus);
+
+ /* Each master serves 1 or more ports, check for the first
+ * one found..
+ */
+ if (!master || master != port->common)
+ master = port->common;
+ else
+ continue;
+
+ lock(&master->lock);
+ if (master->state.req_state & REQ_LIST_EMPTY) {
+ unlock(&master->lock);
+ continue;
+ } else if (master->state.req_state & REQ_WR_COMPLETE) {
+ if (tb_compare(mftb(), master->write_delay) ==
+ TB_ABEFOREB) {
+ unlock(&master->lock);
+ continue;
+ } else {
+ master->state.req_state |= ~REQ_WR_COMPLETE;
+ rc = p8_i2c_start_request(master);
+ if (rc)
+ p8_i2c_complete_request(master, rc);
+ }
+ }
+
+ if (!interrupt)
+ p8_i2c_compare_poll_timer(master);
+ else
+ __p8_i2c_poll(master);
+
+ unlock(&master->lock);
+ }
+}
+
+static void p8_i2c_opal_poll(void *data __unused)
+{
+ p8_i2c_poll_each_master(false);
+}
+
+void p8_i2c_interrupt(void)
+{
+ p8_i2c_poll_each_master(true);
+}
+
+static void opal_p8_i2c_request_complete(int rc, struct i2c_request *req)
+{
+ struct opal_p8_i2c_data *opal_data = req->user_data;
+
+ opal_queue_msg(OPAL_MSG_ASYNC_COMP, NULL, NULL, opal_data->token, rc);
+ opal_data->bus->dealloc_req(req);
+ free(opal_data);
+}
+
+static int opal_p8_i2c_request(uint64_t async_token, uint32_t bus_id,
+ uint32_t dev_addr, uint64_t buffer,
+ uint32_t len, uint8_t subaddr)
+{
+ struct opal_p8_i2c_data *opal_data;
+ struct p8_i2c_master_port *port;
+ struct i2c_request *req;
+ struct i2c_bus *bus;
+ int rc;
+
+ list_for_each(&i2c_bus_list, bus, link) {
+ port = container_of(bus, struct p8_i2c_master_port, bus);
+ if (port->bus_id == bus_id)
+ break;
+ }
+
+ if (!bus) {
+ prlog(PR_ERR, "I2C: Invalid 'bus_id' passed to the OPAL\n");
+ return OPAL_PARAMETER;
+ }
+
+ req = bus->alloc_req(bus);
+ if (!req) {
+ prlog(PR_ERR, "I2C: Failed to allocate 'i2c_request'\n");
+ return OPAL_NO_MEM;
+ }
+
+ opal_data = zalloc(sizeof(*opal_data));
+ if (!opal_data) {
+ prlog(PR_ERR, "I2C: Failed to allocate opal data\n");
+ bus->dealloc_req(req);
+ return OPAL_NO_MEM;
+ }
+
+ opal_data->bus = bus;
+ opal_data->token = async_token;
+
+ if (subaddr) {
+ if (dev_addr & 0x1)
+ req->op = SMBUS_READ;
+ else
+ req->op = SMBUS_WRITE;
+
+ req->offset_bytes = 1;
+ req->offset = subaddr;
+ } else {
+ if (dev_addr & 0x1)
+ req->op = I2C_READ;
+ else
+ req->op = I2C_WRITE;
+ }
+
+ req->dev_addr = (dev_addr >> 1) & 0x7f;
+ req->rw_len = len;
+ req->rw_buf = (void *)buffer;
+ req->completion = opal_p8_i2c_request_complete;
+ req->user_data = opal_data;
+
+ /* Finally, queue the OPAL i2c request and return */
+ rc = bus->queue_req(bus, req);
+ if (rc)
+ return rc;
+
+ return OPAL_ASYNC_COMPLETION;
+}
+
+void p8_i2c_init(void)
+{
+ struct p8_i2c_master_port *port, *prev_port;
+ uint32_t bus_speed, lb_freq, count;
+ struct dt_node *i2cm, *i2cm_port;
+ struct i2c_bus *bus, *next_bus;
+ struct p8_i2c_master *master;
+ uint64_t mode, ex_stat;
+ int rc;
+
+ dt_for_each_compatible(dt_root, i2cm, "ibm,power8-i2cm") {
+ master = zalloc(sizeof(*master));
+ if (!master) {
+ prlog(PR_ERR, "I2C: Failed to allocate p8_i2c_master\n");
+ goto exit_free_list;
+ }
+
+ /* Bus speed in KHz */
+ bus_speed = dt_prop_get_u32(i2cm, "bus-speed-khz");
+ lb_freq = dt_prop_get_u32(i2cm, "local-bus-freq-mhz");
+
+ /* Initialise the i2c master structure */
+ master->chip_id = dt_get_chip_id(i2cm);
+ master->xscom_base = dt_get_address(i2cm, 0, NULL);
+
+ rc = xscom_read(master->chip_id, master->xscom_base +
+ I2C_EXTD_STAT_REG, &ex_stat);
+ if (rc) {
+ prlog(PR_ERR, "I2C: Failed to read EXTD_STAT_REG\n");
+ goto exit_free_master;
+ }
+
+ master->fifo_size = GETFIELD(I2C_EXTD_STAT_FIFO_SIZE, ex_stat);
+ master->state.req_state = REQ_LIST_EMPTY;
+ list_head_init(&master->req_list);
+ master->poll_interval = p8_i2c_get_poll_interval(bus_speed);
+ master->poll_count = MAX_POLL_COUNT(master->poll_interval);
+
+ /* Reset the i2c engine */
+ rc = xscom_write(master->chip_id, master->xscom_base +
+ I2C_RESET_I2C_REG, 0);
+ if (rc) {
+ prlog(PR_ERR, "I2C: Failed to reset the i2c engine\n");
+ goto exit_free_master;
+ }
+
+ /* Set the bit rate divisor value for the base bus speed
+ * this engine operates
+ */
+ rc = xscom_read(master->chip_id, master->xscom_base +
+ I2C_MODE_REG, &mode);
+ if (rc) {
+ prlog(PR_ERR, "I2C: Failed to read MODE_REG\n");
+ goto exit_free_master;
+ }
+
+ master->bit_rate_div = p8_i2c_get_bit_rate_divisor(lb_freq,
+ bus_speed);
+ mode = SETFIELD(I2C_MODE_BIT_RATE_DIV, mode,
+ master->bit_rate_div);
+ rc = xscom_write(master->chip_id, master->xscom_base +
+ I2C_MODE_REG, mode);
+ if (rc) {
+ prlog(PR_ERR, "I2C: Failed to set bit_rate_div in MODE_REG\n");
+ goto exit_free_master;
+ }
+
+ rc = p8_i2c_prog_watermark(master);
+ if (rc)
+ goto exit_free_master;
+
+ /* Allocate ports driven by this master */
+ count = 0;
+ dt_for_each_child(i2cm, i2cm_port)
+ count++;
+
+ port = zalloc(sizeof(*port) * count);
+ if (!port) {
+ prlog(PR_ERR, "I2C: Insufficient memory\n");
+ goto exit_free_master;
+ }
+
+ dt_for_each_child(i2cm, i2cm_port) {
+ port->bus_id = dt_prop_get_u32(i2cm_port, "bus-id");
+ port->port_num = dt_prop_get_u32(i2cm_port, "port-id");
+ port->common = master;
+ port->bus.i2c_port = i2cm_port;
+ port->bus.queue_req = p8_i2c_queue_request;
+ port->bus.alloc_req = p8_i2c_alloc_request;
+ port->bus.dealloc_req = p8_i2c_dealloc_request;
+ list_add_tail(&i2c_bus_list, &port->bus.link);
+ port++;
+ }
+ }
+
+ /* Register the poller, one poller will cater all the masters */
+ opal_add_poller(p8_i2c_opal_poll, NULL);
+
+ /* Register the OPAL interface */
+ opal_register(OPAL_I2C_REQUEST, opal_p8_i2c_request, 6);
+
+ return;
+
+exit_free_master:
+ free(master);
+exit_free_list:
+ prev_port = NULL;
+ list_for_each_safe(&i2c_bus_list, bus, next_bus, link) {
+ port = container_of(bus, struct p8_i2c_master_port, bus);
+ if (!prev_port) {
+ prev_port = port;
+ continue;
+ } else if (prev_port->common == port->common) {
+ continue;
+ } else {
+ free(prev_port->common);
+ free(prev_port);
+ prev_port = NULL;
+ }
+ }
+
+ if (prev_port) { /* Last node left */
+ free(prev_port->common);
+ free(prev_port);
+ }
+}
diff --git a/hw/psi.c b/hw/psi.c
index b96b611..de5c58d 100644
--- a/hw/psi.c
+++ b/hw/psi.c
@@ -27,6 +27,7 @@
#include <trace.h>
#include <xscom.h>
#include <chip.h>
+#include <lpc.h>
#include <timebase.h>
#include <platform.h>
@@ -280,7 +281,7 @@ static void handle_extra_interrupt(struct psi *psi)
if (val & PSIHB_IRQ_STAT_FSI)
printf("PSI: FSI irq received\n");
if (val & PSIHB_IRQ_STAT_LPC)
- printf("PSI: LPC/I2C irq received\n");
+ lpc_interrupt();
if (val & PSIHB_IRQ_STAT_LOCAL_ERR)
printf("PSI: ATTN irq received\n");
if (val & PSIHB_IRQ_STAT_HOST_ERR) {
diff --git a/include/i2c.h b/include/i2c.h
new file mode 100644
index 0000000..f8d5b17
--- /dev/null
+++ b/include/i2c.h
@@ -0,0 +1,53 @@
+/* Copyright 2013-2014 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.
+ */
+
+#ifndef __I2C_H
+#define __I2C_H
+
+struct i2c_request;
+extern struct list_head i2c_bus_list;
+
+struct i2c_bus {
+ struct list_node link;
+ struct dt_node *i2c_port;
+ int (*queue_req)(struct i2c_bus *bus,
+ struct i2c_request *req);
+ struct i2c_request *(*alloc_req)(struct i2c_bus *bus);
+ void (*dealloc_req)(struct i2c_request *req);
+};
+
+struct i2c_request {
+ struct list_node link;
+ enum i2c_operation {
+ I2C_READ, /* RAW read from the device without offset */
+ I2C_WRITE, /* RAW write to the device without offset */
+ SMBUS_READ, /* SMBUS protocol read from the device */
+ SMBUS_WRITE, /* SMBUS protocol write to the device */
+ } op;
+ uint32_t dev_addr; /* Slave device address */
+ uint32_t offset_bytes; /* Internal device offset */
+ uint32_t offset; /* Internal device offset */
+ uint32_t rw_len; /* Length of the data request */
+ void *rw_buf; /* Data request buffer */
+ void (*completion)( /* Completion callback */
+ int rc, struct i2c_request *req);
+ void *user_data; /* Client data */
+};
+
+extern void p8_i2c_init(void);
+extern void p8_i2c_interrupt(void);
+
+#endif /* __I2C_H */
diff --git a/include/lpc.h b/include/lpc.h
index 6463c3d..e4e24ea 100644
--- a/include/lpc.h
+++ b/include/lpc.h
@@ -27,6 +27,9 @@ extern void lpc_init(void);
/* Check for a default bus */
extern bool lpc_present(void);
+/* Handle the interrupt from LPC source */
+extern void lpc_interrupt(void);
+
/* Default bus accessors */
extern int64_t lpc_write(enum OpalLPCAddressType addr_type, uint32_t addr,
uint32_t data, uint32_t sz);
diff --git a/include/opal.h b/include/opal.h
index acb2bb5..347e171 100644
--- a/include/opal.h
+++ b/include/opal.h
@@ -141,7 +141,8 @@
#define OPAL_WRITE_TPO 103
#define OPAL_READ_TPO 104
#define OPAL_GET_DPO_STATUS 105
-#define OPAL_LAST 105
+#define OPAL_I2C_REQUEST 106
+#define OPAL_LAST 106
#ifndef __ASSEMBLY__