Linux下LED子系统分析

Linux下LED子系统分析

本文基于Linux Kernel3.0.8版本

一、主要文件和结构

linux的led子系统的源码路径

include/Linux/leds.h
drivers/leds/*

从drivers/leds/Makefile中可以看到LED子系统的主要文件有几类:
LED核心:leds.h、led-core.c、led-class.c、led-triggers.c(其中led-triggers又分为了timer、ide-disk、heartbeat、backlight、gpio、default-on等)
应用LED的各种Drivers:基于Platform平台总线,SPI总线的应用

# LED Core
obj-$(CONFIG_NEW_LEDS)         += led-core.o
obj-$(CONFIG_LEDS_CLASS)       += led-class.o
obj-$(CONFIG_LEDS_TRIGGERS)        += led-triggers.o

# LED Platform Drivers
......
obj-$(CONFIG_LEDS_GPIO)            += leds-gpio.o
......

# LED SPI Drivers
obj-$(CONFIG_LEDS_DAC124S085)      += leds-dac124s085.o

# LED Triggers
obj-$(CONFIG_LEDS_TRIGGER_TIMER)   += ledtrig-timer.o
obj-$(CONFIG_LEDS_TRIGGER_IDE_DISK)    += ledtrig-ide-disk.o
obj-$(CONFIG_LEDS_TRIGGER_HEARTBEAT)   += ledtrig-heartbeat.o
obj-$(CONFIG_LEDS_TRIGGER_BACKLIGHT)   += ledtrig-backlight.o
obj-$(CONFIG_LEDS_TRIGGER_GPIO)        += ledtrig-gpio.o
obj-$(CONFIG_LEDS_TRIGGER_DEFAULT_ON)  += ledtrig-default-on.o
obj-$(CONFIG_LEDS_TRIGGER_SLEEP)   += ledtrig-sleep.o

include/linux/leds.h

//Led的亮度,分为三等级,关、中间、最亮。
enum led_brightness {
    LED_OFF     = 0,
#if defined(CONFIG_MACH_Q1_BD)   || defined(CONFIG_MACH_U1_NA_USCC)
    LED_BRIGHTNESS_LEVEL1   = 1,
    LED_BRIGHTNESS_LEVEL2   = 2,
    LED_BRIGHTNESS_LEVEL3   = 3,
    LED_BRIGHTNESS_MAX_LEVEL    = LED_BRIGHTNESS_LEVEL3,
#endif
    LED_HALF    = 127,
    LED_FULL    = 255,
};

led_classdev 结构:

struct led_classdev {
    const char      *name;  // Led的名字  
    int          brightness; //Led亮度
    int          max_brightness; //led最大亮度 
    int          flags;

    /* Lower 16 bits reflect status */
#define LED_SUSPENDED       (1 << 0)
    /* Upper 16 bits reflect control information */
#define LED_CORE_SUSPENDRESUME  (1 << 16)

    /* Set LED brightness level */
    /* Must not sleep, use a workqueue if needed */
    void        (*brightness_set)(struct led_classdev *led_cdev,
                      enum led_brightness brightness); // 用于设置亮度的函数指针
    /* Get LED brightness level */
    enum led_brightness (*brightness_get)(struct led_classdev *led_cdev); // 用于获取亮度的函数指针

    /*
     * Activate hardware accelerated blink, delays are in milliseconds
     * and if both are zero then a sensible default should be chosen.
     * The call should adjust the timings in that case and if it can't
     * match the values specified exactly.
     * Deactivate blinking again when the brightness is set to a fixed
     * value via the brightness_set() callback.
     */
    int     (*blink_set)(struct led_classdev *led_cdev,
                     unsigned long *delay_on,
                     unsigned long *delay_off); // 用来设置闪烁时点亮和熄灭时长

    struct device       *dev;
    struct list_head     node;          /* LED Device list LED设备链表 */
    const char      *default_trigger;   /* Trigger to use 该led_classdev使用的trigger的名字,通过这个名字在trigger_list中找到对应的trigger */

    unsigned long        blink_delay_on, blink_delay_off; // 闪烁的开关时间
    struct timer_list    blink_timer; // 闪烁的定时器链表
    int          blink_brightness; // 闪烁的亮度

