Linux自带的LED灯驱动实验
4 ?5 i3 ?- G' x: f# k j- G前面我们都是自己编写LED灯驱动,其实像LED灯这样非常基础的设备驱动,Linux内核已经集成了。Linux内核的LED灯驱动采用platform框架,因此我们只需要按照要求在设备树文件中添加相应的LED节点即可,本章我们就来学习如何使用Linux内核自带的LED驱动来驱动正点原子的STM32MP1开发板上的LED0和LED1这两个LED灯。: ^; z3 C8 c3 D' z3 p+ ~! T
, k+ W8 Y1 H9 r2 {36.1 Linux内核自带LED驱动使能& c' p0 W6 y: y" Z R
上一章节我们编写基于设备树的platform LED灯驱动,其实Linux内核已经自带了LED灯驱动,要使用Linux内核自带的LED灯驱动首先得先配置Linux内核,使能自带的LED灯驱动,输入如下命令打开Linux配置菜单:
& F4 I# l9 F" cmake menuconfig' I5 ]8 o" L+ @* B; ?
按照如下路径打开LED驱动配置项:
1 }- U: E: h8 J2 Q+ jDevice Drivers
! `0 o- O; y5 W ALED Support (NEW_LEDS [=y])
/ k2 E* @ R* b: Y# NLED Support for GPIO connected LEDs$ R- z0 z1 f* N ]3 z* W
按照上述路径,选择“LED Support for GPIO connected LEDs”,将其编译进Linux内核,也即是在此选项上按下“Y”键,使此选项前面变为“<*>”,如图36.1.1所示:
$ R. _2 B8 @+ F) Q. O- M
+ j4 ]% j* ]- q& x3 J0 u; D
+ u7 L3 G: H1 B1 m/ U
! K/ Q! `, Y0 S! e; J; Z( w
图36.1.1 使能LED灯驱动4 `7 e0 W4 {' _4 e3 g! Z {
在“LED Support for GPIO connected LEDs”上按下“?”健可以打开此选项的帮助信息,如图36.1.2所示:
- b2 A0 @; f1 s( H$ W
0 x/ T9 U O" X6 h0 x/ W
4 U! G6 O* v3 y6 W* _- r7 V) K- X5 Y, D; B8 q% ]. f
图36.1.2 内部LED灯驱动帮助信息1 [& R/ d+ `1 ?; G; }; x- e
从图36.1.2可以看出,把Linux内部自带的LED灯驱动编译进内核以后,CONFIG_LEDS_GPIO就会等于‘y’,Linux会根据CONFIG_LEDS_GPIO的值来选择如何编译LED灯驱动,如果为‘y’就将其编译进Linux内核。4 `( ?7 Y& D' _) ]2 a
配置好Linux内核以后退出配置界面,打开.config文件,会找到“CONFIG_LEDS_GPIO=y”这一行,如图36.1.3所示:
4 A& r! P4 v0 V- D, Z( L
3 V: V+ ~' q$ S6 f3 E
0 O% S$ b4 J w0 w0 v$ \' L* Q0 l
T: p4 m' X" I) G2 B图36.1.3 .config文件内容
. h8 t, u Z5 H# t重新编译Linux内核,然后使用新编译出来的zImage镜像启动开发板。
2 Z: b- z. `6 J" T36.2 Linux内核自带LED驱动简介
0 Z0 s7 p8 s3 e# L/ X# W) k9 r7 i36.2.1 LED灯驱动框架分析1 f4 B8 X9 m. I) H* ]
LED灯驱动文件为/drivers/leds/leds-gpio.c,大家可以打开/drivers/leds/Makefile这个文件,找到如下所示内容:' l! E) Q) W& i, w0 v( z( l" |
2 X* R, X& w( z- l; G* O- y( g
- 示例代码36.2.1.1 /drivers/leds/Makefile文件代码段
9 C- ]/ M$ r" |7 ^ - 1 # SPDX-License-Identifier: GPL-2.07 W6 H k- T$ a* h K
- 2 0 |8 G3 m/ o+ x$ e ~. M) o: y
- 3 # LED Core* @% C( o7 ^: [) I( x; S6 _
- 4 obj-$(CONFIG_NEW_LEDS) += led-core.o
# `5 T1 u/ {: W( }) P. l& C - ......
% J5 y) A S! X7 H+ o6 c( d - 31 obj-$(CONFIG_LEDS_PCA9532) += leds-pca9532.o
3 ?4 o5 y% p; L0 G* ?1 O, a' J - 32 obj-$(CONFIG_LEDS_GPIO_REGISTER) += leds-gpio-register.o
8 C5 m/ q; }/ Z6 i! J' L - 33 obj-$(CONFIG_LEDS_GPIO) += leds-gpio.o
9 b8 k# v$ i/ r% g - 34 obj-$(CONFIG_LEDS_LP3944) += leds-lp3944.o! p, _& a. a6 |: J: E9 }7 @
- ......
复制代码 3 h. ?5 Y& m% c& ^
第33行,如果定义了CONFIG_LEDS_GPIO的话就会编译leds-gpio.c这个文件,在上一小节我们选择将LED驱动编译进Linux内核,在.config文件中就会有“CONFIG_LEDS_GPIO=y”这一行,因此leds-gpio.c驱动文件就会被编译。2 P% @3 ^1 |8 \
接下来我们看一下leds-gpio.c这个驱动文件,找到如下所示内容:2 P/ e, w6 }/ W0 I5 K% k, e
; z# ~& Z! d3 I; ~. O- 示例代码36.2.1.2 leds-gpio.c文件代码段
1 {0 \6 D8 I9 Q5 D J5 Z6 v8 t - 203 static const struct of_device_id of_gpio_leds_match[] = {, `/ v. |2 q) \* Y J$ `- a: u
- 204 { .compatible = "gpio-leds", },
; Q- i8 T2 i; G" F; X - 205 {},5 y. V; u/ P! M7 M {' W
- 206 };
" m9 T4 S8 Q/ i, _ - 2077 R! R7 z! O" u6 j. C* _+ Y
- ......
" y K& B- u5 f& |; F0 r2 u - 316 static struct platform_driver gpio_led_driver = {7 b) t* `" M% C
- 317 .probe = gpio_led_probe,
3 l9 S3 L% q8 ?( S% _, ?+ N, n! S - 318 .shutdown = gpio_led_shutdown,
7 W% R& B; G# A6 Y - 319 .driver = {2 F4 y, ?# r3 P
- 320 .name = "leds-gpio",
# v' `& L- s2 Q6 j6 J - 321 .of_match_table = of_gpio_leds_match,6 P) U$ G$ W. J$ c$ T' Z- ~. t8 U
- 322 },
5 I6 n \9 x/ w. C" a - 323 };) Z' `+ I0 z$ E( Q8 e: b4 h, B
- 324
9 g& i) f1 R. O7 i - 325 module_platform_driver(gpio_led_driver);
复制代码 # w2 f! ?( k8 w2 S4 e" v+ K9 L
第203~206行,LED驱动的匹配表,此表只有一个匹配项,compatible内容为“gpio-leds”,因此设备树中的LED灯设备节点的compatible属性值也要为“gpio-leds”,否则设备和驱动匹配不成功,驱动就没法工作。* d. P/ z9 I; V- [; r) Q/ d0 u
第316~323行,platform_driver驱动结构体变量,可以看出,Linux内核自带的LED驱动采用了platform框架。第317行可以看出probe函数为gpio_led_probe,因此当驱动和设备匹配成功以后gpio_led_probe函数就会执行。从320行可以看出,驱动名字为“leds-gpio”,因此会在/sys/bus/platform/drivers目录下存在一个名为“leds-gpio”的文件,如图36.2.1.1所示:5 y4 j4 \4 E4 i* v' f
: Y1 w/ j, V0 E( |4 D' K
) K% b; I3 q& l2 F. G: _: \
- D% c) v' E) O6 J: X/ M L
图36.2.1.1 leds-gpio驱动文件
9 U/ X$ M' y( E, ?6 n3 O第326行通过module_platform_driver函数向Linux内核注册gpio_led_driver这个platform驱动。
- p, C) ?% \2 x/ g- j3 U36.2.2 module_platform_driver函数简析
# L' Q, x {6 j在上一小节中我们知道LED驱动会采用module_platform_driver函数向Linux内核注册platform驱动,其实在Linux内核中会大量采用module_platform_driver来完成向Linux内核注册platform驱动的操作。module_platform_driver定义在include/linux/platform_device.h文件中,为一个宏,定义如下:) |0 S5 ]" S$ o' e
- 示例代码36.2.2.1 module_platform_driver函数
" z7 @+ x, c8 i0 O - 237 #define module_platform_driver(__platform_driver) \
7 y" s2 H, Y; G/ l1 z - 238 module_driver(__platform_driver, platform_driver_register, \
' l* \ b4 V. p. p) B - 239 platform_driver_unregister)
复制代码
4 g% b4 W* m' k: L& p$ e可以看出,module_platform_driver依赖module_driver,module_driver也是一个宏,定义在include/linux/device.h文件中,内容如下:
! J) O* b. K9 x' z* _
0 U8 b1 P8 y7 x& r9 d# c- 示例代码36.2.2.2 module_driver函数7 G v2 J$ B8 h1 X: }( B8 z
- 1898 #define module_driver(__driver, __register, __unregister, ...) \
# a8 d4 H' P2 Z6 `! w4 R8 F - 1899 static int __init __driver##_init(void) \
2 A! d5 d4 | }1 F7 Z4 c- l - 1900 { \
2 `1 U, J$ g9 u! {1 r - 1901 return __register(&(__driver) , ##__VA_ARGS__); \
- W1 ]3 i; A5 s" K Z - 1902 } \
9 S; o+ c1 z2 d - 1903 module_init(__driver##_init); \
1 o) e- g; m( x* k2 o1 _. \ - 1904 static void __exit __driver##_exit(void) \
; l7 a0 G+ F) Z+ t# V - 1905 { \
. ^$ H5 w3 ~% ]2 F& h - 1906 __unregister(&(__driver) , ##__VA_ARGS__); \7 c: {3 t$ G% z. z W5 l
- 1907 } \
, \3 Y# Z2 v. P: r8 Y0 Y - 1908 module_exit(__driver##_exit);
复制代码 * C$ v( q' J0 I8 V! Y5 k
借助示例代码36.2.2.1和示例代码36.2.2.2,将:9 L4 S0 A) Y z$ k. J# Y
0 M5 S8 H/ R$ I9 U2 Z
module_platform_driver(gpio_led_driver)
: X# w6 R2 T% k& V5 J展开以后就是:$ P( [$ _% C1 n( }' ~/ N
static int __init gpio_led_driver_init(void)
3 F1 \7 w# a; f; X c2 Y{
4 e9 H0 ^9 y0 f8 Greturn platform_driver_register (&(gpio_led_driver));" I# U3 q' ~7 z$ ^4 i% I# I- p
}
- h, @: E0 h$ V1 c V' j! F) Vmodule_init(gpio_led_driver_init);- u: b; f5 d2 g
% Q0 T/ N) I! O6 D
static void __exit gpio_led_driver_exit(void), q" E. u/ V% l T' v
{
: m8 ~8 ?6 I- z" L7 Iplatform_driver_unregister (&(gpio_led_driver) );3 `/ r R. z$ S+ P. |
}0 E7 v, @) z7 M5 K
module_exit(gpio_led_driver_exit);
' K% L& G2 L, U0 N上面的代码不就是标准的注册和删除platform驱动吗?因此module_platform_driver函数的功能就是完成platform驱动的注册和删除。
6 t( T2 @3 d. y. d& \% \$ z6 O( l2 T
36.2.3 gpio_led_probe函数简析
2 V* x) @% S5 ?+ @. _4 K当驱动和设备匹配以后gpio_led_probe函数就会执行,此函数主要是从设备树中获取LED灯的GPIO信息,缩减后的函数内容如下所示:
) C) ~: m+ i( q+ {' j& f' p
X1 m, |6 U. C, r- 示例代码36.2.3.1 gpio_led_probe函数4 a3 f( p" {. ^
- 256 static int gpio_led_probe(struct platform_device *pdev)& s- ^* _/ N1 l
- 257 {
8 i- Y/ \# @( u. B - 258 struct gpio_led_platform_data *pdata = 7 t# i9 q* q1 @$ x
- dev_get_platdata(&pdev->dev);
. ~) b7 P3 B, r' b! n7 v - 259 struct gpio_leds_priv *priv;# F x5 v1 g9 W8 ~6 L- @/ A) |
- 260 int i, ret = 0;
b3 ]' o8 O. ?+ ? - 261. J- o+ q0 e# H) g, x
- 262 if (pdata && pdata->num_leds) { /* 非设备树方式 */2 f6 J1 z6 D. E5 D9 `% ^
- /* 获取platform_device信息 */
7 P0 z/ v6 h, M0 s+ R - .....+ D h( n0 h: d' C
- 292 } else { /* 采用设备树 */
8 H# r+ m. B( V8 F7 |& R! U7 S - 293 priv = gpio_leds_create(pdev);
! X9 O% h! S5 z' D - 294 if (IS_ERR(priv))
" y2 Z7 W6 \. b# H! ? - 295 return PTR_ERR(priv);
: q' l/ N" O3 L. e - 296 }
{/ W9 H: G, i4 k" b* k) ~ - 297
+ Q9 K' _+ l7 r$ Y8 ^3 l/ E& E - 298 platform_set_drvdata(pdev, priv);
% G0 K D3 B- M8 n! Y# b - 299( W5 c& o- [0 a( b( J
- 300 return 0;# ]9 m( U1 V# P9 ?! I
- 301 }
复制代码
5 G1 ]4 B- S3 @0 s. W第293~295行,如果使用设备树的话,使用gpio_leds_create函数从设备树中提取设备信息,获取到的LED灯GPIO信息保存在返回值中,gpio_leds_create函数内容如下:/ F, M. L, n, F" }8 z! V9 C9 I
* [ G8 n" r7 P% L* a. r9 a, p
- 示例代码36.2.3.2 gpio_leds_create函数. N6 ~) T3 ]2 K2 f' h
- 134 static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev)1 [% E6 f3 m8 `# X7 c. o2 _5 f
- 135 {
+ \$ @4 E+ D) B d) }2 e3 ` F& E0 X+ K - 136 struct device *dev = &pdev->dev;! G) r6 C# h7 M: X
- 137 struct fwnode_handle *child;& t0 n7 f. _9 w5 Z
- 138 struct gpio_leds_priv *priv;
; k5 p3 @! m) k6 f - 139 int count, ret;
+ k Z$ Z. P: i$ ]- m: F2 R - 1408 ?6 T' x5 Q0 i8 t: D
- 141 count = device_get_child_node_count(dev);
{$ q! a8 L! h9 h - 142 if (!count)
6 O7 [& O# z J4 t, T7 m% n - 143 return ERR_PTR(-ENODEV);
p: F+ `6 ?# L8 X& W - 144. t; ^7 @# e, L% H; M
- 145 priv = devm_kzalloc(dev, sizeof_gpio_leds_priv(count), ! y7 S0 ]8 `' \$ p. D9 r9 P3 o
- GFP_KERNEL);
' f2 J: x* n8 ?+ H* ?# V - 146 if (!priv)
3 }& D' I6 b% j5 l0 |& I& _! ]1 [ - 147 return ERR_PTR(-ENOMEM);
5 ^0 Q# b5 ]1 i$ H* `/ j - 148
6 X. o/ H+ Q. L: @1 c- v4 K0 u - 149 device_for_each_child_node(dev, child) {
) e ^( ^- u! V( G - 150 struct gpio_led_data *led_dat = &priv->leds[priv->num_leds];
" T, U+ d7 K; _0 Y( L D" H; B - 151 struct gpio_led led = {};( u* u1 A! I L7 W4 q4 \
- 152 const char *state = NULL;
) E8 P- Q6 t- j9 t4 d4 c6 k( q - 1539 o8 t2 V3 `* P( J# `
- 154 /*
' W1 f5 ? ~& Z9 X - 155 * Acquire gpiod from DT with uninitialized label, which
& x' p2 {, h& _4 d - 156 * will be updated after LED class device is registered,
9 |5 g1 ]" g2 D( [2 ]+ r - 157 * Only then the final LED name is known.$ y: a: X5 |" w
- 158 */
1 {7 d% B" g% e$ A - 159 led.gpiod = devm_fwnode_get_gpiod_from_child(dev, NULL, 9 P/ n$ p3 }5 t2 e, L6 C) R2 F
- child,
2 h! g2 @5 o/ o) f - 160 GPIOD_ASIS,
! g8 h! D, R3 P8 R5 m W - 161 NULL);" [: n$ X: [0 }: b
- 162 if (IS_ERR(led.gpiod)) {
' t* J7 c! [$ S$ u" { - 163 fwnode_handle_put(child);* h7 ~4 ^7 y% ?, v
- 164 return ERR_CAST(led.gpiod);
- B' V7 m/ t8 }" c( G( l; c - 165 } j) Y2 S0 F3 ?) @4 s% J
- 166" W( G/ F2 T7 m9 A4 S# ?
- 167 led_dat->gpiod = led.gpiod;4 W# }: S) j5 _* e+ e
- 168/ q% z9 ]# o7 x" O$ `2 v) U
- 169 fwnode_property_read_string(child, "linux,default-trigger",; }; C! ?4 g+ ~
- 170 &led.default_trigger);+ a4 w# r w, _' d, f, ?/ W, K
- 171
- J" Q- S. R- N2 U p" \ - 172 if (!fwnode_property_read_string(child, "default-state",; o; E- @1 G" _0 Z
- 173 &state)) {8 r! F' x7 V# P6 |, U# d
- 174 if (!strcmp(state, "keep"))
- K2 `3 H- \7 w1 M( {) z$ @ - 175 led.default_state = LEDS_GPIO_DEFSTATE_KEEP;
+ Y" G& L: F, _ w$ x+ i- P - 176 else if (!strcmp(state, "on"))/ P, L7 l! Q( c _2 ^
- 177 led.default_state = LEDS_GPIO_DEFSTATE_ON;; P+ y1 [& f9 r) g
- 178 else! h' ~5 i# m9 a, k; e! }$ f& `
- 179 led.default_state = LEDS_GPIO_DEFSTATE_OFF;5 H' ^. K2 E" D4 u8 |
- 180 }- k& d, L- [/ w9 c1 s5 D; |) [+ I
- 181
( g7 O; U" b# ~* i6 N - 182 if (fwnode_property_present(child, "retain-state-suspended")): J! ^; R0 D0 ~& d2 w7 b' j, l
- 183 led.retain_state_suspended = 1;" \0 B4 q3 G. i, T
- 184 if (fwnode_property_present(child, "retain-state-shutdown")) C }! _3 I+ i% e& e$ t
- 185 led.retain_state_shutdown = 1;+ m1 j! o" m& H I3 u6 E
- 186 if (fwnode_property_present(child, "panic-indicator"))
1 n% I# j0 \) S3 m - 187 led.panic_indicator = 1; b! | }- u& D2 f) Y
- 188
, w& C2 [4 p( U" l$ k M: I - 189 ret = create_gpio_led(&led, led_dat, dev, child, NULL);
. a; ?- n9 z6 [: i5 U! K4 j - 190 if (ret < 0) {
K+ r6 |" H0 R# Y - 191 fwnode_handle_put(child);
" b' T% ^: q% w; y3 g8 F( | - 192 return ERR_PTR(ret);
4 M+ E& c. e z) q' T - 193 }
- x* W. d2 D) R6 n+ @. @ - 194 /* Set gpiod label to match the corresponding LED name. */
1 w4 B7 `0 V8 g* L - 195 gpiod_set_consumer_name(led_dat->gpiod,& P$ a* G: e' ~/ g
- 196 led_dat->cdev.dev->kobj.name);' O9 l# X T& A, q3 W5 d% W
- 197 priv->num_leds++;
& N) U, `; Y) R" r( q - 198 }
% T# l1 j+ R7 d; w4 }$ _6 W; z - 199% }7 H: d0 ]. ^; R( i7 ^
- 200 return priv;2 V2 X. r' |! n. P( ~* S0 }
- 201 }
复制代码 : I5 l& Q7 N3 L
第141行,调用device_get_child_node_count函数统计子节点数量,一般在设备树中创建一个节点表示LED灯,然后在这个节点下面为每个LED灯创建一个子节点。因此子节点数量也是LED灯的数量。
H! s* F& p1 x7 G第149行,遍历每个子节点,获取每个子节点的信息。. A6 r3 B/ N! b! S; W8 H
第159行,获取LED灯所使用的GPIO信息。2 L* g4 S% G, q; P' ?. l4 O5 i. y
第169~170行,获取“linux,default-trigger”属性值,可以通过此属性设置某个LED灯在Linux系统中的默认功能,比如作为系统心跳指示灯等等。/ r d% O3 E2 ^1 V( _. P7 F
第172~173行,获取“default-state”属性值,也就是LED灯的默认状态属性。
% |4 P- v4 @. `! f, u2 R第189行,调用create_gpio_led函数创建LED相关的io,其实就是设置LED所使用的io为输出之类的。create_gpio_led函数主要是初始化led_dat这个gpio_led_data结构体类型变量,led_dat保存了LED的操作函数等内容。- V# n+ B- k8 t" }
第195~196行,使用label属性作为LED的名字,led_dat->cdev.dev->kobj.name指向设备树里的LED灯节点下的label属性。0 R& [) c; _# ?3 ` K5 c
关于gpio_led_probe函数就分析到这里,gpio_led_probe函数主要功能就是获取LED灯的设备信息,然后根据这些信息来初始化对应的IO,设置为输出等。
+ _ ~2 L$ \( m: X; t# ^
! l3 b7 P; b$ k) E+ d4 C O36.3 设备树节点编写6 q" w9 k# u( P4 P7 H6 n& S# W; S
打开文档Documentation/devicetree/bindings/leds/leds-gpio.txt,此文档详细的讲解了Linux自带驱动对应的设备树节点该如何编写,我们在编写设备节点的时候要注意以下几点:
; x) w- N6 U3 s) @+ p①、创建一个节点表示LED灯设备,比如dtsleds,如果板子上有多个LED灯的话每个LED灯都作为dtsleds的子节点。! n* ]+ s3 n) I$ q |
②、dtsleds节点的compatible属性值一定要为“gpio-leds”。
$ y$ D9 {# X( Q③、设置label属性,此属性为可选,每个子节点都有一个label属性,label属性一般表示LED灯的名字,比如以颜色区分的话就是red、green等等。
# K3 E5 Z" D( U; \& R④、每个子节点必须要设置gpios属性值,表示此LED所使用的GPIO引脚!$ r; f* m" b) B) p u1 F! a `
⑤、可以设置“linux,default-trigger”属性值,也就是设置LED灯的默认功能,查阅Documentation/devicetree/bindings/leds/common.txt这个文档来查看可选功能,比如:, g4 z/ T& z2 e, R$ O1 c8 i: ?
backlight:LED灯作为背光。
+ n+ v7 q( p# X1 n8 l) K2 {" e. Tdefault-on:LED灯打开。$ n. |$ S8 a; C9 K
heartbeat:LED灯作为心跳指示灯,可以作为系统运行提示灯。# l5 K: a# T3 w" K% c
disk-activity:LED灯作为磁盘活动指示灯。
4 H& ^$ a A' i% R0 E6 Fide-disk:LED灯作为硬盘活动指示灯。5 S/ ~" |& P* ?- f$ }6 _
timer:LED灯周期性闪烁,由定时器驱动,闪烁频率可以修改。; X$ }5 F. L1 P& q& V/ v# \
⑥、可以设置“default-state”属性值,可以设置为on、off或keep,为on的时候LED灯默认打开,为off的话LED灯默认关闭,为keep的话LED灯保持当前模式。
3 l. p3 @# t- k% g" |另外还有一些其他的可选属性,比如led-sources、color、function等属性,这些属性的用法在Documentation/devicetree/bindings/leds/common.txt里面有详细的讲解,大家自行查阅。& G$ k1 D) V N. n( x+ ]' n
本节实验我们把STM32MP1开发板上的2个LED灯都用上,其中LED0(红色)连接到PI0引脚上,LED1(绿色)连接到PF3。首先是创建这两个LED灯对应的pinctrl节点,直接在示例代码35.1.2.1上修改即可,修改完成以后如下所示:: h, _3 C8 F" k5 T' f& W: t
* h0 N4 ~5 `$ r( W- j5 B
- 示例代码36.3.1 pinctrl子节点
6 I \4 L. c: \ - 1 led_pins_a: gpioled-0 {- K$ @/ Y! U0 K" @
- 2 pins {$ L6 L$ p8 w [- r, E$ e
- 3 pinmux = <STM32_PINMUX('I', 0, GPIO)>, /* LED0 */$ ?5 w3 x0 B: y5 J% T
- 4 <STM32_PINMUX('F', 3, GPIO)>; /* LED1 */+ @9 Y! L4 a9 J! V
- 5 drive-push-pull;
/ n0 h2 i3 q% c# o8 c# m - 6 bias-pull-up;
0 {& F# I& Q/ x9 H$ w7 `+ q - 7 output-high;
7 g1 s) F( m- y9 Q2 \ - 8 slew-rate = <0>;% ]4 F9 K k, D! n4 P: b
- 9 };
6 q) x0 P* b! t9 K& M - 10 };
复制代码 . I" T/ e% p) _4 t+ ]+ D0 j7 h" [6 f
上述代码仅仅只是在示例代码35.1.2.1只添加了第4行,也就是LED1对应的PF3引脚,其他的属性配置没变。
1 U/ H$ `4 `* h8 D" F最后,根据前面的绑定文档要求添加LED设备子节点,打开stm32mp157d-atk.dts,在“/”根节点下添加如下所示LED灯设备子节点:
9 Q. Q$ R6 K* l, J4 o: B
/ k. x" t* @( j- n; C1 e- 示例代码36.3.2 dtsleds设备节点5 k) ?& a% [( ^& C8 }
- 1 dtsleds {- o# \6 _! t! p2 g- N
- 2 compatible = "gpio-leds"; Q/ c3 `. \3 s. M/ ?
- 3 pinctrl-0 = <&led_pins_a>;
2 Q% X& d3 Y+ K7 T2 S' H' b2 J6 L7 O4 D - 4& L# p1 r4 p7 ~
- 5 led0 {
) U' S# s2 K, A9 t h6 F+ c - 6 label = "red";
! p8 V7 \5 @$ v5 F$ q9 w- V - 7 gpios = <&gpioi 0 GPIO_ACTIVE_LOW>;
* H2 |5 y8 q3 v% k9 V2 ` - 8 default-state = "off";
4 \ c! g* u) S% @/ s$ u6 _ - 9 };+ F5 ?% s9 @3 Q* L$ C
- 10
( `% Z6 |* j }9 d - 11 led1 {
+ T& N/ N2 o( C - 12 label = "green";
; m8 s, f) X8 V! Q! @) T2 d6 z - 13 gpios = <&gpiof 3 GPIO_ACTIVE_LOW>;
) V) `5 I9 T7 T" y( Q - 14 default-state = "off";
4 S% j C" C% d' W9 i - 15 };# U/ }! J) @0 {2 f, [
- 16 };
复制代码 + M- ?7 o+ Q; y; X! ~* X
第3行,设置LED的pinctrl节点为led_pins_a,也就是示例代码36.3.1。第4~8行是开发板上的LED0,第10~13行是开发板上的LED1。修改完成以后保存并重新编译设备树,然后用新的设备树启动开发板。
% R f- C# f3 u4 x1 N! ?' d3 O5 X, I2 S
36.4 运行测试
3 t' } k. C1 m& ]- n+ P3 ~用新的uImage和stm32mp157d-atk.dtb启动开发板,启动以后查看/sys/bus/platform/devices/dtsleds这个目录是否存在,如图36.4.1所示:- T! [4 f1 x% O, N
' s7 v3 l+ z8 z# X8 B: p2 j
E0 P# z) c8 K0 o# R R$ P! b7 B7 e+ {- B4 `' S, O
图36.4.1 dtsleds目录
8 w9 Y. M: C9 @& p( _! Y9 P. {! o进入到dtsleds/leds目录中,此目录中的内容如图36.4.2所示:
+ H ?3 z" O7 |9 N" P4 U4 t9 I7 m! S, M% n7 b% u& {
0 |8 l; u& f/ {+ Q W1 ^) g
+ g( V8 F: d% g2 N图36.4.2 leds目录内容% c( S0 K# e% R- j$ S
从图36.4.2可以看出,在leds目录下有两个子目录,分别为:green和red,其中green就是LED1,red就是LED0,这两个子目录的名字就是我们在示例代码36.3.2中第5行和第11行设置的label属性值。- I, w/ I, h3 U; T8 F& Q9 j4 U( h
我们的设置究竟有没有用,最终是要通过测试才能知道的,首先查看一下系统中有没有“sys/class/leds/red/brightness”和“sys/class/leds/green/brightness”这两个文件,这两个文件分别对应LED0和LED1,通过操作这两个文件即可实现LED0和LED1的打开和关闭。
- ~7 x; F% {/ C* @
) m9 o0 |0 d" D4 p' M$ H果有的话就输入如下命令打开LED0(RED)和LED1(GREEN)这两个LED灯:) V3 {2 k, G$ {- s h: n+ D
echo 1 > /sys/class/leds/red/brightness //打开LED0: E0 ]( L8 F) s' x& E
echo 1 > /sys/class/leds/green/brightness //打开LED1- q& r) q0 k4 [. u# @8 C- L! q( D$ ~
关闭这两个LED灯的命令如下:
t4 D/ Y# V' [echo 0 > /sys/class/leds/red/brightness //关闭LED09 u( f; A, o) o% H& `( `8 m
echo 0 > /sys/class/leds/green/brightness //关闭LED1; F2 b0 U, J! c8 ^- r1 N8 S8 W3 D8 t
如果能正常的打开和关闭两个LED灯话就说明我们Linux内核自带的LED灯驱动工作正常。我们一般会将一个LED灯作为系统指示灯,系统运行正常的话这个LED指示灯就会一闪一闪的。里我们设置LED0作为系统指示灯,在dtsleds/led0这个设备节点中加入“linux,default-trigger”属性信息即可,属性值为“heartbeat”,修改完以后的dtsleds节点内容如下:
$ D% M/ {' y8 `+ \! V% r/ w! Q- G9 l
# l3 L8 T) m/ y% k2 o ~2 t- 示例代码36.4.1 dtsleds设备节点& ]# F( `/ c M2 n$ T; r% w
- 1 dtsleds {& b. @! p5 u' W4 h% n2 y
- 2 compatible = "gpio-leds";3 s+ |, i v! h+ I5 ~3 \* u6 ~
- 3 pinctrl-0 = <&led_pins_a>;5 T* y+ ?( j+ x4 k; H1 `
- 45 \# ~* u1 C+ }5 S* F1 P! {
- 5 led0 {
& [% ~0 a8 l$ b$ Y5 ? - 6 label = "red";
/ T. y8 b5 f- k: h! N4 m+ g4 U& G - 7 gpios = <&gpioi 0 GPIO_ACTIVE_LOW>;4 K+ |- ?7 G$ A, X. X3 ]
- 8 linux,default-trigger = "heartbeat";' E0 e) h% z# X) I* j8 B3 ~
- 9 default-state = "on";
9 L _$ Z1 G( d- ~# h0 D, [ - 10 };/ ]/ T: F: i8 X- z) ~
- 11 * @" `2 Y2 x. H# V$ F4 y8 h
- 12 led1 {9 U, G$ L$ J4 m, A7 ~. _+ e1 N
- 13 label = "green";- q. l& \: i) E0 t( Z, z
- 14 gpios = <&gpiof 3 GPIO_ACTIVE_LOW>;
, D4 ~2 N/ C/ R3 j( T9 ~+ L - 15 default-state = "off";& W1 ]* X4 \4 Q I
- 16 };
4 d* @" U- A/ g. P - 17 };
复制代码
; i# m1 @+ K( t4 X第8行,设置LED0为heartbeat。
1 z7 _4 v8 h( }# q$ c/ h0 L6 A第9行,默认打开LED0。7 J. D) `! @( I2 V& r
重新编译设备树并且使用新的设备树启动Linux系统,启动以后LED0就会闪烁,作为系统心跳指示灯,表示系统正在运行。
+ f+ r. t5 z% ^# e: U, g& N————————————————. G/ c) Z# Y2 H6 X3 o- p
版权声明:正点原子# V3 N1 F# z6 m* |, a. f( P/ v5 {
& g& J; d5 H5 A+ z) ?
1 ^0 W0 p& g0 O# {$ { |