现在的位置: 首页 > 综合 > 正文

linux2.6.32 LED子系统移植及分析

2014年10月06日 ⁄ 综合 ⁄ 共 10987字 ⁄ 字号 评论关闭

linux2.6.32内核中LED类的构架已经完善,移植很简单,只用添加相关资源就可以了。

在mach-fz2440.c中添加相关头文件

/* for LEDS */
#include <linux/leds.h>

添加平台资源初始化文件

/* LED */
static struct gpio_led fz2440_leds[] = {
    [0] = {
        .name = "led1",
        .gpio = S3C2410_GPB(5),                                             //led对应gpio
        .active_low = 1,                                                              //低电平有效(点亮)
        .default_trigger = "heartbeat",                                        //默认触发
        .default_state = LEDS_GPIO_DEFSTATE_OFF,          //默认状态
    },
    [1] = {
        .name = "led2",
        .gpio = S3C2410_GPB(6),
        .active_low = 1,
        .default_trigger = "nand-disk",
        .default_state = LEDS_GPIO_DEFSTATE_OFF,
    },
    [2] = {
        .name = "led3",
        .gpio = S3C2410_GPB(7),
        .active_low = 1,
        .default_trigger = "default-on",
        .default_state = LEDS_GPIO_DEFSTATE_OFF,
    },
    [3] = {
        .name = "led4",
        .gpio = S3C2410_GPB(8),
        .active_low = 1,
        .default_trigger = "timer",
        .default_state = LEDS_GPIO_DEFSTATE_OFF,
    },
};
static struct gpio_led_platform_data s3c_gpio_led_pdata = {
    .num_leds = ARRAY_SIZE(fz2440_leds),
    .leds = fz2440_leds,
};
static struct platform_device s3c_device_led = {
    .name = "leds-gpio",
    .id   = -1,
    .dev  = {
               .platform_data = &s3c_gpio_led_pdata,
    },
};


static struct platform_device __initdata *fz2440_devices[] = {
&s3c_device_usb,
&s3c_device_lcd,
&s3c_device_wdt,
&s3c_device_i2c0,
&s3c_device_iis,
&s3c_device_nand,
&s3c_device_rtc,
&s3c_device_uda1341,
&s3c_device_adc,
&s3c_device_hwmon,
&s3c_device_ts,
&s3c_device_led,         //led平台设备文件
};

编译内核下载,看到"heartbeat"灯在闪烁,"default-on"等一直在亮着。

查看系统类设备

root@FZ:/sys/class/leds# ls
led1  led2  led3  led4

root@FZ:/sys/class/leds# cd led2
root@FZ:/sys/devices/platform/leds-gpio/leds/led2# ls
brightness      max_brightness  subsystem       uevent
device          power           trigger

让led2亮

root@FZ:/sys/devices/platform/leds-gpio/leds/led2# echo 1 > brightness

让led2灭

root@FZ:/sys/devices/platform/leds-gpio/leds/led2# echo 0 > brightness

写一个简单的应用测试程序

/*
 * leds.c - The first kernel app programming
 */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>

int main(int argc, char *argv[])
{
FILE *fp;
int led_no;
if(argc != 3)
{
printf("Usage: %s <led_no> <on/off>\n",argv[0]);
exit(0);
}
led_no = strtoul(argv[1],0,0);
switch (led_no)
{
case 2:
fp = fopen("/sys/class/leds/led2/brightness","w");
break;
case 3:
fp = fopen("/sys/class/leds/led3/brightness","w");
break;
case 4:
fp = fopen("/sys/class/leds/led4/brightness","w");
break;
default:
printf("err: no target led!\n");
break;
}
if(fp == NULL)
{
printf("err: cant open the file\n");
exit(0);
}
if(!strcmp(argv[2],"on")){
fputc('1',fp);
printf("led%d on!\n",led_no);
}else if(!strcmp(argv[2],"off")){
fputc('0',fp);
printf("led%d off!\n",led_no);
}
fclose(fp);
return 0;
}

编译

arm-linux-gcc -o leds leds.c

运行

./leds 3 on

看到led3点亮了

数据结构
/include/linux/leds.h
enum led_brightness {
     LED_OFF          = 0,
     LED_HALF     = 127,
     LED_FULL     = 255,
};

