H5W3
当前位置:H5W3 > 其他技术问题 > 正文

I2C驱动框架+I2C设备驱动编写方法

I2C驱动框架

一、主要对象

1. I2C总线

struct bus_type i2c_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驱动

struct i2c_driver {
int (*probe)(struct i2c_client *, const struct i2c_device_id *); //probe函数
struct device_driver driver; //表明这是一个驱动
const struct i2c_device_id *id_table; //要匹配的从设备信息(名称)
int (*detect)(struct i2c_client *, struct i2c_board_info *); //设备探测函数
const unsigned short *address_list; //设备地址
struct list_head clients; //设备链表
};
 

3. I2C设备

struct i2c_client {
unsigned short addr; //设备地址
char name[I2C_NAME_SIZE]; //设备名称
struct i2c_adapter *adapter; //适配器,I2C控制器。
struct i2c_driver *driver; //设备对应的驱动
struct device dev; //表明这是一个设备
int irq; //中断号
struct list_head detected; //节点
};
 

4. I2C适配器

I2C适配器是什么?

经过上面的介绍,知道有I2C驱动和I2C设备,我们需要通过I2C驱动去和I2C设备通讯,这其中就需要一个I2C适配器,I2C适配器对应的就是SOC上的I2C控制器。

struct i2c_adapter {    //适配器
unsigned int id; //适配器的编号
const struct i2c_algorithm *algo; //算法,发送时序
struct device dev; //表明这是一个设备
};
 

5. I2C算法

I2C算法对应的就是如何发送I2C时序

struct i2c_algorithm {
/* 作为主设备时的发送函数 */
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
int num);
/* 作为从设备时的发送函数 */
int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
unsigned short 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)
{
struct i2c_client	*client;
client = kzalloc(sizeof *client, GFP_KERNEL);
client->dev.bus = &i2c_bus_type; //指定I2C总线
device_register(&client->dev); //向总线注册设备
return client;
}
 

device_register首先会将设备添加到总线的设备链表中,然后遍历总线的驱动链表,判断设备和驱动是否匹配,如果匹配就调用驱动的probe函数:

//第一层
int device_register(struct device *dev)
{
device_add(dev);
}
//第二层
int device_add(struct device *dev)
{
/* 添加设备到总线的设备链表中 */
bus_add_device(dev);
/* 遍历总线的驱动进行操作 */
bus_probe_device(dev);
}
//第三层
int bus_add_device(struct device *dev)
{
klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);
}
void bus_probe_device(struct device *dev)
{
device_attach(dev);
}
//第四层
int device_attach(struct device *dev)
{
/* 遍历总线的驱动链表每一项,然后调用__device_attach */
bus_for_each_drv(dev->bus, NULL, dev, __device_attach);
}
//第五层
static int __device_attach(struct device_driver *drv, void *data)
{
/* 判断设备和驱动是否匹配 */
if (!driver_match_device(drv, dev))
return 0;
/* 匹配成功 */
return driver_probe_device(drv, dev);
}
//第六层
static inline int driver_match_device(struct device_driver *drv,
struct device *dev)
{
/* 调用了总线的match函数,这里的总线在注册i2c设备时以及设置为I2C总线了 */
return drv->bus->match ? drv->bus->match(dev, drv) : 1;
}
int driver_probe_device(struct device_driver *drv, struct device *dev)
{
really_probe(dev, drv);
}
//第七层
struct bus_type i2c_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进行匹配
*/
static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
i2c_match_id(driver->id_table, client);
}
static int really_probe(struct device *dev, struct device_driver *drv)
{
i2c_device_probe(dev)
}
//第八层
static const 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++;
}
return NULL;
}
static int i2c_device_probe(struct device *dev)
{
/* 调用驱动的probe函数 */
driver->probe(client, i2c_match_id(driver->id_table, client));
}
 

2. 注册I2C驱动

与注册设备驱动过程基本一致

//第一层
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{
driver->driver.bus = &i2c_bus_type; //指定I2C总线
driver_register(&driver->driver); //向总线注册驱动
}
//第二层
int driver_register(struct device_driver *drv)
{
bus_add_driver(drv);
}
//第三层
int bus_add_driver(struct device_driver *drv)
{
driver_attach(drv); //此函数会遍历总线设备链表进行操作
klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers); // 添加进bus的driver链表中
}
//第四层
int driver_attach(struct device_driver *drv)
{
/* 遍历总线的设备链表,调用__driver_attach */
bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}
//第五层
static int __driver_attach(struct device *dev, void *data)
{
if (!driver_match_device(drv, dev))
return 0;
driver_probe_device(drv, dev);
}
 

3. I2C适配器构建及其驱动介绍

I2C适配器驱动就是SOC的I2C控制器驱动,主要是由SOC厂商去编写,我们不用过分注意细节。内部两个重要的数据结构i2c_adapter和 i2c_algorithm

//第一层
struct i2c_adapter{
const struct i2c_algorithm *algo; /* 总线访问算法 */
}
/* i2c_algorithm 就是I2C适配器与IIC设备进行通信的方法。*/
//第二层
struct i2c_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,
unsigned short flags, char read_write,
u8 command, int size, union i2c_smbus_data *data);
/* To determine what the adapter supports */
u32 (*functionality) (struct i2c_adapter *);
......
};
/* 实例-构建适配器 */
static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
.master_xfer		= s3c24xx_i2c_xfer,
.functionality		= s3c24xx_i2c_func,
};
static int s3c24xx_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适配器的算法进行操作的,如下

int i2c_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方式
static const struct i2c_device_id my_i2c_dev_id[] = {
{ "my_i2c_dev", 0},  /* 设备名字 */
{ }
};
static struct i2c_driver my_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设备驱动

static int __init my_i2c_drv_init(void)
{
i2c_add_driver(&my_i2c_drv);
return 0;
}
 

3.注册i2c设备

  • 匹配id方式
    /* 静态注册-只能在内核启动时就进行i2c设备注册 */
  1. 定义一个i2c_board_info对象
static struct i2c_board_info  my_i2c_dev_info = {
I2C_BOARD_INFO("my_i2c_dev", 0x20), //名字,设备地址
};
 
  1. 通过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));
 

/* 动态注册可以在内核运行期间注册,也就是可以应用 在加载驱动模块中 */

  1. 定义一个i2c_board_info对象
static struct i2c_board_info  my_i2c_dev_info = {
I2C_BOARD_INFO("my_i2c_dev", 0x20), //名字,设备地址
};
 
  1. 通过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:数组的个数
*/
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
 

2. 传输报文msg的组成

struct i2c_msg {
__u16 addr;	//从设备地址
__u16 flags; //读或写
__u16 len;	//消息的长度
__u8 *buf;	//消息
};
 
  • 例子
/* 定义 i2c_msg 结构体 */
struct i2c_msg msg[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 */
 

本文地址:H5W3 » I2C驱动框架+I2C设备驱动编写方法

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址