aboutsummaryrefslogtreecommitdiff
path: root/hw/p8-i2c.c
diff options
context:
space:
mode:
Diffstat (limited to 'hw/p8-i2c.c')
-rw-r--r--hw/p8-i2c.c166
1 files changed, 75 insertions, 91 deletions
diff --git a/hw/p8-i2c.c b/hw/p8-i2c.c
index 0defe7b..ce56b26 100644
--- a/hw/p8-i2c.c
+++ b/hw/p8-i2c.c
@@ -190,6 +190,7 @@ struct p8_i2c_master {
struct lock lock; /* Lock to guard the members */
enum p8_i2c_master_type type; /* P8 vs. Centaur */
uint64_t start_time; /* Request start time */
+ uint64_t last_update;
uint64_t poll_interval; /* Polling interval */
uint64_t byte_timeout; /* Timeout per byte */
uint64_t xscom_base; /* xscom base of i2cm */
@@ -235,6 +236,17 @@ struct p8_i2c_request {
static int occ_i2c_unlock(struct p8_i2c_master *master);
+
+static int64_t i2cm_read_reg(struct p8_i2c_master *m, int reg, uint64_t *val)
+{
+ return xscom_read(m->chip_id, m->xscom_base + reg, val);
+}
+
+static int64_t i2cm_write_reg(struct p8_i2c_master *m, int reg, uint64_t val)
+{
+ return xscom_write(m->chip_id, m->xscom_base + reg, val);
+}
+
static void p8_i2c_print_debug_info(struct p8_i2c_master_port *port,
struct i2c_request *req, uint64_t end_time)
{
@@ -352,6 +364,17 @@ static int p8_i2c_enable_irqs(struct p8_i2c_master *master)
return rc;
}
+static void p8_i2c_reset_timeout(struct p8_i2c_master *master,
+ struct i2c_request *req)
+{
+ struct p8_i2c_request *request;
+ uint64_t now = mftb();
+
+ master->last_update = now;
+ request = container_of(req, struct p8_i2c_request, req);
+ schedule_timer_at(&master->timeout, now + request->timeout);
+}
+
static int p8_i2c_prog_watermark(struct p8_i2c_master *master)
{
uint64_t watermark;
@@ -795,10 +818,13 @@ static void p8_i2c_status_data_request(struct p8_i2c_master *master,
"state %d in data req !\n", master->state);
rc = OPAL_WRONG_STATE;
}
- if (rc)
+
+ if (rc) {
p8_i2c_complete_request(master, req, rc);
- else
+ } else {
p8_i2c_enable_irqs(master);
+ p8_i2c_reset_timeout(master, req);
+ }
}
static void p8_i2c_complete_offset(struct p8_i2c_master *master,
@@ -848,6 +874,7 @@ static void p8_i2c_complete_offset(struct p8_i2c_master *master,
/* Enable the interrupts */
p8_i2c_enable_irqs(master);
+ p8_i2c_reset_timeout(master, req);
return;
complete:
@@ -886,72 +913,61 @@ static void p8_i2c_status_cmd_completion(struct p8_i2c_master *master,
p8_i2c_complete_request(master, req, rc);
}
-static void p8_i2c_check_status(struct p8_i2c_master *master, bool timeout)
+static void p8_i2c_check_status(struct p8_i2c_master *master)
{
struct p8_i2c_master_port *port;
+ struct p8_i2c_request *request;
+ uint64_t status, deadline, now;
struct i2c_request *req;
- uint64_t status, now = mftb();
int rc;
- /* If we are idle, just return, we'll catch error conditions
- * when we next try to enqueue a request
+ /*
+ * When idle or waiting for the occ to release the bus there's
+ * nothing to check. Error states are handled when starting
+ * a new request.
*/
if (master->state == state_idle || master->state == state_occache_dis)
return;
- /* Read status register */
- rc = xscom_read(master->chip_id, master->xscom_base + I2C_STAT_REG,
- &status);
- if (rc) {
- log_simple_error(&e_info(OPAL_RC_I2C_TRANSFER), "I2C: Failed "
- "to read the STAT_REG\n");
+ /* A non-idle master should always have a pending request */
+ req = list_top(&master->req_list, struct i2c_request, link);
+ if (!req) {
+ prerror("I2C: Master is not idle and has no pending request\n");
return;
}
- /* Nothing happened ? Go back */
- if (!timeout && !(status & (I2C_STAT_ANY_ERR | I2C_STAT_DATA_REQ |
- I2C_STAT_CMD_COMP)))
- return;
-
- DBG("Non-0 status: %016llx\n", status);
-
- /* Mask the interrupts for this engine */
- rc = xscom_write(master->chip_id, master->xscom_base + I2C_INTR_REG,
- ~I2C_INTR_ALL);
+ rc = i2cm_read_reg(master, I2C_STAT_REG, &status);
if (rc) {
- log_simple_error(&e_info(OPAL_RC_I2C_TRANSFER), "I2C: Failed "
- "to disable the interrupts\n");
+ log_simple_error(&e_info(OPAL_RC_I2C_TRANSFER),
+ "I2C: Failed to read the STAT_REG\n");
return;
}
- /* No request ? That's not normal ! Bail out without re-enabling
- * the interrupt
- */
- req = list_top(&master->req_list, struct i2c_request, link);
- if (req == NULL) {
+ /* Mask the interrupts for this engine */
+ rc = i2cm_write_reg(master, I2C_INTR_REG, ~I2C_INTR_ALL);
+ if (rc) {
log_simple_error(&e_info(OPAL_RC_I2C_TRANSFER),
- "I2C: Interrupt with no request"
- ", status=0x%016llx\n", status);
+ "I2C: Failed to disable the interrupts\n");
return;
}
/* Get port for current request */
port = container_of(req->bus, struct p8_i2c_master_port, bus);
+ now = mftb();
- /* Handle the status in that order: errors, data requests and
- * command completion.
- */
- if (status & I2C_STAT_ANY_ERR) {
- /* Mask status to avoid some unrelated bit overwriting
- * our pseudo-status "timeout" bit 63
- */
+ request = container_of(req, struct p8_i2c_request, req);
+ deadline = master->last_update + request->timeout;
+
+ if (status & I2C_STAT_ANY_ERR)
p8_i2c_status_error(port, req, status & I2C_STAT_ANY_ERR, now);
- } else if (status & I2C_STAT_DATA_REQ)
+ else if (status & I2C_STAT_DATA_REQ)
p8_i2c_status_data_request(master, req, status);
else if (status & I2C_STAT_CMD_COMP)
p8_i2c_status_cmd_completion(master, req, now);
- else if (timeout)
+ else if (tb_compare(now, deadline) == TB_AAFTERB)
p8_i2c_status_error(port, req, I2C_STAT_PSEUDO_TIMEOUT, now);
+ else
+ p8_i2c_enable_irqs(master);
}
static int p8_i2c_check_initial_status(struct p8_i2c_master_port *port)
@@ -1129,8 +1145,8 @@ static int p8_i2c_start_request(struct p8_i2c_master *master,
struct p8_i2c_master_port *port;
struct p8_i2c_request *request =
container_of(req, struct p8_i2c_request, req);
- uint64_t cmd, now, poll_interval;
- int64_t rc, tbytes;
+ uint64_t cmd, poll_interval;
+ int64_t rc;
DBG("Starting req %d len=%d addr=%02x (offset=%x)\n",
req->op, req->rw_len, req->dev_addr, req->offset);
@@ -1259,18 +1275,14 @@ static int p8_i2c_start_request(struct p8_i2c_master *master,
poll_interval = TIMER_POLL;
else
poll_interval = master->poll_interval;
- now = schedule_timer(&master->poller, poll_interval);
+ schedule_timer(&master->poller, poll_interval);
- /* Calculate and start timeout */
- if (request->timeout) {
- request->timeout += now;
- } else {
- tbytes = req->rw_len + req->offset_bytes + 2;
- request->timeout = now + tbytes * master->byte_timeout;
- }
+ /* If we don't have a user-set timeout then use the master's default */
+ if (!request->timeout)
+ request->timeout = master->byte_timeout;
/* Start the timeout */
- schedule_timer_at(&master->timeout, request->timeout);
+ p8_i2c_reset_timeout(master, req);
return OPAL_SUCCESS;
}
@@ -1313,7 +1325,7 @@ void p9_i2c_bus_owner_change(u32 chip_id)
master->state = state_idle;
p8_i2c_check_work(master);
- p8_i2c_check_status(master, false);
+ p8_i2c_check_status(master);
done:
unlock(&master->lock);
}
@@ -1388,7 +1400,7 @@ static uint64_t p8_i2c_run_request(struct i2c_request *req)
uint64_t poll_interval = 0;
lock(&master->lock);
- p8_i2c_check_status(master, false);
+ p8_i2c_check_status(master);
p8_i2c_check_work(master);
poll_interval = master->poll_interval;
unlock(&master->lock);
@@ -1414,51 +1426,23 @@ static inline uint64_t p8_i2c_get_poll_interval(uint32_t bus_speed)
return usecs_to_tb(usec);
}
-static void p8_i2c_timeout(struct timer *t __unused, void *data, uint64_t now)
+static void p8_i2c_timeout(struct timer *t __unused, void *data,
+ uint64_t __unused now)
{
- struct p8_i2c_master_port *port;
struct p8_i2c_master *master = data;
- struct p8_i2c_request *request;
- struct i2c_request *req;
lock(&master->lock);
- /* This could be spurrious ... */
- if (master->state == state_idle) {
- DBG("I2C: Timeout in idle state\n");
- goto exit;
- }
-
- /* We might still be spurrious timer, we need to ensure that the
- * head request is indeed old enough to be the one timing out
- */
- req = list_top(&master->req_list, struct i2c_request, link);
- if (req == NULL) {
- DBG("I2C: Timeout with no"
- " pending request state=%d\n", master->state);
- goto exit;
- }
- request = container_of(req, struct p8_i2c_request, req);
- if (tb_compare(now, request->timeout) == TB_ABEFOREB) {
- DBG("I2C: Timeout with request not expired\n");
- goto exit;
- }
-
- request->timeout = 0ul;
- port = container_of(req->bus, struct p8_i2c_master_port, bus);
-
- DBG("timeout on c%de%d\n",
- master->chip_id, master->engine_id);
+ DBG("timeout on c%de%d\n", master->chip_id, master->engine_id);
/*
- * Run through the usual path with timeout checking. The command might
- * have been completed successfully and we just lost an interrupt
- * somewhere.
+ * Run through the usual status checks. It's possible to get spurious
+ * timeouts due to races between the interrupt/poller paths and the
+ * timeout handler. So we do all the checking, all the time.
*/
- p8_i2c_check_status(port->master, true);
- p8_i2c_check_work(port->master);
+ p8_i2c_check_status(master);
+ p8_i2c_check_work(master);
- exit:
unlock(&master->lock);
}
@@ -1531,7 +1515,7 @@ static void p8_i2c_poll(struct timer *t __unused, void *data, uint64_t now)
return;
lock(&master->lock);
- p8_i2c_check_status(master, false);
+ p8_i2c_check_status(master);
if (master->state != state_idle)
schedule_timer_at(&master->poller, now + master->poll_interval);
p8_i2c_check_work(master);
@@ -1553,7 +1537,7 @@ void p8_i2c_interrupt(uint32_t chip_id)
lock(&master->lock);
/* Run the state machine */
- p8_i2c_check_status(master, false);
+ p8_i2c_check_status(master);
/* Check for new work */
p8_i2c_check_work(master);