Android屏幕、键盘背光Framework和Linux led_classdev

应用设计
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。代码如下:

[cpp]  view plain copy
  1. In mipi_samsung.c  
  2. 294static void mipi_samsung_set_backlight(struct msm_fb_data_type *mfd)  
  3. 295{  
  4. 302 if ((mipi_samsung_pdata->enable_wled_bl_ctrl)  
  5. 303     && (wled_trigger_initialized)) {  
  6. 304     led_trigger_event(bkl_led_trigger, mfd->bl_level);  
  7. 305     return;  
  8. 306 }  
  9. 307}  

与bkl_led_trigger关联的led就是屏幕背光wled。屏幕背光wled是做为pmic leds数组的一个元素。如:

[cpp]  view plain copy
  1. 283static struct led_info pm8038_led_info[] = {  
  2. 284 [0] = {  
  3. 285  .name   = "wled",  
  4. 286  .default_trigger = "bkl_trigger",  
  5. 287 },  
  6. 288 [1] = {  
  7. 289  .name   = "led:rgb_red",  
  8. 290  //.default_trigger = "battery-charging",  
  9. 291 },  
  10. 292 [2] = {  
  11. 293  .name   = "led:rgb_green",  
  12. 294 },  
  13. 295 [3] = {  
  14. 296  .name   = "led:rgb_blue",  
  15. 297 },  
  16. 298};  

可以看到wled与"bkl_trigger"相关联,使用bkl_trigger可以操作该led。

该数组中的leds使用同一个驱动:name = PM8XXX_LEDS_DEV_NAME。

In kernel/drivers/leds/leds-pm8xxx.c

[cpp]  view plain copy
  1. #define PM8XXX_LEDS_DEV_NAME    "pm8xxx-led"  
  2. 2283static struct platform_driver pm8xxx_led_driver = {  
  3. 2284    .probe      = pm8xxx_led_probe,  
  4. 2285    .remove     = __devexit_p(pm8xxx_led_remove),  
  5. 2286    .driver     = {  
  6. 2287        .name   = PM8XXX_LEDS_DEV_NAME,  
  7. 2288        .owner  = THIS_MODULE,  
  8. 2289    },  
  9. 2290};  

pm8xxx_led_probe会对pm8038_led_info数组中的每个led使用设置led_classdev字段,并且初始化work item,然后使用led_classdev_register向系统注册每个led设备。

[cpp]  view plain copy
  1. 2197        INIT_WORK(&led_dat->work, pm8xxx_led_work);  
  2. 2198        INIT_WORK(&led_dat->modework, pm8xxx_mode_work);  
  3. 2199        INIT_WORK(&led_dat->testwork, pm8xxx_test_work);  

probe过程中每个led的brightness_set字段设置为pm8xxx_led_set。

[cpp]  view plain copy
  1. 1790static void pm8xxx_led_set(struct led_classdev *led_cdev,  
  2. 1791    enum led_brightness value)  
  3. 1792{  
  4. 1793    struct  pm8xxx_led_data *led;  
  5. 1794  
  6. 1795    led = container_of(led_cdev, struct pm8xxx_led_data, cdev);  
  7. 1796  
  8. 1797    if (value < LED_OFF || value > led->cdev.max_brightness) {  
  9. 1798        dev_err(led->cdev.dev, "Invalid brightness value exceeds");  
  10. 1799        return;  
  11. 1800    }  
  12. 1801  
  13. 1802    led->cdev.brightness = value;  
  14. 1803    schedule_work(&led->work);  
  15. 1804}  

可以看出,是做为work来操作。

[cpp]  view plain copy
  1. 1730static void pm8xxx_led_work(struct work_struct *work)  
  2. 1731{  
  3. 1732    int rc;  
  4. 1733  
  5. 1734    struct pm8xxx_led_data *led = container_of(work,  
  6. 1735                     struct pm8xxx_led_data, work);  
  7. 1736  
  8. 1737    if (led->pwm_dev == NULL) {  
  9. 1738        __pm8xxx_led_work(led, led->cdev.brightness);  
  10. 1739    } else {  
  11. 1740        rc = pm8xxx_led_pwm_work(led);  
  12. 1741        if (rc)  
  13. 1742            pr_err("could not configure PWM mode for LED:%d\n",  
  14. 1743                                led->id);  
  15. 1744    }  
  16. 1745}  

