一、 早期版本框架
1. attach_adapter方法
attach_adapter
方法是早期的一种方式,这种方法通常适用于i2c 设备驱动程序不知道其设备将会连接到哪个I2C adapter的情况下。

attach_adapter例程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
| #include <linux/kernel.h> #include <linux/init.h> #include <linux/module.h> #include <linux/slab.h> #include <linux/jiffies.h> #include <linux/i2c.h> #include <linux/mutex.h> #include <linux/fs.h> #include <asm/uaccess.h>
static unsigned short ignore[] = { I2C_CLIENT_END }; static unsigned short normal_addr[] = { 0x50, I2C_CLIENT_END };
static unsigned short force_addr[] = {ANY_I2C_BUS, 0x60, I2C_CLIENT_END}; static unsigned short * forces[] = {force_addr, NULL};
static struct i2c_client_address_data addr_data = { .normal_i2c = normal_addr, .probe = ignore, .ignore = ignore, };
static int major; static struct class *cls;
struct i2c_client *at24cxx_client; static struct i2c_driver at24cxx_driver;
static ssize_t at24cxx_read(struct file *file, char __user *buf, size_t size, loff_t * offset) { unsigned char address; unsigned char data; struct i2c_msg msg[2]; int ret;
if (size != 1) return -EINVAL;
copy_from_user(&address, buf, 1);
msg[0].addr = at24cxx_client->addr; msg[0].buf = &address; msg[0].len = 1; msg[0].flags = 0;
msg[1].addr = at24cxx_client->addr; msg[1].buf = &data; msg[1].len = 1; msg[1].flags = I2C_M_RD;
ret = i2c_transfer(at24cxx_client->adapter, msg, 2); if (ret == 2) { copy_to_user(buf, &data, 1); return 1; } else return -EIO; }
static ssize_t at24cxx_write(struct file *file, const char __user *buf, size_t size, loff_t *offset) { unsigned char val[2]; struct i2c_msg msg[1]; int ret;
if (size != 2) return -EINVAL; copy_from_user(val, buf, 2);
msg[0].addr = at24cxx_client->addr; msg[0].buf = val; msg[0].len = 2; msg[0].flags = 0;
ret = i2c_transfer(at24cxx_client->adapter, msg, 1); if (ret == 1) return 2; else return -EIO; }
static struct file_operations at24cxx_fops = { .owner = THIS_MODULE, .read = at24cxx_read, .write = at24cxx_write, };
static int at24cxx_detect(struct i2c_adapter *adapter, int address, int kind) { printk("at24cxx_detect\n");
at24cxx_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL); at24cxx_client->addr = address; at24cxx_client->adapter = adapter; at24cxx_client->driver = &at24cxx_driver; strcpy(at24cxx_client->name, "at24cxx"); i2c_attach_client(at24cxx_client);
major = register_chrdev(0, "at24cxx", &at24cxx_fops);
cls = class_create(THIS_MODULE, "at24cxx"); class_device_create(cls, NULL, MKDEV(major, 0), NULL, "at24cxx");
return 0; }
static int at24cxx_attach(struct i2c_adapter *adapter) { return i2c_probe(adapter, &addr_data, at24cxx_detect); }
static int at24cxx_detach(struct i2c_client *client) { printk("at24cxx_detach\n"); class_device_destroy(cls, MKDEV(major, 0)); class_destroy(cls); unregister_chrdev(major, "at24cxx");
i2c_detach_client(client); kfree(i2c_get_clientdata(client));
return 0; }
static struct i2c_driver at24cxx_driver = { .driver = { .name = "at24cxx", }, .attach_adapter = at24cxx_attach, .detach_client = at24cxx_detach, };
static int at24cxx_init(void) { i2c_add_driver(&at24cxx_driver); return 0; }
static void at24cxx_exit(void) { i2c_del_driver(&at24cxx_driver); }
module_init(at24cxx_init); module_exit(at24cxx_exit); MODULE_LICENSE("GPL");
|
Test代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h>
void print_usage(char *file) { printf("%s r addr\n", file); printf("%s w addr val\n", file); }
int main(int argc, char **argv) { int fd; unsigned char buf[2]; if ((argc != 3) && (argc != 4)) { print_usage(argv[0]); return -1; }
fd = open("/dev/at24cxx", O_RDWR); if (fd < 0) { printf("can't open /dev/at24cxx\n"); return -1; }
if (strcmp(argv[1], "r") == 0) { buf[0] = strtoul(argv[2], NULL, 0); read(fd, buf, 1); printf("data: %c, %d, 0x%2x\n", buf[0], buf[0], buf[0]); } else if (strcmp(argv[1], "w") == 0) { buf[0] = strtoul(argv[2], NULL, 0); buf[1] = strtoul(argv[3], NULL, 0); write(fd, buf, 2); } else { print_usage(argv[0]); return -1; }
return 0; }
|
2. i2c_new_device方法
对于已经知道i2c设备挂接于哪个i2c adapter时,可以直接使用i2c_new_device接口注册i2c client设备。实际上注册i2c client设备最后会注册一个i2c_bus_type类型的device ,注册i2c_driver最后会注册一个i2c_bus_type类型的driver。
与platform总线类似,一旦有device或driver注册到i2c_bus_type上,便会调用i2c_bus_type的match函数尝试匹配,若匹配则执行i2c_driver的probe函数。不同版本的内核的匹配机制都大同小异,这里以2.6内核,注册i2c_client做分析:


device代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| #include <linux/kernel.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/i2c.h> #include <linux/err.h> #include <linux/regmap.h> #include <linux/slab.h>
static struct i2c_board_info at24cxx_info = { I2C_BOARD_INFO("at24c08", 0x50), };
static struct i2c_client *at24cxx_client;
static int at24cxx_dev_init(void) { struct i2c_adapter *i2c_adap;
i2c_adap = i2c_get_adapter(0); at24cxx_client = i2c_new_device(i2c_adap, &at24cxx_info); i2c_put_adapter(i2c_adap); return 0; }
static void at24cxx_dev_exit(void) { i2c_unregister_device(at24cxx_client); }
module_init(at24cxx_dev_init); module_exit(at24cxx_dev_exit); MODULE_LICENSE("GPL");
|
driver代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
| #include <linux/kernel.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/i2c.h> #include <linux/err.h> #include <linux/regmap.h> #include <linux/slab.h> #include <linux/fs.h> #include <asm/uaccess.h>
static int major; static struct class *class; static struct i2c_client *at24cxx_client;
static ssize_t at24cxx_read(struct file * file, char __user *buf, size_t count, loff_t *off) { unsigned char addr, data;
copy_from_user(&addr, buf, 1); data = i2c_smbus_read_byte_data(at24cxx_client, addr); copy_to_user(buf, &data, 1); return 1; }
static ssize_t at24cxx_write(struct file *file, const char __user *buf, size_t count, loff_t *off) { unsigned char ker_buf[2]; unsigned char addr, data;
copy_from_user(ker_buf, buf, 2); addr = ker_buf[0]; data = ker_buf[1];
printk("addr = 0x%02x, data = 0x%02x\n", addr, data);
if (!i2c_smbus_write_byte_data(at24cxx_client, addr, data)) return 2; else return -EIO; }
static struct file_operations at24cxx_fops = { .owner = THIS_MODULE, .read = at24cxx_read, .write = at24cxx_write, };
static int __devinit at24cxx_probe(struct i2c_client *client, const struct i2c_device_id *id) { at24cxx_client = client;
major = register_chrdev(0, "at24cxx", &at24cxx_fops); class = class_create(THIS_MODULE, "at24cxx"); device_create(class, NULL, MKDEV(major, 0), NULL, "at24cxx"); return 0; }
static int __devexit at24cxx_remove(struct i2c_client *client) { device_destroy(class, MKDEV(major, 0)); class_destroy(class); unregister_chrdev(major, "at24cxx");
return 0; }
static const struct i2c_device_id at24cxx_id_table[] = { { "at24c08", 0 }, {} };
static struct i2c_driver at24cxx_driver = { .driver = { .name = "100ask", .owner = THIS_MODULE, }, .probe = at24cxx_probe, .remove = __devexit_p(at24cxx_remove), .id_table = at24cxx_id_table, };
static int at24cxx_drv_init(void) { i2c_add_driver(&at24cxx_driver); return 0; }
static void at24cxx_drv_exit(void) { i2c_del_driver(&at24cxx_driver); }
module_init(at24cxx_drv_init); module_exit(at24cxx_drv_exit); MODULE_LICENSE("GPL");
|
Test代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h>
void print_usage(char *file) { printf("%s r addr\n", file); printf("%s w addr val\n", file); }
int main(int argc, char **argv) { int fd; unsigned char buf[2]; if ((argc != 3) && (argc != 4)) { print_usage(argv[0]); return -1; }
fd = open("/dev/at24cxx", O_RDWR); if (fd < 0) { printf("can't open /dev/at24cxx\n"); return -1; }
if (strcmp(argv[1], "r") == 0) { buf[0] = strtoul(argv[2], NULL, 0); read(fd, buf, 1); printf("data: %c, %d, 0x%2x\n", buf[0], buf[0], buf[0]); } else if ((strcmp(argv[1], "w") == 0) && (argc == 4)) { buf[0] = strtoul(argv[2], NULL, 0); buf[1] = strtoul(argv[3], NULL, 0); if (write(fd, buf, 2) != 2) printf("write err, addr = 0x%02x, data = 0x%02x\n", buf[0], buf[1]); } else { print_usage(argv[0]); return -1; }
return 0; }
|
Notes
Note 1:i2c_new_device方法与adapter_attach方法不能同时存在,即i2c_driver中不能同时有probe和adapter_attach成员,i2c框架在注册i2c_driver时做了判断:
1 2 3 4 5 6 7 8 9 10
| #define is_newstyle_driver(d) ((d)->probe || (d)->remove) i2c_add_driver i2c_register_driver if (is_newstyle_driver(driver)) { if (driver->attach_adapter || driver->detach_adapter || driver->detach_client) { printk(KERN_WARNING "i2c-core: driver [%s] is confused\n", driver->driver.name); return -EINVAL; } }
|
i2c_bus_type的match函数里也做了判断,确保i2c_driver端注册i2c_bus_type类型的driver时,不会与i2c_bus_type类型的device匹配成功。is_newstyle_driver判断没有probe成员,就返回0,表示没有匹配成功;有probe成员才真正去匹配:

通过注释,如果是旧式驱动(legacy driver)也就是adapter_attach方法,它会自行扫描每个I2C适配器/总线,而不是使用内核的驱动模型probe探测机制。
Note 2:i2c_new_probed_device
i2c_new_device方法不会判断I2C_BOARD_INFO(“at24c08”, 0x50)中指定的地址是不是真的存在设备,他只管利用给他的I2C_BOARD_INFO信息去注册i2c_client,最后会注册一个i2c_bus_type类型的device。
要注意,I2C_BOARD_INFO中的字符串”at24c08”要和i2c_driver中的id_table数组匹配,因为i2c_driver最后会注册一个i2c_bus_type类型的driver,他们之间是利用这个字符串来匹配,才会执行i2c_driver的probe函数。
若要在地址真实存在设备时才注册i2c_client,使用:i2c_new_probed_device。它会在创建i2c_client设备前调用adapter的传输函数对指定的地址进行探测,看是否能收到该地址的ACK回应信号,以验证设备是否真实存在于总线上。
只需更改device侧代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| #include <linux/kernel.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/i2c.h> #include <linux/err.h> #include <linux/regmap.h> #include <linux/slab.h>
static struct i2c_client *at24cxx_client;
static const unsigned short addr_list[] = { 0x60, 0x50, I2C_CLIENT_END };
static int at24cxx_dev_init(void) { struct i2c_adapter *i2c_adap; struct i2c_board_info at24cxx_info;
memset(&at24cxx_info, 0, sizeof(struct i2c_board_info)); strlcpy(at24cxx_info.type, "at24c08", I2C_NAME_SIZE);
i2c_adap = i2c_get_adapter(0); at24cxx_client = i2c_new_probed_device(i2c_adap, &at24cxx_info, addr_list, NULL); i2c_put_adapter(i2c_adap);
if (at24cxx_client) return 0; else return -ENODEV; }
static void at24cxx_dev_exit(void) { i2c_unregister_device(at24cxx_client); }
module_init(at24cxx_dev_init); module_exit(at24cxx_dev_exit); MODULE_LICENSE("GPL");
|
Note 3:i2c_new_device与i2c_attach_client关系
i2c_new_device需要依据i2c_board_info的信息先分配、设置i2c_client,再调用i2c_attach_client注册i2c_client,最终注册一个i2c_bus_type类型的device。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info) { struct i2c_client *client;
client = kzalloc(sizeof *client, GFP_KERNEL); client->adapter = adap; client->dev.platform_data = info->platform_data; client->addr = info->addr; ......
strlcpy(client->driver_name, info->driver_name,sizeof(client->driver_name));
i2c_attach_client(client); list_add_tail(&client->list,&adapter->clients); client->dev.bus = &i2c_bus_type; device_register(&client->dev);
return client; }
|
二、近期版本框架
1. detect方法
随着linux内核版本的更新,早期的attach_adapter方法已经不推荐了,新增了一个detect方法,我理解成是attach_adapter方法的增强版。也是适用于i2c 设备驱动程序不知道其设备将会连接到哪个I2C adapter的情况下。以4.14内核为例分析。
怎么理解增强呢?在attach_adapter方法中,i2c_driver中不能同时有probe和adapter_attach成员,所以在它的回调函数中需要做很多事情,比如:
① 虽然adapter收到了指定地址返回的ACK信号,但是这只是确定了有i2c设备挂接在这个adapter控制器上,但i2c设备的地址可能相同,是at24c02还是iic oled呢?所以在回调函数里需要读取一下i2c设备信息,分辨是哪款i2c设备。
② attach_adapter方法收到了指定地址返回的ACK信号后,不会帮我们调用i2c_new_device,即分配、设置、注册i2c_client,所以需要在回调函数中完成。
③ 我们可能还需要在回调函数中注册字符设备驱动、input输入设备。
流程分析
detect方法的整体流程和早期版本的attach_adapter方法类似,不管是注册一个i2c_adapter还是注册一个i2c_driver,最终都会调用到i2c_detect(adap, driver);
① 注册i2c_adapter时,取出存放i2c_driver的链表调用i2c_detect(adap, driver);
② 注册i2c_driver时,取出存放i2c_adapter的链表调用i2c_detect(adap, driver);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| i2c_add_adapter(adap); __i2c_add_numbered_adapter(adapter); i2c_register_adapter(adap); bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter); __process_new_adapter i2c_do_add_adapter(to_i2c_driver(d), data); i2c_detect(adap, driver);
i2c_add_driver(driver) i2c_register_driver(THIS_MODULE, driver) i2c_for_each_dev(driver, __process_new_driver); __process_new_driver i2c_do_add_adapter(data, to_i2c_adapter(dev)); i2c_detect(adap, driver);
|
分析i2c_detect:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| i2c_detect(struct i2c_adapter *adapter, struct i2c_driver *driver) address_list = driver->address_list; if (!driver->detect || !address_list) return 0;
for (i = 0; address_list[i] != I2C_CLIENT_END; i += 1) temp_client->addr = address_list[i]; temp_client->adapter = adapter; i2c_detect_address(temp_client, driver); struct i2c_board_info info; struct i2c_adapter *adapter = temp_client->adapter; int addr = temp_client->addr; if (!i2c_default_probe(adapter, addr)) return 0; memset(&info, 0, sizeof(struct i2c_board_info)); info.addr = addr;
err = driver->detect(temp_client, &info); client = i2c_new_device(adapter, &info);
|
可以看到,i2c_driver中可以同时有detect和probe成员,detect方法在收到了指定地址返回的ACK信号后,会调用我们i2c_driver中设置的detect函数成员。
同时帮我们调用i2c_new_device来分配、设置、注册i2c_client,最终会注册一个i2c_bus_type类型的device,与i2c_driver注册的i2c_bus_type类型的driver匹配后,调用i2c_driver的probe函数。这样我们就可以将工作分开,避免糅杂在一起:
① 在i2c_driver中设置的detect函数成员里,分辨是哪款i2c设备。
② 在i2c_driver中设置的probe函数成员里,注册字符设备驱动、input输入设备。
detect代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| #include <linux/kernel.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/i2c.h> #include <linux/err.h> #include <linux/regmap.h> #include <linux/slab.h>
static int __devinit at24cxx_probe(struct i2c_client *client, const struct i2c_device_id *id) { return 0; }
static int __devexit at24cxx_remove(struct i2c_client *client) { return 0; }
static const struct i2c_device_id at24cxx_id_table[] = { { "at24c08", 0 }, {} };
static int at24cxx_detect(struct i2c_client *client, struct i2c_board_info *info) {
printk("at24cxx_detect : addr = 0x%x\n", client->addr);
strlcpy(info->type, "at24c08", I2C_NAME_SIZE); return 0; }
static const unsigned short addr_list[] = { 0x60, 0x50, I2C_CLIENT_END };
static struct i2c_driver at24cxx_driver = { .class = I2C_CLASS_HWMON, .driver = { .name = "100ask", .owner = THIS_MODULE, }, .probe = at24cxx_probe, .remove = __devexit_p(at24cxx_remove), .id_table = at24cxx_id_table, .detect = at24cxx_detect, .address_list = addr_list, };
static int at24cxx_drv_init(void) { i2c_add_driver(&at24cxx_driver); return 0; }
static void at24cxx_drv_exit(void) { i2c_del_driver(&at24cxx_driver); }
module_init(at24cxx_drv_init); module_exit(at24cxx_drv_exit); MODULE_LICENSE("GPL");
|
Test代码
略略略……
2. 设备树方法

通常我们都是知道i2c设备挂载于哪个i2c控制器下,所以不用detect这么复杂的方法,在使用设备树方法时,i2c_driver中不要设置detect和address_list成员即可,如果没有这两项成员,便直接跳过detect方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| i2c_add_adapter(adap); __i2c_add_numbered_adapter(adapter); i2c_register_adapter(adap); bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter); __process_new_adapter i2c_do_add_adapter(to_i2c_driver(d), data); i2c_detect(adap, driver);
i2c_add_driver(driver) i2c_register_driver(THIS_MODULE, driver) i2c_for_each_dev(driver, __process_new_driver); __process_new_driver i2c_do_add_adapter(data, to_i2c_adapter(dev)); i2c_detect(adap, driver);
i2c_detect(struct i2c_adapter *adapter, struct i2c_driver *driver) address_list = driver->address_list; if (!driver->detect || !address_list) return 0;
|
设备树方法其实就是早期版本的i2c_new_probed_device升级版。因为我们只需要在设备树的i2c控制器节点下,定义好我们的i2c设备节点,设备树框架会解析i2c设备节点,分配、设置、注册i2c_client,最后注册一个i2c_bus_type类型的device。

首先,i2c0设备树节点对应一个i2c_adapter,gt911设备节点放在i2c0节点下,在解析设备树时,会用i2c0对应的i2c_adapter去访问gt911节点中的reg=<0x50>地址。如果收到ACK回应,便会为gt911节点分配、设置、注册一个i2c_client,并使这个i2c_adapter与i2c_client绑定,最终会注册一个i2c_bus_type类型的device,如果与i2c_driver注册的i2c_bus_type类型的driver匹配,便调用i2c_driver的probe函数。

