您的位置:首页 > 运维架构 > Linux

Linux I2C核心、总线与设备驱动(二)

2013-09-28 17:36 453 查看
从上面的分析可知,虽然I2C硬件体系结构比较简单,但是I2C体系结构在Linux中的实现却相当复杂。当工程师拿到实际的电路板,面对复杂的
Linux I2C子系统,应该如何下手写驱动呢?究竟有哪些是需要亲自做的,哪些是内核已经提供的呢?理清这个问题非常有意义,可以使我们面对具体问题时迅速地抓住重点。

一方面,适配器驱动可能是Linux内核本身还不包含的。另一方面,挂接在适配器上的具体设备驱动可能也是Linux不存在的。即便上述设备驱动都存在于Linux内核中,其基于的平台也可能与我们的电路板不一样。因此,工程师要实现的主要工作将包括:

? 提供I2C适配器的硬件驱动,探测、初始化I2C适配器(如申请I2C的I/O地址和中断号)、驱动CPU控制的I2C适配器从硬件上产生各种信号以及处理I2C中断等。

? 提供I2C适配器的algorithm,用具体适配器的xxx_xfer()函数填充i2c_algorithm的master_xfer指针,并把i2c_algorithm指针赋值给i2c_adapter的algo指针。

? 实现I2C设备驱动与i2c_driver接口,用具体设备yyy的yyy_attach_adapter()函数指针、
yyy_detach_client()函数指针和yyy_command()函数指针的赋值给i2c_driver的attach_adapter、
detach_adapter和detach_client指针。

? 实现I2C设备驱动的文件操作接口,即实现具体设备yyy的yyy_read()、yyy_write()和yyy_ioctl()函数等。

上述工作中1、2属于I2C总线驱动,3、4属于I2C设备驱动,做完这些工作,系统会增加两个内核模块。本章第3~4节将详细分析这些工作的实施方法,给出设计模板,而5~6节将给出两个具体的实例。

15.2 Linux I2C核心

I2C核心(drivers/i2c/i2c-core.c)中提供了一组不依赖于硬件平台的接口函数,这个文件一般不需要被工程师修改,但是理解其中的主要函数非常关键,因为I2C总线驱动和设备驱动之间依赖于I2C核心作为纽带。I2C核心中的主要函数包括:

? 增加/删除i2c_adapter

int i2c_add_adapter(struct i2c_adapter *adap);

int i2c_del_adapter(struct i2c_adapter *adap);

? 增加/删除i2c_driver

int i2c_register_driver(struct module *owner, struct i2c_driver *driver);

int i2c_del_driver(struct i2c_driver *driver);

inline int i2c_add_driver(struct i2c_driver *driver);

? i2c_client依附/脱离

int i2c_attach_client(struct i2c_client *client);

int i2c_detach_client(struct i2c_client *client);

当一个具体的client被侦测到并被关联的时候,设备和sysfs文件将被注册。相反地,在client被取消关联的时候,sysfs文件和设备也被注销,如代码清单15.6。

代码清单15.6 I2C核心client attach/detach函数

1 int i2c_attach_client(struct i2c_client *client)

2 {

3 ...

4 device_register(&client->dev);

5 device_create_file(&client->dev, &dev_attr_client_name);

6

7 return 0;

8 }

9

10 int i2c_detach_client(struct i2c_client *client)

11 {

12 ...

13 device_remove_file(&client->dev, &dev_attr_client_name);

14 device_unregister(&client->dev);

15 ...

16 }

(4)i2c传输、发送和接收

int i2c_transfer(struct i2c_adapter * adap, struct i2c_msg *msgs, int num);

int i2c_master_send(struct i2c_client *client,const char *buf ,int count);

int i2c_master_recv(struct i2c_client *client, char *buf ,int count);

i2c_transfer ()函数用于进行I2C适配器和I2C设备之间的一组消息交互,i2c_master_send()函数和i2c_master_recv()函数内部会调用i2c_transfer()函数分别完成一条写消息和一条读消息,如代码清单15.7、15.8。

代码清单15.7 I2C核心i2c_master_send函数

1 int i2c_master_send(struct i2c_client *client,const char *buf ,int count)

2 {

3 int ret;

4 struct i2c_adapter *adap=client->adapter;

5 struct i2c_msg msg;

6 /*构造一个写消息*/

7 msg.addr = client->addr;

8 msg.flags = client->flags & I2C_M_TEN;

9 msg.len = count;

10 msg.buf = (char *)buf;

11 /*传输消息*/

12 ret = i2c_transfer(adap, &msg, 1);

13

14 return (ret == 1) ? count : ret;

15 }