对PM8XXX_ID_WLED,是使用__pm8xxx_led_work

[cpp]  view plain copy
  1. 1692static void __pm8xxx_led_work(struct pm8xxx_led_data *led,  
  2. 1693                    enum led_brightness level)  
  3. 1694{  
  4. 1695    int rc;  
  5. 1696  
  6. 1697    mutex_lock(&led->lock);  
  7. 1698  
  8. 1699    switch (led->id) {  
  9. 1700    case PM8XXX_ID_LED_KB_LIGHT:  
  10. 1701        led_kp_set(led, level);  
  11. 1702        break;  
  12. 1703    case PM8XXX_ID_LED_0:  
  13. 1704    case PM8XXX_ID_LED_1:  
  14. 1705    case PM8XXX_ID_LED_2:  
  15. 1706        led_lc_set(led, level);  
  16. 1707        break;  
  17. 1708    case PM8XXX_ID_FLASH_LED_0:  
  18. 1709    case PM8XXX_ID_FLASH_LED_1:  
  19. 1710        led_flash_set(led, level);  
  20. 1711        break;  
  21. 1712    case PM8XXX_ID_WLED:  
  22. 1713        rc = led_wled_set(led, level);  
  23. 1714        if (rc < 0)  
  24. 1715            pr_err("wled brightness set failed %d\n", rc);  
  25. 1716        break;  
  26. 1717    case PM8XXX_ID_RGB_LED_RED:  
  27. 1718    case PM8XXX_ID_RGB_LED_GREEN:  
  28. 1719    case PM8XXX_ID_RGB_LED_BLUE:  
  29. 1720        led_rgb_set(led, level);  
  30. 1721        break;  
  31. 1722    default:  
  32. 1723        dev_err(led->cdev.dev, "unknown led id %d", led->id);  
  33. 1724        break;  
  34. 1725    }  
  35. 1726  
  36. 1727    mutex_unlock(&led->lock);  
  37. 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的亮度。

[cpp]  view plain copy
  1. 1028int pmic_set_led_intensity(enum ledtype type, int level)  
  2. 1029{  
  3. 1030    return pmic_rpc_set_only(type, level, 0, 0, 2, SET_LED_INTENSITY_PROC);  
  4. 1031}  
[cpp]  view plain copy
  1. 383/** 
  2. 384 * pmic_rpc_set_only() - set arguments and no get 
  3. 385 * @data0:   first argumrnt 
  4. 386 * @data1:   second argument 
  5. 387 * @data2:   third argument 
  6. 388 * @data3:   fourth argument 
  7. 389 * @num: number of argument 
  8. 390 * @proc:    command/request id 
  9. 391 * 
  10. 392 * This function covers case a, b, and c 
  11. 393 */  
  12. 394static int pmic_rpc_set_only(uint data0, uint data1, uint data2, uint data3,  
  13. 395     int num, int proc)  
  14. 396{  
  15. 397 struct pmic_ctrl *pm = &pmic_ctrl;  
  16. 398 struct pmic_buf *tp;  
  17. 399 struct pmic_buf *rp;  
  18. 400 int stat;  
  19. 401  
  20. 402  
  21. 403 if (mutex_lock_interruptible(&pmic_mtx))  
  22. 404     return -ERESTARTSYS;  
  23. 405  
  24. 406 if (pm->inited <= 0) {  
  25. 407     stat = pmic_buf_init();  
  26. 408     if (stat < 0) {  
  27. 409         mutex_unlock(&pmic_mtx);  
  28. 410         return stat;  
  29. 411     }  
  30. 412 }  
  31. 413  
  32. 414 tp = &pm->tbuf;  
  33. 415 rp = &pm->rbuf;  
  34. 416  
  35. 417 pmic_buf_reset(tp);  
  36. 418 pmic_buf_reserve(tp, sizeof(struct rpc_request_hdr));  
  37. 419 pmic_buf_reset(rp);  
  38. 420  
  39. 421 if (num > 0)  
  40. 422     pmic_put_tx_data(tp, data0);  
  41. 423  
  42. 424 if (num > 1)  
  43. 425     pmic_put_tx_data(tp, data1);  
  44. 426  
  45. 427 if (num > 2)  
  46. 428     pmic_put_tx_data(tp, data2);  
  47. 429  
  48. 430 if (num > 3)  
  49. 431     pmic_put_tx_data(tp, data3);  
  50. 432  
  51. 433 stat = pmic_rpc_req_reply(tp, rp, proc);  
  52. 434 if (stat < 0) {  
  53. 435     mutex_unlock(&pmic_mtx);  
  54. 436     return stat;  
  55. 437 }  
  56. 438  
  57. 439 pmic_pull_rx_data(rp, &stat);   /* result from server */  
  58. 440  
  59. 441 mutex_unlock(&pmic_mtx);  
  60. 442  
  61. 443 return modem_to_linux_err(stat);  
  62. 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

[cpp]  view plain copy
  1. struct led_classdev {  
  2.      const char        *name; //led 设备的名字,注册这个设备后会出现在sys/class/leds/下  
  3.      int             brightness;//当前led灯的亮度,最大值为下面的变量,为0时代表led灭  
  4.      int             max_brightness;//最大亮度,系统定义了一个enum 用于表明这个变量的范围  
  5.      int             flags;//这个标识的高16bit是控制信息,低16bit是状态信息。主要用来控制suspend  
  6.  //具体的取值见下面的定义  
  7.      /* Lower 16 bits reflect status */  
  8.  #define LED_SUSPENDED        (1 << 0)  
  9.      /* Upper 16 bits reflect control information */  
  10.  #define LED_CORE_SUSPENDRESUME    (1 << 16)  
  11.    
  12.      /* Set LED brightness level */  
  13.      /* Must not sleep, use a workqueue if needed */  
  14.      void        (*brightness_set)(struct led_classdev *led_cdev,  
  15.                        enum led_brightness brightness);//设置led亮度的函数指针,需要driver开发者  
  16.      //根据硬件特性实现。  
  17.      /* Get LED brightness level */  
  18.      enum led_brightness (*brightness_get)(struct led_classdev *led_cdev);  
  19.    
  20.      /* 
  21.       * Activate hardware accelerated blink, delays are in milliseconds 
  22.       * and if both are zero then a sensible default should be chosen. 
  23.       * The call should adjust the timings in that case and if it can't 
  24.       * match the values specified exactly. 
  25.       * Deactivate blinking again when the brightness is set to a fixed 
  26.       * value via the brightness_set() callback. 
  27.       */  
  28.      int        (*blink_set)(struct led_classdev *led_cdev,  
  29.                       unsigned long *delay_on,  
  30.                       unsigned long *delay_off);//硬件闪烁函数,在实现闪烁时,如果这个函数drive实  
  31.    // 现了,那么会优先调用他来硬件加速,否则core 会调用一个软件用定时器模拟的闪烁。在实际的应用中,软件模拟  
  32.    //的闪烁是不可取的。因为他要求cpu要一直工作,这对于像手机这样的电池供电的设备是不可接受的。  
  33.    
  34.      struct device        *dev;  // 属于linux 设备模型的东西,每一个设备都有这么一个device  
  35.     struct list_head     node;            /* LED Device list */     //拥有这个struct list_head 结构的设备可以用它把本身挂到一个属于他的链表中,方便core管理,其实大部分的注册函数都几乎会把向他注册的东东挂到一个他关心的链表中。  
  36.      const char        *default_trigger;    /* Trigger to use */  
  37.     //以下都是trigger 相关的东西,在分析trigger是详细说明  
  38.      unsigned long         blink_delay_on, blink_delay_off;  
  39.      struct timer_list     blink_timer;  
  40.      int             blink_brightness;  
  41.    
  42.  #ifdef CONFIG_LEDS_TRIGGERS  
  43.      /* Protects the trigger data below */  
  44.      struct rw_semaphore     trigger_lock;  
  45.    
  46.      struct led_trigger    *trigger;  
  47.      struct list_head     trig_list;  
  48.      void            *trigger_data;  
  49.  #endif  
  50.  };  

这个结构抽象了一个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

[cpp]  view plain copy
  1. #include <linux/kernel.h>  
  2.  #include <linux/list.h>  
  3.  #include <linux/module.h>  
  4.  #include <linux/rwsem.h>  
  5.  #include <linux/leds.h>  
  6.  #include "leds.h"  
  7.    
  8.  DECLARE_RWSEM(leds_list_lock);  
  9.  EXPORT_SYMBOL_GPL(leds_list_lock);  
  10.    
  11.  LIST_HEAD(leds_list);  
  12.  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 宏定义的一个函数

[cpp]  view plain copy
  1. static int __init leds_init(void)  
  2.  {  
  3.      leds_class = class_create(THIS_MODULE, "leds");  
  4.      if (IS_ERR(leds_class))  
  5.          return PTR_ERR(leds_class);  
  6.      leds_class->suspend = led_suspend;  
  7.      leds_class->resume = led_resume;  
  8.      leds_class->dev_attrs = led_class_attrs;  
  9.      return 0;  
  10.  }  
  11.    
  12.  static void __exit leds_exit(void)  
  13.  {  
  14.      class_destroy(leds_class);  
  15.  }  
  16.    
  17.  subsys_initcall(leds_init);  
  18.  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的属性等类的特性。所以现在有必要看一下这个重量级的注册函数了。

[cpp]  view plain copy
  1. /** 
  2.   * led_classdev_register - register a new object of led_classdev class. 
  3.   * @parent: The device to register. 
  4.   * @led_cdev: the led_classdev structure for this device. 
  5.   */  
  6.  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);  
  10.      if (IS_ERR(led_cdev->dev))  
  11.          return PTR_ERR(led_cdev->dev);  
  12.    
  13.  #ifdef CONFIG_LEDS_TRIGGERS  
  14.      init_rwsem(&led_cdev->trigger_lock);  
  15.  #endif  
  16.      /* add to the list of leds */  
  17.      down_write(&leds_list_lock);  
  18.      list_add_tail(&led_cdev->node, &leds_list);  
  19.      up_write(&leds_list_lock);  
  20.    
  21.      if (!led_cdev->max_brightness)  
  22.          led_cdev->max_brightness = LED_FULL;  
  23.    
  24.      led_update_brightness(led_cdev);  
  25.    
  26.      init_timer(&led_cdev->blink_timer);  
  27.      led_cdev->blink_timer.function = led_timer_function;  
  28.      led_cdev->blink_timer.data = (unsigned long)led_cdev;  
  29.    
  30.  #ifdef CONFIG_LEDS_TRIGGERS  
  31.      led_trigger_set_default(led_cdev);  
  32.  #endif  
  33.    
  34.      printk(KERN_DEBUG "Registered led device: %s\n",  
  35.              led_cdev->name);  
  36.    
  37.      return 0;  
  38.  }  
  39.  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;

