i2c: mxs: remove races in PIO code
[linux-2.6-block.git] / drivers / i2c / busses / i2c-mxs.c
index 120f24646696783791365fb2fe785e1535dbc07c..0ec1f1502def98ced972f8a98e21c0200412cf48 100644 (file)
 #define MXS_I2C_CTRL1_SLAVE_STOP_IRQ           0x02
 #define MXS_I2C_CTRL1_SLAVE_IRQ                        0x01
 
+#define MXS_I2C_STAT           (0x50)
+#define MXS_I2C_STAT_BUS_BUSY                  0x00000800
+#define MXS_I2C_STAT_CLK_GEN_BUSY              0x00000400
+
 #define MXS_I2C_DATA           (0xa0)
 
 #define MXS_I2C_DEBUG0         (0xb0)
@@ -297,12 +301,10 @@ static int mxs_i2c_pio_wait_dmareq(struct mxs_i2c_dev *i2c)
                cond_resched();
        }
 
-       writel(MXS_I2C_DEBUG0_DMAREQ, i2c->regs + MXS_I2C_DEBUG0_CLR);
-
        return 0;
 }
 
-static int mxs_i2c_pio_wait_cplt(struct mxs_i2c_dev *i2c)
+static int mxs_i2c_pio_wait_cplt(struct mxs_i2c_dev *i2c, int last)
 {
        unsigned long timeout = jiffies + msecs_to_jiffies(1000);
 
@@ -323,9 +325,33 @@ static int mxs_i2c_pio_wait_cplt(struct mxs_i2c_dev *i2c)
        writel(MXS_I2C_CTRL1_DATA_ENGINE_CMPLT_IRQ,
                i2c->regs + MXS_I2C_CTRL1_CLR);
 
+       /*
+        * When ending a transfer with a stop, we have to wait for the bus to
+        * go idle before we report the transfer as completed. Otherwise the
+        * start of the next transfer may race with the end of the current one.
+        */
+       while (last && (readl(i2c->regs + MXS_I2C_STAT) &
+                       (MXS_I2C_STAT_BUS_BUSY | MXS_I2C_STAT_CLK_GEN_BUSY))) {
+               if (time_after(jiffies, timeout))
+                       return -ETIMEDOUT;
+               cond_resched();
+       }
+
        return 0;
 }
 
+static void mxs_i2c_pio_trigger_cmd(struct mxs_i2c_dev *i2c, u32 cmd)
+{
+       u32 reg;
+
+       writel(cmd, i2c->regs + MXS_I2C_CTRL0);
+
+       /* readback makes sure the write is latched into hardware */
+       reg = readl(i2c->regs + MXS_I2C_CTRL0);
+       reg |= MXS_I2C_CTRL0_RUN;
+       writel(reg, i2c->regs + MXS_I2C_CTRL0);
+}
+
 static int mxs_i2c_pio_setup_xfer(struct i2c_adapter *adap,
                        struct i2c_msg *msg, uint32_t flags)
 {
@@ -341,23 +367,23 @@ static int mxs_i2c_pio_setup_xfer(struct i2c_adapter *adap,
                addr_data |= I2C_SMBUS_READ;
 
                /* SELECT command. */
-               writel(MXS_I2C_CTRL0_RUN | MXS_CMD_I2C_SELECT,
-                       i2c->regs + MXS_I2C_CTRL0);
+               mxs_i2c_pio_trigger_cmd(i2c, MXS_CMD_I2C_SELECT);
 
                ret = mxs_i2c_pio_wait_dmareq(i2c);
                if (ret)
                        return ret;
 
                writel(addr_data, i2c->regs + MXS_I2C_DATA);
+               writel(MXS_I2C_DEBUG0_DMAREQ, i2c->regs + MXS_I2C_DEBUG0_CLR);
 
-               ret = mxs_i2c_pio_wait_cplt(i2c);
+               ret = mxs_i2c_pio_wait_cplt(i2c, 0);
                if (ret)
                        return ret;
 
                /* READ command. */
-               writel(MXS_I2C_CTRL0_RUN | MXS_CMD_I2C_READ | flags |
-                       MXS_I2C_CTRL0_XFER_COUNT(msg->len),
-                       i2c->regs + MXS_I2C_CTRL0);
+               mxs_i2c_pio_trigger_cmd(i2c,
+                                       MXS_CMD_I2C_READ | flags |
+                                       MXS_I2C_CTRL0_XFER_COUNT(msg->len));
 
                for (i = 0; i < msg->len; i++) {
                        if ((i & 3) == 0) {
@@ -365,6 +391,8 @@ static int mxs_i2c_pio_setup_xfer(struct i2c_adapter *adap,
                                if (ret)
                                        return ret;
                                data = readl(i2c->regs + MXS_I2C_DATA);
+                               writel(MXS_I2C_DEBUG0_DMAREQ,
+                                      i2c->regs + MXS_I2C_DEBUG0_CLR);
                        }
                        msg->buf[i] = data & 0xff;
                        data >>= 8;
@@ -373,9 +401,9 @@ static int mxs_i2c_pio_setup_xfer(struct i2c_adapter *adap,
                addr_data |= I2C_SMBUS_WRITE;
 
                /* WRITE command. */
-               writel(MXS_I2C_CTRL0_RUN | MXS_CMD_I2C_WRITE | flags |
-                       MXS_I2C_CTRL0_XFER_COUNT(msg->len + 1),
-                       i2c->regs + MXS_I2C_CTRL0);
+               mxs_i2c_pio_trigger_cmd(i2c,
+                                       MXS_CMD_I2C_WRITE | flags |
+                                       MXS_I2C_CTRL0_XFER_COUNT(msg->len + 1));
 
                /*
                 * The LSB of data buffer is the first byte blasted across
@@ -391,6 +419,8 @@ static int mxs_i2c_pio_setup_xfer(struct i2c_adapter *adap,
                                if (ret)
                                        return ret;
                                writel(data, i2c->regs + MXS_I2C_DATA);
+                               writel(MXS_I2C_DEBUG0_DMAREQ,
+                                      i2c->regs + MXS_I2C_DEBUG0_CLR);
                        }
                }
 
@@ -401,10 +431,12 @@ static int mxs_i2c_pio_setup_xfer(struct i2c_adapter *adap,
                        if (ret)
                                return ret;
                        writel(data, i2c->regs + MXS_I2C_DATA);
+                       writel(MXS_I2C_DEBUG0_DMAREQ,
+                              i2c->regs + MXS_I2C_DEBUG0_CLR);
                }
        }
 
-       ret = mxs_i2c_pio_wait_cplt(i2c);
+       ret = mxs_i2c_pio_wait_cplt(i2c, flags & MXS_I2C_CTRL0_POST_SEND_STOP);
        if (ret)
                return ret;
 
@@ -686,11 +718,8 @@ static int mxs_i2c_probe(struct platform_device *pdev)
 static int mxs_i2c_remove(struct platform_device *pdev)
 {
        struct mxs_i2c_dev *i2c = platform_get_drvdata(pdev);
-       int ret;
 
-       ret = i2c_del_adapter(&i2c->adapter);
-       if (ret)
-               return -EBUSY;
+       i2c_del_adapter(&i2c->adapter);
 
        if (i2c->dmach)
                dma_release_channel(i2c->dmach);