正点原子 发表于 2022-2-28 15:32:31

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

本帖最后由 正点原子 于 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







第四十章 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
按照如下路径打开LED驱动配置项:
-> Device Drivers                                                                              
      -> LED Support (NEW_LEDS [=y])
                        ->LED Support for GPIO connected LEDs
       按照上述路径,选择“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;
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>;
       第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就会闪烁,作为系统心跳指示灯,表示系统正在运行。


页: [1]
查看完整版本: 《领航者ZYNQ之嵌入式Linux开发指南_V2.0》第四十章 LED驱动实验