driver代码
这里就放一个at24c02的代码吧:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
| #include <linux/module.h> #include <linux/i2c.h> #include <linux/cdev.h> #include <linux/uaccess.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/delay.h>
#define AT24CXX_CNT 1
static struct cdev at24cxx_cdev; static struct class *at24cxx_cls;
struct at24cxx_dev { int major; int irqnum; int irqtype; void *private_data; struct gpio_desc *reset_pin; struct gpio_desc *irq_pin; struct i2c_client *client; };
static struct at24cxx_dev at24cxx;
static int at24cxx_open(struct inode *inode, struct file *file) { return 0; }
static ssize_t at24cxx_read(struct file *file, char __user *buf, size_t size, loff_t *loff) { int ret, i; unsigned short offset; unsigned char offset_msg[2]; unsigned char *data; struct i2c_msg msg[2];
ret = copy_from_user(offset_msg, buf, 2); offset = offset_msg[0] << 8 | offset_msg[1]; if (size < 1 || size > 0xffff || (size + offset) > 0x40000) return -EINVAL;
data = (unsigned char *)kmalloc(size, GFP_KERNEL); if(data == NULL ) { printk("kmalloc failed\n"); return -EIO; }
msg[0].addr = at24cxx.client->addr; msg[0].buf = offset_msg; msg[0].flags = !I2C_M_RD; msg[0].len = 2;
msg[1].addr = at24cxx.client->addr; msg[1].flags = I2C_M_RD; msg[1].len = 1;
for(i = 0; i < size; i++) { msg[1].buf = &data[i]; ret = i2c_transfer(at24cxx.client->adapter, msg, 2); if (ret != 2) { printk("at24cxx_read i2c_transfer error = %d\n", ret); kfree(data); return -EIO; } offset++; offset_msg[0] = offset >> 8; offset_msg[1] = offset; } ret = copy_to_user(buf, data, size); kfree(data); return 0; }
static ssize_t at24cxx_write(struct file *file, const char __user *buf, size_t size, loff_t *loff) { int ret, i; unsigned short offset; unsigned char offset_msg[3]; unsigned char *data; struct i2c_msg msg;
ret = copy_from_user(offset_msg, buf, 2); offset = offset_msg[0] << 8 | offset_msg[1]; if (size < 1 || size > 0xffff || (size + offset) > 0x40000) return -EINVAL;
data = (unsigned char *)kmalloc(size, GFP_KERNEL); if(data == NULL ) { printk("kmalloc failed\n"); return -EIO; } ret = copy_from_user(data, buf + 2, size);
msg.addr = at24cxx.client->addr; msg.buf = offset_msg; msg.flags = !I2C_M_RD; msg.len = 2 + 1;
for(i = 0; i < size; i++) { offset_msg[2] = data[i]; ret = i2c_transfer(at24cxx.client->adapter, &msg, 1); if (ret != 1) { printk("at24cxx_write i2c_transfer error = %d\n", ret); kfree(data); return -EIO; } mdelay(5); offset++; offset_msg[0] = offset >> 8; offset_msg[1] = offset; } kfree(data); return 0; }
static struct file_operations at24cxx_fops = { .owner = THIS_MODULE, .open = at24cxx_open, .read = at24cxx_read, .write = at24cxx_write, };
static int at24cxx_probe(struct i2c_client *client, const struct i2c_device_id *id) { dev_t devid; at24cxx.client = client;
printk("at24cxx_probe\n");
if (at24cxx.major) { devid = MKDEV(at24cxx.major, 0); register_chrdev_region(devid, AT24CXX_CNT, "at24cxx"); } else { alloc_chrdev_region(&devid, 0, AT24CXX_CNT, "at24cxx"); at24cxx.major = MAJOR(devid); }
cdev_init(&at24cxx_cdev, &at24cxx_fops); cdev_add(&at24cxx_cdev, devid, AT24CXX_CNT);
at24cxx_cls = class_create(THIS_MODULE, "at24cxx"); device_create(at24cxx_cls, NULL, MKDEV(at24cxx.major, 0), NULL, "at24cxx");
return 0; }
static int at24cxx_remove(struct i2c_client *client) { device_destroy(at24cxx_cls, MKDEV(at24cxx.major, 0)); class_destroy(at24cxx_cls); unregister_chrdev(at24cxx.major, "at24cxx"); return 0; }
static const struct of_device_id of_match_ids_at24cxx[] = { { .compatible = "Anlogic,eeprom24cm02", .data = NULL }, { }, };
static const struct i2c_device_id at24cxx_ids[] = { { "Anlogic,eeprom24cm02", (kernel_ulong_t)NULL }, { } };
static struct i2c_driver at24cxx_driver = { .driver = { .name = "at24cxx", .of_match_table = of_match_ids_at24cxx, }, .probe = at24cxx_probe, .remove = at24cxx_remove, .id_table = at24cxx_ids, };
static int __init at24cxx_init(void) { return i2c_add_driver(&at24cxx_driver); } module_init(at24cxx_init);
static void __exit at24cxx_exit(void) { i2c_del_driver(&at24cxx_driver); } module_exit(at24cxx_exit);
MODULE_AUTHOR("Anlogic: quenaiyuan"); MODULE_LICENSE("GPL");
|
Test代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h>
void print_usage(char *file) { printf("%s r addr\n", file); printf("%s w addr val\n", file); }
int main(int argc, char **argv) { int fd; unsigned short addr; unsigned char buf[3];
if ((argc != 3) && (argc != 4)) { print_usage(argv[0]); return -1; }
fd = open("/dev/at24cxx", O_RDWR); if (fd < 0) { printf("can't open /dev/at24cxx\n"); return -1; }
if (strcmp(argv[1], "r") == 0) { addr = strtoul(argv[2], NULL, 0); buf[0] = addr >> 8; buf[1] = addr; read(fd, buf, 1); printf("%04x: 0x%2x\n", addr, buf[0]); } else if (strcmp(argv[1], "w") == 0) { addr = strtoul(argv[2], NULL, 0); buf[0] = addr >> 8; buf[1] = addr; buf[2] = strtoul(argv[3], NULL, 0); write(fd, buf, 1); } else { print_usage(argv[0]); return -1; }
return 0; }
|
Notes
设备树方法和detect方法不要一起使用。因为设备树会解析i2c设备节点,然后注册i2c_client。再使用detect方法,i2c_detect中又会注册i2c_client,可能会有冲突。
在使用设备树方法时,i2c_driver中不要设置detect和address_list成员;
在使用detect方法时,不要在设备树的i2c控制器节点下定义这个i2c设备节点;