超级版主 
   
	- 积分
 - 5083
 
        - 金钱
 - 5083 
 
       - 注册时间
 - 2019-5-8
 
      - 在线时间
 - 1269 小时
 
 
 
 | 
 
 本帖最后由 正点原子运营 于 2022-3-23 18:28 编辑  
 
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  
 
  
 
 
  
 
 
第四十章 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配置菜单: 
- make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
 
  
- [color=rgb(51, 102, 153) !important]
 
  复制代码 
  
按照如下路径打开LED驱动配置项: 
- -> Device Drivers
 
 -         -> LED Support (NEW_LEDS [=y])
 
 -                         ->LED Support for GPIO connected LEDs
 
  
- [color=rgb(51, 102, 153) !important]
 
  复制代码 
  
       按照上述路径,选择“LED Support for GPIO connected LEDs”,将其编译进Linux内核,也即是在此选项上按下“Y”键,使此选项前面变为“<*>”,默认情况下已经配置好了,如图 40.1.1所示: 
 
图 40.1.1 使能LED灯驱动  
       在“LED Support for GPIO connected LEDs”上按下可以打开此选项的帮助信息,如图 40.1.2所示: 
 
图 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”这一行,如所示: 
 
图 40.1.3 .config文件内容  
       由于我们使用的linux内核源码默认情况下已经使能了LED驱动,所以我们之前编译好的内核镜像中已经存在LED驱动了,所以不需要再次编译内核源码,使用之前编译好的内核镜像zImage即可! 
       1.2Linux内核自带LED驱动简介 
1.2.1LED驱动框架分析 
       内核源码提供的LED驱动源文件为drivers/leds/leds-gpio.c,大家可以打开/drivers/leds/Makefile这个文件,找到如下所示内容:- 示例代码 40.2.1 drivers/leds/Makefile文件代码段
 
 -   3 # LED Core
 
 -   4 obj-$(CONFIG_NEW_LEDS)                        += led-core.o
 
 -   5 obj-$(CONFIG_LEDS_CLASS)                        += led-class.o
 
 -   6 obj-$(CONFIG_LEDS_CLASS_FLASH)        += led-class-flash.o
 
 -   7 obj-$(CONFIG_LEDS_TRIGGERS)                += led-triggers.o
 
 -   8
 
 -   9 # LED Platform Drivers
 
 - ......
 
 - 29 obj-$(CONFIG_LEDS_GPIO_REGISTER)        += leds-gpio-register.o
 
 - 30 obj-$(CONFIG_LEDS_GPIO)                        += leds-gpio.o
 
 - 31 obj-$(CONFIG_LEDS_LP3944)                        += leds-lp3944.o
 
 - ......
 
  
  复制代码 
       第30行,如果定义了CONFIG_LEDS_GPIO的话就会编译leds-gpio.c这个文件,在上一小节我们在.config文件中看到有“CONFIG_LEDS_GPIO=y”这一行,因此leds-gpio.c驱动文件就会被编译。 
       接下来我们看一下leds-gpio.c这个驱动文件,找到如下所示内容:- 示例代码 40.2.2 leds-gpio.c文件代码段
 
 - 227 static const struct of_device_id of_gpio_leds_match[] = {
 
 - 228     { .compatible = "gpio-leds", },
 
 - 229     {},
 
 - 230 };
 
 - 231
 
 - 232 MODULE_DEVICE_TABLE(of, of_gpio_leds_match);
 
 - ......
 
 - 279 static struct platform_driver gpio_led_driver = {
 
 - 280     .probe                = gpio_led_probe,
 
 - 281     .shutdown        = gpio_led_shutdown,
 
 - 282     .driver                = {
 
 - 283         .name   = "leds-gpio",
 
 - 284         .of_match_table = of_gpio_leds_match,
 
 - 285     },
 
 - 286 };
 
 - 287
 
 - 288 module_platform_driver(gpio_led_driver);
 
 - 289
 
 - 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>>");
 
 - 291 MODULE_DESCRIPTION("GPIO LED driver");
 
 - 292 MODULE_LICENSE("GPL");
 
 - 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”的文件,如所示: 
 
