Chinaunix首页 | 论坛 | 博客
  • 博客访问: 268049
  • 博文数量: 60
  • 博客积分: 2010
  • 博客等级: 大尉
  • 技术积分: 820
  • 用 户 组: 普通用户
  • 注册时间: 2010-01-18 00:28
文章分类

全部博文(60)

文章存档

2010年(60)

我的朋友

分类:

2010-04-20 11:49:05

Linux I2C核心、总线与设备驱动
本章导读
I2C总线仅仅使用SCL、SDA两根信号线就实现了设备之间的数据交互,极大地简化对硬件资源和PCB板布线空间的占用。因此,I2C总线被非常广泛地应用在EEPROM、实时钟、小型LCD等设备与CPU的接口中。
Linux定义了系统的I2C驱动体系结构,在Linux系统中,I2C驱动由3部分组成,即I2C核心、I2C总线驱动和I2C设备驱动。这3部分相互协作,形成了非常通用、可适应性很强的I2C框架。
本章第1节将对Linux I2C体系结构进行分析,讲明3个组成部分各自的功能及相互联系。
第2节将对Linux I2C核心进行分析,解释i2c-core.c文件的功能和主要函数的实现。
第3、4节将分别详细介绍I2C总线驱动和I2C设备驱动的编写方法,给出可供参考的设计模板。
第5、6节将以第3、4节给出的设计模板为基础,讲解S3C2410 ARM处理器I2C总线驱动及挂接在上的SAA7113H视频模拟/数字转换芯片设备驱动的编写方法。
15.1 Linux I2C体系结构
Linux的I2C体系结构分为3个组成部分:
•   I2C核心
I2C 核心提供了I2C总线驱动和设备驱动的注册、注销方法,I2C通信方法(即“algorithm”,笔者认为直译为“运算方法”并不合适,为免引起误解, 下文将直接使用“algorithm”)上层的、与具体适配器无关的代码以及探测设备、检测设备地址的上层代码等。
•   I2C总线驱动
I2C总线驱动是对I2C硬件体系结构中适配器端的实现,适配器可由CPU控制,甚至直接集成在CPU内部。
I2C总线驱动主要包含了I2C适配器数据结构i2c_adapter、I2C适配器的algorithm数据结构i2c_algorithm和控制I2C适配器产生通信信号的函数。
经由I2C总线驱动的代码,我们可以控制I2C适配器以主控方式产生开始位、停止位、读写周期,以及以从设备方式被读写、产生ACK等。
•   I2C设备驱动
I2C设备驱动是对I2C硬件体系结构中设备端的实现,设备一般挂接在受CPU控制的I2C适配器上,通过I2C适配器与CPU交换数据。
I2C设备驱动主要包含了数据结构i2c_driver和i2c_client,我们需要根据具体设备实现其中的成员函数。
图15.1 Linux I2C体系结构

图15.1 Linux I2C体系结构在Linux 2.6内核中,所有的I2C设备都被在sysfs文件系统中显示,存在于/sys/bus/i2c/目录,以适配器地址和芯片地址的形式列出,如:
$ tree /sys/bus/i2c/
/sys/bus/i2c/
|-- devices
| |-- 0-0048 -> ../../../devices/legacy/i2c-0/0-0048
| |-- 0-0049 -> ../../../devices/legacy/i2c-0/0-0049
| |-- 0-004a -> ../../../devices/legacy/i2c-0/0-004a
| |-- 0-004b -> ../../../devices/legacy/i2c-0/0-004b
| |-- 0-004c -> ../../../devices/legacy/i2c-0/0-004c
| |-- 0-004d -> ../../../devices/legacy/i2c-0/0-004d
| |-- 0-004e -> ../../../devices/legacy/i2c-0/0-004e
| `-- 0-004f -> ../../../devices/legacy/i2c-0/0-004f
`-- drivers
|-- i2c_adapter
`-- lm75
       |-- 0-0048 -> ../../../../devices/legacy/i2c-0/0-0048
       |-- 0-0049 -> ../../../../devices/legacy/i2c-0/0-0049
       |-- 0-004a -> ../../../../devices/legacy/i2c-0/0-004a
       |-- 0-004b -> ../../../../devices/legacy/i2c-0/0-004b
       |-- 0-004c -> ../../../../devices/legacy/i2c-0/0-004c
       |-- 0-004d -> ../../../../devices/legacy/i2c-0/0-004d
       |-- 0-004e -> ../../../../devices/legacy/i2c-0/0-004e
       `-- 0-004f -> ../../../../devices/legacy/i2c-0/0-004f
