diff options
Diffstat (limited to 'drivers/i2c/mxc_i2c.c')
| -rw-r--r-- | drivers/i2c/mxc_i2c.c | 418 | 
1 files changed, 287 insertions, 131 deletions
diff --git a/drivers/i2c/mxc_i2c.c b/drivers/i2c/mxc_i2c.c index ebde3c5ab..a805bf62d 100644 --- a/drivers/i2c/mxc_i2c.c +++ b/drivers/i2c/mxc_i2c.c @@ -1,7 +1,15 @@  /* - * i2c driver for Freescale mx31 + * i2c driver for Freescale i.MX series   *   * (c) 2007 Pengutronix, Sascha Hauer <s.hauer@pengutronix.de> + * (c) 2011 Marek Vasut <marek.vasut@gmail.com> + * + * Based on i2c-imx.c from linux kernel: + *  Copyright (C) 2005 Torsten Koschorrek <koschorrek at synertronixx.de> + *  Copyright (C) 2005 Matthias Blaschke <blaschke at synertronixx.de> + *  Copyright (C) 2007 RightHand Technologies, Inc. + *  Copyright (C) 2008 Darius Augulis <darius.augulis at teltonika.lt> + *   *   * See file CREDITS for list of people who contributed to this   * project. @@ -30,11 +38,13 @@  #include <asm/arch/clock.h>  #include <asm/arch/imx-regs.h> -#define IADR	0x00 -#define IFDR	0x04 -#define I2CR	0x08 -#define I2SR	0x0c -#define I2DR	0x10 +struct mxc_i2c_regs { +	uint32_t	iadr; +	uint32_t	ifdr; +	uint32_t	i2cr; +	uint32_t	i2sr; +	uint32_t	i2dr; +};  #define I2CR_IEN	(1 << 7)  #define I2CR_IIEN	(1 << 6) @@ -68,215 +78,361 @@  #endif  #define I2C_MAX_TIMEOUT		10000 -#define I2C_MAX_RETRIES		3 -static u16 div[] = { 30, 32, 36, 42, 48, 52, 60, 72, 80, 88, 104, 128, 144, -	             160, 192, 240, 288, 320, 384, 480, 576, 640, 768, 960, -	             1152, 1280, 1536, 1920, 2304, 2560, 3072, 3840}; +static u16 i2c_clk_div[50][2] = { +	{ 22,	0x20 }, { 24,	0x21 }, { 26,	0x22 }, { 28,	0x23 }, +	{ 30,	0x00 }, { 32,	0x24 }, { 36,	0x25 }, { 40,	0x26 }, +	{ 42,	0x03 }, { 44,	0x27 }, { 48,	0x28 }, { 52,	0x05 }, +	{ 56,	0x29 }, { 60,	0x06 }, { 64,	0x2A }, { 72,	0x2B }, +	{ 80,	0x2C }, { 88,	0x09 }, { 96,	0x2D }, { 104,	0x0A }, +	{ 112,	0x2E }, { 128,	0x2F }, { 144,	0x0C }, { 160,	0x30 }, +	{ 192,	0x31 }, { 224,	0x32 }, { 240,	0x0F }, { 256,	0x33 }, +	{ 288,	0x10 }, { 320,	0x34 }, { 384,	0x35 }, { 448,	0x36 }, +	{ 480,	0x13 }, { 512,	0x37 }, { 576,	0x14 }, { 640,	0x38 }, +	{ 768,	0x39 }, { 896,	0x3A }, { 960,	0x17 }, { 1024,	0x3B }, +	{ 1152,	0x18 }, { 1280,	0x3C }, { 1536,	0x3D }, { 1792,	0x3E }, +	{ 1920,	0x1B }, { 2048,	0x3F }, { 2304,	0x1C }, { 2560,	0x1D }, +	{ 3072,	0x1E }, { 3840,	0x1F } +}; -static inline void i2c_reset(void) -{ -	writew(0, I2C_BASE + I2CR);	/* Reset module */ -	writew(0, I2C_BASE + I2SR); -	writew(I2CR_IEN, I2C_BASE + I2CR); -} +static u8 clk_idx; -void i2c_init(int speed, int unused) +/* + * Calculate and set proper clock divider + */ +static void i2c_imx_set_clk(unsigned int rate)  { -	int freq; +	struct mxc_i2c_regs *i2c_regs = (struct mxc_i2c_regs *)I2C_BASE; +	unsigned int i2c_clk_rate; +	unsigned int div;  	int i;  #if defined(CONFIG_MX31)  	struct clock_control_regs *sc_regs =  		(struct clock_control_regs *)CCM_BASE; +  	/* start the required I2C clock */  	writel(readl(&sc_regs->cgr0) | (3 << I2C_CLK_OFFSET),  		&sc_regs->cgr0);  #endif -	freq = mxc_get_clock(MXC_IPG_PERCLK); -	for (i = 0; i < 0x1f; i++) -		if (freq / div[i] <= speed) -			break; +	/* Divider value calculation */ +	i2c_clk_rate = mxc_get_clock(MXC_IPG_PERCLK); +	div = (i2c_clk_rate + rate - 1) / rate; +	if (div < i2c_clk_div[0][0]) +		i = 0; +	else if (div > i2c_clk_div[ARRAY_SIZE(i2c_clk_div) - 1][0]) +		i = ARRAY_SIZE(i2c_clk_div) - 1; +	else +		for (i = 0; i2c_clk_div[i][0] < div; i++) +			; + +	/* Store divider value */ +	clk_idx = i2c_clk_div[i][1]; +	writeb(clk_idx, &i2c_regs->ifdr); +} + +/* + * Reset I2C Controller + */ +void i2c_reset(void) +{ +	struct mxc_i2c_regs *i2c_regs = (struct mxc_i2c_regs *)I2C_BASE; -	debug("%s: speed: %d\n", __func__, speed); +	writeb(0, &i2c_regs->i2cr);	/* Reset module */ +	writeb(0, &i2c_regs->i2sr); +} -	writew(i, I2C_BASE + IFDR); +/* + * Init I2C Bus + */ +void i2c_init(int speed, int unused) +{ +	i2c_imx_set_clk(speed);  	i2c_reset();  } -static int wait_idle(void) +/* + * 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; +  	int timeout = I2C_MAX_TIMEOUT; -	while ((readw(I2C_BASE + I2SR) & I2SR_IBB) && --timeout) { -		writew(0, I2C_BASE + I2SR); +	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);  	} -	return timeout ? timeout : (!(readw(I2C_BASE + I2SR) & I2SR_IBB)); + +	return 1;  } -static int wait_busy(void) +/* + * Wait for transaction to complete + */ +int i2c_imx_trx_complete(void)  { +	struct mxc_i2c_regs *i2c_regs = (struct mxc_i2c_regs *)I2C_BASE;  	int timeout = I2C_MAX_TIMEOUT; -	while (!(readw(I2C_BASE + I2SR) & I2SR_IBB) && --timeout) +	while (timeout--) { +		if (readb(&i2c_regs->i2sr) & I2SR_IIF) { +			writeb(0, &i2c_regs->i2sr); +			return 0; +		} +  		udelay(1); -	writew(0, I2C_BASE + I2SR); /* clear interrupt */ +	} -	return timeout; +	return 1;  } -static int wait_complete(void) +/* + * Check if the transaction was ACKed + */ +int i2c_imx_acked(void)  { -	int timeout = I2C_MAX_TIMEOUT; +	struct mxc_i2c_regs *i2c_regs = (struct mxc_i2c_regs *)I2C_BASE; -	while ((!(readw(I2C_BASE + I2SR) & I2SR_ICF)) && (--timeout)) { -		writew(0, I2C_BASE + I2SR); -		udelay(1); -	} -	udelay(200); +	return readb(&i2c_regs->i2sr) & I2SR_RX_NO_AK; +} + +/* + * Start the controller + */ +int i2c_imx_start(void) +{ +	struct mxc_i2c_regs *i2c_regs = (struct mxc_i2c_regs *)I2C_BASE; +	unsigned int temp = 0; +	int result; -	writew(0, I2C_BASE + I2SR);	/* clear interrupt */ +	writeb(clk_idx, &i2c_regs->ifdr); -	return timeout; -} +	/* Enable I2C controller */ +	writeb(0, &i2c_regs->i2sr); +	writeb(I2CR_IEN, &i2c_regs->i2cr); +	/* Wait controller to be stable */ +	udelay(50); -static int tx_byte(u8 byte) -{ -	writew(byte, I2C_BASE + I2DR); +	/* 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; + +	temp |= I2CR_MTX | I2CR_TX_NO_AK; +	writeb(temp, &i2c_regs->i2cr); -	if (!wait_complete() || readw(I2C_BASE + I2SR) & I2SR_RX_NO_AK) -		return -1;  	return 0;  } -static int rx_byte(int last) +/* + * Stop the controller + */ +void i2c_imx_stop(void)  { -	if (!wait_complete()) -		return -1; +	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); -	if (last) -		writew(I2CR_IEN, I2C_BASE + I2CR); +	i2c_imx_bus_busy(0); -	return readw(I2C_BASE + I2DR); +	/* Disable I2C controller */ +	writeb(0, &i2c_regs->i2cr);  } -int i2c_probe(uchar chip) +/* + * 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; -	writew(0, I2C_BASE + I2CR); /* Reset module */ -	writew(I2CR_IEN, I2C_BASE + I2CR); +	writeb((chip << 1) | read, &i2c_regs->i2dr); + +	ret = i2c_imx_trx_complete(); +	if (ret) +		return ret; -	writew(I2CR_IEN |  I2CR_MSTA | I2CR_MTX, I2C_BASE + I2CR); -	ret = tx_byte(chip << 1); -	writew(I2CR_IEN | I2CR_MTX, I2C_BASE + I2CR); +	ret = i2c_imx_acked(); +	if (ret) +		return ret;  	return ret;  } -static int i2c_addr(uchar chip, uint addr, int alen) +/* + * Write register address + */ +int i2c_imx_set_reg_addr(uint addr, int alen)  { -	int i, retry = 0; -	for (retry = 0; retry < 3; retry++) { -		if (wait_idle()) +	struct mxc_i2c_regs *i2c_regs = (struct mxc_i2c_regs *)I2C_BASE; +	int ret; +	int i; + +	for (i = 0; i < (8 * alen); i += 8) { +		writeb((addr >> i) & 0xff, &i2c_regs->i2dr); + +		ret = i2c_imx_trx_complete(); +		if (ret)  			break; -		i2c_reset(); -		for (i = 0; i < I2C_MAX_TIMEOUT; i++) -			udelay(1); -	} -	if (retry >= I2C_MAX_RETRIES) { -		debug("%s:bus is busy(%x)\n", -		       __func__, readw(I2C_BASE + I2SR)); -		return -1; -	} -	writew(I2CR_IEN | I2CR_MSTA | I2CR_MTX, I2C_BASE + I2CR); -	if (!wait_busy()) { -		debug("%s:trigger start fail(%x)\n", -		       __func__, readw(I2C_BASE + I2SR)); -		return -1; +		ret = i2c_imx_acked(); +		if (ret) +			break;  	} -	if (tx_byte(chip << 1) || (readw(I2C_BASE + I2SR) & I2SR_RX_NO_AK)) { -		debug("%s:chip address cycle fail(%x)\n", -		       __func__, readw(I2C_BASE + I2SR)); -		return -1; -	} -	while (alen--) -		if (tx_byte((addr >> (alen * 8)) & 0xff) || -		    (readw(I2C_BASE + I2SR) & I2SR_RX_NO_AK)) { -			debug("%s:device address cycle fail(%x)\n", -			       __func__, readw(I2C_BASE + I2SR)); -			return -1; -		} -	return 0; +	return ret; +} + +/* + * Try if a chip add given address responds (probe the chip) + */ +int i2c_probe(uchar chip) +{ +	int ret; + +	ret = i2c_imx_start(); +	if (ret) +		return ret; + +	ret = i2c_imx_set_chip_addr(chip, 0); +	if (ret) +		return ret; + +	i2c_imx_stop(); + +	return ret;  } +/* + * Read data from I2C device + */  int i2c_read(uchar chip, uint addr, int alen, uchar *buf, int len)  { -	int timeout = I2C_MAX_TIMEOUT; +	struct mxc_i2c_regs *i2c_regs = (struct mxc_i2c_regs *)I2C_BASE;  	int ret; +	unsigned int temp; +	int i; -	debug("%s chip: 0x%02x addr: 0x%04x alen: %d len: %d\n", -		__func__, chip, addr, alen, len); +	ret = i2c_imx_start(); +	if (ret) +		return ret; -	if (i2c_addr(chip, addr, alen)) { -		printf("i2c_addr failed\n"); -		return -1; -	} +	/* write slave address */ +	ret = i2c_imx_set_chip_addr(chip, 0); +	if (ret) +		return ret; -	writew(I2CR_IEN | I2CR_MSTA | I2CR_MTX | I2CR_RSTA, I2C_BASE + I2CR); +	ret = i2c_imx_set_reg_addr(addr, alen); +	if (ret) +		return ret; -	if (tx_byte(chip << 1 | 1)) -		return -1; +	temp = readb(&i2c_regs->i2cr); +	temp |= I2CR_RSTA; +	writeb(temp, &i2c_regs->i2cr); -	writew(I2CR_IEN | I2CR_MSTA | -		((len == 1) ? I2CR_TX_NO_AK : 0), -		I2C_BASE + I2CR); +	ret = i2c_imx_set_chip_addr(chip, 1); +	if (ret) +		return ret; -	ret = readw(I2C_BASE + I2DR); +	/* setup bus to read data */ +	temp = readb(&i2c_regs->i2cr); +	temp &= ~(I2CR_MTX | I2CR_TX_NO_AK); +	if (len == 1) +		temp |= I2CR_TX_NO_AK; +	writeb(temp, &i2c_regs->i2cr); +	readb(&i2c_regs->i2dr); -	while (len--) { -		ret = rx_byte(len == 0); -		if (ret  < 0) -			return -1; -		*buf++ = ret; -		if (len <= 1) -			writew(I2CR_IEN | I2CR_MSTA | -				I2CR_TX_NO_AK, -				I2C_BASE + I2CR); -	} +	/* read data */ +	for (i = 0; i < len; i++) { +		ret = i2c_imx_trx_complete(); +		if (ret) +			return ret; -	writew(I2CR_IEN, I2C_BASE + I2CR); +		/* +		 * 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); +		} else if (i == (len - 2)) { +			temp = readb(&i2c_regs->i2cr); +			temp |= I2CR_TX_NO_AK; +			writeb(temp, &i2c_regs->i2cr); +		} -	while (readw(I2C_BASE + I2SR) & I2SR_IBB && --timeout) -		udelay(1); +		buf[i] = readb(&i2c_regs->i2dr); +	} -	return 0; +	i2c_imx_stop(); + +	return ret;  } +/* + * Write data to I2C device + */  int i2c_write(uchar chip, uint addr, int alen, uchar *buf, int len)  { -	int timeout = I2C_MAX_TIMEOUT; -	debug("%s chip: 0x%02x addr: 0x%04x alen: %d len: %d\n", -		__func__, chip, addr, alen, len); +	struct mxc_i2c_regs *i2c_regs = (struct mxc_i2c_regs *)I2C_BASE; +	int ret; +	int i; -	if (i2c_addr(chip, addr, alen)) -		return -1; +	ret = i2c_imx_start(); +	if (ret) +		return ret; -	while (len--) -		if (tx_byte(*buf++)) -			return -1; +	/* write slave address */ +	ret = i2c_imx_set_chip_addr(chip, 0); +	if (ret) +		return ret; -	writew(I2CR_IEN, I2C_BASE + I2CR); +	ret = i2c_imx_set_reg_addr(addr, alen); +	if (ret) +		return ret; -	while (readw(I2C_BASE + I2SR) & I2SR_IBB && --timeout) -		udelay(1); +	for (i = 0; i < len; i++) { +		writeb(buf[i], &i2c_regs->i2dr); -	return 0; -} +		ret = i2c_imx_trx_complete(); +		if (ret) +			return ret; + +		ret = i2c_imx_acked(); +		if (ret) +			return ret; +	} +	i2c_imx_stop(); + +	return ret; +}  #endif /* CONFIG_HARD_I2C */  |