powerpc/5200/i2c: improve i2c bus error recovery
authorAlbrecht Dreß <albrecht.dress@arcor.de>
Wed, 17 Feb 2010 08:59:14 +0000 (08:59 +0000)
committerGrant Likely <grant.likely@secretlab.ca>
Sun, 1 Aug 2010 23:06:44 +0000 (17:06 -0600)
This patch improves the recovery of the MPC's I2C bus from errors like bus
hangs resulting in timeouts:
1. make the bus timeout configurable, as it depends on the bus clock and
    the attached slave chip(s); default is still 1 second;
2. detect any of the cases indicated by the CF, BB and RXAK MSR flags if a
    timeout occurs, and add a missing (required) MAL reset;
3. use a more reliable method to fixup the bus if a hang has been detected.
    The sequence is sent 9 times which seems to be necessary if a slave
    "misses" more than one clock cycle.  For 400 kHz bus speed, the fixup is
    also ~70us (81us vs. 150us) faster.

Tested on a custom Lite5200b derived board, with a Dallas RTC, AD sensors
and NXP IO expander chips attached to the i2c.

Changes vs. v1:
- use improved bus fixup sequence for all chips (not only the 5200)
- calculate real clock from defaults if no clock is given in the device tree
- better description (I hope) of the changes.

I didn't split the changes in this file into three parts as recommended by
Grant, as they actually belong together (i.e. they address one single
problem, just in three places of one single source file).

Signed-off-by: Albrecht Dreß <albrecht.dress@arcor.de>
[grant.likely@secretlab.ca: fixup for ->node to ->dev.of_node transition]
Signed-off-by: Grant Likely <grant.likely@secretlab.ca>
Documentation/powerpc/dts-bindings/fsl/i2c.txt
drivers/i2c/busses/i2c-mpc.c

index 50da203..1eacd6b 100644 (file)
@@ -20,6 +20,7 @@ Recommended properties :
  - fsl,preserve-clocking : boolean; if defined, the clock settings
    from the bootloader are preserved (not touched).
  - clock-frequency : desired I2C bus clock frequency in Hz.
+ - fsl,timeout : I2C bus timeout in microseconds.
 
 Examples :
 
@@ -59,4 +60,5 @@ Examples :
                interrupts = <43 2>;
                interrupt-parent = <&mpic>;
                clock-frequency = <400000>;
+               fsl,timeout = <10000>;
        };
index df00eb1..54247d4 100644 (file)
@@ -63,6 +63,7 @@ struct mpc_i2c {
        wait_queue_head_t queue;
        struct i2c_adapter adap;
        int irq;
+       u32 real_clk;
 };
 
 struct mpc_i2c_divider {
@@ -96,20 +97,23 @@ static irqreturn_t mpc_i2c_isr(int irq, void *dev_id)
 /* Sometimes 9th clock pulse isn't generated, and slave doesn't release
  * the bus, because it wants to send ACK.
  * Following sequence of enabling/disabling and sending start/stop generates
- * the pulse, so it's all OK.
+ * the 9 pulses, so it's all OK.
  */
 static void mpc_i2c_fixup(struct mpc_i2c *i2c)
 {
-       writeccr(i2c, 0);
-       udelay(30);
-       writeccr(i2c, CCR_MEN);
-       udelay(30);
-       writeccr(i2c, CCR_MSTA | CCR_MTX);
-       udelay(30);
-       writeccr(i2c, CCR_MSTA | CCR_MTX | CCR_MEN);
-       udelay(30);
-       writeccr(i2c, CCR_MEN);
-       udelay(30);
+       int k;
+       u32 delay_val = 1000000 / i2c->real_clk + 1;
+
+       if (delay_val < 2)
+               delay_val = 2;
+
+       for (k = 9; k; k--) {
+               writeccr(i2c, 0);
+               writeccr(i2c, CCR_MSTA | CCR_MTX | CCR_MEN);
+               udelay(delay_val);
+               writeccr(i2c, CCR_MEN);
+               udelay(delay_val << 1);
+       }
 }
 
 static int i2c_wait(struct mpc_i2c *i2c, unsigned timeout, int writing)
@@ -190,15 +194,18 @@ static const struct mpc_i2c_divider mpc_i2c_dividers_52xx[] __devinitconst = {
 };
 
 static int __devinit mpc_i2c_get_fdr_52xx(struct device_node *node, u32 clock,
-                                         int prescaler)
+                                         int prescaler, u32 *real_clk)
 {
        const struct mpc_i2c_divider *div = NULL;
        unsigned int pvr = mfspr(SPRN_PVR);
        u32 divider;
        int i;
 
-       if (clock == MPC_I2C_CLOCK_LEGACY)
+       if (clock == MPC_I2C_CLOCK_LEGACY) {
+               /* see below - default fdr = 0x3f -> div = 2048 */
+               *real_clk = mpc5xxx_get_bus_frequency(node) / 2048;
                return -EINVAL;
+       }
 
        /* Determine divider value */
        divider = mpc5xxx_get_bus_frequency(node) / clock;
@@ -216,7 +223,8 @@ static int __devinit mpc_i2c_get_fdr_52xx(struct device_node *node, u32 clock,
                        break;
        }
 
-       return div ? (int)div->fdr : -EINVAL;
+       *real_clk = mpc5xxx_get_bus_frequency(node) / div->divider;
+       return (int)div->fdr;
 }
 
 static void __devinit mpc_i2c_setup_52xx(struct device_node *node,
@@ -231,13 +239,14 @@ static void __devinit mpc_i2c_setup_52xx(struct device_node *node,
                return;
        }
 
-       ret = mpc_i2c_get_fdr_52xx(node, clock, prescaler);
+       ret = mpc_i2c_get_fdr_52xx(node, clock, prescaler, &i2c->real_clk);
        fdr = (ret >= 0) ? ret : 0x3f; /* backward compatibility */
 
        writeb(fdr & 0xff, i2c->base + MPC_I2C_FDR);
 
        if (ret >= 0)
-               dev_info(i2c->dev, "clock %d Hz (fdr=%d)\n", clock, fdr);
+               dev_info(i2c->dev, "clock %u Hz (fdr=%d)\n", i2c->real_clk,
+                        fdr);
 }
 #else /* !(CONFIG_PPC_MPC52xx || CONFIG_PPC_MPC512x) */
 static void __devinit mpc_i2c_setup_52xx(struct device_node *node,
@@ -334,14 +343,17 @@ static u32 __devinit mpc_i2c_get_sec_cfg_8xxx(void)
 }
 
 static int __devinit mpc_i2c_get_fdr_8xxx(struct device_node *node, u32 clock,
-                                         u32 prescaler)
+                                         u32 prescaler, u32 *real_clk)
 {
        const struct mpc_i2c_divider *div = NULL;
        u32 divider;
        int i;
 
-       if (clock == MPC_I2C_CLOCK_LEGACY)
+       if (clock == MPC_I2C_CLOCK_LEGACY) {
+               /* see below - default fdr = 0x1031 -> div = 16 * 3072 */
+               *real_clk = fsl_get_sys_freq() / prescaler / (16 * 3072);
                return -EINVAL;
+       }
 
        /* Determine proper divider value */
        if (of_device_is_compatible(node, "fsl,mpc8544-i2c"))
@@ -364,6 +376,7 @@ static int __devinit mpc_i2c_get_fdr_8xxx(struct device_node *node, u32 clock,
                        break;
        }
 
+       *real_clk = fsl_get_sys_freq() / prescaler / div->divider;
        return div ? (int)div->fdr : -EINVAL;
 }
 
