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)

    08_LED驱动 - 图1

通过改变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