赞
踩
http://blog.csdn.net/zirconsdu/article/details/9288187
亮度设置
应用设计
1.1 设置进度条范围
背光设置是在:设置->声音和显示->亮度,通过进度条来设置的。
文件:packages/apps/Settings/src/com/android/settings/BrightnessPreference.java
private static final int MINIMUM_BACKLIGHT = android.os.Power.BRIGHTNESS_DIM + 10;
private static final int MAXIMUM_BACKLIGHT = android.os.Power.BRIGHTNESS_ON;
mSeekBar.setMax(MAXIMUM_BACKLIGHT - MINIMUM_BACKLIGHT);
设置进度条的范围,BRIGHTNESS_DIM = 20 BRIGHTNESS_ON=255,它们的定义在:
frameworks/base/core/java/android/os/Power.java
1.2 设置亮度
文件:packages/apps/Settings/src/com/android/settings/BrightnessPreference.java
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
setMode(isChecked ? Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC
: Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
if (!isChecked) {
setBrightness(mSeekBar.getProgress() + MINIMUM_BACKLIGHT);
}
}
private void setBrightness(int brightness) {
try {
IPowerManager power = IPowerManager.Stub.asInterface(
ServiceManager.getService("power"));
if (power != null) {
power.setBacklightBrightness(brightness);
}
} catch (RemoteException doe) {
}
}
由以上代码可知,brightness的范围是:20~255;代码通过服务管理器(ServiceManager)获得power服务,然后通过power服务设置亮度。
power.setBacklightBrightness的定义在:
rameworks/base/core/java/android/os/IPowerManager.aidl.java
frameworks/base/core/java/android/os/PowerManager.java
2, Power服务
文件:frameworks/base/core/java/android/os/Power.java
/**
* Brightness value for dim backlight
*/
public static final int BRIGHTNESS_DIM = 20;
/**
* Brightness value for fully on
*/
public static final int BRIGHTNESS_ON = 255;
文件:frameworks/base/core/java/android/os/PowerManager.java
/**
* sets the brightness of the backlights (screen, keyboard, button).
*
* @param brightness value from 0 to 255
*
* {@hide}
*/
public void setBacklightBrightness(int brightness)
{
try {
mService.setBacklightBrightness(brightness);
} catch (RemoteException e) {
}
}
电源管理器(powermager)将brightness转给电源服务,该服务位置如下:
文件: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;
}
}
由以上代码可知,同时设置了背光、键盘、按钮的亮度。mHardware 是硬件服务,通过该服务调用底层与设备打交道的C/C++代码,setLightBrightness_UNCHECKED原型如下:
文件: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);
}
参数说明:int light 表示类型,选项如下:
static final int LIGHT_ID_BACKLIGHT = 0;
static final int LIGHT_ID_KEYBOARD = 1;
static final int LIGHT_ID_BUTTONS = 2;
static final int LIGHT_ID_BATTERY = 3;
static final int LIGHT_ID_NOTIFICATIONS = 4;
static final int LIGHT_ID_ATTENTION = 5;
int brightness 表示亮度值
int brightnessMode 表示亮度的控制模式,选项如下:
/**
* Light brightness is managed by a user setting.
*/
static final int BRIGHTNESS_MODE_USER = 0;
/**
* Light brightness is managed by a light sensor.
*/
static final int BRIGHTNESS_MODE_SENSOR = 1;
由代码:
int b = brightness & 0x000000ff;
b = 0xff000000 | (b << 16) | (b << 8) | b;
可知,亮度值在此进行了修改,即亮度值的格式变成:FFRRGGBB,FF是没有的,RR、GG、BB分别是256色的红绿蓝,并且红绿蓝的值都是一样的亮度值。
3 硬件调用
3.1获取硬件
文件:frameworks/base/services/jni/com_android_server_HardwareService.cpp
enum {
LIGHT_INDEX_BACKLIGHT = 0,
LIGHT_INDEX_KEYBOARD = 1,
LIGHT_INDEX_BUTTONS = 2,
LIGHT_INDEX_BATTERY = 3,
LIGHT_INDEX_NOTIFICATIONS = 4,
LIGHT_INDEX_ATTENTION = 5,
LIGHT_COUNT
};
#define LIGHTS_HARDWARE_MODULE_ID "lights"
static jint 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);
} else {
memset(devices, 0, sizeof(Devices));
}
return (jint)devices;
}
用hw_get_module获取ID为LIGHTS_HARDWARE_MODULE_ID的硬件模块,该模块含有6个不同类型的亮度控制。
hw_get_module 的实现原理,如下:
文件:hardware/libhardware/Hardware.c
#define HAL_LIBRARY_PATH "/system/lib/hw"
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"
};
static const int HAL_VARIANT_KEYS_COUNT =
(sizeof(variant_keys)/sizeof(variant_keys[0]));
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;
}
property_get(variant_keys[i], prop, NULL) 会按如下顺序去获取如下变量所对应的值,然后返回给prop:
"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"
它们对应的变量为:
"ro.product.board=$TARGET_BOOTLOADER_BOARD_NAME"
"ro.board.platform=$TARGET_BOARD_PLATFORM"
如vendor/htc/dream-open/BoardConfig.mk里定义的TARGET_BOARD_PLATFORM := msm7k,则prop返回” msm7k ”,所以path = /system/lib/hw/lights. msm7k.so,也就是说要获取的硬件模块为lights. msm7k.so。
3.2调用硬件
setLight_native对应的jni C/C++代码是:
文件: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);
}
通过light标识找到对应的light设备,然后再设置亮度。
3.3 硬件原型
msm7k的lights对应的硬件原型是在:hardware/msm7k/liblights
文件:hardware/msm7k/liblights/Android.mk
LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw
LOCAL_MODULE := lights.$(TARGET_BOARD_PLATFORM)
也就是生成模块:/system/lib/hw/lights. msm7k.so
文件:hardware/msm7k/liblights/lights.c
/** Open a new instance of a lights device using name */
static int open_lights(const struct hw_module_t* module, char const* name,
struct hw_device_t** device)
{
int (*set_light)(struct light_device_t* dev,
struct light_state_t const* state);
if (0 == strcmp(LIGHT_ID_BACKLIGHT, name)) {
set_light = set_light_backlight;
}
else if (0 == strcmp(LIGHT_ID_KEYBOARD, name)) {
set_light = set_light_keyboard;
}
else if (0 == strcmp(LIGHT_ID_BUTTONS, name)) {
set_light = set_light_buttons;
}
else if (0 == strcmp(LIGHT_ID_BATTERY, name)) {
set_light = set_light_battery;
}
else if (0 == strcmp(LIGHT_ID_NOTIFICATIONS, name)) {
set_light = set_light_notifications;
}
else if (0 == strcmp(LIGHT_ID_ATTENTION, name)) {
set_light = set_light_attention;
}
else {
return -EINVAL;
}
pthread_once(&g_init, init_globals);
struct light_device_t *dev = malloc(sizeof(struct light_device_t));
memset(dev, 0, sizeof(*dev));
dev->common.tag = HARDWARE_DEVICE_TAG;
dev->common.version = 0;
dev->common.module = (struct hw_module_t*)module;
dev->common.close = (int (*)(struct hw_device_t*))close_lights;
dev->set_light = set_light;
*device = (struct hw_device_t*)dev;
return 0;
}
static struct hw_module_methods_t lights_module_methods = {
.open = open_lights,
};
以上代码对应的是:
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);
也就是说,对不同的亮度设置给予了不同的设置函数。
举例,背光设置,背光对应的代码如下:
char const*const LCD_FILE
= "/sys/class/leds/lcd-backlight/brightness";
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)
{
int err = 0;
int brightness = rgb_to_brightness(state);
pthread_mutex_lock(&g_lock);
g_backlight = brightness;
err = write_int(LCD_FILE, brightness);
if (g_haveTrackballLight) {
handle_trackball_light_locked(dev);
}
pthread_mutex_unlock(&g_lock);
return err;
}
也就是往文件/sys/class/leds/lcd-backlight/brightness写入亮度值,然后驱动会根据该文件更改背光的亮度。LCD_FILE的路径根据实际情况更改,同时需要在init.rc 修改其权限,使其可写rgb_to_brightness也根据实际更改,比如要直接亮度值控制,那只要获取r,g,b其中的一个值就行了,如:
static int
rgb_to_brightness(struct light_state_t const* state)
{
int color = state->color & 0x000000ff;
return color;
}
4,led类驱动
4.1,驱动创建leds类,系统启动时执行leds_init在目录/sys/class/创建子目录leds
kernel/drivers/leds/Led-class.c
static int __init leds_init(void)
{
leds_class = class_create(THIS_MODULE, "leds");
if (IS_ERR(leds_class))
return PTR_ERR(leds_class);
leds_class->suspend = led_suspend;
leds_class->resume = led_resume;
return 0;
}
4.2,led_classdev_register,调用这个函数就在目录/sys/class/leds创建子目录led_cdev->name和属性文件brightness
对brightness文件写就执行led_brightness_store,对brightness文件读就执行led_brightness_show,为下面的lcd,led注册做好准备
kernel/drivers/leds/Led-class.c
static ssize_t led_brightness_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
/* no lock needed for this */
led_update_brightness(led_cdev);
return sprintf(buf, "%u/n", led_cdev->brightness);
}
static ssize_t led_brightness_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
ssize_t ret = -EINVAL;
char *after;
unsigned long state = simple_strtoul(buf, &after, 10);
size_t count = after - buf;
if (*after && isspace(*after))
count++;
if (count == size) {
ret = count;
if (state == LED_OFF)
led_trigger_remove(led_cdev);
led_set_brightness(led_cdev, state);
}
return ret;
}
static DEVICE_ATTR(brightness, 0644, led_brightness_show, led_brightness_store);
/**
* led_classdev_register - register a new object of led_classdev class.
* @parent: The device to register.
* @led_cdev: the led_classdev structure for this device.
*/
int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
{
int rc;
led_cdev->dev = device_create(leds_class, parent, 0, led_cdev,
"%s", led_cdev->name);
if (IS_ERR(led_cdev->dev))
return PTR_ERR(led_cdev->dev);
/* register the attributes */
rc = device_create_file(led_cdev->dev, &dev_attr_brightness);
if (rc)
goto err_out;
#ifdef CONFIG_LEDS_TRIGGERS
init_rwsem(&led_cdev->trigger_lock);
#endif
/* add to the list of leds */
down_write(&leds_list_lock);
list_add_tail(&led_cdev->node, &leds_list);
up_write(&leds_list_lock);
led_update_brightness(led_cdev);
#ifdef CONFIG_LEDS_TRIGGERS
rc = device_create_file(led_cdev->dev, &dev_attr_trigger);
if (rc)
goto err_out_led_list;
led_trigger_set_default(led_cdev);
#endif
printk(KERN_INFO "Registered led device: %s/n",
led_cdev->name);
return 0;
#ifdef CONFIG_LEDS_TRIGGERS
err_out_led_list:
device_remove_file(led_cdev->dev, &dev_attr_brightness);
list_del(&led_cdev->node);
#endif
err_out:
device_unregister(led_cdev->dev);
return rc;
}
EXPORT_SYMBOL_GPL(led_classdev_register);
4.3,lcd驱动调用led_classdev_register,在目录/sys/class/leds创建子目录lcd-backlight和属性文件brightness
kernel/drivers/video/msm/Msm_fb.c
static int lcd_backlight_registered;
static void msm_fb_set_bl_brightness(struct led_classdev *led_cdev,
enum led_brightness value)
{
struct msm_fb_data_type *mfd = dev_get_drvdata(led_cdev->dev->parent);
int bl_lvl;
if (value > MAX_BACKLIGHT_BRIGHTNESS)
value = MAX_BACKLIGHT_BRIGHTNESS;
/* This maps android backlight level 0 to 255 into
driver backlight level 0 to bl_max with rounding */
bl_lvl = (2 * value * mfd->panel_info.bl_max + MAX_BACKLIGHT_BRIGHTNESS)
/(2 * MAX_BACKLIGHT_BRIGHTNESS);
if (!bl_lvl && value)
bl_lvl = 1;
msm_fb_set_backlight(mfd, bl_lvl, 1);
}
static struct led_classdev backlight_led = {
.name = "lcd-backlight",
.brightness = MAX_BACKLIGHT_BRIGHTNESS,
.brightness_set = msm_fb_set_bl_brightness,
};
if (!lcd_backlight_registered) {
if (led_classdev_register(&pdev->dev, &backlight_led))
printk(KERN_ERR "led_classdev_register failed/n");
else
lcd_backlight_registered = 1;
}
就在目录/sys/class/leds创建子目录 lcd-backlight和属性文件brightness
当按键或者来的或者改变lcd亮度时,上层对属性文件/sys/class/leds/lcd-backlight/brightness写入背光的亮度数值就
调用led_brightness_store
调用simple_strtoul(buf, &after, 10);将输入的字符串转换为10进制的数字
执行led_set_brightness
执行led_cdev->brightness_set(led_cdev, value
调用msm_fb_set_bl_brightness ,因为 .brightness_set = msm_fb_set_bl_brightness,
/* This maps android backlight level 0 to 255 into driver backlight level 0 to bl_max with rounding */
bl_lvl = (2 * value * mfd->panel_info.bl_max + MAX_BACKLIGHT_BRIGHTNESS) /(2 * MAX_BACKLIGHT_BRIGHTNESS);
将输入的0--255转换为IC的0--bl_max
调用 msm_fb_set_backlight(mfd, bl_lvl, 1);
最终改变LCD的背光驱动电路的设置,调节LCD的背光的亮度.
注意,msm_fb_set_backlight以后是使用led_trigger调用真正led_classdev "wled"的brightnes_set去设置背光。
用户态ioctl通过msm_fb_set_backlight调用到msm_fb_panel_data::set_backlight,
"lcd_backlight".brightness_set -> msm_fb_panel_data::set_backlight -> "bkl_trigger".led_trigger -> "wled".brightness_set。然后找真正操作硬件IC部分。
驱动中设置背光则是绕过"lcd_backlight"设备直接通过backlight_worker工作执行到msm_fb_panel_data::set_backlight,然后-> "bkl_trigger".led_trigger -> "wled".brightness_set。
当使用Samsung MIPI-DSI CMD屏时,msm_fb_panel_data::set_backlight为mipi_samsung_set_backlight。代码如下:
与bkl_led_trigger关联的led就是屏幕背光wled。屏幕背光wled是做为pmic leds数组的一个元素。如:
可以看到wled与"bkl_trigger"相关联,使用bkl_trigger可以操作该led。
该数组中的leds使用同一个驱动:name = PM8XXX_LEDS_DEV_NAME。
In kernel/drivers/leds/leds-pm8xxx.c
pm8xxx_led_probe会对pm8038_led_info数组中的每个led使用设置led_classdev字段,并且初始化work item,然后使用led_classdev_register向系统注册每个led设备。
probe过程中每个led的brightness_set字段设置为pm8xxx_led_set。
可以看出,是做为work来操作。
对PM8XXX_ID_WLED,是使用__pm8xxx_led_work
led_wled_set通过SSBI接口写电源管理芯片PM8xxx的WLED控制寄存器,控制wled亮灭和亮度。
4.4 键盘背光灯
上层对属性文件/sys/class/leds/keyboard-backlight/brightness写入背光的亮度数值
(kernel/drivers/leds/Leds-msm-pmic.c
#define MAX_KEYPAD_BL_LEVEL 16
static void msm_keypad_bl_led_set(struct led_classdev *led_cdev,
enum led_brightness value)
{
int ret;
ret = pmic_set_led_intensity(LED_KEYPAD, value / MAX_KEYPAD_BL_LEVEL);
if (ret)
dev_err(led_cdev->dev, "can't set keypad backlight/n");
}
static struct led_classdev msm_kp_bl_led = {
.name = "keyboard-backlight",
.brightness_set = msm_keypad_bl_led_set,
.brightness = LED_OFF,
};
static int msm_pmic_led_probe(struct platform_device *pdev)
{
int rc;
rc = led_classdev_register(&pdev->dev, &msm_kp_bl_led);
if (rc) {
dev_err(&pdev->dev, "unable to register led class driver/n");
return rc;
}
msm_keypad_bl_led_set(&msm_kp_bl_led, LED_OFF);
return rc;
}
static int __devexit msm_pmic_led_remove(struct platform_device *pdev)
{
led_classdev_unregister(&msm_kp_bl_led);
return 0;
}
#ifdef CONFIG_PM
static int msm_pmic_led_suspend(struct platform_device *dev,
pm_message_t state)
{
led_classdev_suspend(&msm_kp_bl_led);
return 0;
}
static int msm_pmic_led_resume(struct platform_device *dev)
{
led_classdev_resume(&msm_kp_bl_led);
return 0;
}
#else
#define msm_pmic_led_suspend NULL
#define msm_pmic_led_resume NULL
#endif
static struct platform_driver msm_pmic_led_driver = {
.probe = msm_pmic_led_probe,
.remove = __devexit_p(msm_pmic_led_remove),
.suspend = msm_pmic_led_suspend,
.resume = msm_pmic_led_resume,
.driver = {
.name = "pmic-leds",
.owner = THIS_MODULE,
},
};
static int __init msm_pmic_led_init(void)
{
return platform_driver_register(&msm_pmic_led_driver);
}
module_init(msm_pmic_led_init);
static void __exit msm_pmic_led_exit(void)
{
platform_driver_unregister(&msm_pmic_led_driver);
}
module_exit(msm_pmic_led_exit);
MODULE_DESCRIPTION("MSM PMIC LEDs driver");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:p
系统行动执行msm_pmic_led_init(void)
调用 platform_driver_register(&msm_pmic_led_driver);
调用msm_pmic_led_probe
调用 led_classdev_register(&pdev->dev, &msm_kp_bl_led);
就在目录/sys/class/leds创建子目录 keyboard-backlight和属性文件brightness
当按键时,上层对属性文件/sys/class/leds/keyboard-backlight/brightness写入背光的亮度数值就
调用led_brightness_store
调用simple_strtoul(buf, &after, 10);将输入的字符串转换为10进制的数字
执行led_set_brightness
执行led_cdev->brightness_set(led_cdev, value
调用msm_keypad_bl_led_set ,因为 .brightness_set = msm_keypad_bl_led_set,
调用 ret = pmic_set_led_intensity(LED_KEYPAD, value / MAX_KEYPAD_BL_LEVEL);
最终改变LED驱动电路的设置,调节LED的亮度。
=================================================
led class 设备驱动是linux的光学设备驱动,通过sys/class/leds/ 提供节点给用户空间。一般用在手机等系统中控制三色指示灯,键盘,背光等设备。
以下就android 手机系统为例做一分析
1 userspace how to use
内核模块注册了led class 设备后,会在sys/class/leds/ 目录下生成注册时所用的名字的文件节点。
进入adb shell ,ls 一下
camera:flash0
camera:flash1
gpio24_red
gpio26_blue
lcd-backlight
led_drv0
led_drv1
led_drv2:green
led_psenso:keypad
这些就是我的开发手机上注册的led 设备。比如进入gpio26_blue 这个目录下 ls
brightness
device
max_brightness
power
subsystem
trigger
uevent
现在比较关心的是 max_brightness brightness 这两个文件。读取max_brightness 可得知这个led设备所支持的最大的亮度。向brightness 写入0 led 灭 写入小于max_brightness 的值会设置led的亮度。还有一个需要交代的是trigger 这个文件,用它可以实现一些触发事件。比较常用的一个是timer触发。用它实现led的闪烁。
2 代码结构
kernel/driver/leds/
几乎所有的代码分析,都要首先看的两个文件 kconfig makfile。他们像地图一样指引这我们如何或从哪里开始看代码。
但有时单纯的看kconfig 并不能知道那些feature 被编译,因为他们有依赖,甚至有些feature 被定义在一个叫做XXXX_perdefconfig 的文件中。幸运的是如果你编译了你的kernel 那么编译器会生成一个叫做 .config 的文件,这里汇集了所有的被定义的feature。然后结合要分析的代码的kconfig makfile ,文件结构就很清晰了。
led class 设备由三部分组成 ,一是core,二是led class 设备,三是trigger。led class 设备和trigger向core注册,core维护着led class 设备 及 trigger。
3 从led class 设备开始
一个简单的方法就是在module的初始化函数中,注册一个led class 设备。首先需要准备一个 struct led_classdev 类型的数据,
然后调用led_classdev_register 把它注册到led core 中。这样就可以用我上面提到的方法访问这个led了。
剩下的任务就是具体的准备struct led_classdev 这个数据或设备了。让我们看看他有些什么。
leds.h
这个结构抽象了一个led设备,需要driver的开发者实现其中的全部或部分。各个变量的含义及用法见我在代码中的注释。知道了这个设备的结构含义,准备这样一个设备就不难了。
这样就写了一个最简单的led设备驱动,运行后会在sys/class/leds/下生成keyboard-backlight节点。userspace 就可以操作键盘灯了。
4 该看看core了。
现在到时候看一下makefile了
# LED Core
obj-$(CONFIG_NEW_LEDS) += led-core.o
obj-$(CONFIG_LEDS_CLASS) += led-class.o
obj-$(CONFIG_LEDS_TRIGGERS) += led-triggers.o
以上三个文件就是led core的主要内容。很明显led-trigger.c 这个文件是负责管理led的trigger的。暂时不管他了。很幸运文件不多
先看一下led-core.c
这段代码很给力,去掉头文件就是几个变量的定义。
第8,9行 定义了一把锁,望文生义,这把锁锁的就是11 12 行定义的leds_list这个队列了。leds_list这个列表头把所有向core注册的led class 设备组成双向链表。
led-class.c
阅读linux 的code ,最好不过的就是从那些放在init节里的函数了,因为他们在系统启动时会调用,本模块的init节是 subsys_initcall 宏定义的一个函数
我们常见的还有一个是module_init , subsys_initcall 会比module_init 要早一些。关于init节的知识可以参考如下链接
http://blog.163.com/liuqiang_mail@126/blog/static/10996887520124741925773/
目前可以肯定在系统启动时leds_init 会被调用。纵览代码,让只不过做了以下几个工作
1 创建了一个类leds ,于是在sys/class/ 下便有了leds的节点了。
2 给几个成员赋值,挂起和唤醒,及设备属性。
做完这些事后init就很高兴的结束了。很令人失望,从init好像看不出什么来,他就是创建了leds的类,类是设备的类,设备是类的设备,可以想象当我们调用注册函数向core注册led设备时,这个led设备就属于这个leds类了。既然注册的led设备属于leds类,那么led设备就有这个类的所有特征,不然就不属于这个类。换句话说,我们注册的led设备会拥有led_class_attrs的属性等类的特性。所以现在有必要看一下这个重量级的注册函数了。
这个函数接受一个struct devices的指针来表明我们要注册的struct led_classdev 属于的父设备,如果没有,调用这个函数置为NULL就可以了。第二个参数就是struct led_classdev,是我们要向ledcore注册的led设备。第8行, 这行代码创建一个设备,这个设备struct device 是linux设备模型中的通用设备,任何其他定义的设备都应给包含一个strcut device,或他的一个指针。 第一个参数就是前面init时创建的led class,就是应为这个参数的传入,这个设备拥有了led类的说有特征,包括他的属性,电源管理等。在init时我们有如下赋值
leds_class->suspend = led_suspend;
leds_class->resume = led_resume;
leds_class->dev_attrs = led_class_attrs;
我们分析一下这几个函数或数据。
这个属性结构定义了这个ledclass设备数据有的属性,亮度,最大亮度,trigger。并定义了设置属性和读取属性的函数吗,及权限。那么当我们已这个ledclass为基础创建的设备就拥有这些属性,就会在生成相应的属性文件。
这时属性的设置函数,他的第一个结构体就是拥有这个属性的设备的指针。第四行,dev_get_drvdata,这个函数取到我们自定义的的led设备,比如redled。为什么这个device的驱动数据是我们自定义的led呢,返回注册函数
int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
7 {
8 led_cdev->dev = device_create(leds_class, parent, 0, led_cdev,
9 "%s", led_cdev->name);
看到了吧,第4个参数就是我们创建设备是传近去的,device_create会把这个参数设置到设备的驱动数据中去,所以dev_get_drvdata就可以取到了。聪明的你当然可以想到通过container_of 也可以取到我们自定义的设备。
第5个参数是设备号,如果不为0,会在dev目录下生成设备节点。最后是设备名,如redled
接着把这个设备挂到leds_list上,说明这个设备已归ledclass管理了。
26 27 28 ,初始化了一个定时器,后面分析timer trigger时用到,用作led闪烁用的。
到这里就暂告一段落了,下面从整体流程上做一总结。
当我们准备了
led子系统就绪后,会在sys/class/目录下生成leds的一个类。
static struct led_classdev msm_kp_bl_led = {
2 .name = "keyboard-backlight",
3 .brightness_set = msm_keypad_bl_led_set,
4 .brightness = LED_OFF,
5 };
一个led设备后,我们调用led_classdev_register(NULL, &msm_kp_bl_led);
注册这个设备,于是得到sys/class/leds/keyboard-backlight的设备,同时这个设备拥有了如下的属性
brightness
max_brightness
trigger
于是我们就可以修改他的属性,如brightness
echo 255 > brightness
接着设置属性的函数调用led_brightness_store ---------> led_set_brightness(led_cdev, state) 结果键盘灯就亮了。
前面一篇随笔大略的分析了led class设备。其中关于trigger的部分提了一下就略过了。现在具体的做个分析,ledtrigger比led class dev 要复杂的多。做点笔记记录下来以备以后用到。
trigger 中文的翻译叫做触发。既然叫trigger,一定有一个事件或条件达到时led出现一个状态(点亮,亮度改变,闪烁)。做个事件可以来自userspace的请求,或kenel产生的事件,如休眠,cpu空闲等。而这些事件或条件就是我们要注册的trigger。每个led可以由有若干了trigger。可以在注册led设备时指定默认的trigger ,也可以由userspace指定,切换。当trigger发生时,led会产生相应的trigger定义的动作。
既然trigger是led的,那么在ledclass_dev中一定有些记录。
const char *default_trigger; /* Trigger to use */
如:
283static struct led_info pm8038_led_info[] = {
284 [0] = {
285 .name = "wled",
286 .default_trigger = "bkl_trigger",
287 },
288 [1] = {
289 .name = "led:rgb_red",
290 //.default_trigger = "battery-charging",
291 },
292 [2] = {
293 .name = "led:rgb_green",
294 },
295 [3] = {
296 .name = "led:rgb_blue",
297 },
298};
如上代码,default_trigger 是这个led的默认的trigger名。如果在注册led设备时给予了他值,那么这个led就会在default_trigger 的条件下执行动作。
trigger_lock 一把锁,保护trig_list用的。trigger,一个led可以有许多trigger 这个值指向当前trigger。
trig_list。这个led说拥有的所有trigger的一个链表还是为了把这个led设备挂在trigger中的一个节点,这里得不到任何的信息,只能看后面的代码了。
trigger_data当前trigger的私有数据。
现在可以看一下led_trigger这个人物了
第一个成员是这个trigger的名字,不超过50个字符。
仅接着是trigger激活和取消的函数。从函数参数可以推测,这两个函数是针对特定的led的。
第九行是一个链表头,可以推测他把所有属于他trigge的led通过trig_list都链接起来。第12行应是自身的一个链表,用它把所有向ledtrigger注册的trigger链接起来。
接着我们从具体的一个trigger(定时器触发)出发,一步步的理清思路。
ledtrigger_timer.c
这段代码也清楚的说明了如何取写一个trigger。首先定义一个trigger。然后注册就可以了。重点是实现activate和deactivate函数。
这两个函数如何实现,稍后分析,先来看一下注册函数。
10-20 检查这个trigger是否已经注册,如果注册则返回已存在。
否则,把这个trigger加入到trigger_list列表中。
22-30 遍历所有led设备,如果发现某个led的默认trigger是本trigger,那么就把这个led设备通过他的trig_list 挂在 struct list_head led_cdevs 上。
这个过程是通过led_trigger_set(led_cdev, trigger);来完成的。
这个函数做了两件事,一是移除旧的,二是添加新的。通过传递的参数,完成往trigger中添加删除led设备的功能。就是在这里trigger的activate和deactive被执行。到此trigger就算分析完了。
接着ledtrigger_timer分析trigger的两个重量级函数active和deactive
timer_trig_activate 在本led设备下创建了两个节点,delay_on 和delay_off。在用户空间写这两个文件就会形成led的闪烁。具体原理可分析led_delay_on_store和led_delay_off_store两个属性设置函数。device_create_file(led_cdev->dev, &dev_attr_delay_on);这个函数会在这个设备led_cdev->dev下创建delay_on这个文件。
timer trigger需要user的干预才能触发闪烁,属于用户空间的请求。下面简单分析一个kernel空间事件的触发。
ledtrigger_sleep.c
代码很简单,他再init的时候注册了trigger。并注册了pm通知链。当pm状态变化时,pm通知回调会运行于是
led_trigger_event 被调用,还有一个函数是led_trigger_blink。
这个函数遍历所有当前trigger拥有的led设备。并让其闪烁。
系统中可能注册若干trigger,但一个led在某一个时刻有且最多能有一个trigger。那么如何切换led的trigger呢。
前面分析led设备时,在注册led类是有个属性数组,里边有一项就是trigger属性。我们来看一下这个属性。
__ATTR(trigger, 0644, led_trigger_show, led_trigger_store),
看一下这个属性设置函数 led_trigger_store
当user 写 trigger这个文件时。这个函数会调用。22到29完成切换过程。
他遍历trigger_list。找到trigger_name匹配的trigger。然后调用led_trigger_set把这个设备设置给这个trigger。
===============================================================================================================
程序分为两部分
一部分是把led设备注册到trigger上。如果linux已经为led开启触发功能。那么在led设备的创建后,需要把ed设备注册到trigger上。
一部分是触发种类(trigger)的注册。
led设备注册到trigger上:
在trigger链表上搜索与led设备def_trigger名字相同的trigger项。
如果搜索到了,那么把led设备链接到trigger项的led_cdevs为表头的链表上。如果没有搜索到,那么暂时不注册。
触发种类(trigger)的注册:
首先申请一个trigger,在trigger_list这个链表上搜索是否已经注册过该trigger。如果注册过,直接返回,并报错。如果没有注册过,把这个trigger加到trigger_list链表中完成注册。考虑先前可能已经有很多的led设备想注册到这个trigger上。所以还需要遍历leds_list链表(led设备链表)。把需要注册到这个trigger上的led设备进行注册。
Simple LED Tigger Interface
led_trigger_event完成了对trigger的响应。把trigger->led_cdevs所有注册过的设备激活。进行相应的led操作。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。