led子系统实际是linux编写好的一个led驱动框架.对于驱动开发者来说只需要按照规定填充结构体然后调用api注册驱动,就可以完成一个led驱动的编写,并且这样写的led驱动为上层提供了统一的接口或者称为标准linux接口.方便了应用开发人员也减少了驱动开发人员的工作量.
` 有些led是gpio控制的.有些led是pwm控制,可以控制led的亮度.所以在此基础上又引出了GPIO_LED子系统和GPIO_pwm子系统.当然也可以直接使用led子系统,忽略gpio_pwm子系统和gpio_子系统.
led子系统
led子系统驱动的源码位于dirvers/leds目录下, 可以看到有很多的文件,但是很多是 在led子系统上编写的led驱动,不是led子系统的源码.
ls led- 列出的以led-开头的文件菜式led子系统实现代码.
- led-core.c: led抽象功能实现代码.led灯的亮灭, 闪烁,呼吸.
- led-class.c: 调用class_create函数在/sys/class/leds目录创建用户空间控制接口.
- led-triggers.c: led 抽象触发功能实现代码
- led-class-flash.c: 闪烁功能实现
先不看源码,先看下如何编写一个简单的led驱动,仅仅实现亮灭控制.
基于led子系统驱动实现
选一个最简单的led驱动,上源码(kernel/drivers/leds/leds-rb532.c),如下:
/*
* LEDs driver for the "User LED" on Routerboard532
*
* Copyright (C) 2009 Phil Sutter <n0-1@freewrt.org>
*
* Based on leds-cobalt-qube.c by Florian Fainelly and
* rb-diag.c (my own standalone driver for both LED and
* button of Routerboard532).
*/
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <asm/mach-rc32434/gpio.h>
#include <asm/mach-rc32434/rb.h>
static void rb532_led_set(struct led_classdev *cdev,
enum led_brightness brightness)
{
if (brightness)
set_latch_u5(LO_ULED, 0);
else
set_latch_u5(0, LO_ULED);
}
static enum led_brightness rb532_led_get(struct led_classdev *cdev)
{
return (get_latch_u5() & LO_ULED) ? LED_FULL : LED_OFF;
}
static struct led_classdev rb532_uled = {
.name = "uled",
.brightness_set = rb532_led_set,
.brightness_get = rb532_led_get,
.default_trigger = "nand-disk",
};
static int rb532_led_probe(struct platform_device *pdev)
{
return led_classdev_register(&pdev->dev, &rb532_uled);
}
static int rb532_led_remove(struct platform_device *pdev)
{
led_classdev_unregister(&rb532_uled);
return 0;
}
static struct platform_driver rb532_led_driver = {
.probe = rb532_led_probe,
.remove = rb532_led_remove,
.driver = {
.name = "rb532-led",
},
};
module_platform_driver(rb532_led_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("User LED support for Routerboard532");
MODULE_AUTHOR("Phil Sutter <n0-1@freewrt.org>");
MODULE_ALIAS("platform:rb532-led");
代码很简单, 定义并初始化了一个static struct led_classdev 结构体,然后调用led_classdev_register()注册这个结构体并为这个结构体指定了父设备.
led_classdev
看下struct led_classdev结构体:
struct led_classdev {
const char *name;
enum led_brightness brightness;
enum led_brightness max_brightness;
int flags;
/* Lower 16 bits reflect status */
#define LED_SUSPENDED BIT(0)
#define LED_UNREGISTERING BIT(1)
/* Upper 16 bits reflect control information */
#define LED_CORE_SUSPENDRESUME BIT(16)
#define LED_SYSFS_DISABLE BIT(17)
#define LED_DEV_CAP_FLASH BIT(18)
#define LED_HW_PLUGGABLE BIT(19)
#define LED_PANIC_INDICATOR BIT(20)
#define LED_BRIGHT_HW_CHANGED BIT(21)
#define LED_RETAIN_AT_SHUTDOWN BIT(22)
/* set_brightness_work / blink_timer flags, atomic, private. */
unsigned long work_flags;
#define LED_BLINK_SW 0
#define LED_BLINK_ONESHOT 1
#define LED_BLINK_ONESHOT_STOP 2
#define LED_BLINK_INVERT 3
#define LED_BLINK_BRIGHTNESS_CHANGE 4
#define LED_BLINK_DISABLE 5
/* Set LED brightness level
* Must not sleep. Use brightness_set_blocking for drivers
* that can sleep while setting brightness.
*/
void (*brightness_set)(struct led_classdev *led_cdev,
enum led_brightness brightness);
/*
* Set LED brightness level immediately - it can block the caller for
* the time required for accessing a LED device register.
*/
int (*brightness_set_blocking)(struct led_classdev *led_cdev,
enum led_brightness brightness);
/* 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 LED_OFF
* via the brightness_set() callback.
*/
int (*blink_set)(struct led_classdev *led_cdev,
unsigned long *delay_on,
unsigned long *delay_off);
struct device *dev;
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;
int new_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;
struct led_trigger *trigger;
struct list_head trig_list;
void *trigger_data;
/* true if activated - deactivate routine uses it to do cleanup */
bool activated;
#endif
#ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED
int brightness_hw_changed;
struct kernfs_node *brightness_hw_changed_kn;
#endif
/* Ensures consistent access to the LED Flash Class device */
struct mutex led_access;
};
代码注册的很清楚,大致看下:
- char* name : 设备的名字.
- enum led_brightness brightness: 应该是默认的亮度.
- enum led_brightness max_birghtness: led的最大亮度.
- int flags: 标记led当前的状态.状态可以用它下面的那些帮助宏设置.
- brightness_set: 函数指针,设置亮度的实现函数, 不能有休眠,如果必须休眠则应该使用类似工作队列.
- brightness_set_sync: 同步设置led的亮度.
- brightness_get: 获取led当前亮度.
- blink_set: 设置led 闪烁. 设置闪烁后如果再次调用brightness_set(),会停止闪烁.
- struct device *dev: 设备结构体示例.
- const struct attribute_group **grpups: 这个设备的属性列表. 这个是在标准接口的基础上额外添加的属性.
- struct list_head node: 链表节点, 用于把所有的led device 放在一个链表里.
- default_tirgger: 默认的触发方式.
- blink_delay_on, blink_delay_off: 默认的亮灭时间.
- struct timer_list blink_timer: 这是实现闪烁要用到的定时器.
- blink_brightenss: 应该是闪烁情况下led 的亮度.
- set_brightness_work: 设置亮度要用到的工作.
- delayed_set_value: 难道是延时设置亮度.
- struct mutes led_access: led互斥访问.
可以看到这个结构体是一个led设备的抽象,一个结构体实例代表了一个led设备. 并且led子系统支持的功能还很多,比如基础功能,设置led亮度,以及led的最大亮度,可以设置led灯的触发比如实现按键按下led就亮以下的功能,设置闪烁功能.
led_classdev_register,
用于注册一个led 设备,注册成功后上层可以使用标准接口控制led灯.
/**
* 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_classdev_register(),函数是在其他驱动中调用的, 它可能是一个混杂设备,平台设备等等. 第一个参数parent就是用于指定谁创建了这个led设备.第二个参数是一个 struct led_classdev 类型的结构体,该结构体描述了一个led设备,该结构体的示例代表了一个led设备.当不再需要这个led设备时需要使用led_classdev_unregister()释放掉这个设备.
如果不使用触发和闪烁功能,基于led子系统实现一个led驱动还是挺简单的.接下来看下源码实现:
led子系统源码实现.