一、Android源自Linux
Android驱动实际就是linux驱动和封装,内核通过驱动与底层硬件“交互”并为framework层提供统一接口。linux中诸如进程管理、内存管理、中断管理、虚拟文件系统(vfs)、网络管理等内容的都是差别不大的。但在驱动构成上存在下面的差异。
驱动构成的差异:
Linux:内核 + 文件系统 + rootfs
Android:内核 + 文件系统 + rootfs + android文件系统挂载到system
怎么理解这个“android文件系统挂载到system”?需要了解Android系统源代码目录结构:
abi *:应用程序二进制接口
bionic *:基础的库的源代码
bootloader/legacy *:启动引导相关代码
build *:存放编译系统的.mk文件
development *:程序开发需要的模板和工具
device *:设备相关代码
framworks *:核心框架——java和C++语言,是Android应用程序的框架
hardware *:主要是硬件适配层HAL代码
out *:编译完成全部img文件
packages *:Android的各种系统级应用程序
system *:Android根文件系统相关源码
而基本上一般的Linux文件系统没有system这个目录。
二、一个LED驱动
指示灯在Android设备上主要作用就是显示手机状态,高通平台通常情况下默认的RGB接口连接指示灯,个别用户会选择外接的LED芯片控制灯效。Android的LED驱动和Linux下的LED驱动没有太大区别。Android的LED驱动基本位于Kernel目录下。详细的led驱动可以参考drivers/leds/目录下的代码。
- 通常在dts中搜索指示灯关键字
red
可以找到平台默认相关配置。- 对应的_deconfig文件中打开相应的配置宏 CONFIG_LEDS_QTI_TRI_LED –高通平台默认参考驱动模式不同选择的宏不同,根据平台以及硬件连接项目需要配置,无特殊情况选择默认配置即可。
- 确认对应的makefile文件Drivers/leds/makefile中有CONFIG_LEDS_QTI_TRI_LED宏配置
- 检查驱动代码 根据配置的宏查到对应的源文件,查看驱动代码Driver/leds/leds-qti-tri-led.c。
- 检查dts配置 :arch/arm64/boot/dts/qcom/***.dtsi。dts相关配置参数说明可以查看: Documentation/devicetree/bingdings/leds/leds-qti-tri-led.txt.
2.0、LED的硬件原理
LED的原理图非常简单,一般采用平台默认的GPIO引脚,通过高低电平控制LED亮灭,同时可以调节占空比来调节LED电流。
2.1、如何编写一个驱动?
按照内核为某个设备提供的框架去编写,驱动编写分为四步:为结构体分配空间、设置结构体、硬件的相关操作、注册。为了给应用程序提供统一的接口,驱动采用分层的思想,其中核心层是内核为设备提供的框架,除此以外还有设备驱动层。
参考代码:/drivers/leds/led-class.c
实际操作依旧按照:为结构体分配空间、设置结构体、硬件的相关操作、注册来执行。
2.2、驱动源码
# Makefile
KERNELDIR :=/home/linux/fspad_733/lichee/linux-3.4
test:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- -C $(KERNELDIR) M=$(shell pwd) modules
clean:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- -C $(KERNELDIR) M=$(shell pwd) clean
obj-m += fspad_leds.o
/*own led*/
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/leds.h>
#include <asm/io.h>
#define FSPAD733_GPFCON 0x01C208B4
#define FSPAD733_GPFDAT 0x01C208C4
unsigned int *gpfcon;
unsigned int *gpfdat;
struct led_classdev *led_dev; //led_dev结构体声明
//step3:实现结构体中定义的操作(设置LED初始值)
void fspad_brightness_set(struct led_classdev *led_cdev, enum led_brightness brightness){
if(brightness == LED_OFF) {
writel(readl(gpfdat)|(0x1 << 2),gpfdat); //led off
}else{
writel(readl(gpfdat)&~(0x1 << 2),gpfdat); //led on
}
}
//step2:驱动初始化函数
int fspad_leds_init(void){
int ret;
//1、为结构体分配空间,kzalloc分配空间,初始化结构体成员为0,必须要手动释放空间
if(NULL == (led_dev = kzalloc(sizeof(struct led_classdev),GFP_KERNEL)))
return -ENOMEM;
//2、设置结构体
led_dev->name = "led0";
led_dev->flags = LED_CORE_SUSPENDRESUME;
led_dev->brightness_set = fspad_brightness_set;
//3、硬件相关的操作
gpfcon = ioremap(FSPAD733_GPFCON,0x4); //将物理地址映射到一个虚拟地址上
gpfdat = ioremap(FSPAD733_GPFDAT,0x4);
writel((readl(gpfcon)&~(0xf << 8))|(0x1 << 8),gpfcon);
//led init:gpfcon的8~11位先清零,再在第8位写1
writel(readl(gpfdat)&~(0x1 << 2),gpfdat);
//led on
//4、注册
ret = led_classdev_register(NULL, led_dev);
if(ret < 0)
return ret;
return 0;
}
//step4:驱动注销
void fspad_leds_exit(void)
{
led_classdev_unregister(led_dev);
iounmap(gpfcon);
iounmap(gpfdat);
kfree(led_dev);
}
//step1:初始化模块三要素
module_init(fspad_leds_init);
module_exit(fspad_leds_exit);
MODULE_LICENSE("GPL");
2.3、驱动编译和执行
在编译uboot和内核的过程中,采用的是外部传参的方式进行编译,Makefile的写法见上文。另外需要执行:
#sudo vi /etc/bash.bashrc
export PATH=$PATH:/home/linux/lichee/out/sun8iw5p1/android/common/buildroot/external-toolchain/bin
指定输出目录并使其立即生效:
# source /etc/bash.bashrc
insmod own_leds.ko将led驱动加载进内核,会在/sys/class/leds/led0/下生成led节点,进而可对led进行控制。
2.4、LED上层简介
1.framwork 层: LightsService.java 提供java调用jni层方法。一般作为service启动。
2、jni 层: com_android_server_LightsService.cpp
3 hardware层: Lights.c 通过节点控制led状态。
2.5、调试方法
1.通过节点来调整led状态,配合万用表测量对应pin脚输出状态。
2 常用节点,设备注册成功后 会在/sys/class/leds/目录下生成对应的red blue green对应的目录。
# cd sys/class/leds/red/
# ls
blink brightness device fusion led_time max_brightness power subsystem trigger uevent
# echo 255 > brightness --打开点亮led
# echo 0 > brightness --关闭led