图 40.2.1 platform总线下的LED驱动  
       从第288行可以知道,在这个驱动源码中也使用module_platform_driver宏创建模块的入口函数和出口函数,非常的方便! 
1.2.2gpio_led_probe函数简析 
       当驱动和设备匹配以后gpio_led_probe函数就会执行,gpio_led_probe函数内容如下所示: 
- <font size="2">示例代码 40.2.3 gpio_led_probe函数
 
 - 234 static int gpio_led_probe(struct platform_device *pdev)
 
 - 235 {
 
 - 236     struct gpio_led_platform_data *pdata = dev_get_platdata(&pdev->dev);
 
 - 237     struct gpio_leds_priv *priv;
 
 - 238     int i, ret = 0;
 
 - 239
 
 - 240     if (pdata && pdata->num_leds) {
 
 - 241         priv = devm_kzalloc(&pdev->dev,
 
 - 242                     sizeof_gpio_leds_priv(pdata->num_leds),
 
 - 243                         GFP_KERNEL);
 
 - 244         if (!priv)
 
 - 245             return -ENOMEM;
 
 - 246
 
 - 247         priv->num_leds = pdata->num_leds;
 
 - 248         for (i = 0; i < priv->num_leds; i++) {
 
 - <span style="font-style: italic;"><span style="font-style: normal;">249             ret = create_gpio_led(&pdata->leds, &priv->leds,
 
 - 250                              &pdev->dev, NULL,
 
 - 251                              pdata->gpio_blink_set);
 
 - 252             if (ret < 0)
 
 - 253                 return ret;
 
 - 254         }
 
 - 255     } else {
 
 - 256         priv = gpio_leds_create(pdev);
 
 - 257         if (IS_ERR(priv))
 
 - 258             return PTR_ERR(priv);
 
 - 259     }
 
 - 260
 
 - 261     platform_set_drvdata(pdev, priv);
 
 - 262
 
 - 263     return 0;
 
 - 264 }</span></span></font>
 
  复制代码 
  
 
       该函数中分为两种情况处理,不使用设备树的方式和使用设备树的方式,第241~254行,处理不使用设备树的方式,不使用设备就是像第三十七章那样分开两部分platform设备和platform驱动分别进行注册;第254~258行,处理使用设备树的方式,来看看gpio_leds_create函数做了些什么,gpio_leds_create函数内容如下: 
