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设备注册 */

  1. 定义一个i2c_board_info对象

staticstructi2c_board_infomy_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对象

staticstructi2c_board_infomy_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:数组的个数

*/

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

回到顶部