我们分析一下这几个函数或数据。

[cpp]  view plain copy
  1. static struct device_attribute led_class_attrs[] = {  
  2.     __ATTR(brightness, 0644, led_brightness_show, led_brightness_store),  
  3.     __ATTR(max_brightness, 0644, led_max_brightness_show,  
  4.             led_max_brightness_store),  
  5. #ifdef CONFIG_LEDS_TRIGGERS  
  6.     __ATTR(trigger, 0644, led_trigger_show, led_trigger_store),  
  7. #endif  
  8.     __ATTR_NULL,  
  9. };  

这个属性结构定义了这个ledclass设备数据有的属性,亮度,最大亮度,trigger。并定义了设置属性和读取属性的函数吗,及权限。那么当我们已这个ledclass为基础创建的设备就拥有这些属性,就会在生成相应的属性文件。

[cpp]  view plain copy
  1. static ssize_t led_brightness_store(struct device *dev,  
  2.          struct device_attribute *attr, const char *buf, size_t size)  
  3.  {  
  4.      struct led_classdev *led_cdev = dev_get_drvdata(dev);  
  5.      ssize_t ret = -EINVAL;  
  6.      char *after;  
  7.      unsigned long state = simple_strtoul(buf, &after, 10);  
  8.      size_t count = after - buf;  
  9.    
  10.      if (isspace(*after))  
  11.          count++;  
  12.    
  13.      if (count == size) {  
  14.          ret = count;  
  15.    
  16.          if (state == LED_OFF)  
  17.              led_trigger_remove(led_cdev);  
  18.          led_set_brightness(led_cdev, state);  
  19.      }  
  20.    
  21.      return ret;  
  22.  }  

