Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android 驅動 (一) GPIO

Android 驅動 (一) GPIO

編輯:關於Android編程

前面的博文對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,這個結構體其實就用來描述一個gpio

struct 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 結構體,才是真正用來描述單個的gpio
typedef 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 = 1 gpio_num = 11 gpio_pin_1 = port:PI09<1><0> gpio_pin_2 = port:PB03<1><0> gpio_pin_3 = port:PH22<1><0> gpio_pin_4 = port:PH23<1><0> gpio_pin_5 = port:PH24<1><0> gpio_pin_6 = port:PH25<1><0> gpio_pin_7 = port:PH26<1><0> gpio_pin_8 = port:PC19<1><0> gpio_pin_9 = port:PC20<1><0> gpio_pin_10 = port:PC21<1><0> gpio_pin_11 = port:PC22<1><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 =09 mul_sel =1 pull =default drv_level =default data =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代表低電平

sysfs sys文件系統是一個處於內存中的虛擬文件系統,它為我們提供了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喎?/kf/ware/vc/" target="_blank" class="keylink">vc3VuNGktZ3Bpby9waW4vcGk5CgplY2hvIDAgPiAvc3lzL2RldmljZXMvdmlydHVhbC9taXNjL3N1bjRpLWdwaW8vcGluL3BpOQrWsb3TwLS/2NbGR1BJT8HLo6y2+LK70OjSqs2ouf2zzNDyCjxpbWcgc3JjPQ=="/uploadfile/2014/0607/20140607112943969.jpg" alt="\"> 這裡還有一點值得注意,所以的文件都是 -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" ^_^
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved