#include <common.h>
#include <malloc.h>
+#include <i2c.h>
#include <asm/errno.h>
#include <asm/io.h>
#include <asm/arch/clock.h>
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),
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)