6,Linux LED驱动分析
LED子系统是linux 中的一个子系统,所以有些套路是固定的,不需要开发者去做很详细的开发。LED驱动主要包括2部分,
第一部分是内核开发者提供的,主要包括led-class.c和led-core.c。另外一部分需要厂商自己去实现。
6.1 led-class.c
按照驱动流程顺序论述,Led驱动进入/退出方法为,
subsys_initcall(leds_init); //入口方法,和module_init方法作用基本相同。
module_exit(leds_exit);//退出方法
leds_init方法如下,
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;
}
leds_class 是一个static struct class *leds_class的结构体, 这个类显示在/sys/class/ 下。
当调用 class_create(owner, name)函数的时候会在/sys/class/下创建name的目录,这里创建的是leds。
suspend变量指向led_suspend方法,也就是说当系统休眠的时候系统上层会层层通知各个设备进入睡眠状态,那么负责
这个设备的驱动则实际执行睡眠,调用led_suspend方法。
led_suspend方法如下,
static int led_suspend(struct device *dev, pm_message_t state)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
if (led_cdev->flags & LED_CORE_SUSPENDRESUME)
led_classdev_suspend(led_cdev);
return 0;
}
调用led_classdev_suspend方法,该方法如下,
void led_classdev_suspend(struct led_classdev *led_cdev)
{
led_cdev->flags |= LED_SUSPENDED;
led_cdev->brightness_set(led_cdev, 0); //led熄灭
}
EXPORT_SYMBOL_GPL(led_classdev_suspend);
因此,当系统休眠时,会将灯熄灭。
resume变量指向led_resume方法,也就是说唤醒时调用的是led_resume,恢复设备的运行状态。该方法如下,
static int led_resume(struct device *dev)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
if (led_cdev->flags & LED_CORE_SUSPENDRESUME)
led_classdev_resume(led_cdev);
return 0;
}
led_classdev_resume方法如下,
void led_classdev_resume(struct led_classdev *led_cdev)
{
led_cdev->brightness_set(led_cdev, led_cdev->brightness);//设置亮度
led_cdev->flags &= ~LED_SUSPENDED;
}
EXPORT_SYMBOL_GPL(led_classdev_resume);
因此,当系统唤醒时,将led灯设置原来的亮度。
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,
};
这个结构体在设备下创建brightness,max_brightness,trigger的属性文件,权限是0644,读文件是调用对应的show方法,
写文件时调用应的store方法。
例如,用户空间读取led亮度时就会调用led_brightness_show方法,用户空间往led里写入亮度时就会调用led_brightness_store方法。
并且led_classdev结构体定义在kernel\include\linux\leds.h中。
6.2 驱动初始化
这里的驱动初始化是厂商自己实现的,以3253芯片为例来说明。
驱动入口方法,
module_init(tricolor_bct3253_led_init);
module_exit(tricolor_bct3253_led_exit);
tricolor_bct3253_led_init方法如下,
static int __init tricolor_bct3253_led_init(void)
{
return i2c_add_driver(&tricolor_led_driver);
}
tricolor_led_driver结构体如下,
static struct i2c_driver tricolor_led_driver = {
.probe = tricolor_led_probe,
.id_table = bct3253_id,
.remove =tricolor_led_remove,
.suspend = NULL,
.resume = NULL,
.driver = {
.name = "bct3253_led_gps",
.owner = THIS_MODULE,
.of_match_table = bct3253_match_table,
},
};
因此,驱动挂载之后,最开始执行的是probe 方法,对应的tricolor_led_probe方法主要逻辑如下,
1,构造tricolor_led结构体,
struct tricolor_led_data *tricolor_led;
•••
tricolor_led = kzalloc(sizeof(struct tricolor_led_data), GFP_KERNEL);
if (tricolor_led == NULL) {
printk(KERN_ERR "tricolor_led_probe: no memory for device\n");
ret = -ENOMEM;
goto err;
}
memset(tricolor_led, 0, sizeof(struct tricolor_led_data));
2,为tricolor_led结构体赋值,
tricolor_led->client = client;
tricolor_led->leds[0].name = "blueled";
tricolor_led->leds[0].brightness_set = led_brightness_set_tricolor;
•••
brightness_set指向的是led_brightness_set_tricolor方法。
3,注册,
for (i = 0; i < 2; i++) { /* blue_gps,blue_wifi */
ret = led_classdev_register(&client->dev, &tricolor_led->leds[i]);
•••
led-class.c的led_classdev_register如下,
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_WORK(&led_cdev->set_brightness_work, set_brightness_delayed);
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
dev_dbg(parent, "Registered led device: %s\n",
led_cdev->name);
return 0;
}
EXPORT_SYMBOL_GPL(led_classdev_register);
到此, sys/class/leds路径下会有blueled路径,并且有brightness等文件。
用户空间往brightness里面写入 1时,就会调用led_brightness_set_tricolor方法。在led_brightness_set_tricolor方法中可以点亮灯,详细的实现在此就不论述了。
6.3 set brightness流程分析
用户空间调用write方法往sys/class/leds下面的路径里面写入值时,一般的内核空间里面会有对应的write方法,但是led子系统已经封装好了,
会直接回调led_brightness_store方法。
led-class.c的led_brightness_store方法如下,
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);
unsigned long state;
ssize_t ret = -EINVAL;
ret = kstrtoul(buf, 10, &state);
if (ret)
return ret;
__led_set_brightness(led_cdev, state);
return size;
}
kernel\drivers\leds下的leds.h 的__led_set_brightness如下,
static inline void __led_set_brightness(struct led_classdev *led_cdev,
enum led_brightness value)
{
if (value > led_cdev->max_brightness)
value = led_cdev->max_brightness;
led_cdev->brightness = value;
if (!(led_cdev->flags & LED_SUSPENDED))
led_cdev->brightness_set(led_cdev, value);
}
调用结构体的brightness_set指针,而brightness_set指向led_brightness_set_tricolor方法。
因此, 用户空间往brightness里面写值时就会调用led_brightness_set_tricolor方法。