#ifdef CONFIG_LEDS_TRIGGERS
    /* Protects the trigger data below */
    struct rw_semaphore  trigger_lock; // trigger的锁

    struct led_trigger  *trigger; // Led的trigger
    struct list_head     trig_list; // trigger链表
    void            *trigger_data; // trigger数据
#endif
}

led_trigger 结构:

struct led_trigger {
    /* Trigger Properties */
    const char   *name; //trigger的名字 
    void        (*activate)(struct led_classdev *led_cdev); //激活trigger
    void        (*deactivate)(struct led_classdev *led_cdev); //关闭trigger

    /* LEDs under control by this trigger (for simple triggers) */
    rwlock_t      leddev_list_lock;
    struct list_head  led_cdevs;  // 该trigger支持的led设备链表 

    /* Link to next registered trigger */
    struct list_head  next_trig;
};

gpio_led 结构适用于leds-gpio.c:

/* For the leds-gpio driver */
struct gpio_led {
    const char *name; //led的名字
    const char *default_trigger; //默认的trigger  
    unsigned    gpio; //gpio口 
    unsigned    active_low : 1;
    unsigned    retain_state_suspended : 1;
    unsigned    default_state : 2;
    /* default_state should be one of LEDS_GPIO_DEFSTATE_(ON|OFF|KEEP) */
};
struct gpio_led_platform_data {
    int         num_leds; //led的个数 
    const struct gpio_led *leds; //led结构体 

#define GPIO_LED_NO_BLINK_LOW   0   /* No blink GPIO state low */
#define GPIO_LED_NO_BLINK_HIGH  1   /* No blink GPIO state high */
#define GPIO_LED_BLINK      2   /* Please, blink */
    int     (*gpio_blink_set)(unsigned gpio, int state,
                    unsigned long *delay_on,
                    unsigned long *delay_off);
};

led-core.c

声明了leds的链表和锁

DECLARE_RWSEM(leds_list_lock);
EXPORT_SYMBOL_GPL(leds_list_lock);

LIST_HEAD(leds_list);
EXPORT_SYMBOL_GPL(leds_list);

led-class.c

  1. leds_init
    创建leds class,赋值suspend和resume以及dev_attrs。
static int __init leds_init(void)
{
    leds_class = class_create(THIS_MODULE, "leds"); // /sys/class/leds/
    if (IS_ERR(leds_class))
        return PTR_ERR(leds_class);
    leds_class->suspend = led_suspend;
    leds_class->resume = led_resume;
    leds_class->dev_attrs = led_class_attrs;
    return 0;
}

led_class_attrs里面可以设置各项属性的权限,每个属性对应leds class底下的一个文件,应用程序去read属性文件时,会调用到show函数(比如brightness的led_brightness_show),应用程序去write属性文件时,会调用到store函数(比如brightness的led_brightness_store)

static struct device_attribute led_class_attrs[] = {
    // /sys/class/leds/brightness
    __ATTR(brightness, 0644, led_brightness_show, led_brightness_store),
    __ATTR(max_brightness, 0444, led_max_brightness_show, NULL),
#ifdef CONFIG_LEDS_TRIGGERS
    __ATTR(trigger, 0644, led_trigger_show, led_trigger_store),
#endif
    __ATTR_NULL,
};

2.led_classdev_register

/**
 * led_classdev_register - register a new object of led_classdev class.
 * 注册一个led_classdev实例
 * @parent: The device to register.
 * @led_cdev: the led_classdev structure for this device.
 */
int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
{
    led_cdev->dev = device_create(leds_class, parent, 0, led_cdev,
                      "%s", led_cdev->name);
    if (IS_ERR(led_cdev->dev))
        return PTR_ERR(led_cdev->dev);

#ifdef CONFIG_LEDS_TRIGGERS
    init_rwsem(&led_cdev->trigger_lock);
#endif
    /* add to the list of leds */
    down_write(&leds_list_lock);
    list_add_tail(&led_cdev->node, &leds_list); // 将led_classdev实例添加到leds_list链表中
    up_write(&leds_list_lock);

    if (!led_cdev->max_brightness)
        led_cdev->max_brightness = LED_FULL;

    led_update_brightness(led_cdev);

    init_timer(&led_cdev->blink_timer); // 初始化blink_timer定时器
    led_cdev->blink_timer.function = led_timer_function; // 设置定时器处理函数
    led_cdev->blink_timer.data = (unsigned long)led_cdev; // 定时器链表的data

#ifdef CONFIG_LEDS_TRIGGERS
    led_trigger_set_default(led_cdev); // 设置trigger
#endif

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

    return 0;
}

