正点原子-阿尔法开发板-第六十一章 Linux I2C驱动实验代码详解(自我理解)

  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 = &reg; /* 向芯片发送(写入)要读取的寄存器的地址 */
 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 = &reg; /* 向芯片发送(写入)要读取的寄存器的地址 */
 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");

 

posted @ 2025-09-24 21:27  绿树荫下跑猫猫  阅读(25)  评论(0)    收藏  举报