zoukankan      html  css  js  c++  java
  • Android 驱动 (一) GPIO

    前面的博文对Lichee做了系列分析,事实上就是对在《七年之痒》中所说的,Android BSP具备的一项基本素养-SHELL脚本,所以我们Lichee系列的文章着重分析了SHELL脚本和Lichee的基本结构,当然作为一名合格的Android BSPproject师来说,掌握Linux的驱动程序的移植,也是一项基本技能。所以从本文開始,将对sun4i的一些驱动程序做深入分析。当然了,驱动程序涉及的面非常广,比方摄像头的驱动涉及到sensor的移植和内核队列等数据结构相关内容,SD卡驱动又涉及到DMA的基本原理,触摸屏驱动又涉及到输入子系统。中断上下文等相关知识,WIFI驱动会涉及到无线局域网的协议部分以及wap_supplicant等相关知识,2G/3G模块往往会涉及到RIL库及AT命令等等,除此之外,为了方便管理,大部分外部设备都挂在IIC USB UART等各种总线上(或是虚拟总线),每块开发板都有自己的原理图和走线方式,这些硬件相关内容又是BSPproject师们在调试驱动时绕不开的。

    思来想去,因为涉及面过于繁杂。加之本人能力又有限。还是认为站在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能非常easy地通过软件来配置,这里除了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
    ");
    /*
        用来获取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
    ", __FUNCTION__);
      goto exit;
     }
     if(!sun4i_gpio_used) {
      pr_err("%s sun4i_gpio is not used in config
    ", __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
    ", __FUNCTION__);
      goto exit;
     }
     sun4i_gpio_dbg("sun4i_gpio_num:%d
    ", sun4i_gpio_num);
     if(!sun4i_gpio_num) {
      pr_err("%s sun4i_gpio_num is none
    ", __FUNCTION__);
      err = -1;
      goto exit;
     }
    
    /*
        注冊一个杂项设备,主设备号是10,此设备号由系统来定义
    */
     err = misc_register(&sun4i_gpio_dev);
     if(err) {
      pr_err("%s register sun4i_gpio as misc device error
    ", __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
    ", __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
    ", 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
    ", __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 ", 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 ", 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 "); 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><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   =09
    mul_sel       =1
    pull             =default
    drv_level      =default
    data            =0
    在《 Lichee (五) sysconfig1.fex 配置系统》一文中,我们以前简单分析过sysconfig1.fex和描写叙述GPIO的形式

    描写叙述gpio的形式:Port: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/sun4i-gpio/pin/pi9
    echo 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接口,但是这些接口又是很easy的。假设又要给他们编译一个so库,真是很麻烦。

    假设採用sysfs的方式,我们的应用程序仅仅须要读写一个文件就可以操作GPIO了


    static int sun4i_gpio_open(struct inode *inode, struct file *file) {
     pr_info("sun4i_gpio open
    ");
     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
    ");
     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来配置驱动的方式的了解后,提到的一个很easy的驱动程序,本文也力图把驱动程序解说的更加透彻,让刚開始学习的人更加地一目了然,但是在写的过程中发现,假设想一个背景知识的介绍,往往会牵出还有一个背景的介绍,所以想读懂驱动程序,确实还是须要有一定的功底在这里,sun4i-gpio驱动的难点,主要是sys_config1.fex的结合。还有对sysfs的了解,对这2点都比較熟悉的人来说,这个驱动确实是很easy了,并且是比較好的驱动程序。并不像作者在KCONFIG里面的介绍说的是"a ugly gpio driver" ^_^

  • 相关阅读:
    根据屏幕大小,加载不同大小的图片
    规范命名CSS
    iframe框架加载完成后执行函数
    js获取IP地址
    80端口被System占用 造成Apache不能启动的解方案
    自定义一个仿Spinner
    让你的代码量减少3倍!使用kotlin开发Android(一)
    屏幕适配
    一个旋转菜单
    手势监听GestureDetector 案例
  • 原文地址:https://www.cnblogs.com/tlnshuju/p/7028985.html
Copyright © 2011-2022 走看看