代码清单15.8 I2C核心i 2c_master_recv函数

1 int i2c_master_recv(struct i2c_client *client, char *buf ,int count)

2 {

3 struct i2c_adapter *adap=client->adapter;

4 struct i2c_msg msg;

5 int ret;

6 /*构造一个读消息*/

7 msg.addr = client->addr;

8 msg.flags = client->flags & I2C_M_TEN;

9 msg.flags |= I2C_M_RD;

10 msg.len = count;

11 msg.buf = buf;

12 /*传输消息*/

13 ret = i2c_transfer(adap, &msg, 1);

14

15 /* 成功(1条消息被处理),
返回读的字节数 */

16 return (ret == 1) ? count : ret;

17 }

i2c_transfer()函数本身不具备驱动适配器物理硬件完成消息交互的能力,它只是寻找到i2c_adapter对应的i2c_algorithm,并使用i2c_algorithm的master_xfer()函数真正驱动硬件流程,如代码清单15.9。

代码清单15.9 I2C核心i 2c_transfer函数

1 int i2c_transfer(struct i2c_adapter * adap, struct i2c_msg *msgs, int num)

2 {

3 int ret;

4

5 if (adap->algo->master_xfer) {

6 down(&adap->bus_lock);

7 ret = adap->algo->master_xfer(adap,msgs,num); /* 消息传输 */

8 up(&adap->bus_lock);

9 return ret;

10 } else {

11 dev_dbg(&adap->dev, "I2C level transfers not supported\n");

12 return -ENOSYS;

13 }

14 }

(5)I2C控制命令分派

下面函数有助于将发给I2C适配器设备文件ioctl的命令分派给对应适配器的algorithm的algo_control()函数或i2c_driver的command()函数:

int i2c_control(struct i2c_client *client, unsigned int cmd, unsigned long arg);

void i2c_clients_command(struct i2c_adapter *adap, unsigned int cmd, void *arg);

15.3 Linux I2C总线驱动

15.3.1 I2C适配器驱动加载与卸载

I2C总线驱动模块的加载函数要完成两个工作:

? 初始化I2C适配器所使用的硬件资源,申请I/O地址、中断号等。

? 通过i2c_add_adapter()添加i2c_adapter的数据结构,当然这个i2c_adapter数据结构的成员已经被xxx适配器的相应函数指针所初始化。

I2C总线驱动模块的卸载函数要完成的工作与加载函数的相反:

? 释放I2C适配器所使用的硬件资源,释放I/O地址、中断号等。

? 通过i2c_del_adapter()删除i2c_adapter的数据结构。

代码清单15.10给出了I2C适配器驱动模块加载和卸载函数的模板。

代码清单15.10 I2C总线驱动模块加载和卸载函数模板

1 static int __init i2c_adapter_xxx_init(void)

2 {

3 xxx_adpater_hw_init();

4 i2c_add_adapter(&xxx_adapter);

5 }

6

7 static void __exit i2c_adapter_xxx_exit(void)

8 {

9 xxx_adpater_hw_free();

10 i2c_del_adapter(&xxx_adapter);

11 }

上述代码中xxx_adpater_hw_init()和xxx_adpater_hw_free()函数的实现都与具体的CPU和I2C设备硬件直接相关。

15.3.2 I2C总线通信方法

我们需要为特定的I2C适配器实现其通信方法,主要实现i2c_algorithm的master_xfer()函数和functionality()函数。

functionality ()函数非常简单,用于返回algorithm所支持的通信协议,如I2C_FUNC_I2C、I2C_FUNC_10BIT_ADDR、
I2C_FUNC_SMBUS_READ_BYTE、I2C_FUNC_SMBUS_WRITE_BYTE等。

master_xfer()函数在I2C适配器上完成传递给它的i2c_msg数组中的每个I2C消息,代码清单15.11给出了xxx设备的master_xfer()函数模板。

代码清单15.11 I2C总线驱动master_xfer函数模板

1 static int i2c_adapter_xxx_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,

2 int num)

