aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--hw/p8-i2c.c179
1 files changed, 157 insertions, 22 deletions
diff --git a/hw/p8-i2c.c b/hw/p8-i2c.c
index faaf6ed..2216635 100644
--- a/hw/p8-i2c.c
+++ b/hw/p8-i2c.c
@@ -171,7 +171,11 @@ DEFINE_LOG_ENTRY(OPAL_RC_I2C_RESET, OPAL_INPUT_OUTPUT_ERR_EVT, OPAL_I2C,
#define I2C_RESIDUAL_BACK_END PPC_BITMASK(16, 31)
/* Port busy register */
-#define I2C_PORT_BUYS_REG 0xe
+#define I2C_PORT_BUSY_REG 0xe
+#define I2C_SET_S_SCL_REG 0xd
+#define I2C_RESET_S_SCL_REG 0xf
+#define I2C_SET_S_SDA_REG 0x10
+#define I2C_RESET_S_SDA_REG 0x11
enum p8_i2c_master_type {
I2C_POWER8,
@@ -207,6 +211,7 @@ struct p8_i2c_master {
struct timer sensor_cache;
uint8_t recovery_pass;
struct list_node link;
+ struct list_head ports;
};
struct p8_i2c_master_port {
@@ -214,6 +219,7 @@ struct p8_i2c_master_port {
struct p8_i2c_master *master;
uint32_t port_num;
uint32_t bit_rate_div; /* Divisor to set bus speed*/
+ struct list_node link;
};
struct p8_i2c_request {
@@ -463,6 +469,153 @@ static void p8_i2c_translate_error(struct i2c_request *req, uint64_t status)
req->result = OPAL_I2C_TIMEOUT;
}
+static void p8_i2c_force_reset(struct p8_i2c_master *master)
+{
+ struct p8_i2c_master_port *p;
+ uint64_t mode;
+ int rc;
+
+ /* Reset the i2c engine */
+ rc = xscom_write(master->chip_id, master->xscom_base +
+ I2C_RESET_I2C_REG, 0);
+ if (rc) {
+ log_simple_error(&e_info(OPAL_RC_I2C_RESET), "I2C: Failed "
+ "to reset the i2c engine\n");
+ return;
+ }
+ time_wait_us_nopoll(10);
+ /* Reset port busy */
+ rc = xscom_write(master->chip_id, master->xscom_base +
+ I2C_PORT_BUSY_REG, 0x8000000000000000ULL);
+ if (rc) {
+ log_simple_error(&e_info(OPAL_RC_I2C_RESET), "I2C: Failed "
+ "to reset port busy on i2c engine\n");
+ return;
+ }
+ time_wait_us_nopoll(10);
+ list_for_each(&master->ports, p, link) {
+ mode = 0;
+ mode = SETFIELD(I2C_MODE_PORT_NUM, mode, p->port_num);
+ mode = SETFIELD(I2C_MODE_BIT_RATE_DIV, mode, p->bit_rate_div);
+ mode |= I2C_MODE_DIAGNOSTIC;
+ 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");
+
+ time_wait_us_nopoll(10);
+ rc = xscom_write(master->chip_id,
+ master->xscom_base + I2C_RESET_S_SCL_REG,
+ 0);
+ if (rc)
+ prlog(PR_ERR, "I2C: Failed to reset S_SCL\n");
+
+ time_wait_us_nopoll(10);
+ rc = xscom_write(master->chip_id,
+ master->xscom_base + I2C_SET_S_SCL_REG,
+ 0);
+ if (rc)
+ prlog(PR_ERR, "I2C: Failed to set S_SCL\n");
+
+ /* Manually reset */
+ time_wait_us_nopoll(10);
+ rc = xscom_write(master->chip_id,
+ master->xscom_base + I2C_RESET_S_SCL_REG,
+ 0);
+ if (rc)
+ prlog(PR_ERR, "I2C: sendStop: fail reset S_SCL\n");
+
+ time_wait_us_nopoll(10);
+ rc = xscom_write(master->chip_id,
+ master->xscom_base + I2C_RESET_S_SDA_REG,
+ 0);
+ if (rc)
+ prlog(PR_ERR, "I2C: sendStop: fail reset S_SDA\n");
+
+ time_wait_us_nopoll(10);
+ rc = xscom_write(master->chip_id,
+ master->xscom_base + I2C_SET_S_SCL_REG,
+ 0);
+ if (rc)
+ prlog(PR_ERR, "I2C: sendStop: fail set S_SCL\n");
+
+ time_wait_us_nopoll(10);
+ rc = xscom_write(master->chip_id,
+ master->xscom_base + I2C_SET_S_SDA_REG,
+ 0);
+ if (rc)
+ prlog(PR_ERR, "I2C: sendStop: fail set 2 S_SDA\n");
+
+ mode ^= I2C_MODE_DIAGNOSTIC;
+ time_wait_us_nopoll(10);
+ 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");
+ }
+}
+
+static int p8_i2c_reset_engine(struct p8_i2c_master *master)
+{
+ struct p8_i2c_master_port *p;
+ int reset_loops;
+ int rc;
+ uint64_t status;
+
+ list_for_each(&master->ports, p, link) {
+ /*
+ * Reset each port by issuing a STOP command to slave.
+ *
+ * Reprogram the mode register with 'enhanced bit' set
+ */
+ rc = p8_i2c_prog_mode(p, true);
+ if (rc) {
+ log_simple_error(&e_info(OPAL_RC_I2C_RESET),
+ "I2C: Failed to program the MODE_REG\n");
+ return -1;
+ }
+
+ /* Send an immediate stop */
+ master->state = state_error;
+ rc = xscom_write(master->chip_id, master->xscom_base +
+ I2C_CMD_REG, I2C_CMD_WITH_STOP);
+ if (rc) {
+ log_simple_error(&e_info(OPAL_RC_I2C_RESET),
+ "I2C: Failed to issue immediate STOP\n");
+ return -1;
+ }
+
+ /* Wait for COMMAND COMPLETE */
+ reset_loops = 0;
+ do {
+ 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");
+ return -1;
+ }
+ if (! (status & I2C_STAT_CMD_COMP)) {
+ time_wait_ms(10);
+ if (reset_loops++ == 5) {
+ prlog(PR_WARNING, "I2C: Retrying reset, with force!\n");
+ p8_i2c_force_reset(master);
+ continue;
+ }
+ if (reset_loops == 10) {
+ log_simple_error(&e_info(OPAL_RC_I2C_TRANSFER),
+ "I2C: Failed to recover i2c engine\n");
+ break;
+ }
+ }
+ } while (! (status & I2C_STAT_CMD_COMP));
+ }
+ return 0;
+}
+
static void p8_i2c_status_error(struct p8_i2c_master_port *port,
struct i2c_request *req,
uint64_t status)
@@ -495,30 +648,10 @@ static void p8_i2c_status_error(struct p8_i2c_master_port *port,
*/
p8_i2c_complete_request(master, req, req->result);
} else {
- /*
- * Reset the bus by issuing a STOP command to slave.
- *
- * Reprogram the mode register with 'enhanced bit' set
- */
- rc = p8_i2c_prog_mode(port, true);
- if (rc) {
- log_simple_error(&e_info(OPAL_RC_I2C_RESET), "I2C: "
- "Failed to program the MODE_REG\n");
+ if (p8_i2c_reset_engine(master))
goto exit;
- }
-
/* Enable the interrupt */
p8_i2c_enable_irqs(master);
-
- /* Send an immediate stop */
- master->state = state_error;
- rc = xscom_write(master->chip_id, master->xscom_base +
- I2C_CMD_REG, I2C_CMD_WITH_STOP);
- if (rc) {
- log_simple_error(&e_info(OPAL_RC_I2C_RESET), "I2C: "
- "Failed to issue immediate STOP\n");
- goto exit;
- }
}
return;
@@ -1363,6 +1496,7 @@ static void p8_i2c_init_one(struct dt_node *i2cm, enum p8_i2c_master_type type)
master->fifo_size = GETFIELD(I2C_EXTD_STAT_FIFO_SIZE, ex_stat);
list_head_init(&master->req_list);
+ list_head_init(&master->ports);
/* Check if interrupt is usable */
master->irq_ok = p8_i2c_has_irqs(master);
@@ -1422,6 +1556,7 @@ static void p8_i2c_init_one(struct dt_node *i2cm, enum p8_i2c_master_type type)
port->bus.set_req_timeout = p8_i2c_set_request_timeout;
port->bus.run_req = p8_i2c_run_request;
i2c_add_bus(&port->bus);
+ list_add_tail(&master->ports, &port->link);
/* Add OPAL properties to the bus node */
p8_i2c_add_bus_prop(port);