Commit ba15a143 authored by Roman Bacik's avatar Roman Bacik Committed by Wolfram Sang
Browse files

i2c: iproc: handle invalid slave state



Add the code to handle an invalid state when both bits S_RX_EVENT
(indicating a transaction) and S_START_BUSY (indicating the end
of transaction - transition of START_BUSY from 1 to 0) are set in
the interrupt status register during a slave read.

Signed-off-by: default avatarRoman Bacik <roman.bacik@broadcom.com>
Fixes: 1ca1b451 ("i2c: iproc: handle Master aborted error")
Acked-by: default avatarRay Jui <ray.jui@broadcom.com>
Signed-off-by: default avatarWolfram Sang <wsa@kernel.org>
parent 30523014
Loading
Loading
Loading
Loading
+75 −58
Original line number Diff line number Diff line
@@ -316,26 +316,44 @@ static void bcm_iproc_i2c_slave_init(
	iproc_i2c_wr_reg(iproc_i2c, IE_OFFSET, val);
}

static void bcm_iproc_i2c_check_slave_status(
	struct bcm_iproc_i2c_dev *iproc_i2c)
static bool bcm_iproc_i2c_check_slave_status
	(struct bcm_iproc_i2c_dev *iproc_i2c, u32 status)
{
	u32 val;
	bool recover = false;

	/* check slave transmit status only if slave is transmitting */
	if (!iproc_i2c->slave_rx_only) {
		val = iproc_i2c_rd_reg(iproc_i2c, S_CMD_OFFSET);
	/* status is valid only when START_BUSY is cleared after it was set */
	if (val & BIT(S_CMD_START_BUSY_SHIFT))
		return;

		/* status is valid only when START_BUSY is cleared */
		if (!(val & BIT(S_CMD_START_BUSY_SHIFT))) {
			val = (val >> S_CMD_STATUS_SHIFT) & S_CMD_STATUS_MASK;
	if (val == S_CMD_STATUS_TIMEOUT || val == S_CMD_STATUS_MASTER_ABORT) {
		dev_err(iproc_i2c->device, (val == S_CMD_STATUS_TIMEOUT) ?
			if (val == S_CMD_STATUS_TIMEOUT ||
			    val == S_CMD_STATUS_MASTER_ABORT) {
				dev_warn(iproc_i2c->device,
					 (val == S_CMD_STATUS_TIMEOUT) ?
					 "slave random stretch time timeout\n" :
					 "Master aborted read transaction\n");
				recover = true;
			}
		}
	}

	/* RX_EVENT is not valid when START_BUSY is set */
	if ((status & BIT(IS_S_RX_EVENT_SHIFT)) &&
	    (status & BIT(IS_S_START_BUSY_SHIFT))) {
		dev_warn(iproc_i2c->device, "Slave aborted read transaction\n");
		recover = true;
	}

	if (recover) {
		/* re-initialize i2c for recovery */
		bcm_iproc_i2c_enable_disable(iproc_i2c, false);
		bcm_iproc_i2c_slave_init(iproc_i2c, true);
		bcm_iproc_i2c_enable_disable(iproc_i2c, true);
	}

	return recover;
}

static void bcm_iproc_i2c_slave_read(struct bcm_iproc_i2c_dev *iproc_i2c)
@@ -420,48 +438,6 @@ static bool bcm_iproc_i2c_slave_isr(struct bcm_iproc_i2c_dev *iproc_i2c,
	u32 val;
	u8 value;

	/*
	 * Slave events in case of master-write, master-write-read and,
	 * master-read
	 *
	 * Master-write     : only IS_S_RX_EVENT_SHIFT event
	 * Master-write-read: both IS_S_RX_EVENT_SHIFT and IS_S_RD_EVENT_SHIFT
	 *                    events
	 * Master-read      : both IS_S_RX_EVENT_SHIFT and IS_S_RD_EVENT_SHIFT
	 *                    events or only IS_S_RD_EVENT_SHIFT
	 *
	 * iproc has a slave rx fifo size of 64 bytes. Rx fifo full interrupt
	 * (IS_S_RX_FIFO_FULL_SHIFT) will be generated when RX fifo becomes
	 * full. This can happen if Master issues write requests of more than
	 * 64 bytes.
	 */
	if (status & BIT(IS_S_RX_EVENT_SHIFT) ||
	    status & BIT(IS_S_RD_EVENT_SHIFT) ||
	    status & BIT(IS_S_RX_FIFO_FULL_SHIFT)) {
		/* disable slave interrupts */
		val = iproc_i2c_rd_reg(iproc_i2c, IE_OFFSET);
		val &= ~iproc_i2c->slave_int_mask;
		iproc_i2c_wr_reg(iproc_i2c, IE_OFFSET, val);

		if (status & BIT(IS_S_RD_EVENT_SHIFT))
			/* Master-write-read request */
			iproc_i2c->slave_rx_only = false;
		else
			/* Master-write request only */
			iproc_i2c->slave_rx_only = true;

		/* schedule tasklet to read data later */
		tasklet_schedule(&iproc_i2c->slave_rx_tasklet);

		/*
		 * clear only IS_S_RX_EVENT_SHIFT and
		 * IS_S_RX_FIFO_FULL_SHIFT interrupt.
		 */
		val = BIT(IS_S_RX_EVENT_SHIFT);
		if (status & BIT(IS_S_RX_FIFO_FULL_SHIFT))
			val |= BIT(IS_S_RX_FIFO_FULL_SHIFT);
		iproc_i2c_wr_reg(iproc_i2c, IS_OFFSET, val);
	}

	if (status & BIT(IS_S_TX_UNDERRUN_SHIFT)) {
		iproc_i2c->tx_underrun++;
@@ -493,8 +469,9 @@ static bool bcm_iproc_i2c_slave_isr(struct bcm_iproc_i2c_dev *iproc_i2c,
		 * less than PKT_LENGTH bytes were output on the SMBUS
		 */
		iproc_i2c->slave_int_mask &= ~BIT(IE_S_TX_UNDERRUN_SHIFT);
		iproc_i2c_wr_reg(iproc_i2c, IE_OFFSET,
				 iproc_i2c->slave_int_mask);
		val = iproc_i2c_rd_reg(iproc_i2c, IE_OFFSET);
		val &= ~BIT(IE_S_TX_UNDERRUN_SHIFT);
		iproc_i2c_wr_reg(iproc_i2c, IE_OFFSET, val);

		/* End of SMBUS for Master Read */
		val = BIT(S_TX_WR_STATUS_SHIFT);
@@ -515,9 +492,49 @@ static bool bcm_iproc_i2c_slave_isr(struct bcm_iproc_i2c_dev *iproc_i2c,
				 BIT(IS_S_START_BUSY_SHIFT));
	}

	/* check slave transmit status only if slave is transmitting */
	if (!iproc_i2c->slave_rx_only)
		bcm_iproc_i2c_check_slave_status(iproc_i2c);
	/* if the controller has been reset, immediately return from the ISR */
	if (bcm_iproc_i2c_check_slave_status(iproc_i2c, status))
		return true;

	/*
	 * Slave events in case of master-write, master-write-read and,
	 * master-read
	 *
	 * Master-write     : only IS_S_RX_EVENT_SHIFT event
	 * Master-write-read: both IS_S_RX_EVENT_SHIFT and IS_S_RD_EVENT_SHIFT
	 *                    events
	 * Master-read      : both IS_S_RX_EVENT_SHIFT and IS_S_RD_EVENT_SHIFT
	 *                    events or only IS_S_RD_EVENT_SHIFT
	 *
	 * iproc has a slave rx fifo size of 64 bytes. Rx fifo full interrupt
	 * (IS_S_RX_FIFO_FULL_SHIFT) will be generated when RX fifo becomes
	 * full. This can happen if Master issues write requests of more than
	 * 64 bytes.
	 */
	if (status & BIT(IS_S_RX_EVENT_SHIFT) ||
	    status & BIT(IS_S_RD_EVENT_SHIFT) ||
	    status & BIT(IS_S_RX_FIFO_FULL_SHIFT)) {
		/* disable slave interrupts */
		val = iproc_i2c_rd_reg(iproc_i2c, IE_OFFSET);
		val &= ~iproc_i2c->slave_int_mask;
		iproc_i2c_wr_reg(iproc_i2c, IE_OFFSET, val);

		if (status & BIT(IS_S_RD_EVENT_SHIFT))
			/* Master-write-read request */
			iproc_i2c->slave_rx_only = false;
		else
			/* Master-write request only */
			iproc_i2c->slave_rx_only = true;

		/* schedule tasklet to read data later */
		tasklet_schedule(&iproc_i2c->slave_rx_tasklet);

		/* clear IS_S_RX_FIFO_FULL_SHIFT interrupt */
		if (status & BIT(IS_S_RX_FIFO_FULL_SHIFT)) {
			val = BIT(IS_S_RX_FIFO_FULL_SHIFT);
			iproc_i2c_wr_reg(iproc_i2c, IS_OFFSET, val);
		}
	}

	return true;
}