summaryrefslogtreecommitdiffstats
path: root/hw/p8-i2c.c
diff options
context:
space:
mode:
authorBenjamin Herrenschmidt <benh@kernel.crashing.org>2014-11-12 15:44:33 +1100
committerBenjamin Herrenschmidt <benh@kernel.crashing.org>2014-11-12 16:41:25 +1100
commit0859f799583daa5fac57ab3f9a1175aae4de3252 (patch)
treeae5f70cd26f3ecd13e9b44003be1ae2375797260 /hw/p8-i2c.c
parenteec6e53ff88c0cb82f3624642390793cc0f8e3ce (diff)
downloadtalos-skiboot-0859f799583daa5fac57ab3f9a1175aae4de3252.tar.gz
talos-skiboot-0859f799583daa5fac57ab3f9a1175aae4de3252.zip
i2c: Use new timer facility and improve interrupts handling
We only poll the masters for the chip that got the interrupt and we improve the running of the timers as well. We user the new TIMER_POLL facility to replace the use of the OPAL poller, which simplifies the code further. Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Diffstat (limited to 'hw/p8-i2c.c')
-rw-r--r--hw/p8-i2c.c114
1 files changed, 47 insertions, 67 deletions
diff --git a/hw/p8-i2c.c b/hw/p8-i2c.c
index e3f35b14..b1f7a284 100644
--- a/hw/p8-i2c.c
+++ b/hw/p8-i2c.c
@@ -166,8 +166,8 @@
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_interval; /* Polling interval */
+ uint64_t byte_timeout; /* Timeout per byte */
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 */
@@ -185,6 +185,7 @@ struct p8_i2c_master {
struct list_head req_list; /* Request queue head */
struct timer poller;
struct timer timeout;
+ struct list_node link;
};
struct p8_i2c_master_port {
@@ -792,19 +793,14 @@ 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
+ /* Run a poll timer for boot cases or non-working interrupts
+ * cases
*/
- now = mftb();
+ now = schedule_timer(&master->poller, master->poll_interval);
+
+ /* Calculate and start timeout */
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);
+ request->timeout = now + tbytes * master->byte_timeout;
/* Start the timeout */
schedule_timer_at(&master->timeout, request->timeout);
@@ -939,60 +935,42 @@ static void p8_i2c_timeout(struct timer *t __unused, void *data)
static void p8_i2c_poll(struct timer *t __unused, void *data)
{
struct p8_i2c_master *master = data;
- uint64_t now = mftb();
/*
- * 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.
+ * This is called when the interrupt isn't functional or
+ * generally from the opal pollers, so fast while booting
+ * and slowly when Linux is up.
*/
+
+ /* Lockless fast bailout */
+ if (master->state == state_idle)
+ return;
+
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);
+ schedule_timer(&master->poller, master->poll_interval);
p8_i2c_check_work(master);
unlock(&master->lock);
}
-static void p8_i2c_poll_each_master(bool interrupt)
+void p8_i2c_interrupt(uint32_t chip_id)
{
+ struct proc_chip *chip = get_chip(chip_id);
struct p8_i2c_master *master = NULL;
- struct p8_i2c_master_port *port;
- struct i2c_bus *bus;
- list_for_each(&i2c_bus_list, bus, link) {
- uint64_t now = mftb();
+ assert(chip);
+ list_for_each(&chip->i2cms, master, link) {
- port = container_of(bus, struct p8_i2c_master_port, bus);
-
- /* Each master serves 1 or more ports, check for the first
- * one found..
- */
- if (!master || master != port->common)
- master = port->common;
- else
- continue;
-
- /* Lockless fast bailout for polling mode */
- if (!interrupt && master->state == state_idle &&
- list_empty(&master->req_list))
+ /* Lockless fast bailout (shared interrupt) */
+ if (master->state == state_idle)
continue;
lock(&master->lock);
/* 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);
- }
+ p8_i2c_check_status(master);
+
/* Check for new work */
p8_i2c_check_work(master);
@@ -1000,22 +978,13 @@ static void p8_i2c_poll_each_master(bool interrupt)
}
}
-static void p8_i2c_opal_poll(void *data __unused)
-{
- p8_i2c_poll_each_master(false);
-}
-
-void p8_i2c_interrupt(void)
-{
- p8_i2c_poll_each_master(true);
-}
-
void p8_i2c_init(void)
{
struct p8_i2c_master_port *port;
uint32_t bus_speed, lb_freq, count;
struct dt_node *i2cm, *i2cm_port;
struct p8_i2c_master *master;
+ struct proc_chip *chip;
uint64_t ex_stat;
static bool irq_printed;
int rc;
@@ -1035,7 +1004,10 @@ void p8_i2c_init(void)
master->state = state_idle;
master->chip_id = dt_get_chip_id(i2cm);
master->xscom_base = dt_get_address(i2cm, 0, NULL);
+ chip = get_chip(master->chip_id);
+ assert(chip);
init_timer(&master->timeout, p8_i2c_timeout, master);
+ init_timer(&master->poller, p8_i2c_poll, master);
rc = xscom_read(master->chip_id, master->xscom_base +
I2C_EXTD_STAT_REG, &ex_stat);
@@ -1047,12 +1019,9 @@ void p8_i2c_init(void)
master->fifo_size = GETFIELD(I2C_EXTD_STAT_FIFO_SIZE, ex_stat);
list_head_init(&master->req_list);
- master->poll_interval = p8_i2c_get_poll_interval(bus_speed);
- 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;
@@ -1060,6 +1029,20 @@ void p8_i2c_init(void)
master->irq_ok ? "" : "non-");
}
+ /* If we have no interrupt, calculate a poll interval, otherwise
+ * just use a TIMER_POLL timer which will tick on OPAL pollers
+ * only (which allows us to operate during boot before
+ * interrupts are functional etc...
+ */
+ if (master->irq_ok)
+ master->poll_interval = TIMER_POLL;
+ else
+ master->poll_interval =
+ p8_i2c_get_poll_interval(bus_speed);
+ master->byte_timeout = master->irq_ok ?
+ msecs_to_tb(I2C_TIMEOUT_IRQ_MS) :
+ msecs_to_tb(I2C_TIMEOUT_POLL_MS);
+
/* Allocate ports driven by this master */
count = 0;
dt_for_each_child(i2cm, i2cm_port)
@@ -1072,6 +1055,9 @@ void p8_i2c_init(void)
break;
}
+ /* Add master to chip's list */
+ list_add_tail(&chip->i2cms, &master->link);
+
dt_for_each_child(i2cm, i2cm_port) {
port->port_num = dt_prop_get_u32(i2cm_port, "reg");
port->common = master;
@@ -1083,10 +1069,4 @@ void p8_i2c_init(void)
port++;
}
}
-
- /* 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);
}
OpenPOWER on IntegriCloud