本文基于 Linux Kernel 4.4.179 版本
led子系统是 linux kernel中最简单的,由此开始…
以下文字来自 {kernel}\Documentation\leds\leds-class.txt 文件的简单翻译:
主要源码位于 {kernel}/drivers/leds/ 目录下,核心源码文件如下:
led-core.c // led操作的通用逻辑代码
led-class.c // 实现led class,给用户空间操作led提供接口
led-triggers.c // 维护所有的触发器
led_classdev 结构:
struct led_classdev {
const char *name; // Led的名字
enum led_brightness brightness; //Led亮度
enum led_brightness max_brightness; //led最大亮度
int flags;
/* Set LED brightness level */
/* Must not sleep, use a workqueue if needed */
/* 用于设置亮度的函数指针 */
void (*brightness_set)(struct led_classdev *led_cdev, enum led_brightness brightness);
int (*brightness_set_sync)(struct led_classdev *led_cdev, enum led_brightness brightness);
/* Get LED brightness level */
/* 用于获取亮度的函数指针 */
enum led_brightness (*brightness_get)(struct led_classdev *led_cdev);
/* 用来设置闪烁时点亮和熄灭时长 */
int (*blink_set)(struct led_classdev *led_cdev, unsigned long *delay_on, unsigned long *delay_off);
struct device *dev;
/* 对于一些复杂的led设备,可以自定义一些class属性文件接口 */
const struct attribute_group **groups;
struct list_head node; /* LED Device list */
const char *default_trigger; /* Trigger to use */
unsigned long blink_delay_on, blink_delay_off; // 闪烁的开关时间
struct timer_list blink_timer; // 闪烁的定时器链表
int blink_brightness; // 闪烁的亮度
void (*flash_resume)(struct led_classdev *led_cdev);
struct work_struct set_brightness_work;
int delayed_set_value;
#ifdef CONFIG_LEDS_TRIGGERS
/* Protects the trigger data below */
struct rw_semaphore trigger_lock; // trigger的锁
struct led_trigger *trigger; // Led的trigger
struct list_head trig_list; // trigger链表
void *trigger_data; // trigger数据
/* true if activated - deactivate routine uses it to do cleanup */
bool activated; // trigger激活的标志
#endif
/* Ensures consistent access to the LED Flash Class device */
struct mutex led_access;
};
brightness_set / brightness_set_sync:这两个函数指针是由led设备驱动实现的,涉及具体的led设备的硬件操作。如,对于gpio控制的led,就是设置高低电平。一般情况使用 brightness_set;
blink_set:这个函数指针是由led设备驱动实现的,涉及具体的led设备的硬件操作。如果硬件不支持,可以通过软件触发器来实现闪烁功能;
node:将 led_classdev 结构体加入 leds_list 链表的 node;
default_trigger:led_classdev 使用的trigger的名字,通过这个名字在trigger_list中找到对应的默认trigger;
trig_list:将 led_classdev 结构体加入 led_trigger.led_cdevs 链表的 node,用来表示这个触发器可以支持哪些led设备;
led_trigger 结构:
struct led_trigger {
/* Trigger Properties */
const char *name;
void (*activate)(struct led_classdev *led_cdev); // 激活trigger
void (*deactivate)(struct led_classdev *led_cdev); // 关闭trigger
/* LEDs under control by this trigger (for simple triggers) */
rwlock_t leddev_list_lock;
struct list_head led_cdevs; // 该trigger支持的led设备链表
/* Link to next registered trigger */
struct list_head next_trig;
};
activate / deactivate:这两个函数指针是由各自触发器实现;
next_trig:将 led_trigger 结构体加入 trigger_list 链表的 node。
主要涉及定时器和工作队列。
主要就是创建leds class,赋值 led_groups。
static int __init leds_init(void)
{
leds_class = class_create(THIS_MODULE, "leds"); // 创建/sys/class/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;
}
static const struct attribute_group *led_groups[] = {
&led_group,
#ifdef CONFIG_LEDS_TRIGGERS
&led_trigger_group,
#endif
NULL,
};
led_groups[]:实现了led设备通用的3个属性文件:brightness、max_brightness、trigger。
leds_init() 只是创建了led class,还没有创建上面3个属性文件。这3个属性文件是在注册led设备驱动(led_classdev_register)时,为每个led设备都要创建这3个属性文件。
配置led亮灭: echo 1 > brightness
brightness_store(dev, attr, buf, size)
/* 操作全局变量 mutex_lock */
led_set_brightness(led_cdev, state);
/* 闪烁处理 */
led_cdev->delayed_set_value = brightness;
if (brightness == LED_OFF)
schedule_work(&led_cdev->set_brightness_work);
led_set_brightness_async
led_cdev->brightness_set // 调用到led设备驱动实现的操作led亮灭的函数
led_set_brightness_sync
led_cdev->brightness_set_sync
该函数有2个功能:
/* 注册 led_classdev 结构体 */
led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
led_cdev->dev = device_create_with_groups // 创建led设备的属性文件,包含了3个通用的属文件和某些led设备特有的属性文件
list_add_tail(&led_cdev->node, &leds_list); // led设备 加入led链表
led_cdev->flags |= SET_BRIGHTNESS_ASYNC; // 决定调用的是led_cdev->brightness_set -> 这个函数由led设备驱动实现
led_init_core() // 初始化工作队列和定时器,处理led的闪烁
led_trigger_set_default()
list_for_each_entry // 遍历 trigger_list, 取出trig,如果与默认一致,则设置
led_trigger_set(led_cdev, trig);
该注册函数主要完成如下操作:
/* 注册 led_trigger 结构体 */
led_trigger_register(struct led_trigger *trig)
list_for_each_entry(&trigger_list) // 遍历 trigger_list,检测名字是否重复
strcmp(_trig->name, trig->name)
list_add_tail(&trig->next_trig, &trigger_list); // trig 加入 trigger_list
list_for_each_entry(&leds_list) // 遍历led设备, 注册trig(trigger空 && default_trigger设置 && trig没有注册)
led_trigger_set(led_cdev, trig) // 给 led 注册 trig (trigger_lock)
list_add_tail(&led_cdev->trig_list, &trig->led_cdevs); // led_cdev 加入 trig 设备链表(leddev_list_lock)
led_cdev->trigger = trig;
trig->activate(led_cdev); // 注册触发器结构体中的激活函数
该注册函数主要完成如下操作:
配置触发器: echo timer > trigger
led_trigger_store()
list_for_each_entry(&trigger_list) // 遍历触发器链表
led_trigger_set(led_cdev, trig) // 给led设备注册触发器
/* 打印所有的触发器,当前设备使用的触发器用[%s] */
led_trigger_show()
led_classdev_register()
led_init_core()
/* set_brightness_delayed -> del_timer_sync -> brightness_set(0) */
INIT_WORK(&led_cdev->set_brightness_work, set_brightness_delayed);
/* led_timer_function -> brightness_set -> mod_timer */
setup_timer(&led_cdev->blink_timer, led_timer_function, (unsigned long)led_cdev);
led_trigger_register(struct led_trigger *trig)
led_trigger_set(led_cdev, trig)
trig->activate(led_cdev)
timer_trig_activate()
led_blink_set()
del_timer_sync(&led_cdev->blink_timer);
led_blink_setup()
led_set_software_blink()
led_cdev->brightness_set()
mod_timer(&led_cdev->blink_timer, jiffies + 1); // 1 jiffiess后,启动定时器函数
/* 定时器切换灯状态 */
led_timer_function()
/* 切换亮灭参数 */
brightness = led_get_brightness(led_cdev); // led_cdev->brightness
/* brightness, 当前的亮度状态:亮 -> 灭 */
if (!brightness) // 亮
if (delayed_set_value) // 更新亮度值
led_cdev->blink_brightness = delayed_set_value;
delayed_set_value = 0;
brightness = led_cdev->blink_brightness;
delay = led_cdev->blink_delay_on;
else // 灭
led_cdev->blink_brightness = brightness;
brightness = LED_OFF;
delay = led_cdev->blink_delay_off;
/* 切换灯状态 */
led_set_brightness_async(brightness)
led_cdev->brightness = brightness;
led_cdev->brightness_set
mod_timer(jiffies + msecs_to_jiffies(delay))
brightness_store(dev, attr, buf, size)
if (state == LED_OFF)
led_trigger_remove(led_cdev);
led_trigger_set(led_cdev, NULL);
led_set_brightness(led_cdev, state);
if (brightness == LED_OFF)
schedule_work(&led_cdev->set_brightness_work);
|
set_brightness_delayed()
del_timer_sync()
brightness_set(0)
gpio触发器:就是通过gpio的高低电平控制led的亮灭。
对于驱动开发者来说,一般都是编写led设备驱动,实现led亮灭控制函数,按照框架代码的要求注册到链表中即可。
#include
#include
#include
#include
#include
#include
#include
#define LED_DRV_NAME "led_gpio"
struct rk39xx_led_platdata {
int gpio;
int flags;
const char *name;
char *def_trigger;
};
struct rk39xx_gpio_led {
struct led_classdev cdev;
struct rk39xx_led_platdata *pdata;
};
static inline struct rk39xx_gpio_led *pdev_to_gpio(struct platform_device *dev)
{
return platform_get_drvdata(dev);
}
static inline struct rk39xx_gpio_led *to_gpio(struct led_classdev *pled_cdev)
{
return container_of(pled_cdev, struct rk39xx_gpio_led, cdev);
}
/* 控制led亮灭 */
static void rk39xx_led_set(struct led_classdev *pled_cdev, enum led_brightness value)
{
struct rk39xx_gpio_led *pled = to_gpio(pled_cdev);
struct rk39xx_led_platdata *pd = pled->pdata;
int state = (value ? 1 : 0) ^ (pd->flags & OF_GPIO_ACTIVE_LOW);
gpio_set_value(pd->gpio, state);
}
static int rk39xx_led_probe(struct platform_device *pdev)
{
int ret;
struct device_node *np = pdev->dev.of_node;
struct rk39xx_led_platdata *pdata;
struct rk39xx_gpio_led *pled;
int led_gpio;
enum of_gpio_flags gpio_flags;
unsigned long flags = GPIOF_OUT_INIT_LOW;
/* 平台数据,保存硬件信息 */
pdata = devm_kzalloc(&pdev->dev, sizeof(struct rk39xx_led_platdata), GFP_KERNEL);
if (!pdata)
{
return -ENOMEM;
}
pdata->name = np->name;
led_gpio = of_get_gpio_flags(np, 0, &gpio_flags);
if (!gpio_is_valid(led_gpio))
{
dev_err(&pdev->dev, "%s: %d is invalid\n", pdata->name, led_gpio);
return -ENODEV;
}
pdata->gpio = led_gpio;
pdata->flags = gpio_flags;
/* 低电平有效, 初始化为高电平 */
if (pdata->flags & OF_GPIO_ACTIVE_LOW)
{
flags = GPIOF_OUT_INIT_HIGH;
}
ret = devm_gpio_request_one(&pdev->dev, pdata->gpio, flags, pdata->name);
if (ret < 0)
{
dev_err(&pdev->dev, "requesting led gpio error: %d\n", ret);
return ret;
}
pled = devm_kzalloc(&pdev->dev, sizeof(struct rk39xx_gpio_led), GFP_KERNEL);
if (!pled)
{
return -ENOMEM;
}
/* 将led存入pdev->dev->driver_data */
platform_set_drvdata(pdev, pled);
pled->cdev.brightness_set = rk39xx_led_set;
//pled->cdev.default_trigger = pdata->def_trigger;
pled->cdev.name = pdata->name;
pled->pdata = pdata;
/* 注册LED设备 */
ret = led_classdev_register(&pdev->dev, &pled->cdev);
if (ret < 0)
{
dev_err(&pdev->dev, "led_classdev_register failed\n");
return ret;
}
return 0;
}
static int rk39xx_led_remove (struct platform_device *pdev)
{
struct rk39xx_gpio_led *pled = pdev_to_gpio(pdev);
led_classdev_unregister(&pled->cdev);
return 0;
}
static const struct of_device_id led_of_match[] = {
{ .compatible = "th,led" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, led_of_match);
static struct platform_driver rk39xx_led_driver = {
.probe = rk39xx_led_probe,
.remove = rk39xx_led_remove,
.driver = {
.name = LED_DRV_NAME,
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(led_of_match),
},
};
module_platform_driver(rk39xx_led_driver);
MODULE_AUTHOR("qulei " );
MODULE_DESCRIPTION("rk39xx Leds driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:rk39xx_led");