led_classdev代表led的实例
struct led_classdev {
     const char          *name;               //名字
     int               brightness;                //当前亮度
     int               flags;                         //标志,目前只支持 LED_SUSPENDED

#define LED_SUSPENDED          (1 << 0)

     /*设置led的亮度,不可以睡眠,有必要的话可以使用工作队列*/
     void          (*brightness_set)(struct led_classdev *led_cdev,
                           enum led_brightness brightness);
     /* 获取亮度 */
     enum led_brightness (*brightness_get)(struct led_classdev *led_cdev);

     /* 激活硬件加速的闪烁 */
     int          (*blink_set)(struct led_classdev *led_cdev,
                         unsigned long *delay_on,
                         unsigned long *delay_off);

     struct device          *dev;
     struct list_head     node;               /* 所有已经注册的led_classdev使用这个节点串联起来 */
     const char          *default_trigger;     /* 默认触发器 */

#ifdef CONFIG_LEDS_TRIGGERS               //如果配置内核时使能了触发器功能,才会编译下面一段
     /* 这个读写子轩锁保护下面的触发器数据 */
     struct rw_semaphore     trigger_lock;

     struct led_trigger     *trigger;          //触发器指针
     struct list_head     trig_list;               //触发器使用的链表节点,用来连接同一触发器上的所有led_classdev
     void               *trigger_data;            //触发器使用的私有数据
#endif
};


触发器的结构体
#define TRIG_NAME_MAX 50
struct led_trigger {
     const char     *name;          //触发器名字
     void          (*activate)(struct led_classdev *led_cdev);     //激活ledled。led_classdev和触发器建立连接时会调用这个方法。
     void          (*deactivate)(struct led_classdev *led_cdev);     //取消激活。led_classdev和触发器取消连接时会调用这个方法。

     /* 本触发器控制之下的led链表 */
     rwlock_t       leddev_list_lock; //保护链表的锁
     struct list_head  led_cdevs;    //链表头

     /* 连接下一个已注册触发器的链表节点 ,所有已注册的触发器都会被加入一个全局链表*/
     struct list_head  next_trig;
};


平台设备相关的led数据结构
struct led_info {
     const char     *name;
     char          *default_trigger;
     int          flags;
};

struct led_platform_data {
     int          num_leds;
     struct led_info     *leds;
};


平台设备相关的gpio led数据结构
struct gpio_led {
     const char *name;
     char *default_trigger;
     unsigned      gpio;
     u8           active_low;
};

struct gpio_led_platform_data {
     int           num_leds;
     struct gpio_led *leds;
     int          (*gpio_blink_set)(unsigned gpio,
                         unsigned long *delay_on,
                         unsigned long *delay_off);
};



led_classdev接口分析/driver/rtc/led-class.c

注册struct led_classdev:
int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
{
     int rc;

     /* 创建一个struct device,他的父设备是parent,drvdata是led_cdev,名字是led_cdev->name,类别是 leds_class*/
     led_cdev->dev = device_create_drvdata(leds_class, parent, 0, led_cdev,
                               "%s", led_cdev->name);
     if (IS_ERR(led_cdev->dev))
          return PTR_ERR(led_cdev->dev);

     /* register the attributes */
     rc = device_create_file(led_cdev->dev, &dev_attr_brightness);//在sys/class/rtc/下创建一个led的属性文件。
     if (rc)
          goto err_out;

     /* add to the list of leds */
     down_write(&leds_list_lock);
     list_add_tail(&led_cdev->node, &leds_list);//将新的led加入链表,全局链表是leds_list
     up_write(&leds_list_lock);

     led_update_brightness(led_cdev);//获取led当前的亮度更新led_cdev的brightness成员

#ifdef CONFIG_LEDS_TRIGGERS
     init_rwsem(&led_cdev->trigger_lock);//初始化led_cdev的触发器自旋锁

     rc = device_create_file(led_cdev->dev, &dev_attr_trigger);//在sys/class/led中为触发器创建属性文件
     if (rc)
          goto err_out_led_list;

     led_trigger_set_default(led_cdev); //为led_cdev设置默认的触发器
#endif

     printk(KERN_INFO "Registered led device: %s/n",
               led_cdev->name);

     return 0;

#ifdef CONFIG_LEDS_TRIGGERS
err_out_led_list:
     device_remove_file(led_cdev->dev, &dev_attr_brightness);
     list_del(&led_cdev->node);
#endif
err_out:
     device_unregister(led_cdev->dev);
     return rc;
}
EXPORT_SYMBOL_GPL(led_classdev_register);


