static irqreturn_t cdns_i2c_master_isr()

in busses/i2c-cadence.c [389:552]


static irqreturn_t cdns_i2c_master_isr(void *ptr)
{
	unsigned int isr_status, avail_bytes, updatetx;
	unsigned int bytes_to_send;
	bool hold_quirk;
	struct cdns_i2c *id = ptr;
	/* Signal completion only after everything is updated */
	int done_flag = 0;
	irqreturn_t status = IRQ_NONE;

	isr_status = cdns_i2c_readreg(CDNS_I2C_ISR_OFFSET);
	cdns_i2c_writereg(isr_status, CDNS_I2C_ISR_OFFSET);
	id->err_status = 0;

	/* Handling nack and arbitration lost interrupt */
	if (isr_status & (CDNS_I2C_IXR_NACK | CDNS_I2C_IXR_ARB_LOST)) {
		done_flag = 1;
		status = IRQ_HANDLED;
	}

	/*
	 * Check if transfer size register needs to be updated again for a
	 * large data receive operation.
	 */
	updatetx = 0;
	if (id->recv_count > id->curr_recv_count)
		updatetx = 1;

	hold_quirk = (id->quirks & CDNS_I2C_BROKEN_HOLD_BIT) && updatetx;

	/* When receiving, handle data interrupt and completion interrupt */
	if (id->p_recv_buf &&
	    ((isr_status & CDNS_I2C_IXR_COMP) ||
	     (isr_status & CDNS_I2C_IXR_DATA))) {
		/* Read data if receive data valid is set */
		while (cdns_i2c_readreg(CDNS_I2C_SR_OFFSET) &
		       CDNS_I2C_SR_RXDV) {
			if (id->recv_count > 0) {
				*(id->p_recv_buf)++ =
					cdns_i2c_readreg(CDNS_I2C_DATA_OFFSET);
				id->recv_count--;
				id->curr_recv_count--;

				/*
				 * Clear hold bit that was set for FIFO control
				 * if RX data left is less than or equal to
				 * FIFO DEPTH unless repeated start is selected
				 */
				if (id->recv_count <= CDNS_I2C_FIFO_DEPTH &&
				    !id->bus_hold_flag)
					cdns_i2c_clear_bus_hold(id);

			} else {
				dev_err(id->adap.dev.parent,
					"xfer_size reg rollover. xfer aborted!\n");
				id->err_status |= CDNS_I2C_IXR_TO;
				break;
			}

			if (cdns_is_holdquirk(id, hold_quirk))
				break;
		}

		/*
		 * The controller sends NACK to the slave when transfer size
		 * register reaches zero without considering the HOLD bit.
		 * This workaround is implemented for large data transfers to
		 * maintain transfer size non-zero while performing a large
		 * receive operation.
		 */
		if (cdns_is_holdquirk(id, hold_quirk)) {
			/* wait while fifo is full */
			while (cdns_i2c_readreg(CDNS_I2C_XFER_SIZE_OFFSET) !=
			       (id->curr_recv_count - CDNS_I2C_FIFO_DEPTH))
				;

			/*
			 * Check number of bytes to be received against maximum
			 * transfer size and update register accordingly.
			 */
			if (((int)(id->recv_count) - CDNS_I2C_FIFO_DEPTH) >
			    CDNS_I2C_TRANSFER_SIZE) {
				cdns_i2c_writereg(CDNS_I2C_TRANSFER_SIZE,
						  CDNS_I2C_XFER_SIZE_OFFSET);
				id->curr_recv_count = CDNS_I2C_TRANSFER_SIZE +
						      CDNS_I2C_FIFO_DEPTH;
			} else {
				cdns_i2c_writereg(id->recv_count -
						  CDNS_I2C_FIFO_DEPTH,
						  CDNS_I2C_XFER_SIZE_OFFSET);
				id->curr_recv_count = id->recv_count;
			}
		} else if (id->recv_count && !hold_quirk &&
						!id->curr_recv_count) {

			/* Set the slave address in address register*/
			cdns_i2c_writereg(id->p_msg->addr & CDNS_I2C_ADDR_MASK,
						CDNS_I2C_ADDR_OFFSET);

			if (id->recv_count > CDNS_I2C_TRANSFER_SIZE) {
				cdns_i2c_writereg(CDNS_I2C_TRANSFER_SIZE,
						CDNS_I2C_XFER_SIZE_OFFSET);
				id->curr_recv_count = CDNS_I2C_TRANSFER_SIZE;
			} else {
				cdns_i2c_writereg(id->recv_count,
						CDNS_I2C_XFER_SIZE_OFFSET);
				id->curr_recv_count = id->recv_count;
			}
		}

		/* Clear hold (if not repeated start) and signal completion */
		if ((isr_status & CDNS_I2C_IXR_COMP) && !id->recv_count) {
			if (!id->bus_hold_flag)
				cdns_i2c_clear_bus_hold(id);
			done_flag = 1;
		}

		status = IRQ_HANDLED;
	}

	/* When sending, handle transfer complete interrupt */
	if ((isr_status & CDNS_I2C_IXR_COMP) && !id->p_recv_buf) {
		/*
		 * If there is more data to be sent, calculate the
		 * space available in FIFO and fill with that many bytes.
		 */
		if (id->send_count) {
			avail_bytes = CDNS_I2C_FIFO_DEPTH -
			    cdns_i2c_readreg(CDNS_I2C_XFER_SIZE_OFFSET);
			if (id->send_count > avail_bytes)
				bytes_to_send = avail_bytes;
			else
				bytes_to_send = id->send_count;

			while (bytes_to_send--) {
				cdns_i2c_writereg(
					(*(id->p_send_buf)++),
					 CDNS_I2C_DATA_OFFSET);
				id->send_count--;
			}
		} else {
			/*
			 * Signal the completion of transaction and
			 * clear the hold bus bit if there are no
			 * further messages to be processed.
			 */
			done_flag = 1;
		}
		if (!id->send_count && !id->bus_hold_flag)
			cdns_i2c_clear_bus_hold(id);

		status = IRQ_HANDLED;
	}

	/* Update the status for errors */
	id->err_status |= isr_status & CDNS_I2C_IXR_ERR_INTR_MASK;
	if (id->err_status)
		status = IRQ_HANDLED;

	if (done_flag)
		complete(&id->xfer_done);

	return status;
}