这时属性的设置函数,他的第一个结构体就是拥有这个属性的设备的指针。第四行,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};

[cpp]  view plain copy
  1. #ifdef CONFIG_LEDS_TRIGGERS  
  2.      /* Protects the trigger data below */  
  3.      struct rw_semaphore     trigger_lock;  
  4.    
  5.      struct led_trigger    *trigger;  
  6.      struct list_head     trig_list;  
  7.      void            *trigger_data;  
  8.  #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这个人物了

[cpp]  view plain copy
  1. struct led_trigger {  
  2.      /* Trigger Properties */  
  3.      const char     *name;  
  4.      void        (*activate)(struct led_classdev *led_cdev);  
  5.      void        (*deactivate)(struct led_classdev *led_cdev);  
  6.    
  7.      /* LEDs under control by this trigger (for simple triggers) */  
  8.      rwlock_t      leddev_list_lock;  
  9.      struct list_head  led_cdevs;  
  10.    
  11.      /* Link to next registered trigger */  
  12.      struct list_head  next_trig;  
  13.  };  

第一个成员是这个trigger的名字,不超过50个字符。

仅接着是trigger激活和取消的函数。从函数参数可以推测,这两个函数是针对特定的led的。

第九行是一个链表头,可以推测他把所有属于他trigge的led通过trig_list都链接起来。第12行应是自身的一个链表,用它把所有向ledtrigger注册的trigger链接起来。