注销struct led_classdev:
void led_classdev_unregister(struct led_classdev *led_cdev);
注销所做的工作和注册相反。

将led挂起:将led的flag设为LED_SUSPENDED,关闭led.
void led_classdev_suspend(struct led_classdev *led_cdev)
从挂起中恢复:
void led_classdev_resume(struct led_classdev *led_cdev)

sysfs中的属性文件
/driver/rtc/led-class.c会首先创建一个leds类,生成/sys/class/leds目录。
在led_classdev_register中生成了两个sysfs属性文件,它们使用的属性参数如下:

static DEVICE_ATTR(brightness, 0644, led_brightness_show, led_brightness_store);
static DEVICE_ATTR(trigger, 0644, led_trigger_show, led_trigger_store);

led_brightness_show和led_brightness_store分别负责显示和设置亮度,用户控件通过
/sys/class/leds/<device>/brightness查看和设置亮度就是和这两个函数交互的。
 led_trigger_show用于读取当前触发器的名字,led_trigger_store用于指定触发器的名字,
它会寻找所有已注册的触发器,找到同名的并设置为当前led的触发器。
/sys/class/leds/<device>/trigger用于用户空间查看和设置触发器。

 led_classdev全局链表:
led_classdev_register注册的struct led_classdev会被加入leds_list链表,这个链表定义在driver/leds/led-core.c。



led_trigger接口分析/driver/leds/led-triggers.c

注册触发器
int led_trigger_register(struct led_trigger *trigger);
这个函数注册的trigger会被加入全局链表trigger_list,这个链表头是在/driver/leds/led-triggers.c定义的。
此外,这个函数还会遍历所有的已注册的 led_classdev,如果有哪个led_classdev的默认触发器和自己同名,则
调用led_trigger_set将自己设为那个led的触发器。
led_classdev注册的时候也会调用led_trigger_set_default来遍历所有已注册的触发器,找到和led_classdev.default_trigger同名的触发器则将它设为自己的触发器。

注销触发器
void led_trigger_unregister(struct led_trigger *trigger);
这个函数做和注册相反的工作,并把所有和自己建立连接的led的led_classdev.trigger设为NULL。

设置触发器上所有的led为某个亮度
void led_trigger_event(struct led_trigger *trigger, enum led_brightness brightness);

注册触发器的简单方法
指定一个名字就可以注册一个触发器,注册的触发器通过**tp返回,但是这样注册的触发器没有active和deactivede。
void led_trigger_register_simple(const char *name, struct led_trigger **tp);
相对应的注销函数为:
void led_trigger_unregister_simple(struct led_trigger *trigger);

触发器和led的连接
void led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trigger);//建立连接。建立连接的时候会调用触发器的activate方法
void led_trigger_remove(struct led_classdev *led_cdev);//取消连接。取消连接的时候会调用触发器的deactivate方法
void led_trigger_set_default(struct led_classdev *led_cdev);//在所有已注册的触发器中寻找led_cdev的默认触发器并调用 led_trigger_set建立连接

最后总结一下led、led_classdev、led_trigger的关系:
 也就是说trigger好比是控制LED类设备的算法,这个算法决定着LED什么时候亮什么时候暗。LED trigger类设备可以是现实的硬件设备,比如IDE硬盘,也可以是系统心跳等事件。

内核中led触发器实例


 gpio-led框架

/driver/leds/leds-gpio.c下实现了gpio-led框架。这个gpio-led框架的作用是把传入的gpio端口信息,注册成 led_classdev。

数据结构
平台设备相关的gpio led数据结构
/include/linux/leds.h
struct gpio_led {
     const char *name;          //名字
     char *default_trigger;     //默认触发器的名字
     unsigned      gpio;          //使用的gpio编号
     u8           active_low;     //如果为真则逻辑1代表低电平
};

