Skip to content

Commit

Permalink
p8-i2c: occ: Add support for OCC to use I2C engines
Browse files Browse the repository at this point in the history
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@gmail.com>
Signed-off-by: Shilpasri G Bhat <shilpa.bhat@linux.vnet.ibm.com>
[stewart@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@linux.vnet.ibm.com>
  • Loading branch information
shilpasri authored and stewartsmith committed Jun 6, 2017
1 parent 682da46 commit c5fa0d7
Show file tree
Hide file tree
Showing 3 changed files with 154 additions and 0 deletions.
4 changes: 4 additions & 0 deletions hw/occ.c
Expand Up @@ -27,6 +27,7 @@
#include <opal-api.h>
#include <opal-msg.h>
#include <timer.h>
#include <i2c.h>

/* OCC Communication Area for PStates */

Expand Down Expand Up @@ -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.
Expand Down
147 changes: 147 additions & 0 deletions hw/p8-i2c.c
Expand Up @@ -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,
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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)
{
Expand All @@ -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 "
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
}
Expand Down
3 changes: 3 additions & 0 deletions include/i2c.h
Expand Up @@ -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 */

0 comments on commit c5fa0d7

Please sign in to comment.