diff options
Diffstat (limited to 'hw/p8-i2c.c')
-rw-r--r-- | hw/p8-i2c.c | 114 |
1 files changed, 47 insertions, 67 deletions
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); } |