From f32a470ed3dfff493473bc25c7d0a01920507eed Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Mon, 12 Nov 2012 14:34:28 +0000 Subject: i2c: mxs: Abstract out the MXS I2C speed setup This patch pulls out the I2C speed setup from the i2c_init() call and implements the bus configuration lookup table with register values that needs to be programmed into the I2C IP to run at particular speed. This patch is a first step towards implementing run-time I2C bus speed configuration for the MXS I2C IP. Signed-off-by: Marek Vasut Cc: Stefano Babic Cc: Fabio Estevam --- drivers/i2c/mxs_i2c.c | 57 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 37 insertions(+), 20 deletions(-) (limited to 'drivers/i2c/mxs_i2c.c') diff --git a/drivers/i2c/mxs_i2c.c b/drivers/i2c/mxs_i2c.c index 2a193c2..98f6e8c 100644 --- a/drivers/i2c/mxs_i2c.c +++ b/drivers/i2c/mxs_i2c.c @@ -210,34 +210,51 @@ 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; +} + void i2c_init(int speed, int slaveadd) { 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); - mxs_i2c_reset(); - - switch (speed) { - case 100000: - writel((0x0078 << I2C_TIMING0_HIGH_COUNT_OFFSET) | - (0x0030 << I2C_TIMING0_RCV_COUNT_OFFSET), - &i2c_regs->hw_i2c_timing0); - writel((0x0080 << I2C_TIMING1_LOW_COUNT_OFFSET) | - (0x0030 << I2C_TIMING1_XMIT_COUNT_OFFSET), - &i2c_regs->hw_i2c_timing1); - break; - case 400000: - writel((0x000f << I2C_TIMING0_HIGH_COUNT_OFFSET) | - (0x0007 << I2C_TIMING0_RCV_COUNT_OFFSET), - &i2c_regs->hw_i2c_timing0); - writel((0x001f << I2C_TIMING1_LOW_COUNT_OFFSET) | - (0x000f << I2C_TIMING1_XMIT_COUNT_OFFSET), - &i2c_regs->hw_i2c_timing1); - break; - default: + if (!spd) { printf("MXS I2C: Invalid speed selected (%d Hz)\n", speed); return; } + mxs_i2c_reset(); + + writel(spd->timing0, &i2c_regs->hw_i2c_timing0); + writel(spd->timing1, &i2c_regs->hw_i2c_timing1); + writel((0x0015 << I2C_TIMING2_BUS_FREE_OFFSET) | (0x000d << I2C_TIMING2_LEADIN_COUNT_OFFSET), &i2c_regs->hw_i2c_timing2); -- cgit v1.1 From a06f590f7fce2f484d3bc0bff48a7aa90b70f13d Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Mon, 12 Nov 2012 14:34:29 +0000 Subject: i2c: mxs: Implement i2c_get/set_bus_speed() This patch implements the setup and retrieval functions for the I2C bus speed on the MXS I2C IP. Signed-off-by: Marek Vasut Cc: Stefano Babic Cc: Fabio Estevam --- drivers/i2c/mxs_i2c.c | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) (limited to 'drivers/i2c/mxs_i2c.c') diff --git a/drivers/i2c/mxs_i2c.c b/drivers/i2c/mxs_i2c.c index 98f6e8c..4152242 100644 --- a/drivers/i2c/mxs_i2c.c +++ b/drivers/i2c/mxs_i2c.c @@ -240,6 +240,51 @@ static struct mxs_i2c_speed_table *mxs_i2c_speed_to_cfg(uint32_t speed) 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); + + if (!spd) { + printf("MXS I2C: Invalid speed selected (%d Hz)\n", speed); + return -EINVAL; + } + + writel(spd->timing0, &i2c_regs->hw_i2c_timing0); + writel(spd->timing1, &i2c_regs->hw_i2c_timing1); + + writel((0x0015 << I2C_TIMING2_BUS_FREE_OFFSET) | + (0x000d << I2C_TIMING2_LEADIN_COUNT_OFFSET), + &i2c_regs->hw_i2c_timing2); + + return 0; +} + +unsigned int i2c_get_bus_speed(void) +{ + struct mxs_i2c_regs *i2c_regs = (struct mxs_i2c_regs *)MXS_I2C0_BASE; + uint32_t timing0, timing1; + + timing0 = readl(&i2c_regs->hw_i2c_timing0); + timing1 = readl(&i2c_regs->hw_i2c_timing1); + + return mxs_i2c_cfg_to_speed(timing0, timing1); +} + void i2c_init(int speed, int slaveadd) { struct mxs_i2c_regs *i2c_regs = (struct mxs_i2c_regs *)MXS_I2C0_BASE; -- cgit v1.1 From a157e0d5f6aa0b86bd7b6ae09d11b12a4a736d97 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Mon, 12 Nov 2012 14:34:30 +0000 Subject: i2c: mxs: Use i2c_set_bus_speed() in i2c_init() Use i2c_set_bus_speed() in i2c_init() within the mxs i2c driver to avoid duplication of code. Signed-off-by: Marek Vasut Cc: Stefano Babic Cc: Fabio Estevam --- drivers/i2c/mxs_i2c.c | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) (limited to 'drivers/i2c/mxs_i2c.c') diff --git a/drivers/i2c/mxs_i2c.c b/drivers/i2c/mxs_i2c.c index 4152242..3771452 100644 --- a/drivers/i2c/mxs_i2c.c +++ b/drivers/i2c/mxs_i2c.c @@ -287,22 +287,8 @@ unsigned int i2c_get_bus_speed(void) void i2c_init(int speed, int slaveadd) { - 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); - - if (!spd) { - printf("MXS I2C: Invalid speed selected (%d Hz)\n", speed); - return; - } - mxs_i2c_reset(); - - writel(spd->timing0, &i2c_regs->hw_i2c_timing0); - writel(spd->timing1, &i2c_regs->hw_i2c_timing1); - - writel((0x0015 << I2C_TIMING2_BUS_FREE_OFFSET) | - (0x000d << I2C_TIMING2_LEADIN_COUNT_OFFSET), - &i2c_regs->hw_i2c_timing2); + i2c_set_bus_speed(speed); return; } -- cgit v1.1 From aff36ea92ec0700cd9241bf01e72956a3ab9600e Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Mon, 12 Nov 2012 14:34:31 +0000 Subject: i2c: mxs: Fix TIMING2 register value According to FSL, the value in the TIMING2 register shall be 0x00300030 instead of what's written in the datasheet. This new value correlates with older STMP36xx datasheet. Issues were detected in Linux when this register was misconfigured, so write this correct value. Signed-off-by: Marek Vasut Cc: Stefano Babic Cc: Fabio Estevam --- drivers/i2c/mxs_i2c.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/i2c/mxs_i2c.c') diff --git a/drivers/i2c/mxs_i2c.c b/drivers/i2c/mxs_i2c.c index 3771452..006fb91 100644 --- a/drivers/i2c/mxs_i2c.c +++ b/drivers/i2c/mxs_i2c.c @@ -267,8 +267,8 @@ int i2c_set_bus_speed(unsigned int speed) writel(spd->timing0, &i2c_regs->hw_i2c_timing0); writel(spd->timing1, &i2c_regs->hw_i2c_timing1); - writel((0x0015 << I2C_TIMING2_BUS_FREE_OFFSET) | - (0x000d << I2C_TIMING2_LEADIN_COUNT_OFFSET), + writel((0x0030 << I2C_TIMING2_BUS_FREE_OFFSET) | + (0x0030 << I2C_TIMING2_LEADIN_COUNT_OFFSET), &i2c_regs->hw_i2c_timing2); return 0; -- cgit v1.1 From 1e2fc0d19bac9bf4f62d259169e902e700a18bad Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Fri, 30 Nov 2012 18:17:06 +0000 Subject: mxs: i2c: Restore speed setting after block reset The I2C block reset configures the I2C bus speed to strange value. Read the I2C speed from the block before reseting the block and restore it afterwards, so the I2C operates correctly. This issue can be replicated by doing unsuccessful I2C transfer, after such transfer finishes, the I2C block clock speed is misconfigured. Signed-off-by: Marek Vasut Cc: Heiko Schocher Cc: Fabio Estevam --- drivers/i2c/mxs_i2c.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers/i2c/mxs_i2c.c') diff --git a/drivers/i2c/mxs_i2c.c b/drivers/i2c/mxs_i2c.c index 006fb91..73a6659 100644 --- a/drivers/i2c/mxs_i2c.c +++ b/drivers/i2c/mxs_i2c.c @@ -40,6 +40,7 @@ void mxs_i2c_reset(void) { struct mxs_i2c_regs *i2c_regs = (struct mxs_i2c_regs *)MXS_I2C0_BASE; int ret; + int speed = i2c_get_bus_speed(); ret = mxs_reset_block(&i2c_regs->hw_i2c_ctrl0_reg); if (ret) { @@ -53,6 +54,8 @@ void mxs_i2c_reset(void) &i2c_regs->hw_i2c_ctrl1_clr); writel(I2C_QUEUECTRL_PIO_QUEUE_MODE, &i2c_regs->hw_i2c_queuectrl_set); + + i2c_set_bus_speed(speed); } void mxs_i2c_setup_read(uint8_t chip, int len) -- cgit v1.1 From fa5e2845a867a3715240ff221aaec2825b5c73df Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Fri, 30 Nov 2012 18:17:07 +0000 Subject: mxs: i2c: Implement algorithm to set up arbitrary i2c speed 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 Cc: Stefano Babic Cc: Fabio Estevam Cc: Wolfgang Denk --- drivers/i2c/mxs_i2c.c | 87 ++++++++++++++++++++------------------------------- 1 file changed, 34 insertions(+), 53 deletions(-) (limited to 'drivers/i2c/mxs_i2c.c') diff --git a/drivers/i2c/mxs_i2c.c b/drivers/i2c/mxs_i2c.c index 73a6659..b907f7b 100644 --- a/drivers/i2c/mxs_i2c.c +++ b/drivers/i2c/mxs_i2c.c @@ -28,6 +28,7 @@ #include #include +#include #include #include #include @@ -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) -- cgit v1.1