Tiny4412——Android灯光系统

CSDN仅用于增加百度收录权重,排版未优化,日常不维护。请访问:www.hceng.cn 查看、评论。
本博文对应地址: https://hceng.cn/2019/05/09/Tiny4412——Android灯光系统/#more

继续学习Android驱动,Android灯光系统。

1.思路分析

前面对3.2 Android硬件访问服务进行了详细的分析,知道了Android大致可以分为四层:APPJNIHALDriver
回顾下前面写的点灯程序过程: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_offdelay_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-1wirewrite()值即可实现背光控制。

另外,从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 const
const GREEN_LED_FILE = “/sys/class/leds/led2/brightness”;
char constconst BLUE_LED_FILE = “/sys/class/leds/led3/brightness”;
char const
const RED_LED_FILE_TRIGGER = “/sys/class/leds/led1/trigger”;
char constconst GREEN_LED_FILE_TRIGGER = “/sys/class/leds/led2/trigger”;
char const
const BLUE_LED_FILE_TRIGGER = “/sys/class/leds/led3/trigger”;
char constconst RED_LED_FILE_DELAYON = “/sys/class/leds/led1/delay_on”;
char const
const GREEN_LED_FILE_DELAYON = “/sys/class/leds/led2/delay_on”;
char constconst BLUE_LED_FILE_DELAYON = “/sys/class/leds/led3/delay_on”;
char const
const RED_LED_FILE_DELAYOFF = “/sys/class/leds/led1/delay_off”;
char constconst GREEN_LED_FILE_DELAYOFF= “/sys/class/leds/led2/delay_off”;
char const
const 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)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值