环球通讯!基于IIC驱动框架的AP3216芯片设计

2023-06-29 12:15:35   来源:Linux大陆

1、AP3216简介

AP3216C 芯片集成了光强传感器( ALS: Ambient Light Sensor),接近传感器( PS: Proximity Sensor),还有一个红外LED( IR LED)。


(资料图片仅供参考)

这个芯片设计的用途是给手机之类的使用,比如:返回当前环境光强以便调整屏幕亮度;用户接听电话时,将手机放置在耳边后,自动关闭屏幕避免用户误触碰 。

该芯片通过 I2C接口与主控制器相连, 如:

2、IIC驱动简介

Linux下IIC有两种驱动方式:一种是按照字符设备驱动方式来驱动IIC;另一种是走Linux下IIC的框架。按照字符设备驱动的方式可以查阅这一篇文章:Linux IIC 字符设备 驱动例子。

这里我们了解学习一下第二种方式,因为找到的AP3216的驱动就是基于IIC驱动框架的,哈哈。

整个IIC的驱动框架相关代码在driversi2c中,包含的内容有:

IIC驱动框架图如(图片来源于网络,链接见文末参考资料):

IIC驱动框架可大体分为两大部分:

① I2C 总线驱动:SOC 的 I2C 控制器驱动,也叫做 I2C 适配器驱动。

② I2C 设备驱动:针对具体的 I2C 设备而编写的驱动。

其中,访问抽象层与I2C核心层数据I2C 总线驱动部分;driver驱动层属于I2C设备驱动部分。

上面框图对应的代码调用层次图如:

下面的AP3216驱动可以对照这张图来看看。

4、AP3216实验

我们使用设备树来描述AP3216设备信息,首先我们没有在设备树中添加AP3216相关节点时,我们系统的I2C设备如:

添加I2C pinctrl,板子上AP3216接的是I2C1:

配置寄存器的值都设为0x4001b8b0,这一段是什么意思我们在什么是Pinctrl子系统及GPIO子系统?这篇笔记中也有写到,就是几个寄存器及其配置。

接下来在i2c1节点下添加ap3216节点:

编译设备树,传到开发板上,重启。此时我们系统的I2C设备有:

可见,新增的AP3216 I2C设备名就是我们设备树里设置的。

下面编写AP3216驱动(以下代码来源于网络):

ap3216.c:

#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include"ap3216creg.h"/***************************************************************文件名:ap3216c.c描述:AP3216C驱动程序***************************************************************/#defineAP3216C_CNT1#defineAP3216C_NAME"ap3216c"structap3216c_dev{dev_tdevid;/*设备号*/structcdevcdev;/*cdev*/structclass*class;/*类*/structdevice*device;/*设备*/structdevice_node*nd;/*设备节点*/intmajor;/*主设备号*/void*private_data;/*私有数据*/unsignedshortir,als,ps;/*三个光传感器数据*/};staticstructap3216c_devap3216cdev;/**@description:从ap3216c读取多个寄存器数据*@param-dev:ap3216c设备*@param-reg:要读取的寄存器首地址*@param-val:读取到的数据*@param-len:要读取的数据长度*@return:操作结果*/staticintap3216c_read_regs(structap3216c_dev*dev,u8reg,void*val,intlen){intret;structi2c_msgmsg[2];structi2c_client*client=(structi2c_client*)dev->private_data;/*msg[0]为发送要读取的首地址*/msg[0].addr=client->addr;/*ap3216c地址*/msg[0].flags=0;/*标记为发送数据*/msg[0].buf=®/*读取的首地址*/msg[0].len=1;/*reg长度*//*msg[1]读取数据*/msg[1].addr=client->addr;/*ap3216c地址*/msg[1].flags=I2C_M_RD;/*标记为读取数据*/msg[1].buf=val;/*读取数据缓冲区*/msg[1].len=len;/*要读取的数据长度*/ret=i2c_transfer(client->adapter,msg,2);if(ret==2){ret=0;}else{printk("i2crdfailed=%dreg=%06xlen=%d",ret,reg,len);ret=-EREMOTEIO;}returnret;}/**@description:向ap3216c多个寄存器写入数据*@param-dev:ap3216c设备*@param-reg:要写入的寄存器首地址*@param-val:要写入的数据缓冲区*@param-len:要写入的数据长度*@return:操作结果*/statics32ap3216c_write_regs(structap3216c_dev*dev,u8reg,u8*buf,u8len){u8b[256];structi2c_msgmsg;structi2c_client*client=(structi2c_client*)dev->private_data;b[0]=reg;/*寄存器首地址*/memcpy(&b[1],buf,len);/*将要写入的数据拷贝到数组b里面*/msg.addr=client->addr;/*ap3216c地址*/msg.flags=0;/*标记为写数据*/msg.buf=b;/*要写入的数据缓冲区*/msg.len=len+1;/*要写入的数据长度*/returni2c_transfer(client->adapter,&msg,1);}/**@description:读取ap3216c指定寄存器值,读取一个寄存器*@param-dev:ap3216c设备*@param-reg:要读取的寄存器*@return:读取到的寄存器值*/staticunsignedcharap3216c_read_reg(structap3216c_dev*dev,u8reg){u8data=0;ap3216c_read_regs(dev,reg,&data,1);returndata;#if0structi2c_client*client=(structi2c_client*)dev->private_data;returni2c_smbus_read_byte_data(client,reg);#endif}/**@description:向ap3216c指定寄存器写入指定的值,写一个寄存器*@param-dev:ap3216c设备*@param-reg:要写的寄存器*@param-data:要写入的值*@return:无*/staticvoidap3216c_write_reg(structap3216c_dev*dev,u8reg,u8data){u8buf=0;buf=data;ap3216c_write_regs(dev,reg,&buf,1);}/**@description :读取AP3216C的数据,读取原始数据,包括ALS,PS和IR, 注意!*:如果同时打开ALS,IR+PS的话两次数据读取的时间间隔要大于112.5ms*@param-ir:ir数据*@param-ps:ps数据*@param-ps:als数据*@return :无。*/voidap3216c_readdata(structap3216c_dev*dev){unsignedchari=0;unsignedcharbuf[6];/*循环读取所有传感器数据*/for(i=0;i<6;i++){buf[i]=ap3216c_read_reg(dev,AP3216C_IRDATALOW+i);}if(buf[0]&0X80)/*IR_OF位为1,则数据无效*/dev->ir=0;else/*读取IR传感器的数据*/dev->ir=((unsignedshort)buf[1]<<2)|(buf[0]&0X03);dev->als=((unsignedshort)buf[3]<<8)|buf[2];/*读取ALS传感器的数据*/if(buf[4]&0x40)/*IR_OF位为1,则数据无效*/dev->ps=0;else/*读取PS传感器的数据*/dev->ps=((unsignedshort)(buf[5]&0X3F)<<4)|(buf[4]&0X0F);}/**@description:打开设备*@param-inode:传递给驱动的inode*@param-filp:设备文件,file结构体有个叫做private_data的成员变量*一般在open的时候将private_data指向设备结构体。*@return:0成功;其他失败*/staticintap3216c_open(structinode*inode,structfile*filp){filp->private_data=&ap3216cdev;/*初始化AP3216C*/ap3216c_write_reg(&ap3216cdev,AP3216C_SYSTEMCONG,0x04);/*复位AP3216C*/mdelay(50);/*AP3216C复位最少10ms*/ap3216c_write_reg(&ap3216cdev,AP3216C_SYSTEMCONG,0X03);/*开启ALS、PS+IR*/return0;}/**@description:从设备读取数据*@param-filp:要打开的设备文件(文件描述符)*@param-buf:返回给用户空间的数据缓冲区*@param-cnt:要读取的数据长度*@param-offt:相对于文件首地址的偏移*@return:读取的字节数,如果为负值,表示读取失败*/staticssize_tap3216c_read(structfile*filp,char__user*buf,size_tcnt,loff_t*off){shortdata[3];longerr=0;structap3216c_dev*dev=(structap3216c_dev*)filp->private_data;ap3216c_readdata(dev);data[0]=dev->ir;data[1]=dev->als;data[2]=dev->ps;err=copy_to_user(buf,data,sizeof(data));return0;}/**@description:关闭/释放设备*@param-filp:要关闭的设备文件(文件描述符)*@return:0成功;其他失败*/staticintap3216c_release(structinode*inode,structfile*filp){return0;}/*AP3216C操作函数*/staticconststructfile_operationsap3216c_ops={.owner=THIS_MODULE,.open=ap3216c_open,.read=ap3216c_read,.release=ap3216c_release,};/**@description:i2c驱动的probe函数,当驱动与*设备匹配以后此函数就会执行*@param-client:i2c设备*@param-id:i2c设备ID*@return:0,成功;其他负值,失败*/staticintap3216c_probe(structi2c_client*client,conststructi2c_device_id*id){/*1、构建设备号*/if(ap3216cdev.major){ap3216cdev.devid=MKDEV(ap3216cdev.major,0);register_chrdev_region(ap3216cdev.devid,AP3216C_CNT,AP3216C_NAME);}else{alloc_chrdev_region(&ap3216cdev.devid,0,AP3216C_CNT,AP3216C_NAME);ap3216cdev.major=MAJOR(ap3216cdev.devid);}/*2、注册设备*/cdev_init(&ap3216cdev.cdev,&ap3216c_ops);cdev_add(&ap3216cdev.cdev,ap3216cdev.devid,AP3216C_CNT);/*3、创建类*/ap3216cdev.class=class_create(THIS_MODULE,AP3216C_NAME);if(IS_ERR(ap3216cdev.class)){returnPTR_ERR(ap3216cdev.class);}/*4、创建设备*/ap3216cdev.device=device_create(ap3216cdev.class,NULL,ap3216cdev.devid,NULL,AP3216C_NAME);if(IS_ERR(ap3216cdev.device)){returnPTR_ERR(ap3216cdev.device);}ap3216cdev.private_data=client;return0;}/**@description:i2c驱动的remove函数,移除i2c驱动的时候此函数会执行*@param-client:i2c设备*@return:0,成功;其他负值,失败*/staticintap3216c_remove(structi2c_client*client){/*删除设备*/cdev_del(&ap3216cdev.cdev);unregister_chrdev_region(ap3216cdev.devid,AP3216C_CNT);/*注销掉类和设备*/device_destroy(ap3216cdev.class,ap3216cdev.devid);class_destroy(ap3216cdev.class);return0;}/*传统匹配方式ID列表*/staticconststructi2c_device_idap3216c_id[]={{"iot,ap3216c",0},{}};/*设备树匹配列表*/staticconststructof_device_idap3216c_of_match[]={{.compatible="iot,ap3216c"},{/*Sentinel*/}};/*i2c驱动结构体*/staticstructi2c_driverap3216c_driver={.probe=ap3216c_probe,.remove=ap3216c_remove,.driver={.owner=THIS_MODULE,.name="ap3216c",.of_match_table=ap3216c_of_match,},.id_table=ap3216c_id,};/**@description:驱动入口函数*@param:无*@return:无*/staticint__initap3216c_init(void){intret=0;ret=i2c_add_driver(&ap3216c_driver);returnret;}/**@description:驱动出口函数*@param:无*@return:无*/staticvoid__exitap3216c_exit(void){i2c_del_driver(&ap3216c_driver);}/*module_i2c_driver(ap3216c_driver)*/module_init(ap3216c_init);module_exit(ap3216c_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("pjw");

