内核自带的基于GPIO的LED驱动学习(三)

上篇文章讲到了gpio_leds_create函数(),其定义位于drivers/leds/leds-gpio.c,如下:

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;
	struct device_node *np;

	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 led = {};
		const char *state = NULL;

		led.gpiod = devm_get_gpiod_from_child(dev, NULL, child);
		if (IS_ERR(led.gpiod)) {
			fwnode_handle_put(child);
			ret = PTR_ERR(led.gpiod);
			goto err;
		}

		np = of_node(child);

		if (fwnode_property_present(child, "label")) {
			fwnode_property_read_string(child, "label", &led.name);
		} else {
			if (IS_ENABLED(CONFIG_OF) && !led.name && np)
				led.name = np->name;
			if (!led.name)
				return ERR_PTR(-EINVAL);
		}
		fwnode_property_read_string(child, "linux,default-trigger",
					    &led.default_trigger);

		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;

		ret = create_gpio_led(&led, &priv->leds[priv->num_leds++],
				      dev, NULL);
		if (ret < 0) {
			fwnode_handle_put(child);
			goto err;
		}
	}

	return priv;

err:
	for (count = priv->num_leds - 2; count >= 0; count--)
		delete_gpio_led(&priv->leds[count]);
	return ERR_PTR(ret);
}

该函数重点做了以下3件事情:

1)为LED分组创建一个私有结构体,保存分组下的LED设备信息

priv = devm_kzalloc(dev, sizeof_gpio_leds_priv(count), GFP_KERNEL);
	if (!priv)
		return ERR_PTR(-ENOMEM);

2)通过一个for循环遍历LED分组下面的各个LED设备,提取设备树里面的信息

device_for_each_child_node(dev, child) {
	...
}

这些信息都被保存在一个类型为struct gpio_led的结构体临时变量led里面。

3)以第二步得到的led临时变量为模板,调用create_gpio_led函数,创建LED设备,并记录该设备的信息到LED分组的私有结构体里面去。

ret = create_gpio_led(&led, &priv->leds[priv->num_leds++],
				      dev, NULL);

那接下来的工作就是分析create_gpio_led()这个函数是怎么创建一个LED设备的了,该函数的定义位于drivers/leds/leds-gpio.c,如下:

static int create_gpio_led(const struct gpio_led *template,
	struct gpio_led_data *led_dat, struct device *parent,
	int (*blink_set)(struct gpio_desc *, int, unsigned long *,
			 unsigned long *))
{
	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 = 0;

		/* 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 (IS_ERR(led_dat->gpiod))
			return PTR_ERR(led_dat->gpiod);
	}

	led_dat->cdev.name = template->name;
	led_dat->cdev.default_trigger = template->default_trigger;
	led_dat->can_sleep = gpiod_cansleep(led_dat->gpiod);
	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;
	if (template->default_state == LEDS_GPIO_DEFSTATE_KEEP)
		state = !!gpiod_get_value_cansleep(led_dat->gpiod);
	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;

	ret = gpiod_direction_output(led_dat->gpiod, state);
	if (ret < 0)
		return ret;

	INIT_WORK(&led_dat->work, gpio_led_work);

	return led_classdev_register(parent, &led_dat->cdev);
}

可以看到该函数主要做了以下4件事情:

1)根据传入的led模板信息的内容,来设置LED设备对应的struct gpio_led_data结构体,要么直接赋值,要么稍作调整后赋值。

2)设置LED的GPIO引脚为输出,默认状态从设备树里面获取。

ret = gpiod_direction_output(led_dat->gpiod, state);
	if (ret < 0)
		return ret;

3)为该LED设备初始化一个内核线程work,任务函数为gpio_led_work。

INIT_WORK(&led_dat->work, gpio_led_work);

4)调用led_classdev_register(),往内核的LED子系统注册一个LED设备。

return led_classdev_register(parent, &led_dat->cdev);

接下来就转到函数led_classdev_register(),其定义位于drivers/leds/led-class.c,如下:

/**
 * led_classdev_register - register a new object of led_classdev class.
 * @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)
{
	char name[64];
	int ret;

	ret = led_classdev_next_name(led_cdev->name, name, sizeof(name));
	if (ret < 0)
		return ret;

	led_cdev->dev = device_create_with_groups(leds_class, parent, 0,
				led_cdev, led_cdev->groups, "%s", name);
	if (IS_ERR(led_cdev->dev))
		return PTR_ERR(led_cdev->dev);

	if (ret)
		dev_warn(parent, "Led %s renamed to %s due to name collision",
				led_cdev->name, dev_name(led_cdev->dev));

#ifdef CONFIG_LEDS_TRIGGERS
	init_rwsem(&led_cdev->trigger_lock);
#endif
	mutex_init(&led_cdev->led_access);
	/* 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_cdev->flags |= SET_BRIGHTNESS_ASYNC;

	led_update_brightness(led_cdev);

	INIT_WORK(&led_cdev->set_brightness_work, set_brightness_delayed);

	setup_timer(&led_cdev->blink_timer, led_timer_function,
		    (unsigned long)led_cdev);

#ifdef CONFIG_LEDS_TRIGGERS
	led_trigger_set_default(led_cdev);
#endif

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

	return 0;
}
EXPORT_SYMBOL_GPL(led_classdev_register);

这个函数做了以下几件事情:

1)决定LED设备在文件系统里面的名称,方法是调用led_classdev_next_name(),以从设备树节点里面获取到的名称作为初始的name,然后遍历全局类led_class,跟里面的设备逐个对比,如果已经有同名的设备了,则在name后面添加_x形式的后缀,然后再次逐个对比,直到led_class里面找不到同名的设备了,则表示该名称可以用于创建新设备了。

ret = led_classdev_next_name(led_cdev->name, name, sizeof(name));
	if (ret < 0)
		return ret;

该函数返回的ret为检测到命名冲突的次数。

2)调用device_create_with_groups(),在全局类led_class下创建一个LED设备。

led_cdev->dev = device_create_with_groups(leds_class, parent, 0,
				led_cdev, led_cdev->groups, "%s", name);
	if (IS_ERR(led_cdev->dev))
		return PTR_ERR(led_cdev->dev);

device_create_with_groups是新的接口,老的接口为device_create。这里参数parent为LED分组,所以在文件系统里面,该LED设备的节点会被创建在LED分组下面,而不是通常的/dev目录下面。

3)将该LED设备添加到全局的leds_list链表尾部。

/* add to the list of leds */
	down_write(&leds_list_lock);
	list_add_tail(&led_cdev->node, &leds_list);
	up_write(&leds_list_lock);

4)为struct led_classdev初始化内核线程work,任务函数为set_brightness_delayed

INIT_WORK(&led_cdev->set_brightness_work, set_brightness_delayed);

5)为struct led_classdev初始化内核定时器,定时器函数为led_timer_function,参数为结构体本身。

setup_timer(&led_cdev->blink_timer, led_timer_function,
		    (unsigned long)led_cdev);

好,到此,一个LED设备的创建就完成了,准备一个测试使用的设备树文件,编写LED节点如下:

leds {
		compatible = "gpio-leds";

		heart {
			label = "heartbeat";
			gpios = <&gpio1 3 GPIO_ACTIVE_LOW>;
			linux,default-trigger = "heartbeat";
		};
	};

编译后用于启动开发板,则可以在文件系统下找到该设备:

/sys/devices/platform/leds/leds # ls
heartbeat

下一篇文章,我们将一起来看看内核LED驱动,是怎么控制LED灯的。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值