作者:王劲南,华清远见嵌入式培训中心讲师。
Android Telechips89xx背光控制流程
Rocky@20110322
这里我们以Telechips89xx背光控制来示例讲解一下Android2.1下的背光控制。
这个分成几个部分:
*********************************************
应用层调用
Framework层
Hardware层
Kernel层
总结
*********************************************
*********************************************
应用层调用
*********************************************
API中设置背光的方法
首先我们从/android_tcgit/packages/apps/Settings/src/com/android/settings/BrightnessPreference.java中获知:
///////////////////////////////////////
import android.os.RemoteException;
import android.os.IPowerManager;
import android.os.ServiceManager;
private void setBrightness(int brightness) {
try {
IPowerManager power = IPowerManager.Stub.asInterface(
ServiceManager.getService("power"));
if (power != null) {
power.setBacklightBrightness(brightness);
}
} catch (RemoteException doe) {
}
}
///////////////////////////////////////
这样我们也就可以使用同样的方法来设置当前的背光亮度了。
*********************************************
Framework层
*********************************************
从上面可知,它是通过power service的setBacklightBrightness()来设置背光亮度的。
在android_tcgit/frameworks/base/services/java/com/android/server/PowerManagerService.java中有以下函数:
/////////////////////////////////////////////////////
public void setBacklightBrightness(int brightness)
{
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null);
// Don't let applications turn the screen all the way off
brightness = Math.max(brightness, Power.BRIGHTNESS_DIM);
mHardware.setLightBrightness_UNCHECKED(HardwareService.LIGHT_ID_BACKLIGHT, brightness,
HardwareService.BRIGHTNESS_MODE_USER);
mHardware.setLightBrightness_UNCHECKED(HardwareService.LIGHT_ID_KEYBOARD,
(mKeyboardVisible ? brightness : 0), HardwareService.BRIGHTNESS_MODE_USER);
mHardware.setLightBrightness_UNCHECKED(HardwareService.LIGHT_ID_BUTTONS, brightness,
HardwareService.BRIGHTNESS_MODE_USER);
long identity = Binder.clearCallingIdentity();
try {
mBatteryStats.noteScreenBrightness(brightness);
} catch (RemoteException e) {
Log.w(TAG, "RemoteException calling noteScreenBrightness on BatteryStatsService", e);
} finally {
Binder.restoreCallingIdentity(identity);
}
// update our animation state
if (ANIMATE_SCREEN_LIGHTS) {
mScreenBrightness.curValue = brightness;
mScreenBrightness.animating = false;
mScreenBrightness.targetValue = -1;
}
if (ANIMATE_KEYBOARD_LIGHTS) {
mKeyboardBrightness.curValue = brightness;
mKeyboardBrightness.animating = false;
mKeyboardBrightness.targetValue = -1;
}
if (ANIMATE_BUTTON_LIGHTS) {
mButtonBrightness.curValue = brightness;
mButtonBrightness.animating = false;
mButtonBrightness.targetValue = -1;
}
}
/////////////////////////////////////////////////////
这样我们可以知道,这个背光亮度的设置不仅仅是设置LCD_Backlight,还有可鞥设置到键盘,按键等等的。
当然,这里我们不需要管他们。
这里面的设置还是从通过函数
mHardware.setLightBrightness_UNCHECKED(HardwareService.LIGHT_ID_BACKLIGHT, brightness,HardwareService.BRIGHTNESS_MODE_USER);
来设置的。
注意setLightBrightness_UNCHECKED。它的定义的地方是在android_tcgit/frameworks/base/services/java/com/android/server/HardwareService.java
具体实现如下:
/////////////////////////////////////////////////////////////////
void setLightBrightness_UNCHECKED(int light, int brightness, int brightnessMode) {
int b = brightness & 0x000000ff;
b = 0xff000000 | (b << 16) | (b << 8) | b;
setLight_native(mNativePointer, light, b, LIGHT_FLASH_NONE, 0, 0, brightnessMode);
}
//////////////////////////////////////////////////////////////////
这里我们也可以清楚的明白setLightBrightness_UNCHECKED这个函数的三个参数。
第一个参数是light的ID,
第二个参数就是我们设置的亮度值brightness
第三个参数是背光的模式,因为是手动设置这里是BRIGHTNESS_MODE_USER.
brightness结果转换,最终还是调用setLight_native这个函数来实现。注意这个函数是native的,是通过JNI来实现的。他的cpp代码在
android_tcgit/frameworks/base/services/jni/com_android_server_HardwareService.cpp
中声明如下:
//////////////////////////////////////////////////////
static void setLight_native(JNIEnv *env, jobject clazz, int ptr,
int light, int colorARGB, int flashMode, int onMS, int offMS, int 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;
devices->lights[light]->set_light(devices->lights[light], &state);
}
////////////////////////////////////////////////////////
ptr是通过init_native获得的。
init_native又是通过hw_get_module来获得到的。
总之
这里我们发现devices->lights[light]是一个我们已经打开对象,然后通过该对象的set_light()来实现背光的设置。
init_native是这个文件在系统装载的时候,根据系统的配置来装载。具体的实现是在Hardware层来实现的。
同样它的set_light()也是在这Hardware来实现的。接下来我们就需要了解Hardware层了。
*********************************************
Hardware层
*********************************************
首先上面遗留了一个问题,就是Devices * devices是如何获取得到的。使用上面的定义,我们可以知道在Hardware.h中定义了hw_get_module函数。
具体实现在android_tcgit/hardware/libhardware/hardware.c中。
///////////////////////////////////
static const char *variant_keys[] = {
"ro.hardware", /* This goes first so that it can pick up a different file on the emulator. */
"ro.product.board",
"ro.board.platform",
"ro.arch"
};
int hw_get_module(const char *id, const struct hw_module_t **module)
{
int status;
int i;
const struct hw_module_t *hmi = NULL;
char prop[PATH_MAX];
char path[PATH_MAX];
/*
* Here we rely on the fact that calling dlopen multiple times on
* the same .so will simply increment a refcount (and not load
* a new copy of the library).
* We also assume that dlopen() is thread-safe.
*/
/* Loop through the configuration variants looking for a module */
for (i=0 ; i<HAL_VARIANT_KEYS_COUNT+1 ; i++) {
if (i < HAL_VARIANT_KEYS_COUNT) {
if (property_get(variant_keys[i], prop, NULL) == 0) {
continue;
}
snprintf(path, sizeof(path), "%s/%s.%s.so",HAL_LIBRARY_PATH, id, prop);
} else {
snprintf(path, sizeof(path), "%s/%s.default.so",HAL_LIBRARY_PATH, id);
}
if (access(path, R_OK)) {
continue;
}
/* we found a library matching this id/variant */
break;
}
status = -ENOENT;
if (i < HAL_VARIANT_KEYS_COUNT+1) {
/* load the module, if this fails, we're doomed, and we should not try
* to load a different variant. */
status = load(id, path, module);
}
return status;
}
/**
* Load the file defined by the variant and if successful
* return the dlopen handle and the hmi.
* @return 0 = success, !0 = failure.
*/
static int load(const char *id,
const char *path,
const struct hw_module_t **pHmi)
{
int status;
void *handle;
struct hw_module_t *hmi;
/*
* load the symbols resolving undefined symbols before
* dlopen returns. Since RTLD_GLOBAL is not or'd in with
* RTLD_NOW the external symbols will not be global
*/
handle = dlopen(path, RTLD_NOW);
if (handle == NULL) {
char const *err_str = dlerror();
LOGE("load: module=%s/n%s", path, err_str?err_str:"unknown");
status = -EINVAL;
goto done;
}
/* Get the address of the struct hal_module_info. */
const char *sym = HAL_MODULE_INFO_SYM_AS_STR;
hmi = (struct hw_module_t *)dlsym(handle, sym);
if (hmi == NULL) {
LOGE("load: couldn't find symbol %s", sym);
status = -EINVAL;
goto done;
}
/* Check that the id matches */
if (strcmp(id, hmi->id) != 0) {
LOGE("load: id=%s != hmi->id=%s", id, hmi->id);
status = -EINVAL;
goto done;
}
hmi->dso = handle;
/* success */
status = 0;
done:
if (status != 0) {
hmi = NULL;
if (handle != NULL) {
dlclose(handle);
handle = NULL;
}
} else {
LOGV("loaded HAL id=%s path=%s hmi=%p handle=%p",id, path, *pHmi, handle);
}
*pHmi = hmi;
return status;
}
///////////////////////////////////////
这样我们就可以知道通过上面的函数装载了通过属性定义的一些动态链接库.so文件。
我们这里文件就是/system/lib/hw/lights.tcc92xx.so
那么接下来我们就需要找到谁定义了这个light.tcc92xx.so文件的生成。
这介绍一个小技巧。
#########################
在Ubuntu10.04中时得到验证的。
查找一个字符串
grep -n "str" -r ./
在指定的路径(./)中遍历查找字符串str,显示行信息的方式显示出来。
查找一个文件
find 查找目录 -name 查找文件名
在指定路径下查找一个文件名。
##########################
在android_tcgit/hardware/telechips/tcc92xx/module/lights下有这个的定义,其Android.mk文件如下:
##########################
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_PRELINK_MODULE := false
# HAL module implemenation, not prelinked and stored in
# hw/<BACKLIGHT_HARDWARE_MODULE_ID>.<ro.product.board>.so
LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw
LOCAL_SHARED_LIBRARIES := liblog
LOCAL_C_INCLUDES := /
$(LOCAL_PATH)/include /
kernel/arch/arm/mach-tcc92x/include
LOCAL_SRC_FILES := lights.c
LOCAL_MODULE := lights.tcc92xx
LOCAL_CFLAGS := -fno-short-enums /
$(BOARD_MEM_FLAGS)
include $(BUILD_SHARED_LIBRARY)
##############################
这个就足以说明了这个目录的文件就是我们需要找的。
打开同目录下的light.c文件。发现
/////////////////////////////////
#define BACKLIGHT_LCD_DEVICE "/dev/graphics/fb0"
set_light = set_light_backlight;
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;
}
static int set_light_backlight(struct light_device_t* dev,struct light_state_t const* state)
{
struct light_context_t *ctx = (struct light_context_t *) dev;
int ret, level;
level = rgb_to_brightness(state);
LOGV("%s: level=%d", __func__, level);
#ifdef BL_DRIVER
ret = ioctl(ctx->fd, IOCTL_BL_SET_LEVEL, &level);
#else
ret = ioctl(ctx->fd, TCC_LCD_BL_SET, &level);
#endif//
return ret;
}
static int light_device_open(const struct hw_module_t* module, const char* name,struct hw_device_t** device)
{
int (*set_light)(struct light_device_t *dev,struct light_state_t const* staet);
struct light_context_t *ctx;
int status = -EINVAL;
int fd;
if (!strcmp(LIGHT_ID_BACKLIGHT, name)) {
/* open lcd device */
fd = open(BACKLIGHT_LCD_DEVICE, O_RDWR);
if (fd <= 0) {
LOGE("can't open lcd device '%s'", BACKLIGHT_LCD_DEVICE);
return -EINVAL;
}
set_light = set_light_backlight;
} else if (!strcmp(LIGHT_ID_KEYBOARD, name)) {
/* open keyboard device */
fd = open(BACKLIGHT_KBD_DEVICE, O_RDWR);
if (fd <= 0) {
LOGE("can't open keyboard device '%s'", BACKLIGHT_KBD_DEVICE);
return -EINVAL;
}
set_light = set_light_keyboard;
} else if (!strcmp(LIGHT_ID_BUTTONS, name)) {
/* open button device */
fd = open(BACKLIGHT_BTN_DEVICE, O_RDWR);
if (fd <= 0) {
LOGE("can't open button device '%s'", BACKLIGHT_BTN_DEVICE);
return -EINVAL;
}
set_light = set_light_buttons;
} else if (!strcmp(LIGHT_ID_BATTERY, name)) {
set_light = set_light_battery;
} else if (!strcmp(LIGHT_ID_NOTIFICATIONS, name)) {
set_light = set_light_notifications;
} else if (!strcmp(LIGHT_ID_ATTENTION, name)) {
set_light = set_light_attention;
} else {
return -EINVAL;
}
ctx = (struct light_context_t *) malloc(sizeof(struct light_context_t));
if (ctx == NULL) {
LOGE("can't allocate memory");
return ENOMEM;
}
/* initialize our state here */
memset(ctx, 0, sizeof(*ctx));
/* initialize the procs */
ctx->device.common.tag = HARDWARE_DEVICE_TAG;
ctx->device.common.version = 0;
ctx->device.common.module = (struct hw_module_t*)(module);
ctx->device.common.close = light_device_close;
ctx->device.set_light = set_light;
ctx->fd = fd;
*device = &ctx->device.common;
return 0;
}
//////////////////////////////////
这个就说明了,这个里面的调用实际上也是通过ioctl的方式调用另外一个设备驱动程序。
那么接下来我们就看fb0这个内核驱动吧。
*********************************************
Kernel层
*********************************************
通过以上的这些步骤,我们发现,我们实际上最后还是到了Kernel层的驱动调用。
从背光的硬件角度,我们了解到大多素的都是利用PWM控制输出电压的大小,这样来控制背光的亮度。
文件/android_tcgit/kernel/drivers/video/tcc92xxfb.c(可以通过查询TCC_LCD_BL_SET来找到这个文件)
就是定义的这个地方,代码有如下片段:
/////////////////////////////////////////
static int tccfb_ioctl(struct fb_info *info, unsigned int cmd,unsigned long arg)
{
unsigned int imgch=0;
void __user *argp = (void __user *) arg;
unsigned int prev_fmt[CONFIG_FB_TCC_DEVS], prev_pd[CONFIG_FB_TCC_DEVS];
unsigned int curr_fmt[CONFIG_FB_TCC_DEVS], curr_pd[CONFIG_FB_TCC_DEVS];
if((0 <= info->node) && (info->node < CONFIG_FB_TCC_DEVS))
{
imgch = info->node;
}
else
{
dprintk("ioctl: Error - fix.id[%d]/n", info->node);
return 0;
}
switch(cmd)
{
case TCC_LCD_BL_SET:
{
unsigned long bl_val;
copy_from_user((void *)&bl_val, (const void *)arg, sizeof(unsigned long));
dprintk("ioctl: TCC_LCD_BL_SET %d %d %d /n", bl_val, backlight_level, fb_power_state);
backlight_level = bl_val;
tca_bkl_setpowerval(backlight_level);
}
break;
////////////////
...............
///////////////
}
}
/////////////////////////////////////////
可以看得出来,最后的设置是通过tca_bkl_setpowerval()来设置传入的参数的。
继续往下走,看看tca_bkl_setpowerval定义做了什么。
Tca_backlight.c (drivers/char) 中,定义如下:
/////////////////////////////////////////////////////
void tca_bkl_setpowerval(unsigned int inValue)
{
dprintk("tca_bkl_setpowerval inValue (%d) (%d) (%d) /n", inValue, tcc_lcd_fb_on, bl_value);
if(tcc_lcd_fb_on)
{
backlight_ctrl(inValue);
bl_value = inValue;
}
}
////////////////////////////////////////////////
继续看backlight_ctrl
////////////////////////////////////////////////
void backlight_ctrl(unsigned int level)
{
if(level)
{
tca_bkl_OnOff(1);
BITCSET(HwGPIOA->GPFN0, HwPORTCFG_GPFN6_MASK, HwPORTCFG_GPFN6(2));
pTIMER->TMREF2 = (3200*level)/100;
}
else
{
BITCSET(HwGPIOA->GPFN0, HwPORTCFG_GPFN6_MASK, HwPORTCFG_GPFN6(0));
BITCSET(HwGPIOA->GPEN, Hw6, Hw6);
BITCSET(HwGPIOA->GPDAT, Hw6, 0);
tca_bkl_OnOff(0);
}
}
void tca_bkl_OnOff(char onoff)
{
if(onoff)
{//GPIO_F16 output 1,
BITCLR(HwGPIOF->GPFN2, Hw4-Hw0);
BITSET(HwGPIOF->GPEN, Hw16);
BITSET(HwGPIOF->GPDAT, Hw16);
}
else
{ //GPIO F16 output 0
BITCLR(HwGPIOF->GPFN2, Hw4-Hw0);
BITSET(HwGPIOF->GPEN, Hw16);
BITCLR(HwGPIOF->GPDAT, Hw16);
}
}
////////////////////////////////////////////////
上面的代码就比较简单了。无非一些IO和Register操作。这样就完成整个操作。
*********************************************
总结
*********************************************
通过以上我们可以获知,整个系统从应用到底层IO操作是一个怎么样的过程。
这个我们就可以了解了BackLight的整个架构了!