上篇文章讲到了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灯的。