struct gpio_led_platform_data {
     int           num_leds;                                       //gpio led的数量
     struct gpio_led *leds;                                      //指向要注册的gpio_led数组
     int          (*gpio_blink_set)(unsigned gpio,          //硬件闪烁加速设置,可以为NULL
                         unsigned long *delay_on,
                         unsigned long *delay_off);
};


如何注册gpio-led平台设备

最后调用platform_device_register(&leds_gpio)将LED设备注册到内核中。注册之前一定要保证编号为138和139的两个端口是可用的。
成功注册之后,系统中便会出现名为led3和led4的两个led_classdev了。由于是用gpio模拟led,所以对gpio-led设置的亮度,只要不是0就是全亮(gpio只有两个状态)。
对于可能睡眠的gpio,gpio-led会借助于工作队列去设置亮度,所以不用担心会被阻塞。


default-on触发器
在/driver/leds/ledtrig-default-on.c中实现了一个名为“default-on”的触发器。这个触发器只定义了activate成员函数。它的activate函数的定义如下:
static void defon_trig_activate(struct led_classdev *led_cdev)
{
     led_set_brightness(led_cdev, LED_FULL);
}
也就是说,点亮led只能是最亮的亮度,无法调节。一旦ledl_classdev与之建立了连接,就一直处于最亮的状态,直到取消和触发器的连接。


心跳灯触发器
在/driver/leds/ledtrig-heartbeat.c中定义了一个名为"heartbeat"的心跳触发器,它可以控制所有与之建立连接的led会不停的闪烁。这个触发器用来指示内核是否已经挂掉。如果与之建立连接的led不再闪烁了,说明内核已经挂掉了。这就是“心跳”的含义,和从人的心脏是否跳动来判断人是否死亡的原理是类似的。


IDE硬盘指示灯触发器
在/driver/leds/ledtrig-ide-disk.c中定义了一个名为“ide-disk”的IDE硬盘指示灯触发器,与之建立连接的led可以指示硬盘的忙碌状态。这个触发器并没有active接口,因此不会自动闪烁。当内核中的其他模块调用以下函数的时候硬盘指示灯就会亮闪一下:
void ledtrig_ide_activity(void);
这个函数是全局函数,内核空间都可以调用。每调用一次就闪烁一下。具体怎么用,完全依赖于IDE驱动。
可以有多个led_classdev和这个触发器建立连接。每次调用ledtrig_ide_activity,所有与之连接的led都会闪烁一下。
使用ledtrig_ide_activity这个函数的模块应该包含<linux/leds.h>这个头文件。


闪烁定时触发器
在/driver/leds/ledtrig-timer.c中定义了一个名为“timer”的触发器。当某个led_classdev与之连接后,这个触发器会在/sys/class/leds/<device>/下创建两个属性文件delay_on/delay_off。用户空间往这两个文件中写入数据后,相应的led会按照设置的高低电平的时间(单位毫秒)来闪烁。如果led_classdev注册了硬件闪烁的接口led_cdev->blink_set就是用硬件控制闪烁,否则用软件定时器来控制闪烁。
void led_blink_set(struct led_classdev *led_cdev,
           unsigned long *delay_on,
           unsigned long *delay_off){
    del_timer_sync(&led_cdev->blink_timer);
    if (led_cdev->blink_set &&
        !led_cdev->blink_set(led_cdev, delay_on, delay_off))
        return;
    /* blink with 1 Hz as default if nothing specified */
    if (!*delay_on && !*delay_off)
        *delay_on = *delay_off = 500;

    led_set_software_blink(led_cdev, *delay_on, *delay_off);
}

led_classdev的sysfs属性文件
现在假设有一个名为“REDLED”的led_classdev被注册了,那么会出现/sys/class/leds/REDLED这个目录,这个目录下默认有brightness和trigger这两个属性文件,分别可以设置/读取led的亮度和触发器。如果和触发器“timer”建立了连接,还会有delay_on和delay_off,这两个文件用于设置/读取闪烁的熄灭和点亮的时间,单位是毫秒。
LED子系统的使用
系统定义了四个默认触发器:default_on、心跳触发器、硬盘灯触发器、闪烁触发器。除了硬盘灯触发器,其他触发器没有留从其它内核模块访问的接口。led子系统的目的主要是给用户空间控制led的。当然可以定义自己的触发器并留给其它模块访问的接口。



抱歉!评论已关闭.