
LInux内核已自带了LED灯驱动LED灯驱动采用platform框架 只需在设备树文件添加相应的LED节点。在根目录下找到.config文件打开后找到下面的语句删除前面的“#”符号CONFIG_LEDS_GPIOy修改完后为避免编译依赖一致性问题执行一下make olddefconfig然后就可以编译内核了。LED灯驱动文件是/driver/leds/leds-gpio.cMakefile文件中可见leds-gpio.c文件会编译进内核。obj-$(CONFIG_LEDS_GPIO) leds-gpio.oleds-gpio.c文件有如下内容static const struct of_device_id of_gpio_leds_match[] { { .compatible gpio-leds, }, {}, }; static struct platform_driver gpio_led_driver { .probe gpio_led_probe, .remove gpio_led_remove, .driver { .name leds-gpio, .of_match_table of_gpio_leds_match, }, }; module_platform_driver(gpio_led_driver);所以编写设备树的led设备节点的compatible属性值应为“gpio-leds”。Linux内核自带的LED驱动采用platform框架驱动程序需提供gpio_led_probe()和gpio_led_remove()这两个函数驱动名为“leds-gpio”所以在/sys/bus/platform/drivers目录下存在一个“leds-gpio”的文件。module_platform_driver向Linux内核注册gpio_led_driver这个platform驱动。module_platform_driver定义在include/linux/platform_device.h文件中是一个宏module_platform_driver(gpio_led_driver)展开后是一个标准的注册和删除platform驱动static int __init gpio_led_driver_init(void) { return platform_driver_register((gpio_led_driver)); } module_init(gpio_led_driver_init); static void __exit gpio_led_driver_exit(void) { platform_driver_unregister((gpio_led_driver)); } module_exit(gpio_led_driver_exit);gpio_led_probe()函数中获取设备信息的主要代码static int gpio_led_probe(struct platform_device *pdev) { struct gpio_leds_priv *priv; priv gpio_leds_create(pdev); if (IS_ERR(priv)) return PTR_OERR(priv); platform_set_drvdata(pdev, priv); return 0; }gpio_leds_create()函数的主要代码static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev) { struct platform_device *pdev; struct fwnode_handle *child; struct gpio_leds_priv *priv; int count; struct device_node *np; struct device *dev pdev-dev; /* 统计子节点数量一个子节点代表一个LED */ cout device_get_child_node_count(dev); /* 遍历子节点 */ device_for_each_child_node(dev, child) { /* 获取LED所使用的GPIO信息 */ devm_get_gpiod_from_child(dev, NULL, child); if (fwnode_property_present(child, label)) { /* 读取子节点lable属性值 */ fwnode_property_read_string(child, label, led.name); } /* 获取LED的默认功能 */ fwnode_property_read_string(child, linux,default-trigger, led.default_trigger); /* 获取LED的默认状态 *、 if (!fwnode_property_read_string(child, default-state, state)) { if (!strcmp(state, keep)) led.default_state LEDS_GPIO_DEFSTATE_KEEP; else if (!strcmp(state, on)) led.default_state LEDS_GPIO_DEFSTATE_ON; else led.default_state LEDS_GPIO_DEFSTATE_OFF; } /* 设置LED所用io为输出 *、 create_gpio_led(led, priv-leds[priv-num_leds], dev, NULL); }开始验证在imx6ull-alientek-emmc.dts添加LED设备节点dtsleds { compatible gpio-leds; led0 { label red; gpios gpio1 3 GPIO_ACTIVE_LOW; default-state off; }; };启动后查看/sys/devices/platform/dtsleds 这个目录是否存在在leds目录下检查是否有red子目录查看一下系统中有没有“sys/class/leds/red/brightness”这个文件这是 LED 子系统在 sysfs 中为特定 LED 设备创建的一个属性文件。内核驱动会在初始化时注册这个文件作为用户空间与硬件交互的接口。输入下面两条命令验证LED是否亮和灭打开LED0echo 1 /sys/class/leds/red/brightness关闭LED0echo 0 /sys/class/leds/red/brightnessecho 1echo是 shell 命令用于输出文本。这里将字符1通过重定向符写入上述文件。这是 shell 的输出重定向符它的作用是将echo命令的输出即字符1写入到指定的文件中而不是显示在终端。这条命令触发了一系列内核操作VFS 层当echo命令执行写操作时Linux 虚拟文件系统VFS会捕获到对/sys/class/leds/red/brightness这个特殊文件的写入请求。LED 子系统VFS 会将这个写请求路由到 LED 子系统的核心代码通常是drivers/leds/led-class.c。该代码定义了brightness文件的store方法负责处理写入的数据。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);驱动回调LED 子系统核心会将用户写入的值这里是1传递给具体 LED 驱动。驱动led-core.c实现了一个名为led_set_brightness 的回调函数该函数接收这个亮度值并执行实际的硬件操作例如通过 GPIO 或 PWM 控制器设置引脚电平。void led_set_brightness(struct led_classdev *led_cdev, enum led_brightness brightness) { int ret 0; /* delay brightness setting if need to stop soft-blink timer */ if (led_cdev-blink_delay_on || led_cdev-blink_delay_off) { led_cdev-delayed_set_value brightness; schedule_work(led_cdev-set_brightness_work); return; } if (led_cdev-flags SET_BRIGHTNESS_ASYNC) { led_set_brightness_async(led_cdev, brightness); return; } else if (led_cdev-flags SET_BRIGHTNESS_SYNC) ret led_set_brightness_sync(led_cdev, brightness); else ret -EINVAL; if (ret 0) dev_dbg(led_cdev-dev, Setting LED brightness failed (%d)\n, ret); } EXPORT_SYMBOL(led_set_brightness);硬件动作在 led_set_brightness 函数中你通常会根据传入的值1或0来操作硬件寄存器最终使 LED 亮起或熄灭。static inline void led_set_brightness_async(struct led_classdev *led_cdev, enum led_brightness value) { value min(value, led_cdev-max_brightness); led_cdev-brightness value; if (!(led_cdev-flags LED_SUSPENDED)) led_cdev-brightness_set(led_cdev, value); } static inline int led_set_brightness_sync(struct led_classdev *led_cdev, enum led_brightness value) { int ret 0; led_cdev-brightness min(value, led_cdev-max_brightness); if (!(led_cdev-flags LED_SUSPENDED)) ret led_cdev-brightness_set_sync(led_cdev, led_cdev-brightness); return ret; }参考资料1.I.MX6Uq嵌入式Linux驱动开发指南V2.0.1正点原子2. Linux设备驱动开发 详解基于最新的Linux 4.0内核