led-triggers.c

上面注册led_classdev时,设置trigger会用到led-triggers.c里面的led_trigger_set_default函数

void led_trigger_set_default(struct led_classdev *led_cdev)
{
    struct led_trigger *trig;

    if (!led_cdev->default_trigger)
        return;

    down_read(&triggers_list_lock);
    down_write(&led_cdev->trigger_lock);
    //遍历trigger_list链表中的每一个trigger,如果名字跟led_cdev->default_trigger一样的话,将该trigger设置到led_cdev里
    list_for_each_entry(trig, &trigger_list, next_trig) {
        if (!strcmp(led_cdev->default_trigger, trig->name))
            led_trigger_set(led_cdev, trig); //将
    }
    up_write(&led_cdev->trigger_lock);
    up_read(&triggers_list_lock);
}

led_trigger_set会先删除掉led_cdev中已有的trigger

/* Caller must ensure led_cdev->trigger_lock held */
void led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trigger)
{
    unsigned long flags;

    /* Remove any existing trigger */
    if (led_cdev->trigger) {
        write_lock_irqsave(&led_cdev->trigger->leddev_list_lock, flags);
        list_del(&led_cdev->trig_list);
        write_unlock_irqrestore(&led_cdev->trigger->leddev_list_lock,
            flags);
        if (led_cdev->trigger->deactivate)
            led_cdev->trigger->deactivate(led_cdev);
        led_cdev->trigger = NULL;
        led_brightness_set(led_cdev, LED_OFF);
    }
    if (trigger) {
        write_lock_irqsave(&trigger->leddev_list_lock, flags);
        // 将trigger中的led_cdevs加入到led_cdev的trig_list链表中
        list_add_tail(&led_cdev->trig_list, &trigger->led_cdevs);
        write_unlock_irqrestore(&trigger->leddev_list_lock, flags);
        led_cdev->trigger = trigger; //设置该Led用的trigger
        if (trigger->activate)
            trigger->activate(led_cdev); // 激活trigger
    }
}

注册trigger
1.确保trigger_list中没有同名的trigger
2.将trigger添加到trigger_list链表中
3.遍历leds_list链表,如果有default_trigger跟该trigger一样的led_cdev,调用led_trigger_set,将trigger添加到led_cdev->trig_list,并激活trigger

/* LED Trigger Interface */

int led_trigger_register(struct led_trigger *trigger)
{
    struct led_classdev *led_cdev;
    struct led_trigger *trig;

    rwlock_init(&trigger->leddev_list_lock);
    INIT_LIST_HEAD(&trigger->led_cdevs);

    down_write(&triggers_list_lock);
    /* Make sure the trigger's name isn't already in use, 确保trigger_list中没有同名的trigger */
    list_for_each_entry(trig, &trigger_list, next_trig) {
        if (!strcmp(trig->name, trigger->name)) {
            up_write(&triggers_list_lock);
            return -EEXIST;
        }
    }
    /* Add to the list of led triggers, 将trigger添加到trigger_list链表中*/
    list_add_tail(&trigger->next_trig, &trigger_list);
    up_write(&triggers_list_lock);

    /* Register with any LEDs that have this as a default trigger */
    /* 遍历leds_list链表,如果有default_trigger跟该trigger一样的,调用led_trigger_set,将trigger添加到led_cdev->trig_list,并激活trigger */
    down_read(&leds_list_lock);
    list_for_each_entry(led_cdev, &leds_list, node) {
        down_write(&led_cdev->trigger_lock);
        if (!led_cdev->trigger && led_cdev->default_trigger &&
                !strcmp(led_cdev->default_trigger, trigger->name))
            led_trigger_set(led_cdev, trigger);
        up_write(&led_cdev->trigger_lock);
    }
    up_read(&leds_list_lock);

    return 0;
}

二、LED子系统中的trigger

