CSDN仅用于增加百度收录权重,排版未优化,日常不维护。请访问:www.hceng.cn 查看、评论。
本博文对应地址: https://hceng.cn/2019/05/09/Tiny4412——Android灯光系统/#more
继续学习Android驱动,Android灯光系统。
1.思路分析
前面对3.2 Android硬件访问服务进行了详细的分析,知道了Android大致可以分为四层:APP
、JNI
、HAL
、Driver
。
回顾下前面写的点灯程序过程:APP
调用JNI
提供的本地接口方法;JNI
除了向上提供接口,还要调用HAL
提供的硬件操作函数;HAL
操作Linux用户态的LED设备节点;Driver
操作硬件,提供设备节点。
在编写JNI
时,我们自己创建的com_android_server_LedService.cpp
,这样就会导致提供的接口,只有我们才知道,在APP
不通用。因此,更好的做法是使用Android提供的灯光系统,即使用自带的com_android_server_lights_LightsService.cpp
,这样LED的接口就可以通用,APP
也就可以实现通用。相应的,我们要做的就是编写HAL
层代码,提供JNI
所需要的函数,以及编写Driver
提供给HAL
。
首先分析JNI
层的frameworks/base/services/core/jni/com_android_server_lights_LightsService.cpp
。
找到JNINativeMethod
,里面建立Java本地方法与C函数名的映射关系,可以看到有三个函数:
{% codeblock lang:cpp %}
static JNINativeMethod method_table[] = {
{ “init_native”, “()J”, (void*)init_native },
{ “finalize_native”, “(J)V”, (void*)finalize_native },
{ “setLight_native”, “(JIIIIII)V”, (void*)setLight_native },
};
{% endcodeblock %}
先看init_native()
:
a.使用hw_get_module()
,获得hw_module_t
结构体;
b.根据传入的不同name
,使用module->methods->open()
,获得不同的light_device_t
结构体;
{% codeblock lang:cpp %}
static light_device_t* get_device(hw_module_t* module, char const* name)
{
int err;
hw_device_t* device;
err = module->methods->open(module, name, &device);
if (err == 0) {
return (light_device_t*)device;
} else {
return NULL;
}
}
static jlong init_native(JNIEnv env, jobject clazz)
{
int err;
hw_module_t module;
Devices* devices;
devices = (Devices*)malloc(sizeof(Devices));
err = hw_get_module(LIGHTS_HARDWARE_MODULE_ID, (hw_module_t const**)&module);
if (err == 0) {
devices->lights[LIGHT_INDEX_BACKLIGHT]
= get_device(module, LIGHT_ID_BACKLIGHT);
devices->lights[LIGHT_INDEX_KEYBOARD]
= get_device(module, LIGHT_ID_KEYBOARD);
devices->lights[LIGHT_INDEX_BUTTONS]
= get_device(module, LIGHT_ID_BUTTONS);
devices->lights[LIGHT_INDEX_BATTERY]
= get_device(module, LIGHT_ID_BATTERY);
devices->lights[LIGHT_INDEX_NOTIFICATIONS]
= get_device(module, LIGHT_ID_NOTIFICATIONS);
devices->lights[LIGHT_INDEX_ATTENTION]
= get_device(module, LIGHT_ID_ATTENTION);
devices->lights[LIGHT_INDEX_BLUETOOTH]
= get_device(module, LIGHT_ID_BLUETOOTH);
devices->lights[LIGHT_INDEX_WIFI]
= get_device(module, LIGHT_ID_WIFI);
} else {
memset(devices, 0, sizeof(Devices));
}
return (jlong)devices;
}
{% endcodeblock %}
再来看setLight_native()
:
主要就是根据APP
传入的参数,调用前面light_device_t
结构体里的set_light()
。
{% codeblock lang:cpp %}
static void setLight_native(JNIEnv env, jobject clazz, jlong ptr,
jint light, jint colorARGB, jint flashMode, jint onMS, jint offMS, jint brightnessMode)
{
Devices devices = (Devices*)ptr;
light_state_t state;
if (light < 0 || light >= LIGHT_COUNT || devices->lights[light] == NULL) {
return ;
}
memset(&state, 0, sizeof(light_state_t));
state.color = colorARGB;
state.flashMode = flashMode;
state.flashOnMS = onMS;
state.flashOffMS = offMS;
state.brightnessMode = brightnessMode;
{
ALOGD_IF_SLOW(50, "Excessive delay setting light");
devices->lights[light]->set_light(devices->lights[light], &state);
}
}
{% endcodeblock %}
最后finalize_native()
做一些清理操作。
理清楚了JNI
层的操作,对应的HAL
层需要提供什么,也基本清晰了:
a.实现hw_module_t
结构体;
b.实现open()
,根据传入的name
,返回不同的light_device_t
结构体;
c.针对功能需求,实现对应light_device_t
结构体里的set_light()
:set_light_battery()
、set_light_notifications()
、set_light_backlight()
;
根据HAL
层的需求,Driver
层要做的就是:
a.对于set_light_battery()
、set_light_notifications()
,实现控制R、G、B三个LED的亮、灭、闪烁;
b.对于set_light_backlight()
,设置PWM
实现亮度控制;
2.编写驱动
驱动需要两个,一个是用于电源灯/通知灯的,一个是用于LCD背光的。
用于电源灯/通知灯的,需要实现控制LED亮灭、LED灯定时闪烁;用于LCD背光的需要PWM
操作。
2.1 LED驱动
先说用于电源灯/通知灯的驱动,之前在Android访问硬件的方法:3.1.1编写LED驱动写过一次,但当时的驱动只支持LED的亮灭,不支持定时闪烁。我们完全可以添加个ioctl()
操作,利用定时器实现闪烁功能,但没有必要,可以直接利用Linux的LED子系统。关于LED子系统,在以前写AM437x——LED驱动已经详细介绍过了,这里直接采用LED子系统的方式编写LED驱动程序。
-
编写步骤:
a.分配led_classde
结构体;
b.设置led_classdev
结构体:
max_brightness = LED_FULL;
brightness_set = tiny4412_brightness_set;
flags = LED_CORE_SUSPENDRESUME;
brightness = LED_OFF;
name = led_gpios[i].name;
default_trigger = "none";
c.硬件相关初始化(设置GPIO,关闭LED等);
d.使用led_classdev_register()
注册led_classde
;
e.实现tiny4412_brightness_set()
(GPIO不支持亮度设置,这里仅支持亮灭); -
参考代码:
{% codeblock lang:c [tiny4412_leds.c] https://github.com/hceng/learn/blob/master/android/02_灯光系统/driver/leds/tiny4412_leds.c %}
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/leds.h>
#include <plat/gpio-cfg.h>
struct led_desc {
int gpio;
char *name;
};
static struct led_desc led_gpios[] = {
{EXYNOS4212_GPM4(0), “led1”},
{EXYNOS4212_GPM4(1), “led2”},
{EXYNOS4212_GPM4(2), “led3”},
{EXYNOS4212_GPM4(3), “led4”},
};
static int pin_num = sizeof(led_gpios)/sizeof(led_gpios[0]);
struct tiny4412_led_data {
struct led_classdev led_dev;
int gpio;
struct mutex lock;
};
static struct tiny4412_led_data *led_devs;
void tiny4412_brightness_set(struct led_classdev *led_cdev,
enum led_brightness brightness)
{
struct tiny4412_led_data *dev = (struct tiny4412_led_data *)led_cdev;
printk(KERN_DEBUG "Passed %s %d \n",__FUNCTION__,__LINE__);
led_cdev->brightness = brightness;
if (brightness != LED_OFF)
gpio_set_value(dev->gpio, 0);
else
gpio_set_value(dev->gpio, 1);
}
static int leds_drv_init(void)
{
int i, ret;
printk(KERN_DEBUG "Passed %s %d \n",__FUNCTION__,__LINE__);
//1. alloc led_classdev
led_devs = kzalloc(sizeof(struct tiny4412_led_data) * pin_num, GFP_KERNEL);
if (led_devs == NULL) {
printk(KERN_ERR "No memory for device\n");
return -ENOMEM;
}
for (i = 0; i < pin_num; i++)
{
mutex_init(&led_devs[i].lock);
mutex_lock(&led_devs[i].lock);
//2. set led_classdev
led_devs[i].led_dev.max_brightness = LED_FULL; //255
led_devs[i].led_dev.brightness_set = tiny4412_brightness_set;
led_devs[i].led_dev.flags = LED_CORE_SUSPENDRESUME;
led_devs[i].led_dev.brightness = LED_OFF;
led_devs[i].led_dev.name = led_gpios[i].name;
led_devs[i].led_dev.default_trigger = "none";
led_devs[i].gpio = led_gpios[i].gpio;
//3. Hardware setup(Default led off)
s3c_gpio_cfgpin(led_gpios[i].gpio, S3C_GPIO_OUTPUT);
if (led_devs[i].led_dev.brightness == LED_OFF)
gpio_set_value(led_gpios[i].gpio, 1);
else
gpio_set_value(led_gpios[i].gpio, 0);
//4. led_classdev_register
ret = led_classdev_register(NULL, &led_devs[i].led_dev);
if (ret) {
i--;
while (i >= 0) {
led_classdev_unregister(&led_devs[i].led_dev);
i--;
}
mutex_unlock(&led_devs[i].lock);
kfree(led_devs);
return -EIO;
}
mutex_unlock(&led_devs[i].lock);
}
return 0;
}
static void leds_drv_exit(void)
{
int i;
printk(KERN_DEBUG "Passed %s %d \n",__FUNCTION__,__LINE__);
for (i = 0; i < pin_num; i++)
{
mutex_lock(&led_devs[i].lock);
led_classdev_unregister(&led_devs[i].led_dev);
mutex_unlock(&led_devs[i].lock);
}
kfree(led_devs);
}
module_init(leds_drv_init);
module_exit(leds_drv_exit);
MODULE_LICENSE(“GPL”);
MODULE_AUTHOR(“hceng huangcheng.job@foxmail.com”);
MODULE_DESCRIPTION(“Tiny4412 leds driver.”);
MODULE_VERSION(“v1.0”);
{% endcodeblock %}
- 编译进内核:
首先让内核支持LED子系统,且支持Timer
触发,在内核根目录执行make menuconfig
,勾选上下面选项:
[*] LED Support --->
--- LED Support
[*] LED Class Support
……
[*] LED Trigger support
*** LED Triggers ***
<*> LED Timer Trigger
接着将tiny4412_leds.c
放在drivers/leds/
里,并在该路径下的Makefile
添加:
obj-y += tiny4412_leds.o
最后执行make zImage
重新编译内核,并重新烧写内核。
- 应用层测试:
进入系统,可以看到已经生成了LED
的节点:
shell@tiny4412:/ # ls /sys/class/leds/ -l
lrwxrwxrwx root root 2019-05-10 09:58 led1 -> ../../devices/virtual/leds/led1
lrwxrwxrwx root root 2019-05-10 09:58 led2 -> ../../devices/virtual/leds/led2
lrwxrwxrwx root root 2019-05-10 09:58 led3 -> ../../devices/virtual/leds/led3
lrwxrwxrwx root root 2019-05-10 09:58 led4 -> ../../devices/virtual/leds/led4
lrwxrwxrwx root root 2019-05-10 10:05 mmc1:: -> ../../devices/platform/s3c-sdhci.2/leds/mmc1::
lrwxrwxrwx root root 2019-05-10 10:05 mmc2:: -> ../../devices/platform/s3c-sdhci.3/leds/mmc2::
执行su
切换到root
用户,再执如下命令即可控制对应LED亮灭:
shell@tiny4412:/ # echo 255 > /sys/class/leds/led1/brightness
shell@tiny4412:/ # echo 0 > /sys/class/leds/led1/brightness
执行以下命令,修改触发模式为timer
,即可实现LED闪烁:
shell@tiny4412:/ # echo timer > /sys/class/leds/led1/trigger
此时/sys/class/leds/led1/
会生成delay_off
和delay_on
,可修改这两个值,控制闪烁亮灭时间。
2.2 backligth驱动
在之前的博客Exynos4412——LCD之backligth里,已经对Tiny4412的背光“一线触摸”有了研究,它采用的并不是PWM控制背光调试,而是背光部分交给了一个屏幕上的MCU处理,Tiny4412与MCU通过一个GPIO进行数据传输。这个传输协议,友善之臂并没有公开,且只适用它们家的屏幕,也没什么研究价值,因此我们只需知道如何使用该驱动即可。
在友善提供的Android源码android-5.0.2/vendor/friendly-arm/tiny4412/proprietary/
路径下可以看到如下文件:
fa_codec_ctrl lights.tiny4412.so sensors.tiny4412.so
其中就有控制亮度lights.tiny4412.so
,这就是友善提供的HAL
,不开源的部分。
我们可以尝试以下思路进行分析:
a.lights.tiny4412.so
肯定会操作某个Linux设备节点,从而控制亮度,因此使用下面的命令,找到是操作的哪个节点:
{% codeblock lang:shell %}
hceng@android:/work$ hexdump -C lights.tiny4412.so | grep “dev” -A2
00000a50 3a 25 64 0a 00 2f 64 65 76 2f 62 61 63 6b 6c 69 |:%d…/dev/backli|
00000a60 67 68 74 2d 31 77 69 72 65 00 25 64 0a 00 6c 69 |ght-1wire.%d…li|
00000a70 67 68 74 73 20 4d 6f 64 75 6c 65 00 47 6f 6f 67 |ghts Module.Goog
{% endcodeblock %}
可以看出是操作的/dev/backlight-1wire
节点。
b.接着去Tiny4412上验证下是否有该节点:
shell@tiny4412:/ # ls /dev/backlight-1wire
/dev/backlight-1wire
确实有该节点。
c.接着去内核的驱动目录,搜索节点名字:
{% codeblock lang:shell %}
hceng@android:/work/linux_source/linux-3.0.86/drivers$ grep “backlight-1wire” -nr
Binary file built-in.o matches
Binary file input/built-in.o matches
input/touchscreen/tiny4412_1wire_host.c:84:#define BACKLIGHT_DEVICE_NAME “backlight-1wire”
Binary file input/touchscreen/built-in.o matches
Binary file input/touchscreen/tiny4412_1wire_host.o matches
{% endcodeblock %}
可以看到驱动的源码在drivers/input/touchscreen/tiny4412_1wire_host.c
。
d.最后打开该驱动源码,看是否能直接使用,运气好的是,tiny4412_1wire_host.c
里面有个bl_write()
操作函数,传入参数就可以控制亮度。即分析得到:向/dev/backlight-1wire
,write()
值即可实现背光控制。
另外,从if (v > 127) v = 127;
可知,传入的值范围为0~127。
后面我们写HAL
层代码时,就可以直接写操作/dev/backlight-1wire
控制亮度,该部分驱动无需再写。
3.编写HAL
编写完驱动后,得到了/sys/class/leds/led*
和/dev/backlight-1wire
节点,接下来就是编写HAL
代码操作这两个节点,同时向JNI
提供接口。
3.1编写步骤
编写步骤如下:
a.创建一个名为HMI
(HAL_MODULE_INFO_SYM
)的hw_module_t
结构体,该结构体有一个hw_module_methods_t
结构体成员;
b.hw_module_methods_t
结构体里创建一个open()
函数成员;
c.实现open()
函数,分配一个light_device_t
结构体,根据传入的名字不同,设置不同的操作函数作为light_device_t
的成员,最后返回该结构体;
d.操作设备节点,实现需要提供的不同操作函数;
3.2参考代码及分析
{% codeblock lang:c [lights.c] https://github.com/hceng/learn/blob/master/android/02_灯光系统/hal/lights.c %}
#define LOG_NDEBUG 0
#define LOG_TAG “lights”
#include <cutils/log.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <hardware/lights.h>
char constconst RED_LED_FILE = “/sys/class/leds/led1/brightness”;
char constconst GREEN_LED_FILE = “/sys/class/leds/led2/brightness”;
char constconst BLUE_LED_FILE = “/sys/class/leds/led3/brightness”;
char constconst RED_LED_FILE_TRIGGER = “/sys/class/leds/led1/trigger”;
char constconst GREEN_LED_FILE_TRIGGER = “/sys/class/leds/led2/trigger”;
char constconst BLUE_LED_FILE_TRIGGER = “/sys/class/leds/led3/trigger”;
char constconst RED_LED_FILE_DELAYON = “/sys/class/leds/led1/delay_on”;
char constconst GREEN_LED_FILE_DELAYON = “/sys/class/leds/led2/delay_on”;
char constconst BLUE_LED_FILE_DELAYON = “/sys/class/leds/led3/delay_on”;
char constconst RED_LED_FILE_DELAYOFF = “/sys/class/leds/led1/delay_off”;
char constconst GREEN_LED_FILE_DELAYOFF= “/sys/class/leds/led2/delay_off”;
char constconst BLUE_LED_FILE_DELAYOFF = “/sys/class/leds/led3/delay_off”;
char const*const LCD_BACKLIGHT_FILE = “/dev/backlight-1wire”;
/* Synchronization primities */
static pthread_once_t g_init = PTHREAD_ONCE_INIT;
static pthread_mutex_t g_lock = PTHREAD_MUTEX_INITIALIZER;
/* LED state machine */
static struct light_state_t g_notification;
static struct light_state_t g_battery;
/* Write node function */
static int write_int (const char *path, int value)
{
int fd;
static int already_warned = 0;
fd = open(path, O_RDWR);
if (fd < 0) {
if (already_warned == 0) {
ALOGE(“write_int failed to open %s\n”, path);
already_warned = 1;
}
return -errno;
}
char buffer[20];
int bytes = snprintf(buffer, sizeof(buffer), “%d\n”, value);
int written = write (fd, buffer, bytes);
close(fd);
return written == -1 ? -errno : 0;
}
static int write_string (const char *path, const char *value)
{
int fd;
static int already_warned = 0;
fd = open(path, O_RDWR);
if (fd < 0) {
if (already_warned == 0) {
ALOGE(“write_string failed to open %s\n”, path);
already_warned = 1;
}
return -errno;
}
char buffer[20];
int bytes = snprintf(buffer, sizeof(buffer), “%s\n”, value);
int written = write (fd, buffer, bytes);
close(fd);
return written == -1 ? -errno : 0;
}
/* Color tools /
static int is_lit (struct light_state_t const state)
{
return state->color & 0x00ffffff;
}
static int rgb_to_brightness (struct light_state_t const* state)
{
int color = state->color & 0x00ffffff;
return ((77*((color>>16)&0x00ff))
+ (150*((color>>8)&0x00ff)) + (29*(color&0x00ff))) >> 8;
}
/* The actual lights controlling section */
static int set_light_backlight (struct light_device_t *dev, struct light_state_t const *state)
{
int brightness = rgb_to_brightness(state);
ALOGV("%s brightness=%d color=0x%08x", func,brightness,state->color);
pthread_mutex_lock(&g_lock);
//brightness range: 0-255, but LCD_BACKLIGHT_FILE range:0-127
write_int (LCD_BACKLIGHT_FILE, brightness/2);
pthread_mutex_unlock(&g_lock);
return 0;
}
static void set_shared_light_locked (struct light_device_t *dev, struct light_state_t *state)
{
int r, g, b;
int delayOn,delayOff;
r = (state->color >> 16) & 0xFF;
g = (state->color >> 8) & 0xFF;
b = (state->color) & 0xFF;
delayOn = state->flashOnMS;
delayOff = state->flashOffMS;
if (state->flashMode != LIGHT_FLASH_NONE) {
write_string (RED_LED_FILE_TRIGGER, “timer”);
write_string (GREEN_LED_FILE_TRIGGER, “timer”);
write_string (BLUE_LED_FILE_TRIGGER, “timer”);
write_int (RED_LED_FILE_DELAYON, delayOn);
write_int (GREEN_LED_FILE_DELAYON, delayOn);
write_int (BLUE_LED_FILE_DELAYON, delayOn);
write_int (RED_LED_FILE_DELAYOFF, delayOff);
write_int (GREEN_LED_FILE_DELAYOFF, delayOff);
write_int (BLUE_LED_FILE_DELAYOFF, delayOff);
} else {
write_string (RED_LED_FILE_TRIGGER, “none”);
write_string (GREEN_LED_FILE_TRIGGER, “none”);
write_string (BLUE_LED_FILE_TRIGGER, “none”);
}
write_int (RED_LED_FILE, r);
write_int (GREEN_LED_FILE, g);
write_int (BLUE_LED_FILE, b);
}
static void handle_shared_battery_locked (struct light_device_t *dev)
{
if (is_lit (&g_notification))
set_shared_light_locked (dev, &g_notification);
else
set_shared_light_locked (dev, &g_battery);
}
static int set_light_battery (struct light_device_t dev, struct light_state_t const state)