mxs: i2c: Implement algorithm to set up arbitrary i2c speed
authorMarek Vasut <marex@denx.de>
Fri, 30 Nov 2012 18:17:07 +0000 (18:17 +0000)
committerTom Rini <trini@ti.com>
Tue, 11 Dec 2012 20:17:30 +0000 (13:17 -0700)
This algorithm computes the values of TIMING{0,1,2} registers for the
MX28 I2C block. This algorithm was derived by using a scope, but the
result seems correct.

The resulting values programmed into the registers do not correlate
with the contents in datasheet. When using the values from the datasheet,
the I2C clock were completely wrong.

Signed-off-by: Marek Vasut <marex@denx.de>
Cc: Stefano Babic <sbabic@denx.de>
Cc: Fabio Estevam <fabio.estevam@freescale.com>
Cc: Wolfgang Denk <wd@denx.de>
arch/arm/cpu/arm926ejs/mxs/clock.c
arch/arm/include/asm/arch-mxs/clock.h
drivers/i2c/mxs_i2c.c

index bfea6abeb0714e38dc02351bb32fd6ec7031fef2..4ff19c37efe91ac7247f2b48c0d78097de9fd9e1 100644 (file)
@@ -333,6 +333,8 @@ uint32_t mxc_get_clock(enum mxc_clock clk)
                return mx28_get_sspclk(MXC_SSPCLK2);
        case MXC_SSP3_CLK:
                return mx28_get_sspclk(MXC_SSPCLK3);
+       case MXC_XTAL_CLK:
+               return XTAL_FREQ_KHZ * 1000;
        }
 
        return 0;
index 1700fe39155f7a8fd982a38237f72b2eeb0e2a4e..3d39ef235d611ce17c3f466db61ce667e734bc99 100644 (file)
@@ -35,6 +35,7 @@ enum mxc_clock {
        MXC_SSP1_CLK,
        MXC_SSP2_CLK,
        MXC_SSP3_CLK,
+       MXC_XTAL_CLK,
 };
 
 enum mxs_ioclock {
index 73a6659dd4102fa53bc6d3d1dfbdca519245e9b1..b907f7b379bb6c6bb02aabffdff363f4560bc23c 100644 (file)
@@ -28,6 +28,7 @@
 
 #include <common.h>
 #include <malloc.h>
+#include <i2c.h>
 #include <asm/errno.h>
 #include <asm/io.h>
 #include <asm/arch/clock.h>
@@ -213,62 +214,39 @@ int i2c_probe(uchar chip)
        return ret;
 }
 
-static struct mxs_i2c_speed_table {
-       uint32_t        speed;
-       uint32_t        timing0;
-       uint32_t        timing1;
-} mxs_i2c_tbl[] = {
-       {
-               100000,
-               (0x0078 << I2C_TIMING0_HIGH_COUNT_OFFSET) |
-               (0x0030 << I2C_TIMING0_RCV_COUNT_OFFSET),
-               (0x0080 << I2C_TIMING1_LOW_COUNT_OFFSET) |
-               (0x0030 << I2C_TIMING1_XMIT_COUNT_OFFSET)
-       },
-       {
-               400000,
-               (0x000f << I2C_TIMING0_HIGH_COUNT_OFFSET) |
-               (0x0007 << I2C_TIMING0_RCV_COUNT_OFFSET),
-               (0x001f << I2C_TIMING1_LOW_COUNT_OFFSET) |
-               (0x000f << I2C_TIMING1_XMIT_COUNT_OFFSET),
-       }
-};
-
-static struct mxs_i2c_speed_table *mxs_i2c_speed_to_cfg(uint32_t speed)
-{
-       int i;
-       for (i = 0; i < ARRAY_SIZE(mxs_i2c_tbl); i++)
-               if (mxs_i2c_tbl[i].speed == speed)
-                       return &mxs_i2c_tbl[i];
-       return NULL;
-}
-
-static uint32_t mxs_i2c_cfg_to_speed(uint32_t timing0, uint32_t timing1)
-{
-       int i;
-       for (i = 0; i < ARRAY_SIZE(mxs_i2c_tbl); i++) {
-               if (mxs_i2c_tbl[i].timing0 != timing0)
-                       continue;
-               if (mxs_i2c_tbl[i].timing1 != timing1)
-                       continue;
-               return mxs_i2c_tbl[i].speed;
-       }
-
-       return 0;
-}
-
 int i2c_set_bus_speed(unsigned int speed)
 {
        struct mxs_i2c_regs *i2c_regs = (struct mxs_i2c_regs *)MXS_I2C0_BASE;
-       struct mxs_i2c_speed_table *spd = mxs_i2c_speed_to_cfg(speed);
+       /*
+        * The timing derivation algorithm. There is no documentation for this
+        * algorithm available, it was derived by using the scope and fiddling
+        * with constants until the result observed on the scope was good enough
+        * for 20kHz, 50kHz, 100kHz, 200kHz, 300kHz and 400kHz. It should be
+        * possible to assume the algorithm works for other frequencies as well.
+        *
+        * Note it was necessary to cap the frequency on both ends as it's not
+        * possible to configure completely arbitrary frequency for the I2C bus
+        * clock.
+        */
+       uint32_t clk = mxc_get_clock(MXC_XTAL_CLK);
+       uint32_t base = ((clk / speed) - 38) / 2;
+       uint16_t high_count = base + 3;
+       uint16_t low_count = base - 3;
+       uint16_t rcv_count = (high_count * 3) / 4;
+       uint16_t xmit_count = low_count / 4;
+
+       if (speed > 540000) {
+               printf("MXS I2C: Speed too high (%d Hz)\n", speed);
+               return -EINVAL;
+       }
 
-       if (!spd) {
-               printf("MXS I2C: Invalid speed selected (%d Hz)\n", speed);
+       if (speed < 12000) {
+               printf("MXS I2C: Speed too low (%d Hz)\n", speed);
                return -EINVAL;
        }
 
-       writel(spd->timing0, &i2c_regs->hw_i2c_timing0);
-       writel(spd->timing1, &i2c_regs->hw_i2c_timing1);
+       writel((high_count << 16) | rcv_count, &i2c_regs->hw_i2c_timing0);
+       writel((low_count << 16) | xmit_count, &i2c_regs->hw_i2c_timing1);
 
        writel((0x0030 << I2C_TIMING2_BUS_FREE_OFFSET) |
                (0x0030 << I2C_TIMING2_LEADIN_COUNT_OFFSET),
@@ -280,12 +258,15 @@ int i2c_set_bus_speed(unsigned int speed)
 unsigned int i2c_get_bus_speed(void)
 {
        struct mxs_i2c_regs *i2c_regs = (struct mxs_i2c_regs *)MXS_I2C0_BASE;
-       uint32_t timing0, timing1;
+       uint32_t clk = mxc_get_clock(MXC_XTAL_CLK);
+       uint32_t timing0;
 
        timing0 = readl(&i2c_regs->hw_i2c_timing0);
-       timing1 = readl(&i2c_regs->hw_i2c_timing1);
-
-       return mxs_i2c_cfg_to_speed(timing0, timing1);
+       /*
+        * This is a reverse version of the algorithm presented in
+        * i2c_set_bus_speed(). Please refer there for details.
+        */
+       return clk / ((((timing0 >> 16) - 3) * 2) + 38);
 }
 
 void i2c_init(int speed, int slaveadd)