ledtrig-default-on.c用于设置led为最大亮度

static int __init defon_trig_init(void)
{
    return led_trigger_register(&defon_led_trigger);
}
static struct led_trigger defon_led_trigger = {
    .name     = "default-on",
    .activate = defon_trig_activate,
};
static void defon_trig_activate(struct led_classdev *led_cdev)
{
    // 设置led为最大亮度
    led_set_brightness(led_cdev, led_cdev->max_brightness);
}
// drivers/leds/leds.h
static inline void led_set_brightness(struct led_classdev *led_cdev,
                    enum led_brightness value)
{
    if (value > led_cdev->max_brightness)
        value = led_cdev->max_brightness;
    led_cdev->brightness = value;
    if (!(led_cdev->flags & LED_SUSPENDED))
        // 设置亮度
        led_cdev->brightness_set(led_cdev, value);
}

ledtrig-backlight.c

struct bl_trig_notifier {
    struct led_classdev *led;  //led子系统设备 
    int brightness; //亮度
    int old_status;
    struct notifier_block notifier; //内核通知链 
    unsigned invert; //反转
};

模块初始化,注册bl_led_trigger

static int __init bl_trig_init(void)
{
    return led_trigger_register(&bl_led_trigger);
}

bl_led_trigger映射

static struct led_trigger bl_led_trigger = {
    .name       = "backlight",
    .activate   = bl_trig_activate,//激活backlight的trigger时调用
    .deactivate = bl_trig_deactivate//关闭backlight的trigger时调用
};

activate操作函数
1. 分配一个bl_trig_notifier结构
2. device_create_file创建设备文件
3.设置bl_trig_notifier结构
4.fb_register_client将n->notifier注册到framebuffer中的fb_notifier_list中,一旦framebuffer驱动中有事件,就会调用内核通知链中注册好的函数fb_notifier_callback

关于内核通知链参考了:http://blog.csdn.net/eastmoon502136/article/details/37606487
大多数内核子系统都是相互独立的,因此某个子系统可能对其它子系统产生的事件感兴趣。为了满足这个需求,也即是让某个子系统在发生某个事件时通知其它的子系统,Linux内核提供了通知链的机制。通知链表只能够在内核的子系统之间使用,而不能够在内核与用户空间之间进行事件的通知。通知链表是一个函数链表,链表上的每一个节点都注册了一个函数。当某个事情发生时,链表上所有节点对应的函数就会被执行。所以对于通知链表来说有一个通知方与一个接收方。在通知这个事件时所运行的函数由被通知方决定,实际上也即是被通知方注册了某个函数,在发生某个事件时这些函数就得到执行。其实和系统调用signal的思想差不多。
通知链技术可以概括为:事件的被通知者将事件发生时应该执行的操作通过函数指针方式保存在链表(通知链)中,然后当事件发生时通知者依次执行链表中每一个元素的回调函数完成通知。

static void bl_trig_activate(struct led_classdev *led)
{
    int ret;

    struct bl_trig_notifier *n;
    // 分配一个bl_trig_notifier结构
    n = kzalloc(sizeof(struct bl_trig_notifier), GFP_KERNEL);
    led->trigger_data = n;
    if (!n) {
        dev_err(led->dev, "unable to allocate backlight trigger\n");
        return;
    }
    // device_create_file创建设备文件
    ret = device_create_file(led->dev, &dev_attr_inverted);
    if (ret)
        goto err_invert;
    // 设置bl_trig_notifier结构
    n->led = led;
    n->brightness = led->brightness;
    n->old_status = UNBLANK;
    n->notifier.notifier_call = fb_notifier_callback;
    // 将n->notifier注册到framebuffer中的fb_notifier_list中
    ret = fb_register_client(&n->notifier);
    if (ret)
        dev_err(led->dev, "unable to register backlight trigger\n");

    return;

err_invert:
    led->trigger_data = NULL;
    kfree(n);
}

如果触发了FB_EVENT_BLANK,那么就执行fb_notifier_callback的相应操作

