在我们的安卓手机中,有多种灯光,在SDK/hardware/libhardware/include/hardware文件中,可以看到如下定义
#define LIGHT_ID_BACKLIGHT "backlight"
#define LIGHT_ID_KEYBOARD "keyboard"
#define LIGHT_ID_BUTTONS "buttons"
#define LIGHT_ID_BATTERY "battery"
#define LIGHT_ID_NOTIFICATIONS "notifications"
#define LIGHT_ID_ATTENTION "attention"
#define LIGHT_ID_BLUETOOTH "bluetooth"
#define LIGHT_ID_WIFI "wifi"
这些都是android系统逻辑上支持的灯光,他到底对应开发板上的那个LED,由我们的HAL和驱动决定。灯光的使用方式有以下几种:
1.brightness:灯光亮度,可调节0~255;
2.cclor:RGB颜色设定
3.blink:闪烁,onms(亮的时间),offms(灭的时间),通过定时器实现
led_class接口分析
在linux内核中,有一个led子系统,该系统为我们提供了很多接口,在编写灯光系统驱动的时候,我们并不需要从零开始编写,在/drivers/leds目录下,存在文件led_class.c,该文件为我们提供了很多led子系统的接口,其中包括了设置brightness与blink的功能,先查看源码:
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->pm = &leds_class_dev_pm_ops;
leds_class->dev_groups = led_groups;
return 0;
}
入口函数中,在sys/class目录下创建一个leds类,往上我们可以看到一个SIMPLE_DEV_PM_OPS宏, 将led-class中的suspend中的指针,以及resume的指针初始化当系统休眠或唤醒的时候,遍历class,调用其中的pm的书suspend() 或resume()
static SIMPLE_DEV_PM_OPS(leds_class_dev_pm_ops, led_suspend, led_resume);
==>
static const struct dev_pm_ops leds_class_dev_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(led_suspend, led_resume)
}
==>
static const struct dev_pm_ops leds_class_dev_pm_ops = {
.suspend = led_suspend,
.resume = led_resume,
}
然后还会在sys/class/leds目录具体的led下创建3个子节点brightness、max_brightness、trigge,
static DEVICE_ATTR_RW(brightness); // 可读可写
static DEVICE_ATTR_RO(max_brightness); // 只读
static DEVICE_ATTR(trigger, 0644, led_trigger_show, led_trigger_store); // 可读
我们可以用其创建的子节点对LED进行控制,如
读 cat /sys/class/leds/xxx/brightness
写 echo 255 > /sys/class/leds/xxx/brightness
我们就能查询或者控制led的亮度了,那么为什么呢?
static ssize_t brightness_show(struct device *dev,
struct device_attribute *attr, char *buf)
static ssize_t brightness_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
static DEVICE_ATTR_RW(brightness);
当往brightness文件写入时,会调用 brightness_store函数,当从brightness读出时,会调用brightness_show函数,我们brightness_store函数中会调用led_set_brightness(led_cdev, state)函数,然后根据led_cdev->flags 判断之后,如果为SET_BRIGHTNESS_ASYNC则最终调用led_cdev->brightness_set(led_cdev, value),如果为SET_BRIGHTNESS_SYNC则最终调用 led_cdev->brightness_set_sync(led_cdev,led_cdev->brightness);从这里可以看出来,我们编写驱动程序的时候,必须根据led_cdev->flags实现一个led_cdev->brightness_set或者brightness_set_sync函数。
编写led驱动程序根据前面的分析,如果我们通过led子系统提供的框架编写驱动程序,那么我们至少需要实现一下几点,
1.分配一个struct led_classdev led_cdev,
2.设置
led_cdev->max_brightness最大亮度值。
led_cdev->brightness
led_cdev->flgs(SET_BRIGHTNESS_SYNC,SET_BRIGHTNESS_ASYNC)
led_cdev->brightness_set_sync或者led_cdev->brightness_set函数。
3.注册:led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
编写led_gec3399_drv.c具体代码如下:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
struct led_desc {
int gpio;
char *name;
};
struct led_classdev_gec3399 {
struct led_classdev cdev;
int gpio;
};
static struct led_desc led_gpios[] = {
{(32*0 + 8*1 + 4),"led1"},
{(32*0 + 8*1 + 0),"led2"},
};
static struct led_classdev_gec3399 *led_devs;
static void brightness_set_gec3399(struct led_classdev *led_cdev,enum led_brightness brightness)
{
struct led_classdev_gec3399 *dev = (struct led_classdev_gec3399*)led_cdev;
if(brightness != LED_OFF)
gpio_set_value(dev->gpio,0);
else
gpio_set_value(dev->gpio,1);
led_cdev->brightness = brightness;
}
static int __init gec3399_leds_init(void)
{
int i=0;
int ret = 0;
/*1.alloc led_classdev*/
led_devs = (void*)kzalloc(sizeof(struct led_classdev_gec3399)*sizeof(led_gpios)/sizeof(led_gpios[0]),GFP_KERNEL);
if (!led_devs){
printk("%s %d NO memory for device\n",__FUNCTION__,__LINE__);
return -ENOMEM;
}
for(i=0; i= 0)
{
led_classdev_unregister(&led_devs[i].cdev);
i--;
}
kfree(led_devs);
return -EIO;
}
}
return 0;
}
static void __exit gec3399_leds_exit(void)
{
int i;
for(i=0; i
LED Trigger support --->
LED Timer Trigger
打开该配置是为了后续实现闪烁功能
4.在kernel目录下执行
make ARCH=arm64 rk3399-sapphire-excavator-edp.img -j12
等待编译完成之后,执行
source build/envsetup.sh
lunch rk3399_all-userdebug
make bootimage -j3
编译完成之后,把新生成的out/target/product/rk3399/boot.img烧写到开发板中, 在sys/class/leds下会存在两个目录,分别为led1,与led2。然后我们操控目录下的文件,就可以实现对LED的控制了
命令操控 led控制我们cd到 /sys/class/leds/led1目录下可以看到: brightness max_brightness power subsystem trigger uevent 我们执行 echo 255 > /sys/class/leds/led1/brightness 即可点亮LED1 echo 0 > /sys/class/leds/led1/brightness 即可熄灭LED1 执行cat trigger可以看到: [none] rc-feedback test_ac-online test_battery-charging-or-full test_battery-charging test_battery-full test_battery-charging-blink-full-solid test_usb-online mmc0 mmc1 timer backlight default-on rfkill0 mmc2 其中[]代表现在闪烁的模式,none代表还没有设置 执行 echo > timer /sys/class/leds/led1/trigger 即可设置led以定时器模式闪烁 此时我们再次查看/sys/class/leds/led1: brightness delay_off delay_on max_brightness power subsystem trigger uevent 可以看到多了delay_on,delay_off文件,我们可以通过echo往里面写值,控制灯亮和灯灭的时间 同样我们可以往max_brightness写入或者读取,去获取或者设置亮度值 echo 255 > /sys/class/leds/led1/brightness
backlight和上面相似,在sys/class目录下存在backlight文件夹,我们cd到该目录下可以看到如下文件:
actual_brightness brightness max_brightness
subsystem uevent bl_power device power type
和前面一样,我们可以通过 echo xxx > /sys/class/backlight/backlight/brightness可以改变屏幕的亮度
trigger分析为什么我们通过echo > timer /sys/class/leds/led1/trigger就能控制led进行闪烁呢?在前面我们提到过led_class.c文件中,存在宏static DEVICE_ATTR(trigger, 0644, led_trigger_show, led_trigger_store),他会在/sys/classled1或led2目录下(我们注册的led)创建文件trigger,当我们往该文件写入时,会调用led_trigger_store,读取时会调用led_trigger_show。 在led_trigger.c文件中,我们可以看到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;
int ret = count;
mutex_lock(&led_cdev->led_access);
if (led_sysfs_is_disabled(led_cdev)) {
ret = -EBUSY;
goto unlock;
}
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);
goto unlock;
}
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);
goto unlock;
}
}
up_read(&triggers_list_lock);
unlock:
mutex_unlock(&led_cdev->led_access);
return ret;
调用strncpy(trigger_name, buf, sizeof(trigger_name) - 1);从buf中取出trigger,在之前的实验中我们设置为timer。然后执行list_for_each_entry(trig, &trigger_list, next_trig) ,即用取出的trigger与trigger_list中的每一项进行匹配,如果能匹配成功,则执行led_trigger_set(led_cdev, trig),其中有代码如下:
led_cdev->trigger = trig;
if (trig->activate)
trig->activate(led_cdev);
如果存在trig->activate,则会调用该函数,下面我们查看ledtrig-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);
}
可知其设定了.activate = timer_trig_activate,其 timer_trig_activate函数如下:
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->activated = true;
return;
err_out_delayon:
device_remove_file(led_cdev->dev, &dev_attr_delay_on);
}
可以知道其创建了两个文件即前面出现的delay_on与delay_off。并设置这两个文件的读写函数,并执行led_blink_set(led_cdev, &led_cdev->blink_delay_on,&led_cdev->blink_delay_off)使LED进行闪烁,led_blink_set中调用了led_set_software_blink,其led_set_software_blink代码如下:
static void led_set_software_blink(struct led_classdev *led_cdev,
unsigned long delay_on,
unsigned long delay_off)
{
int current_brightness;
current_brightness = led_get_brightness(led_cdev);
if (current_brightness)
led_cdev->blink_brightness = current_brightness;
if (!led_cdev->blink_brightness)
led_cdev->blink_brightness = led_cdev->max_brightness;
led_cdev->blink_delay_on = delay_on;
led_cdev->blink_delay_off = delay_off;
/* never on - just set to off */
if (!delay_on) {
led_set_brightness_async(led_cdev, LED_OFF);
return;
}
/* never off - just set to brightness */
if (!delay_off) {
led_set_brightness_async(led_cdev, led_cdev->blink_brightness);
return;
}
mod_timer(&led_cdev->blink_timer, jiffies + 1);
}
可以知道,最终使用mod_timer(&led_cdev->blink_timer, jiffies + 1);实现了闪烁功能。
小节结语该小节,主要分析了led子系统提供的接口,然后利用该接口实现我们的驱动程序,以及如何在命令行控制LED,最后在对led子系统闪烁功能进行了讲解