天天看点

u-boot源码解析之一-I2C

最近在看uboot源码,写个博客分享一下自己的学习例程,同时也是做个笔记,方便后面的复习。

硬件平台:RK3188

u-boot版本:2014-01

RK平台I2C源码在drivers/i2c目录中的rk_i2c.c和rk_i2c.h文件中,面向用户调用的函数有两个

/*
 * i2c_read - Read from i2c memory
 * @chip:	target i2c address
 * @addr:	address to read from
 * @alen:
 * @buffer:	buffer for read data
 * @len:	num of bytes to be read
 *
 * Read from i2c memory.
 */
int i2c_read(uchar chip, uint addr, int alen, uchar *buf, int len)
{
	struct rk30_i2c *i2c = (struct rk30_i2c *)get_base();

	if (i2c == NULL || buf == NULL) {
		printf("i2c_read error: i2c = 0x%08x, buf = 0x%08x\n", i2c, buf);
		return -1;
	}

	return rk_i2c_read(i2c, chip, addr, alen, buf, len);
}


/*
 * i2c_write - Write to i2c memory
 * @chip:	target i2c address
 * @addr:	address to read from
 * @alen:
 * @buffer:	buffer for read data
 * @len:	num of bytes to be read
 *
 * Write to i2c memory.
 */
int i2c_write(uchar chip, uint addr, int alen, uchar *buf, int len)
{
	struct rk30_i2c *i2c = (struct rk30_i2c *)get_base();

	if (i2c == NULL || buf == NULL) {
		printf("i2c_read error: i2c = 0x%08x, buf = 0x%08x\n", i2c, buf);
		return -1;
	}

	return rk_i2c_write(i2c, chip, addr, alen, buf, len);
}
           

从上面源码的注释中可以看出,传入参数分别为设备地址(chip)、寄存器地址(addr)、读或写的数据地址(buffer)和需要读写的字节数(len),其中alen未作注释。这两个函数只是对i2c的状态做一个判断,同时得到当前需要操作的i2c基地址,具体作用的函数在 rk_i2c_write(i2c, chip, addr, alen, buf, len)函数中,下面以写函数进行分析。

往下追一层函数得到下面的代码:

static int rk_i2c_write(struct rk30_i2c *i2c, uchar chip, uint reg, uint r_len, uchar *buf, uint b_len)
{
	if (r_len + b_len + 1 > 32) {
		return -1;
	}
	rk30_i2c_enable(i2c, 0, 1);
	rk_i2c_get_ipd_event(i2c, I2C_STARTIPD);
	rk_i2c_clean_start(i2c);

	rk_i2c_write_prepare(i2c, chip, reg, r_len, buf, b_len);
	rk_i2c_get_ipd_event(i2c, I2C_MBTFIPD);
	
	rk_i2c_send_stop(i2c);
	rk_i2c_get_ipd_event(i2c, I2C_STOPIPD);
	rk_i2c_clean_stop(i2c);
	rk_i2c_disable(i2c);

	return 0;
}
           

函数的传入参数增加了调用函数中得到的i2c基地址,同时做了一些I2C基本的操作,数据的写入在 rk_i2c_write_prepare(i2c, chip, reg, r_len, buf, b_len)函数中,函数内容如下:

static void rk_i2c_write_prepare(struct rk30_i2c *i2c, uchar chip, uint reg, uint r_len, uchar *buf, uint b_len)
{
	unsigned int data = 0, cnt = 0, len = 0, i, j;
	unsigned char byte;

	i2c_writel(I2C_MBTFIEN, i2c->regs + I2C_IEN);

	for(i = 0; i < 8; i++) {
		data = 0;
		for(j = 0; j < 4; j++) {
			if (is_msgend(cnt, r_len, b_len))
				break;
			if (cnt == 0) {
				byte = (chip & 0x7f) << 1;
			} else if ((cnt == 1) && (r_len != 0)) {
				byte = (unsigned char)(reg & 0xff);
			} else if ((cnt == 2) && (r_len == 2)) {
				byte = (unsigned char)((reg >> 8) & 0xff);
			} else {
				byte =  buf[len++];
			}

			cnt++;
			data |= (byte << (j * 8));
		}
		i2c_writel(data, i2c->regs + I2C_TXDATA_BASE + 4 * i);
		if (is_msgend(cnt, r_len, b_len))
			break;
	}
	i2c_writel(cnt, i2c->regs + I2C_MTXCNT);
}
           

对于传入数据的操作全部在for循环中完成,总共发送8次,在这里可以看出,r_len决定了寄存器地址的长度,下面从r_len为2和不是2的两种情况来分析。

在r_len = 2的时候,将data分为4块,首次进入后将设备地址(chip)放在data的低8位(0字节),将寄存器地址分为高低字节,将低地址放在data的第1字节,高地址放在data的第2字节,data第3字节由写入数据的收地址buf[0]填充;

在r_len != 2的时候,首次进入后将设备地址(chip)放在data的低8位(0字节),将寄存器地址放在data的第1字节,data的第2和3字节分别有buf[0]和buf[1]填充;

从上面的分析可以看出,正常使用时,如果传送的寄存器地址为8位,则r_len=1,寄存器地址为16位,则r_len=2。

在循环中有两个用于判断是否数据传输完成的语句if (is_msgend(cnt, r_len, b_len))break,其函数原型如下:

/* returns TRUE if we reached the end of the current message */
static inline int is_msgend(uint cnt, uint r_len, uint b_len)
{
	return cnt >= 1 + r_len + b_len;
}
           

其实就是根据寄存器地址长度和数据长度来判断i2c的发送是否已经完成,如果发送完成则跳出发送循环。

因此,使用i2c读写函数的时候,函数中的参数需要传入设备地址(chip)、寄存器地址(addr)、寄存器地址长度(r_len)、需要读写的数据(*buf)和数据长度(len)。

继续阅读