static u32 aspeed_i2c_master_irq()

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;
}