Commit 9fae82e1 authored by Harini Katakam's avatar Harini Katakam Committed by Wolfram Sang

i2c: cadence: Handle > 252 byte transfers

The I2C controller sends a NACK to the slave when transfer size register
reaches zero, irrespective of the hold bit. So, in order to handle transfers
greater than 252 bytes, the transfer size register has to be maintained at a
value >= 1. This patch implements the same.
The interrupt status is cleared at the beginning of the isr instead of
the end, to avoid missing any interrupts.
Signed-off-by: default avatarHarini Katakam <harinik@xilinx.com>
[wsa: added braces around else branch]
Signed-off-by: default avatarWolfram Sang <wsa@the-dreams.de>
parent 1c574993
...@@ -128,6 +128,7 @@ ...@@ -128,6 +128,7 @@
* @suspended: Flag holding the device's PM status * @suspended: Flag holding the device's PM status
* @send_count: Number of bytes still expected to send * @send_count: Number of bytes still expected to send
* @recv_count: Number of bytes still expected to receive * @recv_count: Number of bytes still expected to receive
* @curr_recv_count: Number of bytes to be received in current transfer
* @irq: IRQ number * @irq: IRQ number
* @input_clk: Input clock to I2C controller * @input_clk: Input clock to I2C controller
* @i2c_clk: Maximum I2C clock speed * @i2c_clk: Maximum I2C clock speed
...@@ -146,6 +147,7 @@ struct cdns_i2c { ...@@ -146,6 +147,7 @@ struct cdns_i2c {
u8 suspended; u8 suspended;
unsigned int send_count; unsigned int send_count;
unsigned int recv_count; unsigned int recv_count;
unsigned int curr_recv_count;
int irq; int irq;
unsigned long input_clk; unsigned long input_clk;
unsigned int i2c_clk; unsigned int i2c_clk;
...@@ -182,14 +184,15 @@ static void cdns_i2c_clear_bus_hold(struct cdns_i2c *id) ...@@ -182,14 +184,15 @@ static void cdns_i2c_clear_bus_hold(struct cdns_i2c *id)
*/ */
static irqreturn_t cdns_i2c_isr(int irq, void *ptr) static irqreturn_t cdns_i2c_isr(int irq, void *ptr)
{ {
unsigned int isr_status, avail_bytes; unsigned int isr_status, avail_bytes, updatetx;
unsigned int bytes_to_recv, bytes_to_send; unsigned int bytes_to_send;
struct cdns_i2c *id = ptr; struct cdns_i2c *id = ptr;
/* Signal completion only after everything is updated */ /* Signal completion only after everything is updated */
int done_flag = 0; int done_flag = 0;
irqreturn_t status = IRQ_NONE; irqreturn_t status = IRQ_NONE;
isr_status = cdns_i2c_readreg(CDNS_I2C_ISR_OFFSET); isr_status = cdns_i2c_readreg(CDNS_I2C_ISR_OFFSET);
cdns_i2c_writereg(isr_status, CDNS_I2C_ISR_OFFSET);
/* Handling nack and arbitration lost interrupt */ /* Handling nack and arbitration lost interrupt */
if (isr_status & (CDNS_I2C_IXR_NACK | CDNS_I2C_IXR_ARB_LOST)) { if (isr_status & (CDNS_I2C_IXR_NACK | CDNS_I2C_IXR_ARB_LOST)) {
...@@ -197,89 +200,112 @@ static irqreturn_t cdns_i2c_isr(int irq, void *ptr) ...@@ -197,89 +200,112 @@ static irqreturn_t cdns_i2c_isr(int irq, void *ptr)
status = IRQ_HANDLED; status = IRQ_HANDLED;
} }
/* Handling Data interrupt */ /*
if ((isr_status & CDNS_I2C_IXR_DATA) && * Check if transfer size register needs to be updated again for a
(id->recv_count >= CDNS_I2C_DATA_INTR_DEPTH)) { * large data receive operation.
/* Always read data interrupt threshold bytes */ */
bytes_to_recv = CDNS_I2C_DATA_INTR_DEPTH; updatetx = 0;
id->recv_count -= CDNS_I2C_DATA_INTR_DEPTH; if (id->recv_count > id->curr_recv_count)
avail_bytes = cdns_i2c_readreg(CDNS_I2C_XFER_SIZE_OFFSET); updatetx = 1;
/* /* When receiving, handle data interrupt and completion interrupt */
* if the tranfer size register value is zero, then if (id->p_recv_buf &&
* check for the remaining bytes and update the ((isr_status & CDNS_I2C_IXR_COMP) ||
* transfer size register. (isr_status & CDNS_I2C_IXR_DATA))) {
*/ /* Read data if receive data valid is set */
if (!avail_bytes) { while (cdns_i2c_readreg(CDNS_I2C_SR_OFFSET) &
if (id->recv_count > CDNS_I2C_TRANSFER_SIZE) CDNS_I2C_SR_RXDV) {
cdns_i2c_writereg(CDNS_I2C_TRANSFER_SIZE, /*
CDNS_I2C_XFER_SIZE_OFFSET); * Clear hold bit that was set for FIFO control if
else * RX data left is less than FIFO depth, unless
cdns_i2c_writereg(id->recv_count, * repeated start is selected.
CDNS_I2C_XFER_SIZE_OFFSET); */
} if ((id->recv_count < CDNS_I2C_FIFO_DEPTH) &&
!id->bus_hold_flag)
cdns_i2c_clear_bus_hold(id);
/* Process the data received */
while (bytes_to_recv--)
*(id->p_recv_buf)++ = *(id->p_recv_buf)++ =
cdns_i2c_readreg(CDNS_I2C_DATA_OFFSET); cdns_i2c_readreg(CDNS_I2C_DATA_OFFSET);
id->recv_count--;
id->curr_recv_count--;
if (!id->bus_hold_flag && if (updatetx &&
(id->recv_count <= CDNS_I2C_FIFO_DEPTH)) (id->curr_recv_count == CDNS_I2C_FIFO_DEPTH + 1))
cdns_i2c_clear_bus_hold(id); break;
}
status = IRQ_HANDLED; /*
} * 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 (updatetx &&
(id->curr_recv_count == CDNS_I2C_FIFO_DEPTH + 1)) {
/* wait while fifo is full */
while (cdns_i2c_readreg(CDNS_I2C_XFER_SIZE_OFFSET) !=
(id->curr_recv_count - CDNS_I2C_FIFO_DEPTH))
;
/* Handling Transfer Complete interrupt */
if (isr_status & CDNS_I2C_IXR_COMP) {
if (!id->p_recv_buf) {
/* /*
* If the device is sending data If there is further * Check number of bytes to be received against maximum
* data to be sent. Calculate the available space * transfer size and update register accordingly.
* in FIFO and fill the FIFO with that many bytes.
*/ */
if (id->send_count) { if (((int)(id->recv_count) - CDNS_I2C_FIFO_DEPTH) >
avail_bytes = CDNS_I2C_FIFO_DEPTH - CDNS_I2C_TRANSFER_SIZE) {
cdns_i2c_readreg(CDNS_I2C_XFER_SIZE_OFFSET); cdns_i2c_writereg(CDNS_I2C_TRANSFER_SIZE,
if (id->send_count > avail_bytes) CDNS_I2C_XFER_SIZE_OFFSET);
bytes_to_send = avail_bytes; id->curr_recv_count = CDNS_I2C_TRANSFER_SIZE +
else CDNS_I2C_FIFO_DEPTH;
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 { } else {
/* cdns_i2c_writereg(id->recv_count -
* Signal the completion of transaction and CDNS_I2C_FIFO_DEPTH,
* clear the hold bus bit if there are no CDNS_I2C_XFER_SIZE_OFFSET);
* further messages to be processed. id->curr_recv_count = id->recv_count;
*/
done_flag = 1;
} }
if (!id->send_count && !id->bus_hold_flag) }
cdns_i2c_clear_bus_hold(id);
} else { /* Clear hold (if not repeated start) and signal completion */
if ((isr_status & CDNS_I2C_IXR_COMP) && !id->recv_count) {
if (!id->bus_hold_flag) if (!id->bus_hold_flag)
cdns_i2c_clear_bus_hold(id); 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 {
/* /*
* If the device is receiving data, then signal * Signal the completion of transaction and
* the completion of transaction and read the data * clear the hold bus bit if there are no
* present in the FIFO. Signal the completion of * further messages to be processed.
* transaction.
*/ */
while (cdns_i2c_readreg(CDNS_I2C_SR_OFFSET) &
CDNS_I2C_SR_RXDV) {
*(id->p_recv_buf)++ =
cdns_i2c_readreg(CDNS_I2C_DATA_OFFSET);
id->recv_count--;
}
done_flag = 1; done_flag = 1;
} }
if (!id->send_count && !id->bus_hold_flag)
cdns_i2c_clear_bus_hold(id);
status = IRQ_HANDLED; status = IRQ_HANDLED;
} }
...@@ -289,8 +315,6 @@ static irqreturn_t cdns_i2c_isr(int irq, void *ptr) ...@@ -289,8 +315,6 @@ static irqreturn_t cdns_i2c_isr(int irq, void *ptr)
if (id->err_status) if (id->err_status)
status = IRQ_HANDLED; status = IRQ_HANDLED;
cdns_i2c_writereg(isr_status, CDNS_I2C_ISR_OFFSET);
if (done_flag) if (done_flag)
complete(&id->xfer_done); complete(&id->xfer_done);
...@@ -316,6 +340,8 @@ static void cdns_i2c_mrecv(struct cdns_i2c *id) ...@@ -316,6 +340,8 @@ static void cdns_i2c_mrecv(struct cdns_i2c *id)
if (id->p_msg->flags & I2C_M_RECV_LEN) if (id->p_msg->flags & I2C_M_RECV_LEN)
id->recv_count = I2C_SMBUS_BLOCK_MAX + 1; id->recv_count = I2C_SMBUS_BLOCK_MAX + 1;
id->curr_recv_count = id->recv_count;
/* /*
* Check for the message size against FIFO depth and set the * Check for the message size against FIFO depth and set the
* 'hold bus' bit if it is greater than FIFO depth. * 'hold bus' bit if it is greater than FIFO depth.
...@@ -335,11 +361,14 @@ static void cdns_i2c_mrecv(struct cdns_i2c *id) ...@@ -335,11 +361,14 @@ static void cdns_i2c_mrecv(struct cdns_i2c *id)
* receive if it is less than transfer size and transfer size if * receive if it is less than transfer size and transfer size if
* it is more. Enable the interrupts. * it is more. Enable the interrupts.
*/ */
if (id->recv_count > CDNS_I2C_TRANSFER_SIZE) if (id->recv_count > CDNS_I2C_TRANSFER_SIZE) {
cdns_i2c_writereg(CDNS_I2C_TRANSFER_SIZE, cdns_i2c_writereg(CDNS_I2C_TRANSFER_SIZE,
CDNS_I2C_XFER_SIZE_OFFSET); CDNS_I2C_XFER_SIZE_OFFSET);
else id->curr_recv_count = CDNS_I2C_TRANSFER_SIZE;
} else {
cdns_i2c_writereg(id->recv_count, CDNS_I2C_XFER_SIZE_OFFSET); cdns_i2c_writereg(id->recv_count, CDNS_I2C_XFER_SIZE_OFFSET);
}
/* Clear the bus hold flag if bytes to receive is less than FIFO size */ /* Clear the bus hold flag if bytes to receive is less than FIFO size */
if (!id->bus_hold_flag && if (!id->bus_hold_flag &&
((id->p_msg->flags & I2C_M_RECV_LEN) != I2C_M_RECV_LEN) && ((id->p_msg->flags & I2C_M_RECV_LEN) != I2C_M_RECV_LEN) &&
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment