【TINY4412】LINUX移植笔记:(7)LED驱动分析

【TINY4412】LINUX移植笔记:(7)LED驱动分析

宿主机 : 虚拟机 Ubuntu 16.04 LTS / X64
目标板[底板]: Tiny4412SDK - 1506
目标板[核心板]: Tiny4412 - 1412
LINUX内核: 4.12.0
交叉编译器: gcc-arm-none-eabi-5_4-2016q3
日期: 2017-7-22 12:08:02
作者: SY

简介

Tiny4412开发板上有4个LED自定义指示灯,我们使用LED1作为Heartbeat心跳。只要系统正常运行,小灯便永不停息,就像人的心跳一样!

对于LINUX来说,最好的方式是:只需一种驱动便可适配多个设备,驱动可以自由的管理设备的安装、卸载、打开、关闭等操作。

设备树本质上和xml文件类似,主要用来被内核做字符串解析,是一个静态文本。就像安卓的页面配置文件activity_main.xml用来描述页面的布局,元素等。

分析设备

LINUX 3.5以后,都是使用内核设备树配置外设。也就是说,很多驱动在LINUX内核源码层次,不用修改,只要配置dts,然后在menuconfig中打开就行了。

root@ubuntu:/opt/linux-4.12# more arch/arm/boot/dts/exynos4412-tiny4412.dts
leds {
  compatible = "gpio-leds";

  led1 {
    label = "led1";
    gpios = <&gpm4 0 GPIO_ACTIVE_LOW>;
    default-state = "off";
    linux,default-trigger = "heartbeat";
  };

  led2 {
    label = "led2";
    gpios = <&gpm4 1 GPIO_ACTIVE_LOW>;
    default-state = "off";
  };

  led3 {
    label = "led3";
    gpios = <&gpm4 2 GPIO_ACTIVE_LOW>;
    default-state = "off";
  };

  led4 {
    label = "led4";
    gpios = <&gpm4 3 GPIO_ACTIVE_LOW>;
    default-state = "off";
    linux,default-trigger = "mmc0";
  };
};

本次主要分析led1

compatible = "gpio-leds";主要匹配:./drivers/leds/leds-gpio.c

static const struct of_device_id of_gpio_leds_match[] = {
    { .compatible = "gpio-leds", },
    {},
};

ledprobe时,将设备树中的匹配到的节点全部进行初始化。

  • 属性label在下面解释:

    static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev)
    {
        ret = fwnode_property_read_string(child, "label", &led.name);
    }
    • 属性gpios在下面解释:
    static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev)
    {
    led.gpiod = devm_fwnode_get_gpiod_from_child(dev, NULL, child,
                                 GPIOD_ASIS,
                                 led.name); <--
      devm_fwnode_get_index_gpiod_from_child <--
      for (i = 0; i < ARRAY_SIZE(gpio_suffixes); i++) {
          if (con_id)
            snprintf(prop_name, sizeof(prop_name), "%s-%s",
                     con_id, gpio_suffixes[i]);
          else
            snprintf(prop_name, sizeof(prop_name), "%s",
                     gpio_suffixes[i]);
    
          desc = fwnode_get_named_gpiod(child, prop_name, index, flags,
                                        label);
          if (!IS_ERR(desc) || (PTR_ERR(desc) != -ENOENT))
            break;
      }
        static const char * const gpio_suffixes[] = { "gpios", "gpio" };
        也就是说属性gpios也可以写成gpio,内核也可以识别
    }
    • 属性default-state = "off"在下面解释:
    static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev)
    {
      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;
      }
    }
    • 属性linux,default-trigger在下面解释:
    static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev)
    {
      fwnode_property_read_string(child, "linux,default-trigger",
                          &led.default_trigger);
    }
root@ubuntu:/opt/linux-4.12# make menuconfig
Device Drivers  --->
    [*] LED Support  --->
        <*>   LED Class Support
        <*>   LED Support for GPIO connected LEDs
        [*]   LED Trigger support  ---> 
            <*>   LED Heartbeat Trigger

支持三种配置方式:

< >:不支持

:以模块方式加载

<*>:编译进内核

分析驱动

我们采用从后向前分析的方式,我们的目的是让LED1实现心跳,首先要找到实现心跳的驱动位置:

./drivers/leds/trigger/ledtrig-heartbeat.c

module_init(heartbeat_trig_init); -->
static int __init heartbeat_trig_init(void)
{
    int rc = led_trigger_register(&heartbeat_led_trigger);

    if (!rc) {
        atomic_notifier_chain_register(&panic_notifier_list,
                           &heartbeat_panic_nb);
        register_reboot_notifier(&heartbeat_reboot_nb);
    }
    return rc;
}

static struct led_trigger heartbeat_led_trigger = {
    .name     = "heartbeat",
    .activate = heartbeat_trig_activate,
    .deactivate = heartbeat_trig_deactivate,
};

led_trigger_register./drivers/leds/led-triggers.c中定义。

int led_trigger_register(struct led_trigger *trig)
{
    ...
    /* Add to the list of led triggers */    
    list_add_tail(&trig->next_trig, &trigger_list);
    [1] 这样 heartbeat_led_trigger 被添加到链表中,通过name字段来标识

    /* Register with any LEDs that have this as a default trigger */
    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, trig->name))
            led_trigger_set(led_cdev, trig);
        up_write(&led_cdev->trigger_lock);
    }
    [2] 遍历leds_list链表,那么肯定有地方要添加元素到链表,找到函数 
    leds_list <--
    of_led_classdev_register <--
    devm_of_led_classdev_register <--
    create_gpio_led <--
    gpio_led_probe <--                      
        static struct platform_driver gpio_led_driver = {
          .probe        = gpio_led_probe,
          .shutdown = gpio_led_shutdown,
          .driver       = {
            .name   = "leds-gpio",
            .of_match_table = of_gpio_leds_match,
          },
        };
    module_platform_driver(gpio_led_driver); <--
        /* module_platform_driver() - Helper macro for drivers that don't do
         * anything special in module init/exit.  This eliminates a lot of
         * boilerplate.  Each module may only use this macro once, and
         * calling it replaces module_init() and module_exit()
         */
        #define module_platform_driver(__platform_driver) \
                            module_driver(__platform_driver, platform_driver_register, \
                                    platform_driver_unregister)                       
}

[1]
struct led_trigger {
    /* Trigger Properties */
    const char   *name;
    void        (*activate)(struct led_classdev *led_cdev);
    void        (*deactivate)(struct led_classdev *led_cdev);

    /* LEDs under control by this trigger (for simple triggers) */
    rwlock_t      leddev_list_lock;
    struct list_head  led_cdevs;

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

[2]
./drivers/leds/led-class.c
int of_led_classdev_register(struct device *parent, struct device_node *np,
                struct led_classdev *led_cdev)
{
    ...
    list_add_tail(&led_cdev->node, &leds_list);
}

我们在顺序执行流程整理一下:[分支A]

module_platform_driver(gpio_led_driver);
gpio_led_driver --> 
gpio_led_probe -->
gpio_leds_create -->
create_gpio_led -->
devm_of_led_classdev_register -->
of_led_classdev_register -->
    list_add_tail(&led_cdev->node, &leds_list); # leds_list链表产生了节点

再来看另一个分支:[分支B]

module_init(heartbeat_trig_init);
heartbeat_trig_init -->
led_trigger_register -->
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, trig->name))
        led_trigger_set(led_cdev, trig);
    up_write(&led_cdev->trigger_lock);
} # 遍历leds_list链表

这样两个分支便产生了关联,分支A先执行,然后分支B执行。其实也很好理解,分支A在做底层的初始化和配置工作,分支B是中间接口层,向下连接底层驱动,向上提供访问接口。

如果要控制LED,需要调用函数:

static struct led_trigger heartbeat_led_trigger = {
    .name     = "heartbeat",
    .activate = heartbeat_trig_activate,
    .deactivate = heartbeat_trig_deactivate,
};

通过搜索,找到./drivers/leds/led-triggers.c中的函数:

void led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trig)
{
    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);
        cancel_work_sync(&led_cdev->set_brightness_work);
        led_stop_software_blink(led_cdev);
        if (led_cdev->trigger->deactivate)      # 使无效
            led_cdev->trigger->deactivate(led_cdev);
        led_cdev->trigger = NULL;
        led_set_brightness(led_cdev, LED_OFF);
    }
    if (trig) {
        write_lock_irqsave(&trig->leddev_list_lock, flags);
        list_add_tail(&led_cdev->trig_list, &trig->led_cdevs);
        write_unlock_irqrestore(&trig->leddev_list_lock, flags);
        led_cdev->trigger = trig;
        if (trig->activate)                     # 使生效
            trig->activate(led_cdev);
    }
}

搜索led_trigger_set的调用者:

ssize_t led_trigger_store(struct device *dev, struct device_attribute *attr,
        const char *buf, size_t count)
{
    led_trigger_set(led_cdev, trig);
}

搜索led_trigger_store的调用者:

#ifdef CONFIG_LEDS_TRIGGERS
static DEVICE_ATTR(trigger, 0644, led_trigger_show, led_trigger_store);
static struct attribute *led_trigger_attrs[] = {
    &dev_attr_trigger.attr,
    NULL,
};
static const struct attribute_group led_trigger_group = {
    .attrs = led_trigger_attrs,
};
#endif

