aboutsummaryrefslogtreecommitdiff
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
parenteec6e53ff88c0cb82f3624642390793cc0f8e3ce (diff)
downloadskiboot-0859f799583daa5fac57ab3f9a1175aae4de3252.zip
skiboot-0859f799583daa5fac57ab3f9a1175aae4de3252.tar.gz
skiboot-0859f799583daa5fac57ab3f9a1175aae4de3252.tar.bz2
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>
-rw-r--r--core/chip.c1
-rw-r--r--core/i2c.c2
-rw-r--r--hw/lpc.c8
-rw-r--r--hw/p8-i2c.c114
-rw-r--r--hw/psi.c12
-rw-r--r--include/chip.h5
-rw-r--r--include/i2c.h4
-rw-r--r--include/lpc.h2
8 files changed, 68 insertions, 80 deletions
diff --git a/core/chip.c b/core/chip.c
index e6eb81c..272f024 100644
--- a/core/chip.c
+++ b/core/chip.c
@@ -81,5 +81,6 @@ void init_chips(void)
0xffffffff);
chip->pcid = dt_prop_get_u32_def(xn, "ibm,proc-chip-id",
0xffffffff);
+ list_head_init(&chip->i2cms);
};
}
diff --git a/core/i2c.c b/core/i2c.c
index 2eb77db..e0af9b2 100644
--- a/core/i2c.c
+++ b/core/i2c.c
@@ -20,7 +20,7 @@
#include <device.h>
#include <opal-msg.h>
-LIST_HEAD(i2c_bus_list);
+static LIST_HEAD(i2c_bus_list);
/* Used to assign OPAL IDs */
static uint32_t i2c_next_bus;
diff --git a/hw/lpc.c b/hw/lpc.c
index 975edd6..96d3e39 100644
--- a/hw/lpc.c
+++ b/hw/lpc.c
@@ -20,7 +20,6 @@
#include <lock.h>
#include <chip.h>
#include <lpc.h>
-#include <i2c.h>
#include <timebase.h>
#include <fsp-elog.h>
@@ -455,12 +454,9 @@ bool lpc_present(void)
return lpc_default_chip_id >= 0;
}
-void lpc_interrupt(void)
+void lpc_interrupt(uint32_t chip_id __unused)
{
- /* i2c interrupts are routed through lpc */
- p8_i2c_interrupt();
-
- /* Handle the lpc interrupt source */
+ /* Handle the lpc interrupt source (errors etc...) TODO... */
}
void lpc_init(void)
diff --git a/hw/p8-i2c.c b/hw/p8-i2c.c
index e3f35b1..b1f7a28 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);
}
diff --git a/hw/psi.c b/hw/psi.c
index de5c58d..1dba69a 100644
--- a/hw/psi.c
+++ b/hw/psi.c
@@ -28,6 +28,7 @@
#include <xscom.h>
#include <chip.h>
#include <lpc.h>
+#include <i2c.h>
#include <timebase.h>
#include <platform.h>
@@ -280,8 +281,15 @@ static void handle_extra_interrupt(struct psi *psi)
occ_interrupt();
if (val & PSIHB_IRQ_STAT_FSI)
printf("PSI: FSI irq received\n");
- if (val & PSIHB_IRQ_STAT_LPC)
- lpc_interrupt();
+ if (val & PSIHB_IRQ_STAT_LPC) {
+ lpc_interrupt(psi->chip_id);
+
+ /*
+ * i2c interrupts are ORed with the LPC ones on
+ * Murano DD2.1 and Venice DD2.0
+ */
+ p8_i2c_interrupt(psi->chip_id);
+ }
if (val & PSIHB_IRQ_STAT_LOCAL_ERR)
printf("PSI: ATTN irq received\n");
if (val & PSIHB_IRQ_STAT_HOST_ERR) {
diff --git a/include/chip.h b/include/chip.h
index 10623e6..fb2771e 100644
--- a/include/chip.h
+++ b/include/chip.h
@@ -20,6 +20,8 @@
#include <stdint.h>
#include <lock.h>
+#include <ccan/list/list.h>
+
/*
* Note on chip IDs:
*
@@ -132,6 +134,9 @@ struct proc_chip {
/* Used by hw/centaur.c */
struct centaur_chip *centaurs;
+
+ /* Used by hw/p8-i2c.c */
+ struct list_head i2cms;
};
extern uint32_t pir_to_chip_id(uint32_t pir);
diff --git a/include/i2c.h b/include/i2c.h
index 2647bdc..1384697 100644
--- a/include/i2c.h
+++ b/include/i2c.h
@@ -47,8 +47,6 @@ struct i2c_request {
void *user_data; /* Client data */
};
-extern struct list_head i2c_bus_list;
-
/* Generic i2c */
extern void i2c_add_bus(struct i2c_bus *bus);
extern struct i2c_bus *i2c_find_bus_by_id(uint32_t opal_id);
@@ -70,6 +68,6 @@ static inline int i2c_queue_req(struct i2c_request *req)
/* P8 implementation details */
extern void p8_i2c_init(void);
-extern void p8_i2c_interrupt(void);
+extern void p8_i2c_interrupt(uint32_t chip_id);
#endif /* __I2C_H */
diff --git a/include/lpc.h b/include/lpc.h
index e4e24ea..632727f 100644
--- a/include/lpc.h
+++ b/include/lpc.h
@@ -28,7 +28,7 @@ extern void lpc_init(void);
extern bool lpc_present(void);
/* Handle the interrupt from LPC source */
-extern void lpc_interrupt(void);
+extern void lpc_interrupt(uint32_t chip_id);
/* Default bus accessors */
extern int64_t lpc_write(enum OpalLPCAddressType addr_type, uint32_t addr,