diff --git a/drivers/i2c/busses/i2c-k1.c b/drivers/i2c/busses/i2c-k1.c index 913aaea853e3..9152cf436bea 100644 --- a/drivers/i2c/busses/i2c-k1.c +++ b/drivers/i2c/busses/i2c-k1.c @@ -98,6 +98,10 @@ #define SPACEMIT_BUS_RESET_CLK_CNT_MAX 9 +#define SPACEMIT_WAIT_TIMEOUT 1000 /* ms */ +#define SPACEMIT_POLL_TIMEOUT 1000 /* us */ +#define SPACEMIT_POLL_INTERVAL 30 /* us */ + enum spacemit_i2c_state { SPACEMIT_STATE_IDLE, SPACEMIT_STATE_START, @@ -126,6 +130,7 @@ struct spacemit_i2c_dev { enum spacemit_i2c_state state; bool read; + bool use_pio; struct completion complete; u32 status; }; @@ -172,6 +177,14 @@ static int spacemit_i2c_handle_err(struct spacemit_i2c_dev *i2c) return i2c->status & SPACEMIT_SR_ACKNAK ? -ENXIO : -EIO; } +static inline void spacemit_i2c_delay(struct spacemit_i2c_dev *i2c, unsigned int us) +{ + if (i2c->use_pio) + udelay(us); + else + fsleep(us); +} + static void spacemit_i2c_conditionally_reset_bus(struct spacemit_i2c_dev *i2c) { u32 status; @@ -183,7 +196,8 @@ static void spacemit_i2c_conditionally_reset_bus(struct spacemit_i2c_dev *i2c) return; spacemit_i2c_reset(i2c); - usleep_range(10, 20); + + spacemit_i2c_delay(i2c, 10); for (clk_cnt = 0; clk_cnt < SPACEMIT_BUS_RESET_CLK_CNT_MAX; clk_cnt++) { status = readl(i2c->base + SPACEMIT_IBMR); @@ -212,9 +226,15 @@ static int spacemit_i2c_wait_bus_idle(struct spacemit_i2c_dev *i2c) if (!(val & (SPACEMIT_SR_UB | SPACEMIT_SR_IBB))) return 0; - ret = readl_poll_timeout(i2c->base + SPACEMIT_ISR, - val, !(val & (SPACEMIT_SR_UB | SPACEMIT_SR_IBB)), - 1500, SPACEMIT_I2C_BUS_BUSY_TIMEOUT); + if (i2c->use_pio) + ret = readl_poll_timeout_atomic(i2c->base + SPACEMIT_ISR, + val, !(val & (SPACEMIT_SR_UB | SPACEMIT_SR_IBB)), + 1500, SPACEMIT_I2C_BUS_BUSY_TIMEOUT); + else + ret = readl_poll_timeout(i2c->base + SPACEMIT_ISR, + val, !(val & (SPACEMIT_SR_UB | SPACEMIT_SR_IBB)), + 1500, SPACEMIT_I2C_BUS_BUSY_TIMEOUT); + if (ret) spacemit_i2c_reset(i2c); @@ -226,7 +246,7 @@ static void spacemit_i2c_check_bus_release(struct spacemit_i2c_dev *i2c) /* in case bus is not released after transfer completes */ if (readl(i2c->base + SPACEMIT_ISR) & SPACEMIT_SR_EBB) { spacemit_i2c_conditionally_reset_bus(i2c); - usleep_range(90, 150); + spacemit_i2c_delay(i2c, 90); } } @@ -238,25 +258,33 @@ spacemit_i2c_clear_int_status(struct spacemit_i2c_dev *i2c, u32 mask) static void spacemit_i2c_init(struct spacemit_i2c_dev *i2c) { - u32 val; + u32 val = 0; - /* - * Unmask interrupt bits for all xfer mode: - * bus error, arbitration loss detected. - * For transaction complete signal, we use master stop - * interrupt, so we don't need to unmask SPACEMIT_CR_TXDONEIE. - */ - val = SPACEMIT_CR_BEIE | SPACEMIT_CR_ALDIE; + if (!i2c->use_pio) { + /* + * Enable interrupt bits for all xfer mode: + * bus error, arbitration loss detected. + */ + val |= SPACEMIT_CR_BEIE | SPACEMIT_CR_ALDIE; - /* - * Unmask interrupt bits for interrupt xfer mode: - * When IDBR receives a byte, an interrupt is triggered. - * - * For the tx empty interrupt, it will be enabled in the - * i2c_start function. - * Otherwise, it will cause an erroneous empty interrupt before i2c_start. - */ - val |= SPACEMIT_CR_DRFIE; + /* + * Unmask interrupt bits for interrupt xfer mode: + * When IDBR receives a byte, an interrupt is triggered. + * + * For the tx empty interrupt, it will be enabled in the + * i2c_start(). + * We don't want a TX empty interrupt until we start + * a transfer in i2c_start(). + */ + val |= SPACEMIT_CR_DRFIE; + + /* + * Enable master stop interrupt bit. + * For transaction complete signal, we use master stop + * interrupt, so we don't need to unmask SPACEMIT_CR_TXDONEIE. + */ + val |= SPACEMIT_CR_MSDIE; + } if (i2c->clock_freq == SPACEMIT_I2C_MAX_FAST_MODE_FREQ) val |= SPACEMIT_CR_MODE_FAST; @@ -268,7 +296,7 @@ static void spacemit_i2c_init(struct spacemit_i2c_dev *i2c) val |= SPACEMIT_CR_SCLE; /* enable master stop detected */ - val |= SPACEMIT_CR_MSDE | SPACEMIT_CR_MSDIE; + val |= SPACEMIT_CR_MSDE; writel(val, i2c->base + SPACEMIT_ICR); @@ -301,7 +329,12 @@ static void spacemit_i2c_start(struct spacemit_i2c_dev *i2c) /* send start pulse */ val = readl(i2c->base + SPACEMIT_ICR); val &= ~SPACEMIT_CR_STOP; - val |= SPACEMIT_CR_START | SPACEMIT_CR_TB | SPACEMIT_CR_DTEIE; + val |= SPACEMIT_CR_START | SPACEMIT_CR_TB; + + /* Enable the TX empty interrupt */ + if (!i2c->use_pio) + val |= SPACEMIT_CR_DTEIE; + writel(val, i2c->base + SPACEMIT_ICR); } @@ -316,8 +349,23 @@ static bool spacemit_i2c_is_last_msg(struct spacemit_i2c_dev *i2c) return !i2c->unprocessed; } +static inline void spacemit_i2c_complete(struct spacemit_i2c_dev *i2c) +{ + /* SPACEMIT_STATE_IDLE avoids triggering the next byte */ + i2c->state = SPACEMIT_STATE_IDLE; + + if (i2c->use_pio) + return; + + complete(&i2c->complete); +} + static void spacemit_i2c_handle_write(struct spacemit_i2c_dev *i2c) { + /* If there's no space in the IDBR, we're done */ + if (!(i2c->status & SPACEMIT_SR_ITE)) + return; + /* if transfer completes, SPACEMIT_ISR will handle it */ if (i2c->status & SPACEMIT_SR_MSD) return; @@ -328,16 +376,19 @@ static void spacemit_i2c_handle_write(struct spacemit_i2c_dev *i2c) return; } - /* SPACEMIT_STATE_IDLE avoids trigger next byte */ - i2c->state = SPACEMIT_STATE_IDLE; - complete(&i2c->complete); + spacemit_i2c_complete(i2c); } static void spacemit_i2c_handle_read(struct spacemit_i2c_dev *i2c) { + /* If there's nothing in the IDBR, we're done */ + if (!(i2c->status & SPACEMIT_SR_IRF)) + return; + if (i2c->unprocessed) { *i2c->msg_buf++ = readl(i2c->base + SPACEMIT_IDBR); i2c->unprocessed--; + return; } /* if transfer completes, SPACEMIT_ISR will handle it */ @@ -348,9 +399,7 @@ static void spacemit_i2c_handle_read(struct spacemit_i2c_dev *i2c) if (i2c->unprocessed) return; - /* SPACEMIT_STATE_IDLE avoids trigger next byte */ - i2c->state = SPACEMIT_STATE_IDLE; - complete(&i2c->complete); + spacemit_i2c_complete(i2c); } static void spacemit_i2c_handle_start(struct spacemit_i2c_dev *i2c) @@ -384,8 +433,129 @@ static void spacemit_i2c_err_check(struct spacemit_i2c_dev *i2c) spacemit_i2c_clear_int_status(i2c, SPACEMIT_I2C_INT_STATUS_MASK); - i2c->state = SPACEMIT_STATE_IDLE; - complete(&i2c->complete); + spacemit_i2c_complete(i2c); +} + +static void spacemit_i2c_handle_state(struct spacemit_i2c_dev *i2c) +{ + u32 val; + + if (i2c->status & SPACEMIT_SR_ERR) + goto err_out; + + switch (i2c->state) { + case SPACEMIT_STATE_START: + spacemit_i2c_handle_start(i2c); + break; + case SPACEMIT_STATE_READ: + spacemit_i2c_handle_read(i2c); + break; + case SPACEMIT_STATE_WRITE: + spacemit_i2c_handle_write(i2c); + break; + default: + break; + } + + if (i2c->state != SPACEMIT_STATE_IDLE) { + val = readl(i2c->base + SPACEMIT_ICR); + val &= ~(SPACEMIT_CR_TB | SPACEMIT_CR_ACKNAK | + SPACEMIT_CR_STOP | SPACEMIT_CR_START); + val |= SPACEMIT_CR_TB; + if (!i2c->use_pio) + val |= SPACEMIT_CR_ALDIE; + + if (spacemit_i2c_is_last_msg(i2c)) { + /* trigger next byte with stop */ + val |= SPACEMIT_CR_STOP; + + if (i2c->read) + val |= SPACEMIT_CR_ACKNAK; + } + writel(val, i2c->base + SPACEMIT_ICR); + } + +err_out: + spacemit_i2c_err_check(i2c); +} + +/* + * In PIO mode, this function is used as a replacement for + * wait_for_completion_timeout(), whose return value indicates + * the remaining time. + * + * We do not have a meaningful remaining-time value here, so + * return a non-zero value on success to indicate "not timed out". + * Returning 1 ensures callers treating the return value as + * time_left will not incorrectly report a timeout. + */ +static int spacemit_i2c_wait_pio_xfer(struct spacemit_i2c_dev *i2c) +{ + u32 mask, msec = jiffies_to_msecs(i2c->adapt.timeout); + ktime_t timeout = ktime_add_ms(ktime_get(), msec); + int ret; + + mask = SPACEMIT_SR_IRF | SPACEMIT_SR_ITE; + + do { + i2c->status = readl(i2c->base + SPACEMIT_ISR); + + spacemit_i2c_clear_int_status(i2c, i2c->status); + + if (i2c->status & mask) + spacemit_i2c_handle_state(i2c); + else + udelay(SPACEMIT_POLL_INTERVAL); + } while (i2c->unprocessed && ktime_compare(ktime_get(), timeout) < 0); + + if (i2c->unprocessed) + return 0; + + if (i2c->read) + return 1; + + /* + * If this is the last byte to write of the current message, + * we have to wait here. Otherwise, control will proceed directly + * to start(), which would overwrite the current data. + */ + ret = readl_poll_timeout_atomic(i2c->base + SPACEMIT_ISR, + i2c->status, i2c->status & SPACEMIT_SR_ITE, + SPACEMIT_POLL_INTERVAL, SPACEMIT_POLL_TIMEOUT); + if (ret) + return 0; + + /* + * For writes: in interrupt mode, an ITE (write-empty) interrupt is triggered + * after the last byte, and the MSD-related handling takes place there. + * In PIO mode, however, we need to explicitly call err_check() to emulate this + * step, otherwise the next transfer will fail. + */ + if (i2c->msg_idx == i2c->msg_num - 1) { + mask = SPACEMIT_SR_MSD | SPACEMIT_SR_ERR; + /* + * In some cases, MSD may not arrive immediately; + * wait here to handle that. + */ + ret = readl_poll_timeout_atomic(i2c->base + SPACEMIT_ISR, + i2c->status, i2c->status & mask, + SPACEMIT_POLL_INTERVAL, SPACEMIT_POLL_TIMEOUT); + if (ret) + return 0; + + spacemit_i2c_err_check(i2c); + } + + return 1; +} + +static int spacemit_i2c_wait_xfer_complete(struct spacemit_i2c_dev *i2c) +{ + if (i2c->use_pio) + return spacemit_i2c_wait_pio_xfer(i2c); + + return wait_for_completion_timeout(&i2c->complete, + i2c->adapt.timeout); } static int spacemit_i2c_xfer_msg(struct spacemit_i2c_dev *i2c) @@ -403,8 +573,8 @@ static int spacemit_i2c_xfer_msg(struct spacemit_i2c_dev *i2c) spacemit_i2c_start(i2c); - time_left = wait_for_completion_timeout(&i2c->complete, - i2c->adapt.timeout); + time_left = spacemit_i2c_wait_xfer_complete(i2c); + if (!time_left) { dev_err(i2c->dev, "msg completion timeout\n"); spacemit_i2c_conditionally_reset_bus(i2c); @@ -422,7 +592,7 @@ static int spacemit_i2c_xfer_msg(struct spacemit_i2c_dev *i2c) static irqreturn_t spacemit_i2c_irq_handler(int irq, void *devid) { struct spacemit_i2c_dev *i2c = devid; - u32 status, val; + u32 status; status = readl(i2c->base + SPACEMIT_ISR); if (!status) @@ -432,41 +602,8 @@ static irqreturn_t spacemit_i2c_irq_handler(int irq, void *devid) spacemit_i2c_clear_int_status(i2c, status); - if (i2c->status & SPACEMIT_SR_ERR) - goto err_out; + spacemit_i2c_handle_state(i2c); - val = readl(i2c->base + SPACEMIT_ICR); - val &= ~(SPACEMIT_CR_TB | SPACEMIT_CR_ACKNAK | SPACEMIT_CR_STOP | SPACEMIT_CR_START); - - switch (i2c->state) { - case SPACEMIT_STATE_START: - spacemit_i2c_handle_start(i2c); - break; - case SPACEMIT_STATE_READ: - spacemit_i2c_handle_read(i2c); - break; - case SPACEMIT_STATE_WRITE: - spacemit_i2c_handle_write(i2c); - break; - default: - break; - } - - if (i2c->state != SPACEMIT_STATE_IDLE) { - val |= SPACEMIT_CR_TB | SPACEMIT_CR_ALDIE; - - if (spacemit_i2c_is_last_msg(i2c)) { - /* trigger next byte with stop */ - val |= SPACEMIT_CR_STOP; - - if (i2c->read) - val |= SPACEMIT_CR_ACKNAK; - } - writel(val, i2c->base + SPACEMIT_ICR); - } - -err_out: - spacemit_i2c_err_check(i2c); return IRQ_HANDLED; } @@ -475,6 +612,11 @@ static void spacemit_i2c_calc_timeout(struct spacemit_i2c_dev *i2c) unsigned long timeout; int idx = 0, cnt = 0; + if (i2c->use_pio) { + i2c->adapt.timeout = msecs_to_jiffies(SPACEMIT_WAIT_TIMEOUT); + return; + } + for (; idx < i2c->msg_num; idx++) cnt += (i2c->msgs + idx)->len + 1; @@ -487,11 +629,14 @@ static void spacemit_i2c_calc_timeout(struct spacemit_i2c_dev *i2c) i2c->adapt.timeout = usecs_to_jiffies(timeout + USEC_PER_SEC / 10) / i2c->msg_num; } -static int spacemit_i2c_xfer(struct i2c_adapter *adapt, struct i2c_msg *msgs, int num) +static inline int +spacemit_i2c_xfer_common(struct i2c_adapter *adapt, struct i2c_msg *msgs, int num, bool use_pio) { struct spacemit_i2c_dev *i2c = i2c_get_adapdata(adapt); int ret; + i2c->use_pio = use_pio; + i2c->msgs = msgs; i2c->msg_num = num; @@ -519,6 +664,16 @@ static int spacemit_i2c_xfer(struct i2c_adapter *adapt, struct i2c_msg *msgs, in return ret < 0 ? ret : num; } +static int spacemit_i2c_xfer(struct i2c_adapter *adapt, struct i2c_msg *msgs, int num) +{ + return spacemit_i2c_xfer_common(adapt, msgs, num, false); +} + +static int spacemit_i2c_pio_xfer_atomic(struct i2c_adapter *adapt, struct i2c_msg *msgs, int num) +{ + return spacemit_i2c_xfer_common(adapt, msgs, num, true); +} + static u32 spacemit_i2c_func(struct i2c_adapter *adap) { return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK); @@ -526,6 +681,7 @@ static u32 spacemit_i2c_func(struct i2c_adapter *adap) static const struct i2c_algorithm spacemit_i2c_algo = { .xfer = spacemit_i2c_xfer, + .xfer_atomic = spacemit_i2c_pio_xfer_atomic, .functionality = spacemit_i2c_func, };