- 示例代码 40.2.4 gpio_leds_create函数
 
 - 158 static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev)
 
 - 159 {
 
 - 160     struct device *dev = &pdev->dev;
 
 - 161     struct fwnode_handle *child;
 
 - 162     struct gpio_leds_priv *priv;
 
 - 163     int count, ret;
 
 - 164
 
 - 165     count = device_get_child_node_count(dev);
 
 - 166     if (!count)
 
 - 167         return ERR_PTR(-ENODEV);
 
 - 168
 
 - 169     priv = devm_kzalloc(dev, sizeof_gpio_leds_priv(count), GFP_KERNEL);
 
 - 170     if (!priv)
 
 - 171         return ERR_PTR(-ENOMEM);
 
 - 172
 
 - 173     device_for_each_child_node(dev, child) {
 
 - 174         struct gpio_led_data *led_dat = &priv->leds[priv->num_leds];
 
 - 175         struct gpio_led led = {};
 
 - 176         const char *state = NULL;
 
 - 177         struct device_node *np = to_of_node(child);
 
 - 178
 
 - 179         ret = fwnode_property_read_string(child, "label", &led.name);
 
 - 180         if (ret && IS_ENABLED(CONFIG_OF) && np)
 
 - 181             led.name = np->name;
 
 - 182         if (!led.name) {
 
 - 183             fwnode_handle_put(child);
 
 - 184             return ERR_PTR(-EINVAL);
 
 - 185         }
 
 - 186
 
 - 187         led.gpiod = devm_fwnode_get_gpiod_from_child(dev, NULL, child,
 
 - 188                                  GPIOD_ASIS,
 
 - 189                                  led.name);
 
 - 190         if (IS_ERR(led.gpiod)) {
 
 - 191             fwnode_handle_put(child);
 
 - 192             return ERR_CAST(led.gpiod);
 
 - 193         }
 
 - 194
 
 - 195         fwnode_property_read_string(child, "linux,default-trigger",
 
 - 196                         &led.default_trigger);
 
 - 197
 
 - 198         if (!fwnode_property_read_string(child, "default-state",
 
 - 199                          &state)) {
 
 - 200             if (!strcmp(state, "keep"))
 
 - 201                 led.default_state = LEDS_GPIO_DEFSTATE_KEEP;
 
 - 202             else if (!strcmp(state, "on"))
 
 - 203                 led.default_state = LEDS_GPIO_DEFSTATE_ON;
 
 - 204             else
 
 - 205                 led.default_state = LEDS_GPIO_DEFSTATE_OFF;
 
 - 206         }
 
 - 207
 
 - 208         if (fwnode_property_present(child, "retain-state-suspended"))
 
 - 209             led.retain_state_suspended = 1;
 
 - 210         if (fwnode_property_present(child, "retain-state-shutdown"))
 
 - 211             led.retain_state_shutdown = 1;
 
 - 212         if (fwnode_property_present(child, "panic-indicator"))
 
 - 213             led.panic_indicator = 1;
 
 - 214
 
 - 215         ret = create_gpio_led(&led, led_dat, dev, np, NULL);
 
 - 216         if (ret < 0) {
 
 - 217             fwnode_handle_put(child);
 
 - 218             return ERR_PTR(ret);
 
 - 219         }
 
 - 220         led_dat->cdev.dev->of_node = np;
 
 - 221         priv->num_leds++;
 
 - 222     }
 
 - 223
 
 - 224     return priv;
 
 - 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”开头的属性,例如: 
- gpio = <&gpio0 7 GPIO_ACTIVE_HIGH>;
 - gpios = <&gpio0 7 GPIO_ACTIVE_HIGH>;
 - gpios-led = <&gpio0 7 GPIO_ACTIVE_HIGH>;
 
 
  
[color=rgb(51, 102, 153) !important]复制代码 
 
       第195行,获取子节点中的“linux,default-trigger”属性值,这个属性定义了LED的触发状态,在上一章中已经给大家介绍过了。 
       第198~206行,获取子节点中的“default-state”属性值,这个属性定义了LED的默认状态。 
       第215行调用了create_gpio_led函数,create_gpio_led函数内容如下所示: 
- 示例代码 40.2.5 create_gpio_led函数
 
 - 078 static int create_gpio_led(const struct gpio_led *template,
 
 - 079     struct gpio_led_data *led_dat, struct device *parent,
 
 - 080     struct device_node *np, gpio_blink_set_t blink_set)
 
 - 081 {
 
 - 082     int ret, state;
 
 - 083
 
 - 084     led_dat->gpiod = template->gpiod;
 
 - 085     if (!led_dat->gpiod) {
 
 - 086         /*
 
 - 087          * This is the legacy code path for platform code that
 
 - 088          * still uses GPIO numbers. Ultimately we would like to get
 
 - 089          * rid of this block completely.
 
 - 090          */
 
 - 091         unsigned long flags = GPIOF_OUT_INIT_LOW;
 
 - 092
 
 - 093         /* skip leds that aren't available */
 
 - 094         if (!gpio_is_valid(template->gpio)) {
 
 - 095             dev_info(parent, "Skipping unavailable LED gpio %d (%s)\n",
 
 - 096                     template->gpio, template->name);
 
 - 097             return 0;
 
 - 098         }
 
 - 099
 
 - 100         if (template->active_low)
 
 - 101             flags |= GPIOF_ACTIVE_LOW;
 
 - 102
 
 - 103         ret = devm_gpio_request_one(parent, template->gpio, flags,
 
 - 104                         template->name);
 
 - 105         if (ret < 0)
 
 - 106             return ret;
 
 - 107
 
 - 108         led_dat->gpiod = gpio_to_desc(template->gpio);
 
 - 109         if (!led_dat->gpiod)
 
 - 110             return -EINVAL;
 
 - 111     }
 
 - 112
 
 - 113     led_dat->cdev.name = template->name;
 
 - 114     led_dat->cdev.default_trigger = template->default_trigger;
 
 - 115     led_dat->can_sleep = gpiod_cansleep(led_dat->gpiod);
 
 - 116     if (!led_dat->can_sleep)
 
 - 117         led_dat->cdev.brightness_set = gpio_led_set;
 
 - 118     else
 
 - 119         led_dat->cdev.brightness_set_blocking = gpio_led_set_blocking;
 
 - 120     led_dat->blinking = 0;
 
 - 121     if (blink_set) {
 
 - 122         led_dat->platform_gpio_blink_set = blink_set;
 
 - 123         led_dat->cdev.blink_set = gpio_blink_set;
 
 - 124     }
 
 - 125     if (template->default_state == LEDS_GPIO_DEFSTATE_KEEP) {
 
 - 126         state = gpiod_get_value_cansleep(led_dat->gpiod);
 
 - 127         if (state < 0)
 
 - 128             return state;
 
 - 129     } else {
 
 - 130         state = (template->default_state == LEDS_GPIO_DEFSTATE_ON);
 
 - 131     }
 
 - 132     led_dat->cdev.brightness = state ? LED_FULL : LED_OFF;
 
 - 133     if (!template->retain_state_suspended)
 
 - 134         led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME;
 
 - 135     if (template->panic_indicator)
 
 - 136         led_dat->cdev.flags |= LED_PANIC_INDICATOR;
 
 - 137     if (template->retain_state_shutdown)
 
 - 138         led_dat->cdev.flags |= LED_RETAIN_AT_SHUTDOWN;
 
 - 139
 
 - 140     ret = gpiod_direction_output(led_dat->gpiod, state);
 
 - 141     if (ret < 0)
 
 - 142         return ret;
 
 - 143
 
 - 144     return devm_of_led_classdev_register(parent, np, &led_dat->cdev);
 
 - 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设备节点: 
- 示例代码 40.3.1 user-leds设备节点
 
 - 44 user-leds {
 
 - 45     compatible = "gpio-leds";
 
 - 46
 
 - 47     led@0 {
 
 - 48         label = "ps_led0";
 
 - 49         gpios = <&gpio0 7 GPIO_ACTIVE_HIGH>;
 
 - 50         default-state = "off";
 
 - 51     }
 
 - 52 };
 
 
  复制代码 
         领航者底板上有4颗LED灯,这里我们只添加了一个,剩下的几个交给大家,算是给大家的一个小作业。 
设备树文件修改完成以后保存退出,在内核源码目录下执行下面这条命令重新编译设备树: 
 
图 40.3.1 重新编译设备树文件  
      将编译得到的dtb文件system-top.dtb重命名为system.dtb,将其拷贝到开发板SD启动卡的第一个分区Fat分区,替换掉之前的文件,然后重启开发板。 
       1.4运行测试 
       开发板重新启动之后查看/sys/bus/platform/devices/user-leds这个目录是否存在,如果存在的话就如到此目录中,如图 40.4.1所示: 
 
图 40.4.1 user-leds目录  
       进入到leds目录中,此目录中的内容如图 40.4.2所示: 
 
图 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节点内容如下: 
- 示例代码 40.4.1 user-leds设备节点
 
 - 44 user-leds {
 
 - 45     compatible = "gpio-leds";
 
 - 46
 
 - 47     led@0 {
 
 - 48         label = "ps_led0";
 
 - 49         gpios = <&gpio0 7 GPIO_ACTIVE_HIGH>;
 
 - 50         linux,default-trigger = "heartbeat";
 
 - 51         default-state = "on";
 
 - 52     }
 
 - 53 };
 
  复制代码 
       第50行,设置PS_LED0为heartbeat触发模式。 
       第51行,默认打开PS_LED0。 
       重新编译设备树并且使用新的设备树启动Linux系统,启动以后PS_LED0就会闪烁,作为系统心跳指示灯,表示系统正在运行。 
 
 
 
 |   
 
 
 
 |