@@ -380,7 +393,7 @@ static void __devinit mpc_i2c_setup_8xxx(struct device_node *node,
                return;
        }
 
-       ret = mpc_i2c_get_fdr_8xxx(node, clock, prescaler);
+       ret = mpc_i2c_get_fdr_8xxx(node, clock, prescaler, &i2c->real_clk);
        fdr = (ret >= 0) ? ret : 0x1031; /* backward compatibility */
 
        writeb(fdr & 0xff, i2c->base + MPC_I2C_FDR);
@@ -388,7 +401,7 @@ static void __devinit mpc_i2c_setup_8xxx(struct device_node *node,
 
        if (ret >= 0)
                dev_info(i2c->dev, "clock %d Hz (dfsrr=%d fdr=%d)\n",
-                        clock, fdr >> 8, fdr & 0xff);
+                        i2c->real_clk, fdr >> 8, fdr & 0xff);
 }
 
 #else /* !CONFIG_FSL_SOC */
@@ -500,10 +513,14 @@ static int mpc_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
                        return -EINTR;
                }
                if (time_after(jiffies, orig_jiffies + HZ)) {
+                       u8 status = readb(i2c->base + MPC_I2C_SR);
+
                        dev_dbg(i2c->dev, "timeout\n");
-                       if (readb(i2c->base + MPC_I2C_SR) ==
-                           (CSR_MCF | CSR_MBB | CSR_RXAK))
+                       if ((status & (CSR_MCF | CSR_MBB | CSR_RXAK)) != 0) {
+                               writeb(status & ~CSR_MAL,
+                                      i2c->base + MPC_I2C_SR);
                                mpc_i2c_fixup(i2c);
+                       }
                        return -EIO;
                }
                schedule();
@@ -595,6 +612,14 @@ static int __devinit fsl_i2c_probe(struct of_device *op,
                        mpc_i2c_setup_8xxx(op->dev.of_node, i2c, clock, 0);
        }
 
+       prop = of_get_property(op->dev.of_node, "fsl,timeout", &plen);
+       if (prop && plen == sizeof(u32)) {
+               mpc_ops.timeout = *prop * HZ / 1000000;
+               if (mpc_ops.timeout < 5)
+                       mpc_ops.timeout = 5;
+       }
+       dev_info(i2c->dev, "timeout %u us\n", mpc_ops.timeout * 1000000 / HZ);
+
        dev_set_drvdata(&op->dev, i2c);
 
        i2c->adap = mpc_ops;