From: Wolfram Sang <wsa+rene...@sang-engineering.com> This whole series caused sometimes timeouts and even OOPSes on some r8a7791 Koelsch boards. We need to understand and fix those first.
Revert "i2c: rcar: clean up after refactoring" Revert "i2c: rcar: revoke START request early" Revert "i2c: rcar: check master irqs before slave irqs" Revert "i2c: rcar: don't issue stop when HW does it automatically" Revert "i2c: rcar: init new messages in irq" Revert "i2c: rcar: refactor setup of a msg" Revert "i2c: rcar: remove spinlock" Revert "i2c: rcar: remove unused IOERROR state" Revert "i2c: rcar: rework hw init" Signed-off-by: Wolfram Sang <wsa+rene...@sang-engineering.com> --- drivers/i2c/busses/i2c-rcar.c | 193 ++++++++++++++++++++++++++---------------- 1 file changed, 122 insertions(+), 71 deletions(-) diff --git a/drivers/i2c/busses/i2c-rcar.c b/drivers/i2c/busses/i2c-rcar.c index bbf3b2505aaf09..09bdce969bd8e6 100644 --- a/drivers/i2c/busses/i2c-rcar.c +++ b/drivers/i2c/busses/i2c-rcar.c @@ -1,8 +1,7 @@ /* * Driver for the Renesas RCar I2C unit * - * Copyright (C) 2014-15 Wolfram Sang <w...@sang-engineering.com> - * Copyright (C) 2011-2015 Renesas Electronics Corporation + * Copyright (C) 2014 Wolfram Sang <w...@sang-engineering.com> * * Copyright (C) 2012-14 Renesas Solutions Corp. * Kuninori Morimoto <kuninori.morimoto...@renesas.com> @@ -10,6 +9,9 @@ * This file is based on the drivers/i2c/busses/i2c-sh7760.c * (c) 2005-2008 MSC Vertriebsges.m.b.H, Manuel Lauss <m...@msc-ge.com> * + * This file used out-of-tree driver i2c-rcar.c + * Copyright (C) 2011-2012 Renesas Electronics Corporation + * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. @@ -31,6 +33,7 @@ #include <linux/platform_device.h> #include <linux/pm_runtime.h> #include <linux/slab.h> +#include <linux/spinlock.h> /* register offsets */ #define ICSCR 0x00 /* slave ctrl */ @@ -81,7 +84,6 @@ #define RCAR_BUS_PHASE_START (MDBS | MIE | ESG) #define RCAR_BUS_PHASE_DATA (MDBS | MIE) -#define RCAR_BUS_MASK_DATA (~(ESG | FSB) & 0xFF) #define RCAR_BUS_PHASE_STOP (MDBS | MIE | FSB) #define RCAR_IRQ_SEND (MNR | MAL | MST | MAT | MDE) @@ -92,6 +94,7 @@ #define RCAR_IRQ_ACK_RECV (~(MAT | MDR) & 0xFF) #define ID_LAST_MSG (1 << 0) +#define ID_IOERROR (1 << 1) #define ID_DONE (1 << 2) #define ID_ARBLOST (1 << 3) #define ID_NACK (1 << 4) @@ -105,10 +108,10 @@ enum rcar_i2c_type { struct rcar_i2c_priv { void __iomem *io; struct i2c_adapter adap; - struct i2c_msg *msg; - int msgs_left; + struct i2c_msg *msg; struct clk *clk; + spinlock_t lock; wait_queue_head_t wait; int pos; @@ -141,10 +144,9 @@ static void rcar_i2c_init(struct rcar_i2c_priv *priv) { /* reset master mode */ rcar_i2c_write(priv, ICMIER, 0); - rcar_i2c_write(priv, ICMCR, MDBS); + rcar_i2c_write(priv, ICMCR, 0); rcar_i2c_write(priv, ICMSR, 0); - /* start clock */ - rcar_i2c_write(priv, ICCCR, priv->icccr); + rcar_i2c_write(priv, ICMAR, 0); } static int rcar_i2c_bus_barrier(struct rcar_i2c_priv *priv) @@ -243,7 +245,9 @@ scgd_find: dev_dbg(dev, "clk %d/%d(%lu), round %u, CDF:0x%x, SCGD: 0x%x\n", scl, bus_speed, clk_get_rate(priv->clk), round, cdf, scgd); - /* keep icccr value */ + /* + * keep icccr value + */ priv->icccr = scgd << cdf_width | cdf; return 0; @@ -253,24 +257,12 @@ static void rcar_i2c_prepare_msg(struct rcar_i2c_priv *priv) { int read = !!rcar_i2c_is_recv(priv); - priv->pos = 0; - priv->flags = 0; - if (priv->msgs_left == 1) - rcar_i2c_flags_set(priv, ID_LAST_MSG); - rcar_i2c_write(priv, ICMAR, (priv->msg->addr << 1) | read); rcar_i2c_write(priv, ICMSR, 0); rcar_i2c_write(priv, ICMCR, RCAR_BUS_PHASE_START); rcar_i2c_write(priv, ICMIER, read ? RCAR_IRQ_RECV : RCAR_IRQ_SEND); } -static void rcar_i2c_next_msg(struct rcar_i2c_priv *priv) -{ - priv->msg++; - priv->msgs_left--; - rcar_i2c_prepare_msg(priv); -} - /* * interrupt functions */ @@ -278,10 +270,21 @@ static int rcar_i2c_irq_send(struct rcar_i2c_priv *priv, u32 msr) { struct i2c_msg *msg = priv->msg; - /* FIXME: sometimes, unknown interrupt happened. Do nothing */ + /* + * FIXME + * sometimes, unknown interrupt happened. + * Do nothing + */ if (!(msr & MDE)) return 0; + /* + * If address transfer phase finished, + * goto data phase. + */ + if (msr & MAT) + rcar_i2c_write(priv, ICMCR, RCAR_BUS_PHASE_DATA); + if (priv->pos < msg->len) { /* * Prepare next data to ICRXTX register. @@ -302,17 +305,21 @@ static int rcar_i2c_irq_send(struct rcar_i2c_priv *priv, u32 msr) * [ICRXTX] -> [SHIFT] -> [I2C bus] */ - if (priv->flags & ID_LAST_MSG) { + if (priv->flags & ID_LAST_MSG) /* * If current msg is the _LAST_ msg, * prepare stop condition here. * ID_DONE will be set on STOP irq. */ rcar_i2c_write(priv, ICMCR, RCAR_BUS_PHASE_STOP); - } else { - rcar_i2c_next_msg(priv); - return 0; - } + else + /* + * If current msg is _NOT_ last msg, + * it doesn't call stop phase. + * thus, there is no STOP irq. + * return ID_DONE here. + */ + return ID_DONE; } rcar_i2c_write(priv, ICMSR, RCAR_IRQ_ACK_SEND); @@ -324,30 +331,39 @@ static int rcar_i2c_irq_recv(struct rcar_i2c_priv *priv, u32 msr) { struct i2c_msg *msg = priv->msg; - /* FIXME: sometimes, unknown interrupt happened. Do nothing */ + /* + * FIXME + * sometimes, unknown interrupt happened. + * Do nothing + */ if (!(msr & MDR)) return 0; if (msr & MAT) { - /* Address transfer phase finished, but no data at this point. */ + /* + * Address transfer phase finished, + * but, there is no data at this point. + * Do nothing. + */ } else if (priv->pos < msg->len) { - /* get received data */ + /* + * get received data + */ msg->buf[priv->pos] = rcar_i2c_read(priv, ICRXTX); priv->pos++; } /* - * If next received data is the _LAST_, go to STOP phase. Might be - * overwritten by REP START when setting up a new msg. Not elegant - * but the only stable sequence for REP START I have found so far. + * If next received data is the _LAST_, + * go to STOP phase, + * otherwise, go to DATA phase. */ if (priv->pos + 1 >= msg->len) rcar_i2c_write(priv, ICMCR, RCAR_BUS_PHASE_STOP); - - if (priv->pos == msg->len && !(priv->flags & ID_LAST_MSG)) - rcar_i2c_next_msg(priv); else - rcar_i2c_write(priv, ICMSR, RCAR_IRQ_ACK_RECV); + rcar_i2c_write(priv, ICMCR, RCAR_BUS_PHASE_DATA); + + rcar_i2c_write(priv, ICMSR, RCAR_IRQ_ACK_RECV); return 0; } @@ -410,21 +426,22 @@ static bool rcar_i2c_slave_irq(struct rcar_i2c_priv *priv) static irqreturn_t rcar_i2c_irq(int irq, void *ptr) { struct rcar_i2c_priv *priv = ptr; - u32 msr, val; + irqreturn_t result = IRQ_HANDLED; + u32 msr; - /* Clear START or STOP as soon as we can */ - val = rcar_i2c_read(priv, ICMCR); - rcar_i2c_write(priv, ICMCR, val & RCAR_BUS_MASK_DATA); + /*-------------- spin lock -----------------*/ + spin_lock(&priv->lock); + + if (rcar_i2c_slave_irq(priv)) + goto exit; msr = rcar_i2c_read(priv, ICMSR); /* Only handle interrupts that are currently enabled */ msr &= rcar_i2c_read(priv, ICMIER); if (!msr) { - if (rcar_i2c_slave_irq(priv)) - return IRQ_HANDLED; - - return IRQ_NONE; + result = IRQ_NONE; + goto exit; } /* Arbitration lost */ @@ -435,7 +452,8 @@ static irqreturn_t rcar_i2c_irq(int irq, void *ptr) /* Nack */ if (msr & MNR) { - /* HW automatically sends STOP after received NACK */ + /* go to stop phase */ + rcar_i2c_write(priv, ICMCR, RCAR_BUS_PHASE_STOP); rcar_i2c_write(priv, ICMIER, RCAR_IRQ_STOP); rcar_i2c_flags_set(priv, ID_NACK); goto out; @@ -443,7 +461,6 @@ static irqreturn_t rcar_i2c_irq(int irq, void *ptr) /* Stop */ if (msr & MST) { - priv->msgs_left--; /* The last message also made it */ rcar_i2c_flags_set(priv, ID_DONE); goto out; } @@ -460,7 +477,11 @@ out: wake_up(&priv->wait); } - return IRQ_HANDLED; +exit: + spin_unlock(&priv->lock); + /*-------------- spin unlock -----------------*/ + + return result; } static int rcar_i2c_master_xfer(struct i2c_adapter *adap, @@ -469,11 +490,22 @@ static int rcar_i2c_master_xfer(struct i2c_adapter *adap, { struct rcar_i2c_priv *priv = i2c_get_adapdata(adap); struct device *dev = rcar_i2c_priv_to_dev(priv); + unsigned long flags; int i, ret; - long time_left; + long timeout; pm_runtime_get_sync(dev); + /*-------------- spin lock -----------------*/ + spin_lock_irqsave(&priv->lock, flags); + + rcar_i2c_init(priv); + /* start clock */ + rcar_i2c_write(priv, ICCCR, priv->icccr); + + spin_unlock_irqrestore(&priv->lock, flags); + /*-------------- spin unlock -----------------*/ + ret = rcar_i2c_bus_barrier(priv); if (ret < 0) goto out; @@ -482,28 +514,48 @@ static int rcar_i2c_master_xfer(struct i2c_adapter *adap, /* This HW can't send STOP after address phase */ if (msgs[i].len == 0) { ret = -EOPNOTSUPP; - goto out; + break; } - } - /* init data */ - priv->msg = msgs; - priv->msgs_left = num; - - rcar_i2c_prepare_msg(priv); - - time_left = wait_event_timeout(priv->wait, - rcar_i2c_flags_has(priv, ID_DONE), - num * adap->timeout); - if (!time_left) { - rcar_i2c_init(priv); - ret = -ETIMEDOUT; - } else if (rcar_i2c_flags_has(priv, ID_NACK)) { - ret = -ENXIO; - } else if (rcar_i2c_flags_has(priv, ID_ARBLOST)) { - ret = -EAGAIN; - } else { - ret = num - priv->msgs_left; /* The number of transfer */ + /*-------------- spin lock -----------------*/ + spin_lock_irqsave(&priv->lock, flags); + + /* init each data */ + priv->msg = &msgs[i]; + priv->pos = 0; + priv->flags = 0; + if (i == num - 1) + rcar_i2c_flags_set(priv, ID_LAST_MSG); + + rcar_i2c_prepare_msg(priv); + + spin_unlock_irqrestore(&priv->lock, flags); + /*-------------- spin unlock -----------------*/ + + timeout = wait_event_timeout(priv->wait, + rcar_i2c_flags_has(priv, ID_DONE), + adap->timeout); + if (!timeout) { + ret = -ETIMEDOUT; + break; + } + + if (rcar_i2c_flags_has(priv, ID_NACK)) { + ret = -ENXIO; + break; + } + + if (rcar_i2c_flags_has(priv, ID_ARBLOST)) { + ret = -EAGAIN; + break; + } + + if (rcar_i2c_flags_has(priv, ID_IOERROR)) { + ret = -EIO; + break; + } + + ret = i + 1; /* The number of transfer */ } out: pm_runtime_put(dev); @@ -612,10 +664,9 @@ static int rcar_i2c_probe(struct platform_device *pdev) if (IS_ERR(priv->io)) return PTR_ERR(priv->io); - rcar_i2c_init(priv); - irq = platform_get_irq(pdev, 0); init_waitqueue_head(&priv->wait); + spin_lock_init(&priv->lock); adap = &priv->adap; adap->nr = pdev->id; -- 2.1.4 -- To unsubscribe from this list: send the line "unsubscribe linux-i2c" in the body of a message to majord...@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html