static int fb_notifier_callback(struct notifier_block *p,
                unsigned long event, void *data)
{
    struct bl_trig_notifier *n = container_of(p,
                    struct bl_trig_notifier, notifier);
    struct led_classdev *led = n->led;
    struct fb_event *fb_event = data;
    int *blank = fb_event->data;
    int new_status = *blank ? BLANK : UNBLANK;

    switch (event) {
    case FB_EVENT_BLANK :
        if (new_status == n->old_status)
            break;

        if ((n->old_status == UNBLANK) ^ n->invert) {
            n->brightness = led->brightness;
            led_set_brightness(led, LED_OFF);
        } else {
            led_set_brightness(led, n->brightness);
        }

        n->old_status = new_status;

        break;
    }

    return 0;
}

上面创建设备文件的时候用了

ret = device_create_file(led->dev, &dev_attr_inverted);

这里的dev_attr_inverted从哪里来?

static DEVICE_ATTR(inverted, 0644, bl_trig_invert_show, bl_trig_invert_store);

DEVICE_ATTR宏定义:

// include/linux/device.h
#define DEVICE_ATTR(_name, _mode, _show, _store) \
struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)

将参数代入宏定义,相当于实例化一个名字为dev_attr_inverted 的device_attribute 结构。

struct device_attribute dev_attr_inverted = __ATTR(inverted, 0644, bl_trig_invert_show, bl_trig_invert_store)

应用程序read设备节点的时候调用bl_trig_invert_show:

static ssize_t bl_trig_invert_show(struct device *dev,
        struct device_attribute *attr, char *buf)
{
    struct led_classdev *led = dev_get_drvdata(dev);
    struct bl_trig_notifier *n = led->trigger_data;

    return sprintf(buf, "%u\n", n->invert);
}

应用程序write设备节点的时候调用bl_trig_invert_store:

static ssize_t bl_trig_invert_store(struct device *dev,
        struct device_attribute *attr, const char *buf, size_t num)
{
    struct led_classdev *led = dev_get_drvdata(dev);
    struct bl_trig_notifier *n = led->trigger_data;
    unsigned long invert;
    int ret;

    ret = strict_strtoul(buf, 10, &invert);
    if (ret < 0)
        return ret;

    if (invert > 1)
        return -EINVAL;

    n->invert = invert;

    /* After inverting, we need to update the LED. */
    if ((n->old_status == BLANK) ^ n->invert)
        led_set_brightness(led, LED_OFF);
    else
        led_set_brightness(led, n->brightness);

    return num;
}

ledtrig-timer.c

static int __init timer_trig_init(void)
{
    return led_trigger_register(&timer_led_trigger);
}

创建一个led_trigger并映射:

static struct led_trigger timer_led_trigger = {
    .name     = "timer",
    .activate = timer_trig_activate,
    .deactivate = timer_trig_deactivate,
};

当某个led_classdev与之连接后,激活timer_led_trigger 时(通过echo timer > /sys/class/leds/led1/trigger)调用timer_trig_activate:
1. 这个触发器会在/sys/class/leds//下创建两个文件delay_on和delay_off,用户空间往这两个文件中写入数据后,相应的led会按照设置的高低电平的时间(ms)来闪烁。
2. 如果led_classdev注册了硬件闪烁的接口led_cdev->blink_set就是用硬件控制闪烁,否则用软件定时器来控制闪烁。

static DEVICE_ATTR(delay_on, 0666, led_delay_on_show, led_delay_on_store);
static DEVICE_ATTR(delay_off, 0666, led_delay_off_show, led_delay_off_store);

static void timer_trig_activate(struct led_classdev *led_cdev)
{
    int rc;

    led_cdev->trigger_data = NULL;

    rc = device_create_file(led_cdev->dev, &dev_attr_delay_on);
    if (rc)
        return;
    rc = device_create_file(led_cdev->dev, &dev_attr_delay_off);
    if (rc)
        goto err_out_delayon;

    led_blink_set(led_cdev, &led_cdev->blink_delay_on,
              &led_cdev->blink_delay_off);

    led_cdev->trigger_data = (void *)1;

    return;

err_out_delayon:
    device_remove_file(led_cdev->dev, &dev_attr_delay_on);
}

ledtrig-heartbeat.c

通过定时来实现类似于心跳的led灯。

