Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1755985AbcCaK2u (ORCPT ); Thu, 31 Mar 2016 06:28:50 -0400 Received: from mail-wm0-f66.google.com ([74.125.82.66]:33328 "EHLO mail-wm0-f66.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1755560AbcCaK2q (ORCPT ); Thu, 31 Mar 2016 06:28:46 -0400 From: Jan Glauber To: Wolfram Sang Cc: linux-kernel@vger.kernel.org, linux-i2c@vger.kernel.org, David Daney , David Daney , Jan Glauber Subject: [PATCH v5 04/14] i2c: octeon: Enable High-Level Controller Date: Thu, 31 Mar 2016 12:28:17 +0200 Message-Id: <71c4dc9b38bcc661fd5fb49af9ecc1026ecf9668.1459352583.git.jglauber@cavium.com> X-Mailer: git-send-email 1.9.1 In-Reply-To: References: In-Reply-To: References: Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 15354 Lines: 564 From: David Daney Use High-Level Controller (HLC) when possible. The HLC can read/write up to 8 bytes and is completely optional. The most important difference of the HLC is that it only requires one interrupt for a transfer (up to 8 bytes) where the low-level read/write requires 2 interrupts plus one interrupt per transferred byte. Since the interrupts are costly using the HLC improves the performance. Also, the HLC provides improved error handling. Signed-off-by: David Daney Signed-off-by: Jan Glauber --- drivers/i2c/busses/i2c-octeon.c | 397 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 380 insertions(+), 17 deletions(-) diff --git a/drivers/i2c/busses/i2c-octeon.c b/drivers/i2c/busses/i2c-octeon.c index a037245..bc0ed20 100644 --- a/drivers/i2c/busses/i2c-octeon.c +++ b/drivers/i2c/busses/i2c-octeon.c @@ -29,13 +29,23 @@ /* Register offsets */ #define SW_TWSI 0x00 #define TWSI_INT 0x10 +#define SW_TWSI_EXT 0x18 /* Controller command patterns */ #define SW_TWSI_V BIT_ULL(63) /* Valid bit */ +#define SW_TWSI_EIA BIT_ULL(61) /* Extended internal address */ #define SW_TWSI_R BIT_ULL(56) /* Result or read bit */ +#define SW_TWSI_SOVR BIT_ULL(55) /* Size override */ +#define SW_TWSI_SIZE_SHIFT 52 +#define SW_TWSI_ADDR_SHIFT 40 +#define SW_TWSI_IA_SHIFT 32 /* Internal address */ /* Controller opcode word (bits 60:57) */ #define SW_TWSI_OP_SHIFT 57 +#define SW_TWSI_OP_7 (0ULL << SW_TWSI_OP_SHIFT) +#define SW_TWSI_OP_7_IA (1ULL << SW_TWSI_OP_SHIFT) +#define SW_TWSI_OP_10 (2ULL << SW_TWSI_OP_SHIFT) +#define SW_TWSI_OP_10_IA (3ULL << SW_TWSI_OP_SHIFT) #define SW_TWSI_OP_TWSI_CLK (4ULL << SW_TWSI_OP_SHIFT) #define SW_TWSI_OP_EOP (6ULL << SW_TWSI_OP_SHIFT) /* Extended opcode */ @@ -48,7 +58,7 @@ #define SW_TWSI_EOP_TWSI_RST (SW_TWSI_OP_EOP | 7ULL << SW_TWSI_EOP_SHIFT) /* Controller command and status bits */ -#define TWSI_CTL_CE 0x80 +#define TWSI_CTL_CE 0x80 /* High level controller enable */ #define TWSI_CTL_ENAB 0x40 /* Bus enable */ #define TWSI_CTL_STA 0x20 /* Master-mode start, HW clears when done */ #define TWSI_CTL_STP 0x10 /* Master-mode stop, HW clears when done */ @@ -87,9 +97,16 @@ #define STAT_IDLE 0xF8 /* TWSI_INT values */ +#define TWSI_INT_ST_INT BIT_ULL(0) +#define TWSI_INT_TS_INT BIT_ULL(1) +#define TWSI_INT_CORE_INT BIT_ULL(2) +#define TWSI_INT_ST_EN BIT_ULL(4) +#define TWSI_INT_TS_EN BIT_ULL(5) #define TWSI_INT_CORE_EN BIT_ULL(6) #define TWSI_INT_SDA_OVR BIT_ULL(8) #define TWSI_INT_SCL_OVR BIT_ULL(9) +#define TWSI_INT_SDA BIT_ULL(10) +#define TWSI_INT_SCL BIT_ULL(11) struct octeon_i2c { wait_queue_head_t queue; @@ -99,6 +116,7 @@ struct octeon_i2c { int sys_freq; void __iomem *twsi_base; struct device *dev; + bool hlc_enabled; }; static int reset_how; @@ -111,7 +129,7 @@ static int reset_how; * * The I2C core registers are accessed indirectly via the SW_TWSI CSR. */ -static void octeon_i2c_write_sw(struct octeon_i2c *i2c, u64 eop_reg, u8 data) +static void octeon_i2c_write_sw(struct octeon_i2c *i2c, u64 eop_reg, u32 data) { u64 tmp; @@ -122,7 +140,7 @@ static void octeon_i2c_write_sw(struct octeon_i2c *i2c, u64 eop_reg, u8 data) } /** - * octeon_i2c_read_sw - read lower bits of an I2C core register + * octeon_i2c_read_sw64 - read an I2C core register * @i2c: The struct octeon_i2c * @eop_reg: Register selector * @@ -130,7 +148,7 @@ static void octeon_i2c_write_sw(struct octeon_i2c *i2c, u64 eop_reg, u8 data) * * The I2C core registers are accessed indirectly via the SW_TWSI CSR. */ -static u8 octeon_i2c_read_sw(struct octeon_i2c *i2c, u64 eop_reg) +static u64 octeon_i2c_read_sw64(struct octeon_i2c *i2c, u64 eop_reg) { u64 tmp; @@ -139,7 +157,21 @@ static u8 octeon_i2c_read_sw(struct octeon_i2c *i2c, u64 eop_reg) tmp = __raw_readq(i2c->twsi_base + SW_TWSI); } while ((tmp & SW_TWSI_V) != 0); - return tmp & 0xFF; + return tmp; +} + +/** + * octeon_i2c_read_sw - read lower bits of an I2C core register + * @i2c: The struct octeon_i2c + * @eop_reg: Register selector + * + * Returns the data. + * + * The I2C core registers are accessed indirectly via the SW_TWSI CSR. + */ +static u8 octeon_i2c_read_sw(struct octeon_i2c *i2c, u64 eop_reg) +{ + return octeon_i2c_read_sw64(i2c, eop_reg) & 0xFF; } /** @@ -172,6 +204,15 @@ static void octeon_i2c_int_disable(struct octeon_i2c *i2c) octeon_i2c_write_int(i2c, 0); } +static void octeon_i2c_disable_hlc(struct octeon_i2c *i2c) +{ + if (!i2c->hlc_enabled) + return; + + i2c->hlc_enabled = false; + octeon_i2c_write_sw(i2c, SW_TWSI_EOP_TWSI_CTL, TWSI_CTL_ENAB); +} + /** * octeon_i2c_unblock - unblock the bus * @i2c: The struct octeon_i2c @@ -181,13 +222,18 @@ static void octeon_i2c_int_disable(struct octeon_i2c *i2c) */ static void octeon_i2c_unblock(struct octeon_i2c *i2c) { - int i; + int state, i; + octeon_i2c_disable_hlc(i2c); dev_dbg(i2c->dev, "%s\n", __func__); + /* cycle 8+1 clocks with SDA high */ for (i = 0; i < 9; i++) { octeon_i2c_write_int(i2c, 0); udelay(5); + state = __raw_readq(i2c->twsi_base + TWSI_INT); + if (state & (TWSI_INT_SDA | TWSI_INT_SCL)) + break; octeon_i2c_write_int(i2c, TWSI_INT_SCL_OVR); udelay(5); } @@ -210,9 +256,14 @@ static irqreturn_t octeon_i2c_isr(int irq, void *dev_id) return IRQ_HANDLED; } +static u64 octeon_i2c_read_ctl(struct octeon_i2c *i2c) +{ + return octeon_i2c_read_sw64(i2c, SW_TWSI_EOP_TWSI_CTL); +} + static int octeon_i2c_test_iflg(struct octeon_i2c *i2c) { - return (octeon_i2c_read_sw(i2c, SW_TWSI_EOP_TWSI_CTL) & TWSI_CTL_IFLG) != 0; + return (octeon_i2c_read_ctl(i2c) & TWSI_CTL_IFLG) != 0; } /** @@ -237,6 +288,43 @@ static int octeon_i2c_wait(struct octeon_i2c *i2c) return 0; } +/* + * Cleanup low-level state & enable high-level. + * Returns -EAGAIN if low-level state could not be cleaned. + */ +static int octeon_i2c_enable_hlc(struct octeon_i2c *i2c) +{ + int try = 0, ret = 0; + u64 val; + + if (i2c->hlc_enabled) + return 0; + i2c->hlc_enabled = true; + + while (1) { + val = octeon_i2c_read_ctl(i2c) & (TWSI_CTL_STA | TWSI_CTL_STP); + if (!val) + break; + + /* clear IFLG event */ + if (val & TWSI_CTL_IFLG) + octeon_i2c_write_sw(i2c, SW_TWSI_EOP_TWSI_CTL, TWSI_CTL_ENAB); + + if (try++ > 100) { + pr_err("%s: giving up\n", __func__); + ret = -EAGAIN; + break; + } + + /* spin until any start/stop has finished */ + udelay(10); + } + + octeon_i2c_write_sw(i2c, SW_TWSI_EOP_TWSI_CTL, + TWSI_CTL_CE | TWSI_CTL_AAK | TWSI_CTL_ENAB); + return ret; +} + static int octeon_i2c_lost_arb(u8 code, int final_read) { switch (code) { @@ -297,6 +385,253 @@ static void octeon_i2c_stop(struct octeon_i2c *i2c) TWSI_CTL_ENAB | TWSI_CTL_STP); } +static bool octeon_i2c_hlc_test_ready(struct octeon_i2c *i2c) +{ + u64 val = __raw_readq(i2c->twsi_base + SW_TWSI); + + return (val & SW_TWSI_V) == 0; +} + +static void octeon_i2c_hlc_int_enable(struct octeon_i2c *i2c) +{ + octeon_i2c_write_int(i2c, TWSI_INT_ST_EN); +} + +static void octeon_i2c_hlc_int_clear(struct octeon_i2c *i2c) +{ + /* clear ST/TS events, listen for neither */ + octeon_i2c_write_int(i2c, TWSI_INT_ST_INT | TWSI_INT_TS_INT); +} + +/** + * octeon_i2c_hlc_wait - wait for an HLC operation to complete + * @i2c: The struct octeon_i2c + * + * Returns 0 on success, otherwise a negative errno. + */ +static int octeon_i2c_hlc_wait(struct octeon_i2c *i2c) +{ + int time_left; + + octeon_i2c_hlc_int_enable(i2c); + time_left = wait_event_interruptible_timeout(i2c->queue, + octeon_i2c_hlc_test_ready(i2c), + i2c->adap.timeout); + octeon_i2c_int_disable(i2c); + if (!time_left) { + octeon_i2c_hlc_int_clear(i2c); + dev_dbg(i2c->dev, "%s: timeout\n", __func__); + return -ETIMEDOUT; + } + + if (time_left < 0) { + dev_dbg(i2c->dev, "%s: wait interrupted\n", __func__); + return time_left; + } + return 0; +} + +/* high-level-controller pure read of up to 8 bytes */ +static int octeon_i2c_simple_read(struct octeon_i2c *i2c, struct i2c_msg *msgs) +{ + int i, j, ret = 0; + u64 cmd; + + octeon_i2c_enable_hlc(i2c); + cmd = SW_TWSI_V | SW_TWSI_R | SW_TWSI_SOVR; + /* SIZE */ + cmd |= (u64) (msgs[0].len - 1) << SW_TWSI_SIZE_SHIFT; + /* A */ + cmd |= (u64) (msgs[0].addr & 0x7full) << SW_TWSI_ADDR_SHIFT; + + if (msgs[0].flags & I2C_M_TEN) + cmd |= SW_TWSI_OP_10; + else + cmd |= SW_TWSI_OP_7; + + octeon_i2c_hlc_int_clear(i2c); + __raw_writeq(cmd, i2c->twsi_base + SW_TWSI); + + ret = octeon_i2c_hlc_wait(i2c); + if (ret) + goto err; + + cmd = __raw_readq(i2c->twsi_base + SW_TWSI); + if ((cmd & SW_TWSI_R) == 0) + return -EAGAIN; + + for (i = 0, j = msgs[0].len - 1; i < msgs[0].len && i < 4; i++, j--) + msgs[0].buf[j] = (cmd >> (8 * i)) & 0xff; + + if (msgs[0].len > 4) { + cmd = __raw_readq(i2c->twsi_base + SW_TWSI_EXT); + for (i = 0; i < msgs[0].len - 4 && i < 4; i++, j--) + msgs[0].buf[j] = (cmd >> (8 * i)) & 0xff; + } + +err: + return ret; +} + +/* high-level-controller pure write of up to 8 bytes */ +static int octeon_i2c_simple_write(struct octeon_i2c *i2c, struct i2c_msg *msgs) +{ + int i, j, ret = 0; + u64 cmd; + + octeon_i2c_enable_hlc(i2c); + octeon_i2c_hlc_int_clear(i2c); + + ret = check_arb(i2c, false); + if (ret) + goto err; + + cmd = SW_TWSI_V | SW_TWSI_SOVR; + /* SIZE */ + cmd |= (u64) (msgs[0].len - 1) << SW_TWSI_SIZE_SHIFT; + /* A */ + cmd |= (u64) (msgs[0].addr & 0x7full) << SW_TWSI_ADDR_SHIFT; + + if (msgs[0].flags & I2C_M_TEN) + cmd |= SW_TWSI_OP_10; + else + cmd |= SW_TWSI_OP_7; + + for (i = 0, j = msgs[0].len - 1; i < msgs[0].len && i < 4; i++, j--) + cmd |= (u64)msgs[0].buf[j] << (8 * i); + + if (msgs[0].len > 4) { + u64 ext = 0; + + for (i = 0; i < msgs[0].len - 4 && i < 4; i++, j--) + ext |= (u64) msgs[0].buf[j] << (8 * i); + __raw_writeq(ext, i2c->twsi_base + SW_TWSI_EXT); + } + + __raw_writeq(cmd, i2c->twsi_base + SW_TWSI); + + ret = octeon_i2c_hlc_wait(i2c); + if (ret) + goto err; + + cmd = __raw_readq(i2c->twsi_base + SW_TWSI); + if ((cmd & SW_TWSI_R) == 0) + return -EAGAIN; + + ret = check_arb(i2c, false); + +err: + return ret; +} + +/* high-level-controller composite write+read, msg0=addr, msg1=data */ +static int octeon_i2c_ia_read(struct octeon_i2c *i2c, struct i2c_msg *msgs) +{ + int i, j, ret = 0; + u64 cmd; + + octeon_i2c_enable_hlc(i2c); + + cmd = SW_TWSI_V | SW_TWSI_R | SW_TWSI_SOVR; + /* SIZE */ + cmd |= (u64)(msgs[1].len - 1) << SW_TWSI_SIZE_SHIFT; + /* A */ + cmd |= (u64)(msgs[0].addr & 0x7full) << SW_TWSI_ADDR_SHIFT; + + if (msgs[0].flags & I2C_M_TEN) + cmd |= SW_TWSI_OP_10_IA; + else + cmd |= SW_TWSI_OP_7_IA; + + if (msgs[0].len == 2) { + u64 ext = 0; + + cmd |= SW_TWSI_EIA; + ext = (u64) msgs[0].buf[0] << SW_TWSI_IA_SHIFT; + cmd |= (u64) msgs[0].buf[1] << SW_TWSI_IA_SHIFT; + __raw_writeq(ext, i2c->twsi_base + SW_TWSI_EXT); + } else + cmd |= (u64) msgs[0].buf[0] << SW_TWSI_IA_SHIFT; + + octeon_i2c_hlc_int_clear(i2c); + __raw_writeq(cmd, i2c->twsi_base + SW_TWSI); + + ret = octeon_i2c_hlc_wait(i2c); + if (ret) + goto err; + + cmd = __raw_readq(i2c->twsi_base + SW_TWSI); + if ((cmd & SW_TWSI_R) == 0) + return -EAGAIN; + + for (i = 0, j = msgs[1].len - 1; i < msgs[1].len && i < 4; i++, j--) + msgs[1].buf[j] = (cmd >> (8 * i)) & 0xff; + + if (msgs[1].len > 4) { + cmd = __raw_readq(i2c->twsi_base + SW_TWSI_EXT); + for (i = 0; i < msgs[1].len - 4 && i < 4; i++, j--) + msgs[1].buf[j] = (cmd >> (8 * i)) & 0xff; + } + +err: + return ret; +} + +/* high-level-controller composite write+write, m[0]len<=2, m[1]len<=8 */ +static int octeon_i2c_ia_write(struct octeon_i2c *i2c, struct i2c_msg *msgs) +{ + bool set_ext = false; + int i, j, ret = 0; + u64 cmd, ext = 0; + + octeon_i2c_enable_hlc(i2c); + + cmd = SW_TWSI_V | SW_TWSI_SOVR; + /* SIZE */ + cmd |= (u64) (msgs[1].len - 1) << SW_TWSI_SIZE_SHIFT; + /* A */ + cmd |= (u64) (msgs[0].addr & 0x7full) << SW_TWSI_ADDR_SHIFT; + + if (msgs[0].flags & I2C_M_TEN) + cmd |= SW_TWSI_OP_10_IA; + else + cmd |= SW_TWSI_OP_7_IA; + + if (msgs[0].len == 2) { + cmd |= SW_TWSI_EIA; + ext |= (u64) msgs[0].buf[0] << SW_TWSI_IA_SHIFT; + set_ext = true; + cmd |= (u64) msgs[0].buf[1] << SW_TWSI_IA_SHIFT; + } else + cmd |= (u64) msgs[0].buf[0] << SW_TWSI_IA_SHIFT; + + for (i = 0, j = msgs[1].len - 1; i < msgs[1].len && i < 4; i++, j--) + cmd |= (u64) msgs[1].buf[j] << (8 * i); + + if (msgs[1].len > 4) { + for (i = 0; i < msgs[1].len - 4 && i < 4; i++, j--) + ext |= (u64)msgs[1].buf[j] << (8 * i); + set_ext = true; + } + if (set_ext) + __raw_writeq(ext, i2c->twsi_base + SW_TWSI_EXT); + + octeon_i2c_hlc_int_clear(i2c); + __raw_writeq(cmd, i2c->twsi_base + SW_TWSI); + + ret = octeon_i2c_hlc_wait(i2c); + if (ret) + goto err; + + cmd = octeon_i2c_read_sw64(i2c, SW_TWSI_EOP_TWSI_STAT); + if ((cmd & SW_TWSI_R) == 0) + return -EAGAIN; + ret = octeon_i2c_lost_arb(cmd, false); + +err: + return ret; +} + /* calculate and set clock divisors */ static void octeon_i2c_set_clock(struct octeon_i2c *i2c) { @@ -341,23 +676,27 @@ static void octeon_i2c_set_clock(struct octeon_i2c *i2c) static int octeon_i2c_init_lowlevel(struct octeon_i2c *i2c) { - u8 status; + u8 status = 0; int tries; - /* disable high level controller, enable bus access */ - octeon_i2c_write_sw(i2c, SW_TWSI_EOP_TWSI_CTL, TWSI_CTL_ENAB); - /* reset controller */ octeon_i2c_write_sw(i2c, SW_TWSI_EOP_TWSI_RST, 0); - for (tries = 10; tries; tries--) { + for (tries = 10; tries && status != STAT_IDLE; tries--) { udelay(1); status = octeon_i2c_read_sw(i2c, SW_TWSI_EOP_TWSI_STAT); - if (status == STAT_IDLE) - return 0; } - dev_err(i2c->dev, "%s: TWSI_RST failed! (0x%x)\n", __func__, status); - return -EIO; + + if (status != STAT_IDLE) { + dev_err(i2c->dev, "%s: TWSI_RST failed! (0x%x)\n", + __func__, status); + return -EIO; + } + + /* toggle twice to force both teardowns */ + octeon_i2c_enable_hlc(i2c); + octeon_i2c_disable_hlc(i2c); + return 0; } /* @@ -408,6 +747,8 @@ static int octeon_i2c_start(struct octeon_i2c *i2c, int first) int result; u8 data; + octeon_i2c_disable_hlc(i2c); + while (1) { octeon_i2c_write_sw(i2c, SW_TWSI_EOP_TWSI_CTL, TWSI_CTL_ENAB | TWSI_CTL_STA); @@ -565,6 +906,28 @@ static int octeon_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, struct octeon_i2c *i2c = i2c_get_adapdata(adap); int i, ret = 0; + if (num == 1) { + if (msgs[0].len > 0 && msgs[0].len <= 8) { + if (msgs[0].flags & I2C_M_RD) + ret = octeon_i2c_simple_read(i2c, msgs); + else + ret = octeon_i2c_simple_write(i2c, msgs); + goto out; + } + } else if (num == 2) { + if ((msgs[0].flags & I2C_M_RD) == 0 && + (msgs[1].flags & I2C_M_RECV_LEN) == 0 && + msgs[0].len > 0 && msgs[0].len <= 2 && + msgs[1].len > 0 && msgs[1].len <= 8 && + msgs[0].addr == msgs[1].addr) { + if (msgs[1].flags & I2C_M_RD) + ret = octeon_i2c_ia_read(i2c, msgs); + else + ret = octeon_i2c_ia_write(i2c, msgs); + goto out; + } + } + for (i = 0; ret == 0 && i < num; i++) { struct i2c_msg *pmsg = &msgs[i]; bool last = (i == (num - 1)); @@ -582,7 +945,7 @@ static int octeon_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, pmsg->len, !i, last); } octeon_i2c_stop(i2c); - +out: return (ret != 0) ? ret : num; } -- 1.9.1