驱动详解可查阅注释及配合上诉的I2C驱动框架的框图及数据手册理解。

ap3216creg.h:

#ifndefAP3216C_H#defineAP3216C_H/***************************************************************文件名:ap3216creg.h描述:AP3216C寄存器地址描述头文件***************************************************************/#defineAP3216C_ADDR0X1E/*AP3216C器件地址*//*AP3316C寄存器*/#defineAP3216C_SYSTEMCONG0x00/*配置寄存器*/#defineAP3216C_INTSTATUS0X01/*中断状态寄存器*/#defineAP3216C_INTCLEAR0X02/*中断清除寄存器*/#defineAP3216C_IRDATALOW0x0A/*IR数据低字节*/#defineAP3216C_IRDATAHIGH0x0B/*IR数据高字节*/#defineAP3216C_ALSDATALOW0x0C/*ALS数据低字节*/#defineAP3216C_ALSDATAHIGH0X0D/*ALS数据高字节*/#defineAP3216C_PSDATALOW0X0E/*PS数据低字节*/#defineAP3216C_PSDATAHIGH0X0F/*PS数据高字节*/#endif

ap3216应用:

ap3216cApp.c:

#include"stdio.h"#include"unistd.h"#include"sys/types.h"#include"sys/stat.h"#include"sys/ioctl.h"#include"fcntl.h"#include"stdlib.h"#include"string.h"#include#include#include#include#include/***************************************************************文件名:ap3216cApp.c描述: ap3216c设备测试APP。使用方法:./ap3216cApp /dev/ap3216c***************************************************************//**@description:main主程序*@param-argc:argv数组元素个数*@param-argv:具体参数*@return:0成功;其他失败*/intmain(intargc,char*argv[]){intfd;char*filename;unsignedshortdatabuf[3];unsignedshortir,als,ps;intret=0;if(argc!=2){printf("ErrorUsage!");return-1;}filename=argv[1];fd=open(filename,O_RDWR);if(fd<0){printf("can"topenfile%s",filename);return-1;}while(1){ret=read(fd,databuf,sizeof(databuf));if(ret==0){/*数据读取成功*/ir=databuf[0];/*ir传感器数据*/als=databuf[1];/*als传感器数据*/ps=databuf[2];/*ps传感器数据*/printf("ir=%d,als=%d,ps=%d",ir,als,ps);}usleep(200000);/*100ms*/}close(fd);/*关闭文件*/return0;}

编写Makefile,从之前的文章拷贝过来修改:

KERN_DIR=/home/book/100ask_imx6ull-sdk/Linux-4.9.88all:make-C$(KERN_DIR)M=`pwd`modules$(CROSS_COMPILE)gcc-oap3216cAppap3216cApp.cclean:make-C$(KERN_DIR)M=`pwd`modulescleanrm-rfmodules.orderrm-fap3216cApp#参考内核源码drivers/char/ipmi/Makefile#要想把a.c,b.c编译成ab.ko,可以这样指定:#ab-y:=a.ob.o#obj-m+=ab.oobj-m+=ap3216.o

编译得到ap3216.ko及ap3216cApp,传到板子上运行:

以上就是本次的实验分享,如果文章对你有帮助,欢迎转发,谢谢!

编辑:黄飞

关键词: