LED子系统是Linux内核中有关LED的框架,开发者可以使用这个框架来进行LED设备的注册。
LED子系统程序在/driver/leds/led-class.c中
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 (1 << 0)
/* Upper 16 bits reflect control information */
#define LED_CORE_SUSPENDRESUME (1 << 16)
#define LED_BLINK_ONESHOT (1 << 17)
#define LED_BLINK_ONESHOT_STOP (1 << 18)
#define LED_BLINK_INVERT (1 << 19)
#define LED_SYSFS_DISABLE (1 << 20)
#define SET_BRIGHTNESS_ASYNC (1 << 21)
#define SET_BRIGHTNESS_SYNC (1 << 22)
#define LED_DEV_CAP_FLASH (1 << 23)
/* Set LED brightness level */
/* Must not sleep, use a workqueue if needed */
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_sync)(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 a fixed
* value 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;
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
/* Ensures consistent access to the LED Flash Class device */
struct mutex led_access;
};
我们通常需要关注的以下几个成员变量
name:LED设备名字
brightness:LED的亮度
max_brightness:LED的最大亮度
brightness_set:设置LED的亮度函数
brightness_get:返回LED亮度函数
enum led_brightness {
LED_OFF = 0,
LED_HALF = 127,
LED_FULL = 255,
};
led_brightness是一个枚举,里面定义了3个亮度值。灭,半亮和全亮。
static int __init leds_init(void)
{
leds_class = class_create(THIS_MODULE, "leds"); // 创建led类
if (IS_ERR(leds_class))
return PTR_ERR(leds_class);
leds_class->pm = &leds_class_dev_pm_ops;
leds_class->dev_groups = led_groups; // 在leds类中创建属性
return 0;
}
首先内核会先调用leds_init函数来初始化led子系统。先创建一个叫名字叫leds的类,然后在leds类中创建文件属性,这些文件属性可以在后面动态地设置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)
{
char name[64];
int ret;
ret = led_classdev_next_name(led_cdev->name, name, sizeof(name));
if (ret < 0)
return ret;
led_cdev->dev = device_create_with_groups(leds_class, parent, 0,
led_cdev, led_cdev->groups, "%s", name); // 创建led设备
if (IS_ERR(led_cdev->dev))
return PTR_ERR(led_cdev->dev);
if (ret)
dev_warn(parent, "Led %s renamed to %s due to name collision",
led_cdev->name, dev_name(led_cdev->dev));
#ifdef CONFIG_LEDS_TRIGGERS
init_rwsem(&led_cdev->trigger_lock);
#endif
mutex_init(&led_cdev->led_access);
/* add to the list of leds */
down_write(&leds_list_lock);
list_add_tail(&led_cdev->node, &leds_list); // 将led设备添加内核链表
up_write(&leds_list_lock);
if (!led_cdev->max_brightness) // 判断是否设备LED最大亮度
led_cdev->max_brightness = LED_FULL;
led_cdev->flags |= SET_BRIGHTNESS_ASYNC;
led_update_brightness(led_cdev);
INIT_WORK(&led_cdev->set_brightness_work, set_brightness_delayed);
setup_timer(&led_cdev->blink_timer, led_timer_function,
(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;
}
内核给驱动开发人员提供了一个LED设备注册函数,叫led_classdev_register。
static ssize_t 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;
mutex_lock(&led_cdev->led_access);
if (led_sysfs_is_disabled(led_cdev)) {
ret = -EBUSY;
goto unlock;
}
ret = kstrtoul(buf, 10, &state);
if (ret)
goto unlock;
if (state == LED_OFF)
led_trigger_remove(led_cdev);
led_set_brightness(led_cdev, state);
ret = size;
unlock:
mutex_unlock(&led_cdev->led_access);
return ret;
}
static DEVICE_ATTR_RW(brightness);
static ssize_t max_brightness_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
return sprintf(buf, "%u\n", led_cdev->max_brightness);
}
static DEVICE_ATTR_RO(max_brightness);
static struct attribute *led_class_attrs[] = {
&dev_attr_brightness.attr,
&dev_attr_max_brightness.attr,
NULL,
};
static const struct attribute_group led_group = {
.attrs = led_class_attrs,
};
上面几行代码会在每个led设备下创建两个设备属性文件,分别是max_brightness和brightness,用户可以直接通过读max_brightness设备文件获取LED的最大亮度,通过读写brightness设备文件来控制LED灯的亮灭。
接下来就利用led子系统写一个驱动程序来控制开发板上的LED灯,在开始写程序时要先看一下内核是否支持led子系统。
首先进入内核的图形化配置界面,在
Device Drivers
–>LED Support
–>LED Class Support
看一下LED Class Support选项是否选中,如果没有选中,就需要选中该配置项并重新编译内核代码。
如果led子系统配置成功会在/sys/class目录下看到一个叫“leds”的类。
驱动程序
struct led_dev_type
{
struct miscdevice led_misc;
int gpio;
struct device_node* nd; // 设备树节点
struct mutex ledmutex;
struct led_classdev ledclasscdev;
};
struct led_dev_type ledcdev;
void led_brightness_set(struct led_classdev *led_cdev,enum led_brightness brightness)
{
struct led_dev_type *pdata = container_of(led_cdev, struct led_dev_type, ledclasscdev);
if(LED_OFF == brightness) // 判断用户传递参数是否为0
{
gpio_set_value(pdata->gpio,1); // LED灭
}
else
{
gpio_set_value(pdata->gpio,0); // LED亮
}
}
static int led_probe(struct platform_device *pdev)
{
int ret = 0;
DEBUG_SFLR();
ledcdev.nd = pdev->dev.of_node; // 获取设备树节点
DEBUG_SFLR("ledcdev.nd -> name = %s\r\n",ledcdev.nd->name);
ledcdev.gpio = of_get_named_gpio(ledcdev.nd,"led_gpio",0); // 获取LED的GPIO号
if(ledcdev.gpio < 0){
ret = -1;
goto end;
}
DEBUG_SFLR("ledcdev.gpio = %d\r\n",ledcdev.gpio );
if(gpio_is_valid(ledcdev.gpio))
{
ret = devm_gpio_request_one(&pdev->dev,ledcdev.gpio,GPIOF_OUT_INIT_HIGH,"atkgpioled"); // 申请GPIO
if(ret < 0){
ret = -1;
goto end;
}
}
else{
DEBUG_SFLR("ledcdev.gpio is busy\r\n..." );
ret = -1;
goto end;
}
ledcdev.ledclasscdev.name = "imx6ull_led"; // 设置led设备名
ledcdev.ledclasscdev.brightness_set = led_brightness_set; // 设置LED控制看书
devm_led_classdev_register(&pdev->dev,&ledcdev.ledclasscdev); // 注册LED设备
platform_set_drvdata(pdev,&ledcdev);
end:
return ret;
}
static int led_remove(struct platform_device *pdev)
{
// remove函数负责卸载驱动
struct led_dev_type *pdata = platform_get_drvdata(pdev);
devm_led_classdev_unregister(&pdev->dev,&pdata->ledclasscdev);
gpio_set_value(pdata->gpio,1);
gpio_free(pdata->gpio);
return 0;
}
static const struct of_device_id led_match[] = {
{ .compatible = "fsl,imx6ull gpioled", },
{ },
};
static struct platform_driver led_driver = {
.probe = led_probe,
.remove = led_remove, // 驱动卸载时执行remove函数
.driver = {
.name = "gpioled",
.of_match_table = led_match, // 新版有设备树时通过compatible节点匹配
},
};
static int __init led_init_module(void)
{
return platform_driver_register(&led_driver); // 利用platform总线注册驱动
}
static void __exit led_exit_module(void)
{
platform_driver_unregister(&led_driver);
}
module_init(led_init_module);
module_exit(led_exit_module);
MODULE_DESCRIPTION("xxxxxx");
MODULE_AUTHOR("xxxxx");
MODULE_LICENSE("GPL");
在程序中先申请LED灯的GPIO口,然后设置LED设备的名字以及LED灯的控制函数,然后注册LED设备。将驱动程序编译并加载到内核。
在加载驱动前,在leds类中还没有一个叫"imx6ull_led"的设备。
当驱动加载成功后,再次查看leds类中的设备,就会发现多了一个叫"imx6ull_led"的设备
进入"imx6ull_led"这个文件会发现里面有很多属性文件,我们可以读写这些属性文件来测试LED。