struct heartbeat_trig_data {
    unsigned int phase;
    unsigned int period;
    struct timer_list timer;
};
static struct led_trigger heartbeat_led_trigger = {
    .name     = "heartbeat",
    .activate = heartbeat_trig_activate,
    .deactivate = heartbeat_trig_deactivate,
};
static void heartbeat_trig_activate(struct led_classdev *led_cdev)
{
    struct heartbeat_trig_data *heartbeat_data;

    heartbeat_data = kzalloc(sizeof(*heartbeat_data), GFP_KERNEL);
    if (!heartbeat_data)
        return;

    led_cdev->trigger_data = heartbeat_data;
    setup_timer(&heartbeat_data->timer,
            led_heartbeat_function, (unsigned long) led_cdev);
    heartbeat_data->phase = 0;
    led_heartbeat_function(heartbeat_data->timer.data);
}

设置了heartbeat_data->phase,然后调用led_heartbeat_function。

static void led_heartbeat_function(unsigned long data)
{
    struct led_classdev *led_cdev = (struct led_classdev *) data;
    struct heartbeat_trig_data *heartbeat_data = led_cdev->trigger_data;
    unsigned long brightness = LED_OFF;
    unsigned long delay = 0;

    /* acts like an actual heart beat -- ie thump-thump-pause... */
    switch (heartbeat_data->phase) {
    case 0:
        /*
         * The hyperbolic function below modifies the
         * heartbeat period length in dependency of the
         * current (1min) load. It goes through the points
         * f(0)=1260, f(1)=860, f(5)=510, f(inf)->300.
         */
        heartbeat_data->period = 300 +
            (6720 << FSHIFT) / (5 * avenrun[0] + (7 << FSHIFT));
        heartbeat_data->period =
            msecs_to_jiffies(heartbeat_data->period);
        delay = msecs_to_jiffies(70);
        heartbeat_data->phase++;
        brightness = led_cdev->max_brightness;
        break;
    case 1:
        delay = heartbeat_data->period / 4 - msecs_to_jiffies(70);
        heartbeat_data->phase++;
        break;
    case 2:
        delay = msecs_to_jiffies(70);
        heartbeat_data->phase++;
        brightness = led_cdev->max_brightness;
        break;
    default:
        delay = heartbeat_data->period - heartbeat_data->period / 4 -
            msecs_to_jiffies(70);
        heartbeat_data->phase = 0;
        break;
    }

    led_set_brightness(led_cdev, brightness);
    mod_timer(&heartbeat_data->timer, jiffies + delay);
}

ledtrig-ide-disk.c

通过定时器实现类似于硬盘灯的指示。

static int __init ledtrig_ide_init(void)
{
    led_trigger_register_simple("ide-disk", &ledtrig_ide);
    return 0;
}
static void ledtrig_ide_timerfunc(unsigned long data)
{
    if (ide_lastactivity != ide_activity) {
        ide_lastactivity = ide_activity;
        /* INT_MAX will set each LED to its maximum brightness */
        led_trigger_event(ledtrig_ide, INT_MAX);
        mod_timer(&ledtrig_ide_timer, jiffies + msecs_to_jiffies(10));
    } else {
        led_trigger_event(ledtrig_ide, LED_OFF);
    }
}

三、分析leds-gpio.c

注册platform平台驱动

static int __init gpio_led_init(void)
{
    return platform_driver_register(&gpio_led_driver);
}
static struct platform_driver gpio_led_driver = {
    .probe      = gpio_led_probe,
    .remove     = __devexit_p(gpio_led_remove),
    .driver     = {
        .name   = "leds-gpio",
        .owner  = THIS_MODULE,
        .of_match_table = of_gpio_leds_match,
    },
};

在自己的平台下添加platform device,当device和dirver匹配后,就会调用driver的probe函数

static int __devinit gpio_led_probe(struct platform_device *pdev)
{
    // 获取platform_device的数据
    struct gpio_led_platform_data *pdata = pdev->dev.platform_data;
    struct gpio_leds_priv *priv;
    int i, ret = 0;

    if (pdata && pdata->num_leds) {
        priv = kzalloc(sizeof_gpio_leds_priv(pdata->num_leds),
                GFP_KERNEL);
        if (!priv)
            return -ENOMEM;

        priv->num_leds = pdata->num_leds;
        for (i = 0; i < priv->num_leds; i++) {
            // 创建LED
            ret = create_gpio_led(&pdata->leds[i],
                          &priv->leds[i],
                          &pdev->dev, pdata->gpio_blink_set);
            if (ret < 0) {
                /* On failure: unwind the led creations */
                for (i = i - 1; i >= 0; i--)
                    delete_gpio_led(&priv->leds[i]);
                kfree(priv);
                return ret;
            }
        }
    } else {
        priv = gpio_leds_create_of(pdev);
        if (!priv)
            return -ENODEV;
    }

    platform_set_drvdata(pdev, priv);

    return 0;
}
static int __devinit create_gpio_led(const struct gpio_led *template,
    struct gpio_led_data *led_dat, struct device *parent,
    int (*blink_set)(unsigned, int, unsigned long *, unsigned long *))
{
    int ret, state;

    led_dat->gpio = -1;

    /* skip leds that aren't available */
    if (!gpio_is_valid(template->gpio)) {
        printk(KERN_INFO "Skipping unavailable LED gpio %d (%s)\n",
                template->gpio, template->name);
        return 0;
    }
    // 申请gpio
    ret = gpio_request(template->gpio, template->name);
    if (ret < 0)
        return ret;
    // 设置gpio_led_data 
    led_dat->cdev.name = template->name;
    led_dat->cdev.default_trigger = template->default_trigger;
    led_dat->gpio = template->gpio;
    led_dat->can_sleep = gpio_cansleep(template->gpio);
    led_dat->active_low = template->active_low;
    led_dat->blinking = 0;
    if (blink_set) {
        led_dat->platform_gpio_blink_set = blink_set;
        led_dat->cdev.blink_set = gpio_blink_set;
    }
    led_dat->cdev.brightness_set = gpio_led_set;
    // 根据gpio的默认状态,设置输出电平
    if (template->default_state == LEDS_GPIO_DEFSTATE_KEEP)
        state = !!gpio_get_value(led_dat->gpio) ^ led_dat->active_low;
    else
        state = (template->default_state == LEDS_GPIO_DEFSTATE_ON);
    led_dat->cdev.brightness = state ? LED_FULL : LED_OFF;
    if (!template->retain_state_suspended)
        led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME;
    // gpio为输出状态,并设置
    ret = gpio_direction_output(led_dat->gpio, led_dat->active_low ^ state);
    if (ret < 0)
        goto err;
    // 初始化工作队列
    INIT_WORK(&led_dat->work, gpio_led_work);
    // 注册led设备
    ret = led_classdev_register(parent, &led_dat->cdev);
    if (ret < 0)
        goto err;

    return 0;
err:
    gpio_free(led_dat->gpio);
    return ret;
}
struct gpio_led_data {
    struct led_classdev cdev;
    unsigned gpio;
    struct work_struct work;
    u8 new_level;
    u8 can_sleep;
    u8 active_low;
    u8 blinking;
    int (*platform_gpio_blink_set)(unsigned gpio, int state,
            unsigned long *delay_on, unsigned long *delay_off);
};

platform设备如何应用?
比如注册了:

Static struct gpio_led gpio_leds[] = {  
    {  
           .name=”my-led”,  
           .default_trigger= “timer”,  
           .gpio= 30,  
           .active_low= 1,  
           .default_state= LEDS_GPIO_DEFSTATE_OFF,  
    }  
}; 

那么在/sys/class/leds/下会有my-led目录,在目录下面会创建两个文件delay_on和delay_off。
可以通过
echo 100 > /sys/class/leds/my-led/delay_on
echo 100 > /sys/class/leds/my-led/delay_off
来控制闪烁的时间。
cat /sys/class/leds/my-led/delay_on
cat /sys/class/leds/my-led/delay_off
来获取当前的delay_on和delay_off的值

四、另一种方式编写LED驱动

前面的leds-gpio.c是采用platform平台总线方式注册platform_driver,用户可以编写程序注册platform_device去匹配,从而调用gpio_led_probe函数最终通过led_classdev_register去注册led设备。
下面的4412 led驱动直接在模块初始化函数里直接分配设置led_classdev并通过led_classdev_register注册led

leds_4412.c


#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include 
#include 
#include 

#include 

struct led_desc {
    int gpio;
    char *name;
};

static struct led_desc led_gpios[] = {
    {EXYNOS4212_GPM4(0), "led1"},
    {EXYNOS4212_GPM4(1), "led2"},
    {EXYNOS4212_GPM4(2), "led3"},
    {EXYNOS4212_GPM4(3), "led4"},
};

struct led_classdev_4412 {
    struct led_classdev cdev;
    int gpio; // led_classdev未包含对gpio的描述
};


static struct led_classdev_4412 *led_devs;
static void  brightness_set_4412(struct led_classdev *led_cdev,
              enum led_brightness brightness)
{
    struct led_classdev_4412 *dev = (struct led_classdev_4412 *)led_cdev;

    led_cdev->brightness = brightness;

    if (brightness != LED_OFF)
        gpio_set_value(dev->gpio, 0);
    else
        gpio_set_value(dev->gpio, 1);
}


static int leds_init(void)
{
    int i;
    int ret;

    /* 1. alloc led_classdev 为每个led分别分配一个led_classdev  */
    led_devs = kzalloc(sizeof(struct led_classdev_4412) * sizeof(led_gpios)/sizeof(led_gpios[0]), GFP_KERNEL);
    if (led_devs == NULL) {
        printk("No memory for device\n");
        return -ENOMEM;
    }

    for (i = 0; i < sizeof(led_gpios)/sizeof(led_gpios[0]); i++)
    {
        // 默认输出高电平
        s3c_gpio_cfgpin(led_gpios[i].gpio, S3C_GPIO_OUTPUT);
        gpio_set_value(led_gpios[i].gpio, 1);

        /* 2. set */
        led_devs[i].cdev.max_brightness = LED_FULL; // 最大亮度255
        led_devs[i].cdev.brightness_set = brightness_set_4412; // 设置亮度的函数
        led_devs[i].cdev.flags = LED_CORE_SUSPENDRESUME;
        led_devs[i].cdev.brightness = LED_OFF; // 默认亮度为LED_OFF,灭
        led_devs[i].cdev.name = led_gpios[i].name; // LED名字
        //led_devs[i].cdev.default_trigger = "timer"; //默认trigger为timer
        led_devs[i].gpio = led_gpios[i].gpio; // gpio端口号

        /* 3. led_classdev_register 注册LED设备 */
        ret = led_classdev_register(NULL, &led_devs[i].cdev);
        if (ret) {
            i--;
            while (i >= 0) {
                led_classdev_unregister(&led_devs[i].cdev);
                i--;
            }
            kfree(led_devs);
            return -EIO;
        }
    }

    return 0;
}

static void leds_exit(void)
{
    int i;
    for (i = 0; i < sizeof(led_gpios)/sizeof(led_gpios[0]); i++)
    {
        led_classdev_unregister(&led_devs[i].cdev);
    }
    kfree(led_devs);
}

module_init(leds_init);
module_exit(leds_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("xy");

用户空间接口

编译烧录后在/sys/class/leds/led1目录下有以下文件:
brightness max_brightness power subsystem trigger uevent
其中brightness、max_brightness 、trigger 是led-class.c中led_class_attrs数组中的三个属性

  1. 点亮LED
    echo 255 > /sys/class/leds/led1/brightness
    cat /sys/class/leds/led1/brightness
    cat /sys/class/leds/led1/max_brightness

  2. 闪烁
    cat /sys/class/leds/led1/trigger
    分析:会看到trigger_list里面的trigger_list:[none] mmc0 mmc1 mmc2 timer
    其中的timer这个trigger是ledtrig-timer.c中模块初始化的时候注册进去的

    echo timer > /sys/class/leds/led1/trigger
    分析这一句会调用led_trigger_store()->led_trigger_set()->trigger->activate(led_cdev);从而调用ledtrig-timer.c文件里的timer_trig_activate(),在/sys/class/leds/led1/下创建delay_on、delay_off两个文件

    echo 100 > /sys/class/leds/led1/delay_on
    echo 200 > /sys/class/leds/led1/delay_off

  3. 关闭LED
    echo 0 > /sys/class/leds/led1/delay_on

    echo 0 > /sys/class/leds/led1/brightness

你可能感兴趣的:(Linux内核驱动)