diff options
Diffstat (limited to 'drivers/i2c/busses/i2c-octeon-core.c')
| -rw-r--r-- | drivers/i2c/busses/i2c-octeon-core.c | 283 |
1 files changed, 217 insertions, 66 deletions
diff --git a/drivers/i2c/busses/i2c-octeon-core.c b/drivers/i2c/busses/i2c-octeon-core.c index 5b7b942141e7..93a49e4637ec 100644 --- a/drivers/i2c/busses/i2c-octeon-core.c +++ b/drivers/i2c/busses/i2c-octeon-core.c @@ -45,7 +45,7 @@ static bool octeon_i2c_test_iflg(struct octeon_i2c *i2c) * octeon_i2c_wait - wait for the IFLG to be set * @i2c: The struct octeon_i2c * - * Returns 0 on success, otherwise a negative errno. + * Returns: 0 on success, otherwise a negative errno. */ static int octeon_i2c_wait(struct octeon_i2c *i2c) { @@ -135,11 +135,37 @@ static void octeon_i2c_hlc_disable(struct octeon_i2c *i2c) octeon_i2c_ctl_write(i2c, TWSI_CTL_ENAB); } +static void octeon_i2c_block_enable(struct octeon_i2c *i2c) +{ + u64 mode; + + if (i2c->block_enabled || !OCTEON_REG_BLOCK_CTL(i2c)) + return; + + i2c->block_enabled = true; + mode = __raw_readq(i2c->twsi_base + OCTEON_REG_MODE(i2c)); + mode |= TWSX_MODE_BLOCK_MODE; + octeon_i2c_writeq_flush(mode, i2c->twsi_base + OCTEON_REG_MODE(i2c)); +} + +static void octeon_i2c_block_disable(struct octeon_i2c *i2c) +{ + u64 mode; + + if (!i2c->block_enabled || !OCTEON_REG_BLOCK_CTL(i2c)) + return; + + i2c->block_enabled = false; + mode = __raw_readq(i2c->twsi_base + OCTEON_REG_MODE(i2c)); + mode &= ~TWSX_MODE_BLOCK_MODE; + octeon_i2c_writeq_flush(mode, i2c->twsi_base + OCTEON_REG_MODE(i2c)); +} + /** * octeon_i2c_hlc_wait - wait for an HLC operation to complete * @i2c: The struct octeon_i2c * - * Returns 0 on success, otherwise -ETIMEDOUT. + * Returns: 0 on success, otherwise -ETIMEDOUT. */ static int octeon_i2c_hlc_wait(struct octeon_i2c *i2c) { @@ -221,14 +247,14 @@ static int octeon_i2c_check_status(struct octeon_i2c *i2c, int final_read) case STAT_LOST_ARB_B0: return -EAGAIN; - /* Being addressed as slave, should back off & listen */ + /* Being addressed as local target, should back off & listen */ case STAT_SLAVE_60: case STAT_SLAVE_70: case STAT_GENDATA_ACK: case STAT_GENDATA_NAK: return -EOPNOTSUPP; - /* Core busy as slave */ + /* Core busy as local target */ case STAT_SLAVE_80: case STAT_SLAVE_88: case STAT_SLAVE_A0: @@ -273,7 +299,7 @@ static int octeon_i2c_recovery(struct octeon_i2c *i2c) * octeon_i2c_start - send START to the bus * @i2c: The struct octeon_i2c * - * Returns 0 on success, otherwise a negative errno. + * Returns: 0 on success, otherwise a negative errno. */ static int octeon_i2c_start(struct octeon_i2c *i2c) { @@ -281,6 +307,7 @@ static int octeon_i2c_start(struct octeon_i2c *i2c) u8 stat; octeon_i2c_hlc_disable(i2c); + octeon_i2c_block_disable(i2c); octeon_i2c_ctl_write(i2c, TWSI_CTL_ENAB | TWSI_CTL_STA); ret = octeon_i2c_wait(i2c); @@ -314,7 +341,7 @@ static void octeon_i2c_stop(struct octeon_i2c *i2c) * * The address is sent over the bus, then the data is read. * - * Returns 0 on success, otherwise a negative errno. + * Returns: 0 on success, otherwise a negative errno. */ static int octeon_i2c_read(struct octeon_i2c *i2c, int target, u8 *data, u16 *rlength, bool recv_len) @@ -382,7 +409,7 @@ static int octeon_i2c_read(struct octeon_i2c *i2c, int target, * * The address is sent over the bus, then the data. * - * Returns 0 on success, otherwise a negative errno. + * Returns: 0 on success, otherwise a negative errno. */ static int octeon_i2c_write(struct octeon_i2c *i2c, int target, const u8 *data, int length) @@ -421,17 +448,12 @@ static int octeon_i2c_hlc_read(struct octeon_i2c *i2c, struct i2c_msg *msgs) octeon_i2c_hlc_enable(i2c); octeon_i2c_hlc_int_clear(i2c); - cmd = SW_TWSI_V | SW_TWSI_R | SW_TWSI_SOVR; + cmd = SW_TWSI_V | SW_TWSI_R | SW_TWSI_SOVR | SW_TWSI_OP_7; /* 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_writeq_flush(cmd, i2c->twsi_base + OCTEON_REG_SW_TWSI(i2c)); ret = octeon_i2c_hlc_wait(i2c); if (ret) @@ -463,17 +485,12 @@ static int octeon_i2c_hlc_write(struct octeon_i2c *i2c, struct i2c_msg *msgs) octeon_i2c_hlc_enable(i2c); octeon_i2c_hlc_int_clear(i2c); - cmd = SW_TWSI_V | SW_TWSI_SOVR; + cmd = SW_TWSI_V | SW_TWSI_SOVR | SW_TWSI_OP_7; /* 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); @@ -498,6 +515,45 @@ err: return ret; } +/* Process hlc transaction */ +static int octeon_i2c_hlc_cmd_send(struct octeon_i2c *i2c, u64 cmd) +{ + octeon_i2c_hlc_int_clear(i2c); + octeon_i2c_writeq_flush(cmd, i2c->twsi_base + OCTEON_REG_SW_TWSI(i2c)); + + return octeon_i2c_hlc_wait(i2c); +} + +/* Generic consideration for extended internal addresses in i2c hlc r/w ops */ +static bool octeon_i2c_hlc_ext(struct octeon_i2c *i2c, struct i2c_msg msg, u64 *cmd_in, u64 *ext) +{ + bool set_ext = false; + u64 cmd = 0; + + if (msg.len == 2) { + cmd |= SW_TWSI_EIA; + *ext = (u64)msg.buf[0] << SW_TWSI_IA_SHIFT; + cmd |= (u64)msg.buf[1] << SW_TWSI_IA_SHIFT; + set_ext = true; + } else { + cmd |= (u64)msg.buf[0] << SW_TWSI_IA_SHIFT; + } + + *cmd_in |= cmd; + return set_ext; +} + +/* Construct and send i2c transaction core cmd for read ops */ +static int octeon_i2c_hlc_read_cmd(struct octeon_i2c *i2c, struct i2c_msg msg, u64 cmd) +{ + u64 ext = 0; + + if (octeon_i2c_hlc_ext(i2c, msg, &cmd, &ext)) + octeon_i2c_writeq_flush(ext, i2c->twsi_base + OCTEON_REG_SW_TWSI_EXT(i2c)); + + return octeon_i2c_hlc_cmd_send(i2c, cmd); +} + /* high-level-controller composite write+read, msg0=addr, msg1=data */ static int octeon_i2c_hlc_comp_read(struct octeon_i2c *i2c, struct i2c_msg *msgs) { @@ -506,32 +562,14 @@ static int octeon_i2c_hlc_comp_read(struct octeon_i2c *i2c, struct i2c_msg *msgs octeon_i2c_hlc_enable(i2c); - cmd = SW_TWSI_V | SW_TWSI_R | SW_TWSI_SOVR; + cmd = SW_TWSI_V | SW_TWSI_R | SW_TWSI_SOVR | SW_TWSI_OP_7_IA; /* 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; - octeon_i2c_writeq_flush(ext, i2c->twsi_base + OCTEON_REG_SW_TWSI_EXT(i2c)); - } else { - cmd |= (u64)msgs[0].buf[0] << SW_TWSI_IA_SHIFT; - } - - octeon_i2c_hlc_int_clear(i2c); - octeon_i2c_writeq_flush(cmd, i2c->twsi_base + OCTEON_REG_SW_TWSI(i2c)); - - ret = octeon_i2c_hlc_wait(i2c); + /* Send core command */ + ret = octeon_i2c_hlc_read_cmd(i2c, msgs[0], cmd); if (ret) goto err; @@ -561,25 +599,14 @@ static int octeon_i2c_hlc_comp_write(struct octeon_i2c *i2c, struct i2c_msg *msg octeon_i2c_hlc_enable(i2c); - cmd = SW_TWSI_V | SW_TWSI_SOVR; + cmd = SW_TWSI_V | SW_TWSI_SOVR | SW_TWSI_OP_7_IA; /* 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; - } + /* Set parameters for extended message (if required) */ + set_ext = octeon_i2c_hlc_ext(i2c, msgs[0], &cmd, &ext); for (i = 0, j = msgs[1].len - 1; i < msgs[1].len && i < 4; i++, j--) cmd |= (u64)msgs[1].buf[j] << (8 * i); @@ -592,10 +619,7 @@ static int octeon_i2c_hlc_comp_write(struct octeon_i2c *i2c, struct i2c_msg *msg if (set_ext) octeon_i2c_writeq_flush(ext, i2c->twsi_base + OCTEON_REG_SW_TWSI_EXT(i2c)); - octeon_i2c_hlc_int_clear(i2c); - octeon_i2c_writeq_flush(cmd, i2c->twsi_base + OCTEON_REG_SW_TWSI(i2c)); - - ret = octeon_i2c_hlc_wait(i2c); + ret = octeon_i2c_hlc_cmd_send(i2c, cmd); if (ret) goto err; @@ -608,12 +632,131 @@ err: } /** - * octeon_i2c_xfer - The driver's master_xfer function + * octeon_i2c_hlc_block_comp_read - high-level-controller composite block read + * @i2c: The struct octeon_i2c + * @msgs: msg[0] contains address, place read data into msg[1] + * + * i2c core command is constructed and written into the SW_TWSI register. + * The execution of the command will result in requested data being + * placed into a FIFO buffer, ready to be read. + * Used in the case where the i2c xfer is for greater than 8 bytes of read data. + * + * Returns: 0 on success, otherwise a negative errno. + */ +static int octeon_i2c_hlc_block_comp_read(struct octeon_i2c *i2c, struct i2c_msg *msgs) +{ + int ret; + u16 len, i; + u64 cmd; + + octeon_i2c_hlc_enable(i2c); + octeon_i2c_block_enable(i2c); + + /* Write (size - 1) into block control register */ + len = msgs[1].len - 1; + octeon_i2c_writeq_flush((u64)len, i2c->twsi_base + OCTEON_REG_BLOCK_CTL(i2c)); + + /* Prepare core command */ + cmd = SW_TWSI_V | SW_TWSI_R | SW_TWSI_SOVR | SW_TWSI_OP_7_IA; + cmd |= (u64)(msgs[0].addr & 0x7full) << SW_TWSI_ADDR_SHIFT; + + /* Send core command */ + ret = octeon_i2c_hlc_read_cmd(i2c, msgs[0], cmd); + if (ret) + goto err; + + cmd = __raw_readq(i2c->twsi_base + OCTEON_REG_SW_TWSI(i2c)); + if ((cmd & SW_TWSI_R) == 0) { + octeon_i2c_block_disable(i2c); + return octeon_i2c_check_status(i2c, false); + } + + /* read data in FIFO */ + octeon_i2c_writeq_flush(TWSX_BLOCK_STS_RESET_PTR, + i2c->twsi_base + OCTEON_REG_BLOCK_STS(i2c)); + for (i = 0; i <= len; i += 8) { + /* Byte-swap FIFO data and copy into msg buffer */ + __be64 rd = cpu_to_be64(__raw_readq(i2c->twsi_base + OCTEON_REG_BLOCK_FIFO(i2c))); + + memcpy(&msgs[1].buf[i], &rd, min(8, msgs[1].len - i)); + } + +err: + octeon_i2c_block_disable(i2c); + return ret; +} + +/** + * octeon_i2c_hlc_block_comp_write - high-level-controller composite block write + * @i2c: The struct octeon_i2c + * @msgs: msg[0] contains address, msg[1] contains data to be written + * + * i2c core command is constructed and write data is written into the FIFO buffer. + * The execution of the command will result in HW write, using the data in FIFO. + * Used in the case where the i2c xfer is for greater than 8 bytes of write data. + * + * Returns: 0 on success, otherwise a negative errno. + */ +static int octeon_i2c_hlc_block_comp_write(struct octeon_i2c *i2c, struct i2c_msg *msgs) +{ + bool set_ext; + int ret; + u16 len, i; + u64 cmd, ext = 0; + + octeon_i2c_hlc_enable(i2c); + octeon_i2c_block_enable(i2c); + + /* Write (size - 1) into block control register */ + len = msgs[1].len - 1; + octeon_i2c_writeq_flush((u64)len, i2c->twsi_base + OCTEON_REG_BLOCK_CTL(i2c)); + + /* Prepare core command */ + cmd = SW_TWSI_V | SW_TWSI_SOVR | SW_TWSI_OP_7_IA; + cmd |= (u64)(msgs[0].addr & 0x7full) << SW_TWSI_ADDR_SHIFT; + + /* Set parameters for extended message (if required) */ + set_ext = octeon_i2c_hlc_ext(i2c, msgs[0], &cmd, &ext); + + /* Write msg into FIFO buffer */ + octeon_i2c_writeq_flush(TWSX_BLOCK_STS_RESET_PTR, + i2c->twsi_base + OCTEON_REG_BLOCK_STS(i2c)); + for (i = 0; i <= len; i += 8) { + __be64 buf = 0; + + /* Copy 8 bytes or remaining bytes from message buffer */ + memcpy(&buf, &msgs[1].buf[i], min(8, msgs[1].len - i)); + + /* Byte-swap message data and write into FIFO */ + buf = cpu_to_be64(buf); + octeon_i2c_writeq_flush((u64)buf, i2c->twsi_base + OCTEON_REG_BLOCK_FIFO(i2c)); + } + if (set_ext) + octeon_i2c_writeq_flush(ext, i2c->twsi_base + OCTEON_REG_SW_TWSI_EXT(i2c)); + + /* Send command to core (send data in FIFO) */ + ret = octeon_i2c_hlc_cmd_send(i2c, cmd); + if (ret) + goto err; + + cmd = __raw_readq(i2c->twsi_base + OCTEON_REG_SW_TWSI(i2c)); + if ((cmd & SW_TWSI_R) == 0) { + octeon_i2c_block_disable(i2c); + return octeon_i2c_check_status(i2c, false); + } + +err: + octeon_i2c_block_disable(i2c); + return ret; +} + +/** + * octeon_i2c_xfer - The driver's xfer function * @adap: Pointer to the i2c_adapter structure * @msgs: Pointer to the messages to be processed * @num: Length of the MSGS array * - * Returns the number of messages processed, or a negative errno on failure. + * Returns: the number of messages processed, or a negative errno on failure. */ int octeon_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) { @@ -633,13 +776,21 @@ int octeon_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) 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[1].len > 0 && msgs[0].addr == msgs[1].addr) { - if (msgs[1].flags & I2C_M_RD) - ret = octeon_i2c_hlc_comp_read(i2c, msgs); - else - ret = octeon_i2c_hlc_comp_write(i2c, msgs); - goto out; + if (msgs[1].len <= 8) { + if (msgs[1].flags & I2C_M_RD) + ret = octeon_i2c_hlc_comp_read(i2c, msgs); + else + ret = octeon_i2c_hlc_comp_write(i2c, msgs); + goto out; + } else if (msgs[1].len <= 1024 && OCTEON_REG_BLOCK_CTL(i2c)) { + if (msgs[1].flags & I2C_M_RD) + ret = octeon_i2c_hlc_block_comp_read(i2c, msgs); + else + ret = octeon_i2c_hlc_block_comp_write(i2c, msgs); + goto out; + } } } } |
