]> www.pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - drivers/i2c/busses/i2c-omap.c
i2c-omap: fix I2C timeouts due to recursive omap_i2c_{un,}idle()
[linux-2.6-omap-h63xx.git] / drivers / i2c / busses / i2c-omap.c
index 9903e35d0b20966b3245f78869ff7b4873fd5111..a9996063483e9c269c8a2e45e8e737ed0f923e33 100644 (file)
@@ -9,8 +9,9 @@
  * Additional contributions by:
  *     Tony Lindgren <tony@atomide.com>
  *     Imre Deak <imre.deak@nokia.com>
- *     Juha Yrjölä <juha.yrjola@nokia.com>
+ *     Juha Yrjölä <juha.yrjola@solidboot.com>
  *     Syed Khasim <x0khasim@ti.com>
+ *     Nishant Menon <nm@ti.com>
  *
  * 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
 #include <linux/completion.h>
 #include <linux/platform_device.h>
 #include <linux/clk.h>
-
-#include <asm/io.h>
-
-/* Hack to enable zero length transfers and smbus quick until clean fix
-   is available */
-#define OMAP_HACK
+#include <linux/io.h>
 
 /* timeout waiting for the controller to respond */
 #define OMAP_I2C_TIMEOUT (msecs_to_jiffies(1000))
@@ -64,8 +60,8 @@
 #define OMAP_I2C_BUFSTAT_REG           0x40
 
 /* I2C Interrupt Enable Register (OMAP_I2C_IE): */
-#define OMAP_I2C_IE_XDR                (1 << 14)       /* TX Buffer draining int enable */
-#define OMAP_I2C_IE_RDR                (1 << 13)       /* RX Buffer draining int enable */
+#define OMAP_I2C_IE_XDR                (1 << 14)       /* TX Buffer drain int enable */
+#define OMAP_I2C_IE_RDR                (1 << 13)       /* RX Buffer drain int enable */
 #define OMAP_I2C_IE_XRDY       (1 << 4)        /* TX data ready int enable */
 #define OMAP_I2C_IE_RRDY       (1 << 3)        /* RX data ready int enable */
 #define OMAP_I2C_IE_ARDY       (1 << 2)        /* Access ready int enable */
@@ -145,6 +141,8 @@ struct omap_i2c_dev {
                                                 */
        unsigned                rev1:1;
        unsigned                b_hw:1;         /* bad h/w fixes */
+       unsigned                idle:1;
+       u16                     iestate;        /* Saved interrupt register */
 };
 
 static inline void omap_i2c_write_reg(struct omap_i2c_dev *i2c_dev,
@@ -158,7 +156,7 @@ static inline u16 omap_i2c_read_reg(struct omap_i2c_dev *i2c_dev, int reg)
        return __raw_readw(i2c_dev->base + reg);
 }
 
-static int omap_i2c_get_clocks(struct omap_i2c_dev *dev)
+static int __init omap_i2c_get_clocks(struct omap_i2c_dev *dev)
 {
        if (cpu_is_omap16xx() || cpu_class_is_omap2()) {
                dev->iclk = clk_get(dev->dev, "i2c_ick");
@@ -202,18 +200,40 @@ static void omap_i2c_put_clocks(struct omap_i2c_dev *dev)
        }
 }
 
-static void omap_i2c_enable_clocks(struct omap_i2c_dev *dev)
+static void omap_i2c_unidle(struct omap_i2c_dev *dev)
 {
+       WARN_ON(!dev->idle);
+
        if (dev->iclk != NULL)
                clk_enable(dev->iclk);
        clk_enable(dev->fclk);
+       dev->idle = 0;
+       if (dev->iestate)
+               omap_i2c_write_reg(dev, OMAP_I2C_IE_REG, dev->iestate);
 }
 
