1、linux下的I2C协议框架
一般i2c_adapter以下的特定适配器硬件相关操作代码由芯片厂家开发完成。只需要完成在I2C协议基础上的二次开发,比方说基于I2C协议实现网络数据的传输,或者I2C外围器件的控制,或者blalalala
2、I2C协议的读写时序图
I2C设备挂载I2C总线以后,I2C设备的设备地址由硬件确定。同一条I2C总线上的不同I2C设备的设备地址不同。I2C协议传输的单元是一个字节(有些是10bit)。
写时序
一次I2C的写操作包括设备地址+写操作;寄存器地址,数据。
每次CPU发送一个字节的数据后,I2C设备把DATA线拉低,做ACK
读时序
读时序相对写时序稍微复杂一点点。分成两部分,第一步写操作,告诉I2C设备需要读寄存器的地址。第二步读操作,第二次发送I2C设备地址,等待I2C设备ACK后,完成数据读取。【此时CPU成为接收端,当完成读取一个字节的数据后,CPU可以发送ACK。假如有ACK,从设备会继续发送寄存器的数据(大部分I2C设备支持多地址读取,发送一次寄存器地址,连续接下来的多个寄存器数据);假如NOACK,I2C设备停止发送数据】
3、代码框架
抛个问题:一个SOC上有好几路IIC,一路IIC上可以挂多个从设备。怎么指定使用的是哪一路IIC呢?
3.1、i2c设备节点的创建
如图1所示,其实一路IIC对应单独的i2c_adapter和i2c_client。可以使用
struct i2c_adapter *i2c_get_adapter(int nr)
i2c-core-base.c - drivers/i2c/i2c-core-base.c - Linux source code (v5.18.13) - Bootlin
实现i2c_adapter的创建,函数的入参刚好是IIC通道号。在i2c_adapter创建后,再使用
struct i2c_client *
i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
完成i2c_client的创建。
见以下代码,其中i2c_info下的0x45就是从设备的地址:
//需要和i2c_board_info中的名字xxxx一样
static const struct i2c_device_id module_id[] = {
{ "xxxx", 0 },
{ /* END OF LIST */ }
};
MODULE_DEVICE_TABLE(i2c, module_id);
static struct i2c_driver module_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "i2c-module",
},
.id_table = module_id,
.probe = module_probe,
.remove = module_remove,
};
static struct i2c_board_info i2c_info[] = {
{
I2C_BOARD_INFO("xxxx", 0x45),
.platform_data = 0,
},
};
int module_init(void)
{
......
adapter = i2c_get_adapter(channel);
if (!adapter) {
printk("failed to get I2C adapter %d\n", channel);
return -1;
}
device_client = i2c_new_device(adapter, i2c_info);
if (!device_client) {
printk("i2c new device fail!\n");
return -1;
}
ret = i2c_add_driver(&module_driver);
......
}
初始化成功后,系统就会调用module_driver下的module_probe函数实现初始化。
在/sys/class/i2c可以看到节点
待补充
3.2、i2c协议的读写函数,统一使用下面这个函数
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
写函数的封装。从上面时序得知,写操作只有写动作,只需要一个msg,但是可以一次写好几个数据。
typedef struct reg {
uint8_t *buf;
int len;
}reg_t;
typedef struct buf {
uint8_t *buf;
int len;
}buf_t;
int i2c_module_write_data(void *handle, const reg_t *s_reg)
{
int ret = 0;
struct i2c_client *client = (struct i2c_client *)handle;
struct i2c_msg msg;
memset(&msg, 0x00, sizeof(struct i2c_msg));
msg.addr = client->addr; //从设备的地址
msg.flags = 0; //写标记
msg.len = s_reg->len; //发送的数据长度,不考虑第一个从设备地址
msg.buf = s_reg->buf;
ret = i2c_transfer(client->adapter, &msg, 1);
if(ret <= 0)
{
printk("transfer cmd data error.\n");
}
return ret;
}
读函数的封装。从上面时序可知,读操作有两个动作,先一个写操作,后一个读操作,需要两个msg。
int i2c_module_read_data(void *handle, const reg_t *s_reg, const buf_t *r_buf)
{
int ret = 0;
struct i2c_client *client = (struct i2c_client *)handle;
struct i2c_msg msg[] = {
{
.addr = client->addr,
.flags = 0,
.len = s_reg->len, //表示寄存器地址字节长度,以byte为单位
.buf = s_reg->buf,
},
{
.addr = client->addr,
.flags = I2C_M_RD, //读标记
.len = r_buf->len, //表示期望读到数据的字节长度,以byte为单位
.buf = r_buf->buf, //将读取到的数据保存在buffer中
},
};
ret = i2c_transfer(client->adapter, msg, sizeof(msg)/sizeof(msg[0]));
if(ret != (sizeof(msg)/sizeof(msg[0]))) {
printk("read regs from chip error.\n");
return -1;
}
return 0;
}