3 {

4 ...

5 for (i = 0; i < num; i++)

6 {

7 i2c_adapter_xxx_start(); /*产生开始位*/

8 /*是读消息*/

9 if (msgs[i]->flags &I2C_M_RD)

10 {

11 i2c_adapter_xxx_setaddr((msg->addr << 1) | 1); /*发送从设备读地址*/

12 i2c_adapter_xxx_wait_ack(); /*获得从设备的ack*/

13 i2c_adapter_xxx_readbytes(msgs[i]->buf, msgs[i]->len); /*读取msgs[i]

14 ->len长的数据到msgs[i]->buf*/

15 }

16 else

17 /*是写消息*/

18 {

19 i2c_adapter_xxx_setaddr(msg->addr << 1); /*发送从设备写地址*/

20 i2c_adapter_xxx_wait_ack(); /*获得从设备的ack*/

21 i2c_adapter_xxx_writebytes(msgs[i]->buf, msgs[i]->len); /*读取msgs[i]

22 ->len长的数据到msgs[i]->buf*/

23 }

24 }

25 i2c_adapter_xxx_stop(); /*产生停止位*/

26 }

上 述代码实际上给出了一个master_xfer()函数处理I2C消息数组的流程,对于数组中的每个消息,判断消息类型,若为读消息,则赋从设备地址为
(msg->addr << 1) | 1,否则为msg->addr << 1。对每个消息产生1个开始位,紧接着传送从设备地址,然后开始数据的发送或接收,对最后的消息还需产生1个停止位。图15.3描述了整个
master_xfer()完成的时序。





图15.3 algorithm中master_xfer的时序

master_xfer()函
数模板中的i2c_adapter_xxx_start()、i2c_adapter_xxx_setaddr()、 i2c_adapter_xxx_wait_ack()、i2c_adapter_xxx_readbytes()、
i2c_adapter_xxx_writebytes()和i2c_adapter_xxx_stop()函数用于完成适配器的底层硬件操作,与I2C
适配器和CPU的具体硬件直接相关,需要由工程师根据芯片的数据手册来实现。

i2c_adapter_xxx_readbytes()用于从从设备上接收一串数据,i2c_adapter_xxx_writebytes()用于向从设备写入一串数据,这两个函数的内部也会涉及到I2C总线协议中的ACK应答。

master_xfer ()函数的实现在形式上会很多样,即便是Linux内核源代码中已经给出的一些I2C总线驱动的master_xfer()函数,由于由不同的组织或个人
完成,风格上的差别也非常大,不一定能与模板完全对应,如master_xfer()函数模板给出的消息处理是顺序进行的,而有的驱动以中断方式来完成这
个流程(第5节的实例即是如此)。不管具体怎么实施,流程的本质都是不变的。因为这个流程不以驱动工程师的意志为转移,最终由I2C总线硬件上的通信协议
决定。

多数I2C总线驱动会定义一个xxx_i2c结构体,作为i2c_adapter的algo_data(类似“私有数据”),其中包含
I2C消息数组指针、数组索引及I2C适配器algorithm访问控制用的自旋锁、等待队列等,而master_xfer()函数完成消息数组中消息的
处理也可通过对xxx_i2c结构体相关成员的访问来控制。代码清单15.12给出了xxx_i2c结构体的定义,与图15.2中的xxx_i2c是对应
的。

代码清单15.12 xxx_i2c结构体模板

1 struct xxx_i2c

2 {

3 spinlock_t lock;

4 wait_queue_head_t wait;

5 struct i2c_msg *msg;

6 unsigned int msg_num;

7 unsigned int msg_idx;

8 unsigned int msg_ptr;

9 ...

10 struct i2c_adapter adap;

11 };

15.4 Linux I2C设备驱动

I2C 设备驱动要使用i2c_driver和i2c_client数据结构并填充其中的成员函数。i2c_client一般被包含在设备的私有信息结构体
yyy_data中,而i2c_driver则适宜被定义为全局变量并初始化,代码清单15.13显示了被初始化的i2c_driver。

代码清单15.13 初始化的i2c_driver

1 static struct i2c_driver yyy_driver =

2 {

3 .driver =

4 {

5 .name = "yyy",

6 } ,

7 .attach_adapter = yyy_attach_adapter,

8 .detach_client = yyy_detach_client,

9 .command = yyy_command,

10 };

15.4.1 Linux I2C设备驱动模块加载与卸载

I2C设备驱动模块加载函数通用的方法是在I2C设备驱动模块加载函数中完成两件事:

? 通过register_chrdev()函数将I2C设备注册为一个字符设备。

? 通过I2C核心的i2c_add_driver()函数添加i2c_driver。