在Linux内核源代码中的drivers目录下包含一个i2c目录,而在i2c目录下又包含如下文件和文件夹:
•   i2c-core.c
这个文件实现了I2C核心的功能以及/proc/bus/i2c*接口。
•   i2c-dev.c
实 现了I2C适配器设备文件的功能,每一个I2C适配器都被分配一个设备。通过适配器访问设备时的主设备号都为89,次设备号为0~255。应用程序通过 “i2c-%d” (i2c-0, i2c-1, ..., i2c-10, ...)文件名并使用文件操作接口open()、write()、read()、ioctl()和close()等来访问这个设备。
i2c-dev.c并没有针对特定的设备而设计,只是提供了通用的read()、write()和ioctl()等接口,应用层可以借用这些接口访问挂接在适配器上的I2C设备的存储空间或寄存器并控制I2C设备的工作方式。
•   chips文件夹
这个目录中包含了一些特定的I2C设备驱动,如Dallas公司的DS1337实时钟芯片、EPSON公司的RTC8564实时钟芯片和I2C接口的EEPROM驱动等。
•   busses文件夹
这个文件中包含了一些I2C总线的驱动,如S3C2410的I2C控制器驱动为i2c-s3c2410.c。
•   algos文件夹
实现了一些I2C总线适配器的algorithm。
此外,内核中的i2c.h这个头文件对i2c_driver、i2c_client、i2c_adapter和i2c_algorithm这4个数据结构进行了定义。理解这4个结构体的作用十分关键,代码清单15.1、15.2、15.3、15.4分别给出了它们的定义。
代码清单15.1 i2c_adapter结构体
1   struct i2c_adapter {
2 struct module *owner;/*所属模块*/
3   unsigned int id; /*algorithm的类型,定义于i2c-id.h,以I2C_ALGO_开始*/
4   unsigned int class;
5   struct i2c_algorithm *algo;/*总线通信方法结构体指针 */
6   void *algo_data; /* algorithm数据 */
7   int (*client_register)(struct i2c_client *);   /*client注册时调用*/
8   int (*client_unregister)(struct i2c_client *); /*client注销时调用*/
9   struct semaphore bus_lock; /*控制并发访问的自旋锁*/
10 struct semaphore clist_lock;
11 int timeout;
12 int retries; /*重试次数*/
13 struct device dev;   /* 适配器设备 */
14 struct class_device class_dev; /* 类设备 */
15 int nr;
16 struct list_head clients;   /* client链表头*/
17 struct list_head list;
18 char name[I2C_NAME_SIZE];   /*适配器名称*/
19 struct completion dev_released; /*用于同步*/
20 struct completion class_dev_released;
21};
代码清单15.2 i2c_algorithm结构体
1   struct i2c_algorithm {
2 int (*master_xfer)(struct i2c_adapter *adap,struct i2c_msg *msgs,
3                   int num);   /*i2c传输函数指针*/
4 int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr, /*smbus传输函数指针*/
5                   unsigned short flags, char read_write,
6                   u8 command, int size, union i2c_smbus_data * data);
7 int (*slave_send)(struct i2c_adapter *,char*,int);/*当i2c适配器为slave时,发送函数*/
8 int (*slave_recv)(struct i2c_adapter *,char*,int); /*当i2c适配器为slave时,接收函数*/
9 int (*algo_control)(struct i2c_adapter *, unsigned int, unsigned long); /*类似ioctl*/
10   u32 (*functionality) (struct i2c_adapter *);/*返回适配器支持的功能*/
11 };
上述代码第4行对应为SMBus传输函数指针,SMBus大部分基于I2C总线规范,SMBus不需增加额外引脚。与I2C总线相比,SMBus增加了一些新的功能特性,在访问时序也有一定的差异。
代码清单15.3 i2c_driver结构体
1   struct i2c_driver {
2 int id;
3 unsigned int class;
4 int (*attach_adapter)(struct i2c_adapter *); /*依附i2c_adapter函数指针 */
5 int (*detach_adapter)(struct i2c_adapter *); /*脱离i2c_adapter函数指针*/
6 int (*detach_client)(struct i2c_client *);   /*i2c client脱离函数指针*/
7 int (*command)(struct i2c_client *client,unsigned int cmd, void *arg); /*类似ioctl*/
8 struct device_driver driver; /*设备驱动结构体*/
9 struct list_head list;       /*链表头*/
10 };
代码清单15.4 i2c_client结构体
1   struct i2c_client {
2 unsigned int flags;   /* 标志 */
3 unsigned short addr;     /* 低7位为芯片地址 */
4 struct i2c_adapter *adapter; /*依附的i2c_adapter*/
5 struct i2c_driver *driver; /*依附的i2c_driver */
6 int usage_count;     /* 访问计数   */
7 struct device dev;     /* 设备结构体 */
8 struct list_head list;    /* 链表头 */
9 char name[I2C_NAME_SIZE]; /* 设备名称 */
10   struct completion released; /* 用于同步 */
11 };
下面分析一下i2c_driver、i2c_client、i2c_adapter和i2c_algorithm这4个数据结构的作用及其盘根错节的关系。
•   i2c_adapter与i2c_algorithm
i2c_adapter 对应于物理上的一个适配器,而i2c_algorithm对应一套通信方法。一个I2C适配器需要i2c_algorithm中提供的通信函数来控制适配 器上产生特定的访问周期。缺少i2c_algorithm的i2c_adapter什么也做不了,因此i2c_adapter中包含其使用的 i2c_algorithm的指针。
i2c_algorithm中的关键函数master_xfer()用于产生I2C访问周期需要的信号,以i2c_msg(即I2C消息)为单位。i2c_msg结构体也非常关键,代码清单15.5给出了它的定义。
代码清单15.5 i2c_msg结构体
1 struct i2c_msg {
2   __u16 addr; /* 设备地址*/
3 __u16 flags; /* 标志 */
4 __u16 len;   /* 消息长度*/
5 __u8 *buf;   /* 消息数据*/
6 };
•   i2c_driver与i2c_client
i2c_driver对应一套驱动方法,是纯粹的用于辅助作用的数据结构,它不对应于任何的物理实体。i2c_client对应于真实的物理设备,每个I2C设备都需要一个i2c_client来描述。i2c_client一般被包含在i2c字符设备的私有信息结构体中。
i2c_driver 与i2c_client发生关联的时刻在i2c_driver的attach_adapter()函数被运行时。attach_adapter()会探测 物理设备,当确定一个client存在时,把该client使用的i2c_client数据结构的adapter指针指向对应的i2c_adapter, driver指针指向该i2c_driver,并会调用i2c_adapter的client_register()函数。相反的过程发生在 i2c_driver 的detach_client()函数被调用的时候。
•   i2c_adpater与i2c_client
i2c_adpater 与i2c_client的关系与I2C硬件体系中适配器和设备的关系一致,即i2c_client依附于i2c_adpater。由于一个适配器上可以连 接多个I2C设备,所以一个i2c_adpater也可以被多个i2c_client依附,i2c_adpater中包括依附于它的i2c_client 的链表。
假设I2C总线适配器xxx上有两个使用相同驱动程序的yyy I2C设备,在打开该I2C总线的设备结点后相关数据结构之间的逻辑组织关系将如图15.2所示。
图15.2 I2C驱动各数据结构关系

图15.2 I2C驱动各数据结构关系 从上面的分析可知,虽然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 flags &I2C_M_RD)
10     {
11    i2c_adapter_xxx_setaddr((msg->addr buf, msgs->len); /*读取msgs
14
      ->len长的数据到msgs->buf*/
15     }
16     else
17    /*是写消息*/
18     {
19    i2c_adapter_xxx_setaddr(msg->addr buf, msgs->len); /*读取msgs
22       ->len长的数据到msgs->buf*/
23     }
24 }
25 i2c_adapter_xxx_stop(); /*产生停止位*/
26 }
上 述代码实际上给出了一个master_xfer()函数处理I2C消息数组的流程,对于数组中的每个消息,判断消息类型,若为读消息,则赋从设备地址为 (msg->addr addr 图15.3 algorithm中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 }
 

阅读(956) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~