搜索
bottom↓
回复: 0

《领航者ZYNQ之嵌入式Linux开发指南_V2.0》第四十章 LED驱动实验

[复制链接]

出0入234汤圆

发表于 2022-2-28 15:32:31 | 显示全部楼层 |阅读模式
本帖最后由 正点原子 于 2022-2-28 15:32 编辑

1)实验平台:正点原子领航者V2 ZYNQ开发板
2)  章节摘自【正点原子】《领航者ZYNQ之嵌入式Linux开发指南_V2.0》
3)购买链接:https://detail.tmall.com/item.htm?id=609032204975
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-329957-1-1.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子FPGA技术交流QQ群:90562473
1.png

2.jpg


3.png


第四十章 Linux自带的LED驱动实验

       第三十九章我们学习了linux下的LED驱动框架,并动手编写一个简单地基于LED驱动框架的驱动程序,linux系统之所以受到嵌入式开发者的喜爱,很大一部分原因在于linux的软件生态做得好,对于很多的通用的硬件设备在linux源码中都有提供相应的驱动源文件,也包括LED设备;Linux内核源码中提供的LED驱动程序同样也是基于LED驱动框架编写的,有了驱动程序,我们只需要在设备树中添加对应节点即可使用。因本章我们就来学习如何使用Linux内核自带的LED驱动程序来驱动开发板的PS_LED0。

1.1Linux内核自带LED驱动使能
        要使用Linux内核自带的LED驱动首先得先配置Linux内核,使能自带的LED灯驱动,进入到linux内核源码目录,输入如下命令打开Linux配置菜单:
  1. make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
复制代码

按照如下路径打开LED驱动配置项:
  1. -> Device Drivers                                                                                
  2.         -> LED Support (NEW_LEDS [=y])
  3.                         ->LED Support for GPIO connected LEDs
复制代码

       按照上述路径,选择“LED Support for GPIO connected LEDs”,将其编译进Linux内核,也即是在此选项上按下“Y”键,使此选项前面变为“<*>”,默认情况下已经配置好了,如图 40.1.1所示:
第四十章 Linux自带的LED驱动实验780.png

图 40.1.1 使能LED灯驱动

       在“LED Support for GPIO connected LEDs”上按下可以打开此选项的帮助信息,如图 40.1.2所示:
第四十章 Linux自带的LED驱动实验934.png

图 40.1.2 内部LED灯驱动帮助信息

       从图 40.1.2可以看出,把Linux内部自带的LED灯驱动编译进内核以后,CONFIG_LEDS_GPIO就会等于‘y’,Linux会根据CONFIG_LEDS_GPIO的值来选择如何编译LED灯驱动,如果为‘y’就将其编译进Linux内核。
配置好Linux内核以后退出配置界面,打开.config文件,会找到“CONFIG_LEDS_GPIO=y”这一行,如所示:
第四十章 Linux自带的LED驱动实验1212.png

图 40.1.3 .config文件内容

       由于我们使用的linux内核源码默认情况下已经使能了LED驱动,所以我们之前编译好的内核镜像中已经存在LED驱动了,所以不需要再次编译内核源码,使用之前编译好的内核镜像zImage即可!
       1.2Linux内核自带LED驱动简介
1.2.1LED驱动框架分析

       内核源码提供的LED驱动源文件为drivers/leds/leds-gpio.c,大家可以打开/drivers/leds/Makefile这个文件,找到如下所示内容:
  1. 示例代码 40.2.1 drivers/leds/Makefile文件代码段
  2.   3 # LED Core
  3.   4 obj-$(CONFIG_NEW_LEDS)                        += led-core.o
  4.   5 obj-$(CONFIG_LEDS_CLASS)                        += led-class.o
  5.   6 obj-$(CONFIG_LEDS_CLASS_FLASH)        += led-class-flash.o
  6.   7 obj-$(CONFIG_LEDS_TRIGGERS)                += led-triggers.o
  7.   8
  8.   9 # LED Platform Drivers
  9. ......
  10. 29 obj-$(CONFIG_LEDS_GPIO_REGISTER)        += leds-gpio-register.o
  11. 30 obj-$(CONFIG_LEDS_GPIO)                        += leds-gpio.o
  12. 31 obj-$(CONFIG_LEDS_LP3944)                        += leds-lp3944.o
  13. ......
复制代码

       第30行,如果定义了CONFIG_LEDS_GPIO的话就会编译leds-gpio.c这个文件,在上一小节我们在.config文件中看到有“CONFIG_LEDS_GPIO=y”这一行,因此leds-gpio.c驱动文件就会被编译。
       接下来我们看一下leds-gpio.c这个驱动文件,找到如下所示内容:
  1. 示例代码 40.2.2 leds-gpio.c文件代码段
  2. 227 static const struct of_device_id of_gpio_leds_match[] = {
  3. 228     { .compatible = "gpio-leds", },
  4. 229     {},
  5. 230 };
  6. 231
  7. 232 MODULE_DEVICE_TABLE(of, of_gpio_leds_match);
  8. ......
  9. 279 static struct platform_driver gpio_led_driver = {
  10. 280     .probe                = gpio_led_probe,
  11. 281     .shutdown        = gpio_led_shutdown,
  12. 282     .driver                = {
  13. 283         .name   = "leds-gpio",
  14. 284         .of_match_table = of_gpio_leds_match,
  15. 285     },
  16. 286 };
  17. 287
  18. 288 module_platform_driver(gpio_led_driver);
  19. 289
  20. 290 MODULE_AUTHOR("Raphael Assenat <<a href="mailto:raph@8d.com">raph@8d.com</a>>, Trent Piepho <<a href="mailto:tpiepho@freescale.com">tpiepho@freescale.com</a>>");
  21. 291 MODULE_DESCRIPTION("GPIO LED driver");
  22. 292 MODULE_LICENSE("GPL");
  23. 293 MODULE_ALIAS("platform:leds-gpio");
复制代码

       第227~230行,LED驱动的匹配表,此表只有一个匹配项,compatible内容为“gpio-leds”,因此我们需要在设备树中添加一个led设备对应的节点,它的compatible属性值也要为“gpio-leds”,否则设备和驱动匹配不成功,驱动就没法工作。
       第279~286行,platform_driver驱动结构体变量,可以看出,Linux内核自带的LED驱动同样也是基于platform总线。第280行可以看出probe函数为gpio_led_probe,因此当驱动和设备匹配成功以后gpio_led_probe函数就会执行。从283行可以看出,驱动名字为“leds-gpio”,因此会在/sys/bus/platform/drivers目录下存在一个名为“leds-gpio”的文件,如所示:
第四十章 Linux自带的LED驱动实验3252.png

图 40.2.1 platform总线下的LED驱动

       从第288行可以知道,在这个驱动源码中也使用module_platform_driver宏创建模块的入口函数和出口函数,非常的方便!
1.2.2gpio_led_probe函数简析
       当驱动和设备匹配以后gpio_led_probe函数就会执行,gpio_led_probe函数内容如下所示:
  1. <font size="2">示例代码 40.2.3 gpio_led_probe函数
  2. 234 static int gpio_led_probe(struct platform_device *pdev)
  3. 235 {
  4. 236     struct gpio_led_platform_data *pdata = dev_get_platdata(&pdev->dev);
  5. 237     struct gpio_leds_priv *priv;
  6. 238     int i, ret = 0;
  7. 239
  8. 240     if (pdata && pdata->num_leds) {
  9. 241         priv = devm_kzalloc(&pdev->dev,
  10. 242                     sizeof_gpio_leds_priv(pdata->num_leds),
  11. 243                         GFP_KERNEL);
  12. 244         if (!priv)
  13. 245             return -ENOMEM;
  14. 246
  15. 247         priv->num_leds = pdata->num_leds;
  16. 248         for (i = 0; i < priv->num_leds; i++) {
  17. <span style="font-style: italic;"><span style="font-style: normal;">249             ret = create_gpio_led(&pdata->leds, &priv->leds,
  18. 250                              &pdev->dev, NULL,
  19. 251                              pdata->gpio_blink_set);
  20. 252             if (ret < 0)
  21. 253                 return ret;
  22. 254         }
  23. 255     } else {
  24. 256         priv = gpio_leds_create(pdev);
  25. 257         if (IS_ERR(priv))
  26. 258             return PTR_ERR(priv);
  27. 259     }
  28. 260
  29. 261     platform_set_drvdata(pdev, priv);
  30. 262
  31. 263     return 0;
  32. 264 }</span></span></font>
复制代码

       该函数中分为两种情况处理,不使用设备树的方式和使用设备树的方式,第241~254行,处理不使用设备树的方式,不使用设备就是像第三十七章那样分开两部分platform设备和platform驱动分别进行注册;第254~258行,处理使用设备树的方式,来看看gpio_leds_create函数做了些什么,gpio_leds_create函数内容如下:
  1. 示例代码 40.2.4 gpio_leds_create函数
  2. 158 static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev)
  3. 159 {
  4. 160     struct device *dev = &pdev->dev;
  5. 161     struct fwnode_handle *child;
  6. 162     struct gpio_leds_priv *priv;
  7. 163     int count, ret;
  8. 164
  9. 165     count = device_get_child_node_count(dev);
  10. 166     if (!count)
  11. 167         return ERR_PTR(-ENODEV);
  12. 168
  13. 169     priv = devm_kzalloc(dev, sizeof_gpio_leds_priv(count), GFP_KERNEL);
  14. 170     if (!priv)
  15. 171         return ERR_PTR(-ENOMEM);
  16. 172
  17. 173     device_for_each_child_node(dev, child) {
  18. 174         struct gpio_led_data *led_dat = &priv->leds[priv->num_leds];
  19. 175         struct gpio_led led = {};
  20. 176         const char *state = NULL;
  21. 177         struct device_node *np = to_of_node(child);
  22. 178
  23. 179         ret = fwnode_property_read_string(child, "label", &led.name);
  24. 180         if (ret && IS_ENABLED(CONFIG_OF) && np)
  25. 181             led.name = np->name;
  26. 182         if (!led.name) {
  27. 183             fwnode_handle_put(child);
  28. 184             return ERR_PTR(-EINVAL);
  29. 185         }
  30. 186
  31. 187         led.gpiod = devm_fwnode_get_gpiod_from_child(dev, NULL, child,
  32. 188                                  GPIOD_ASIS,
  33. 189                                  led.name);
  34. 190         if (IS_ERR(led.gpiod)) {
  35. 191             fwnode_handle_put(child);
  36. 192             return ERR_CAST(led.gpiod);
  37. 193         }
  38. 194
  39. 195         fwnode_property_read_string(child, "linux,default-trigger",
  40. 196                         &led.default_trigger);
  41. 197
  42. 198         if (!fwnode_property_read_string(child, "default-state",
  43. 199                          &state)) {
  44. 200             if (!strcmp(state, "keep"))
  45. 201                 led.default_state = LEDS_GPIO_DEFSTATE_KEEP;
  46. 202             else if (!strcmp(state, "on"))
  47. 203                 led.default_state = LEDS_GPIO_DEFSTATE_ON;
  48. 204             else
  49. 205                 led.default_state = LEDS_GPIO_DEFSTATE_OFF;
  50. 206         }
  51. 207
  52. 208         if (fwnode_property_present(child, "retain-state-suspended"))
  53. 209             led.retain_state_suspended = 1;
  54. 210         if (fwnode_property_present(child, "retain-state-shutdown"))
  55. 211             led.retain_state_shutdown = 1;
  56. 212         if (fwnode_property_present(child, "panic-indicator"))
  57. 213             led.panic_indicator = 1;
  58. 214
  59. 215         ret = create_gpio_led(&led, led_dat, dev, np, NULL);
  60. 216         if (ret < 0) {
  61. 217             fwnode_handle_put(child);
  62. 218             return ERR_PTR(ret);
  63. 219         }
  64. 220         led_dat->cdev.dev->of_node = np;
  65. 221         priv->num_leds++;
  66. 222     }
  67. 223
  68. 224     return priv;
  69. 225 }
复制代码

        第165~167行,通过device_get_child_node_count函数获取设备树中定义的led设备节点中有几个子节点(一个子节点表示一个LED的信息),一般在在设备树中创建一个节点表示LED设备,然后在这个节点下面为每个LED灯创建一个子节点,因此子节点数量也是LED灯的数量,如果不存在子节点则退出。
        第169行,devm_kzalloc函数我们在上一个章节讲过了,该函数用于分配内存空间。
        第173~222行,通过device_for_each_child_node函数遍历每一个子节点,获取子节点中的信息;第179行,读取子节点的label属性值,因为使用label属性作为LED灯的名字;
       第187行,通过调用devm_fwnode_get_gpiod_from_child函数获取LED灯所使用的GPIO信息。这个函数我们就不去分析了,devm_fwnode_get_gpiod_from_child函数会自动解析节点中以“gpio”和“gpios”开头的属性,例如:
  1. gpio = <&gpio0 7 GPIO_ACTIVE_HIGH>;
  2. gpios = <&gpio0 7 GPIO_ACTIVE_HIGH>;
  3. gpios-led = <&gpio0 7 GPIO_ACTIVE_HIGH>;
复制代码

       第195行,获取子节点中的“linux,default-trigger”属性值,这个属性定义了LED的触发状态,在上一章中已经给大家介绍过了。
       第198~206行,获取子节点中的“default-state”属性值,这个属性定义了LED的默认状态。
       第215行调用了create_gpio_led函数,create_gpio_led函数内容如下所示:
  1. 示例代码 40.2.5 create_gpio_led函数
  2. 078 static int create_gpio_led(const struct gpio_led *template,
  3. 079     struct gpio_led_data *led_dat, struct device *parent,
  4. 080     struct device_node *np, gpio_blink_set_t blink_set)
  5. 081 {
  6. 082     int ret, state;
  7. 083
  8. 084     led_dat->gpiod = template->gpiod;
  9. 085     if (!led_dat->gpiod) {
  10. 086         /*
  11. 087          * This is the legacy code path for platform code that
  12. 088          * still uses GPIO numbers. Ultimately we would like to get
  13. 089          * rid of this block completely.
  14. 090          */
  15. 091         unsigned long flags = GPIOF_OUT_INIT_LOW;
  16. 092
  17. 093         /* skip leds that aren't available */
  18. 094         if (!gpio_is_valid(template->gpio)) {
  19. 095             dev_info(parent, "Skipping unavailable LED gpio %d (%s)\n",
  20. 096                     template->gpio, template->name);
  21. 097             return 0;
  22. 098         }
  23. 099
  24. 100         if (template->active_low)
  25. 101             flags |= GPIOF_ACTIVE_LOW;
  26. 102
  27. 103         ret = devm_gpio_request_one(parent, template->gpio, flags,
  28. 104                         template->name);
  29. 105         if (ret < 0)
  30. 106             return ret;
  31. 107
  32. 108         led_dat->gpiod = gpio_to_desc(template->gpio);
  33. 109         if (!led_dat->gpiod)
  34. 110             return -EINVAL;
  35. 111     }
  36. 112
  37. 113     led_dat->cdev.name = template->name;
  38. 114     led_dat->cdev.default_trigger = template->default_trigger;
  39. 115     led_dat->can_sleep = gpiod_cansleep(led_dat->gpiod);
  40. 116     if (!led_dat->can_sleep)
  41. 117         led_dat->cdev.brightness_set = gpio_led_set;
  42. 118     else
  43. 119         led_dat->cdev.brightness_set_blocking = gpio_led_set_blocking;
  44. 120     led_dat->blinking = 0;
  45. 121     if (blink_set) {
  46. 122         led_dat->platform_gpio_blink_set = blink_set;
  47. 123         led_dat->cdev.blink_set = gpio_blink_set;
  48. 124     }
  49. 125     if (template->default_state == LEDS_GPIO_DEFSTATE_KEEP) {
  50. 126         state = gpiod_get_value_cansleep(led_dat->gpiod);
  51. 127         if (state < 0)
  52. 128             return state;
  53. 129     } else {
  54. 130         state = (template->default_state == LEDS_GPIO_DEFSTATE_ON);
  55. 131     }
  56. 132     led_dat->cdev.brightness = state ? LED_FULL : LED_OFF;
  57. 133     if (!template->retain_state_suspended)
  58. 134         led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME;
  59. 135     if (template->panic_indicator)
  60. 136         led_dat->cdev.flags |= LED_PANIC_INDICATOR;
  61. 137     if (template->retain_state_shutdown)
  62. 138         led_dat->cdev.flags |= LED_RETAIN_AT_SHUTDOWN;
  63. 139
  64. 140     ret = gpiod_direction_output(led_dat->gpiod, state);
  65. 141     if (ret < 0)
  66. 142         return ret;
  67. 143
  68. 144     return devm_of_led_classdev_register(parent, np, &led_dat->cdev);
  69. 145 }
复制代码

       这个函数我们重点就是去初始化led_classdev变量,然后通过devm_of_led_classdev_register函数向LED驱动框架核心层注册LED设备,devm_of_led_classdev_register函数和上一章我们使用的led_classdev_register基本上是一样的,也是LED驱动框架提供的API,上一章也说到了,函数前面加了devm前缀函数得特点,当模块在卸载时会自动释放,所以devm_of_led_classdev_register函数注册的LED设备在模块卸载时会自动卸载。
关于leds-gpio.c源文件我们就分析到这里,gpio_led_probe函数主要功能就是获取LED的设备信息,然后根据这些信息来初始化对应LED设备结构体led_classdev变量,然后向LED驱动框架核心层注册LED设备,总体上来说跟上一章我们的编写的leds-atk.c是一样的。
       1.3设备树节点编写
       打开文档内核源码目录Documentation/devicetree/bindings/leds/leds-gpio.txt,此文档详细的讲解了Linux内核源码提供的LED驱动程序leds-gpio.c对应的设备树节点该如何编写,我们在编写设备节点的时候要注意以下几点:
       ①、在设备树中创建一个节点表示LED灯设备,比如user-leds,如果板子上有多个LED灯的话每个LED灯都作为user-leds的子节点。
       ②、user-leds节点的compatible属性值一定要为“gpio-leds”。
       ③、设置子节点的label属性,此属性为可选,每个子节点都有一个label属性,label属性一般表示LED灯的名字,比如ps_led0、ps_led1、pl_led0等。
       ④、每个子节点必须要设置gpios属性值,表示此LED所使用的GPIO引脚。
       ⑤、可以设置“linux,default-trigger”属性值,也就是设置LED灯的默认触发类型,可以查阅Documentation/devicetree/bindings/leds/common.txt这个文档来查看可选功能,比如:
backlight:LED灯作为背光。
default-on:LED灯打开
heartbeat:LED灯作为心跳指示灯,可以作为系统运行提示灯。
ide-disk:LED灯作为硬盘活动指示灯。
timer:LED灯周期性闪烁,由定时器驱动,闪烁频率可以修改
       ⑥、可以设置“default-state”属性值,可以设置为on、off或keep,为on的时候LED灯默认打开,为off的话LED灯默认关闭,为keep的话LED灯保持当前模式。
根据上述几条要求,我们就可以在设备树文件创建LED设备节点了,打开arch/arm/boot/dts/system-top.dts设备树文件,在设备树根节点中添加如下所示LED设备节点:
  1. 示例代码 40.3.1 user-leds设备节点
  2. 44 user-leds {
  3. 45     compatible = "gpio-leds";
  4. 46
  5. 47     led@0 {
  6. 48         label = "ps_led0";
  7. 49         gpios = <&gpio0 7 GPIO_ACTIVE_HIGH>;
  8. 50         default-state = "off";
  9. 51     }
  10. 52 };
复制代码

        领航者底板上有4颗LED灯,这里我们只添加了一个,剩下的几个交给大家,算是给大家的一个小作业。
设备树文件修改完成以后保存退出,在内核源码目录下执行下面这条命令重新编译设备树:
第四十章 Linux自带的LED驱动实验12426.png

图 40.3.1 重新编译设备树文件

      将编译得到的dtb文件system-top.dtb重命名为system.dtb,将其拷贝到开发板SD启动卡的第一个分区Fat分区,替换掉之前的文件,然后重启开发板。
       1.4运行测试
       开发板重新启动之后查看/sys/bus/platform/devices/user-leds这个目录是否存在,如果存在的话就如到此目录中,如图 40.4.1所示:
第四十章 Linux自带的LED驱动实验12684.png

图 40.4.1 user-leds目录

       进入到leds目录中,此目录中的内容如图 40.4.2所示:
第四十章 Linux自带的LED驱动实验12805.png

图 40.4.2 leds目录

       从图 40.4.2中可以看出,在leds目录下有一个名为“ps_led0”子目录,这个子目录的名字就是我们在设备树中设置的label属性值。我们的设置究竟有没有用,最终是要通过测试才能知道的,进入到ps_led0目录下,可以看到brightness、max_brightness、trigger等文件,跟上一章所介绍的情况是一样的,同样LED的测试方法也是一样的,具体参考39.5.2小节。
如果能正常点亮和熄灭LED则说明Linux内核自带的LED驱动工作正常。我们一般会使用一个LED灯作为系统指示灯,系统运行正常的话这个LED指示灯就会一闪一闪的。如果要把PS_LED0作为系统指示灯,在user-leds这个设备节点中加入“linux,default-trigger”属性信息即可,属性值为“heartbeat”,修改完以后的user-leds节点内容如下:
  1. 示例代码 40.4.1 user-leds设备节点
  2. 44 user-leds {
  3. 45     compatible = "gpio-leds";
  4. 46
  5. 47     led@0 {
  6. 48         label = "ps_led0";
  7. 49         gpios = <&gpio0 7 GPIO_ACTIVE_HIGH>;
  8. 50         linux,default-trigger = "heartbeat";
  9. 51         default-state = "on";
  10. 52     }
  11. 53 };
复制代码
       第50行,设置PS_LED0为heartbeat触发模式。
       第51行,默认打开PS_LED0。
       重新编译设备树并且使用新的设备树启动Linux系统,启动以后PS_LED0就会闪烁,作为系统心跳指示灯,表示系统正在运行。



回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子论坛 ( 公安交互式论坛备案:44190002001997 粤ICP备09047143号 )

GMT+8, 2022-8-8 22:29

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表