標籤:linux android平台 gpio 驅動程式 a10
前面的博文對Lichee做了系列分析,其實就是對在《七年之癢》中所說的,Android BSP具備的一項基本素質-SHELL指令碼,所以我們Lichee系列的文章著重分析了SHELL指令碼和Lichee的基本結構,當然作為一名合格的Android BSP工程師來說,掌握Linux的驅動程式的移植,也是一項基本技能。所以從本文開始,將對sun4i的一些驅動程式做深入分析。當然了,驅動程式涉及的面很廣,比如網路攝影機的驅動涉及到sensor的移植和核心隊列等資料結構相關內容,SD卡驅動又涉及到DMA的基本原理,觸控螢幕驅動又涉及到輸入子系統,中斷上下文等相關知識,WIFI驅動會涉及到無線區域網路的協議部分以及wap_supplicant等相關知識,2G/3G模組往往會涉及到RIL庫及AT命令等等,除此之外,為了方便管理,大部分外部裝置都掛在IIC USB UART等各種匯流排上(或是虛擬匯流排),每塊開發板都有自己的原理圖和走線方式,這些硬體相關內容又是BSP工程師們在調試驅動時繞不開的。思來想去,由於涉及面過於繁雜,加之本人能力又有限,還是覺得站在BSP的角度上去分析,首先簡單介紹基礎的背景知識,搞懂驅動程式的意思之後,而後再著手最佳化移植驅動,用這種比較實用的方式慢慢地模組看似比較難的驅動程式。
處理器: SUN4I A10系統: Android平台架構: ARM
由簡入繁,首先我們來探討一下sun4i的gpio的驅動程式drivers/misc/sun4i-gpio.c
一、什麼是GPIO
GPIO,英文全稱為General-Purpose IO ports,也就是通用IO口。嵌入式系統中常常有數量眾多,但是結構卻比較簡單的外部裝置/電路,對這些裝置/電路有的需要CPU為之提供控制手段,有的則需要被CPU用作輸入訊號。而且,許多這樣的裝置/電路只要求一位,即只要有開/關兩種狀態就夠了,比如燈亮與滅。對這些裝置/電路的控制,使用傳統的串列口或並行口都不合適。所以在微控制器晶片上一般都會提供一個“通用可程式化IO介面”,即GPIO。
介面至少有兩個寄存器,即“通用IO控制寄存器”與“通用IO資料寄存器”。資料寄存器的各位都直接引到晶片外部,而對這種寄存器中每一位的作用,即每一位的訊號流通方向,則可以通過控制寄存器中對應位獨立的加以設定。這樣,有無GPIO介面也就成為微控制器區別於微處理器的一個特徵。
二、A10 GPIO
A10 有8組多功能輸入/輸出GPIO,如下所示 Port A(PA): 18 input/output port Port B(PB): 24 input/output port Port C(PC): 25 input/output port Port D(PD): 28 input/output port Port E(PE) : 12 input/output port Port F(PF) : 6 input/output port Port G(PG) : 12 input/output port Port H(PH) : 28 input/output port Port I(PI) : 22 input/output port Port S(PS) : 84 input/output port for DRAM controller
為了應對多變的系統配置,這幾組GPIO能很容易地通過軟體來配置,這裡除了PS以外,其他幾組的GPIO均能夠配置為多功能的模式,也支援32個能被軟體配置的外部中斷源和中斷模式Port S(PS) 是專為DRAM控制器所使用的,所以我們通常所使用的GPIO口都是PA-PI之間
三、源碼解析在linux-3.0目錄下 drivers/misc/sun4i-gpio.c我們知道在一個驅動載入的時候,會執行module_init ,驅動退出的時候會執行module_exit那我們就從module_init(sun4i_gpio_init)開始分析,當驅動被載入時,就會到sun4i_gpio_init(void)中去
// 通常情況下,驅動中的函數一般都是要用static來修飾,因為C語言中並沒有C++中裡的namespace,也就是命名空間,在用static修飾過之後,函數名就相當於在當前源檔案內可見,就算其他源檔案中有同名函數也不會影響編譯,這裡我們就選取本驅動程式中,最終要的一個函數sun4i_gpio_init來全文分析
static int __init sun4i_gpio_init(void) { int err; int i; int sun4i_gpio_used = 0; struct sun4i_gpio_data *gpio_i;/*include /linux/sysfs.h-------------------------------------struct attribute { const char *name; struct module *owner; mode_t mode;};struct device_attribute { struct attribute attr; ssize_t (*show)(struct device *dev, struct device_attribute *attr, char *buf); ssize_t (*store)(struct device *dev, struct device_attribute *attr, const char *buf, size_t count);};這2個都是sys檔案系統的中的結構體,關鍵點在device_attribute 中的show 和 store,其實就是對裝置的讀和寫*/ struct device_attribute *attr_i; char pin[16]; pr_info("sun4i gpio driver init\n");/* 用來擷取sys_config1.fex主鍵gpio_para中的子鍵gpio_used的值,如果gpio_used的值為0,則表示該驅動已經通過配置關閉了,這樣就是實現了用配置來控制開啟和關閉驅動 這裡我們知道了如果我們要增加單獨控制的gpio,我們只需要在sys_config1.fex檔案中,添加gpio_para的主鍵和名為gpio_used的子鍵*/ err = script_parser_fetch("gpio_para", "gpio_used", &sun4i_gpio_used, sizeof(sun4i_gpio_used)/sizeof(int)); if(err) { pr_err("%s script_parser_fetch \"gpio_para\" \"gpio_used\" error\n", __FUNCTION__); goto exit; } if(!sun4i_gpio_used) { pr_err("%s sun4i_gpio is not used in config\n", __FUNCTION__); err = -1; goto exit; }/* 用來擷取sys_config1.fex主鍵gpio_para中的子鍵gpio_num的值,很顯然子鍵gpio_num的值,用來定義配置中一共有多少個gpio*/ err = script_parser_fetch("gpio_para", "gpio_num", &sun4i_gpio_num, sizeof(sun4i_gpio_num)/sizeof(int)); if(err) { pr_err("%s script_parser_fetch \"gpio_para\" \"gpio_num\" error\n", __FUNCTION__); goto exit; } sun4i_gpio_dbg("sun4i_gpio_num:%d\n", sun4i_gpio_num); if(!sun4i_gpio_num) { pr_err("%s sun4i_gpio_num is none\n", __FUNCTION__); err = -1; goto exit; }/* 註冊一個雜項裝置,主裝置號是10,此裝置號由系統來定義*/ err = misc_register(&sun4i_gpio_dev); if(err) { pr_err("%s register sun4i_gpio as misc device error\n", __FUNCTION__); goto exit; }/* 根據gpio的個數,對每個gpio結構體申請一塊記憶體,用來儲存從sys_config1.fex檔案中讀取到的每個gpio的屬性*/ psun4i_gpio = kzalloc(sizeof(struct sun4i_gpio_data) * sun4i_gpio_num, GFP_KERNEL);/* 按照gpio的個數,對每個gpio申請一個裝置屬性,每個裝置屬性將用來對sys檔案系統中的gpio的讀寫*/ pattr = kzalloc(sizeof(struct device_attribute) * sun4i_gpio_num, GFP_KERNEL); if(!psun4i_gpio || !pattr) { pr_err("%s kzalloc failed\n", __FUNCTION__); err = -ENOMEM; goto exit; } gpio_i = psun4i_gpio; attr_i = pattr;/* 迴圈對每個gpio的在sys_config1.fex檔案的值進行讀取,並將解析出來的值儲存到gpio_i中*/ for(i = 0; i < sun4i_gpio_num; i++) {/* 由此可以看出,子鍵類似於gpio_pin_1 gpio_pin_2 gpio_pin_3 ......這種方式來命名的*/ sprintf(pin, "gpio_pin_%d", i+1); sun4i_gpio_dbg("pin:%s\n", pin); err = script_parser_fetch("gpio_para", pin, (int *)&gpio_i->info, sizeof(script_gpio_set_t)); if(err) { pr_err("%s script_parser_fetch \"gpio_para\" \"%s\" error\n", __FUNCTION__, pin); break; }/************************************************************************************************************** 這是 CSP_GPIO_Request_EX函數的說明 * CSP_GPIO_Request_EX** 函數名稱:** 參數說明: main_name 傳進的主鍵名稱,匹配模組(驅動名稱)** sub_name 傳進的子鍵名稱,如果是空,表示全部,否則尋找到匹配的單獨GPIO** 傳回值 :0 : err* other: success** 說明 :暫時沒有做衝突檢查***************************************************************************************************************/ gpio_i->gpio_handler = gpio_request_ex("gpio_para", pin); sun4i_gpio_dbg("gpio handler: %d", gpio_i->gpio_handler); if(!gpio_i->gpio_handler) { pr_err("%s can not get \"gpio_para\" \"%s\" gpio handler, already used by others?", __FUNCTION__, pin); break; } sun4i_gpio_dbg("%s: port:%d, portnum:%d\n", pin, gpio_i->info.port, gpio_i->info.port_num); /* Turn the name to pa1, pb2 etc... */ sprintf(gpio_i->name, "p%c%d", 'a'+gpio_i->info.port-1, gpio_i->info.port_num); sun4i_gpio_dbg("psun4i_gpio->name%s\n", gpio_i->name); /* Add attributes to the group *//*這裡將屬性初始化到sys檔案系統,並對device_attribute 結構體的成員賦值,這樣其實就是定義了讀寫IO的函數sun4i_gpio_enable_show就是讀出IO的data,而sun4i_gpio_enable_store就是往IO中寫入值*/ sysfs_attr_init(&attr_i->attr); attr_i->attr.name = gpio_i->name; attr_i->attr.mode = S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH; attr_i->show = sun4i_gpio_enable_show; attr_i->store = sun4i_gpio_enable_store; sun4i_gpio_attributes[i] = &attr_i->attr; gpio_i++; attr_i++; } sysfs_create_group(&sun4i_gpio_dev.this_device->kobj, &sun4i_gpio_attribute_group);exit: return err;}static void __exit sun4i_gpio_exit(void) { sun4i_gpio_dbg("bye, sun4i_gpio exit\n"); sysfs_remove_group(&sun4i_gpio_dev.this_device->kobj, &sun4i_gpio_attribute_group); misc_deregister(&sun4i_gpio_dev); kfree(psun4i_gpio); kfree(pattr);}struct sun4i_gpio_data,這個結構體其實就用來描述一個gpiostruct sun4i_gpio_data { int status; //目前狀態,其實就是gpio的值,0或者1 unsigned gpio_handler; //用來標識這個gpio,相當於一個唯一的id script_gpio_set_t info; char name[8]; //8個位元組的字串用來描述名字 例如"PI09"}script_gpio_set_t 結構體,才是真正用來描述單個的gpiotypedef struct{ char gpio_name[32]; int port; int port_num; int mul_sel; int pull; int drv_level; int data;} script_gpio_set_t;
我們先來看看關於gpio的設定檔;--------------------------------------------------------------------------------;GPIO configuration PC19-PC22 4個IO口是輸入;gpio_pin_1 蜂鳴器;gpio_pin_2 網路攝影機燈;gpio_pin_3 印表機燈;--------------------------------------------------------------------------------[gpio_para]gpio_used = 1gpio_num = 11gpio_pin_1 = port:PI09<1><default><default><0>gpio_pin_2 = port:PB03<1><default><default><0>gpio_pin_3 = port:PH22<1><default><default><0>gpio_pin_4 = port:PH23<1><default><default><0>gpio_pin_5 = port:PH24<1><default><default><0>gpio_pin_6 = port:PH25<1><default><default><0>gpio_pin_7 = port:PH26<1><default><default><0>gpio_pin_8 = port:PC19<1><default><default><0>gpio_pin_9 = port:PC20<1><default><default><0>gpio_pin_10 = port:PC21<1><default><default><0>gpio_pin_11 = port:PC22<1><default><default><0>
以gpio_pin_1為例 , err = script_parser_fetch("gpio_para", pin, (int *)&gpio_i->info, sizeof(script_gpio_set_t)); 這句代碼的意思是從設定檔中擷取主鍵為"gpio_para",子鍵為"gpio_pin_1"的內容儲存在結構script_gpio_set_t中其中
gpio_name ="gpio_pin_1 "
port =‘I‘
port_num =09mul_sel =1pull =defaultdrv_level =defaultdata =0在《 Lichee (五) sysconfig1.fex 配置系統》一文中,我們曾經簡單分析過sysconfig1.fex和描述GPIO的形式
描述gpio的形式:Port:連接埠+組內序號<功能分配><內部電阻狀態><驅動能力><輸出電平狀態>
對應的,mul_sel就是功能分配,是Multifunction select的縮寫,這個決定了屬於什麼功能,由於我們這裡是輸出功能,所以預設值為1查看相關GPIO手冊,mul_sel的選擇如下:
pull對應著內部電阻狀態,drv_level 對應著驅動能力,一般來說都是採用預設值defalut
data即代表著該GPIO的輸出電平狀態,通常情況下1代表高電平,0代表低電平
sysfssys檔案系統是一個處於記憶體中的虛擬檔案系統,它為我們提供了kobject對象的階層師徒,協助使用者能以一個簡單的檔案系統的方式來觀察系統中裝置的拓撲結構。藉助屬性對象,kobject可以用匯出檔案的方式,將核心變數提供給使用者讀取或寫入。裝置模型本來是為了方便電源管理而提出的一種裝置拓撲結構,但是sysfs是頗為意外的收穫,為了方便調試,裝置模型的開發人員決定將裝置結構樹匯出為一個檔案系統。這個舉措很快被證明是非常明智的,首先sysfs代替了處於/proc下的裝置相關檔案;另外它為系統對象提供了一個很有用的視圖,實際上,sysfs起初被稱為driverfs,它早於kobject出現。最終sysfs使我們認識到一個全新的物件模型非常有利於系統,於是kobject應運而生。從kernel在2.6引入sysfs開始,sysfs總是不可或缺的核心的一部分了。
四、結果
當我們載入了這個驅動之後,就出現了跟我們sys_config1.fex檔案裡面同名的gpio,我們可以通過echo 1 > /sys/devices/virtual/misc/sun4i-gpio/pin/pi9echo 0 > /sys/devices/virtual/misc/sun4i-gpio/pin/pi9直接來控制GPIO了,而不需要通過程式這裡還有一點值得注意,所以的檔案都是 -rw-rw-rw-,也就是任何使用者都可以讀寫該檔案,而這個又是attr_i->attr.mode = S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH;這句代碼控制的
五、分析當我剛看到這個驅動程式的時候,我驚訝的發現壓根就沒有真正的open , write, read等這些函數,因為他們sun4i_gpio_write和sun4i_gpio_open就是做了下列印,read函數甚至連列印都沒有,壓根就不存在,我當時認真的把所有代碼讀完了才發現,這個gpio的驅動全部放棄了file_operations 這種方式,而是採用sysfs的方式來讀寫GPIO
至此,我們來歸納一下用sysfs的來處理gpio的好處1. GPIO的特性決定了採用sysfs非常非常適合,GPIO驅動程式最主要的工作就是把電平拉高拉低(有時候也有來發波形脈衝),拉高時,可以給一些裝置提供電壓,讓裝置工作,這類裝置中典型的有,蜂鳴器、LED燈、震動馬達(motor)等2. Android作業系統中採用sysfs也非常非常非常適合,有些應用APP通常需要控制硬體,比如控制LED的亮滅、蜂鳴器的響和不響,如果用系統調用的方式來open write read的話,那我們必須給應用程式層的JAVA程式提供JNI介面,可是這些介面又是非常簡單的,如果又要給他們編譯一個so庫,真是很麻煩。如果採用sysfs的方式,我們的應用程式只需要讀寫一個檔案即可操作GPIO了
static int sun4i_gpio_open(struct inode *inode, struct file *file) { pr_info("sun4i_gpio open\n"); return 0;}ssize_t sun4i_gpio_write(struct file *file, const char __user *buf, size_t size, loff_t *offset) { pr_info("sun4i_gpio write\n"); return 0;}static const struct file_operations sun4i_gpio_fops = { .open = sun4i_gpio_open, .write = sun4i_gpio_write, .release= sun4i_gpio_release};
本文是在介紹了Lichee之後,大家對Lichee有了一個基本認識後,尤其是對sys_config1.fex來配置驅動的方式的瞭解後,提到的一個非常簡單的驅動程式,本文也力圖把驅動程式講解的更加透徹,讓初學者更加地一目瞭然,可是在寫的過程中發現,如果想一個背景知識的介紹,往往會牽出另一個背景的介紹,所以想讀懂驅動程式,確實還是需要有一定的功底在這裡,sun4i-gpio驅動的痛點,主要是sys_config1.fex的結合,還有對sysfs的瞭解,對這2點都比較熟悉的人來說,這個驅動確實是非常簡單了,而且是比較好的驅動程式,並不像作者在KCONFIG裡面的介紹說的是"a ugly gpio driver" ^_^