接着我们从具体的一个trigger(定时器触发)出发,一步步的理清思路。

ledtrigger_timer.c

[cpp]  view plain copy
  1. static struct led_trigger timer_led_trigger = {  
  2.      .name     = "timer",  
  3.      .activate = timer_trig_activate,  
  4.      .deactivate = timer_trig_deactivate,  
  5.  };  
  6.    
  7.  static int __init timer_trig_init(void)  
  8.  {  
  9.      return led_trigger_register(&timer_led_trigger);  
  10.  }  
  11.    
  12.  static void __exit timer_trig_exit(void)  
  13.  {  
  14.      led_trigger_unregister(&timer_led_trigger);  
  15.  }  
  16.    
  17.  module_init(timer_trig_init);  
  18.  module_exit(timer_trig_exit);  

这段代码也清楚的说明了如何取写一个trigger。首先定义一个trigger。然后注册就可以了。重点是实现activate和deactivate函数。

这两个函数如何实现,稍后分析,先来看一下注册函数。

[cpp]  view plain copy
  1. int led_trigger_register(struct led_trigger *trigger)  
  2.  {  
  3.      struct led_classdev *led_cdev;  
  4.      struct led_trigger *trig;  
  5.    
  6.      rwlock_init(&trigger->leddev_list_lock);  
  7.      INIT_LIST_HEAD(&trigger->led_cdevs);  
  8.    
  9.      down_write(&triggers_list_lock);  
  10.      /* Make sure the trigger's name isn't already in use */  
  11.      list_for_each_entry(trig, &trigger_list, next_trig) {  
  12.          if (!strcmp(trig->name, trigger->name)) {  
  13.              up_write(&triggers_list_lock);  
  14.              return -EEXIST;  
  15.          }  
  16.      }  
  17.      /* Add to the list of led triggers */  
  18.      list_add_tail(&trigger->next_trig, &trigger_list);  
  19.      up_write(&triggers_list_lock);  
  20.    
  21.      /* Register with any LEDs that have this as a default trigger */  
  22.      down_read(&leds_list_lock);  
  23.      list_for_each_entry(led_cdev, &leds_list, node) {  
  24.          down_write(&led_cdev->trigger_lock);  
  25.          if (!led_cdev->trigger && led_cdev->default_trigger &&  
  26.                  !strcmp(led_cdev->default_trigger, trigger->name))  
  27.              led_trigger_set(led_cdev, trigger);  
  28.          up_write(&led_cdev->trigger_lock);  
  29.      }  
  30.      up_read(&leds_list_lock);  
  31.    
  32.      return 0;  
  33.  }  

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);来完成的。

