in busses/i2c-sh7760.c [112:236]
static irqreturn_t sh7760_i2c_irq(int irq, void *ptr)
{
struct cami2c *id = ptr;
struct i2c_msg *msg = id->msg;
char *data = msg->buf;
unsigned long msr, fsr, fier, len;
msr = IN32(id, I2CMSR);
fsr = IN32(id, I2CFSR);
/* arbitration lost */
if (msr & MSR_MAL) {
OUT32(id, I2CMCR, 0);
OUT32(id, I2CSCR, 0);
OUT32(id, I2CSAR, 0);
id->status |= IDS_DONE | IDS_ARBLOST;
goto out;
}
if (msr & MSR_MNR) {
/* NACK handling is very screwed up. After receiving a
* NAK IRQ one has to wait a bit before writing to any
* registers, or the ctl will lock up. After that delay
* do a normal i2c stop. Then wait at least 1 ms before
* attempting another transfer or ctl will stop working
*/
udelay(100); /* wait or risk ctl hang */
OUT32(id, I2CFCR, FCR_RFRST | FCR_TFRST);
OUT32(id, I2CMCR, MCR_MIE | MCR_FSB);
OUT32(id, I2CFIER, 0);
OUT32(id, I2CMIER, MIE_MSTE);
OUT32(id, I2CSCR, 0);
OUT32(id, I2CSAR, 0);
id->status |= IDS_NACK;
msr &= ~MSR_MAT;
fsr = 0;
/* In some cases the MST bit is also set. */
}
/* i2c-stop was sent */
if (msr & MSR_MST) {
id->status |= IDS_DONE;
goto out;
}
/* i2c slave addr was sent; set to "normal" operation */
if (msr & MSR_MAT)
OUT32(id, I2CMCR, MCR_MIE);
fier = IN32(id, I2CFIER);
if (fsr & FSR_RDF) {
len = IN32(id, I2CRFDR);
if (msg->len <= len) {
if (id->flags & IDF_STOP) {
OUT32(id, I2CMCR, MCR_MIE | MCR_FSB);
OUT32(id, I2CFIER, 0);
/* manual says: wait >= 0.5 SCL times */
udelay(5);
/* next int should be MST */
} else {
id->status |= IDS_DONE;
/* keep the RDF bit: ctrl holds SCL low
* until the setup for the next i2c_msg
* clears this bit.
*/
fsr &= ~FSR_RDF;
}
}
while (msg->len && len) {
*data++ = IN32(id, I2CRXTX);
msg->len--;
len--;
}
if (msg->len) {
len = (msg->len >= FIFO_SIZE) ? FIFO_SIZE - 1
: msg->len - 1;
OUT32(id, I2CFCR, FCR_TFRST | ((len & 0xf) << 4));
}
} else if (id->flags & IDF_SEND) {
if ((fsr & FSR_TEND) && (msg->len < 1)) {
if (id->flags & IDF_STOP) {
OUT32(id, I2CMCR, MCR_MIE | MCR_FSB);
} else {
id->status |= IDS_DONE;
/* keep the TEND bit: ctl holds SCL low
* until the setup for the next i2c_msg
* clears this bit.
*/
fsr &= ~FSR_TEND;
}
}
if (fsr & FSR_TDFE) {
while (msg->len && (IN32(id, I2CTFDR) < FIFO_SIZE)) {
OUT32(id, I2CRXTX, *data++);
msg->len--;
}
if (msg->len < 1) {
fier &= ~FIER_TXIE;
OUT32(id, I2CFIER, fier);
} else {
len = (msg->len >= FIFO_SIZE) ? 2 : 0;
OUT32(id, I2CFCR,
FCR_RFRST | ((len & 3) << 2));
}
}
}
out:
if (id->status & IDS_DONE) {
OUT32(id, I2CMIER, 0);
OUT32(id, I2CFIER, 0);
id->msg = NULL;
complete(&id->xfer_done);
}
/* clear status flags and ctrl resumes work */
OUT32(id, I2CMSR, ~msr);
OUT32(id, I2CFSR, ~fsr);
OUT32(id, I2CSSR, 0);
return IRQ_HANDLED;
}