I2C驱动框架+I2C设备驱动编写方法
I2C驱动框架
一、主要对象
1. I2C总线
structbus_typei2c_bus_type = {.name = "i2c",
.match = i2c_device_match, //匹配规则
.probe = i2c_device_probe, //匹配成功后的行为
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
.pm = &i2c_device_pm_ops,
};
I2C总线对应着/bus下的一条总线,这个i2c总线结构体管理着i2c设备与I2C驱动的匹配,删除等操作,I2C总线会调用i2c_device_match函数看I2C设备和I2C驱动是否匹配,如果匹配就调用i2c_device_prob函数,进而调用I2C驱动的probe函数。
特别提示:i2c_device_match会管理I2C设备和I2C总线匹配规则,这将和如何编写I2C驱动程序息息相关。
2. I2C驱动
structi2c_driver {int (*probe)(struct i2c_client *, const struct i2c_device_id *); //probe函数
structdevice_driverdriver;//表明这是一个驱动
conststructi2c_device_id *id_table;//要匹配的从设备信息(名称)
int (*detect)(struct i2c_client *, struct i2c_board_info *); //设备探测函数
constunsignedshort *address_list; //设备地址
structlist_headclients;//设备链表
};
3. I2C设备
structi2c_client {unsignedshort addr; //设备地址
char name[I2C_NAME_SIZE]; //设备名称
structi2c_adapter *adapter;//适配器,I2C控制器。
structi2c_driver *driver;//设备对应的驱动
structdevicedev;//表明这是一个设备
int irq; //中断号
structlist_headdetected;//节点
};
4. I2C适配器
I2C适配器是什么?
经过上面的介绍,知道有I2C驱动和I2C设备,我们需要通过I2C驱动去和I2C设备通讯,这其中就需要一个I2C适配器,I2C适配器对应的就是SOC上的I2C控制器。
structi2c_adapter {//适配器unsignedint id; //适配器的编号
conststructi2c_algorithm *algo;//算法,发送时序
structdevicedev;//表明这是一个设备
};
5. I2C算法
I2C算法对应的就是如何发送I2C时序
structi2c_algorithm {/* 作为主设备时的发送函数 */
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
int num);
/* 作为从设备时的发送函数 */
int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
unsignedshort flags, char read_write,
u8 command, int size, union i2c_smbus_data *data);
};
小结
I2C驱动有4个重要的东西,I2C总线、I2C驱动、I2C设备、I2C适配器
- I2C总线:维护着两个链表(I2C驱动、I2C设备),管理I2C设备和I2C驱动的匹配和删除等
- I2C驱动:对应的就是I2C设备的驱动程序
- I2C设备:是具体硬件设备的一个抽象
- I2C适配器:用于I2C驱动和I2C设备间的通讯,是SOC上I2C控制器的一个抽象
I2C总线的运行机制
I2C驱动框架可以分为四部分,I2C核心、I2C设备、I2C驱动、I2C适配器,其中I2C总线位于I2C核心中。
- I2C核心维护着一条I2C总线,提供了注册I2C设备、I2C驱动、I2C适配器的接口
- I2C总线维护着一条设备链表和驱动链表,当向I2C核心层注册设备时,会将其添加到总线的设备链表中,然后遍历总线上的驱动链表,查看二者是否匹配,如果匹配就调用驱动的probe函数。
- 当注册I2C驱动时,也会将其添加到I2C总线的驱动链表中,然后遍历总线的设备链表,查看二者是否匹配,如果匹配就调用驱动的probe函数。
- 在I2C驱动程序中,通过I2C适配器中的算法向I2C硬件设备传输数据。
二、内核源码分析
1. 注册I2C设备
注册I2C设备可以通过i2c_new_device,此函数会生成一个i2c_client,指定对应的总线为I2C总线,然后向总线注册设备。
struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{structi2c_client *client;
client = kzalloc(sizeof *client, GFP_KERNEL);
client->dev.bus = &i2c_bus_type; //指定I2C总线
device_register(&client->dev); //向总线注册设备
return client;
}
device_register首先会将设备添加到总线的设备链表中,然后遍历总线的驱动链表,判断设备和驱动是否匹配,如果匹配就调用驱动的probe函数:
//第一层intdevice_register(struct device *dev)
{
device_add(dev);
}
//第二层
intdevice_add(struct device *dev)
{
/* 添加设备到总线的设备链表中 */
bus_add_device(dev);
/* 遍历总线的驱动进行操作 */
bus_probe_device(dev);
}
//第三层
intbus_add_device(struct device *dev)
{
klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);
}
voidbus_probe_device(struct device *dev)
{
device_attach(dev);
}
//第四层
intdevice_attach(struct device *dev)
{
/* 遍历总线的驱动链表每一项,然后调用__device_attach */
bus_for_each_drv(dev->bus, NULL, dev, __device_attach);
}
//第五层
staticint __device_attach(struct device_driver *drv, void *data)
{
/* 判断设备和驱动是否匹配 */
if (!driver_match_device(drv, dev))
return0;
/* 匹配成功 */
return driver_probe_device(drv, dev);
}
//第六层
staticinlineintdriver_match_device(struct device_driver *drv,
struct device *dev)
{
/* 调用了总线的match函数,这里的总线在注册i2c设备时以及设置为I2C总线了 */
return drv->bus->match ? drv->bus->match(dev, drv) : 1;
}
intdriver_probe_device(struct device_driver *drv, struct device *dev)
{
really_probe(dev, drv);
}
//第七层
structbus_typei2c_bus_type = {
.name = "i2c",
.match = i2c_device_match, //匹配规则
.probe = i2c_device_probe, //匹配后的行为
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
.pm = &i2c_device_pm_ops,
};
/* 这里调用了i2c_device_match函数
* i2c_device_match会通过I2C驱动的id_table中每一的name和I2C设备的name进行匹配
*/
staticinti2c_device_match(struct device *dev, struct device_driver *drv)
{
i2c_match_id(driver->id_table, client);
}
staticintreally_probe(struct device *dev, struct device_driver *drv)
{
i2c_device_probe(dev)
}
//第八层
staticconst struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id, const struct i2c_client *client)
{
while (id->name[0]) {
if (strcmp(client->name, id->name) == 0) //字符串匹配
return id;
id++;
}
returnNULL;
}
staticinti2c_device_probe(struct device *dev)
{
/* 调用驱动的probe函数 */
driver->probe(client, i2c_match_id(driver->id_table, client));
}
2. 注册I2C驱动
与注册设备驱动过程基本一致
//第一层inti2c_register_driver(struct module *owner, struct i2c_driver *driver)
{
driver->driver.bus = &i2c_bus_type; //指定I2C总线
driver_register(&driver->driver); //向总线注册驱动
}
//第二层
intdriver_register(struct device_driver *drv)
{
bus_add_driver(drv);
}
//第三层
intbus_add_driver(struct device_driver *drv)
{
driver_attach(drv); //此函数会遍历总线设备链表进行操作
klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers); // 添加进bus的driver链表中
}
//第四层
intdriver_attach(struct device_driver *drv)
{
/* 遍历总线的设备链表,调用__driver_attach */
bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}
//第五层
staticint __driver_attach(struct device *dev, void *data)
{
if (!driver_match_device(drv, dev))
return0;
driver_probe_device(drv, dev);
}
3. I2C适配器构建及其驱动介绍
I2C适配器驱动就是SOC的I2C控制器驱动,主要是由SOC厂商去编写,我们不用过分注意细节。内部两个重要的数据结构i2c_adapter和 i2c_algorithm
//第一层structi2c_adapter{
conststructi2c_algorithm *algo;/* 总线访问算法 */
}
/* i2c_algorithm 就是I2C适配器与IIC设备进行通信的方法。*/
//第二层
structi2c_algorithm {
......
/* I2C适配器的传输函数,此函数完成与IIC设备的通信 */
int (*master_xfer)(struct i2c_adapter *adap,
struct i2c_msg *msgs,
int num);
/* SMBUS总线的传输函数 */
int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
unsignedshort flags, char read_write,
u8 command, int size, union i2c_smbus_data *data);
/* To determine what the adapter supports */
u32 (*functionality) (struct i2c_adapter *);
......
};
/* 实例-构建适配器 */
staticconststructi2c_algorithms3c24xx_i2c_algorithm = {
.master_xfer = s3c24xx_i2c_xfer,
.functionality = s3c24xx_i2c_func,
};
staticints3c24xx_i2c_probe(struct platform_device *pdev)
{
i2c->adap.algo = &s3c24xx_i2c_algorithm; //构建了算法
i2c_add_numbered_adapter(&i2c->adap); //注册了适配器
}
4. I2C数据传输
上面介绍I2C数据传输是通过I2C适配器完成的,下面来分析一下源码在I2C驱动中,使用i2c_transfer来传输I2C数据,此函数肯定是通过I2C适配器的算法进行操作的,如下
inti2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num){adap->algo->master_xfer(adap, msgs, num); //调用适配器的算法
}
设备驱动编写方法
i2c设备驱动重点关注两个数据结构i2c_client 和 i2c_driver,前者是描述设备信息的,后者是描述驱动的。
一、注册设备
1. 设置I2C设备驱动信息
- 匹配ID方式
staticconststructi2c_device_idmy_i2c_dev_id[] = {{ "my_i2c_dev", 0}, /* 设备名字 */
{ }
};
staticstructi2c_drivermy_i2c_drv = {
.driver = {
.name = "no", /* 这个名字不重要 */
.owner = THIS_MODULE,
},
.probe = my_i2c_drv_probe, /* 当匹配到i2c设备时调用 */
.remove = my_i2c_drv_remove, /* 当卸载i2c设备或驱动时调用 */
.id_table = my_i2c_dev_id, /* 这个结构体中的名字很重要 */
};
其中my_i2c_dev非常的重要,因为这个名字就是用来和设备进行匹配的名字。
- 设备树匹配方式
/* 设备树匹配列表 */static const struct of_device_id my_i2c_dev_of_match[] = {
{ .compatible = "my_i2c_dev, 0" },
{ /* Sentinel */ }
};
/* i2c驱动结构体 */
static struct i2c_driver my_i2c_drv = {
.probe = ap3216c_probe,
.remove = ap3216c_remove,
.driver = {
.owner = THIS_MODULE,
.name = "no",
.of_match_table = my_i2c_dev_of_match,
},
};
2. 注册i2c设备驱动
staticint __init my_i2c_drv_init(void){i2c_add_driver(&my_i2c_drv);
return0;
}
3.注册i2c设备
- 匹配id方式
/* 静态注册-只能在内核启动时就进行i2c设备注册 */
- 定义一个i2c_board_info对象
staticstructi2c_board_infomy_i2c_dev_info = {I2C_BOARD_INFO("my_i2c_dev", 0x20), //名字,设备地址
};
- 通过i2c_register_board_info注册
/** busnum:哪一条总线,也就是选择哪一个i2c控制器
* info:i2c设备信息数组
* n:数组有几项
*/
i2c_register_board_info(int busnum, struct i2c_board_info const * info, unsigned n);
i2c_register_board_info(0, my_i2c_dev_info, ARRAY_SIZE(my_i2c_dev_info));
/* 动态注册可以在内核运行期间注册,也就是可以应用 在加载驱动模块中 */
- 定义一个i2c_board_info对象
staticstructi2c_board_infomy_i2c_dev_info = {I2C_BOARD_INFO("my_i2c_dev", 0x20), //名字,设备地址
};
- 通过i2c_new_device来动态注册
/** adap:指定i2c设备器,以后访问设备的时候,使用过哪一个设备器(i2c主机控制器)去访问
* info:指定i2c设备信息
*/
struct i2c_client *
i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info);
- 使用设备树时
/* 在i2c节点下添加设备信息 */&i2c1 {
my_i2c_dev@20 {
compatible = "my_i2c_dev,0"
}
}
二、数据传输函数介绍
1. 传输函数
/** adap:i2c适配器
* msgs:消息数据
* num:数组的个数
*/
inti2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
2. 传输报文msg的组成
structi2c_msg {__u16 addr; //从设备地址
__u16 flags; //读或写
__u16 len; //消息的长度
__u8 *buf; //消息
};
- 例子
/* 定义 i2c_msg 结构体 */structi2c_msgmsg[2];
char val[10]
/* 填充msg */
msg[0].addr = my_i2c_client->addr; /* 这个client在probe函数中得到的 */
msg[0].flags = 0; /* 0表示写,1表示读 */
msg[0].buf = 0x80; /* 写:要发送的数据地址,读:读取到的数据存放的地址 */
msg[0].len = 1; /* 想要传输的字节数 */
/* 填充msg */
msg[1].addr = my_i2c_client->addr; /* 这个client在probe函数中得到的 */
msg[1].flags = 1; /* 1表示读 */
msg[1].buf = val; /* 读到的数据存在这里 */
msg[1].len = 4; /* 想要读取的字节数 */
/* 传输数据 */
i2c_transfer(my_i2c_client->adapter, msg, 2); /* 有两个msg */
以上是 I2C驱动框架+I2C设备驱动编写方法 的全部内容, 来源链接: utcz.com/a/30570.html