[cpp]  view plain copy
  1. void led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trigger)  
  2.  {  
  3.      unsigned long flags;  
  4.    
  5.      /* Remove any existing trigger */  
  6.      if (led_cdev->trigger) {  
  7.          write_lock_irqsave(&led_cdev->trigger->leddev_list_lock, flags);  
  8.          list_del(&led_cdev->trig_list);  
  9.          write_unlock_irqrestore(&led_cdev->trigger->leddev_list_lock,  
  10.              flags);  
  11.          if (led_cdev->trigger->deactivate)  
  12.              led_cdev->trigger->deactivate(led_cdev);  
  13.          led_cdev->trigger = NULL;  
  14.          led_brightness_set(led_cdev, LED_OFF);  
  15.      }  
  16.      if (trigger) {  
  17.          write_lock_irqsave(&trigger->leddev_list_lock, flags);  
  18.          list_add_tail(&led_cdev->trig_list, &trigger->led_cdevs);  
  19.          write_unlock_irqrestore(&trigger->leddev_list_lock, flags);  
  20.          led_cdev->trigger = trigger;  
  21.          if (trigger->activate)  
  22.              trigger->activate(led_cdev);  
  23.      }  
  24.  }  

这个函数做了两件事,一是移除旧的,二是添加新的。通过传递的参数,完成往trigger中添加删除led设备的功能。就是在这里trigger的activate和deactive被执行。到此trigger就算分析完了。

接着ledtrigger_timer分析trigger的两个重量级函数active和deactive

[cpp]  view plain copy
  1. static DEVICE_ATTR(delay_on, 0644, led_delay_on_show, led_delay_on_store);  
  2.  static DEVICE_ATTR(delay_off, 0644, led_delay_off_show, led_delay_off_store);  
  3.    
  4.  static void timer_trig_activate(struct led_classdev *led_cdev)  
  5.  {  
  6.      int rc;  
  7.    
  8.      led_cdev->trigger_data = NULL;  
  9.    
  10.      rc = device_create_file(led_cdev->dev, &dev_attr_delay_on);  
  11.      if (rc)  
  12.          return;  
  13.      rc = device_create_file(led_cdev->dev, &dev_attr_delay_off);  
  14.      if (rc)  
  15.          goto err_out_delayon;  
  16.    
  17.      led_blink_set(led_cdev, &led_cdev->blink_delay_on,  
  18.                &led_cdev->blink_delay_off);  
  19.    
  20.      led_cdev->trigger_data = (void *)1;  
  21.    
  22.      return;  
  23.    
  24.  err_out_delayon:  
  25.      device_remove_file(led_cdev->dev, &dev_attr_delay_on);  
  26.  }  
  27.    
  28.  static void timer_trig_deactivate(struct led_classdev *led_cdev)  
  29.  {  
  30.      if (led_cdev->trigger_data) {  
  31.          device_remove_file(led_cdev->dev, &dev_attr_delay_on);  
  32.          device_remove_file(led_cdev->dev, &dev_attr_delay_off);  
  33.      }  
  34.    
  35.      /* Stop blinking */  
  36.      led_brightness_set(led_cdev, LED_OFF);  
  37.  }  

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通知回调会运行于是

[cpp]  view plain copy
  1. static int ledtrig_sleep_pm_callback(struct notifier_block *nfb,  
  2.                      unsigned long action,  
  3.                      void *ignored)  
  4.  {  
  5.      switch (action) {  
  6.      case PM_HIBERNATION_PREPARE:  
  7.      case PM_SUSPEND_PREPARE:  
  8.          led_trigger_event(ledtrig_sleep, LED_OFF);  
  9.          return NOTIFY_OK;  
  10.      case PM_POST_HIBERNATION:  
  11.      case PM_POST_SUSPEND:  
  12.          led_trigger_event(ledtrig_sleep, LED_FULL);  
  13.          return NOTIFY_OK;  
  14.      }  
  15.    
  16.      return NOTIFY_DONE;  
  17.  }  