-static void omap_i2c_disable_clocks(struct omap_i2c_dev *dev)
+static void omap_i2c_idle(struct omap_i2c_dev *dev)
 {
+       u16 iv;
+
+       WARN_ON(dev->idle);
+
+       dev->iestate = omap_i2c_read_reg(dev, OMAP_I2C_IE_REG);
+       omap_i2c_write_reg(dev, OMAP_I2C_IE_REG, 0);
+       if (dev->rev1)
+               iv = omap_i2c_read_reg(dev, OMAP_I2C_IV_REG); /* Read clears */
+       else
+               omap_i2c_write_reg(dev, OMAP_I2C_STAT_REG, dev->iestate);
+       /*
+        * The wmb() is to ensure that the I2C interrupt mask write
+        * reaches the I2C controller before the dev->idle store
+        * occurs.
+        */
+       wmb();
+       dev->idle = 1;
+       clk_disable(dev->fclk);
        if (dev->iclk != NULL)
                clk_disable(dev->iclk);
-       clk_disable(dev->fclk);
 }
 
 static int omap_i2c_init(struct omap_i2c_dev *dev)
@@ -353,16 +373,12 @@ static int omap_i2c_xfer_msg(struct i2c_adapter *adap,
                             struct i2c_msg *msg, int stop)
 {
        struct omap_i2c_dev *dev = i2c_get_adapdata(adap);
-#ifdef OMAP_HACK
-       u8 zero_byte = 0;
-#endif
        int r;
        u16 w;
 
        dev_dbg(dev->dev, "addr: 0x%04x, len: %d, flags: 0x%x, stop: %d\n",
                msg->addr, msg->len, msg->flags, stop);
 
-#ifndef OMAP_HACK
        if (msg->len == 0)
                return -EINVAL;
 
@@ -372,27 +388,6 @@ static int omap_i2c_xfer_msg(struct i2c_adapter *adap,
        dev->buf = msg->buf;
        dev->buf_len = msg->len;
 
-#else
-
-       omap_i2c_write_reg(dev, OMAP_I2C_SA_REG, msg->addr);
-       /* REVISIT: Remove this hack when we can get I2C chips from board-*.c
-        *          files
-        * Sigh, seems we can't do zero length transactions. Thus, we
-        * can't probe for devices w/o actually sending/receiving at least
-        * a single byte. So we'll set count to 1 for the zero length
-        * transaction case and hope we don't cause grief for some
-        * arbitrary device due to random byte write/read during
-        * probes.
-        */
-       if (msg->len == 0) {
-               dev->buf = &zero_byte;
-               dev->buf_len = 1;
-       } else {
-               dev->buf = msg->buf;
-               dev->buf_len = msg->len;
-       }
-#endif
-
        omap_i2c_write_reg(dev, OMAP_I2C_CNT_REG, dev->buf_len);
 
        /* Clear the FIFO Buffers */
@@ -419,11 +414,24 @@ static int omap_i2c_xfer_msg(struct i2c_adapter *adap,
 
        omap_i2c_write_reg(dev, OMAP_I2C_CON_REG, w);
 
+       /*
+        * Don't write stt and stp together on some hardware
+        */
        if (dev->b_hw && stop) {
-               /* H/w behavior: dont write stt and stp together.. */
-               while (omap_i2c_read_reg(dev, OMAP_I2C_CON_REG) & OMAP_I2C_CON_STT) {
-                       /* Dont do anything - this will come in a couple of loops at max*/
+               unsigned long delay = jiffies + OMAP_I2C_TIMEOUT;
+               u16 con = omap_i2c_read_reg(dev, OMAP_I2C_CON_REG);
+               while (con & OMAP_I2C_CON_STT) {
+                       con = omap_i2c_read_reg(dev, OMAP_I2C_CON_REG);
+
+                       /* Let the user know if i2c is in a bad state */
+                       if (time_after(jiffies, delay)) {
+                               dev_err(dev->dev, "controller timed out "
+                               "waiting for start condition to finish\n");
+                               return -ETIMEDOUT;
+                       }
+                       cpu_relax();
                }
+
                w |= OMAP_I2C_CON_STP;
                w &= ~OMAP_I2C_CON_STT;
                omap_i2c_write_reg(dev, OMAP_I2C_CON_REG, w);
@@ -474,9 +482,10 @@ omap_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
        int i;
        int r;
 
-       omap_i2c_enable_clocks(dev);
+       omap_i2c_unidle(dev);
 
-       if ((r = omap_i2c_wait_for_bb(dev)) < 0)
+       r = omap_i2c_wait_for_bb(dev);
+       if (r < 0)
                goto out;
 
        for (i = 0; i < num; i++) {
@@ -488,18 +497,14 @@ omap_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
        if (r == 0)
                r = num;
 out:
-       omap_i2c_disable_clocks(dev);
+       omap_i2c_idle(dev);
        return r;
 }
 
 static u32
 omap_i2c_func(struct i2c_adapter *adap)
 {
-#ifndef OMAP_HACK
        return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK);
-#else
-       return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
-#endif
 }
 
 static inline void
@@ -515,12 +520,18 @@ omap_i2c_ack_stat(struct omap_i2c_dev *dev, u16 stat)
        omap_i2c_write_reg(dev, OMAP_I2C_STAT_REG, stat);
 }
 
+/* rev1 devices are apparently only on some 15xx */
+#ifdef CONFIG_ARCH_OMAP15XX
+
 static irqreturn_t
 omap_i2c_rev1_isr(int this_irq, void *dev_id)
 {
        struct omap_i2c_dev *dev = dev_id;
        u16 iv, w;
 
+       if (dev->idle)
+               return IRQ_NONE;
+
        iv = omap_i2c_read_reg(dev, OMAP_I2C_IV_REG);
        switch (iv) {
        case 0x00:      /* None */
@@ -566,6 +577,9 @@ omap_i2c_rev1_isr(int this_irq, void *dev_id)
 
        return IRQ_HANDLED;
 }
+#else
+#define omap_i2c_rev1_isr              0
+#endif
 
 static irqreturn_t
 omap_i2c_isr(int this_irq, void *dev_id)
@@ -573,7 +587,10 @@ omap_i2c_isr(int this_irq, void *dev_id)
        struct omap_i2c_dev *dev = dev_id;
        u16 bits;
        u16 stat, w;
-       int count = 0;
+       int err, count = 0;
+
+       if (dev->idle)
+               return IRQ_NONE;
 
        bits = omap_i2c_read_reg(dev, OMAP_I2C_IE_REG);
        while ((stat = (omap_i2c_read_reg(dev, OMAP_I2C_STAT_REG))) & bits) {
@@ -585,15 +602,27 @@ omap_i2c_isr(int this_irq, void *dev_id)
 
                omap_i2c_write_reg(dev, OMAP_I2C_STAT_REG, stat);
 
-               if (stat & OMAP_I2C_STAT_ARDY) {
-                       omap_i2c_complete_cmd(dev, 0);
-                       continue;
+               err = 0;
+               if (stat & OMAP_I2C_STAT_NACK) {
+                       err |= OMAP_I2C_STAT_NACK;
+                       omap_i2c_write_reg(dev, OMAP_I2C_CON_REG,
+                                          OMAP_I2C_CON_STP);
                }
+               if (stat & OMAP_I2C_STAT_AL) {
+                       dev_err(dev->dev, "Arbitration lost\n");
+                       err |= OMAP_I2C_STAT_AL;
+               }
+               if (stat & (OMAP_I2C_STAT_ARDY | OMAP_I2C_STAT_NACK |
+                                       OMAP_I2C_STAT_AL))
+                       omap_i2c_complete_cmd(dev, err);
                if (stat & (OMAP_I2C_STAT_RRDY | OMAP_I2C_STAT_RDR)) {
                        u8 num_bytes = 1;
                        if (dev->fifo_size) {
-                               num_bytes = (stat & OMAP_I2C_STAT_RRDY) ? dev->fifo_size :
-                                               omap_i2c_read_reg(dev, OMAP_I2C_BUFSTAT_REG);
+                               if (stat & OMAP_I2C_STAT_RRDY)
+                                       num_bytes = dev->fifo_size;
+                               else
+                                       num_bytes = omap_i2c_read_reg(dev,
+                                                       OMAP_I2C_BUFSTAT_REG);
                        }
                        while (num_bytes) {
                                num_bytes--;
@@ -611,22 +640,28 @@ omap_i2c_isr(int this_irq, void *dev_id)
                                        }
                                } else {
                                        if (stat & OMAP_I2C_STAT_RRDY)
-                                               dev_err(dev->dev, "RRDY IRQ while no data "
-                                                               "requested\n");
+                                               dev_err(dev->dev,
+                                                       "RRDY IRQ while no data"
+                                                               " requested\n");
                                        if (stat & OMAP_I2C_STAT_RDR)
-                                               dev_err(dev->dev, "RDR IRQ while no data "
-                                                               "requested\n");
+                                               dev_err(dev->dev,
+                                                       "RDR IRQ while no data"
+                                                               " requested\n");
                                        break;
                                }
                        }
-                       omap_i2c_ack_stat(dev, stat & (OMAP_I2C_STAT_RRDY | OMAP_I2C_STAT_RDR));
+                       omap_i2c_ack_stat(dev,
+                               stat & (OMAP_I2C_STAT_RRDY | OMAP_I2C_STAT_RDR));
                        continue;
                }
                if (stat & (OMAP_I2C_STAT_XRDY | OMAP_I2C_STAT_XDR)) {
                        u8 num_bytes = 1;
                        if (dev->fifo_size) {
-                               num_bytes = (stat & OMAP_I2C_STAT_XRDY) ? dev->fifo_size :
-                                               omap_i2c_read_reg(dev, OMAP_I2C_BUFSTAT_REG);
+                               if (stat & OMAP_I2C_STAT_XRDY)
+                                       num_bytes = dev->fifo_size;
+                               else
+                                       num_bytes = omap_i2c_read_reg(dev,
+                                                       OMAP_I2C_BUFSTAT_REG);
                        }
                        while (num_bytes) {
                                num_bytes--;
@@ -644,16 +679,19 @@ omap_i2c_isr(int this_irq, void *dev_id)
                                        }
                                } else {
                                        if (stat & OMAP_I2C_STAT_XRDY)
-                                               dev_err(dev->dev, "XRDY IRQ while no "
-                                                               "data to send\n");
+                                               dev_err(dev->dev,
+                                                       "XRDY IRQ while no "
+                                                       "data to send\n");
                                        if (stat & OMAP_I2C_STAT_XDR)
-                                               dev_err(dev->dev, "XDR IRQ while no "
-                                                               "data to send\n");
+                                               dev_err(dev->dev,
+                                                       "XDR IRQ while no "
+                                                       "data to send\n");
                                        break;
                                }
                                omap_i2c_write_reg(dev, OMAP_I2C_DATA_REG, w);
                        }
-                       omap_i2c_ack_stat(dev, stat & (OMAP_I2C_STAT_XRDY | OMAP_I2C_STAT_XDR));
+                       omap_i2c_ack_stat(dev,
+                               stat & (OMAP_I2C_STAT_XRDY | OMAP_I2C_STAT_XDR));
                        continue;
                }
                if (stat & OMAP_I2C_STAT_ROVR) {
@@ -661,18 +699,9 @@ omap_i2c_isr(int this_irq, void *dev_id)
                        dev->cmd_err |= OMAP_I2C_STAT_ROVR;
                }
                if (stat & OMAP_I2C_STAT_XUDF) {
-                       dev_err(dev->dev, "Transmit overflow\n");
+                       dev_err(dev->dev, "Transmit underflow\n");
                        dev->cmd_err |= OMAP_I2C_STAT_XUDF;
                }
-               if (stat & OMAP_I2C_STAT_NACK) {
-                       omap_i2c_complete_cmd(dev, OMAP_I2C_STAT_NACK);
-                       omap_i2c_write_reg(dev, OMAP_I2C_CON_REG,
-                                          OMAP_I2C_CON_STP);
-               }
-               if (stat & OMAP_I2C_STAT_AL) {
-                       dev_err(dev->dev, "Arbitration lost\n");
-                       omap_i2c_complete_cmd(dev, OMAP_I2C_STAT_AL);
-               }
        }
 
        return count ? IRQ_HANDLED : IRQ_NONE;
@@ -683,7 +712,7 @@ static const struct i2c_algorithm omap_i2c_algo = {
        .functionality  = omap_i2c_func,
 };
 
-static int
+static int __init
 omap_i2c_probe(struct platform_device *pdev)
 {
        struct omap_i2c_dev     *dev;
@@ -723,27 +752,37 @@ omap_i2c_probe(struct platform_device *pdev)
                *speed = 100; /* Defualt speed */
 
        dev->speed = *speed;
+       dev->idle = 1;
        dev->dev = &pdev->dev;
        dev->irq = irq->start;
-       dev->base = (void __iomem *) IO_ADDRESS(mem->start);
+       dev->base = ioremap(mem->start, mem->end - mem->start + 1);
+       if (!dev->base) {
+               r = -ENOMEM;
+               goto err_free_mem;
+       }
+
        platform_set_drvdata(pdev, dev);
 
-       if ((r = omap_i2c_get_clocks(dev)) != 0)
-               goto err_free_mem;
+       r = omap_i2c_get_clocks(dev);
+       if (r != 0)
+               goto err_iounmap;
 
-       omap_i2c_enable_clocks(dev);
+       omap_i2c_unidle(dev);
 
        if (cpu_is_omap15xx())
                dev->rev1 = omap_i2c_read_reg(dev, OMAP_I2C_REV_REG) < 0x20;
 
        if (cpu_is_omap2430() || cpu_is_omap34xx()) {
+               u16 s;
+
                /* Set up the fifo size - Get total size */
-               dev->fifo_size = 0x8 <<
-                       ((omap_i2c_read_reg(dev, OMAP_I2C_BUFSTAT_REG) >> 14) & 0x3);
+               s = (omap_i2c_read_reg(dev, OMAP_I2C_BUFSTAT_REG) >> 14) & 0x3;
+               dev->fifo_size = 0x8 << s;
+
                /*
-                * Set up notification threshold as half the total available size
-                * This is to ensure that we can handle the status on int call back
-                * latencies
+                * Set up notification threshold as half the total available
+                * size. This is to ensure that we can handle the status on int
+                * call back latencies.
                 */
                dev->fifo_size = (dev->fifo_size / 2);
                dev->b_hw = 1; /* Enable hardware fixes */
@@ -763,6 +802,8 @@ omap_i2c_probe(struct platform_device *pdev)
        dev_info(dev->dev, "bus %d rev%d.%d at %d kHz\n",
                 pdev->id, r >> 4, r & 0xf, dev->speed);
 
+       omap_i2c_idle(dev);
+
        adap = &dev->adapter;
        i2c_set_adapdata(adap, dev);
        adap->owner = THIS_MODULE;
@@ -779,16 +820,16 @@ omap_i2c_probe(struct platform_device *pdev)
                goto err_free_irq;
        }
 
-       omap_i2c_disable_clocks(dev);
-
        return 0;
 
 err_free_irq:
        free_irq(dev->irq, dev);
 err_unuse_clocks:
        omap_i2c_write_reg(dev, OMAP_I2C_CON_REG, 0);
-       omap_i2c_disable_clocks(dev);
+       omap_i2c_idle(dev);
        omap_i2c_put_clocks(dev);
+err_iounmap:
+       iounmap(dev->base);
 err_free_mem:
        platform_set_drvdata(pdev, NULL);
        kfree(dev);
@@ -810,6 +851,7 @@ omap_i2c_remove(struct platform_device *pdev)
        i2c_del_adapter(&dev->adapter);
        omap_i2c_write_reg(dev, OMAP_I2C_CON_REG, 0);
        omap_i2c_put_clocks(dev);
+       iounmap(dev->base);
        kfree(dev);
        mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
        release_mem_region(mem->start, (mem->end - mem->start) + 1);
@@ -826,14 +868,14 @@ static struct platform_driver omap_i2c_driver = {
 };
 
 /* I2C may be needed to bring up other drivers */
-static int __init
+static int __devinit
 omap_i2c_init_driver(void)
 {
        return platform_driver_register(&omap_i2c_driver);
 }
 subsys_initcall(omap_i2c_init_driver);
 
-static void __exit omap_i2c_exit_driver(void)
+static void __devexit omap_i2c_exit_driver(void)
 {
        platform_driver_unregister(&omap_i2c_driver);
 }
@@ -842,3 +884,4 @@ module_exit(omap_i2c_exit_driver);
 MODULE_AUTHOR("MontaVista Software, Inc. (and others)");
 MODULE_DESCRIPTION("TI OMAP I2C bus adapter");
 MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:i2c_omap");