1、AP3216簡介
AP3216C 芯片集成了光強傳感器( ALS: Ambient Light Sensor),接近傳感器( PS: Proximity Sensor),還有一個紅外 LED( IR LED)。
這個芯片設計的用途是給手機之類的使用,比如:返回當前環境光強以便調整屏幕亮度;用戶接聽電話時,將手機放置在耳邊后,自動關閉屏幕避免用戶誤觸碰 。
2、IIC驅動簡介
Linux下IIC有兩種驅動方式:一種是按照字符設備驅動方式來驅動IIC;另一種是走Linux下IIC的框架。按照字符設備驅動的方式可以查閱這一篇文章:Linux IIC 字符設備 驅動例子。
這里我們了解學習一下第二種方式,因為找到的AP3216的驅動就是基于IIC驅動框架的,哈哈。
整個IIC的驅動框架相關代碼在driversi2c中,包含的內容有:
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驅動程序 ***************************************************************/ #define?AP3216C_CNT?1 #define?AP3216C_NAME?"ap3216c" ? struct?ap3216c_dev?{ ?dev_t?devid;???/*?設備號???*/ ?struct?cdev?cdev;??/*?cdev??*/ ?struct?class?*class;?/*?類???*/ ?struct?device?*device;?/*?設備???*/ ?struct?device_node?*nd;?/*?設備節點?*/ ?int?major;???/*?主設備號?*/ ?void?*private_data;?/*?私有數據?*/ ?unsigned?short?ir,?als,?ps;??/*?三個光傳感器數據?*/ }; ? static?struct?ap3216c_dev?ap3216cdev; ? /* ?*?@description?:?從ap3216c讀取多個寄存器數據 ?*?@param?-?dev:??ap3216c設備 ?*?@param?-?reg:??要讀取的寄存器首地址 ?*?@param?-?val:??讀取到的數據 ?*?@param?-?len:??要讀取的數據長度 ?*?@return???:?操作結果 ?*/ static?int?ap3216c_read_regs(struct?ap3216c_dev?*dev,?u8?reg,?void?*val,?int?len) { ?int?ret; ?struct?i2c_msg?msg[2]; ?struct?i2c_client?*client?=?(struct?i2c_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("i2c?rd?failed=%d?reg=%06x?len=%d ",ret,?reg,?len); ??ret?=?-EREMOTEIO; ?} ?return?ret; } ? /* ?*?@description?:?向ap3216c多個寄存器寫入數據 ?*?@param?-?dev:??ap3216c設備 ?*?@param?-?reg:??要寫入的寄存器首地址 ?*?@param?-?val:??要寫入的數據緩沖區 ?*?@param?-?len:??要寫入的數據長度 ?*?@return????:???操作結果 ?*/ static?s32?ap3216c_write_regs(struct?ap3216c_dev?*dev,?u8?reg,?u8?*buf,?u8?len) { ?u8?b[256]; ?struct?i2c_msg?msg; ?struct?i2c_client?*client?=?(struct?i2c_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;???/*?要寫入的數據長度?*/ ? ?return?i2c_transfer(client->adapter,?&msg,?1); } ? /* ?*?@description?:?讀取ap3216c指定寄存器值,讀取一個寄存器 ?*?@param?-?dev:??ap3216c設備 ?*?@param?-?reg:??要讀取的寄存器 ?*?@return????:???讀取到的寄存器值 ?*/ static?unsigned?char?ap3216c_read_reg(struct?ap3216c_dev?*dev,?u8?reg) { ?u8?data?=?0; ? ?ap3216c_read_regs(dev,?reg,?&data,?1); ?return?data; ? #if?0 ?struct?i2c_client?*client?=?(struct?i2c_client?*)dev->private_data; ?return?i2c_smbus_read_byte_data(client,?reg); #endif } ? /* ?*?@description?:?向ap3216c指定寄存器寫入指定的值,寫一個寄存器 ?*?@param?-?dev:??ap3216c設備 ?*?@param?-?reg:??要寫的寄存器 ?*?@param?-?data:?要寫入的值 ?*?@return???:????無 ?*/ static?void?ap3216c_write_reg(struct?ap3216c_dev?*dev,?u8?reg,?u8?data) { ?u8?buf?=?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 ??:?無。 ?*/ void?ap3216c_readdata(struct?ap3216c_dev?*dev) { ?unsigned?char?i?=0; ????unsigned?char?buf[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?=?((unsigned?short)buf[1]?<2)?|?(buf[0]?&?0X03);???? ? ?dev->als?=?((unsigned?short)buf[3]?<8)?|?buf[2];?/*?讀取ALS傳感器的數據?????*/?? ? ????if(buf[4]?&?0x40)?/*?IR_OF位為1,則數據無效????*/ ??dev->ps?=?0;????????????????? ?else?????/*?讀取PS傳感器的數據????*/ ??dev->ps?=?((unsigned?short)(buf[5]?&?0X3F)?<4)?|?(buf[4]?&?0X0F);? } ? /* ?*?@description??:?打開設備 ?*?@param?-?inode??:?傳遞給驅動的inode ?*?@param?-?filp??:?設備文件,file結構體有個叫做private_data的成員變量 ?*????????一般在open的時候將private_data指向設備結構體。 ?*?@return????:?0?成功;其他?失敗 ?*/ static?int?ap3216c_open(struct?inode?*inode,?struct?file?*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???*/ ?return?0; } ? /* ?*?@description??:?從設備讀取數據? ?*?@param?-?filp??:?要打開的設備文件(文件描述符) ?*?@param?-?buf??:?返回給用戶空間的數據緩沖區 ?*?@param?-?cnt??:?要讀取的數據長度 ?*?@param?-?offt??:?相對于文件首地址的偏移 ?*?@return????:?讀取的字節數,如果為負值,表示讀取失敗 ?*/ static?ssize_t?ap3216c_read(struct?file?*filp,?char?__user?*buf,?size_t?cnt,?loff_t?*off) { ?short?data[3]; ?long?err?=?0; ? ?struct?ap3216c_dev?*dev?=?(struct?ap3216c_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)); ?return?0; } ? /* ?*?@description??:?關閉/釋放設備 ?*?@param?-?filp??:?要關閉的設備文件(文件描述符) ?*?@return????:?0?成功;其他?失敗 ?*/ static?int?ap3216c_release(struct?inode?*inode,?struct?file?*filp) { ?return?0; } ? /*?AP3216C操作函數?*/ static?const?struct?file_operations?ap3216c_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,成功;其他負值,失敗 ??*/ static?int?ap3216c_probe(struct?i2c_client?*client,?const?struct?i2c_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))?{ ??return?PTR_ERR(ap3216cdev.class); ?} ? ?/*?4、創建設備?*/ ?ap3216cdev.device?=?device_create(ap3216cdev.class,?NULL,?ap3216cdev.devid,?NULL,?AP3216C_NAME); ?if?(IS_ERR(ap3216cdev.device))?{ ??return?PTR_ERR(ap3216cdev.device); ?} ? ?ap3216cdev.private_data?=?client; ? ?return?0; } ? /* ?*?@description?????:?i2c驅動的remove函數,移除i2c驅動的時候此函數會執行 ?*?@param?-?client??:?i2c設備 ?*?@return??????????:?0,成功;其他負值,失敗 ?*/ static?int?ap3216c_remove(struct?i2c_client?*client) { ?/*?刪除設備?*/ ?cdev_del(&ap3216cdev.cdev); ?unregister_chrdev_region(ap3216cdev.devid,?AP3216C_CNT); ? ?/*?注銷掉類和設備?*/ ?device_destroy(ap3216cdev.class,?ap3216cdev.devid); ?class_destroy(ap3216cdev.class); ?return?0; } ? /*?傳統匹配方式ID列表?*/ static?const?struct?i2c_device_id?ap3216c_id[]?=?{ ?{"iot,ap3216c",?0},?? ?{} }; ? /*?設備樹匹配列表?*/ static?const?struct?of_device_id?ap3216c_of_match[]?=?{ ?{?.compatible?=?"iot,ap3216c"?}, ?{?/*?Sentinel?*/?} }; ? /*?i2c驅動結構體?*/? static?struct?i2c_driver?ap3216c_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???:?無 ?*/ static?int?__init?ap3216c_init(void) { ?int?ret?=?0; ? ?ret?=?i2c_add_driver(&ap3216c_driver); ?return?ret; } ? /* ?*?@description?:?驅動出口函數 ?*?@param???:?無 ?*?@return???:?無 ?*/ static?void?__exit?ap3216c_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:
#ifndef?AP3216C_H #define?AP3216C_H /*************************************************************** 文件名??:?ap3216creg.h 描述?????:?AP3216C寄存器地址描述頭文件 ***************************************************************/ #define?AP3216C_ADDR?????0X1E?/*?AP3216C器件地址??*/ /*?AP3316C寄存器?*/ #define?AP3216C_SYSTEMCONG?0x00?/*?配置寄存器???????*/ #define?AP3216C_INTSTATUS?0X01?/*?中斷狀態寄存器???*/ #define?AP3216C_INTCLEAR?0X02?/*?中斷清除寄存器???*/ #define?AP3216C_IRDATALOW?0x0A?/*?IR數據低字節?????*/ #define?AP3216C_IRDATAHIGH?0x0B?/*?IR數據高字節?????*/ #define?AP3216C_ALSDATALOW?0x0C?/*?ALS數據低字節????*/ #define?AP3216C_ALSDATAHIGH?0X0D?/*?ALS數據高字節????*/ #define?AP3216C_PSDATALOW?0X0E?/*?PS數據低字節?????*/ #define?AP3216C_PSDATAHIGH?0X0F?/*?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?成功;其他?失敗 ?*/ int?main(int?argc,?char?*argv[]) { ?int?fd; ?char?*filename; ?unsigned?short?databuf[3]; ?unsigned?short?ir,?als,?ps; ?int?ret?=?0; ? ?if?(argc?!=?2)?{ ??printf("Error?Usage! "); ??return?-1; ?} ? ?filename?=?argv[1]; ?fd?=?open(filename,?O_RDWR); ?if(fd?0)?{ ??printf("can't?open?file?%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);?/*?關閉文件?*/? ?return?0; }
編寫Makefile,從之前的文章拷貝過來修改:
KERN_DIR?=?/home/book/100ask_imx6ull-sdk/Linux-4.9.88 all: ?make?-C?$(KERN_DIR)?M=`pwd`?modules? ?$(CROSS_COMPILE)gcc?-o?ap3216cApp?ap3216cApp.c? clean: ?make?-C?$(KERN_DIR)?M=`pwd`?modules?clean ?rm?-rf?modules.order ?rm?-f?ap3216cApp #?參考內核源碼drivers/char/ipmi/Makefile #?要想把a.c,?b.c編譯成ab.ko,?可以這樣指定: #?ab-y?:=?a.o?b.o #?obj-m?+=?ab.o obj-m?+=?ap3216.o
編譯得到ap3216.ko及ap3216cApp,傳到板子上運行:
以上就是本次的實驗分享,如果文章對你有幫助,歡迎轉發,謝謝!
編輯:黃飛
?
評論
查看更多