参考[1],上述代码可以修改为以下形式:
static device_attribute dev_attr_trigger = {
    .attr = {.trigger = __stringify(trigger),               \
         .mode = VERIFY_OCTAL_PERMISSIONS(0644) },      \
    .show   = led_trigger_show,                     \
    .store  = led_trigger_store,                        \
};
static struct attribute *led_trigger_attrs[] = {
    &dev_attr_trigger.attr,
    NULL,
};
static const struct attribute_group led_trigger_group = {
    .attrs = led_trigger_attrs,
};

[1]
#define DEVICE_ATTR(_name, _mode, _show, _store) \
    struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)
#define __ATTR(_name, _mode, _show, _store) {               \
    .attr = {.name = __stringify(_name),                \
         .mode = VERIFY_OCTAL_PERMISSIONS(_mode) },     \
    .show   = _show,                        \
    .store  = _store,                       \
}

搜索led_trigger_group调用者:

static const struct attribute_group *led_groups[] = {
    &led_group,
#ifdef CONFIG_LEDS_TRIGGERS
    &led_trigger_group,
#endif
    NULL,
};

搜索led_groups调用者:

static int __init leds_init(void)
{
    leds_class = class_create(THIS_MODULE, "leds");
    if (IS_ERR(leds_class))
        return PTR_ERR(leds_class);
    leds_class->pm = &leds_class_dev_pm_ops;
    leds_class->dev_groups = led_groups;
    return 0;
}

subsys_initcall(leds_init);

再次总结:led-class.cled类向外提供的通用接口,调用顺序:

module_platform_driver(gpio_led_driver);  -->   # ./drivers/leds/leds-gpio.c
module_init(heartbeat_trig_init); -->           # ./drivers/leds/trigger/heartbeat.c
led_trigger_set(); -->                          # ./drivers/leds/led-triggers.c
subsys_initcall(leds_init); -->                 # ./drivers/leds/led-class.c

对于LED心跳来说,咚咚两下,就是说需要使用状态机驱动,在切换状态时,修改定时器的定时周期即可。

最核心的执行体为:led_heartbeat_function

创建定时器:

static void heartbeat_trig_activate(struct led_classdev *led_cdev)
{
    setup_timer(&heartbeat_data->timer,
            led_heartbeat_function, (unsigned long) led_cdev);
}

只执行一次,将led_heartbeat_function作为定时器溢出的回调函数,用来切换状态机。

修改定时器:

static void led_heartbeat_function(unsigned long data)
{
    mod_timer(&heartbeat_data->timer, jiffies + delay);
}

每次定时器溢出,执行一次。

关闭定时器:

static void heartbeat_trig_deactivate(struct led_classdev *led_cdev)
{
    del_timer_sync(&heartbeat_data->timer);
}

永远不会执行,心跳永远不会停止。

LED打开时机:

led_trigger_store -->

ssize_t led_trigger_store(struct device *dev, struct device_attribute *attr,
                            const char *buf, size_t count)
{
  list_for_each_entry(trig, &trigger_list, next_trig) {
    if (sysfs_streq(buf, trig->name)) { 
      down_write(&led_cdev->trigger_lock);
      led_trigger_set(led_cdev, trig);  -->
        up_write(&led_cdev->trigger_lock);

      up_read(&triggers_list_lock);
      goto unlock;
    }
  }
}

void led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trig)
{
  if (trig) {
    write_lock_irqsave(&trig->leddev_list_lock, flags);
    list_add_tail(&led_cdev->trig_list, &trig->led_cdevs);
    write_unlock_irqrestore(&trig->leddev_list_lock, flags);
    led_cdev->trigger = trig;
    if (trig->activate)
      trig->activate(led_cdev); -->
  }
}

static void heartbeat_trig_activate(struct led_classdev *led_cdev)
{
  setup_timer(&heartbeat_data->timer,
              led_heartbeat_function, (unsigned long) led_cdev); --> # Open HeartBeat
  led_heartbeat_function(heartbeat_data->timer.data);          
}

设备位置

LED 会在 /sys/bus/platform/devices/leds/leds/ 目录生成文件。读写文件可以操作 LED 设备了。

查看触发器:

[root@TINY4412:~]# cat /sys/bus/platform/devices/leds/leds/led4/trigger 
none kbd-scrolllock kbd-numlock kbd-capslock kbd-kanalock kbd-shiftlock kbd-altgrlock kbd-ctrllock kbd-altlock kbd-shiftllock kbd-shiftrlock kbd-ctrlllock kbd-ctrlrlock usbport usb-gadget usb-host [mmc0] mmc1 heartbeat 

[xxx] 表示当前触发器被选中,当前设备被读写时, LED 会有相应动作。

LED4 的触发器为 mmc0mmc0 表示 SD卡。 当我们拔掉 SD卡 时, LED4 开始闪烁。

上述列表中的触发器都是可以配置的。

你可能感兴趣的:(TINY4412,LINUX)