继续上一篇博文没讲完的内容“针对 RepStart 型i2c设备的驱动模型”,其中涉及的内容有:i2c_client 的注册、i2c_driver 的注册、驱动程序的编写。
一、i2c 设备的注册分析:在新版本内核的i2c驱动模型中,支持多种方式来注册 i2c 设备,在Documentation/i2c/instantiating-devices文件中有讲到,在内核中对应的抽象数据结构就是 struct i2c_client。
(1)Declare the I2C devices by bus number 以i2c总线号来声明设备:主要适用于嵌入式系统设备,系统外的设备比较固定。
通过 struct i2c_board_info 结构来声明,使用 i2c_register_board_info() 函数来注册。
1 //在include/linux/i2c.h中 2 struct i2c_board_info { 3 char type[I2C_NAME_SIZE]; //设备名称 4 unsigned short flags; 5 unsigned short addr; //设备地址 6 void *platform_data; 7 struct dev_archdata *archdata; 8 struct device_node *of_node; 9 struct acpi_dev_node acpi_node; 10 int irq; 11 }; 12 13 //在drivers/i2c/i2c-boardinfo.c中 14 LIST_HEAD(__i2c_board_list); 15 EXPORT_SYMBOL_GPL(__i2c_board_list); 16 17 int __init i2c_register_board_info(int busnum,struct i2c_board_info const *info, unsigned len) 18 { 19 int status; 20 21 /* dynamic bus numbers will be assigned after the last static one */ 22 if (busnum >= __i2c_first_dynamic_bus_num) 23 __i2c_first_dynamic_bus_num = busnum + 1; 24 //__i2c_first_dynamic_bus_num是个全局变量,初始值为0,因此,他的值始终比busnum大1!这一点可以解决上一篇博文中提出的一个问题。 25 26 for (status = 0; len; len--, info++) { 27 struct i2c_devinfo *devinfo; 28 29 devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL); 30 if (!devinfo) { 31 pr_debug("i2c-core: can't register boardinfo! "); 32 status = -ENOMEM; 33 break; 34 } 35 36 devinfo->busnum = busnum; 37 devinfo->board_info = *info; 38 list_add_tail(&devinfo->list, &__i2c_board_list);//将这个 i2c_board_info 添加到该总线的 __i2c_board_list i2c设备链表中 39 } 40 return status; 41 }
问题思考:board_list 链表结构是怎样的?是一条 i2c总线一条链表还是类似于哈希链表?
寻找答案:答案是一条i2c设备链表,里边链着的是所有i2c总线上的设备,分析i2c-core.c中的函数:
1 i2c_scan_static_board_info(struct i2c_adapter *adapter) 2 list_for_each_entry(devinfo, &__i2c_board_list, list) { 3 if (devinfo->busnum == adapter->nr && !i2c_new_device(adapter,&devinfo->board_info)) 4 dev_err(&adapter->dev,"Can't create device at 0x%02x ",devinfo->board_info.addr); 5 } 6 //遍历__i2c_board_list这整个链表,比较其中每一个结点的 busnum 与 adapter->nr是否相等
看看这种方法的具体应用:以 at24cxx 为例来分析。
1 //在arch/arm/mach-s5pv210/mach-smdkv210.c中 2 static struct i2c_board_info smdkv210_i2c_devs0[] __initdata = { 3 { I2C_BOARD_INFO("tq210-at24cxx", 0x50), }, /* 目标i2c外设 */ 4 { I2C_BOARD_INFO("wm8580", 0x1b), }, 5 }; 6 smdkv210_machine_init() 7 { 8 ... 9 i2c_register_board_info(0, smdkv210_i2c_devs0,ARRAY_SIZE(smdkv210_i2c_devs0));//注册i2c-0总线上的i2c设备到设备链表 10 i2c_register_board_info(1, smdkv210_i2c_devs1,ARRAY_SIZE(smdkv210_i2c_devs1));//注册i2c-1总线上的i2c设备到设备链表 11 i2c_register_board_info(2, smdkv210_i2c_devs2,ARRAY_SIZE(smdkv210_i2c_devs2));//注册i2c-2总线上的i2c设备到设备链表 12 ... 13 }
我们在前一篇博文中分析platform_driver.probe函数(s3c24xx_i2c_probe)的时候得知函数的调用关系是:
i2c_add_numbered_adapter -> i2c_register_adapter -> i2c_scan_static_board_info -> i2c_new_device,即在i2c_adapter注册好之后内核会去尝试从__i2c_board_list链表中搜索匹配的i2c设备。这种方法有个缺陷就是:必须在调用 i2c_register_adapter 来注册 i2c_adapter 之前就把i2c设备链表注册好,因此,不适合使用 insmod 来动态注册i2c设备。
(2)Instantiate the devices explicitly 立即检测 i2c设备:
这种方法就是“认为i2c设备一定肯定存在”后直接使用 i2c_new_device函数来注册i2c_client。看看怎样做,再来详细跟进这个函数:
1 struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info) 2 { 3 struct i2c_client *client; 4 int status; 5 6 client = kzalloc(sizeof *client, GFP_KERNEL); 7 8 // 设置这个client 9 client->adapter = adap; 10 client->dev.platform_data = info->platform_data; 11 ...... 12 client->flags = info->flags; 13 client->addr = info->addr; 14 client->irq = info->irq; 15 strlcpy(client->name, info->type, sizeof(client->name)); 16 /* Check for address validity */ 17 status = i2c_check_client_addr_validity(client); // 地址合法性 18 ...... 19 /* Check for address business */ 20 status = i2c_check_addr_busy(adap, client->addr);// 地址是否被复用 21 ..... 22 client->dev.bus = &i2c_bus_type; 23 client->dev.type = &i2c_client_type; 24 ..... 25 status = device_register(&client->dev); //注册i2c_client设备 26 ...... 27 return client; //返回i2c_client结构,方便在字符设备编程中的使用!!! 28 ...... 29 }
但是使用这个函数前从其参数可以发现需要准备一些材料:i2c_adapter 和 i2c_board_list ,i2c_adapter 需要用核心层的 i2c_get_adapter() 函数来获得, i2c_board_list 的话自己现场构造一个。
其实到这里就可以看穿其真实面目:把之前放在注册 i2c_adapter之后的任务:遍历 i2c_board_list 链表来注册 i2c_client 版移到这里来实现,这样就可以使用 “insmod” 来动态装载i2c设备了。
(3)Probe an I2C bus for certain devices 通过probe来探测确定在i2c总线上的设备:
使用核心层的 i2c_new_probed_device函数来实现:
1 i2c_new_probed_device(struct i2c_adapter *adap,struct i2c_board_info *info,unsigned short const *addr_list,int (*probe)(struct i2c_adapter *, unsigned short addr)) 2 { 3 if (!probe) 4 probe = i2c_default_probe; 5 ...... 6 /* Test address responsiveness */ 7 for (i = 0; addr_list[i] != I2C_CLIENT_END; i++) { 8 ...... 9 if (probe(adap, addr_list[i])) //真实的发地址去探测 10 break; //成功就跳出for循环往后进行注册i2c设备 11 } 12 if (addr_list[i] == I2C_CLIENT_END) { //看看是不是探测地址用完了,用完了函数就返回,不往下注册设备。 13 dev_dbg(&adap->dev, "Probing failed, no device found "); 14 return NULL; 15 } 16 info->addr = addr_list[i]; 17 return i2c_new_device(adap, info);//注册设备:创建 i2c_client 并注册 18 }
其实这种方法是2.6或之前的内核做法,传闻说这种探测机制会有副作用不建议使用,具体什么副作用就不知道了。
(4)Instantiate from user-space 从用户空间来创建i2c设备:
这种方法最最......好吧,竟然找不到一个词汇来形容对它的这种感觉。
直接使用指令来完成:
echo eeprom 0x50 > /sys/bus/i2c/devices/i2c-0/new_device
大体原理可以意会到一些,具体的原理可以看看内核文档的介绍。
二、i2c 设备驱动的分析和实例编写
不管上边的哪一种方法注册 i2c_client ,对我的i2c设备驱动的设计都没有太大的出入。下边给出是在第一种方式下注册的 i2c_client 时的驱动编写,分析的内容已经容纳在程序的注释中。
1 #include <linux/module.h> 2 #include <linux/input.h> 3 #include <linux/gpio.h> 4 #include <linux/delay.h> 5 #include <linux/input.h> 6 #include <plat/gpio-cfg.h> 7 #include <linux/interrupt.h> 8 #include <linux/kernel.h> 9 #include <linux/init.h> 10 #include <linux/module.h> 11 #include <linux/slab.h> 12 #include <linux/jiffies.h> 13 #include <linux/mutex.h> 14 #include <linux/fs.h> 15 #include <asm/uaccess.h> 16 #include <linux/device.h> 17 #include <linux/notifier.h> 18 #include <linux/fs.h> 19 #include <linux/list.h> 20 #include <linux/i2c.h> 21 #include <linux/i2c-dev.h> 22 23 static int major; 24 static struct class *cls; 25 static struct device *i2c_dev; 26 struct i2c_client *at24cxx_client ; 27 28 static unsigned write_timeout = 25; 29 30 31 int at24cxx_open(struct inode *inode, struct file *file) 32 { 33 printk("at24cxx_open "); 34 return 0; 35 } 36 37 static ssize_t at24cxx_read(struct file *file, char __user *buf, size_t size, loff_t * offset) 38 { 39 unsigned char address; 40 unsigned char data; 41 int ret; 42 struct i2c_msg msg[2]; 43 44 unsigned long timeout, read_time; 45 46 /* address = buf[0] 47 * data = buf[1] 48 */ 49 if (size != 1) 50 return -EINVAL; 51 52 ret = copy_from_user(&address, buf, 1); 53 printk("read addr:%d ",address); 54 /* 数据传输三要素: 源,目的,长度 */ 55 /* 读AT24CXX时,要先把要读的存储空间的地址发给它 */ 56 msg[0].addr = at24cxx_client->addr; /* 目的 */ 57 msg[0].buf = &address; /* 读的源头 */ 58 msg[0].len = 1; /* 地址=1 byte */ 59 //msg[0].flags = at24cxx_client->flags & I2C_M_TEN; /* 表示写 */ 60 61 /* 然后启动读操作 */ 62 msg[1].addr = at24cxx_client->addr; /* 源 */ 63 msg[1].buf = &data; /* 目的 */ 64 msg[1].len = 1; /* 数据=1 byte */ 65 msg[1].flags = at24cxx_client->flags & I2C_M_TEN; 66 msg[1].flags |= I2C_M_RD; /* 表示读 */ 67 68 timeout = jiffies + msecs_to_jiffies(write_timeout); 69 do{ 70 read_time = jiffies; 71 ret = i2c_transfer(at24cxx_client->adapter, msg, 2); 72 msleep(1); 73 } while (time_before(read_time, timeout)); 74 printk("ret=%d ",ret); 75 if (ret >= 0) 76 { 77 ret = copy_to_user(buf, &data, 1); 78 return ret; 79 } 80 else 81 return -1; 82 83 } 84 85 static ssize_t at24cxx_write(struct file *file, const char __user *buf, size_t size, loff_t *offset) 86 { 87 unsigned char val[2]; 88 struct i2c_msg msg[1]; 89 int ret; 90 /* address = buf[0] 91 * data = buf[1] 92 */ 93 if (size != 2) 94 return -EINVAL; 95 printk("at24cxx_write "); 96 ret = copy_from_user(val, buf, 2); 97 98 /* 数据传输三要素: 源,目的,长度 */ 99 msg[0].addr = at24cxx_client->addr; /* 目的 */ 100 msg[0].buf = val; /* 源 */ 101 msg[0].len = 2; /* 地址+数据=2 byte */ 102 msg[0].flags = at24cxx_client->flags & I2C_M_TEN; /* 表示写:i2c_transfer函数就知道将buf[0]当做写地址,后边的是写地址 */ 103 104 ret = i2c_transfer(at24cxx_client->adapter, msg, 1); 105 if (ret == 1) 106 return 2; 107 else 108 return -EIO; 109 } 110 111 long at24cxx__ioctl(struct file *file, unsigned int cmd, unsigned long arg) 112 { 113 114 return 0; 115 } 116 117 static struct file_operations at24cxx_fops = { 118 .owner = THIS_MODULE, 119 .open = at24cxx_open, 120 .read = at24cxx_read, 121 .write = at24cxx_write, 122 .unlocked_ioctl = at24cxx__ioctl, 123 }; 124 125 static int at24cxx_probe(struct i2c_client *client, const struct i2c_device_id *dev_id) 126 { 127 printk("at24cxx_probe "); 128 129 /* 注册字符设备驱动:通用模型中这个任务是放在xxx_init()函数来实现 */ 130 major = register_chrdev(0, "at24cxx", &at24cxx_fops); 131 132 cls = class_create(THIS_MODULE, "at24cxx"); 133 i2c_dev = device_create(cls, NULL, MKDEV(major, 0), NULL, "at24cxx"); /* /dev/at24cxx */ 134 135 /* 拽住要操作的i2c_client,要它成为读写的操作对象 */ 136 at24cxx_client = client; 137 138 return 0; 139 } 140 141 int at24cxx_remove(struct i2c_client *client) 142 { 143 device_unregister(i2c_dev); 144 class_destroy(cls); 145 unregister_chrdev(major, "at24cxx"); 146 147 return 0; 148 } 149 150 static const struct i2c_device_id at24cxx_id[] = { 151 { "tq210-at24cxx", 0 }, /* i2c设备的设备名:这个驱动根据这个名在i2c总线中来找匹配的client */ 152 { } 153 }; 154 155 static struct i2c_driver at24cxx_driver = { 156 .driver = { 157 .name= "at24cxx", 158 .owner = THIS_MODULE, 159 }, 160 .probe = at24cxx_probe, /* 当有i2c_client和i2c_driver匹配时调用 */ 161 .remove = at24cxx_remove, /* 注销时调用 */ 162 .id_table = at24cxx_id, /* 匹配规则 */ 163 }; 164 165 static int at24cxx_init(void) 166 { 167 printk("at24cxx_init "); 168 i2c_add_driver(&at24cxx_driver); 169 return 0; 170 } 171 172 static void at24cxx_exit(void) 173 { 174 printk("at24cxx_exit "); 175 i2c_del_driver(&at24cxx_driver); 176 } 177 178 module_init(at24cxx_init); 179 module_exit(at24cxx_exit); 180 MODULE_LICENSE("GPL");
应用层测试程序:
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 <linux/i2c.h> 9 #include <linux/i2c-dev.h> 10 11 int main() 12 { 13 int fd; 14 unsigned char wr_buf[2]; 15 unsigned char rd_buf; 16 int ret; 17 18 fd = open("/dev/at24cxx",O_RDWR); 19 if(!fd) 20 { 21 printf("open error. "); 22 return -1; 23 } 24 wr_buf[0] = 0x10;//wr_addr 25 wr_buf[1] = 0x66;//wr_val 26 ret = write(fd,wr_buf,2); //2bytes 27 printf("write %dbytes. ",ret); 28 29 rd_buf = 0x10;//rd_addr 30 ret = read(fd,&rd_buf,1); // rd_val -> rd_buf[0] 31 printf("read addr:0x00 -> data:%d ",rd_buf); 32 close(fd); 33 return 0; 34 }