aboutsummaryrefslogtreecommitdiff
path: root/hw/p8-i2c.c
diff options
context:
space:
mode:
authorNeelesh Gupta <neelegup@linux.vnet.ibm.com>2014-10-25 23:33:52 +0530
committerStewart Smith <stewart@linux.vnet.ibm.com>2014-10-27 18:13:41 +1100
commit2fe43d8776b19ff5896312c8bfef6a46156d0d48 (patch)
tree14c00d74019d94ae7ce7af1d79565cc6c0b18adc /hw/p8-i2c.c
parent531bdefda91102cb3240e4e3289900af0a50e78b (diff)
downloadskiboot-2fe43d8776b19ff5896312c8bfef6a46156d0d48.zip
skiboot-2fe43d8776b19ff5896312c8bfef6a46156d0d48.tar.gz
skiboot-2fe43d8776b19ff5896312c8bfef6a46156d0d48.tar.bz2
hw/i2c: i2c driver infrastructure providing device I/O
This patch adds the generic i2c driver infrastructure to handle multiple i2c master cores present in the system and exposes structures and interfaces for the client to perform I/O on the i2c slave devices. The driver adds the capability to queue multiple requests from client and let clients notified asynchronously after completion. It does that by handling the i2c interrupt or through OPAL poller in the absence of interrupt. Signed-off-by: Neelesh Gupta <neelegup@linux.vnet.ibm.com> Signed-off-by: Stewart Smith <stewart@linux.vnet.ibm.com>
Diffstat (limited to 'hw/p8-i2c.c')
-rw-r--r--hw/p8-i2c.c1111
1 files changed, 1111 insertions, 0 deletions
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);
+ }
+}