Commit a49cc1fe authored by Ulrich Hecht's avatar Ulrich Hecht Committed by Wolfram Sang

i2c: sh_mobile: implement atomic transfers

Implements atomic transfers to fix reboot/shutdown on r8a7790 Lager and
similar boards.
Signed-off-by: default avatarUlrich Hecht <uli+renesas@fpond.eu>
Tested-by: default avatarWolfram Sang <wsa+renesas@sang-engineering.com>
Tested-by: default avatarGeert Uytterhoeven <geert+renesas@glider.be>
[wsa: some whitespace fixing]
Signed-off-by: default avatarWolfram Sang <wsa@kernel.org>
parent aafced67
...@@ -129,6 +129,7 @@ struct sh_mobile_i2c_data { ...@@ -129,6 +129,7 @@ struct sh_mobile_i2c_data {
int sr; int sr;
bool send_stop; bool send_stop;
bool stop_after_dma; bool stop_after_dma;
bool atomic_xfer;
struct resource *res; struct resource *res;
struct dma_chan *dma_tx; struct dma_chan *dma_tx;
...@@ -330,13 +331,15 @@ static unsigned char i2c_op(struct sh_mobile_i2c_data *pd, enum sh_mobile_i2c_op ...@@ -330,13 +331,15 @@ static unsigned char i2c_op(struct sh_mobile_i2c_data *pd, enum sh_mobile_i2c_op
ret = iic_rd(pd, ICDR); ret = iic_rd(pd, ICDR);
break; break;
case OP_RX_STOP: /* enable DTE interrupt, issue stop */ case OP_RX_STOP: /* enable DTE interrupt, issue stop */
iic_wr(pd, ICIC, if (!pd->atomic_xfer)
ICIC_DTEE | ICIC_WAITE | ICIC_ALE | ICIC_TACKE); iic_wr(pd, ICIC,
ICIC_DTEE | ICIC_WAITE | ICIC_ALE | ICIC_TACKE);
iic_wr(pd, ICCR, ICCR_ICE | ICCR_RACK); iic_wr(pd, ICCR, ICCR_ICE | ICCR_RACK);
break; break;
case OP_RX_STOP_DATA: /* enable DTE interrupt, read data, issue stop */ case OP_RX_STOP_DATA: /* enable DTE interrupt, read data, issue stop */
iic_wr(pd, ICIC, if (!pd->atomic_xfer)
ICIC_DTEE | ICIC_WAITE | ICIC_ALE | ICIC_TACKE); iic_wr(pd, ICIC,
ICIC_DTEE | ICIC_WAITE | ICIC_ALE | ICIC_TACKE);
ret = iic_rd(pd, ICDR); ret = iic_rd(pd, ICDR);
iic_wr(pd, ICCR, ICCR_ICE | ICCR_RACK); iic_wr(pd, ICCR, ICCR_ICE | ICCR_RACK);
break; break;
...@@ -429,7 +432,8 @@ static irqreturn_t sh_mobile_i2c_isr(int irq, void *dev_id) ...@@ -429,7 +432,8 @@ static irqreturn_t sh_mobile_i2c_isr(int irq, void *dev_id)
if (wakeup) { if (wakeup) {
pd->sr |= SW_DONE; pd->sr |= SW_DONE;
wake_up(&pd->wait); if (!pd->atomic_xfer)
wake_up(&pd->wait);
} }
/* defeat write posting to avoid spurious WAIT interrupts */ /* defeat write posting to avoid spurious WAIT interrupts */
...@@ -581,6 +585,9 @@ static void start_ch(struct sh_mobile_i2c_data *pd, struct i2c_msg *usr_msg, ...@@ -581,6 +585,9 @@ static void start_ch(struct sh_mobile_i2c_data *pd, struct i2c_msg *usr_msg,
pd->pos = -1; pd->pos = -1;
pd->sr = 0; pd->sr = 0;
if (pd->atomic_xfer)
return;
pd->dma_buf = i2c_get_dma_safe_msg_buf(pd->msg, 8); pd->dma_buf = i2c_get_dma_safe_msg_buf(pd->msg, 8);
if (pd->dma_buf) if (pd->dma_buf)
sh_mobile_i2c_xfer_dma(pd); sh_mobile_i2c_xfer_dma(pd);
...@@ -637,15 +644,13 @@ static int poll_busy(struct sh_mobile_i2c_data *pd) ...@@ -637,15 +644,13 @@ static int poll_busy(struct sh_mobile_i2c_data *pd)
return i ? 0 : -ETIMEDOUT; return i ? 0 : -ETIMEDOUT;
} }
static int sh_mobile_i2c_xfer(struct i2c_adapter *adapter, static int sh_mobile_xfer(struct sh_mobile_i2c_data *pd,
struct i2c_msg *msgs, struct i2c_msg *msgs, int num)
int num)
{ {
struct sh_mobile_i2c_data *pd = i2c_get_adapdata(adapter);
struct i2c_msg *msg; struct i2c_msg *msg;
int err = 0; int err = 0;
int i; int i;
long timeout; long time_left;
/* Wake up device and enable clock */ /* Wake up device and enable clock */
pm_runtime_get_sync(pd->dev); pm_runtime_get_sync(pd->dev);
...@@ -662,15 +667,35 @@ static int sh_mobile_i2c_xfer(struct i2c_adapter *adapter, ...@@ -662,15 +667,35 @@ static int sh_mobile_i2c_xfer(struct i2c_adapter *adapter,
if (do_start) if (do_start)
i2c_op(pd, OP_START); i2c_op(pd, OP_START);
/* The interrupt handler takes care of the rest... */ if (pd->atomic_xfer) {
timeout = wait_event_timeout(pd->wait, unsigned long j = jiffies + pd->adap.timeout;
pd->sr & (ICSR_TACK | SW_DONE),
adapter->timeout); time_left = time_before_eq(jiffies, j);
while (time_left &&
/* 'stop_after_dma' tells if DMA transfer was complete */ !(pd->sr & (ICSR_TACK | SW_DONE))) {
i2c_put_dma_safe_msg_buf(pd->dma_buf, pd->msg, pd->stop_after_dma); unsigned char sr = iic_rd(pd, ICSR);
if (sr & (ICSR_AL | ICSR_TACK |
ICSR_WAIT | ICSR_DTE)) {
sh_mobile_i2c_isr(0, pd);
udelay(150);
} else {
cpu_relax();
}
time_left = time_before_eq(jiffies, j);
}
} else {
/* The interrupt handler takes care of the rest... */
time_left = wait_event_timeout(pd->wait,
pd->sr & (ICSR_TACK | SW_DONE),
pd->adap.timeout);
/* 'stop_after_dma' tells if DMA xfer was complete */
i2c_put_dma_safe_msg_buf(pd->dma_buf, pd->msg,
pd->stop_after_dma);
}
if (!timeout) { if (!time_left) {
dev_err(pd->dev, "Transfer request timed out\n"); dev_err(pd->dev, "Transfer request timed out\n");
if (pd->dma_direction != DMA_NONE) if (pd->dma_direction != DMA_NONE)
sh_mobile_i2c_cleanup_dma(pd); sh_mobile_i2c_cleanup_dma(pd);
...@@ -696,14 +721,35 @@ static int sh_mobile_i2c_xfer(struct i2c_adapter *adapter, ...@@ -696,14 +721,35 @@ static int sh_mobile_i2c_xfer(struct i2c_adapter *adapter,
return err ?: num; return err ?: num;
} }
static int sh_mobile_i2c_xfer(struct i2c_adapter *adapter,
struct i2c_msg *msgs,
int num)
{
struct sh_mobile_i2c_data *pd = i2c_get_adapdata(adapter);
pd->atomic_xfer = false;
return sh_mobile_xfer(pd, msgs, num);
}
static int sh_mobile_i2c_xfer_atomic(struct i2c_adapter *adapter,
struct i2c_msg *msgs,
int num)
{
struct sh_mobile_i2c_data *pd = i2c_get_adapdata(adapter);
pd->atomic_xfer = true;
return sh_mobile_xfer(pd, msgs, num);
}
static u32 sh_mobile_i2c_func(struct i2c_adapter *adapter) static u32 sh_mobile_i2c_func(struct i2c_adapter *adapter)
{ {
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_PROTOCOL_MANGLING; return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_PROTOCOL_MANGLING;
} }
static const struct i2c_algorithm sh_mobile_i2c_algorithm = { static const struct i2c_algorithm sh_mobile_i2c_algorithm = {
.functionality = sh_mobile_i2c_func, .functionality = sh_mobile_i2c_func,
.master_xfer = sh_mobile_i2c_xfer, .master_xfer = sh_mobile_i2c_xfer,
.master_xfer_atomic = sh_mobile_i2c_xfer_atomic,
}; };
static const struct i2c_adapter_quirks sh_mobile_i2c_quirks = { static const struct i2c_adapter_quirks sh_mobile_i2c_quirks = {
......
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