一、 早期版本框架

1. attach_adapter方法

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

attach_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 };
/* 改为0x60的话, 由于不存在设备地址为0x60的设备, 所以at24cxx_detect不被调用 */

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, /* 要发出S信号和设备地址并得到ACK信号,才能确定存在这个设备 */
.probe = ignore,
.ignore = ignore,
//.forces = forces, /* 强制认为存在这个设备 */
};

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;

/* address = buf[0]
* data = buf[1]
*/
if (size != 1)
return -EINVAL;

copy_from_user(&address, buf, 1);

/* 数据传输三要素: 源,目的,长度 */

/* 读AT24CXX时,要先把要读的存储空间的地址发给它 */
msg[0].addr = at24cxx_client->addr; /* 目的 */
msg[0].buf = &address; /* 源 */
msg[0].len = 1; /* 地址=1 byte */
msg[0].flags = 0; /* 表示写 */

/* 然后启动读操作 */
msg[1].addr = at24cxx_client->addr; /* 源 */
msg[1].buf = &data; /* 目的 */
msg[1].len = 1; /* 数据=1 byte */
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;

/* address = buf[0]
* data = buf[1]
*/
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; /* 地址+数据=2 byte */
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");

/* 构构一个i2c_client结构体: 以后收改数据时会用到它 */
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"); /* /dev/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;
}

/* 1. 分配一个i2c_driver结构体 */
/* 2. 设置i2c_driver结构体 */
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>

/* i2c_test r addr
* i2c_test w addr val
*/

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做分析:

i2c_new_device方法1

i2c_new_device方法2

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设备挂接于i2c adapter 0 */
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;

/* 传入: buf[0] : addr
* 输出: buf[0] : data
*/
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;
}

/* buf[0] : addr
* buf[1] : data
*/
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"); /* /dev/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 },
{}
};

/* 1. 分配/设置i2c_driver */
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)
{
/* 2. 注册i2c_driver */
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>

/* i2c_test r addr
* i2c_test w addr val
*/

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
/* new style driver methods can't mix with legacy ones */
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成员才真正去匹配:

i2c_device_match

通过注释,如果是旧式驱动(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);
/* 这里相当于注册i2c_client */
list_add_tail(&client->list,&adapter->clients);
/* 注册i2c_bus_type类型的device */
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_adapter */
i2c_add_adapter(adap);
__i2c_add_numbered_adapter(adapter);
i2c_register_adapter(adap);
/* Notify drivers */
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_driver */
i2c_add_driver(driver)
i2c_register_driver(THIS_MODULE, driver)
/* Walk the adapters that are already present */
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
/* Detect supported devices on that bus, and instantiate them */
i2c_detect(struct i2c_adapter *adapter, struct i2c_driver *driver)
/* 取出i2c_driver中的设备地址数组 */
address_list = driver->address_list;
/* 判断是否使用detect方法,如果i2c_driver没有设置对应成员就是不使用 */
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;
/* 对每个地址都去访问,看看能否收到ACK回应 */
i2c_detect_address(temp_client, driver);
struct i2c_board_info info;
struct i2c_adapter *adapter = temp_client->adapter;
int addr = temp_client->addr;

/* 判断这个设备是否存在:简单的发出S信号确定有ACK */
if (!i2c_default_probe(adapter, addr))
return 0;

/* 调用我们i2c_driver设置的detect函数成员,分辨具体芯片,设置info->type */
memset(&info, 0, sizeof(struct i2c_board_info));
info.addr = addr;
/* 后面的i2c_new_device需要利用i2c_board_info的信息来设置i2c_client和注册一个i2c_bus_type类型的device
* 为了让i2c_driver的probe函数被调用,就需要在driver->detect中设置info->type与i2c_driver中id_table某项匹配
* 4.14版本与上面的2.6版本不同,4.14版本用info->type代替了2.6版本的info->driver_name,但其实都大同小异。*/
err = driver->detect(temp_client, &info);

/* 分配、设置、注册i2c_client,最终会注册一个i2c_bus_type类型的device */
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)
{
/* 4. 注册字符设备驱动、input输入设备... */
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)
{
/* 3. 能运行到这里, 表示该addr的设备是存在的
* 但是有些设备单凭地址无法分辨(A芯片的地址是0x50, B芯片的地址也是0x50)
* 还需要进一步读写I2C设备来分辨是哪款芯片
* detect就是用来进一步分辨这个芯片是哪一款,并且设置info->type
*/

printk("at24cxx_detect : addr = 0x%x\n", client->addr);

/* 进一步判断是哪一款i2c设备 */
/* ...... */

/* 设置info->type,i2c_driver->detect执行后便执行i2c_new_device
* 他需要利用i2c_board_info的信息来设置i2c_client和注册一个i2c_bus_type类型的device */
strlcpy(info->type, "at24c08", I2C_NAME_SIZE);
return 0;
}

