Android 驱动 (一) GPIO

前面的博文对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><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:端口+组内序号<功能分配><内部电阻状态><驱动能力><输出电平状态>

对应的,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接口,可是这些接口又是非常简单的,如果又要给他们编译一个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" ^_^

Android 驱动 (一) GPIO

时间: 2024-08-11 07:39:40

Android 驱动 (一) GPIO的相关文章

第6章 Android驱动编程

第6章  Android驱动编程 通过介绍本章设备驱动.字符设备驱动编程.GPIO驱动程序实例和4*4扫描键盘驱动等内容,熟练掌握了Android驱动编程.Android内核内核模块编程中包括设备驱动和内核模块.模块相关命令.Android内核内核模块编程和内核模块实例程序.Android内核中采用可加载的模块化设计,一般情况下编译的Android内核是支持可插入式模块的,也就是将最基本的核心代码编译在内核中.模块相关命令中lsmod列出了当前系统中加载的模块,rmmood用于当前模块卸载,in

MT6582 Android 驱动设置问题

MT6582 Android 驱动设置问题 MT6577是MTK公司的双核处理器的3G手机芯片,下面是整理的MT6577 Android驱动设置: 1. LCD 1.1怎样新建一个LCD驱动 LCD模组主要包括LCD显示屏和驱动IC.比如LF040DNYB16a模组的驱动IC型号为NT35510.要在MT577平台上新建这个lcd的驱动,步骤如下: A.      新建文件夹nt35510: \mediatek\custom\common\kernel\lcm\nt35510 \mediatek

Android驱动开发5-8章读书笔记

Android驱动开发读书笔记                                                              第五章 S5PV210是一款32位处理器,具有低功耗的的特点,可为移动设备和一般应用提高性能的微处理器解决方案.它集成了ARM CORTEX-A8核心.实现了ARM架构V7且支持外围设备.他的关键功能是“以带有NEON的cpu子系统为基础的arm”,32/32kb i/d缓存,512kb l2缓存,操作频率800hz为1.1v,1ghz为1.2

初入android驱动开发之字符设备(四-中断)

上一篇讲到android驱动开发中,应用是怎样去操作底层硬件的整个流程,实现了按键控制led的亮灭.当然,这是一个非常easy的实例,只是略微演变一下,就能够得到广泛的应用. 如开发扫描头,应用透过监听上报的按键的键值,监听到,则调用扫描头的模块.成功,则点亮LED灯,并把扫描头解码后的条码信息.通过广播的形式发出.又扯到其他地方,这里主要说说中断. 1. 中断的一些概念 中断,是什么? 中断.能够看成是cpu对特殊事件的一种处理的机制,这类特殊事件一般指紧急事件或者说异常事件.非常easy的一

Android驱动移植与驱动开发概述

本书的第一章主要是对Android系统进行介绍,对Android系统移植前的准备工作进行了解,还有是对Linux内核及驱动进行讲解. Android是已经形成了非常完善的嵌入式操作系统.Android的系统架构分为4层:第一层Linux内核,主要包括驱动程序以及管理内存.进程.电源等资源的程序:第二层C/C++代码库,主要包括Linux的.so文件以及嵌入到APK程序中的NDK代码:第三层Android SDK API,直接面向应用程序的Java APK:第四层应用程序,直接面向最终用户的Jav

Android应用层操作GPIO

Android应用层操作GPIO的方法: http://my.oschina.net/u/1176566/blog/177554 在使用某个GPIO之前首先的保证此GPIO没有正在被使用,否则是无法操作的: 如NAND模块的NCE3/PC18,此io口正被使用,在导入echo 74 > /sys/class/gpio/export时没问题,但是设置方向是无权操作,如图: 下面是我写的DEMO,单独操作GPIO和控制LED操作的代码.之前想用C部分来操作,然后通过JNI把方法暴露给上层,但是貌似C

自己动手写最简单的Android驱动---LED驱动的编写【转】

本文转载自:http://blog.csdn.net/k_linux_man/article/details/7023824 转载注明出处,作者:K_Linux_Man, 薛凯 山东中医药大学,给文章内容引入个人毕业设计. 开发平台:farsight s5pc100-a 内核:linux2.6.29 环境搭配:有博文介绍 开发环境:Ubuntu .Eclipse 首先强调一下要点: 1.编写Android驱动时,首先先要完成Linux驱动,因为android驱动其实是在linux驱动基础之上完成

Android驱动开发之Hello实例

Android驱动开发之Hello实例: 驱动部分 modified:   kernel/arch/arm/configs/msm8909-1gb_w100_hd720p-perf_defconfig modified:   kernel/arch/arm/configs/msm8909-1gb_w100_hd720p_defconfig modified:   kernel/drivers/input/misc/Kconfig modified:   kernel/drivers/input/

【视频】嵌入式Linux/Android驱动开发揭秘(1)触摸屏驱动开发

嵌入式Linux/Android驱动开发揭秘(1)触摸屏驱动开发 专题简介:自1971年,美国人SamHurst发明了世界上第一个触摸传感器以来,触摸屏技术不断革新,给了程序设计师和UI工程师无限的想象空间,它极大改善了终端用户对各种设备的操作方便程度,现在我们的日常生活如手机.平板等,已经很大程度上依赖于和习惯于使用和操作触摸屏.做为工程师,我们很有必要掌握触摸屏的工作原理和软件驱动方法,如果您对一窥如何在嵌入式中操控和使用触摸屏这一司空见惯却又神奇的技术感兴趣,敬请关注! 1.LINUX驱动