aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenjamin Herrenschmidt <benh@kernel.crashing.org>2014-11-10 15:06:01 +1100
committerBenjamin Herrenschmidt <benh@kernel.crashing.org>2014-11-10 15:06:01 +1100
commitd3d9c9e6b9a20bcc138d98c87ffaa48b4776d4c2 (patch)
tree1801db3f6a1bc021d0567d106dc95cf9f7feb03a
parent49f3f0eccac49f66d35c951204b06933b46ad828 (diff)
downloadskiboot-d3d9c9e6b9a20bcc138d98c87ffaa48b4776d4c2.zip
skiboot-d3d9c9e6b9a20bcc138d98c87ffaa48b4776d4c2.tar.gz
skiboot-d3d9c9e6b9a20bcc138d98c87ffaa48b4776d4c2.tar.bz2
i2c: Improve operations without interrupts
We use a timer for running the state machine without interrupts. When we get proper timers using the SLW engine, we'll be able to also reduce the timeouts but for now, use a large (4s) per-char timeout for polled mode as it depends on the opal poll frequency which can be very slow. Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
-rw-r--r--hw/p8-i2c.c109
1 files changed, 91 insertions, 18 deletions
diff --git a/hw/p8-i2c.c b/hw/p8-i2c.c
index 16d83eb..ef17c40 100644
--- a/hw/p8-i2c.c
+++ b/hw/p8-i2c.c
@@ -28,10 +28,12 @@
#ifdef DEBUG
#define DBG(fmt...) prlog(PR_ERR, "I2C: " fmt)
-#define I2C_TIMEOUT_MS 1 /* 1s timeout */
+#define I2C_TIMEOUT_IRQ_MS 100 /* 100ms/byte timeout */
+#define I2C_TIMEOUT_POLL_MS 4000 /* 4s/byte timeout */
#else
#define DBG(fmt...) prlog(PR_TRACE, "I2C: " fmt)
-#define I2C_TIMEOUT_MS 100 /* 100 msec timeout */
+#define I2C_TIMEOUT_IRQ_MS 1 /* 1ms/byte timeout */
+#define I2C_TIMEOUT_POLL_MS 4000 /* 4s/byte timeout */
#endif
#define USEC_PER_SEC 1000000
@@ -168,13 +170,13 @@ struct p8_i2c_master {
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 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 */
uint8_t obuf[4]; /* Offset buffer */
uint32_t bytes_sent;
+ bool irq_ok; /* Interrupt working ? */
enum request_state {
state_idle,
state_offset,
@@ -183,6 +185,7 @@ struct p8_i2c_master {
state_recovery,
} state;
struct list_head req_list; /* Request queue head */
+ struct timer poller;
struct timer timeout;
};
@@ -201,6 +204,27 @@ struct p8_i2c_request {
static LIST_HEAD(i2c_bus_list);
+static bool p8_i2c_has_irqs(void)
+{
+ struct proc_chip *chip = next_chip(NULL);
+
+ /* The i2c interrurpt was only added to Murano DD2.1 and Venice
+ * DD2.0. When operating without interrupts, we need to bump the
+ * timeouts as we rely solely on the polls from Linux which can
+ * be up to 2s appart !
+ *
+ * Also we don't have interrupts for the Centaur i2c.
+ */
+ switch (chip->type) {
+ case PROC_CHIP_P8_MURANO:
+ return chip->ec_level >= 0x21;
+ case PROC_CHIP_P8_VENICE:
+ return chip->ec_level >= 0x20;
+ default:
+ return false;
+ }
+}
+
static int p8_i2c_enable_irqs(struct p8_i2c_master *master)
{
int rc;
@@ -701,8 +725,8 @@ static int p8_i2c_start_request(struct p8_i2c_master *master,
{
struct p8_i2c_request *request =
container_of(req, struct p8_i2c_request, req);
- uint64_t cmd;
- int rc;
+ uint64_t cmd, now;
+ int rc, tbytes;
DBG("Starting req %d len=%d addr=%02x (offset=%x)\n",
req->op, req->rw_len, req->dev_addr, req->offset);
@@ -773,8 +797,21 @@ static int p8_i2c_start_request(struct p8_i2c_master *master,
/* Enable the interrupts */
p8_i2c_enable_irqs(master);
+ /* If interrupts aren't working, start the poll timer. Also
+ * calculate the timeout differently
+ */
+ now = mftb();
+ tbytes = req->rw_len + req->offset_bytes + 2;
+ if (!master->irq_ok) {
+ schedule_timer_at(&master->poller,
+ now + master->poll_interval);
+ request->timeout = now +
+ tbytes * msecs_to_tb(I2C_TIMEOUT_POLL_MS);
+ } else
+ request->timeout = now +
+ tbytes * msecs_to_tb(I2C_TIMEOUT_IRQ_MS);
+
/* Start the timeout */
- request->timeout = mftb() + msecs_to_tb(I2C_TIMEOUT_MS);
schedule_timer_at(&master->timeout, request->timeout);
return OPAL_SUCCESS;
@@ -854,8 +891,11 @@ static inline uint32_t p8_i2c_get_bit_rate_divisor(uint32_t lb_freq_mhz,
static inline uint64_t p8_i2c_get_poll_interval(uint32_t bus_speed)
{
+ uint64_t usec;
+
/* Polling Interval = 8 * (1/bus_speed) * (1/10) -> convert to uSec */
- return ((8 * USEC_PER_SEC) / (10 * bus_speed * 1000));
+ usec = ((8 * USEC_PER_SEC) / (10 * bus_speed * 1000));
+ return usecs_to_tb(usec);
}
static void p8_i2c_timeout(struct timer *t __unused, void *data)
@@ -900,16 +940,28 @@ static void p8_i2c_timeout(struct timer *t __unused, void *data)
unlock(&master->lock);
}
-static void p8_i2c_compare_poll_timer(struct p8_i2c_master *master)
+static void p8_i2c_poll(struct timer *t __unused, void *data)
{
+ struct p8_i2c_master *master = data;
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) {
- master->poll_timer = now + usecs_to_tb(master->poll_interval);
- p8_i2c_check_status(master);
- }
+ /*
+ * This is called when the interrupt isn't functional and
+ * will essentially just run the state machine from a timer
+ *
+ * There is some (mild) duplication with the OPAL poller but
+ * the latter is going to generally run very slowly so this
+ * isn't a problem. During boot they will both kick in but
+ * here too, this isn't a real problem.
+ */
+ lock(&master->lock);
+ master->poll_timer = now + master->poll_interval;
+ p8_i2c_check_status(master);
+ if (master->state != state_idle)
+ schedule_timer_at(&master->poller,
+ now + master->poll_interval);
+ p8_i2c_check_work(master);
+ unlock(&master->lock);
}
static void p8_i2c_poll_each_master(bool interrupt)
@@ -919,6 +971,8 @@ static void p8_i2c_poll_each_master(bool interrupt)
struct i2c_bus *bus;
list_for_each(&i2c_bus_list, bus, link) {
+ uint64_t now = mftb();
+
port = container_of(bus, struct p8_i2c_master_port, bus);
/* Each master serves 1 or more ports, check for the first
@@ -935,11 +989,17 @@ static void p8_i2c_poll_each_master(bool interrupt)
continue;
lock(&master->lock);
- if (!interrupt)
- p8_i2c_compare_poll_timer(master);
- else
+
+ /* Run the state machine */
+ if (interrupt || master->poll_timer == 0 ||
+ tb_compare(now, master->poll_timer) == TB_AAFTERB ||
+ tb_compare(now, master->poll_timer) == TB_AEQUALB) {
+ master->poll_timer = now + master->poll_interval;
p8_i2c_check_status(master);
+ }
+ /* Check for new work */
p8_i2c_check_work(master);
+
unlock(&master->lock);
}
}
@@ -1035,6 +1095,7 @@ void p8_i2c_init(void)
struct i2c_bus *bus, *next_bus;
struct p8_i2c_master *master;
uint64_t ex_stat;
+ static bool irq_printed;
int rc;
dt_for_each_compatible(dt_root, i2cm, "ibm,power8-i2cm") {
@@ -1067,6 +1128,15 @@ void p8_i2c_init(void)
master->poll_timer = 0;
master->bit_rate_div = p8_i2c_get_bit_rate_divisor(lb_freq,
bus_speed);
+ /* Check if interrupt is usable */
+ init_timer(&master->poller, p8_i2c_poll, master);
+ master->irq_ok = p8_i2c_has_irqs();
+ if (!irq_printed) {
+ irq_printed = true;
+ prlog(PR_INFO, "I2C: Interrupts %sfunctional\n",
+ master->irq_ok ? "" : "non-");
+ }
+
/* Allocate ports driven by this master */
count = 0;
dt_for_each_child(i2cm, i2cm_port)
@@ -1092,7 +1162,10 @@ void p8_i2c_init(void)
}
}
- /* Register the poller, one poller will cater all the masters */
+ /* Register the poller, one poller will cater all the masters,
+ * this is needed for when we operate without Linux running
+ * and thus no interrupts
+ */
opal_add_poller(p8_i2c_opal_poll, NULL);
/* Register the OPAL interface */