LED子系统
LED子系统 框架
LED子系统是Linux内核中的一个子系统,用于驱动LED灯。它提供了一套标准的驱动框架,使得开发人员能够很容易地开发LED驱动程序,并且不用关心底层硬件细节。
LED子系统的核心是一个驱动模型,驱动模型为LED提供了一套标准的API,包括LED的开启、关闭、闪烁等操作。驱动模型还提供了一个标准的设备树节点格式,允许开发人员通过设备树来描述硬件信息。
LED子系统中的驱动程序可以分为两类:LED Class驱动和Platform驱动。LED Class驱动是一种通用的LED驱动程序,它可以适用于多种不同的硬件平台和LED类型。Platform驱动则是特定于某一硬件平台的驱动程序,它依赖于特定的硬件平台,需要针对不同的硬件平台进行编写。
在编写LED驱动程序时,开发人员需要实现LED子系统提供的一组标准接口,包括probe、remove、set_brightness等接口。其中,probe接口用于驱动初始化,remove接口用于驱动卸载,set_brightness接口用于设置LED的亮度。开发人员还需要在设备树中描述LED的硬件信息,包括GPIO引脚、亮度范围等。
总之,LED子系统提供了一套标准的驱动框架,使得开发人员能够很容易地开发LED驱动程序,并且不用关心底层硬件细节。
例子
设备树
led@0 {
compatible = "gpio-leds";
label = "LED";
gpios = <&gpio1 16 GPIO_ACTIVE_LOW>;
default-state = "off";
};
linux 驱动
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/leds.h>
#include <linux/gpio.h>
#define LED_NAME "led"
#define LED_DEFAULT_TRIGGER "heartbeat"
struct led_info {
struct led_classdev cdev;
int gpio;
};
static int led_probe(struct platform_device *pdev)
{
struct led_info *led;
struct device_node *node = pdev->dev.of_node;
const char *name = node->name;
const char *trigger = LED_DEFAULT_TRIGGER;
int ret;
led = devm_kzalloc(&pdev->dev, sizeof(struct led_info), GFP_KERNEL);
if (!led)
return -ENOMEM;
ret = of_property_read_string(node, "default-state", &led->cdev.default_trigger);
if (ret) {
dev_err(&pdev->dev, "failed to read default-state: %d\n", ret);
return ret;
}
ret = of_property_read_string(node, "default-trigger", &trigger);
if (ret) {
dev_err(&pdev->dev, "failed to read default-trigger: %d\n", ret);
return ret;
}
led->gpio = of_get_gpio_flags(node, 0, NULL);
if (led->gpio < 0) {
dev_err(&pdev->dev, "failed to get gpio: %d\n", led->gpio);
return led->gpio;
}
led->cdev.name = name;
led->cdev.brightness_set = led_brightness_set;
led->cdev.brightness = LED_OFF;
led->cdev.max_brightness = LED_FULL;
ret = devm_led_classdev_register(&pdev->dev, &led->cdev);
if (ret) {
dev_err(&pdev->dev, "failed to register led classdev: %d\n", ret);
return ret;
}
led_trigger_register_simple(name, &led->cdev, trigger);
platform_set_drvdata(pdev, led);
dev_info(&pdev->dev, "LED driver initialized\n");
return 0;
}
static int led_remove(struct platform_device *pdev)
{
struct led_info *led = platform_get_drvdata(pdev);
led_trigger_unregister_simple(&led->cdev);
devm_led_classdev_unregister(&pdev->dev, &led->cdev);
return 0;
}
static const struct of_device_id my_led_dt_ids[] = {
{ .compatible = "my_leds" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_led_dt_ids);
static struct platform_driver my_led_driver = {
.probe = my_led_probe,
.remove = my_led_remove,
.driver = {
.name = "my_led",
.owner = THIS_MODULE,
.of_match_table = my_led_dt_ids,
},
};
module_platform_driver(my_led_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your name");
MODULE_DESCRIPTION("My LED driver");
MODULE_ALIAS("platform:my_led");
注意,上述代码中的 “my_led” 为驱动程序的名字,“my_leds” 是设备树中该设备节点的 compatible 属性值,需要保持一致。
下面是一个简单的应用程序示例,它可以控制 LED 灯的开关状态,利用了 LED 子系统提供的 sysfs 接口:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define LED_PATH "/sys/class/leds/my_led"
int main(int argc, char *argv[])
{
int fd;
char buf[1024];
if (argc < 2) {
printf("Usage: %s on|off\n", argv[0]);
return -1;
}
fd = open(LED_PATH "/brightness", O_WRONLY);
if (fd < 0) {
printf("Failed to open %s\n", LED_PATH "/brightness");
return -1;
}
if (strcmp(argv[1], "on") == 0) {
strcpy(buf, "1\n");
} else if (strcmp(argv[1], "off") == 0) {
strcpy(buf, "0\n");
} else {
printf("Invalid command %s\n", argv[1]);
close(fd);
return -1;
}
if (write(fd, buf, strlen(buf)) != strlen(buf)) {
printf("Failed to write %s\n", LED_PATH "/brightness");
close(fd);
return -1;
}
close(fd);
return 0;
}
下面是带有echo 接口的驱动例子
#include <linux/module.h>
#include <linux/init.h>
#include <linux/leds.h>
#include <linux/platform_device.h>
static struct led_classdev my_led;
static ssize_t my_led_brightness_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int brightness = my_led.brightness;
return sprintf(buf, "%d\n", brightness);
}
static ssize_t my_led_brightness_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
int brightness;
sscanf(buf, "%d", &brightness);
if (brightness < 0)
brightness = 0;
else if (brightness > 255)
brightness = 255;
my_led.brightness = brightness;
led_set_brightness(&my_led, brightness);
return count;
}
static DEVICE_ATTR(brightness, S_IRUGO | S_IWUSR,
my_led_brightness_show, my_led_brightness_store);
static int my_led_probe(struct platform_device *pdev)
{
struct resource *res;
int err;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "Failed to get memory resource\n");
return -EINVAL;
}
my_led.name = "my_led";
my_led.brightness = 0;
my_led.max_brightness = 255;
my_led.blink_brightness = 0;
my_led.blink_delay_on = 100;
my_led.blink_delay_off = 100;
err = led_classdev_register(&pdev->dev, &my_led);
if (err < 0) {
dev_err(&pdev->dev, "Failed to register LED device\n");
return err;
}
err = device_create_file(&pdev->dev, &dev_attr_brightness);
if (err < 0) {
dev_err(&pdev->dev, "Failed to create sysfs file\n");
led_classdev_unregister(&my_led);
return err;
}
dev_info(&pdev->dev, "LED driver initialized\n");
return 0;
}
static int my_led_remove(struct platform_device *pdev)
{
device_remove_file(&pdev->dev, &dev_attr_brightness);
led_classdev_unregister(&my_led);
return 0;
}
static const struct of_device_id my_led_of_match[] = {
{ .compatible = "virtual,my_led", },
{},
};
MODULE_DEVICE_TABLE(of, my_led_of_match);
static struct platform_driver my_led_driver = {
.probe = my_led_probe,
.remove = my_led_remove,
.driver = {
.name = "my_led",
.of_match_table = my_led_of_match,
.owner = THIS_MODULE,
},
};
module_platform_driver(my_led_driver);
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("LED driver with read/write operations");
MODULE_LICENSE("GPL");
下面是它的设备树
/dts-v1/;
/ {
model = "my_board";
compatible = "my_board";
my_led {
compatible = "virtual,my_led";
reg = <0x1000>;
brightness = <0>;
};
};
下面是一个简单的 LED 子系统驱动程序示例,它利用了 sysfs 接口来控制 LED 灯的开关状态:
驱动代码
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#define DRIVER_NAME "myled"
#define LED_CLASS_NAME "my_led"
#define LED_NAME "my_led"
#define LED_PATH "/sys/class/leds/my_led/brightness"
static struct class *led_class;
static struct device *led_device;
static ssize_t myled_read(struct file *filep, char __user *buf, size_t count, loff_t *f_pos)
{
int fd, ret;
char led_status;
fd = open(LED_PATH, O_RDONLY);
if (fd < 0) {
pr_err("Failed to open %s\n", LED_PATH);
return -EINVAL;
}
ret = read(fd, &led_status, 1);
if (ret < 0) {
pr_err("Failed to read %s\n", LED_PATH);
close(fd);
return -EINVAL;
}
close(fd);
if (copy_to_user(buf, &led_status, 1)) {
pr_err("Failed to copy data to user\n");
return -EFAULT;
}
return 1;
}
static ssize_t myled_write(struct file *filep, const char __user *buf, size_t count, loff_t *f_pos)
{
int fd, ret;
char led_status;
if (copy_from_user(&led_status, buf, 1)) {
pr_err("Failed to copy data from user\n");
return -EFAULT;
}
fd = open(LED_PATH, O_WRONLY);
if (fd < 0) {
pr_err("Failed to open %s\n", LED_PATH);
return -EINVAL;
}
ret = write(fd, &led_status, 1);
if (ret < 0) {
pr_err("Failed to write %s\n", LED_PATH);
close(fd);
return -EINVAL;
}
close(fd);
return 1;
}
static struct file_operations myled_fops = {
.owner = THIS_MODULE,
.read = myled_read,
.write = myled_write,
};
static int myled_init(void)
{
int ret;
ret = register_chrdev(0, DRIVER_NAME, &myled_fops);
if (ret < 0) {
pr_err("Failed to register char device\n");
return ret;
}
led_class = class_create(THIS_MODULE, LED_CLASS_NAME);
if (IS_ERR(led_class)) {
pr_err("Failed to create LED class\n");
unregister_chrdev(ret, DRIVER_NAME);
return PTR_ERR(led_class);
}
led_device = device_create(led_class, NULL, MKDEV(ret, 0), NULL, LED_NAME);
if (IS_ERR(led_device)) {
pr_err("Failed to create LED device\n");
class_destroy(led_class);
unregister_chrdev(ret, DRIVER_NAME);
return PTR_ERR(led_device);
}
return 0;
}
static void myled_exit(void)
{
device_destroy(led_class, MKDEV(0, 0));
class_unregister(led_class);
class_destroy(led_class);
unregister_chrdev(0, DRIVER_NAME);
}
module_init(myled_init);
module_exit(myled_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("
DEVICE_ATTR是用于创建一个sysfs设备文件节点的宏定义,可以通过该节点访问设备的属性值。该宏定义的语法如下:
scss
DEVICE_ATTR(name, mode, show, store)
name: 属性名称,字符串类型,必选参数。
mode: 属性的访问权限,属性值为S_IRUGO表示只读,属性值为S_IRUGO | S_IWUSR表示读写,属性值为S_IWUSR表示只写,可选参数。
show: 指向show函数的指针,用于读取属性值,函数返回值为属性值的长度,必选参数。
store: 指向store函数的指针,用于写入属性值,函数返回值为写入的属性值的长度,必须有读写属性才需要这个参数。
可以加入用DEVICE_ATTR 节点
以下是一个示例:
static ssize_t led_state_show(struct device *dev, struct device_attribute *attr, char *buf)
{
int state;
struct led_dev *led = dev_get_drvdata(dev);
state = gpio_get_value(led->gpio_pin);
return scnprintf(buf, PAGE_SIZE, "%d\n", state);
}
static ssize_t led_state_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
int state;
struct led_dev *led = dev_get_drvdata(dev);
sscanf(buf, "%d", &state);
gpio_set_value(led->gpio_pin, state);
return count;
}
static DEVICE_ATTR(state, S_IRUGO | S_IWUSR, led_state_show, led_state_store);
这里定义了一个名为state的属性,它的访问权限为可读可写,show函数为led_state_show,store函数为led_state_store。在应用层可以通过/sys/class/leds/led1/state文件节点来读写LED状态。