在模块卸载函数中需要做相反的两件事:

? 通过I2C核心的i2c_del_driver()函数删除i2c_driver。

? 通过unregister_chrdev()函数注销字符设备。

代码清单15.14给出了I2C设备驱动加载与卸载函数的模板。

代码清单15.14 I2C设备驱动模块加载与卸载函数模板

1 static int __init yyy_init(void)

2 {

3 int res;

4 /*注册字符设备*/

5 res = register_chrdev(YYY_MAJOR, "yyy", &yyy_fops); //老内核接口

6 if (res)

7 goto out;

8 /*添加i2c_driver*/

9 res = i2c_add_driver(&yyy_driver);

10 if (res)

11 goto out_unreg_class;

12 return 0;

13

14 out_unreg_chrdev: unregister_chrdev(I2C_MAJOR, "i2c");

15 out: printk(KERN_ERR "%s: Driver Initialisation failed\n", __FILE__);

16 return res;

17 }

18

19 static void __exit yyy_exit(void)

20 {

21 i2c_del_driver(&i2cdev_driver);

22 unregister_chrdev(YYY_MAJOR, "yyy");

23 }

第5行代码说明注册“yyy”这个字符设备时,使用的file_operations结构体为yyy_fops,15.4.3节将讲解这个结构体中成员函数的实现。

15.4.2 Linux I2C设备驱动i2c_driver成员函数

i2c_add_driver (&yyy_driver)的执行会引发i2c_driver结构体中yyy_attach_adapter()函数的执行,我们可以在
yyy_attach_adapter()函数里探测物理设备。为了实现探测,yyy_attach_adapter()函数里面也只需简单地调用I2C
核心的i2c_probe()函数,如代码清单15.15。

代码清单15.15 I2C设备驱动i2c_attach_adapter函数模板

1 static int yyy_attach_adapter(struct i2c_adapter *adapter)

2 {

3 return i2c_probe(adapter, &addr_data, yyy_detect);

4 }

代码第3行传递给i2c_probe()函数的第1个参数是i2c_adapter指针,第2个参数是要探测的地址数据,第3个参数是具体的探测函数。要探测的地址实际列表在一个16位无符号整型数组中,这个数组以I2C_CLIENT_END为最后一个元素。

i2c_probe()函数会引发yyy_detect()函数的调用,可以在yyy_detect()函数中初始化i2c_client,如代码清单15.16。

代码清单15.16 I2C设备驱动detect函数模板

1 static int yyy_detect(struct i2c_adapter *adapter, int address, int kind)

2 {

3 struct i2c_client *new_client;

4 struct yyy_data *data;

5 int err = 0;

6

7 if (!i2c_check_functionality(adapter, I2C_FUNC_XXX)

8 goto exit;

9

10 if (!(data = kzalloc(sizeof(struct yyy_data), GFP_KERNEL)))

11 {

12 err = - ENOMEM;

13 goto exit;

14 }

15

16 new_client = &data->client;

17 new_client->addr = address;

18 new_client->adapter = adapter;

19 new_client->driver = &yyy_driver;

20 new_client->flags = 0;

21

22 /* 新的client将依附于adapter */

23 if ((err = i2c_attach_client(new_client)))

24 goto exit_kfree;

25

26 yyy_init_client(new_client);

27 return 0;

28 exit_kfree: kfree(data);

29 exit: return err;

30}

代 码第10行分配私有信息结构体的内存,i2c_client也被创建。第16~20行对新创建的i2c_client进行初始化。第23行调用内核的
i2c_attach_client()知会I2C核心系统中包含了一个新的I2C设备。第26行代码初始化i2c_client对应的I2C设备,这个
函数是硬件相关的。

图15.4描述了当I2C设备驱动的模块加载函数被调用的时候引发的连锁反应的流程。





图15.4 I2C设备驱动模块加载连锁反应

I2C 设备驱动卸载函数进行i2c_del_driver(&yyy_driver)调用后,会引发与yyy_driver关联的每个
i2c_client与之解除关联,即yyy_detach_client()函数将因此而被调用,代码清单15.17给出了函数 yyy_detach_client()的设计。

代码清单15.17 I2C设备驱动i2c_detach_client函数模板

1 static int yyy_detach_client(struct i2c_client *client)

2 {

3 int err;

4 struct yyy_data *data = i2c_get_clientdata(client);

5

6 if ((err = i2c_detach_client(client)))

7 return err;

8

9 kfree(data);

10 return 0;

11 }

上 述函数中第4行的i2c_get_clientdata()函数用于从yyy_data私有信息结构中的i2c_client的指针获取yyy_data
的指针。第6行调用I2C核心函数i2c_detach_client(),这个函数会引发i2c_adapter的client_unregister
()函数被调用。第9行代码释放yyy_data的内存。

图15.5描述了当I2C设备驱动的模块卸载函数被调用的时候引发的连锁反应的流程。





图15.5 I2C设备驱动模块卸载连锁反应

下面开始分析i2c_driver中重要函数yyy_command()的实现,它实现了针对设备的控制命令。具体的控制命令是设备相关的,如对于实时钟而言,命令将是设置时间和获取时间,而对于视频AD设备而言,命令会是设置采样方式、选择通道等。

假设yyy设备接受两类命令YYY_CMD1、YYY_CMD2,而处理这两个命令的函数分别为yyy_cmd1()、yyy_cmd2(),代码清单15.18给出了yyy_command()函数的设计。

代码清单15.18 I2C设备驱动command函数模板

1 static int yyy_command(struct i2c_client *client, unsigned int cmd, void

2 *arg)

3 {

4 switch (cmd)

5 {

6 case YYY_CMD1:

7 return yyy_cmd1(client, arg);

8 case YYY_CMD2:

9 return yyy_cmd2(client, arg);

10 default:

11 return - EINVAL;

12 }

13 }

具体命令的实现是通过组件i2c_msg消息数组,并调用I2C核心的传输、发送和接收函数,由I2C核心的传输、发送和接收函数调用I2C适配器对应的algorithm相关函数来完成的。代码清单15.19给出了一个yyy_cmd1()的例子。

代码清单15.19 I2C设备具体命令处理函数模板

1 static int yyy_cmd1(struct i2c_client *client, struct rtc_time *dt)

2 {

3 struct i2c_msg msg[2];

4 /*第一条消息是写消息*/

5 msg[0].addr = client->addr;

6 msg[0].flags = 0;

7 msg[0].len = 1;

8 msg[0].buf = &offs;

9 /*第二条消息是读消息*/

10 msg[1].addr = client->addr;

11 msg[1].flags = I2C_M_RD;

12 msg[1].len = sizeof(buf);

13 msg[1].buf = &buf[0];

14

15 i2c_transfer(client->adapter, msg, 2);

16 ...

17 }

15.4.3 Linux I2C设备驱动文件操作接口

作 为一种字符类设备,Linux I2C设备驱动文件操作接口与普通的设备驱动是完全一致的,但是在其中要使用i2c_client、i2c_driver、i2c_adapter和
i2c_algorithm结构体和I2C核心,并且对设备的读写和控制需要借助体系结构中各组成部分的协同合作。代码清单15.20给出一个I2C设备
写函数的例子。

代码清单15.20 I2C设备文件接口写函数范例

1 static ssize_t yyy_write(struct file *file, char *buf, size_t count, loff_t off)

2 {

3 struct i2c_client *client = (struct i2c_client*)file->private_data;

4 i2c_msg msg[1];

5 char *tmp;

6 int ret;

7

8 tmp = kmalloc(count, GFP_KERNEL);

9 if (tmp == NULL)

10 return - ENOMEM;

11 if (copy_from_user(tmp, buf, count))

12 {

13 kfree(tmp);

14 return - EFAULT;

15 }

16

17 msg[0].addr = client->addr;//地址

18 msg[0].flags = 0; //0为写

19 msg[0].len = count; //要写的字节数

20 msg[0].buf = tmp; //要写的数据

21 ret = i2c_transfer(client->adapter, msg, 1); //传输i2c消息

22 return (ret == 1) ? count : ret;

23 }

上述程序给出的仅仅是一个写函数的例子,具体的写操作与设备密切相关。我们通过这个例来仔细分析I2C设备读写过程中数据的流向和函数的调用关系。I2C设备的写操作经历了如下几个步骤:

① 从用户空间到字符设备驱动写函数接口,写函数构造I2C消息数组。

② 写函数把构造的I2C消息数组传递给I2C核心的传输函数i2c_transfer()。

③ I2C核心的传输函数i2c_transfer()找到对应适配器algorithm的通信方法函数master_xfer()去最终完成I2C消息的处理。

图15.6描述了从用户空间发起读写操作到algorithm进行消息传输的流程。





图15.6 I2C设备读写完整流程
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: