上篇文章讲到了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;elseled.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);elsestate = (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_TRIGGERSinit_rwsem(&led_cdev->trigger_lock);
#endifmutex_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_TRIGGERSled_trigger_set_default(led_cdev);
#endifdev_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灯的。

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

  1. 内核自带的基于GPIO的LED驱动学习(一)

    为什么学习内核自带的LED驱动? 前面已经学习过了基于纯字符设备的LED驱动,也学习过了基于平台驱动的LED驱动,但是感觉都是按照教程在生搬硬套,到底我们写出来的驱动能不能拿得上台面,是否能在实际的生 ...

  2. 内核自带的基于GPIO的LED驱动学习(二)

    2)分析平台驱动的probe函数 好,既然这个LED驱动使用的是平台驱动框架,当设备和驱动匹配上之后,就会执行指定的probe函数,那接下来的工作就转移到分析对应的probe函数了.为了直观,我把pr ...

  3. 不写一行代码(一):实现安卓基于GPIO的LED设备驱动

    文章目录 系列文章 一.前言 二.准备工作 2.1 内核版本 2.2 内核文档:bindings->leds 2.3 文档解析: leds-gpio.txt 三.编写DTS 3.1 查原理图,挑 ...

  4. 基于触摸屏的LED驱动电路设计

    本系统是利用触摸屏控制的可调颜色的照明灯具.灯源为大功率超高亮三基色发光二极管组成.本设计最大的亮点是触摸屏上所指示的颜色与实际灯照出的颜色一致.到通过c语言程序对单片机的PWM 功能进行控制实现相应 ...

  5. 基于exynos4412的led驱动编程

    本文基于华清4412开发板,讲解如何从零开始编写led驱动程序和测试程序.首先介绍一下该4412开发板的led硬件原理图. 从原理图上我们可以看出,让led点亮的条件是往对应端口送高电平,熄灭的条件是 ...

  6. 基于RK3399的LED驱动开发

    1.添加设备树 在设备树 arch/arm64/boot/dts/rockchip/rk3399-firefly-linux.dts 中添加 gpio-led{status = "okay& ...

  7. 字符设备驱动之Led驱动学习记录

    一.概述 Linux内核就是由各种驱动组成的,内核源码中大约有85%的各种渠道程序的代码.一般来说,编写Linux设备驱动大致流程如下: 1.查看原理图,数据手册,了解设备的操作方法. 2.在内核中找 ...

  8. 基于RK3399PRo的串口驱动学习-XR21V1414IM48

    目录 原理图 XR21V1414IM48简介 重点代码 函数入口初始化 tty串口操作集 USB转串初始化 设备ID 测试代码 头文件 宏定义 输入参数提示 打开设备 设置波特率 配置数据位.停止位. ...

  9. 大功率LED驱动 AP2403 三功能降压恒流驱动器

    PCB 布线参考1.大电流路径走线要粗,铺铜走线最佳. 2.大电路回路面积以最短.最宽路径完成 最佳. 3.开关切换连接点:电感 L.SW PIN 与 续流肖特基二极管,走线要短与粗,铺铜 走线最佳, ...

最新文章

  1. ATT汇编leave指令
  2. signal(SIGHUP, SIG_IGN);
  3. 解决kindeditor在线编辑器 过滤dl、dd、dt的两种方法
  4. MPLS(多协议标记交换)协议能否降低跨省组建企业专网的成本?
  5. GDAL源码剖析(八)之编译GEOS和PROJ4库
  6. 搞了一个迭代发布下SpringBoot Jar瘦身方案,老大给我打了个A+
  7. C++11 chrono库
  8. paip.URL参数压缩64进制
  9. python实现sm3算法
  10. Webpower揭晓2017最有效数字营销策略
  11. 显微镜自动聚焦原理是什么_激光共聚焦显微镜系统的原理和应用讲解
  12. c语言sub函数是什么,用$Super$$和$Sub$$对函数进行重定义
  13. 烧洋芋、苞谷、饵块和昭通酱
  14. EPICS设备支持的简单示例
  15. 移动应用测试篇(1)——移动应用的发展
  16. 【Android】更改程序图标
  17. linux操作系统基础复习
  18. 【读书笔记】赞成功利主义的学习
  19. 例1.1-2 Strongbox
  20. Sql语句——删除表数据drop、truncate和delete的用法

热门文章

  1. 前端ios和安卓的兼容性问题
  2. 【英文文本分类实战】之一——实战项目总览
  3. 为了让你们进阶 Canvas,我花 7 小时写了 3 个有趣的小游戏!!!
  4. 金融专业 计算机 好,计算机和金融选择哪个更好?两个各自的优势是什么?
  5. Linux虚拟机(lvm)报Unmount and run xfs_repair
  6. 微信公众号粉丝快速涨粉的五个方法
  7. 【算法leetcode每日一练】剑指 Offer II 080. 含有 k 个元素的组合 | 77. 组合
  8. 慎独、主敬、求仁、习劳
  9. css样式实现圆角矩形
  10. 在windbg中显示win32k.sys调试符号