diff options
Diffstat (limited to 'drivers/i2c/mxc_i2c.c')
| -rw-r--r-- | drivers/i2c/mxc_i2c.c | 477 | 
1 files changed, 264 insertions, 213 deletions
| diff --git a/drivers/i2c/mxc_i2c.c b/drivers/i2c/mxc_i2c.c index fc68062b118..73d89587019 100644 --- a/drivers/i2c/mxc_i2c.c +++ b/drivers/i2c/mxc_i2c.c @@ -31,13 +31,12 @@   */  #include <common.h> -#include <asm/io.h> - -#if defined(CONFIG_HARD_I2C) -  #include <asm/arch/clock.h>  #include <asm/arch/imx-regs.h> +#include <asm/errno.h> +#include <asm/io.h>  #include <i2c.h> +#include <watchdog.h>  struct mxc_i2c_regs {  	uint32_t	iadr; @@ -56,17 +55,14 @@ struct mxc_i2c_regs {  #define I2SR_ICF	(1 << 7)  #define I2SR_IBB	(1 << 5) +#define I2SR_IAL	(1 << 4)  #define I2SR_IIF	(1 << 1)  #define I2SR_RX_NO_AK	(1 << 0) -#ifdef CONFIG_SYS_I2C_BASE -#define I2C_BASE	CONFIG_SYS_I2C_BASE -#else +#if defined(CONFIG_HARD_I2C) && !defined(CONFIG_SYS_I2C_BASE)  #error "define CONFIG_SYS_I2C_BASE to use the mxc_i2c driver"  #endif -#define I2C_MAX_TIMEOUT		10000 -  static u16 i2c_clk_div[50][2] = {  	{ 22,	0x20 }, { 24,	0x21 }, { 26,	0x22 }, { 28,	0x23 },  	{ 30,	0x00 }, { 32,	0x24 }, { 36,	0x25 }, { 40,	0x26 }, @@ -117,46 +113,29 @@ static uint8_t i2c_imx_get_clk(unsigned int rate)  }  /* - * Reset I2C Controller + * Set I2C Bus speed   */ -void i2c_reset(void) +int bus_i2c_set_bus_speed(void *base, int speed)  { -	struct mxc_i2c_regs *i2c_regs = (struct mxc_i2c_regs *)I2C_BASE; - -	writeb(0, &i2c_regs->i2cr);	/* Reset module */ -	writeb(0, &i2c_regs->i2sr); -} - -/* - * Init I2C Bus - */ -void i2c_init(int speed, int unused) -{ -	struct mxc_i2c_regs *i2c_regs = (struct mxc_i2c_regs *)I2C_BASE; +	struct mxc_i2c_regs *i2c_regs = (struct mxc_i2c_regs *)base;  	u8 clk_idx = i2c_imx_get_clk(speed);  	u8 idx = i2c_clk_div[clk_idx][1];  	/* Store divider value */  	writeb(idx, &i2c_regs->ifdr); -	i2c_reset(); -} - -/* - * Set I2C Speed - */ -int i2c_set_bus_speed(unsigned int speed) -{ -	i2c_init(speed, 0); +	/* Reset module */ +	writeb(0, &i2c_regs->i2cr); +	writeb(0, &i2c_regs->i2sr);  	return 0;  }  /*   * Get I2C Speed   */ -unsigned int i2c_get_bus_speed(void) +unsigned int bus_i2c_get_bus_speed(void *base)  { -	struct mxc_i2c_regs *i2c_regs = (struct mxc_i2c_regs *)I2C_BASE; +	struct mxc_i2c_regs *i2c_regs = (struct mxc_i2c_regs *)base;  	u8 clk_idx = readb(&i2c_regs->ifdr);  	u8 clk_div; @@ -166,210 +145,163 @@ unsigned int i2c_get_bus_speed(void)  	return mxc_get_clock(MXC_IPG_PERCLK) / i2c_clk_div[clk_div][0];  } -/* - * Wait for bus to be busy (or free if for_busy = 0) - * - * for_busy = 1: Wait for IBB to be asserted - * for_busy = 0: Wait for IBB to be de-asserted - */ -int i2c_imx_bus_busy(int for_busy) -{ -	struct mxc_i2c_regs *i2c_regs = (struct mxc_i2c_regs *)I2C_BASE; -	unsigned int temp; +#define ST_BUS_IDLE (0 | (I2SR_IBB << 8)) +#define ST_BUS_BUSY (I2SR_IBB | (I2SR_IBB << 8)) +#define ST_IIF (I2SR_IIF | (I2SR_IIF << 8)) -	int timeout = I2C_MAX_TIMEOUT; - -	while (timeout--) { -		temp = readb(&i2c_regs->i2sr); - -		if (for_busy && (temp & I2SR_IBB)) -			return 0; -		if (!for_busy && !(temp & I2SR_IBB)) -			return 0; - -		udelay(1); +static int wait_for_sr_state(struct mxc_i2c_regs *i2c_regs, unsigned state) +{ +	unsigned sr; +	ulong elapsed; +	ulong start_time = get_timer(0); +	for (;;) { +		sr = readb(&i2c_regs->i2sr); +		if (sr & I2SR_IAL) { +			writeb(sr & ~I2SR_IAL, &i2c_regs->i2sr); +			printf("%s: Arbitration lost sr=%x cr=%x state=%x\n", +				__func__, sr, readb(&i2c_regs->i2cr), state); +			return -ERESTART; +		} +		if ((sr & (state >> 8)) == (unsigned char)state) +			return sr; +		WATCHDOG_RESET(); +		elapsed = get_timer(start_time); +		if (elapsed > (CONFIG_SYS_HZ / 10))	/* .1 seconds */ +			break;  	} - -	return 1; +	printf("%s: failed sr=%x cr=%x state=%x\n", __func__, +			sr, readb(&i2c_regs->i2cr), state); +	return -ETIMEDOUT;  } -/* - * Wait for transaction to complete - */ -int i2c_imx_trx_complete(void) +static int tx_byte(struct mxc_i2c_regs *i2c_regs, u8 byte)  { -	struct mxc_i2c_regs *i2c_regs = (struct mxc_i2c_regs *)I2C_BASE; -	int timeout = I2C_MAX_TIMEOUT; - -	while (timeout--) { -		if (readb(&i2c_regs->i2sr) & I2SR_IIF) { -			writeb(0, &i2c_regs->i2sr); -			return 0; -		} - -		udelay(1); -	} +	int ret; -	return 1; +	writeb(0, &i2c_regs->i2sr); +	writeb(byte, &i2c_regs->i2dr); +	ret = wait_for_sr_state(i2c_regs, ST_IIF); +	if (ret < 0) +		return ret; +	if (ret & I2SR_RX_NO_AK) +		return -ENODEV; +	return 0;  }  /* - * Check if the transaction was ACKed + * Stop I2C transaction   */ -int i2c_imx_acked(void) +static void i2c_imx_stop(struct mxc_i2c_regs *i2c_regs)  { -	struct mxc_i2c_regs *i2c_regs = (struct mxc_i2c_regs *)I2C_BASE; +	int ret; +	unsigned int temp = readb(&i2c_regs->i2cr); -	return readb(&i2c_regs->i2sr) & I2SR_RX_NO_AK; +	temp &= ~(I2CR_MSTA | I2CR_MTX); +	writeb(temp, &i2c_regs->i2cr); +	ret = wait_for_sr_state(i2c_regs, ST_BUS_IDLE); +	if (ret < 0) +		printf("%s:trigger stop failed\n", __func__);  }  /* - * Start the controller + * Send start signal, chip address and + * write register address   */ -int i2c_imx_start(void) +static int i2c_init_transfer_(struct mxc_i2c_regs *i2c_regs, +		uchar chip, uint addr, int alen)  { -	struct mxc_i2c_regs *i2c_regs = (struct mxc_i2c_regs *)I2C_BASE; -	unsigned int temp = 0; -	int result; +	unsigned int temp; +	int ret;  	/* Enable I2C controller */ +	if (!(readb(&i2c_regs->i2cr) & I2CR_IEN)) { +		writeb(I2CR_IEN, &i2c_regs->i2cr); +		/* Wait for controller to be stable */ +		udelay(50); +	} +	if (readb(&i2c_regs->iadr) == (chip << 1)) +		writeb((chip << 1) ^ 2, &i2c_regs->iadr);  	writeb(0, &i2c_regs->i2sr); -	writeb(I2CR_IEN, &i2c_regs->i2cr); - -	/* Wait controller to be stable */ -	udelay(50); +	ret = wait_for_sr_state(i2c_regs, ST_BUS_IDLE); +	if (ret < 0) +		return ret;  	/* Start I2C transaction */  	temp = readb(&i2c_regs->i2cr);  	temp |= I2CR_MSTA;  	writeb(temp, &i2c_regs->i2cr); -	result = i2c_imx_bus_busy(1); -	if (result) -		return result; +	ret = wait_for_sr_state(i2c_regs, ST_BUS_BUSY); +	if (ret < 0) +		return ret;  	temp |= I2CR_MTX | I2CR_TX_NO_AK;  	writeb(temp, &i2c_regs->i2cr); -	return 0; -} - -/* - * Stop the controller - */ -void i2c_imx_stop(void) -{ -	struct mxc_i2c_regs *i2c_regs = (struct mxc_i2c_regs *)I2C_BASE; -	unsigned int temp = 0; - -	/* Stop I2C transaction */ -	temp = readb(&i2c_regs->i2cr); -	temp |= ~(I2CR_MSTA | I2CR_MTX); -	writeb(temp, &i2c_regs->i2cr); - -	i2c_imx_bus_busy(0); - -	/* Disable I2C controller */ -	writeb(0, &i2c_regs->i2cr); -} - -/* - * Set chip address and access mode - * - * read = 1: READ access - * read = 0: WRITE access - */ -int i2c_imx_set_chip_addr(uchar chip, int read) -{ -	struct mxc_i2c_regs *i2c_regs = (struct mxc_i2c_regs *)I2C_BASE; -	int ret; - -	writeb((chip << 1) | read, &i2c_regs->i2dr); - -	ret = i2c_imx_trx_complete(); -	if (ret) -		return ret; - -	ret = i2c_imx_acked(); -	if (ret) +	/* write slave address */ +	ret = tx_byte(i2c_regs, chip << 1); +	if (ret < 0)  		return ret; -	return ret; -} - -/* - * Write register address - */ -int i2c_imx_set_reg_addr(uint addr, int alen) -{ -	struct mxc_i2c_regs *i2c_regs = (struct mxc_i2c_regs *)I2C_BASE; -	int ret = 0; -  	while (alen--) { -		writeb((addr >> (alen * 8)) & 0xff, &i2c_regs->i2dr); - -		ret = i2c_imx_trx_complete(); -		if (ret) -			break; - -		ret = i2c_imx_acked(); -		if (ret) -			break; +		ret = tx_byte(i2c_regs, (addr >> (alen * 8)) & 0xff); +		if (ret < 0) +			return ret;  	} - -	return ret; +	return 0;  } -/* - * Try if a chip add given address responds (probe the chip) - */ -int i2c_probe(uchar chip) +static int i2c_idle_bus(void *base); + +static int i2c_init_transfer(struct mxc_i2c_regs *i2c_regs, +		uchar chip, uint addr, int alen)  { +	int retry;  	int ret; +	for (retry = 0; retry < 3; retry++) { +		ret = i2c_init_transfer_(i2c_regs, chip, addr, alen); +		if (ret >= 0) +			return 0; +		i2c_imx_stop(i2c_regs); +		if (ret == -ENODEV) +			return ret; -	ret = i2c_imx_start(); -	if (ret) -		return ret; - -	ret = i2c_imx_set_chip_addr(chip, 0); -	if (ret) -		return ret; - -	i2c_imx_stop(); - +		printf("%s: failed for chip 0x%x retry=%d\n", __func__, chip, +				retry); +		if (ret != -ERESTART) +			writeb(0, &i2c_regs->i2cr);	/* Disable controller */ +		udelay(100); +		if (i2c_idle_bus(i2c_regs) < 0) +			break; +	} +	printf("%s: give up i2c_regs=%p\n", __func__, i2c_regs);  	return ret;  }  /*   * Read data from I2C device   */ -int i2c_read(uchar chip, uint addr, int alen, uchar *buf, int len) +int bus_i2c_read(void *base, uchar chip, uint addr, int alen, uchar *buf, +		int len)  { -	struct mxc_i2c_regs *i2c_regs = (struct mxc_i2c_regs *)I2C_BASE;  	int ret;  	unsigned int temp;  	int i; +	struct mxc_i2c_regs *i2c_regs = (struct mxc_i2c_regs *)base; -	ret = i2c_imx_start(); -	if (ret) -		return ret; - -	/* write slave address */ -	ret = i2c_imx_set_chip_addr(chip, 0); -	if (ret) -		return ret; - -	ret = i2c_imx_set_reg_addr(addr, alen); -	if (ret) +	ret = i2c_init_transfer(i2c_regs, chip, addr, alen); +	if (ret < 0)  		return ret;  	temp = readb(&i2c_regs->i2cr);  	temp |= I2CR_RSTA;  	writeb(temp, &i2c_regs->i2cr); -	ret = i2c_imx_set_chip_addr(chip, 1); -	if (ret) +	ret = tx_byte(i2c_regs, (chip << 1) | 1); +	if (ret < 0) { +		i2c_imx_stop(i2c_regs);  		return ret; +	}  	/* setup bus to read data */  	temp = readb(&i2c_regs->i2cr); @@ -377,73 +309,192 @@ int i2c_read(uchar chip, uint addr, int alen, uchar *buf, int len)  	if (len == 1)  		temp |= I2CR_TX_NO_AK;  	writeb(temp, &i2c_regs->i2cr); -	readb(&i2c_regs->i2dr); +	writeb(0, &i2c_regs->i2sr); +	readb(&i2c_regs->i2dr);		/* dummy read to clear ICF */  	/* read data */  	for (i = 0; i < len; i++) { -		ret = i2c_imx_trx_complete(); -		if (ret) +		ret = wait_for_sr_state(i2c_regs, ST_IIF); +		if (ret < 0) { +			i2c_imx_stop(i2c_regs);  			return ret; +		}  		/*  		 * It must generate STOP before read I2DR to prevent  		 * controller from generating another clock cycle  		 */  		if (i == (len - 1)) { -			temp = readb(&i2c_regs->i2cr); -			temp &= ~(I2CR_MSTA | I2CR_MTX); -			writeb(temp, &i2c_regs->i2cr); -			i2c_imx_bus_busy(0); +			i2c_imx_stop(i2c_regs);  		} else if (i == (len - 2)) {  			temp = readb(&i2c_regs->i2cr);  			temp |= I2CR_TX_NO_AK;  			writeb(temp, &i2c_regs->i2cr);  		} - +		writeb(0, &i2c_regs->i2sr);  		buf[i] = readb(&i2c_regs->i2dr);  	} - -	i2c_imx_stop(); - -	return ret; +	i2c_imx_stop(i2c_regs); +	return 0;  }  /*   * Write data to I2C device   */ -int i2c_write(uchar chip, uint addr, int alen, uchar *buf, int len) +int bus_i2c_write(void *base, uchar chip, uint addr, int alen, +		const uchar *buf, int len)  { -	struct mxc_i2c_regs *i2c_regs = (struct mxc_i2c_regs *)I2C_BASE;  	int ret;  	int i; +	struct mxc_i2c_regs *i2c_regs = (struct mxc_i2c_regs *)base; -	ret = i2c_imx_start(); -	if (ret) +	ret = i2c_init_transfer(i2c_regs, chip, addr, alen); +	if (ret < 0)  		return ret; -	/* write slave address */ -	ret = i2c_imx_set_chip_addr(chip, 0); -	if (ret) -		return ret; +	for (i = 0; i < len; i++) { +		ret = tx_byte(i2c_regs, buf[i]); +		if (ret < 0) +			break; +	} +	i2c_imx_stop(i2c_regs); +	return ret; +} + +struct i2c_parms { +	void *base; +	void *idle_bus_data; +	int (*idle_bus_fn)(void *p); +}; + +struct sram_data { +	unsigned curr_i2c_bus; +	struct i2c_parms i2c_data[3]; +}; -	ret = i2c_imx_set_reg_addr(addr, alen); +/* + * For SPL boot some boards need i2c before SDRAM is initialized so force + * variables to live in SRAM + */ +static struct sram_data __attribute__((section(".data"))) srdata; + +void *get_base(void) +{ +#ifdef CONFIG_SYS_I2C_BASE +#ifdef CONFIG_I2C_MULTI_BUS +	void *ret = srdata.i2c_data[srdata.curr_i2c_bus].base;  	if (ret)  		return ret; +#endif +	return (void *)CONFIG_SYS_I2C_BASE; +#elif defined(CONFIG_I2C_MULTI_BUS) +	return srdata.i2c_data[srdata.curr_i2c_bus].base; +#else +	return srdata.i2c_data[0].base; +#endif +} -	for (i = 0; i < len; i++) { -		writeb(buf[i], &i2c_regs->i2dr); +static struct i2c_parms *i2c_get_parms(void *base) +{ +	int i = 0; +	struct i2c_parms *p = srdata.i2c_data; +	while (i < ARRAY_SIZE(srdata.i2c_data)) { +		if (p->base == base) +			return p; +		p++; +		i++; +	} +	printf("Invalid I2C base: %p\n", base); +	return NULL; +} -		ret = i2c_imx_trx_complete(); -		if (ret) -			return ret; +static int i2c_idle_bus(void *base) +{ +	struct i2c_parms *p = i2c_get_parms(base); +	if (p && p->idle_bus_fn) +		return p->idle_bus_fn(p->idle_bus_data); +	return 0; +} -		ret = i2c_imx_acked(); -		if (ret) -			return ret; +#ifdef CONFIG_I2C_MULTI_BUS +unsigned int i2c_get_bus_num(void) +{ +	return srdata.curr_i2c_bus; +} + +int i2c_set_bus_num(unsigned bus_idx) +{ +	if (bus_idx >= ARRAY_SIZE(srdata.i2c_data)) +		return -1; +	if (!srdata.i2c_data[bus_idx].base) +		return -1; +	srdata.curr_i2c_bus = bus_idx; +	return 0; +} +#endif + +int i2c_read(uchar chip, uint addr, int alen, uchar *buf, int len) +{ +	return bus_i2c_read(get_base(), chip, addr, alen, buf, len); +} + +int i2c_write(uchar chip, uint addr, int alen, uchar *buf, int len) +{ +	return bus_i2c_write(get_base(), chip, addr, alen, buf, len); +} + +/* + * Test if a chip at a given address responds (probe the chip) + */ +int i2c_probe(uchar chip) +{ +	return bus_i2c_write(get_base(), chip, 0, 0, NULL, 0); +} + +void bus_i2c_init(void *base, int speed, int unused, +		int (*idle_bus_fn)(void *p), void *idle_bus_data) +{ +	int i = 0; +	struct i2c_parms *p = srdata.i2c_data; +	if (!base) +		return; +	for (;;) { +		if (!p->base || (p->base == base)) { +			p->base = base; +			if (idle_bus_fn) { +				p->idle_bus_fn = idle_bus_fn; +				p->idle_bus_data = idle_bus_data; +			} +			break; +		} +		p++; +		i++; +		if (i >= ARRAY_SIZE(srdata.i2c_data)) +			return;  	} +	bus_i2c_set_bus_speed(base, speed); +} + +/* + * Init I2C Bus + */ +void i2c_init(int speed, int unused) +{ +	bus_i2c_init(get_base(), speed, unused, NULL, NULL); +} -	i2c_imx_stop(); +/* + * Set I2C Speed + */ +int i2c_set_bus_speed(unsigned int speed) +{ +	return bus_i2c_set_bus_speed(get_base(), speed); +} -	return ret; +/* + * Get I2C Speed + */ +unsigned int i2c_get_bus_speed(void) +{ +	return bus_i2c_get_bus_speed(get_base());  } -#endif /* CONFIG_HARD_I2C */ | 
