`
isiqi
  • 浏览: 16020099 次
  • 性别: Icon_minigender_1
  • 来自: 济南
社区版块
存档分类
最新评论

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

阅读更多

第十五章 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体系结构
在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驱动各数据结构关系
从上面的分析可知,虽然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 6 {
7 i2c_adapter_xxx_start(); /*产生开始位*/
8 /*是读消息*/
9 if (msgs[i]->flags &I2C_M_RD)
10 {
11 i2c_adapter_xxx_setaddr((msg->addr 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 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 addr 图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设备读写完整流程
通常,如果I2C设备不是一个输入输出设备或存储设备,就并不需要给I2C设备提供读写函数。许多I2C设备只是需要被设置以某种方式工作,而不是被读写。另外,I2C设备驱动的文件操作接口也不是必需的,甚至极少被需要。大多数情况下,我们只需要通过i2c-dev.c文件提供的I2C适配器设备文件接口就可完成对I2C设备的读写。
15.4.4 Linux i2c-dev.c文件分析
i2c-dev.c文件完全可以被看作一个I2C设备驱动,其结构与15.4.1~15.4.3节的描述是基本一致的,不过,它实现的一个i2c_client是虚拟的、临时的,随着设备文件的打开而产生,并随设备文件的关闭而撤销,并没有被添加到i2c_adapter的clients链表中。i2c-dev.c针对每个I2C适配器生成一个主设备为89的设备文件,实现了i2c_driver的成员函数以及文件操作接口,所以i2c-dev.c的主体是“i2c_driver成员函数 + 字符设备驱动”。
i2c-dev.c中提供i2cdev_read()、i2cdev_write()函数来对应用户空间要使用的read()和write()文件操作接口,这两个函数分别调用I2C核心的i2c_master_recv()和i2c_master_send()函数来构造1条I2C消息并引发适配器algorithm通信函数的调用,完成消息的传输,对应于图15.7所示的时序。但是,很遗憾,大多数稍微复杂一点I2C设备的读写流程并不对应于1条消息,往往需要2条甚至更多的消息来进行一次读写周期(即如图15.8所示的重复开始位RepStart模式),这种情况下,在应用层仍然调用read()、write()文件API来读写I2C设备,将不能正确地读写。许多工程师碰到过类似的问题,往往经过相当长时间的调试都没法解决I2C设备的读写,连错误的原因也无法找到,显然是对i2cdev_read()和i2cdev_write()函数的作用有所误解。
图15.7 i2cdev_read和i2cdev_write函数对应时序
图15.8 RepStart模式
鉴于上述原因,i2c-dev.c中i2cdev_read()和i2cdev_write()函数不具备太强的通用性,没有太大的实用价值,只能适用于非RepStart模式的情况。对于2条以上消息组成的读写,在用户空间需要组织i2c_msg消息数组并调用I2C_RDWR IOCTL命令。代码清单15.21给出了i2cdev_ioctl()函数的框架,其中详细列出了I2C_RDWR命令的处理过程。
代码清单15.21 i2c-dev.c中的i2cdev_ioctl函数
1 static int i2cdev_ioctl(struct inode *inode, struct file *file,
2 unsigned int cmd, unsigned long arg)
3 {
4 struct i2c_client *client = (struct i2c_client *)file->private_data;
5 ...
6 switch ( cmd ) {
7 case I2C_SLAVE:
8 case I2C_SLAVE_FORCE:
9 ... /*设置从设备地址*/
10 case I2C_TENBIT:
11 ...
12 case I2C_PEC:
13 ...
14 case I2C_FUNCS:
15 ...
16 case I2C_RDWR:
17 if (copy_from_user(&rdwr_arg,
18 (struct i2c_rdwr_ioctl_data __user *)arg,
19 sizeof(rdwr_arg)))
20 return -EFAULT;
21 /* 一次传入的消息太多 */
22 if (rdwr_arg.nmsgs > I2C_RDRW_IOCTL_MAX_MSGS)
23 return -EINVAL;
24 /*获得用户空间传入的消息数组
25 rdwr_pa = (struct i2c_msg *)
26 kmalloc(rdwr_arg.nmsgs * sizeof(struct i2c_msg),
27 GFP_KERNEL);
28 if (rdwr_pa == NULL) return -ENOMEM;
29 if (copy_from_user(rdwr_pa, rdwr_arg.msgs,
30 rdwr_arg.nmsgs * sizeof(struct i2c_msg))) {
31 kfree(rdwr_pa);
32 return -EFAULT;
33 }
34 data_ptrs = kmalloc(rdwr_arg.nmsgs * sizeof(u8 __user *), GFP_KERNEL);
35 if (data_ptrs == NULL) {
36 kfree(rdwr_pa);
37 return -ENOMEM;
38 }
39 res = 0;
40 for( i=0; i<rdwr_arg.nmsgs i></rdwr_arg.nmsgs>41 /* 限制消息的长度 */
42 if (rdwr_pa[i].len > 8192) {
43 res = -EINVAL;
44 break;
45 }
46 data_ptrs[i] = (u8 __user *)rdwr_pa[i].buf;
47 rdwr_pa[i].buf = kmalloc(rdwr_pa[i].len, GFP_KERNEL);
48 if(rdwr_pa[i].buf == NULL) {
49 res = -ENOMEM;
50 break;
51 }
52 if(copy_from_user(rdwr_pa[i].buf,
53 data_ptrs[i],
54 rdwr_pa[i].len)) {
55 ++i; /* Needs to be kfreed too */
56 res = -EFAULT;
57 break;
58 }
59 }
60 if (res 61 int j;
62 for (j = 0; j 63 kfree(rdwr_pa[j].buf);
64 kfree(data_ptrs);
65 kfree(rdwr_pa);
66 return res;
67 }
68 /*把这些消息交给通信方法去处理*/
69 res = i2c_transfer(client->adapter,
70 rdwr_pa,
71 rdwr_arg.nmsgs);
72 while(i-- > 0) { /*如果是读消息,把值拷贝到用户空间*/
73 if( res>=0 && (rdwr_pa[i].flags & I2C_M_RD)) {
74 if(copy_to_user(
75 data_ptrs[i],
76 rdwr_pa[i].buf,
77 rdwr_pa[i].len)) {
78 res = -EFAULT;
79 }
80 }
81 kfree(rdwr_pa[i].buf);
82 }
83 kfree(data_ptrs);
84 kfree(rdwr_pa);
85 return res;
86 case I2C_SMBUS:
87 ...
88 default:
89 return i2c_control(client,cmd,arg);
90 }
91 return 0;
92 }
常用的IOCTL包括I2C_SLAVE(设置从设备地址)、I2C_RETRIES(没有收到设备ACK情况下的重试次数,缺省为1)、I2C_TIMEOU(超时)以及I2C_RDWR。
由第17~19行可以看出,应用程序需要通过i2c_rdwr_ioctl_data结构体来给内核传递I2C消息,这个结构体定义如代码清单15.22,i2c_msg数组指针及消息数量就被包含在i2c_rdwr_ioctl_data结构体中。
代码清单15.22 i2c_rdwr_ioctl_data结构体
1 struct i2c_rdwr_ioctl_data {
2 struct i2c_msg __user *msgs; /* I2C消息指针 */
3 __u32 nmsgs; /* I2C消息数量 */
4 };
代码清单15.23和代码清单15.24分别演示了直接通过read()、write()接口和O_RDWR IOCTL读写I2C设备的例子。
代码清单15.23 直接通过read()/write()读写I2C设备
1 #include <stdio.h><br>2 #include <linux><br>3 #include <fcntl.h><br>4 #include <unistd.h><br>5 #include <stdlib.h><br>6 #include <sys><br>7 #include <sys><br>8 <br>9 #define I2C_RETRIES 0x0701 <br>10 #define I2C_TIMEOUT 0x0702 <br>11 #define I2C_SLAVE 0x0703 <br>12 <br>13 int main(int argc, char **argv) <br>14 { <br>15 unsigned int fd; <br>16 unsigned short mem_addr; <br>17 unsigned short size; <br>18 unsigned short idx; <br>19 #define BUFF_SIZE 32 <br>20 char buf[BUFF_SIZE]; <br>21 char cswap; <br>22 union <br>23 { <br>24 unsigned short addr; <br>25 char bytes[2]; <br>26 } tmp; <br>27 <br>28 if (argc 29 { <br>30 printf("Use:\n%s /dev/i2c-x mem_addr size\n", argv[0]); <br>31 return 0; <br>32 } <br>33 sscanf(argv[2], "%d", &amp;mem_addr); <br>34 sscanf(argv[3], "%d", &amp;size); <br>35 <br>36 if (size &gt; BUFF_SIZE) <br>37 size = BUFF_SIZE; <br>38 <br>39 fd = open(argv[1], O_RDWR); <br>40 <br>41 if (!fd) <br>42 { <br>43 printf("Error on opening the device file\n"); <br>44 return 0; <br>45 } <br>46 <br>47 ioctl(fd, I2C_SLAVE, 0x50); /* 设置eeprom地址 */ <br>48 ioctl(fd, I2C_TIMEOUT, 1); /* 设置超时 */ <br>49 ioctl(fd, I2C_RETRIES, 1); /* 设置重试次数 */ <br>50 <br>51 for (idx = 0; idx 52 { <br>53 tmp.addr = mem_addr; <br>54 cswap = tmp.bytes[0]; <br>55 tmp.bytes[0] = tmp.bytes[1]; <br>56 tmp.bytes[1] = cswap; <br>57 write(fd, &amp;tmp.addr, 2); <br>58 read(fd, &amp;buf[idx], 1); <br>59 } <br>60 buf[size] = 0; <br>61 close(fd); <br>62 printf("Read %d char: %s\n", size, buf); <br>63 return 0; <br>64 } <br>代码清单15.24 通过O_RDWR IOCTL读写I2C设备 <br>1 #include <stdio.h><br>2 #include <linux><br>3 #include <fcntl.h><br>4 #include <unistd.h><br>5 #include <stdlib.h><br>6 #include <sys><br>7 #include <sys><br>8 #include <errno.h><br>9 #include <assert.h><br>10 #include <string.h><br>11 <br>12 #define MAX_I2C_MSG 2 <br>13 <br>14 #define I2C_RETRIES 0x0701 <br>15 #define I2C_TIMEOUT 0x0702 <br>16 #define I2C_RDWR 0x0707 <br>17 <br>18 struct i2c_msg <br>19 { <br>20 __u16 addr; /* 从地址 */ <br>21 __u16 flags; <br>22 #define I2C_M_RD 0x01 <br>23 __u8 *buf; /* 消息数据指针 */ <br>24 }; <br>25 struct i2c_rdwr_ioctl_data <br>26 { <br>27 struct i2c_msg *msgs; /* i2c_msg[]指针 */ <br>28 int nmsgs; /* i2c_msg数量 */ <br>29 }; <br>30 <br>31 int main(int argc, char **argv) <br>32 { <br>33 struct i2c_rdwr_ioctl_data work_queue; <br>34 unsigned int idx; <br>35 unsigned int fd; <br>36 unsigned short start_address; <br>37 int ret; <br>38 <br>39 if (argc 40 { <br>41 printf("Usage:\n%s /dev/i2c-x start_addr\n", argv[0]); <br>42 return 0; <br>43 } <br>44 <br>45 fd = open(argv[1], O_RDWR); <br>46 <br>47 if (!fd) <br>48 { <br>49 printf("Error on opening the device file\n"); <br>50 return 0; <br>51 } <br>52 sscanf(argv[2], "%x", &amp;start_address); <br>53 work_queue.nmsgs = MAX_I2C_MSG; /* 消息数量 */ <br>54 <br>55 work_queue.msgs = (struct i2c_msg*)malloc(work_queue.nmsgs *sizeof(struct <br>56 i2c_msg)); <br>57 if (!work_queue.msgs) <br>58 { <br>59 printf("Memory alloc error\n"); <br>60 close(fd); <br>61 return 0; <br>62 } <br>63 <br>64 for (idx = 0; idx 65 { <br>66 (work_queue.msgs[idx]).len = 0; <br>67 (work_queue.msgs[idx]).addr = start_address + idx; <br>68 (work_queue.msgs[idx]).buf = NULL; <br>69 } <br>70 <br>71 ioctl(fd, I2C_TIMEOUT, 2); /* 设置超时 */ <br>72 ioctl(fd, I2C_RETRIES, 1); /* 设置重试次数 */ <br>73 <br>74 ret = ioctl(fd, I2C_RDWR, (unsigned long) &amp;work_queue); <br>75 <br>76 if (ret 77 { <br>78 printf("Error during I2C_RDWR ioctl with error code: %d\n", ret); <br>79 } <br>80 <br>81 close(fd); <br>82 return ; <br>83 } <br>15.5 实例:S3C2410 I2C总线驱动 <br>15.5.1 S3C2410 I2C控制器硬件描述 <br>S3C2410处理器内部集成了一个I2C控制器,通过4个寄存器就可方便地对其进行控制,这4个寄存器为: <br>• IICCON I2C控制寄存器 <br>• IICSTAT I2C状态寄存器 <br>• IICDS I2C收发数据移位寄存器 <br>• IICADD I2C地址寄存器 <br> S3C2410处理器内部集成的I2C控制器可支持主、从两种模式,我们主要使用其主模式。通过对IICCON、IICDS和IICADD寄存器的操作,可在I2C总线线产生开始位、停止位、数据和地址,而传输的状态则通过IICSTAT寄存器获取。 <br>15.5.2 S3C2410 I2C总线驱动总体分析 <br>根据15.3节的分析,针对S3C2410的I2C总线驱动设计主要要实现的工作包括: <br>• 设计对应于i2c_adapter_xxx_init()模板的S3C2410的模块加载函数和对应于i2c_adapter_xxx_exit()函数模板的模块卸载函数。 <br>• 设计对应于i2c_adapter_xxx_xfer()模板的S3C2410适配器的通信方法函数。 <br>针对S3C2410,functionality()函数只需简单的返回I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_PROTOCOL_MANGLING表明其支持的功能。 <br>内核源代码中的/drivers/i2c/busses/i2c-s3c2410.c文件包含了由Ben Dooksben@simtec.co.uk&gt;编写的S3C2410的I2C总线驱动,在Internet上我们还可以获得其他版本的S3C2410 I2C总线驱动源代码,如Steve Heinssh@sgi.com&gt;为三星SMDK2410参考板编写的i2c-s3c2410.c、i2c-algo-s3c2410.c文件,对比发现,它与Ben Dooks版本的驱动实现风格迥异。 <br>这里分析内核中自带的Ben Dooks版本驱动,它同时支持S3C2410和S3C2440。图15.8给出了S3C2410驱动中的主要函数与15.3节模板函数的对应关系,由于实现通信方法的方式不一样,模板的一个函数可能对应于S3C2410 I2C总线驱动的多个函数。 <br>图15.8 I2C总线驱动模板与S3C2410 I2C总线驱动的映射 <br>15.5.3 S3C2410 I2C适配器驱动模块加载与卸载 <br>I2C适配器驱动被作为一个单独的模块被加载进内核,在模块的加载和卸载函数中,只需注册和注销一个platform_driver结构体,如代码清单15.25。 <br>代码清单15.25 S3C2410 I2C总线驱动模块加载与卸载 <br>1 static int __init i2c_adap_s3c_init(void) <br>2 { <br>3 int ret; <br>4 <br>5 ret = platform_driver_register(&amp;s3c2410_i2c_driver); <br>6 if (ret == 0) { <br>7 ret = platform_driver_register(&amp;s3c2440_i2c_driver); <br>8 if (ret) <br>9 platform_driver_unregister(&amp;s3c2410_i2c_driver); <br>10 } <br>11 <br>12 return ret; <br>13 } <br>14 <br>15 static void __exit i2c_adap_s3c_exit(void) <br>16 { <br>17 platform_driver_unregister(&amp;s3c2410_i2c_driver); <br>18 platform_driver_unregister(&amp;s3c2440_i2c_driver); <br>19 } <br>20 module_init(i2c_adap_s3c_init); <br>21 module_exit(i2c_adap_s3c_exit); <br>platform_driver结构体包含了具体适配器的probe()函数、remove()函数、resume()函数指针等信息,它需要被定义和赋值,如代码清单15.26。 <br>代码清单15.26 platform_driver结构体 <br>1 static struct platform_driver s3c2410_i2c_driver = { <br>2 .probe = s3c24xx_i2c_probe, <br>3 .remove = s3c24xx_i2c_remove, <br>4 .resume = s3c24xx_i2c_resume, <br>5 .driver = { <br>6 .owner = THIS_MODULE, <br>7 .name = "s3c2410-i2c", <br>8 }, <br>9 }; <br>当通过Linux内核源代码/drivers/base/platform.c文件中定义platform_driver_unregister()函数注册platform_driver结构体时,其中probe指针指向的s3c24xx_i2c_probe()函数将被调用以初始化适配器硬件,如代码清单15.27。 <br>代码清单15.27 S3C2410 I2C总线驱动中的s3c24xx_i2c_probe函数 <br>1 static int s3c24xx_i2c_probe(struct platform_device *pdev) <br>2 { <br>3 struct s3c24xx_i2c *i2c = &amp;s3c24xx_i2c; <br>4 struct resource *res; <br>5 int ret; <br>6 <br>7 /* 使能I2C的时钟 */ <br>8 i2c-&gt;dev = &amp;pdev-&gt;dev; <br>9 i2c-&gt;clk = clk_get(&amp;pdev-&gt;dev, "i2c"); <br>10 if (IS_ERR(i2c-&gt;clk)) { <br>11 dev_err(&amp;pdev-&gt;dev, "cannot get clock\n"); <br>12 ret = -ENOENT; <br>13 goto out; <br>14 } <br>15 clk_enable(i2c-&gt;clk); <br>16 <br>17 /* 映射寄存器 */ <br>18 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); <br>19 if (res == NULL) { <br>20 dev_err(&amp;pdev-&gt;dev, "cannot find IO resource\n"); <br>21 ret = -ENOENT; <br>22 goto out; <br>23 } <br>24 i2c-&gt;ioarea = request_mem_region(res-&gt;start, (res-&gt;end-res-&gt;start)+1, <br>25 pdev-&gt;name); <br>26 if (i2c-&gt;ioarea == NULL) { <br>27 dev_err(&amp;pdev-&gt;dev, "cannot request IO\n"); <br>28 ret = -ENXIO; <br>29 goto out; <br>30 } <br>31 <br>32 i2c-&gt;regs = ioremap(res-&gt;start, (res-&gt;end-res-&gt;start)+1); <br>33 if (i2c-&gt;regs == NULL) { <br>34 dev_err(&amp;pdev-&gt;dev, "cannot map IO\n"); <br>35 ret = -ENXIO; <br>36 goto out; <br>37 } <br>38 <br>39 /* 设置I2C的信息块 */ <br>40 i2c-&gt;adap.algo_data = i2c; <br>41 i2c-&gt;adap.dev.parent = &amp;pdev-&gt;dev; <br>42 <br>43 /* 初始化I2C控制器 */ <br>44 ret = s3c24xx_i2c_init(i2c); <br>45 if (ret != 0) <br>46 goto out; <br>47 <br>48 /* 申请中断 */ <br>49 res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); <br>50 if (res == NULL) { <br>51 ret = -ENOENT; <br>52 goto out; <br>53 } <br>54 ret = request_irq(res-&gt;start, s3c24xx_i2c_irq, SA_INTERRUPT, <br>55 pdev-&gt;name, i2c); <br>56 if (ret != 0) { <br>57 goto out; <br>58 } <br>59 i2c-&gt;irq = res; <br>60 <br>61 ret = i2c_add_adapter(&amp;i2c-&gt;adap); /*添加i2c_adapter*/ <br>62 if (ret 63 goto out; <br>64 } <br>65 <br>66 platform_set_drvdata(pdev, i2c); <br>67 <br>68 out: <br>69 if (ret 70 s3c24xx_i2c_free(i2c); <br>71 return ret; <br>72 } <br>上述代码中的主体工作是使能硬件并申请I2C适配器使用的I/O地址、中断号等,在这些工作都完成无误后,通过I2C核心提供的i2c_add_adapter()函数添加这个适配器。因为S3C2410内部集成I2C控制器,可以确定I2C适配器一定存在,s3c24xx_i2c_probe()函数虽然命名“探测”,但实际没有也不必进行任何探测工作,之所以这样命名完全是一种设计习惯。 <br>与s3c24xx_i2c_probe()函数完成相反功能的函数是s3c24xx_i2c_remove()函数,它在适配器模块卸载函数调用platform_driver_unregister()函数时被通过platform_driver的remove指针方式调用。xxx_i2c_remove()的设计模板见代码清单15.28。 <br>代码清单15.28 S3C2410 I2C总线驱动中的s3c24xx_i2c_remove函数 <br>1 static int s3c24xx_i2c_remove(struct platform_device *pdev) <br>2 { <br>3 struct s3c24xx_i2c *i2c = platform_get_drvdata(pdev); <br>4 <br>5 if (i2c != NULL) { <br>6 s3c24xx_i2c_free(i2c); <br>7 platform_set_drvdata(pdev, NULL); <br>8 } <br>9 <br>10 return 0; <br>11 } <br>代码清单15.27和15.28中用到的s3c24xx_i2c结构体进行适配器所有信息的封装,类似于私有信息结构体,它与代码清单15.12给出的xxx_i2c结构体模板对应。代码清单15.29给出了s3c24xx_i2c结构体的定义,以及驱动模块定义的一个s3c24xx_i2c结构体全局实例。 <br>代码清单15.29 s3c24xx_i2c结构体 <br>1 struct s3c24xx_i2c { <br>2 spinlock_t lock; <br>3 wait_queue_head_t wait; <br>4 struct i2c_msg *msg; <br>5 unsigned int msg_num; <br>6 unsigned int msg_idx; <br>7 unsigned int msg_ptr; <br>8 enum s3c24xx_i2c_state state; <br>9 void __iomem *regs; <br>10 struct clk *clk; <br>11 struct device *dev; <br>12 struct resource *irq; <br>13 struct resource *ioarea; <br>14 struct i2c_adapter adap; <br>15 }; <br>16 <br>17 static struct s3c24xx_i2c s3c24xx_i2c = { <br>18 .lock = SPIN_LOCK_UNLOCKED, /*自旋锁未锁定*/ <br>19 .wait = __WAIT_QUEUE_HEAD_INITIALIZER(s3c24xx_i2c.wait),/*等待队列初始化*/ <br>20 .adap = { <br>21 .name = "s3c2410-i2c", <br>22 .owner = THIS_MODULE, <br>23 .algo = &amp;s3c24xx_i2c_algorithm, <br>24 .retries = 2, <br>25 .class = I2C_CLASS_HWMON, <br>26 }, <br>27 }; <br>15.5.4 S3C2410 I2C总线通信方法 <br>由代码清单15.29第23行可以看出,I2C适配器对应的i2c_algorithm结构体实例为s3c24xx_i2c_algorithm,代码清单15.30给出了s3c24xx_i2c_algorithm的定义。 <br>代码清单15.30 S3C2410的i2c_algorithm结构体 <br>1 static struct i2c_algorithm s3c24xx_i2c_algorithm = { <br>2 .master_xfer = s3c24xx_i2c_xfer, <br>3 .functionality = s3c24xx_i2c_func, <br>4 }; <br>上述代码第1行指定了S3C2410 I2C总线通信传输函数s3c24xx_i2c_xfer(),这个函数是如此的关键,所有I2C总线上对设备的访问最终应该由它来完成,代码清单15.31给出了这个重要函数以及其依赖的s3c24xx_i2c_doxfer()函数和s3c24xx_i2c_message_start()函数的源代码。 <br>代码清单15.31 S3C2410 I2C总线驱动的master_xfer函数 <br>1 static int s3c24xx_i2c_xfer(struct i2c_adapter *adap, <br>2 struct i2c_msg *msgs, int num) <br>3 { <br>4 struct s3c24xx_i2c *i2c = (struct s3c24xx_i2c *)adap-&gt;algo_data; <br>5 int retry; <br>6 int ret; <br>7 <br>8 for (retry = 0; retry retries; retry++) { <br>9 ret = s3c24xx_i2c_doxfer(i2c, msgs, num); <br>10 if (ret != -EAGAIN) <br>11 return ret; <br>12 udelay(100); <br>13 } <br>14 <br>15 return -EREMOTEIO; <br>16 } <br>17 <br>18 static int s3c24xx_i2c_doxfer(struct s3c24xx_i2c *i2c, struct i2c_msg *msgs, int num) <br>19 { <br>20 unsigned long timeout; <br>21 int ret; <br>22 <br>23 ret = s3c24xx_i2c_set_master(i2c); <br>24 if (ret != 0) { <br>25 ret = -EAGAIN; <br>26 goto out; <br>27 } <br>28 <br>29 spin_lock_irq(&amp;i2c-&gt;lock); <br>30 i2c-&gt;msg = msgs; <br>31 i2c-&gt;msg_num = num; <br>32 i2c-&gt;msg_ptr = 0; <br>33 i2c-&gt;msg_idx = 0; <br>34 i2c-&gt;state = STATE_START; <br>35 s3c24xx_i2c_enable_irq(i2c); <br>36 s3c24xx_i2c_message_start(i2c, msgs); <br>37 spin_unlock_irq(&amp;i2c-&gt;lock); <br>38 <br>39 timeout = wait_event_timeout(i2c-&gt;wait, i2c-&gt;msg_num == 0, HZ * 5); <br>40 <br>41 ret = i2c-&gt;msg_idx; <br>42 <br>43 if (timeout == 0) <br>44 dev_dbg(i2c-&gt;dev, "timeout\n"); <br>45 else if (ret != num) <br>46 dev_dbg(i2c-&gt;dev, "incomplete xfer (%d)\n", ret); <br>47 <br>48 msleep(1);/* 确保停止位已经被传递 */ <br>49 <br>50 out: <br>51 return ret; <br>52 } <br>53 <br>54 static void s3c24xx_i2c_message_start(struct s3c24xx_i2c *i2c, <br>55 struct i2c_msg *msg) <br>56 { <br>57 unsigned int addr = (msg-&gt;addr &amp; 0x7f) 58 unsigned long stat; <br>59 unsigned long iiccon; <br>60 <br>61 stat = 0; <br>62 stat |= S3C2410_IICSTAT_TXRXEN; <br>63 <br>64 if (msg-&gt;flags &amp; I2C_M_RD) { <br>65 stat |= S3C2410_IICSTAT_MASTER_RX; <br>66 addr |= 1; <br>67 } else <br>68 stat |= S3C2410_IICSTAT_MASTER_TX; <br>69 if (msg-&gt;flags &amp; I2C_M_REV_DIR_ADDR) <br>70 addr ^= 1; <br>71 <br>72 s3c24xx_i2c_enable_ack(i2c);/*如果要使能ACK,则使能*/ <br>73 <br>74 iiccon = readl(i2c-&gt;regs + S3C2410_IICCON); <br>75 writel(stat, i2c-&gt;regs + S3C2410_IICSTAT); <br>76 writeb(addr, i2c-&gt;regs + S3C2410_IICDS); <br>77 <br>78 udelay(1);/*在发送新的开始位前延迟1位*/ <br>79 writel(iiccon, i2c-&gt;regs + S3C2410_IICCON); <br>80 stat |= S3C2410_IICSTAT_START; <br>81 writel(stat, i2c-&gt;regs + S3C2410_IICSTAT); <br>82 } <br>s3c24xx_i2c_xfer()函数调用s3c24xx_i2c_doxfer()函数传输I2C消息,第8行的循环意味着最多可以重试adap-&gt;retries次。 <br>s3c24xx_i2c_doxfer()首先将S3C2410的I2C适配器设置为I2C主设备,其后初始化s3c24xx_i2c结构体,使能I2C中断,并调用s3c24xx_i2c_message_start()函数启动I2C消息的传输。 <br>s3c24xx_i2c_message_start()函数写S3C2410适配器对应的控制寄存器向I2C从设备传递开始位和从设备地址。 <br>上述代码只是启动了I2C消息数组的传输周期,并没有完整实现图15.3中给出的algorithm master_xfer流程。这个流程的完整实现需要借助I2C适配器上的中断来步步推进。代码清单15.32给出了S3C2410 I2C适配器中断处理函数以及其依赖的i2s_s3c_irq_nextbyte()函数的源代码。 <br>代码清单15.32 S3C2410 I2C适配器中断处理函数 <br>1 static irqreturn_t s3c24xx_i2c_irq(int irqno, void *dev_id, <br>2 struct pt_regs *regs) <br>3 { <br>4 struct s3c24xx_i2c *i2c = dev_id; <br>5 unsigned long status; <br>6 unsigned long tmp; <br>7 <br>8 status = readl(i2c-&gt;regs + S3C2410_IICSTAT); <br>9 if (status &amp; S3C2410_IICSTAT_ARBITR) { <br>10 ... <br>11 } <br>12 <br>13 if (i2c-&gt;state == STATE_IDLE) { <br>14 tmp = readl(i2c-&gt;regs + S3C2410_IICCON); <br>15 tmp &amp;= ~S3C2410_IICCON_IRQPEND; <br>16 writel(tmp, i2c-&gt;regs + S3C2410_IICCON); <br>17 goto out; <br>18 } <br>19 <br>20 i2s_s3c_irq_nextbyte(i2c, status);/* 把传输工作进一步推进 */ <br>21 <br>22 out: <br>23 return IRQ_HANDLED; <br>24 } <br>25 <br>26 static int i2s_s3c_irq_nextbyte(struct s3c24xx_i2c *i2c, unsigned long iicstat) <br>27 { <br>28 unsigned long tmp; <br>29 unsigned char byte; <br>30 int ret = 0; <br>31 <br>32 switch (i2c-&gt;state) { <br>33 case STATE_IDLE: <br>34 goto out; <br>35 break; <br>36 case STATE_STOP: <br>37 s3c24xx_i2c_disable_irq(i2c); <br>38 goto out_ack; <br>39 case STATE_START: <br>40 /* 我们最近做的一件事是启动一个新I2C消息*/ <br>41 if (iicstat &amp; S3C2410_IICSTAT_LASTBIT &amp;&amp; <br>42 !(i2c-&gt;msg-&gt;flags &amp; I2C_M_IGNORE_NAK)) { <br>43 /* 没有收到ACK */ <br>44 s3c24xx_i2c_stop(i2c, -EREMOTEIO); <br>45 goto out_ack; <br>46 } <br>47 <br>48 if (i2c-&gt;msg-&gt;flags &amp; I2C_M_RD) <br>49 i2c-&gt;state = STATE_READ; <br>50 else <br>51 i2c-&gt;state = STATE_WRITE; <br>52 <br>53 /* 仅一条消息,而且长度为0(主要用于适配器探测),发送停止位*/ <br>54 if (is_lastmsg(i2c) &amp;&amp; i2c-&gt;msg-&gt;len == 0) { <br>55 s3c24xx_i2c_stop(i2c, 0); <br>56 goto out_ack; <br>57 } <br>58 <br>59 if (i2c-&gt;state == STATE_READ) <br>60 goto prepare_read; <br>61 /* 进入写状态 */ <br>62 case STATE_WRITE: <br>63 retry_write: <br>64 if (!is_msgend(i2c)) { <br>65 byte = i2c-&gt;msg-&gt;buf[i2c-&gt;msg_ptr++]; <br>66 writeb(byte, i2c-&gt;regs + S3C2410_IICDS); <br>67 <br>68 } else if (!is_lastmsg(i2c)) { <br>69 /* 推进到下一条消息 */ <br>70 i2c-&gt;msg_ptr = 0; <br>71 i2c-&gt;msg_idx ++; <br>72 i2c-&gt;msg++; <br>73 <br>74 /* 检查是否要为该消息产生开始位 */ <br>75 if (i2c-&gt;msg-&gt;flags &amp; I2C_M_NOSTART) { <br>76 if (i2c-&gt;msg-&gt;flags &amp; I2C_M_RD) { <br>77 s3c24xx_i2c_stop(i2c, -EINVAL); <br>78 } <br>79 goto retry_write; <br>80 } else { <br>81 /* 发送新的开始位 */ <br>82 s3c24xx_i2c_message_start(i2c, i2c-&gt;msg); <br>83 i2c-&gt;state = STATE_START; <br>84 } <br>85 } else { <br>86 s3c24xx_i2c_stop(i2c, 0);/* send stop */ <br>87 } <br>88 break; <br>89 case STATE_READ: <br>90 /* 有一个字节可读,看是否还有消息要处理 */ <br>91 if (!(i2c-&gt;msg-&gt;flags &amp; I2C_M_IGNORE_NAK) &amp;&amp; <br>92 !(is_msglast(i2c) &amp;&amp; is_lastmsg(i2c))) { <br>93 <br>94 if (iicstat &amp; S3C2410_IICSTAT_LASTBIT) { <br>95 dev_dbg(i2c-&gt;dev, "READ: No Ack\n"); <br>96 <br>97 s3c24xx_i2c_stop(i2c, -ECONNREFUSED); <br>98 goto out_ack; <br>99 } <br>100 } <br>101 byte = readb(i2c-&gt;regs + S3C2410_IICDS); <br>102 i2c-&gt;msg-&gt;buf[i2c-&gt;msg_ptr++] = byte; <br>103 <br>104 prepare_read: <br>105 if (is_msglast(i2c)) {/* last byte of buffer */ <br>106 if (is_lastmsg(i2c)) <br>107 s3c24xx_i2c_disable_ack(i2c); <br>108 <br>109 } else if (is_msgend(i2c)) { <br>110 /* 还有消息要处理吗? */ <br>111 if (is_lastmsg(i2c)) { <br>112 s3c24xx_i2c_stop(i2c, 0);/* last message, send stop and complete */ <br>113 } else { <br>114 /* 推进到下一条消息 */ <br>115 i2c-&gt;msg_ptr = 0; <br>116 i2c-&gt;msg_idx++; <br>117 i2c-&gt;msg++; <br>118 } <br>119 } <br>120 break; <br>121 } <br>122 <br>123 /* irq清除 */ <br>124 out_ack: <br>125 tmp = readl(i2c-&gt;regs + S3C2410_IICCON); <br>126 tmp &amp;= ~S3C2410_IICCON_IRQPEND; <br>127 writel(tmp, i2c-&gt;regs + S3C2410_IICCON); <br>128 out: <br>129 return ret; <br>130 } <br>中断处理函数s3c24xx_i2c_irq()主要通过调用i2s_s3c_irq_nextbyte()函数进行传输工作的进一步推进。i2s_s3c_irq_nextbyte()函数通过switch(i2c-&gt;state)语句分成i2c-&gt;state的不同状态进行处理,在每种状态下,先检查i2c-&gt;state的状态与硬件寄存器应该处于的状态是否一致,如果不一致,则证明有误,直接返回。当I2C处于读状态STATE_READ或写状态STATE_WRITE时,通过is_lastmsg()函数判断是否传输的是最后一条I2C消息,如果是,则产生停止位,否则通过i2c-&gt;msg_idx++、i2c-&gt;msg++推进到下一条消息。 <br>15.6 实例:SAA7113H视频AD芯片I2C设备驱动 <br>15.6.1 SAA7113H视频AD芯片硬件描述 <br>如图15.9,SAA7113H是飞利浦半导体推出的9位视频AD芯片,它可以选择4路视频输入中的1路,并采样为9位的数字信号。 <br>图15.9 SAA7113H输入、输出与I2C接口 <br>对SAA7113H输入通道的选择以及采样方式的设置都需通过其I2C接口进行,以0x4A地址可读SAA7113H寄存器,以0x4B可写SAA7113H寄存器。SAA7113H的I2C接口连接在S3C2410的I2C控制器上,作为S3C2410 I2C控制器的从设备。SAA7113H的写时序与图15.7给出的写时序是一致的,但是读时序则需通过2条I2C消息解决,在第1条消息中应该给SAA7113H写入寄存器子地址,如图15.10。 <br>图15.10 SAA7113H读时序 <br>15.6.2 SAA7113H视频AD芯片驱动模块加载与卸载 <br>由于不需要给SAA7113H实现文件操作接口,在设备驱动模块的加载和卸载函数中均不再需要注册和注销字符设备的操作,代码清单15.33给出了SAA7113H设备驱动模块加载和卸载的源代码。 <br>代码清单15.33 SAA7113H设备驱动模块加载和卸载函数 <br>1 static int __init saa711x_init (void) <br>2 { <br>3 return i2c_add_driver(&amp;i2c_driver_saa711x); //注册i2c_driver <br>4 } <br>5 <br>6 static void __exit saa711x_exit (void) <br>7 { <br>8 i2c_del_driver(&amp;i2c_driver_saa711x); //注销i2c_driver <br>9 } <br>10 <br>11 module_init(saa711x_init); <br>12 module_exit(saa711x_exit); <

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics