本文来对Linux内核自带的leds-gpio代码进行分析,在分析之前,先来看下如何配置这个自带的GPIO驱动进内核:
通过“make menuconfig”打开Linux配置菜单,然后按照如下路径打开配置项:
-> Device Drivers -> LED Support (NEW_LEDS [=y]) -> LED Support for GPIO connected LEDs
按照上述路径,选择“LED Support for GPIO connected LEDs”,将其编译进Linux内核,就是在此选项上按下“Y”键,使此选项前面变为“<*>”,如下图所示:
在“LED Support for GPIO connected LEDs”上按下“?”健可以打开此选项的帮助信息,如下图所示:
从上图可以看出,把Linux内部自带的LED灯驱动编译进内核以后,CONFIG_LEDS_GPIO就会等于‘y’,Linux会根据CONFIG_LEDS_GPIO的值来选择如何编译LED灯驱动,如果为‘y’就将其编译进Linux内核。
配置好Linux内核以后退出配置界面,打开.config文件,会找到“CONFIG_LEDS_GPIO=y”这一行。
重新编译Linux内核,然后使用新编译出来的内核镜像启动开发板。
LED灯驱动文件为/drivers/leds/leds-gpio.c,可以打开/drivers/leds/Makefile这个文件,找到如下所示内容:
obj-$(CONFIG_LEDS_COBALT_QUBE) += leds-cobalt-qube.o obj-$(CONFIG_LEDS_COBALT_RAQ) += leds-cobalt-raq.o obj-$(CONFIG_LEDS_SUNFIRE) += leds-sunfire.o obj-$(CONFIG_LEDS_PCA9532) += leds-pca9532.o obj-$(CONFIG_LEDS_GPIO_REGISTER) += leds-gpio-register.o obj-$(CONFIG_LEDS_GPIO) += leds-gpio.o obj-$(CONFIG_LEDS_LP3944) += leds-lp3944.o obj-$(CONFIG_LEDS_LP3952) += leds-lp3952.o
如果定义了CONFIG_LEDS_GPIO的话就会编译leds-gpio.c这个文件,在上一小节选择将LED驱动编译进Linux内核,在.config文件中就会有“CONFIG_LEDS_GPIO=y”这一行,因此leds-gpio.c驱动文件就会被编译。
接下来看一下leds-gpio.c这个驱动文件,找到如下所示内容:
static const struct of_device_id of_gpio_leds_match[] = { { .compatible = "gpio-leds", }, {}, }; MODULE_DEVICE_TABLE(of, of_gpio_leds_match); 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, }, };
可以看到驱动的名字是"leds-gpio",从《Linux内核平台设备已经存在的情况下注册平台驱动的匹配绑定过程分析》中我们知道,当驱动与设备树中的节点转换成的设备匹配成功以后,会在/sys/bus/platform/drivers目录下创建名为leds-gpio的目录,在这个目录下会有相关的属性文件,可以对这些属性文件进行操作。
通过module_platform_driver(gpio_led_driver)来注册平台驱动,具体过程在《Linux内核平台设备已经存在的情况下注册平台驱动的匹配绑定过程分析》中,这里不再介绍。匹配成功后先调用平台总线的probe函数,再调用本驱动的probe函数。
static int gpio_led_probe(struct platform_device *pdev) { struct gpio_led_platform_data *pdata = dev_get_platdata(&pdev->dev); struct gpio_leds_priv *priv; int i, ret = 0; if (pdata && pdata->num_leds) { // /*非设备树方式,以前那种板级配置文件的方式,board文件中会有平台数据 */ priv = devm_kzalloc(&pdev->dev, 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++) { ret = create_gpio_led(&pdata->leds[i], &priv->leds[i], &pdev->dev, NULL, pdata->gpio_blink_set); if (ret < 0) return ret; } } else { //采用设备树文件 priv = gpio_leds_create(pdev); if (IS_ERR(priv)) return PTR_ERR(priv); } platform_set_drvdata(pdev, priv); return 0; }
现在基本内核都是用设备树的,所以调用gpio_leds_create:
static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct fwnode_handle *child; struct gpio_leds_priv *priv; int count, ret; count = device_get_child_node_count(dev); if (!count) return ERR_PTR(-ENODEV); priv = devm_kzalloc(dev, sizeof_gpio_leds_priv(count), GFP_KERNEL); if (!priv) return ERR_PTR(-ENOMEM); device_for_each_child_node(dev, child) { struct gpio_led_data *led_dat = &priv->leds[priv->num_leds]; struct gpio_led led = {}; const char *state = NULL; struct device_node *np = to_of_node(child); ret = fwnode_property_read_string(child, "label", &led.name); if (ret && IS_ENABLED(CONFIG_OF) && np) led.name = np->name; if (!led.name) { fwnode_handle_put(child); return ERR_PTR(-EINVAL); } led.gpiod = devm_fwnode_get_gpiod_from_child(dev, NULL, child, GPIOD_ASIS, led.name); if (IS_ERR(led.gpiod)) { fwnode_handle_put(child); return ERR_CAST(led.gpiod); } fwnode_property_read_string(child, "linux,default-trigger", &led.default_trigger); //delay trigger,wucaicheng,1378913492@qq.com,20230914 of_property_read_u32(np, "linux,delay-reg", &led.delay_reg); of_property_read_u32(np, "linux,blink-delay-on", (u32*)(&led_dat->cdev.blink_delay_on)); of_property_read_u32(np, "linux,blink-delay-off", (u32*)(&led_dat->cdev.blink_delay_off)); 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; } if (fwnode_property_present(child, "retain-state-suspended")) led.retain_state_suspended = 1; if (fwnode_property_present(child, "retain-state-shutdown")) led.retain_state_shutdown = 1; if (fwnode_property_present(child, "panic-indicator")) led.panic_indicator = 1; ret = create_gpio_led(&led, led_dat, dev, np, NULL); if (ret < 0) { fwnode_handle_put(child); return ERR_PTR(ret); } led_dat->cdev.dev->of_node = np; priv->num_leds++; } return priv; }
为了便于分析,这里把设备树节点信息列出来:
leds: leds { compatible = "gpio-leds"; rgb_led_r: rgb-led-r { gpios = <&gpio1 RK_PB2 GPIO_ACTIVE_LOW>; linux,default-trigger = "timer"; linux,delay-reg = <0>; // 延时注册 linux,blink-delay-on = <500>; // 打开时间 linux,blink-delay-off = <500>; // 关闭时间 }; rgb_led_g: rgb-led-g { gpios = <&gpio1 RK_PB1 GPIO_ACTIVE_LOW>; linux,default-trigger = "timer"; linux,delay-reg = <100>; // 延时注册 linux,blink-delay-on = <1000>; linux,blink-delay-off = <1000>; }; rgb_led_b: rgb-led-b { gpios = <&gpio1 RK_PB0 GPIO_ACTIVE_LOW>; linux,default-trigger = "timer"; linux,delay-reg = <100>; // 延时注册 linux,blink-delay-on = <1500>; linux,blink-delay-off = <1500>; }; };
调用device_get_child_node_count函数统计子节点数量,一般在设备树中创建一个节点表示LED灯,然后在这个节点下面为每个LED灯创建一个子节点。因此子节点数量也是LED灯的数量,我们这里为3个。
接着是遍历每个子节点,获取每个子节点的信息,我们以第一个LED为例:
wnode_property_read_string(child, "label", &led.name); 获取label标签的属性赋值给led.name。设备树中rgb_led_r : rgb-led-r ,按照格式:label: node-name@unit-address,所以label为rgb_led_r ,led.name也就是rgb_led_r 。
led.gpiod = devm_fwnode_get_gpiod_from_child(dev, NULL, child,GPIOD_ASIS,led.name);获取LED灯所使用的GPIO信息,会自动解析节点中以“gpio”和“gpios”开头的属性,例如:gpios = <&gpio1 RK_PB2 GPIO_ACTIVE_LOW>;
gpiod结构体的定义如下:
struct gpio_led { const char *name; const char *default_trigger; unsigned gpio; unsigned active_low : 1; unsigned retain_state_suspended : 1; unsigned panic_indicator : 1; unsigned default_state : 2; unsigned retain_state_shutdown : 1; unsigned int delay_reg; //delay trigger,wucaicheng,1378913492@qq.com,20230914 /* default_state should be one of LEDS_GPIO_DEFSTATE_(ON|OFF|KEEP) */ struct gpio_desc *gpiod; };
设备树的GPIO信息转换如如上结构体。
fwnode_property_read_string(child, "linux,default-trigger",&led.default_trigger); 读取设备树节点的 "linux,default-trigger"属性,我们例子的设备树节点为linux,default-trigger = "timer",所以led.default_trigger=timer。
of_property_read_u32(np, "linux,delay-reg", &led.delay_reg);读取设备树节点的 "linux,delay-reg"属性,我们例子的设备树节点为linux,delay-reg = <0>,所以led.delay_reg=0。
of_property_read_u32(np, "linux,blink-delay-on", (u32*)(&led_dat->cdev.blink_delay_on));读取设备树节点的 "linux,blink-delay-on"属性,我们例子的设备树节点为linux,blink-delay-on = <500>,所以led_dat->cdev.blink_delay_on=500。
of_property_read_u32(np, "linux,blink-delay-off", (u32*)(&led_dat->cdev.blink_delay_off));读取设备树节点的 "linux,blink-delay-off"属性,我们例子的设备树节点为linux,blink-delay-off = <500>,所以led_dat->cdev.blink_delay_off=500。
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; }
fwnode_property_read_string(child, "default-state",&state) 读取设备树的属性,如果存在则返回设备树中的字符串,如果不存在则返回state字符串,state字符串这里为NULL,实际我们设备树也没有定义,所以这里返回为NULL,并且state不为keep和on,所以led.default_state = LEDS_GPIO_DEFSTATE_OFF。
if (fwnode_property_present(child, "retain-state-suspended")) led.retain_state_suspended = 1; if (fwnode_property_present(child, "retain-state-shutdown")) led.retain_state_shutdown = 1; if (fwnode_property_present(child, "panic-indicator")) led.panic_indicator = 1;
fwnode_property_present如果在设备树中能找到属性,则返回指向该属性的指针,否则返回为空。我们设备树中没有这三个属性,所以三个变量不会被赋值。
接着调用create_gpio_led(&led, led_dat, dev, np, NULL)。
static int create_gpio_led(const struct gpio_led *template, struct gpio_led_data *led_dat, struct device *parent, struct device_node *np, gpio_blink_set_t blink_set) { int ret, state; led_dat->gpiod = template->gpiod; if (!led_dat->gpiod) { /* * This is the legacy code path for platform code that * still uses GPIO numbers. Ultimately we would like to get * rid of this block completely. */ unsigned long flags = GPIOF_OUT_INIT_LOW; /* skip leds that aren't available */ if (!gpio_is_valid(template->gpio)) { dev_info(parent, "Skipping unavailable LED gpio %d (%s)\n", template->gpio, template->name); return 0; } if (template->active_low) flags |= GPIOF_ACTIVE_LOW; ret = devm_gpio_request_one(parent, template->gpio, flags, template->name); if (ret < 0) return ret; led_dat->gpiod = gpio_to_desc(template->gpio); if (!led_dat->gpiod) return -EINVAL; } led_dat->cdev.name = template->name; led_dat->cdev.default_trigger = template->default_trigger; led_dat->can_sleep = gpiod_cansleep(led_dat->gpiod); if (!led_dat->can_sleep) led_dat->cdev.brightness_set = gpio_led_set; else led_dat->cdev.brightness_set_blocking = gpio_led_set_blocking; led_dat->blinking = 0; if (blink_set) { led_dat->platform_gpio_blink_set = blink_set; led_dat->cdev.blink_set = gpio_blink_set; } if (template->default_state == LEDS_GPIO_DEFSTATE_KEEP) { state = gpiod_get_value_cansleep(led_dat->gpiod); if (state < 0) return state; } 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; if (template->panic_indicator) led_dat->cdev.flags |= LED_PANIC_INDICATOR; if (template->retain_state_shutdown) led_dat->cdev.flags |= LED_RETAIN_AT_SHUTDOWN; ret = gpiod_direction_output(led_dat->gpiod, state); if (ret < 0) return ret; //delay trigger,wucaicheng,1378913492@qq.com,20230914 if (template->delay_reg > 0) { msleep(template->delay_reg); } return devm_of_led_classdev_register(parent, np, &led_dat->cdev); }
led_dat->gpiod = template->gpiod;将上一层获取到的gpiod结构体数据赋值给led_dat->gpiod 。if (!led_dat->gpiod) {......}是为了兼容以前没有设备树时板级文件的处理,所以这里不会执行这一段。
然后及时对led_dat->cdev结构体下的成员变量进行赋值,然后通过devm_of_led_classdev_register函数向LED驱动框架核心层注册LED设备。
int devm_of_led_classdev_register(struct device *parent, struct device_node *np, struct led_classdev *led_cdev) { struct led_classdev **dr; int rc; dr = devres_alloc(devm_led_classdev_release, sizeof(*dr), GFP_KERNEL); if (!dr) return -ENOMEM; rc = of_led_classdev_register(parent, np, led_cdev); if (rc) { devres_free(dr); return rc; } *dr = led_cdev; devres_add(parent, dr); return 0; }
int of_led_classdev_register(struct device *parent, struct device_node *np, struct led_classdev *led_cdev) { char name[LED_MAX_NAME_SIZE]; int ret; ret = led_classdev_next_name(led_cdev->name, name, sizeof(name)); if (ret < 0) return ret; mutex_init(&led_cdev->led_access); mutex_lock(&led_cdev->led_access); led_cdev->dev = device_create_with_groups(leds_class, parent, 0, led_cdev, led_cdev->groups, "%s", name); if (IS_ERR(led_cdev->dev)) { mutex_unlock(&led_cdev->led_access); return PTR_ERR(led_cdev->dev); } led_cdev->dev->of_node = np; if (ret) dev_warn(parent, "Led %s renamed to %s due to name collision", led_cdev->name, dev_name(led_cdev->dev)); if (led_cdev->flags & LED_BRIGHT_HW_CHANGED) { ret = led_add_brightness_hw_changed(led_cdev); if (ret) { device_unregister(led_cdev->dev); mutex_unlock(&led_cdev->led_access); return ret; } } led_cdev->work_flags = 0; #ifdef CONFIG_LEDS_TRIGGERS init_rwsem(&led_cdev->trigger_lock); #endif #ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED led_cdev->brightness_hw_changed = -1; #endif /* add to the list of leds */ down_write(&leds_list_lock); list_add_tail(&led_cdev->node, &leds_list); up_write(&leds_list_lock); if (!led_cdev->max_brightness) led_cdev->max_brightness = LED_FULL; led_update_brightness(led_cdev); led_init_core(led_cdev); #ifdef CONFIG_LEDS_TRIGGERS led_trigger_set_default(led_cdev); #endif mutex_unlock(&led_cdev->led_access); dev_dbg(parent, "Registered led device: %s\n", led_cdev->name); return 0; }
led_classdev_next_name函数决定 LED 设备在文件系统里面的名称。从设备树节点里面获取到的名称(label属性)作为初始的 name,然后遍历全局类 leds_class(class类型),跟里面的设备逐个对比,如果已经有同名的设备了,则在 name 后面添加 _x 形式的后缀,然后再次逐个对比,直到 leds_class 里面找不到同名的设备了,则表示该名称可以用于创建新设备了。该函数返回的 ret 为检测到命名冲突的次数。
device_create_with_groups==>device_create_groups_vargs
static __printf(6, 0) struct device * device_create_groups_vargs(struct class *class, struct device *parent, dev_t devt, void *drvdata, const struct attribute_group **groups, const char *fmt, va_list args) { struct device *dev = NULL; int retval = -ENODEV; if (class == NULL || IS_ERR(class)) goto error; dev = kzalloc(sizeof(*dev), GFP_KERNEL); if (!dev) { retval = -ENOMEM; goto error; } device_initialize(dev); dev->devt = devt; dev->class = class; dev->parent = parent; dev->groups = groups; dev->release = device_create_release; dev_set_drvdata(dev, drvdata); retval = kobject_set_name_vargs(&dev->kobj, fmt, args); if (retval) goto error; retval = device_add(dev); if (retval) goto error; return dev; error: put_device(dev); return ERR_PTR(retval); }
通过device_add()在sys/class/leds/目录下创建对应颜色的LED灯文件以及属性文件。什么是attribute?对应/sys/class/leds/
目录里的内容,一般是文件和文件夹。这些文件其实就是sysfs开放给应用层的一些操作接口(非常类似于/dev/目录下的那些设备文件)
attribute有什么用?作用就是让应用程序可以通过/sys/class/leds/目录下面的属性文件来操作驱动进而操作硬件设备。
attribute其实是另一条驱动实现的路线。有区别于之前讲的file_operations那条线。相当于用户空间与内核空间交互的另外一种方式。
static int led_add_brightness_hw_changed(struct led_classdev *led_cdev) { struct device *dev = led_cdev->dev; int ret; ret = device_create_file(dev, &dev_attr_brightness_hw_changed); if (ret) { dev_err(dev, "Error creating brightness_hw_changed\n"); return ret; } led_cdev->brightness_hw_changed_kn = sysfs_get_dirent(dev->kobj.sd, "brightness_hw_changed"); if (!led_cdev->brightness_hw_changed_kn) { dev_err(dev, "Error getting brightness_hw_changed kn\n"); device_remove_file(dev, &dev_attr_brightness_hw_changed); return -ENXIO; } return 0; }
device_create_file(dev, &dev_attr_brightness_hw_changed);在设备的文件目录下创建dev_attr_brightness_hw_changed的属性文件。
list_add_tail(&led_cdev->node, &leds_list);将LED节点添加到leds_list列表。
int led_update_brightness(struct led_classdev *led_cdev) { int ret = 0; if (led_cdev->brightness_get) { ret = led_cdev->brightness_get(led_cdev); if (ret >= 0) { led_cdev->brightness = ret; return 0; } } return ret; }
调用gpio-leds.c中的brightness_get函数获取当前LED的状态,将状态值中心赋值给led_cdev->brightness。
void led_init_core(struct led_classdev *led_cdev) { INIT_WORK(&led_cdev->set_brightness_work, set_brightness_delayed); timer_setup(&led_cdev->blink_timer, led_timer_function, 0); }
timer_setup定时器周期执行led_timer_function函数,如果当前是LED打开状态就关闭,否则就打开,来实现闪烁功能。
static int __led_set_brightness_blocking(struct led_classdev *led_cdev, enum led_brightness value) { if (!led_cdev->brightness_set_blocking) return -ENOTSUPP; return led_cdev->brightness_set_blocking(led_cdev, value); } static void led_timer_function(struct timer_list *t) { struct led_classdev *led_cdev = from_timer(led_cdev, t, blink_timer); unsigned long brightness; unsigned long delay; if (!led_cdev->blink_delay_on || !led_cdev->blink_delay_off) { //如果没有给闪烁延时赋值,则关闭LED等,退出 led_set_brightness_nosleep(led_cdev, LED_OFF); clear_bit(LED_BLINK_SW, &led_cdev->work_flags); return; } if (test_and_clear_bit(LED_BLINK_ONESHOT_STOP, &led_cdev->work_flags)) { clear_bit(LED_BLINK_SW, &led_cdev->work_flags); return; } brightness = led_get_brightness(led_cdev); //读取当前LED的状态 if (!brightness) { //如果是关闭状态,则打开LED,根据设置的亮度值设置亮度,设置延时值 /* Time to switch the LED on. */ if (test_and_clear_bit(LED_BLINK_BRIGHTNESS_CHANGE, &led_cdev->work_flags)) brightness = led_cdev->new_blink_brightness; else brightness = led_cdev->blink_brightness; delay = led_cdev->blink_delay_on; } else { //如果是打开状态,关闭LED灯,设置延时关闭值 /* Store the current brightness value to be able * to restore it when the delay_off period is over. */ led_cdev->blink_brightness = brightness; brightness = LED_OFF; delay = led_cdev->blink_delay_off; } led_set_brightness_nosleep(led_cdev, brightness); //设置打开还是关闭LED /* Return in next iteration if led is in one-shot mode and we are in * the final blink state so that the led is toggled each delay_on + * delay_off milliseconds in worst case. */ if (test_bit(LED_BLINK_ONESHOT, &led_cdev->work_flags)) { if (test_bit(LED_BLINK_INVERT, &led_cdev->work_flags)) { if (brightness) set_bit(LED_BLINK_ONESHOT_STOP, &led_cdev->work_flags); } else { if (!brightness) set_bit(LED_BLINK_ONESHOT_STOP, &led_cdev->work_flags); } } mod_timer(&led_cdev->blink_timer, jiffies + msecs_to_jiffies(delay)); //重新设置定时器下一次的中断时间 }
因为定义了CONFIG_LEDS_TRIGGERS,所以会执行led_trigger_set_default(led_cdev):
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); 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触发器列表依次去对比设备树中的触发器的名字,如果相同就设置该触发器为LED的触发器。
/* Caller must ensure led_cdev->trigger_lock held */ int led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trig) { unsigned long flags; char *event = NULL; char *envp[2]; const char *name; int ret; if (!led_cdev->trigger && !trig) return 0; name = trig ? trig->name : "none"; event = kasprintf(GFP_KERNEL, "TRIGGER=%s", name); /* 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); 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); device_remove_groups(led_cdev->dev, led_cdev->trigger->groups); led_cdev->trigger = NULL; led_cdev->trigger_data = NULL; led_cdev->activated = false; 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) ret = trig->activate(led_cdev); else ret = 0; if (ret) goto err_activate; ret = device_add_groups(led_cdev->dev, trig->groups); if (ret) { dev_err(led_cdev->dev, "Failed to add trigger attributes\n"); goto err_add_groups; } } if (event) { envp[0] = event; envp[1] = NULL; if (kobject_uevent_env(&led_cdev->dev->kobj, KOBJ_CHANGE, envp)) dev_err(led_cdev->dev, "%s: Error sending uevent\n", __func__); kfree(event); } return 0; err_add_groups: if (trig->deactivate) trig->deactivate(led_cdev); err_activate: 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); led_cdev->trigger = NULL; led_cdev->trigger_data = NULL; led_set_brightness(led_cdev, LED_OFF); kfree(event); return ret; }
如果led_cdev->trigger的触发器已经存在,则删除掉。然后将新的触发器挂到led_cdev->trig_list列表。执行触发器的trig->activate(led_cdev)来激活触发器。在LED文件目录下添加触发器相关的属性文件。
devres_add(parent, dr);添加设备资源。具体关于devres的内容后面单独文章来介绍。