1 #include<linux/types.h>
2 #include<linux/kernel.h>
3 #include<linux/delay.h>
4 #include<linux/ide.h>
5 #include<linux/init.h>
6 #include<linux/module.h>
7 #include<linux/errno.h>
8 #include<linux/gpio.h>
9 #include<linux/cdev.h>
10 #include<linux/device.h>
11 #include<linux/of_gpio.h>
12 #include<linux/semaphore.h>
13 #include<linux/timer.h>
14 #include<linux/i2c.h>
15 #include<asm/mach/map.h>
16 #include<asm/uaccess.h>
17 #include<asm/io.h>
18 #include"ap3216creg.h"
19
20 /* 这个驱动对应只有一个同类设备的问题,如果有多个同类型的传感器怎么处理,这就需要再学习了 */
21 /* 一、配置设备相关的信息 */
22
23 #define AP3216C_CNT 1 /* 定义设备数量 */
24 #define AP3216C_NAME "ap3216c" /* 设备名称 */
25
26 struct ap3216c_dev {
27 dev_t devid; /* 设备号,dev_t就是个普通的整数,是主设备号和次设备号计算出来的 */
28 struct cdev cdev; /* cdev, 表示一个字符设备,需要向系统注册 */
29 struct class *class; /* 类,表示一类设备,在/sys/class下,还理解的不深刻*/
30 struct device *device; /* 创建的设备,在/dev/下显示的设备,通过device_create创建 */
31 struct device_node *nd; /* 设备节点,存储设备树相关的信息,接收申请的返回值?本实验没有用到*/
32 int major; /* 主设备号*/
33 void *private_data; /* 私有数据,这个应该主要是自己用的,这里存储的是i2c_client,和filp->private_data要区分开 */
34 unsigned short ir, als, ps; /* 传感器数据 */
35 };
36
37 static struct ap3216c_dev ap3216cdev; /* 全局变量,用于整体设备的控制 */
38 /**
39 * @自我理解:封装读取寄存器这个函数,用于被后面调用,这里可以读取以reg为首地址的连续的多个值,跟
40 * 后面的ap3216c_read_reg读取单个寄存器的要区分开,这个ap3216没有用到(可能也不支持)读取连续地址对应的数值,这
41 * 个函数可能是为了编码规范而编写的,是一些通用的编程规范。
42 * @param - dev : ap2316c设备
43 *
44 */
45
46 /* 二、一些自定义的读取写函数,这里的都不确定,因为基本都是自己定义的 */
47
48 static int ap3216c_read_regs(struct ap3216c_dev *dev, u8 reg, void *val, int len)
49 {
50 int ret; /* 保存返回值 */
51 struct i2c_msg msg[2]; /* i2c发送的数据,这里需要写入,然后读取,所以需要两个就行了。msg可以理解为数据包 */
52 struct i2c_client *client = (struct i2c_client*)dev->private_data; /* 从自定义的设备结构体中取出i2c_client这个重要的操作对象 */
53 /**也就是需要先向芯片发送写命令,然后发送要读取的寄存器的地址(相当于把寄存器地址写入芯片),
54 * 然后再发送读命令,这样芯片返回的数据就是刚才写入的这个寄存器地址的对应的值了?
55 * 这里的好几个读和写的含义要区分开,一个是对于i2c来说的,一个是对于寄存器来说。
56 * */
57 /* msg[0]为一个写命令数据包:为发送要读取的寄存器的首地址 */
58 msg[0].addr = client->addr; /* ap3216c地址 */
59 msg[0].flags = 0; /* 标记为发送数据,就是i2c协议中的那个读写标志位 */
60 msg[0].buf = ® /* 向芯片发送(写入)要读取的寄存器的地址 */
61 msg[0].len = 1; /* 要发送的数据长度,也就是寄存器地址长度,以字节为单位 */
62
63 /* msg[1]为一个读命令数据包:读取msg[0]中发送的寄存器地址的值 */
64 msg[1].addr = client->addr; /* ap3216c地址 */
65 msg[1].flags = I2C_M_RD; /* 标记为发送数据,就是i2c协议中的那个读写标志位 */
66 msg[1].buf = ® /* 向芯片发送(写入)要读取的寄存器的地址 */
67 msg[1].len = 1; /* 要发送的数据长度,也就是寄存器地址长度,以字节为单位 */
68
69 ret = i2c_transfer(client->adapter, msg, 2); /* 发送数据,这里client->adapter参数不能省略,因为一个i2c口对应一个适配器,也就是说一个设备如果有多个i2c口,就会有多个适配器,所以需要制指定从那个i2c口发,这里msg是一个数组,所以是一个地址(严格意义上不算地址)*/
70 if(ret == 2) {
71 ret = 0; // 发送成功
72 } else {
73 printk("i2c rd failed=%d reg=%06x len=%d\n",ret, reg, len);
74 ret = -EREMOTEIO; /* EREMOTEIO = 121 */
75 }
76 return ret;
77 }
78
79 /**
80 * 向寄存器写入数据,这里也是可以写入连续的数据,跟ap3216c_write_reg区分开,解释见上
81 * 面的ap3216c_read_regs注释
82 */
83 static s32 ap3216c_write_regs(struct ap3216c_dev *dev, u8 reg, u8 *buf, u8 len)
84 {
85 u8 b[256]; /* 申请256字节栈空间 */
86 struct i2c_msg msg; /* 要写入数据的数据包 */
87 struct i2c_client *client = (struct i2c_client *)dev->private_data;
88
89
90 /* 要发送的数据b,在主机发送写命令后,ap3216c要求先发送要写入的寄存器地址,然后再发送要写入的数据 */
91 b[0] = reg; /* 要写入数据的寄存器首地址 */
92 memcpy(&b[1], buf, len); /* b[1]为目标,buf为源,len长度,b[0]已经存放了寄存器地址,从b[1]开始存储要写入的数据 */
93
94 msg.addr = client->addr; /* 地址 */
95 msg.flags = 0; /* 写数据 */
96
97 msg.buf = b; /* 要写入的数据缓冲区 */
98 msg.len = len + 1; /* 要写入的数据的长度,还有个寄存器地址 */
99
100 return i2c_transfer(client->adapter, &msg, 1); /* 这里msg是一个实体,所以需要取地址 */
101 }
102
103 /**
104 * 只读取一个字节,所以用了unsigned char
105 */
106 static unsigned char ap3216c_read_reg(struct ap3216c_dev *dev, u8 reg)
107 {
108 u8 data = 0;
109 ap3216c_read_regs(dev, reg, &data, 1); /* 长度为1,所以只读取一个寄存器 */
110 return data;
111
112 #if 0 /* 这里也是一个知识点,i2c_smbus_read_byte_data函数是对i2c_transfer跟高级别的封装,可能需要开启某些选项才行 */
113 struct i2c_client *client = (struct i2c_client *)dev->private_data;
114 return i2c_smbus_read_byte_data(client, reg);
115 #endif
116 }
117
118 /**
119 * 具体关系和解释同上面的读函数
120 */
121 static void ap3216c_write_reg(struct ap3216c_dev *dev, u8 reg, u8 data)
122 {
123 u8 buf = 0;
124 buf = data;
125 ap3216c_write_regs(dev, reg, &buf, 1);
126 }
127
128 /**
129 * 利用上面的函数,对上面函数进行封装,读取原始数据,如果同时打开ALS,IR+PS的话两次数据读取的时间间隔要大于112.5ms
130 * 发现这个ap3216c_dev结构体贯穿时钟啊,看来很重要呢
131 */
132 void ap3216c_readdata(struct ap3216c_dev *dev)
133 {
134 unsigned char i =0; /* 可能尽量节省空间吧 */
135 unsigned char buf [6]; /* 节省空间 */
136
137 for(i = 0; i < 6; i++){
138 buf[i] = ap3216c_read_reg(dev, AP3216C_IRDATALOW + 1);
139 }
140
141 if(buf[0] & 0X80){ /* 看数据手册可知其第八位为数据是否有效标志位 */
142 dev->ir = 0; /* 无效的话,设置为0 */
143 } else {
144 /* buf[0]低两位 + buf[1] */
145 dev->ir = ((unsigned short)buf[1] << 2) | (buf[0] & 0X03); /* unsigned short 等价于 unsigned short int 占用2字节,是int的一半?也是为了节省空间*/
146 }
147
148 dev->als = ((unsigned short)buf[3] << 8) | buf[2]; /* 数据计算同理 */
149 if(buf[4] & 0x40){ /* 标志位,为1则数据无效 */
150 dev->ps = 0;
151 } else {
152 dev->ps = ((unsigned short)(buf[5] & 0x3F) << 4) | (buf[4] & 0x0F);
153 }
154 }
155
156
157 /* 三、用户层面文件操作相关的接口*/
158
159 /**
160 * 被用户打开时调用的函数,跟prob函数分开,那个是与设备树匹配成功后执行的函数
161 * @param-inode: 内核用来保存文件元信息,比如文件类型、权限、大小、时间戳、设备号等,以供驱动可以判断一些东西
162 * 只有打开的时候会传递inode,然后内核会自动将file结构体中的f_inode赋值为inode。
163 */
164 static int ap3616_open(struct inode *inode, struct file *filp)
165 {
166 filp->private_data = &ap3216cdev; /* 注意这里的private_data跟自定义的private_data不一样,这个是linux编程指定的,不是自定义 */
167 /* 初始化ap3216c, 正常来说这些应该都要加判断是否成功的吧 */
168 ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0x04); /* 0x40是软复位 */
169 mdelay(50); /* 延迟50ms,因为复位需要实践 */
170 ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0x03); /* 使能ALS+PS+IR */
171 return 0;
172 }
173 /*
174 * @description : 从设备读取数据
175 * 注意:这里不需要传递inode了,因为在filp里面已经有了
176 * @param - filp : 要打开的设备文件(文件描述符)
177 * @param - buf : 返回给用户空间的数据缓冲区
178 * @param - cnt : 要读取的数据长度
179 * @param - offt : 相对于文件首地址的偏移
180 * @return : 读取的字节数,如果为负值,表示读取失败
181 */
182 static ssize_t ap3216c_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
183 {
184 short data[3]; /* 保存数据 */
185 long err = 0; /* 返回值,可以用ret吧? */
186
187 struct ap3216c_dev *dev = (struct ap3216c_dev*)filp->private_data;
188
189 ap3216c_readdata(dev);
190
191 data[0] = dev->ir;
192 data[1] = dev->als;
193 data[2] = dev->ps;
194 err = copy_to_user(buf, data, sizeof(data));
195 return 0;
196 }
197 /**
198 * 用不到write,但是加上吧,了解下函数结构
199 * @param – filp : 设备文件,表示打开的文件描述符
200 * 注意:这里不需要传递inode了,因为在filp里面已经有了
201 * @param - buf : 要写给设备写入的数据
202 * @param - cnt : 要写入的数据长度
203 * @param – offt : 相对于文件首地址的偏移
204 */
205 static ssize_t ap3216c_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
206 {
207 return 0;
208 }
209 /**
210 * 用户层面文件关闭/释放设备时候调用的函数,跟后面驱动相关的exit/remove函数区分开
211 */
212 static int ap3216_release(struct inode *inode, struct file *filp)
213 {
214 return 0;
215 }
216
217 /**
218 * 构建AP3216用户文件操作层面的结构体函数,注意,这里是文件操作
219 */
220 static const struct file_operations ap3216c_ops = {
221 .owner = THIS_MODULE,
222 .open = ap3616_open,
223 .read = ap3216c_read,
224 .write = ap3216c_write,
225 .release = ap3216_release,
226 };
227
228 /* 到此未知,有关用户层面对文件的打开关闭操作已经有了 */
229
230 /* 四、下面是驱动层面干的事情了,就是对设备相关的操作了,更加底层 */
231
232 /**
233 * prob函数,最重要的函数,用于设配匹配成功后执行
234 * 要记住不同总线的prob函数应该是不一样的
235 * 这里的这两个参数应该是从设备树或者传统匹配方式得到的数据,设备匹配成功后会把这两个参数传递进来
236 */
237 static int ap3216c_probe(struct i2c_client *client, const struct i2c_device_id *id)
238 {
239 /* 1. 构建设备号,AP3216C_CNT>1的情况我还没懂,但大体意思就是一个驱动管理多个同类型的传感器,可以公用一套驱动代码,等待研究*/
240 if(ap3216cdev.major){ /* 存在设备号,也就是有自己想设置的设号 */
241 ap3216cdev.devid = MKDEV(ap3216cdev.major, 0); /* 设置主设别号和次设备号,次设备号一般设置为0 */
242 register_chrdev_region(ap3216cdev.devid, AP3216C_CNT, AP3216C_NAME);
243 } else {
244 alloc_chrdev_region(&ap3216cdev.devid, 0, AP3216C_CNT, AP3216C_NAME); /* 得到设备号,可以解析出主设备号和次设备号,参数0代表的是启示开始分配的设备号,一般从0开始,也就是说如果从0开始,就是从0开始一个个查找,查找到可用的就用上,也对应上一个驱动适配多个相同设备了 */
245 ap3216cdev.major = MAJOR(ap3216cdev.devid); /* 解析出主设备号 */
246 }
247
248 /* 2. 注册字符设备,向内核中注册一个字符设备,但是用户层面还是看不到相应的设备 */
249 cdev_init(&ap3216cdev.cdev, &ap3216c_ops); /* 第一个参数是要初始化的设别结构体,第二个是文件操作函数 */
250 cdev_add(&ap3216cdev.cdev, ap3216cdev.devid, AP3216C_CNT); /* 第三个参数是要创建的设备数量 */
251
252 /* 3. 创建类,就是在/sys/class/创建同一类设备的文件夹 */
253 ap3216cdev.class = class_create(THIS_MODULE, AP3216C_NAME);
254 if( IS_ERR(ap3216cdev.class)){ /* 如果创建类是黑白失败 */
255 return PTR_ERR(ap3216cdev.class);
256 }
257
258 /* 4. 创建设备,使其暴露在/dev/下面,让用户使用 */
259 /**
260 * device_create是个可变参数函数,参数 class就是设备要创建哪个类下面;参数 parent是父
261 * 设备,一般为 NULL,也就是没有父设备;参数 devt是设备号;参数 drvdata是设备可能会使用
262 * 的一些数据,一般为 NULL;参数 fmt是设备名字,如果设置 fmt=xxx的话,就会生成 /dev/xxx这个设备文件。返回值就是创建好的设备。
263 */
264 ap3216cdev.device = device_create(ap3216cdev.class, NULL, ap3216cdev.devid, NULL, AP3216C_NAME);
265 if(IS_ERR(ap3216cdev.device)){
266 return PTR_ERR(ap3216cdev.device);
267 }
268 ap3216cdev.private_data = client;
269
270 return 0;
271 }
272 /**
273 * 设备移除时对设备进行的操作,可能类似设备拔出之后的操作
274 */
275 static int ap3216c_remove(struct i2c_client *client)
276 {
277 /*1. 删除字符设备相关 */
278 cdev_del(&ap3216cdev.cdev);
279 /* 这个注销函数,应该是从devid开始的设备号,忘后AP3216C_CNT注销AP3216C_CNT个设备,因为前面注册的时候,是对应AP3216C_CNT个设备的 */
280 /* 这些函数的详细用法还需要单独学习,可以看官方手册? */
281 unregister_chrdev_region(ap3216cdev.devid, AP3216C_CNT); /* 这里AP3216C_CNT是1,也就是说值对应一个设备的情况,多个设备的情况还需要再学习 */
282
283 /* 2. 注销掉类和设备 */
284 device_destroy(ap3216cdev.class, ap3216cdev.devid);
285 class_destroy(ap3216cdev.class);
286 return 0;
287 }
288
289 /* 除了设备树之外的匹配方式都叫传统方式 */
290 /* 传统匹配方式 */
291 static const struct i2c_device_id ap3216c_id[] = {
292 {"alientek,ap3216c", 0}, /* 注意这里的0不是什么ID,而是一个私有数据,可能是用于区分不同的设备? */
293 {}
294 };
295
296 /* 设备树匹配方式 */
297 static const struct of_device_id ap3216c_of_match[] = {
298 { .compatible = "alientek,ap3216c"},
299 { }
300 };
301
302 /* 写完相关函数之后,下面配置驱动相关的操作的结构体 */
303 /**
304 * 感觉这个才是真正封装好的驱动,其中有驱动的各种操作,所以init中是将这个add进去的
305 */
306 static struct i2c_driver ap3216c_driver = {
307 .probe = ap3216c_probe,
308 .remove = ap3216c_remove,
309 .driver = {
310 .owner = THIS_MODULE,
311 .name = "ap3216c",
312 .of_match_table = ap3216c_of_match,
313 },
314 .id_table = ap3216c_id,
315 };
316
317 /* 驱动入口函数,这个是最先执行的函数 */
318
319 static int __init ap3216c_init(void)
320 {
321 int ret = 0;
322 ret = i2c_add_driver(&ap3216c_driver);
323 return ret;
324 }
325 /**
326 * 驱动出口函数,没什么好说的,卸载模块的时候用
327 */
328 static void __exit ap3216c_exit(void)
329 {
330 i2c_del_driver(&ap3216c_driver);
331 }
332
333 /* module_i2c_driver(ap3216c_driver) */ /* 这个是直接把module_init和module_exit简化成要给函数了 */
334 module_init(ap3216c_init);
335 module_exit(ap3216c_exit);
336 MODULE_LICENSE("GPL");
337 MODULE_AUTHOR("ydteng");