Linux内核自带的leds-gpio代码分析

qewnjaLinux驱动 2024-06-30 10:26:32 3594阅读 举报



本文来对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函数在全局类led_class下创建一个LED设备,这里参数parent为LED分组,所以在文件系统里面,该LED设备的节点会被创建在LED分组下面,而不是通常的/dev目录下面。

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列表。

led_update_brightness函数更新 led 的亮度状态:

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。

led_init_core初始化工作队列和定时器,处理led的闪烁。

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的内容后面单独文章来介绍。




标签: #Linux#

版权声明:
作者:qewnja
链接:https://www.dianziwang.net/p/224820a84fc52f.html
来源:Linux驱动
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以点击 “举报”


登录 后发表评论
0条评论
还没有人评论过~