diff options
author | Benjamin Herrenschmidt <benh@kernel.crashing.org> | 2014-10-28 15:57:51 +1100 |
---|---|---|
committer | Benjamin Herrenschmidt <benh@kernel.crashing.org> | 2014-10-28 15:57:51 +1100 |
commit | 931ffa22751ba143f0269ba95dd9bf83def4a3fa (patch) | |
tree | 1cd824f303ab807686741fb6ffdc42fa6d575520 | |
parent | 5d254c78374a1092318785a1fcdda1eca54eb761 (diff) | |
parent | 62be0cb831e9ad497b77ae828e40a407cf97b72c (diff) | |
download | skiboot-931ffa22751ba143f0269ba95dd9bf83def4a3fa.zip skiboot-931ffa22751ba143f0269ba95dd9bf83def4a3fa.tar.gz skiboot-931ffa22751ba143f0269ba95dd9bf83def4a3fa.tar.bz2 |
Merge remote-tracking branch 'ltcgit/master'
-rw-r--r-- | core/init.c | 177 | ||||
-rw-r--r-- | core/test/run-mem_region_init.c | 5 | ||||
-rw-r--r-- | hw/Makefile.inc | 2 | ||||
-rw-r--r-- | hw/lpc.c | 9 | ||||
-rw-r--r-- | hw/p8-i2c.c | 1111 | ||||
-rw-r--r-- | hw/psi.c | 3 | ||||
-rw-r--r-- | include/i2c.h | 53 | ||||
-rw-r--r-- | include/lpc.h | 3 | ||||
-rw-r--r-- | include/opal.h | 3 |
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 @@ -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); + } +} @@ -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__ |