I2C 驅(qū)動(dòng)開發(fā) 文檔一,、 開發(fā)背景 開發(fā)環(huán)境:DM355開發(fā)板 內(nèi)核版本:2.6.10 二、 BQ27501驅(qū)動(dòng)開發(fā)的需求 BQ27501是一個(gè)鋰電池管理的芯片,,可以向外提供鋰電池的有關(guān)的信息,。在用戶空間用戶不能直接訪問bq27501的寄存器,所以要為其編寫驅(qū)動(dòng),,該驅(qū)動(dòng)能夠根據(jù)用戶空間的命令,,返回對(duì)應(yīng)的電池信息。 三,、 I2C 驅(qū)動(dòng)的架構(gòu) bq27501是通過I2C總線與DM355通信的,,故bq27501的驅(qū)動(dòng)實(shí)際上就是bq27501的I2C讀寫的驅(qū)動(dòng),。 Linux內(nèi)核中的 i2c 驅(qū)動(dòng)程序可以分為三個(gè)層次,如下圖所示: 圖1 Linux I2C體系結(jié)構(gòu)
1) i2c 驅(qū)動(dòng)框架 i2c框架主要有i2c.h和 i2c-core.c文件實(shí)現(xiàn),。它們定義驅(qū)動(dòng)中使用的核心數(shù)據(jù)結(jié)構(gòu),,完成 i2c適配器和設(shè)備驅(qū)動(dòng)的注冊(cè),注銷,,并且實(shí)現(xiàn) i2c驅(qū)動(dòng)的algorithm,。 i2c驅(qū)動(dòng)中的algorithm與適配器無關(guān),它屬于上層代碼,,還包括探測(cè)設(shè)備、檢測(cè)設(shè)備地址的上層代碼,。另外,,i2c-dev.c還用于對(duì) i2c設(shè)備節(jié)點(diǎn)的創(chuàng)建,并完成其訪問方法的實(shí)現(xiàn)等,。 2) i2c 總線驅(qū)動(dòng) 總線驅(qū)動(dòng)的職責(zé),,是為系統(tǒng)中每個(gè)I2C總線增加相應(yīng)的讀寫方法。但是總線驅(qū)動(dòng)本身并不會(huì)進(jìn)行任何的通訊,,它只是存在那里,,等待設(shè)備驅(qū)動(dòng)調(diào)用其函數(shù)。在系統(tǒng)開機(jī)時(shí),,首先裝載的是I2C總線驅(qū)動(dòng),。一個(gè)總線驅(qū)動(dòng)用于支持一條特定的I2C總線的讀寫。這部分主要定義i2c_adapter和i2c_algorithm數(shù)據(jù)結(jié)構(gòu),,前者用來描述具體的 i2c 總線適配器,,后者則描述i2c 總線的通信方法。一般總線驅(qū)動(dòng)由平臺(tái)提供實(shí)現(xiàn),,davinci的i2c總線驅(qū)動(dòng)在i2c-davinci.c文件中定義了,,然后通過i2c_davinci_init函數(shù)調(diào)用i2c_add_adapter(i2c_davinci_adap)將這個(gè)兩個(gè)模塊注冊(cè)到操作系統(tǒng)中,總線驅(qū)動(dòng)就裝上了,。
3) i2c 設(shè)備驅(qū)動(dòng) 設(shè)備驅(qū)動(dòng)則是與掛在I2C總線上的具體的設(shè)備通訊的驅(qū)動(dòng),。通過I2C總線驅(qū)動(dòng)提供的函數(shù),設(shè)備驅(qū)動(dòng)可以忽略不同總線控制器的差異,,不考慮其實(shí)現(xiàn)細(xì)節(jié)地與硬件設(shè)備通訊,。它實(shí)現(xiàn)對(duì)具體的i2c設(shè)備的描述,另外還包括一些可能用到的數(shù)據(jù)結(jié)構(gòu),。它借助 i2c框架中的 i2c_probe函數(shù)實(shí)現(xiàn)設(shè)備的attach_adapter方法,,完成設(shè)備檢測(cè)成功后i2c_client 數(shù)據(jù)結(jié)構(gòu)回調(diào)函數(shù)的創(chuàng)建。實(shí)際操作過程中可以跳過i2c_probe函數(shù)直接調(diào)用實(shí)現(xiàn)i2c_client的函數(shù),,這樣可以不必遵循調(diào)用i2c_probe固有的參數(shù)格式,,從而可以提高效率和節(jié)省存儲(chǔ)空間,。bq27501的驅(qū)動(dòng)加載流程如下圖2所示。
圖2 bq27501的i2c驅(qū)動(dòng)加載流程圖
bq27501_init()函數(shù)為該驅(qū)動(dòng)模塊的模塊初始化函數(shù),,當(dāng)在linux下使用insmod命令裝載該模塊時(shí)會(huì)執(zhí)行該函數(shù),。在初始化函數(shù)中,對(duì)i2c_driver結(jié)構(gòu)體變量初始化,,然后調(diào)用register_chrdev()函數(shù)注冊(cè)字符設(shè)備,。接著調(diào)用i2c_add_driver()函數(shù)添加一個(gè)i2c的driver。i2c_add_driver函數(shù)的執(zhí)行會(huì)引發(fā)i2c_driver中attach_adapter指向的函數(shù)bq27501_i2c_probe_adapter()函數(shù)的執(zhí)行,,該函數(shù)是用來探測(cè)物理設(shè)備的,。它需要通過調(diào)用_i2c_attach_client函數(shù)來實(shí)現(xiàn)探測(cè),并且在_i2c_attach_client函數(shù)內(nèi)調(diào)用i2c_attach_client函數(shù)在總線上附加一個(gè)新的client,;或者調(diào)用i2c-core.c中的i2c_probe函數(shù),,由i2c_probe函數(shù)再調(diào)用探測(cè)物理設(shè)備的函數(shù),bq27501驅(qū)動(dòng)中是采用的第一種方式,。 四,、 實(shí)現(xiàn)I2C驅(qū)動(dòng)編寫方式 最新的內(nèi)核支持兩種編寫i2c驅(qū)動(dòng)的方式,一個(gè)是“Adapter方式(legacy)”,,另一個(gè)是“Probe方式(new style)”,。兩種方式的區(qū)別在于i2c_driver結(jié)構(gòu)體不同,“Adapter方式”的i2c_driver結(jié)構(gòu)是:
“Probe方式”的i2c_driver結(jié)構(gòu)是:
兩種方式i2c_driver結(jié)構(gòu)主要的不同是后者添加了probe和remove函數(shù)指針和id_table,。bq27501使用的Ti-davinci內(nèi)核是較早版本的,,只支持Adapter方式,不支持Probe方式,,所以bq27501采用的是Adapter方式,。Adapter方式編寫的流程就是上節(jié)中所描述的i2c驅(qū)動(dòng)加載的流程相同。至于Probe方式,,在本次驅(qū)動(dòng)編寫中沒有使用,,所以在此不作詳細(xì)的介紹。兩種方式的對(duì)比可以參加網(wǎng)頁資料http://www./Column/Column213.htm,。 五,、 BQ27501的I2C驅(qū)動(dòng)編寫 要使bq27501的i2c驅(qū)動(dòng)模塊能夠運(yùn)行,必須至少要編寫兩個(gè)文件,,第一個(gè)是驅(qū)動(dòng)的源文件,,第二個(gè)是編譯源文件的Makefile文件。另外該驅(qū)動(dòng)的兩個(gè)關(guān)鍵點(diǎn)是i2c通信和與用戶空間交互數(shù)據(jù),。 1) Makefile文件編寫 #如果已定義KERNELRELEASE,,則說明是從內(nèi)核構(gòu)造系統(tǒng)調(diào)用的,因此可以利用其內(nèi)建語句,。 ifneq($(KERNELRELEASE),) obj-m := bq27501.o #否則,,是直接從命令開始調(diào)用的,,這時(shí)要調(diào)用內(nèi)核構(gòu)造系統(tǒng)。
else KDIR ?=/home/zl/ti-davinci PWD := $(shellpwd) CROSS_COMPILE=arm-v5t-le- CC=$(CROSS_COMPILE)gcc default: make -C $(KDIR) M=$(PWD) modules endif clean: rm -rf *.o *.cmd *.mod.c *.sysmvers
注: 1. Makefile文件的文件名中M一定要大寫,。這是因?yàn)榫幾g的時(shí)候首先看環(huán)境變量KERNELRELEASE是否定義,,如果沒定義則調(diào)用Linux內(nèi)核編譯build腳本。該腳本會(huì)首先編譯內(nèi)核,,其間會(huì)創(chuàng)建環(huán)境變量KERNELRELEASE,,接著編譯當(dāng)前工作目錄下的hello模塊,此時(shí)會(huì)第二遍讀取Makefile,再次判斷環(huán)境變量KERNELRELEASE是否定義,,已經(jīng)定義的情況下開始編譯hello模塊,。 2. Makefile文件中的命令行,以Tab鍵開頭(不能是空格),,例如make,,clean。依賴條件頂格,,例如default,clean,。 3. KDIR:指向嵌入系統(tǒng)的linux內(nèi)核,,而不是正在運(yùn)行的系統(tǒng)的內(nèi)核。 4.CROSS_COMPILE:交叉編譯環(huán)境,,也就是安裝的dvdsdk,。 5. CC:指明編譯器,加上CROSS_COMPILE,,定義了一個(gè)交叉編譯器,。 6. clean:當(dāng)時(shí)使用makeclean命令時(shí)清除編譯的結(jié)果。 7. default:當(dāng)使用make命令后面不加任何參數(shù)時(shí),,默認(rèn)執(zhí)行的語句,。 將驅(qū)動(dòng)代碼的源文件和Makefile文件放到同一個(gè)文件夾下面。在終端進(jìn)入到目錄下,,使用make命令對(duì)其進(jìn)行編譯,。
2) 模塊編程 為了bq27501驅(qū)動(dòng)測(cè)試的方便,所以采用模塊編程的方式來實(shí)現(xiàn),。將bq27501驅(qū)動(dòng)作為一個(gè)內(nèi)核模塊動(dòng)態(tài)加載到內(nèi)核中,,而不是采用在內(nèi)核樹中添加代碼實(shí)現(xiàn)這種靜態(tài)的方法實(shí)現(xiàn)的。 第一,,模塊編程的程序的頭文件,。 #include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> 這三個(gè)頭文件是編寫內(nèi)核模塊程序必須包含的3個(gè)頭文件。 第二,,內(nèi)核模塊必須包含兩個(gè)函數(shù),。一個(gè)是加載時(shí)模塊初始化函數(shù),,另一個(gè)是模塊卸載時(shí)卸載的函數(shù)。bq27501的初始化函數(shù)為staticint __initbq27501_init(void),;module_init(bq27501_init),;初始化函數(shù)聲明為static,因?yàn)槌跏蓟瘮?shù)在特定文件文件之外沒有其他意義,,并且驅(qū)動(dòng)初始化函數(shù)前面加上__init標(biāo)記,,這表明該函數(shù)僅在初始化期間使用。在模塊被裝載之后,,模塊裝載器就會(huì)將初始化函數(shù)扔掉,,這樣可以將該函數(shù)占用的內(nèi)存釋放出來以作他用(注,不能在初始化之后仍要使用的函數(shù),,或者數(shù)據(jù)結(jié)構(gòu)),。module_init這個(gè)宏會(huì)在模塊的目標(biāo)代碼中增加一個(gè)特殊的段,用于說明內(nèi)核初始化函數(shù)所在的位置,。如果沒有這個(gè)定義,,那么初始化函數(shù)bq27501將不會(huì)被調(diào)用。所以是必須使用的module_init,。bq27501的卸載函數(shù)為staticvoid __exit bq27501_exit(void),;module_exit(bq27501_exit);該函數(shù)在模塊被卸載前注銷接口并向系統(tǒng)中返回所有資源,。卸載函數(shù)沒有返回值,,故要聲明為void。__exit標(biāo)記表示該代碼用于模塊卸載,。如果模塊被直接內(nèi)核的配置不允許卸載模塊,,則被標(biāo)記為__exit的函數(shù)將被簡(jiǎn)單地丟棄。所以被標(biāo)記為__exit的函數(shù)只能在模塊被卸載或者系統(tǒng)關(guān)閉時(shí)被調(diào)用,,其他任何用法都是錯(cuò)誤的,。和module_init類似,module_exit用于內(nèi)核可以找到模塊的卸載函數(shù),。如果模塊沒有定義卸載函數(shù),,那么內(nèi)核不允許卸載該模塊。 第三,,模塊的聲明與描述,。MODULE_LICENSE("GPLv2");描述內(nèi)核模塊的許可權(quán)限,如果不聲明LICENSE,,模塊被加載時(shí),,將收到內(nèi)核的警告。 在Linux2.6 內(nèi)核中,,可接受的LICENSE包括“GPL”(任一版本的GNU通用公共許可證),,“GPL v2”(GPL版本2),,“GPLand additional rights”(MPL/GPL雙重許可證),,“DualBSD/GPL”(雙重許可證),,“DualMPL/GPL”,“Proprietary”,。 MODULE_AUTHOR("zl"); 聲明該模塊的作者,。MODULE_DESCRIPTION("BQ27501Driver");對(duì)該模塊功能簡(jiǎn)單的描述,。 以上是內(nèi)核模塊編程的三個(gè)特點(diǎn)。其他函數(shù)的編寫和調(diào)用和普通的GNU C一樣,。 3) bq27501的i2c通信 bq27501驅(qū)動(dòng)的核心和關(guān)鍵就是完成i2c的通信,,對(duì)于不同的設(shè)備i2c通信的消息格式可能不同,需要查閱對(duì)應(yīng)的數(shù)據(jù)手冊(cè),。從bq27501的數(shù)據(jù)手冊(cè)中獲得與其通信的message的格式,,如下圖(3)所示: 圖(3) bq27501的i2c通信消息格式 選擇1-byte read消息格式,ADDR+CMD為一條寫消息,,ADDR+DATA為一條讀消息,。CMD為要讀信息的command code,DATA是返回的數(shù)據(jù),。例如電壓voltage的commandcode為0x08/0x09兩個(gè)字節(jié),,返回值為一個(gè)unsignedint型的值,即也為兩個(gè)字節(jié),,所以要讀取電壓值,要執(zhí)行兩次1-byte read,。構(gòu)造的i2c_msg格式如下面的代碼片段所示,。
4) 驅(qū)動(dòng)與用戶空間的數(shù)據(jù)交互 內(nèi)核中的數(shù)據(jù)與用戶空間數(shù)據(jù)交互常用的函數(shù)有copy_to_user,copy_from_user,,和宏定義put_user,,get_user,__put_user,,__get_user,。copy_from_user和copy_to_user函數(shù)復(fù)制塊數(shù)據(jù),如數(shù)組,,結(jié)構(gòu)體,;put_user,get_user,,__put_user,,__get_user復(fù)制的內(nèi)存是簡(jiǎn)單類型,如char,,int,,long,,而且只能復(fù)制1,2,4,8個(gè)字節(jié)。put_user,,get_user,,__put_user,__get_user執(zhí)行效率比copy_to_user,,copy_from_user的效率要高很多,。put_user和__put_user區(qū)別在于,前者會(huì)調(diào)用access_ok進(jìn)行內(nèi)核地址的檢查,,而后者不進(jìn)行地址檢查,。bq27501驅(qū)動(dòng)的功能是向用戶空間提供電池相關(guān)的信息;而且不需要用戶空間向電池輸入數(shù)據(jù),;另外電池的信息都是很小的數(shù)據(jù),,都可以使用unsigned short表示,所以沒有必要使用copy_to_user塊數(shù)據(jù)傳遞的函數(shù),;另外再根據(jù)《Linux驅(qū)動(dòng)程序》書中“大多數(shù)驅(qū)動(dòng)程序代碼中都不需要access_ok,,內(nèi)存管理程序會(huì)處理它”,所以選擇__put_user來向用戶空間傳遞電池信息數(shù)據(jù),。 __put_user(var, ptr),,var將內(nèi)核中的數(shù)據(jù)var復(fù)制到用戶空間;ptr是用戶地址空間的指針,,指向內(nèi)核空間中ioctl最后一個(gè)參數(shù),。bq27501與用戶空間交互的代碼片段如下:
在用戶空間, ioctl 系統(tǒng)調(diào)用的原型為:intioctl(int fd, unsigned long cmd, ...); 這個(gè)原型中的點(diǎn)表示函數(shù)有一個(gè)單個(gè)可選的參數(shù), 傳統(tǒng)上標(biāo)識(shí)為 char *argp. 這些點(diǎn)在那里只是為了阻止在編譯時(shí)的類型檢查。第二個(gè)參數(shù),,是用戶向驅(qū)動(dòng)傳遞的命令(如讀取剩余電量值),。第三個(gè)參數(shù)的實(shí)際特點(diǎn)依賴所發(fā)出的特定的控制命令,即第二個(gè)參數(shù),。一些命令不用參數(shù), 一些用一個(gè)整數(shù)值, 以及一些使用指向其他數(shù)據(jù)的指針,。是否使用參數(shù)和指針是根據(jù)打開字符設(shè)備的方式?jīng)Q定,打開設(shè)備的方式有三種,,在fcntl.h有其宏定義,,只讀,只寫,,讀寫,,如下所示:
六,、 bq27501驅(qū)動(dòng)的測(cè)試 1. 測(cè)試目的: 驗(yàn)證bq27501驅(qū)動(dòng)能否讀取寄存器的值,,和用戶空間的交互。
2. 測(cè)試過程和結(jié)果: bq27501驅(qū)動(dòng)的測(cè)試是要在用戶空間編寫測(cè)試程序。Linux將所有設(shè)備都作為文件來處理的,,所以在用戶空間必須打開bq27501設(shè)備文件,。fd =open("/dev/bq27501",2);這里使用open打開在dev下創(chuàng)建的bq27501的字符設(shè)備,,“2”表示以讀寫的方式打開(也可以用O_RDWR這個(gè)宏來表示),,fd用來保存文件句柄。通過ioctl向設(shè)備文件發(fā)送命令,,并接收返回的電池統(tǒng)計(jì)信息值,,ioctl(fd,atoi(argv[1]),&v);fd是打開的設(shè)備文件的句柄,atoi(argv[1])是將main的參數(shù)轉(zhuǎn)換成整型作為參數(shù)傳給驅(qū)動(dòng),,v用來保存驅(qū)動(dòng)返回的數(shù)值,。 測(cè)試程序編寫完成之后,因?yàn)橐贒M355平臺(tái)上運(yùn)行,,所以必須要交叉編譯源文件testbq.c,。使用交叉編譯環(huán)境dvsdk下的命令arm_v5t_le-gcc testbq.c –o testbq,生成可執(zhí)行的testbq文件。使用make命令編譯驅(qū)動(dòng)的源文件bq27501.c,,生成bq27501.ko文件,。在DM355中使用tftp命令“tftp –g–r bq27501.ko 10.10.101.138”下載bq27501.ko到開發(fā)板上(10.10.101.138為tftp服務(wù)器的地址)。使用insmod命令加載驅(qū)動(dòng)模塊,,即“insmod bq27501.ko”,。加載成功會(huì)打印添加的信息如下: registersucceed!... bq27501_driver->id225 .... I2C:detect address is 55 ... Adaptername is DAVINCI I2Cadapter.. Adddriver succeed!... 使用mknod命令為bq27501創(chuàng)建一個(gè)設(shè)備節(jié)點(diǎn),即命令“mknod /dev/bq27501 c 225 0”,。節(jié)點(diǎn)的名稱為bq27501,,是在/dev目錄下創(chuàng)建的;c表示該節(jié)點(diǎn)是一個(gè)字符設(shè)備,;225表示主設(shè)備號(hào),,這個(gè)必須和驅(qū)動(dòng)中注冊(cè)時(shí)的一樣;0表示從設(shè)備號(hào),。 在DM355中使用tftp命令“tftp –g –r testbq 10.10.101.138”下載testbq到開發(fā)板上,。使用命令“chmod 777 testbq”修改執(zhí)行的權(quán)限,。使用命令“./testbq 3”執(zhí)行testbq文件,,“3”是傳給main函數(shù)的參數(shù),表示獲得電池的電壓值,。運(yùn)行結(jié)果為“Voltage is 3779 mV ... ”,,即3.78v,和電池的額定輸出電壓3.7一樣,。另外還測(cè)試了溫度“Temperature is 2974 k...”,,即24.25攝氏度;剩余電量可用的時(shí)間“Time to Empty is 65535 min..”,即65535分鐘,,表示電池沒有處于放電狀態(tài),,符合實(shí)際情況。 根據(jù)測(cè)試的結(jié)果,,可以說明bq27501驅(qū)動(dòng)和用戶測(cè)試程序的基本功能點(diǎn)已經(jīng)正確實(shí)現(xiàn),。 七、 總結(jié) 1. 不能省略用戶空間測(cè)試文件的編寫,,在初始化函數(shù)中調(diào)用i2c_read_reg函數(shù)測(cè)試驅(qū)動(dòng)i2c通信是否成功,。模塊初始化module_init調(diào)用初始化函數(shù)bq27501_init函數(shù)需要系統(tǒng)調(diào)用sys_init_module()。sys_init_module要得到bq27501_init的返回值才能完成模塊的初始化,。sys_init_module中有這么段代碼:
從上面的代碼可以看出,,模塊的init函數(shù)只能返回0或者負(fù)的錯(cuò)誤碼,否則會(huì)提示錯(cuò)誤,。所以在return語句之前調(diào)用模塊中的其他函數(shù)時(shí)不正確的,,因?yàn)檫@時(shí)模塊還未真正初始化完成。 2. 在編寫bq27501驅(qū)動(dòng)過程中最常見的錯(cuò)誤是“segmentation fault”,。該錯(cuò)誤的原因是訪問了非法的內(nèi)存,,也就是使用了空指針或者未給一個(gè)指針變量分配內(nèi)存空間就直接使用。在內(nèi)核中對(duì)一個(gè)結(jié)構(gòu)體的指針變量,,要先使用kmalloc函數(shù)為其分配空間,,然后才能賦值。 3. 嵌入式linux系統(tǒng)中,,由于內(nèi)存較小,,可以將需要在多個(gè)函數(shù)之間傳遞的參數(shù)的變量設(shè)為全局變量,減少相同變量的申請(qǐng),,這樣就可以節(jié)省內(nèi)存資源,。 |
|