[Skiboot] [PATCH] p8-i2c: occ: Add support for OCC to use I2C engines

Stewart Smith stewart at linux.vnet.ibm.com
Tue Jun 6 21:11:01 AEST 2017


From: Shilpasri G Bhat <shilpa.bhat at linux.vnet.ibm.com>

This patch adds support to share the I2C engines with host and OCC.
OCC uses I2C engines to read DIMM temperatures and to communicate with
GPU. OCC Flag register is used for locking between host and OCC. Host
requests for the bus by setting a bit in OCC Flag register. OCC sends
an interrupt to indicate the change in ownership.

Originally-from: Oliver O'Halloran <oohall at gmail.com>
Signed-off-by: Shilpasri G Bhat <shilpa.bhat at linux.vnet.ibm.com>
[stewart at linux.vnet.ibm.com: Pretty heavily rework logic, including
fixing bus owner change and separating out occ lock from sensor cache]
Signed-off-by: Stewart Smith <stewart at linux.vnet.ibm.com>
---
 hw/occ.c      |   4 ++
 hw/p8-i2c.c   | 147 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 include/i2c.h |   3 ++
 3 files changed, 154 insertions(+)

diff --git a/hw/occ.c b/hw/occ.c
index 4be4df43efa1..34ef9a74dddc 100644
--- a/hw/occ.c
+++ b/hw/occ.c
@@ -27,6 +27,7 @@
 #include <opal-api.h>
 #include <opal-msg.h>
 #include <timer.h>
+#include <i2c.h>
 
 /* OCC Communication Area for PStates */
 
@@ -1397,6 +1398,9 @@ void occ_p9_interrupt(uint32_t chip_id)
 	if (ireg & OCB_OCI_OCIMISC_IRQ_SHMEM)
 		occ_throttle_poll(NULL);
 
+	if (ireg & OCB_OCI_OCIMISC_IRQ_I2C)
+		p9_i2c_bus_owner_change(chip_id);
+
 	/* We may have masked-out OCB_OCI_OCIMISC_IRQ in the previous
 	 * OCCMISC_AND write. Check if there are any new source bits set,
 	 * and trigger another interrupt if so.
diff --git a/hw/p8-i2c.c b/hw/p8-i2c.c
index fefc1d4b250b..d55d08ee8c34 100644
--- a/hw/p8-i2c.c
+++ b/hw/p8-i2c.c
@@ -200,6 +200,7 @@ struct p8_i2c_master {
 	uint32_t		bytes_sent;
 	bool			irq_ok;		/* Interrupt working ? */
 	bool			occ_cache_dis;  /* I have disabled the cache */
+	bool			occ_lock_acquired; /* Acquired lock from OCC */
 	enum request_state {
 		state_idle,
 		state_occache_dis,
@@ -232,6 +233,8 @@ struct p8_i2c_request {
 	uint64_t		timeout;
 };
 
+static int occ_i2c_unlock(struct p8_i2c_master *master);
+
 static void p8_i2c_print_debug_info(struct p8_i2c_master_port *port,
 				    struct i2c_request *req, uint64_t end_time)
 {
@@ -427,6 +430,10 @@ static void p8_i2c_complete_request(struct p8_i2c_master *master,
 		schedule_timer(&master->sensor_cache,
 			       msecs_to_tb(SENSOR_CACHE_EN_DELAY));
 
+	/* If we're done with i2c master, allow OCC to use it */
+	if (master->occ_lock_acquired && list_empty(&master->req_list))
+		occ_i2c_unlock(master);
+
 	unlock(&master->lock);
 	if (req->completion)
 		req->completion(ret, req);
@@ -1016,6 +1023,94 @@ static int p8_i2c_check_initial_status(struct p8_i2c_master_port *port)
 	return 0;
 }
 
+/*
+ * On POWER9, the I2C may also wish to use some of the i2cm engines,
+ * to do things like read sensor data. There's a couple of shared
+ * registers with the OCC to negotiate locking of the i2cm engines.
+ * See occ/src/occ_405/lock/lock.c
+ */
+static bool occ_uses_master(struct p8_i2c_master *master)
+{
+	/* OCC uses I2CM Engines 1,2 and 3, only on POWER9 */
+	if (master->type == I2C_POWER8 && proc_gen == proc_gen_p9)
+		return master->engine_id >= 1;
+
+	return false;
+}
+
+#define OCCFLG_BASE  0x00000000006C08A
+#define OCCFLG_CLEAR 0x00000000006C08B
+#define OCCFLG_SET   0x00000000006C08C
+
+static int occ_i2c_lock(struct p8_i2c_master *master)
+{
+	u64 occflags, busflag;
+	int rc;
+
+	if (!occ_uses_master(master))
+		return 0;
+
+	if (master->occ_lock_acquired)
+		return 0;
+
+	rc = xscom_read(master->chip_id, OCCFLG_BASE, &occflags);
+	if (rc) {
+		prerror("I2C: Failed to read OCC FLAG register\n");
+		return rc;
+	}
+
+	assert(master->engine_id > 0);
+
+	busflag = PPC_BIT(16 + (master->engine_id - 1) * 2);
+
+	DBG("occflags = %llx (locks = %.6llx)\n", (u64)occflags,
+	    GETFIELD(PPC_BITMASK(16, 22), occflags));
+
+	rc = xscom_write(master->chip_id, OCCFLG_SET, busflag);
+	if (rc) {
+		prerror("I2C: Failed to write OCC FLAG register\n");
+		return rc;
+	}
+
+	/* If the OCC also has this bus locked then wait for IRQ */
+	if (occflags & (busflag << 1))
+		return 1;
+
+	master->occ_lock_acquired = true;
+
+	return 0;
+}
+
+static int occ_i2c_unlock(struct p8_i2c_master *master)
+{
+	u64 busflag, occflags;
+	int rc;
+
+	if (!occ_uses_master(master))
+		return 0;
+
+	rc = xscom_read(master->chip_id, OCCFLG_BASE, &occflags);
+	if (rc) {
+		prerror("I2C: Failed to read OCC Flag register\n");
+		return rc;
+	}
+
+	busflag = PPC_BIT(16 + (master->engine_id - 1) * 2);
+
+	if (!(occflags & busflag)) {
+		prerror("I2C: busflag for %d already cleared (flags = %.16llx)",
+			master->engine_id, occflags);
+	}
+
+	rc = xscom_write(master->chip_id, OCCFLG_CLEAR, busflag);
+	if (rc)
+		prerror("I2C: Failed to write OCC Flag register\n");
+
+	master->occ_lock_acquired = false;
+
+	return rc;
+}
+
 static int p8_i2c_start_request(struct p8_i2c_master *master,
 				struct i2c_request *req)
 {
@@ -1035,6 +1130,7 @@ static int p8_i2c_start_request(struct p8_i2c_master *master,
 	if (master->type == I2C_CENTAUR && !master->occ_cache_dis) {
 		DBG("Disabling OCC cache...\n");
 		rc = centaur_disable_sensor_cache(master->chip_id);
+
 		if (rc < 0) {
 			log_simple_error(&e_info(OPAL_RC_I2C_START_REQ),
 					 "I2C: Failed "
@@ -1052,6 +1148,23 @@ static int p8_i2c_start_request(struct p8_i2c_master *master,
 		}
 	}
 
+	/*
+	 * on P9 we need to set the "I2C master using bit" so we don't
+	 * conflict with the OCC's use of the i2c master.
+	 */
+	rc = occ_i2c_lock(master);
+	if (rc < 0) {
+		log_simple_error(&e_info(OPAL_RC_I2C_START_REQ),
+				 "I2C: Failed to get I2CM lock from OCC\n");
+		return rc;
+	}
+	if (rc > 0) {
+		/* Wait for OCC IRQ */
+		master->state = state_occache_dis;
+		schedule_timer(&master->recovery, rc);
+		return 0;
+	}
+
 	/* Convert the offset if needed */
 	if (req->offset_bytes) {
 		int i;
@@ -1163,6 +1276,36 @@ static void p8_i2c_check_work(struct p8_i2c_master *master)
 	}
 }
 
+/* OCC IRQ Handler for I2C Ownership Change*/
+void p9_i2c_bus_owner_change(u32 chip_id)
+{
+	struct proc_chip *chip = get_chip(chip_id);
+	struct p8_i2c_master *master = NULL;
+	int rc;
+
+	assert(chip);
+	list_for_each(&chip->i2cms, master, link) {
+		if (master->state == state_idle  ||
+		    master->state != state_occache_dis)
+			continue;
+
+		lock(&master->lock);
+
+		/* Can we now lock this master? */
+		rc = occ_i2c_lock(master);
+		if (rc)
+			continue;
+
+		/* Run the state machine */
+		p8_i2c_check_status(master);
+
+		/* Check for new work */
+		p8_i2c_check_work(master);
+
+		unlock(&master->lock);
+	}
+}
+
 static int p8_i2c_queue_request(struct i2c_request *req)
 {
 	struct i2c_bus *bus = req->bus;
@@ -1322,6 +1465,9 @@ static void p8_i2c_recover(struct timer *t __unused, void *data,
 		master->occ_cache_dis = false;
 	}
 
+	if (master->occ_lock_acquired && list_empty(&master->req_list))
+		occ_i2c_unlock(master);
+
 	/* Re-check for new work */
 	p8_i2c_check_work(master);
 	unlock(&master->lock);
@@ -1506,6 +1652,7 @@ static void p8_i2c_init_one(struct dt_node *i2cm, enum p8_i2c_master_type type)
 				 "Failed to read EXTD_STAT_REG\n");
 		if (master->type == I2C_CENTAUR)
 			centaur_enable_sensor_cache(master->chip_id);
+
 		free(master);
 		return;
 	}
diff --git a/include/i2c.h b/include/i2c.h
index 2c4c6a1d4bd2..e5e8584b4073 100644
--- a/include/i2c.h
+++ b/include/i2c.h
@@ -110,4 +110,7 @@ static inline int i2c_check_quirk(struct i2c_request *req, int *rc)
 extern void p8_i2c_init(void);
 extern void p8_i2c_interrupt(uint32_t chip_id);
 
+/* P9 I2C Ownership Change OCC interrupt handler */
+extern void p9_i2c_bus_owner_change(u32 chip_id);
+
 #endif /* __I2C_H */
-- 
2.9.4



More information about the Skiboot mailing list