Linux内核支持多种驱动框架,其中就支持LED这样的设备模型。
Linux内核实现了一个虚拟的文件系统 sysfs ,用于提供一种从用户空间访问内核设备的方法
。
笔者此处就基于 sysfs 文件系统的 Led 驱动做一个简单的介绍。
SofTool.CN Notes:
要在 /etc/init.d/rcS 里面挂载sysfs:
mount -t sysfs sysfs /sys
1、LED驱动目录
在 /drivers/leds 目录下实现了Linux内核的LED驱动框架。
leds目录下Kconfig对led驱动进行配置,如:配置led驱动支持、led class支持、特定平台支持等。
LED驱动可分为平台相关和无关这两部分:平台无关的部分无需任何移植,平台相关的部分需根据特定平台实现LED驱动框架的硬件层访问接口。
SofTool.CN Notes:
drivers/leds/Makefile
平台无关部分:
# LED Core
obj-$(CONFIG_NEW_LEDS) +=led-core.o
obj-$(CONFIG_LEDS_CLASS)+= led-class.o
obj-$(CONFIG_LEDS_CLASS_FLASH)+= led-class-flash.o
obj-$(CONFIG_LEDS_TRIGGERS)+= led-triggers.o
针对s3c2416特定平台的驱动:
obj-$(CONFIG_LEDS_S3C24XX)+= leds-s3c24xx.o
2、LED平台驱动相关
Linux内核把设备驱动程序分成两部分:一部分为设备(程序),另一部分为驱动(程序)。
SofTool.CN Notes:
本篇文章的“设备”程序,位于:arch/arm/mach-s3c24xx/mach-home2416.c
本篇文章的“驱动”程序,位于:drivers/leds/leds-s3c24xx.c
分析:
从Linux 2.6起引入了一套新的驱动管理和注册机制: Platform_device 和 Platform_driver。
Linux中大部分的设备驱动,都可以使用这套机制, 设备用Platform_device表示,驱动用Platform_driver进行注册。
Linux内核已经支持多个平台驱动(不同厂家CPU使用的开发板),对于未实现的平台,这是需要移植的部分。
2.1 注册设备
设备信息包含了设备名字、独有的资源等等一些驱动程序的硬件或自定义信息。
通过 platform_add_devices() ( platform_device_register() ) 函数将定义的平台设备添加(注册)到内核中,添加(注册)成功后,在 /sys/devices/platform 出现相应的设备目录。
SofTool.CN Notes:
arch/arm/mach-s3c24xx/mach-home2416.c
/* LED devices */
static struct s3c24xx_led_platdata home_pdata_led4 =
{
.gpio =S3C2410_GPE(13),
.flags =S3C24XX_LEDF_TRISTATE,
.name ="led4",
.def_trigger ="timer",
};
static struct s3c24xx_led_platdata home_pdata_led5 =
{
.gpio =S3C2410_GPE(11),
.flags =S3C24XX_LEDF_TRISTATE,
.name ="led5",
.def_trigger ="nand-disk",
};
static struct s3c24xx_led_platdata home_pdata_led6 =
{
.gpio =S3C2410_GPL(13),
.flags =S3C24XX_LEDF_TRISTATE,
.name ="led6",
};
static struct s3c24xx_led_platdata home_pdata_led7 =
{
.gpio =S3C2410_GPE(12),
.flags =S3C24XX_LEDF_TRISTATE,
.name ="led7",
};
static struct platform_device home_led4 =
{
.name ="s3c24xx_led", //设备名字,同类设备可以使用相同的名字
.id = 0, //如果同类设备使用相同的名字,就可以通过这里的id进行区别具体的设备了.
//本设备对应的具体IO口
.dev = {
.platform_data = &home_pdata_led4,
},
};
static struct platform_device home_led5 =
{
.name ="s3c24xx_led", //设备名字,同类设备可以使用相同的名字
.id = 1,
.dev = {
.platform_data = &home_pdata_led5,
},
};
static struct platform_device home_led6 =
{
.name ="s3c24xx_led", //设备名字,同类设备可以使用相同的名字
.id = 2,
.dev = {
.platform_data = &home_pdata_led6,
},
};
static struct platform_device home_led7 =
{
.name ="s3c24xx_led", //设备名字,同类设备可以使用相同的名字
.id = 3,
.dev = {
.platform_data = &home_pdata_led7,
},
};
2.2. 注册驱动
对于s3c2416平台来说,led驱动程序在 leds-s3c24xx.c 文件中。
SofTool.CN Notes:
drivers/leds/leds-s3c24xx.c
static struct platform_driver s3c24xx_led_driver =
{
.probe = s3c24xx_led_probe,
.remove = s3c24xx_led_remove,
.driver = {
.name ="s3c24xx_led", //驱动名
},
};
module_platform_driver(s3c24xx_led_driver);
module_platform_driver() 为宏定义,会自动去替换 module_init() 和 module_exit() 宏功能,而 module_init() 和 module_exit() 宏会分别调用 platform_driver_register() 和 platform_driver_unregister() 。
platform_driver_register() 为驱动注册函数,当驱动加载的时候,就会执行该函数。
s3c24xx_led_driver 结构体中定义了驱动的 name ,驱动注册时,内核会遍历比较驱动程序 name 与注册的平台设备程序 name,若一致,就将两者绑定,调用探测函数s3c24xx_led_probe()。探测函数 s3c24xx_led_probe() 通过调用 led_classdev_register() 这个函数注册一个新的led设备类对象,实现在目录 /sys/class/leds,创建子目录led->cdev->name,并在led->cdev->name子目录下创建各属性brightness、max_brightness等。
3、应用编程
在 /sys/class/leds 目录下有相应的 Led 设备子目录,假设进入led4目录,会看到有 brightness、max_brightness等属性文件:
SofTool.CN Notes:
- 如需自动创建Led设备子目录,需要 mdev 的支持,同时在 /etc/init.d/rcS 要对应启动它,否则无法自动创建 ;
- 下图是我的板子对应的内容:(因为硬件和作者不同,所以下图中的led0对应作者的led4)
通过改变brightness属性文件的值,即可控制led灯的亮灭。
在控制台对brightness文件写入0或1控制led灯的亮灭。
echo 1 >/sys/class/leds/led4/brightness // 亮灯
echo 0 >/sys/class/leds/led4/brightness // 灭灯
也可以通过应用程序访问sysfs中的led设备文件控制led等的亮灭。以 led_test.c 文件为例如下:
#include "fcntl.h"
#include "unistd.h"
#include "sys/types.h"
#include "stdio.h"
#include "stdlib.h"
void led_control(int index, int on)
{
int fd;
char buf[1024];
char path[] = "/sys/class/leds/led4/brightness";
if (index == 4)
{
// led 4
fd = open(path, O_RDWR);
if(fd < 0)
{
printf("Open failed\n");
exit(1);
}
sprintf(buf, "%d", on);
if (write(fd, buf, strlen(buf)) != strlen(buf))
{
close(fd);
printf("Write failed\n");
exit(1);
}
close(fd);
}
}
int main(void)
{
while (1)
{
led_control(4, 1);
sleep(1);
led_control(4, 0);
sleep(1);
}
}
用 arm-linux-gcc 静态编译,使之生成 arm CPU 可执行的指令,并且可脱离任何库独立运行:
arm-linux-gcc -static -o led_test
led_test.c,生成led_test可执行文件。复制可执行文件到根文件系统,目标板启动后在目录输入./led_test即可执行。
4、附录
文件 | 下载地址 |
---|---|
bootloader源码以及使用说明 | https://pan.baidu.com/s/1slczwhJ |
Qt5.8官网源码 | https://pan.baidu.com/s/1eRDJtNs |
本系列例程的根文件系统 | https://pan.baidu.com/s/1nuGmSqt |
opev3.2.0官网源码 | https://pan.baidu.com/s/1i5btLGT |
yaffs官网源码 | https://pan.baidu.com/s/1pLpuHw3 |
busybox-1.26.2官网源码 | https://pan.baidu.com/s/1bpkZynt |
tslib官网源码 | https://pan.baidu.com/s/1i4EtjfR |
mplayer-1.3.0官网源码 | https://pan.baidu.com/s/1i5MGRhb |
基于S3C2416修改的linux-4.10.10源码 | https://pan.baidu.com/s/1sl0fXlr |