in busses/i2c-aspeed.c [401:593]
static u32 aspeed_i2c_master_irq(struct aspeed_i2c_bus *bus, u32 irq_status)
{
u32 irq_handled = 0, command = 0;
struct i2c_msg *msg;
u8 recv_byte;
int ret;
if (irq_status & ASPEED_I2CD_INTR_BUS_RECOVER_DONE) {
bus->master_state = ASPEED_I2C_MASTER_INACTIVE;
irq_handled |= ASPEED_I2CD_INTR_BUS_RECOVER_DONE;
goto out_complete;
}
/*
* We encountered an interrupt that reports an error: the hardware
* should clear the command queue effectively taking us back to the
* INACTIVE state.
*/
ret = aspeed_i2c_is_irq_error(irq_status);
if (ret) {
dev_dbg(bus->dev, "received error interrupt: 0x%08x\n",
irq_status);
irq_handled |= (irq_status & ASPEED_I2CD_INTR_MASTER_ERRORS);
if (bus->master_state != ASPEED_I2C_MASTER_INACTIVE) {
bus->cmd_err = ret;
bus->master_state = ASPEED_I2C_MASTER_INACTIVE;
goto out_complete;
}
}
/* Master is not currently active, irq was for someone else. */
if (bus->master_state == ASPEED_I2C_MASTER_INACTIVE ||
bus->master_state == ASPEED_I2C_MASTER_PENDING)
goto out_no_complete;
/* We are in an invalid state; reset bus to a known state. */
if (!bus->msgs) {
dev_err(bus->dev, "bus in unknown state. irq_status: 0x%x\n",
irq_status);
bus->cmd_err = -EIO;
if (bus->master_state != ASPEED_I2C_MASTER_STOP &&
bus->master_state != ASPEED_I2C_MASTER_INACTIVE)
aspeed_i2c_do_stop(bus);
goto out_no_complete;
}
msg = &bus->msgs[bus->msgs_index];
/*
* START is a special case because we still have to handle a subsequent
* TX or RX immediately after we handle it, so we handle it here and
* then update the state and handle the new state below.
*/
if (bus->master_state == ASPEED_I2C_MASTER_START) {
#if IS_ENABLED(CONFIG_I2C_SLAVE)
/*
* If a peer master starts a xfer immediately after it queues a
* master command, clear the queued master command and change
* its state to 'pending'. To simplify handling of pending
* cases, it uses S/W solution instead of H/W command queue
* handling.
*/
if (unlikely(irq_status & ASPEED_I2CD_INTR_SLAVE_MATCH)) {
writel(readl(bus->base + ASPEED_I2C_CMD_REG) &
~ASPEED_I2CD_MASTER_CMDS_MASK,
bus->base + ASPEED_I2C_CMD_REG);
bus->master_state = ASPEED_I2C_MASTER_PENDING;
dev_dbg(bus->dev,
"master goes pending due to a slave start\n");
goto out_no_complete;
}
#endif /* CONFIG_I2C_SLAVE */
if (unlikely(!(irq_status & ASPEED_I2CD_INTR_TX_ACK))) {
if (unlikely(!(irq_status & ASPEED_I2CD_INTR_TX_NAK))) {
bus->cmd_err = -ENXIO;
bus->master_state = ASPEED_I2C_MASTER_INACTIVE;
goto out_complete;
}
pr_devel("no slave present at %02x\n", msg->addr);
irq_handled |= ASPEED_I2CD_INTR_TX_NAK;
bus->cmd_err = -ENXIO;
aspeed_i2c_do_stop(bus);
goto out_no_complete;
}
irq_handled |= ASPEED_I2CD_INTR_TX_ACK;
if (msg->len == 0) { /* SMBUS_QUICK */
aspeed_i2c_do_stop(bus);
goto out_no_complete;
}
if (msg->flags & I2C_M_RD)
bus->master_state = ASPEED_I2C_MASTER_RX_FIRST;
else
bus->master_state = ASPEED_I2C_MASTER_TX_FIRST;
}
switch (bus->master_state) {
case ASPEED_I2C_MASTER_TX:
if (unlikely(irq_status & ASPEED_I2CD_INTR_TX_NAK)) {
dev_dbg(bus->dev, "slave NACKed TX\n");
irq_handled |= ASPEED_I2CD_INTR_TX_NAK;
goto error_and_stop;
} else if (unlikely(!(irq_status & ASPEED_I2CD_INTR_TX_ACK))) {
dev_err(bus->dev, "slave failed to ACK TX\n");
goto error_and_stop;
}
irq_handled |= ASPEED_I2CD_INTR_TX_ACK;
fallthrough;
case ASPEED_I2C_MASTER_TX_FIRST:
if (bus->buf_index < msg->len) {
bus->master_state = ASPEED_I2C_MASTER_TX;
writel(msg->buf[bus->buf_index++],
bus->base + ASPEED_I2C_BYTE_BUF_REG);
writel(ASPEED_I2CD_M_TX_CMD,
bus->base + ASPEED_I2C_CMD_REG);
} else {
aspeed_i2c_next_msg_or_stop(bus);
}
goto out_no_complete;
case ASPEED_I2C_MASTER_RX_FIRST:
/* RX may not have completed yet (only address cycle) */
if (!(irq_status & ASPEED_I2CD_INTR_RX_DONE))
goto out_no_complete;
fallthrough;
case ASPEED_I2C_MASTER_RX:
if (unlikely(!(irq_status & ASPEED_I2CD_INTR_RX_DONE))) {
dev_err(bus->dev, "master failed to RX\n");
goto error_and_stop;
}
irq_handled |= ASPEED_I2CD_INTR_RX_DONE;
recv_byte = readl(bus->base + ASPEED_I2C_BYTE_BUF_REG) >> 8;
msg->buf[bus->buf_index++] = recv_byte;
if (msg->flags & I2C_M_RECV_LEN) {
if (unlikely(recv_byte > I2C_SMBUS_BLOCK_MAX)) {
bus->cmd_err = -EPROTO;
aspeed_i2c_do_stop(bus);
goto out_no_complete;
}
msg->len = recv_byte +
((msg->flags & I2C_CLIENT_PEC) ? 2 : 1);
msg->flags &= ~I2C_M_RECV_LEN;
}
if (bus->buf_index < msg->len) {
bus->master_state = ASPEED_I2C_MASTER_RX;
command = ASPEED_I2CD_M_RX_CMD;
if (bus->buf_index + 1 == msg->len)
command |= ASPEED_I2CD_M_S_RX_CMD_LAST;
writel(command, bus->base + ASPEED_I2C_CMD_REG);
} else {
aspeed_i2c_next_msg_or_stop(bus);
}
goto out_no_complete;
case ASPEED_I2C_MASTER_STOP:
if (unlikely(!(irq_status & ASPEED_I2CD_INTR_NORMAL_STOP))) {
dev_err(bus->dev,
"master failed to STOP. irq_status:0x%x\n",
irq_status);
bus->cmd_err = -EIO;
/* Do not STOP as we have already tried. */
} else {
irq_handled |= ASPEED_I2CD_INTR_NORMAL_STOP;
}
bus->master_state = ASPEED_I2C_MASTER_INACTIVE;
goto out_complete;
case ASPEED_I2C_MASTER_INACTIVE:
dev_err(bus->dev,
"master received interrupt 0x%08x, but is inactive\n",
irq_status);
bus->cmd_err = -EIO;
/* Do not STOP as we should be inactive. */
goto out_complete;
default:
WARN(1, "unknown master state\n");
bus->master_state = ASPEED_I2C_MASTER_INACTIVE;
bus->cmd_err = -EINVAL;
goto out_complete;
}
error_and_stop:
bus->cmd_err = -EIO;
aspeed_i2c_do_stop(bus);
goto out_no_complete;
out_complete:
bus->msgs = NULL;
if (bus->cmd_err)
bus->master_xfer_result = bus->cmd_err;
else
bus->master_xfer_result = bus->msgs_index + 1;
complete(&bus->cmd_complete);
out_no_complete:
return irq_handled;
}