超级版主
- 积分
- 4666
- 金钱
- 4666
- 注册时间
- 2019-5-8
- 在线时间
- 1224 小时
|
本帖最后由 正点原子运营 于 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就会闪烁,作为系统心跳指示灯,表示系统正在运行。
|
|