Android屏幕、键盘背光Framework和Linux led_classdev
2013-07-10 10:51
519 查看
转载并且对控制led的driver部分做了补充。
亮度设置
应用设计
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操作。
亮度设置
应用设计
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。代码如下:
In mipi_samsung.c 294static void mipi_samsung_set_backlight(struct msm_fb_data_type *mfd) 295{ 302 if ((mipi_samsung_pdata->enable_wled_bl_ctrl) 303 && (wled_trigger_initialized)) { 304 led_trigger_event(bkl_led_trigger, mfd->bl_level); 305 return; 306 } 307}
与bkl_led_trigger关联的led就是屏幕背光wled。屏幕背光wled是做为pmic leds数组的一个元素。如:
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};
可以看到wled与"bkl_trigger"相关联,使用bkl_trigger可以操作该led。
该数组中的leds使用同一个驱动:name = PM8XXX_LEDS_DEV_NAME。
In kernel/drivers/leds/leds-pm8xxx.c
#define PM8XXX_LEDS_DEV_NAME "pm8xxx-led" 2283static struct platform_driver pm8xxx_led_driver = { 2284 .probe = pm8xxx_led_probe, 2285 .remove = __devexit_p(pm8xxx_led_remove), 2286 .driver = { 2287 .name = PM8XXX_LEDS_DEV_NAME, 2288 .owner = THIS_MODULE, 2289 }, 2290};
pm8xxx_led_probe会对pm8038_led_info数组中的每个led使用设置led_classdev字段,并且初始化work item,然后使用led_classdev_register向系统注册每个led设备。
2197 INIT_WORK(&led_dat->work, pm8xxx_led_work); 2198 INIT_WORK(&led_dat->modework, pm8xxx_mode_work); 2199 INIT_WORK(&led_dat->testwork, pm8xxx_test_work);
probe过程中每个led的brightness_set字段设置为pm8xxx_led_set。
1790static void pm8xxx_led_set(struct led_classdev *led_cdev, 1791 enum led_brightness value) 1792{ 1793 struct pm8xxx_led_data *led; 1794 1795 led = container_of(led_cdev, struct pm8xxx_led_data, cdev); 1796 1797 if (value < LED_OFF || value > led->cdev.max_brightness) { 1798 dev_err(led->cdev.dev, "Invalid brightness value exceeds"); 1799 return; 1800 } 1801 1802 led->cdev.brightness = value; 1803 schedule_work(&led->work); 1804}
可以看出,是做为work来操作。
1730static void pm8xxx_led_work(struct work_struct *work) 1731{ 1732 int rc; 1733 1734 struct pm8xxx_led_data *led = container_of(work, 1735 struct pm8xxx_led_data, work); 1736 1737 if (led->pwm_dev == NULL) { 1738 __pm8xxx_led_work(led, led->cdev.brightness); 1739 } else { 1740 rc = pm8xxx_led_pwm_work(led); 1741 if (rc) 1742 pr_err("could not configure PWM mode for LED:%d\n", 1743 led->id); 1744 } 1745}
对PM8XXX_ID_WLED,是使用__pm8xxx_led_work
1692static void __pm8xxx_led_work(struct pm8xxx_led_data *led, 1693 enum led_brightness level) 1694{ 1695 int rc; 1696 1697 mutex_lock(&led->lock); 1698 1699 switch (led->id) { 1700 case PM8XXX_ID_LED_KB_LIGHT: 1701 led_kp_set(led, level); 1702 break; 1703 case PM8XXX_ID_LED_0: 1704 case PM8XXX_ID_LED_1: 1705 case PM8XXX_ID_LED_2: 1706 led_lc_set(led, level); 1707 break; 1708 case PM8XXX_ID_FLASH_LED_0: 1709 case PM8XXX_ID_FLASH_LED_1: 1710 led_flash_set(led, level); 1711 break; 1712 case PM8XXX_ID_WLED: 1713 rc = led_wled_set(led, level); 1714 if (rc < 0) 1715 pr_err("wled brightness set failed %d\n", rc); 1716 break; 1717 case PM8XXX_ID_RGB_LED_RED: 1718 case PM8XXX_ID_RGB_LED_GREEN: 1719 case PM8XXX_ID_RGB_LED_BLUE: 1720 led_rgb_set(led, level); 1721 break; 1722 default: 1723 dev_err(led->cdev.dev, "unknown led id %d", led->id); 1724 break; 1725 } 1726 1727 mutex_unlock(&led->lock); 1728}
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的亮度。
1028int pmic_set_led_intensity(enum ledtype type, int level) 1029{ 1030 return pmic_rpc_set_only(type, level, 0, 0, 2, SET_LED_INTENSITY_PROC); 1031}
383/** 384 * pmic_rpc_set_only() - set arguments and no get 385 * @data0: first argumrnt 386 * @data1: second argument 387 * @data2: third argument 388 * @data3: fourth argument 389 * @num: number of argument 390 * @proc: command/request id 391 * 392 * This function covers case a, b, and c 393 */ 394static int pmic_rpc_set_only(uint data0, uint data1, uint data2, uint data3, 395 int num, int proc) 396{ 397 struct pmic_ctrl *pm = &pmic_ctrl; 398 struct pmic_buf *tp; 399 struct pmic_buf *rp; 400 int stat; 401 402 403 if (mutex_lock_interruptible(&pmic_mtx)) 404 return -ERESTARTSYS; 405 406 if (pm->inited <= 0) { 407 stat = pmic_buf_init(); 408 if (stat < 0) { 409 mutex_unlock(&pmic_mtx); 410 return stat; 411 } 412 } 413 414 tp = &pm->tbuf; 415 rp = &pm->rbuf; 416 417 pmic_buf_reset(tp); 418 pmic_buf_reserve(tp, sizeof(struct rpc_request_hdr)); 419 pmic_buf_reset(rp); 420 421 if (num > 0) 422 pmic_put_tx_data(tp, data0); 423 424 if (num > 1) 425 pmic_put_tx_data(tp, data1); 426 427 if (num > 2) 428 pmic_put_tx_data(tp, data2); 429 430 if (num > 3) 431 pmic_put_tx_data(tp, data3); 432 433 stat = pmic_rpc_req_reply(tp, rp, proc); 434 if (stat < 0) { 435 mutex_unlock(&pmic_mtx); 436 return stat; 437 } 438 439 pmic_pull_rx_data(rp, &stat); /* result from server */ 440 441 mutex_unlock(&pmic_mtx); 442 443 return modem_to_linux_err(stat); 444}
=================================================
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
struct led_classdev { const char *name; //led 设备的名字,注册这个设备后会出现在sys/class/leds/下 int brightness;//当前led灯的亮度,最大值为下面的变量,为0时代表led灭 int max_brightness;//最大亮度,系统定义了一个enum 用于表明这个变量的范围 int flags;//这个标识的高16bit是控制信息,低16bit是状态信息。主要用来控制suspend //具体的取值见下面的定义 /* Lower 16 bits reflect status */ #define LED_SUSPENDED (1 << 0) /* Upper 16 bits reflect control information */ #define LED_CORE_SUSPENDRESUME (1 << 16) /* Set LED brightness level */ /* Must not sleep, use a workqueue if needed */ void (*brightness_set)(struct led_classdev *led_cdev, enum led_brightness brightness);//设置led亮度的函数指针,需要driver开发者 //根据硬件特性实现。 /* Get LED brightness level */ enum led_brightness (*brightness_get)(struct led_classdev *led_cdev); /* * Activate hardware accelerated blink, delays are in milliseconds * and if both are zero then a sensible default should be chosen. * The call should adjust the timings in that case and if it can't * match the values specified exactly. * Deactivate blinking again when the brightness is set to a fixed * value via the brightness_set() callback. */ int (*blink_set)(struct led_classdev *led_cdev, unsigned long *delay_on, unsigned long *delay_off);//硬件闪烁函数,在实现闪烁时,如果这个函数drive实 // 现了,那么会优先调用他来硬件加速,否则core 会调用一个软件用定时器模拟的闪烁。在实际的应用中,软件模拟 //的闪烁是不可取的。因为他要求cpu要一直工作,这对于像手机这样的电池供电的设备是不可接受的。 struct device *dev; // 属于linux 设备模型的东西,每一个设备都有这么一个device struct list_head node; /* LED Device list */ //拥有这个struct list_head 结构的设备可以用它把本身挂到一个属于他的链表中,方便core管理,其实大部分的注册函数都几乎会把向他注册的东东挂到一个他关心的链表中。 const char *default_trigger; /* Trigger to use */ //以下都是trigger 相关的东西,在分析trigger是详细说明 unsigned long blink_delay_on, blink_delay_off; struct timer_list blink_timer; int blink_brightness; #ifdef CONFIG_LEDS_TRIGGERS /* Protects the trigger data below */ struct rw_semaphore trigger_lock; struct led_trigger *trigger; struct list_head trig_list; void *trigger_data; #endif };
这个结构抽象了一个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
#include <linux/kernel.h> #include <linux/list.h> #include <linux/module.h> #include <linux/rwsem.h> #include <linux/leds.h> #include "leds.h" DECLARE_RWSEM(leds_list_lock); EXPORT_SYMBOL_GPL(leds_list_lock); LIST_HEAD(leds_list); EXPORT_SYMBOL_GPL(leds_list);
这段代码很给力,去掉头文件就是几个变量的定义。
第8,9行 定义了一把锁,望文生义,这把锁锁的就是11 12 行定义的leds_list这个队列了。leds_list这个列表头把所有向core注册的led class 设备组成双向链表。
led-class.c
阅读linux 的code ,最好不过的就是从那些放在init节里的函数了,因为他们在系统启动时会调用,本模块的init节是 subsys_initcall 宏定义的一个函数
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; leds_class->dev_attrs = led_class_attrs; return 0; } static void __exit leds_exit(void) { class_destroy(leds_class); } subsys_initcall(leds_init); module_exit(leds_exit);
我们常见的还有一个是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的属性等类的特性。所以现在有必要看一下这个重量级的注册函数了。
/** * 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) { 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); #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); if (!led_cdev->max_brightness) led_cdev->max_brightness = LED_FULL; led_update_brightness(led_cdev); init_timer(&led_cdev->blink_timer); led_cdev->blink_timer.function = led_timer_function; led_cdev->blink_timer.data = (unsigned long)led_cdev; #ifdef CONFIG_LEDS_TRIGGERS led_trigger_set_default(led_cdev); #endif printk(KERN_DEBUG "Registered led device: %s\n", led_cdev->name); return 0; } EXPORT_SYMBOL_GPL(led_classdev_register);
这个函数接受一个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;
我们分析一下这几个函数或数据。
static struct device_attribute led_class_attrs[] = { __ATTR(brightness, 0644, led_brightness_show, led_brightness_store), __ATTR(max_brightness, 0644, led_max_brightness_show, led_max_brightness_store), #ifdef CONFIG_LEDS_TRIGGERS __ATTR(trigger, 0644, led_trigger_show, led_trigger_store), #endif __ATTR_NULL, };
这个属性结构定义了这个ledclass设备数据有的属性,亮度,最大亮度,trigger。并定义了设置属性和读取属性的函数吗,及权限。那么当我们已这个ledclass为基础创建的设备就拥有这些属性,就会在生成相应的属性文件。
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 (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; }
这时属性的设置函数,他的第一个结构体就是拥有这个属性的设备的指针。第四行,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};
#ifdef CONFIG_LEDS_TRIGGERS /* Protects the trigger data below */ struct rw_semaphore trigger_lock; struct led_trigger *trigger; struct list_head trig_list; void *trigger_data; #endif
如上代码,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这个人物了
struct led_trigger { /* Trigger Properties */ const char *name; void (*activate)(struct led_classdev *led_cdev); void (*deactivate)(struct led_classdev *led_cdev); /* LEDs under control by this trigger (for simple triggers) */ rwlock_t leddev_list_lock; struct list_head led_cdevs; /* Link to next registered trigger */ struct list_head next_trig; };
第一个成员是这个trigger的名字,不超过50个字符。
仅接着是trigger激活和取消的函数。从函数参数可以推测,这两个函数是针对特定的led的。
第九行是一个链表头,可以推测他把所有属于他trigge的led通过trig_list都链接起来。第12行应是自身的一个链表,用它把所有向ledtrigger注册的trigger链接起来。
接着我们从具体的一个trigger(定时器触发)出发,一步步的理清思路。
ledtrigger_timer.c
static struct led_trigger timer_led_trigger = { .name = "timer", .activate = timer_trig_activate, .deactivate = timer_trig_deactivate, }; static int __init timer_trig_init(void) { return led_trigger_register(&timer_led_trigger); } static void __exit timer_trig_exit(void) { led_trigger_unregister(&timer_led_trigger); } module_init(timer_trig_init); module_exit(timer_trig_exit);
这段代码也清楚的说明了如何取写一个trigger。首先定义一个trigger。然后注册就可以了。重点是实现activate和deactivate函数。
这两个函数如何实现,稍后分析,先来看一下注册函数。
int led_trigger_register(struct led_trigger *trigger) { struct led_classdev *led_cdev; struct led_trigger *trig; rwlock_init(&trigger->leddev_list_lock); INIT_LIST_HEAD(&trigger->led_cdevs); down_write(&triggers_list_lock); /* Make sure the trigger's name isn't already in use */ list_for_each_entry(trig, &trigger_list, next_trig) { if (!strcmp(trig->name, trigger->name)) { up_write(&triggers_list_lock); return -EEXIST; } } /* Add to the list of led triggers */ list_add_tail(&trigger->next_trig, &trigger_list); up_write(&triggers_list_lock); /* Register with any LEDs that have this as a default trigger */ down_read(&leds_list_lock); list_for_each_entry(led_cdev, &leds_list, node) { down_write(&led_cdev->trigger_lock); if (!led_cdev->trigger && led_cdev->default_trigger && !strcmp(led_cdev->default_trigger, trigger->name)) led_trigger_set(led_cdev, trigger); up_write(&led_cdev->trigger_lock); } up_read(&leds_list_lock); return 0; }
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);来完成的。
void led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trigger) { unsigned long flags; /* Remove any existing trigger */ if (led_cdev->trigger) { write_lock_irqsave(&led_cdev->trigger->leddev_list_lock, flags); list_del(&led_cdev->trig_list); write_unlock_irqrestore(&led_cdev->trigger->leddev_list_lock, flags); if (led_cdev->trigger->deactivate) led_cdev->trigger->deactivate(led_cdev); led_cdev->trigger = NULL; led_brightness_set(led_cdev, LED_OFF); } if (trigger) { write_lock_irqsave(&trigger->leddev_list_lock, flags); list_add_tail(&led_cdev->trig_list, &trigger->led_cdevs); write_unlock_irqrestore(&trigger->leddev_list_lock, flags); led_cdev->trigger = trigger; if (trigger->activate) trigger->activate(led_cdev); } }
这个函数做了两件事,一是移除旧的,二是添加新的。通过传递的参数,完成往trigger中添加删除led设备的功能。就是在这里trigger的activate和deactive被执行。到此trigger就算分析完了。
接着ledtrigger_timer分析trigger的两个重量级函数active和deactive
static DEVICE_ATTR(delay_on, 0644, led_delay_on_show, led_delay_on_store); static DEVICE_ATTR(delay_off, 0644, led_delay_off_show, led_delay_off_store); static void timer_trig_activate(struct led_classdev *led_cdev) { int rc; led_cdev->trigger_data = NULL; rc = device_create_file(led_cdev->dev, &dev_attr_delay_on); if (rc) return; rc = device_create_file(led_cdev->dev, &dev_attr_delay_off); if (rc) goto err_out_delayon; led_blink_set(led_cdev, &led_cdev->blink_delay_on, &led_cdev->blink_delay_off); led_cdev->trigger_data = (void *)1; return; err_out_delayon: device_remove_file(led_cdev->dev, &dev_attr_delay_on); } static void timer_trig_deactivate(struct led_classdev *led_cdev) { if (led_cdev->trigger_data) { device_remove_file(led_cdev->dev, &dev_attr_delay_on); device_remove_file(led_cdev->dev, &dev_attr_delay_off); } /* Stop blinking */ led_brightness_set(led_cdev, LED_OFF); }
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通知回调会运行于是
static int ledtrig_sleep_pm_callback(struct notifier_block *nfb, unsigned long action, void *ignored) { switch (action) { case PM_HIBERNATION_PREPARE: case PM_SUSPEND_PREPARE: led_trigger_event(ledtrig_sleep, LED_OFF); return NOTIFY_OK; case PM_POST_HIBERNATION: case PM_POST_SUSPEND: led_trigger_event(ledtrig_sleep, LED_FULL); return NOTIFY_OK; } return NOTIFY_DONE; }
led_trigger_event 被调用,还有一个函数是led_trigger_blink。
214void led_trigger_event(struct led_trigger *trigger, 215 enum led_brightness brightness) 216{ 217 struct list_head *entry; 218 219 if (!trigger) 220 return; 221 222 read_lock(&trigger->leddev_list_lock); 223 list_for_each(entry, &trigger->led_cdevs) { 224 struct led_classdev *led_cdev; 225 226 led_cdev = list_entry(entry, struct led_classdev, trig_list); 227 led_set_brightness(led_cdev, brightness); 228 } 229 read_unlock(&trigger->leddev_list_lock); 230} 231EXPORT_SYMBOL_GPL(led_trigger_event);
void led_trigger_blink(struct led_trigger *trigger, unsigned long *delay_on, unsigned long *delay_off) { struct list_head *entry; if (!trigger) return; read_lock(&trigger->leddev_list_lock); list_for_each(entry, &trigger->led_cdevs) { struct led_classdev *led_cdev; led_cdev = list_entry(entry, struct led_classdev, trig_list); led_blink_set(led_cdev, delay_on, delay_off); } read_unlock(&trigger->leddev_list_lock); }
这个函数遍历所有当前trigger拥有的led设备。并让其闪烁。
系统中可能注册若干trigger,但一个led在某一个时刻有且最多能有一个trigger。那么如何切换led的trigger呢。
前面分析led设备时,在注册led类是有个属性数组,里边有一项就是trigger属性。我们来看一下这个属性。
__ATTR(trigger, 0644, led_trigger_show, led_trigger_store),
看一下这个属性设置函数 led_trigger_store
ssize_t led_trigger_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct led_classdev *led_cdev = dev_get_drvdata(dev); char trigger_name[TRIG_NAME_MAX]; struct led_trigger *trig; size_t len; trigger_name[sizeof(trigger_name) - 1] = '\0'; strncpy(trigger_name, buf, sizeof(trigger_name) - 1); len = strlen(trigger_name); if (len && trigger_name[len - 1] == '\n') trigger_name[len - 1] = '\0'; if (!strcmp(trigger_name, "none")) { led_trigger_remove(led_cdev); return count; } down_read(&triggers_list_lock); list_for_each_entry(trig, &trigger_list, next_trig) { if (!strcmp(trigger_name, trig->name)) { down_write(&led_cdev->trigger_lock); led_trigger_set(led_cdev, trig); up_write(&led_cdev->trigger_lock); up_read(&triggers_list_lock); return count; } } up_read(&triggers_list_lock); return -EINVAL; } EXPORT_SYMBOL_GPL(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操作。
相关文章推荐
- Android屏幕、键盘背光Framework和Linux led_classdev
- Android屏幕、键盘背光Framework和Linux led_classdev
- 在Linux下开启背光Led键盘的背光
- 在Linux下开启背光Led键盘的背光
- Dev Guide/Framework Topics/Search-版本为Android 4.0 r1
- android 点击屏幕 关闭输入弹出框或键盘
- Android应用: 改变当前屏幕背光亮度
- android 输入法出现挤压屏幕、android输入键盘覆盖了屏幕控件的解决办法
- linux/Android LED deamon
- Android软键盘弹出时挤压屏幕高度解决办法
- 关于Android LCD和键盘背光亮度 .
- androidLCD和键盘 背光亮度设置
- android屏幕旋转在framework中的修改
- Android屏幕背光调整机制
- Android led_class Led驱动
- android屏幕旋转在framework中的修改。
- android 用gpio作为pwm输出控制led背光
- MTK android配置LCD背光和LED
- android 模拟器键盘控制键 以及 设置模拟器屏幕大小
- [置顶] android点击屏幕上EditText区域以外的任何地方隐藏键盘的方法