static const unsigned short addr_list[] = { 0x60, 0x50, I2C_CLIENT_END };

/* 1. 分配/设置i2c_driver */
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, /* 用这个函数来检测具体是什么i2c设备 */
.address_list = addr_list, /* 这些设备的地址 */
};

static int at24cxx_drv_init(void)
{
/* 2. 注册i2c_driver */
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_adapter */
i2c_add_adapter(adap);
__i2c_add_numbered_adapter(adapter);
i2c_register_adapter(adap);
/* Notify drivers */
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_driver */
i2c_add_driver(driver)
i2c_register_driver(THIS_MODULE, driver)
/* Walk the adapters that are already present */
i2c_for_each_dev(driver, __process_new_driver);
__process_new_driver
i2c_do_add_adapter(data, to_i2c_adapter(dev));
i2c_detect(adap, driver);

/* detect方法 */
i2c_detect(struct i2c_adapter *adapter, struct i2c_driver *driver)
/* 取出i2c_driver中的设备地址数组 */
address_list = driver->address_list;
/* 判断是否使用detect方法,如果i2c_driver没有设置对应成员就是不使用 */
if (!driver->detect || !address_list)
return 0;

设备树方法其实就是早期版本的i2c_new_probed_device升级版。因为我们只需要在设备树的i2c控制器节点下,定义好我们的i2c设备节点,设备树框架会解析i2c设备节点,分配、设置、注册i2c_client,最后注册一个i2c_bus_type类型的device。

iic设备树

首先,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函数。

iic设备树2

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];

/* offset = buf[0] | buf[1]
* data -> buf[0] ~ buf[size-1]
*/
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;
}

/* at24cm02 reg addr is 2 Bytes, A15 - A8 | A7 - A0 */
msg[0].addr = at24cxx.client->addr; /* at24cxx dev addr */
msg[0].buf = offset_msg; /* eeprom offset */
msg[0].flags = !I2C_M_RD; /* i2c write */
msg[0].len = 2; /* data len */

msg[1].addr = at24cxx.client->addr; /* at24cxx dev addr */
msg[1].flags = I2C_M_RD; /* i2c read */
msg[1].len = 1; /* data len */

/* msg[1] read data */
for(i = 0; i < size; i++) {
msg[1].buf = &data[i]; /* data buffer */
ret = i2c_transfer(at24cxx.client->adapter, msg, 2);
if (ret != 2) {
printk("at24cxx_read i2c_transfer error = %d\n", ret);
kfree(data);
return -EIO;
}
/* update offset */
offset++;
offset_msg[0] = offset >> 8;
offset_msg[1] = offset;
}
ret = copy_to_user(buf, data, size);
/* free memory */
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;

/* offset = buf[0] | buf[1]
* data -> buf[0] ~ buf[size-1]
*/
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; /* at24cxx dev addr */
msg.buf = offset_msg; /* eeprom offset & write data */
msg.flags = !I2C_M_RD; /* i2c write */
msg.len = 2 + 1; /* data len */

for(i = 0; i < size; i++) {
offset_msg[2] = data[i]; /* data buffer */
ret = i2c_transfer(at24cxx.client->adapter, &msg, 1);
if (ret != 1) {
printk("at24cxx_write i2c_transfer error = %d\n", ret);
kfree(data);
return -EIO;
}
/* wait Twr, it's write duration */
mdelay(5);
/* update offset */
offset++;
offset_msg[0] = offset >> 8;
offset_msg[1] = offset;
}
/* free memory */
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"); /* /dev/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 },
{ /* END OF LIST */ },
};

static const struct i2c_device_id at24cxx_ids[] = {
{ "Anlogic,eeprom24cm02", (kernel_ulong_t)NULL },
{ /* END OF LIST */ }
};

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>

/* i2c_test r addr
* i2c_test w addr val
*/

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设备节点;