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