led_trigger_event 被调用,还有一个函数是led_trigger_blink。

[cpp]  view plain copy
  1. 214void led_trigger_event(struct led_trigger *trigger,  
  2. 215         enum led_brightness brightness)  
  3. 216{  
  4. 217 struct list_head *entry;  
  5. 218  
  6. 219 if (!trigger)  
  7. 220     return;  
  8. 221  
  9. 222 read_lock(&trigger->leddev_list_lock);  
  10. 223 list_for_each(entry, &trigger->led_cdevs) {  
  11. 224     struct led_classdev *led_cdev;  
  12. 225  
  13. 226     led_cdev = list_entry(entry, struct led_classdev, trig_list);  
  14. 227     led_set_brightness(led_cdev, brightness);  
  15. 228 }  
  16. 229 read_unlock(&trigger->leddev_list_lock);  
  17. 230}  
  18. 231EXPORT_SYMBOL_GPL(led_trigger_event);  

 

[cpp]  view plain copy
  1. void led_trigger_blink(struct led_trigger *trigger,  
  2.                 unsigned long *delay_on,  
  3.                 unsigned long *delay_off)  
  4.  {  
  5.      struct list_head *entry;  
  6.    
  7.      if (!trigger)  
  8.          return;  
  9.    
  10.      read_lock(&trigger->leddev_list_lock);  
  11.      list_for_each(entry, &trigger->led_cdevs) {  
  12.          struct led_classdev *led_cdev;  
  13.    
  14.          led_cdev = list_entry(entry, struct led_classdev, trig_list);  
  15.          led_blink_set(led_cdev, delay_on, delay_off);  
  16.      }  
  17.      read_unlock(&trigger->leddev_list_lock);  
  18.  }  

 这个函数遍历所有当前trigger拥有的led设备。并让其闪烁。

系统中可能注册若干trigger,但一个led在某一个时刻有且最多能有一个trigger。那么如何切换led的trigger呢。

前面分析led设备时,在注册led类是有个属性数组,里边有一项就是trigger属性。我们来看一下这个属性。

__ATTR(trigger, 0644, led_trigger_show, led_trigger_store),

看一下这个属性设置函数 led_trigger_store

[cpp]  view plain copy
  1. ssize_t led_trigger_store(struct device *dev, struct device_attribute *attr,  
  2.          const char *buf, size_t count)  
  3.  {  
  4.      struct led_classdev *led_cdev = dev_get_drvdata(dev);  
  5.      char trigger_name[TRIG_NAME_MAX];  
  6.      struct led_trigger *trig;  
  7.      size_t len;  
  8.    
  9.      trigger_name[sizeof(trigger_name) - 1] = '\0';  
  10.      strncpy(trigger_name, buf, sizeof(trigger_name) - 1);  
  11.      len = strlen(trigger_name);  
  12.    
  13.      if (len && trigger_name[len - 1] == '\n')  
  14.          trigger_name[len - 1] = '\0';  
  15.    
  16.      if (!strcmp(trigger_name, "none")) {  
  17.          led_trigger_remove(led_cdev);  
  18.          return count;  
  19.      }  
  20.    
  21.      down_read(&triggers_list_lock);  
  22.      list_for_each_entry(trig, &trigger_list, next_trig) {  
  23.          if (!strcmp(trigger_name, trig->name)) {  
  24.              down_write(&led_cdev->trigger_lock);  
  25.              led_trigger_set(led_cdev, trig);  
  26.              up_write(&led_cdev->trigger_lock);  
  27.    
  28.              up_read(&triggers_list_lock);  
  29.              return count;  
  30.          }  
  31.      }  
  32.      up_read(&triggers_list_lock);  
  33.    
  34.      return -EINVAL;  
  35.  }  
  36.  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操作

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值