Linux自带的LED灯驱动实验$ i3 [! {+ C/ s+ |: k+ D3 e
前面我们都是自己编写LED灯驱动,其实像LED灯这样非常基础的设备驱动,Linux内核已经集成了。Linux内核的LED灯驱动采用platform框架,因此我们只需要按照要求在设备树文件中添加相应的LED节点即可,本章我们就来学习如何使用Linux内核自带的LED驱动来驱动正点原子的STM32MP1开发板上的LED0和LED1这两个LED灯。- z6 \3 `& e I5 ?! P) K
, _% D+ D Q4 m4 [' \36.1 Linux内核自带LED驱动使能: K4 a1 i% m2 E: z
上一章节我们编写基于设备树的platform LED灯驱动,其实Linux内核已经自带了LED灯驱动,要使用Linux内核自带的LED灯驱动首先得先配置Linux内核,使能自带的LED灯驱动,输入如下命令打开Linux配置菜单:
/ W t& E% M, f" b2 S: umake menuconfig
* A2 t* S2 R1 l9 E# @( E# j# v7 ~按照如下路径打开LED驱动配置项:
6 ]5 |) {0 U) M, |2 mDevice Drivers
9 A. N$ x1 e% o, L4 o, g4 eLED Support (NEW_LEDS [=y])* c6 I* M7 r$ ~( { }
LED Support for GPIO connected LEDs2 G6 t8 m3 j, C. {! l" U
按照上述路径,选择“LED Support for GPIO connected LEDs”,将其编译进Linux内核,也即是在此选项上按下“Y”键,使此选项前面变为“<*>”,如图36.1.1所示:
. m$ j4 C+ ]" u1 A S8 A
5 Z4 j# V! e8 E9 q
" `+ x( a" Y& w2 y7 M6 j. v- ]. D3 o
9 f0 L8 Q/ a6 Q, s图36.1.1 使能LED灯驱动7 Q" V- c4 u5 `" w
在“LED Support for GPIO connected LEDs”上按下“?”健可以打开此选项的帮助信息,如图36.1.2所示:' B; Z& N# ^: E+ I4 k
0 r( r$ A- B5 U; ` B9 ]
! L* z4 a9 S2 x+ B, }
/ q2 e$ i' a1 b' Z- o6 x4 j图36.1.2 内部LED灯驱动帮助信息" u8 e- W% {! ?& Q
从图36.1.2可以看出,把Linux内部自带的LED灯驱动编译进内核以后,CONFIG_LEDS_GPIO就会等于‘y’,Linux会根据CONFIG_LEDS_GPIO的值来选择如何编译LED灯驱动,如果为‘y’就将其编译进Linux内核。# g& _7 ?8 z6 h; a* W
配置好Linux内核以后退出配置界面,打开.config文件,会找到“CONFIG_LEDS_GPIO=y”这一行,如图36.1.3所示: |0 U2 ?* v3 z
, a+ \+ B( M, Y8 d: _. W
. d! M! `8 S' V0 p; `! A: Q z! d! T# d p: F) p/ j
图36.1.3 .config文件内容
- I( @* N: @! N- n8 e" ]6 J8 `) d重新编译Linux内核,然后使用新编译出来的zImage镜像启动开发板。
# ~2 Y. @! \4 B8 W+ _, o36.2 Linux内核自带LED驱动简介
; @# @. b0 Q; |7 K7 t36.2.1 LED灯驱动框架分析
. @8 a ~6 q9 G: e/ I, ELED灯驱动文件为/drivers/leds/leds-gpio.c,大家可以打开/drivers/leds/Makefile这个文件,找到如下所示内容:
; Z! U& ~& e9 ?+ d5 b; H" C! r4 s$ j) T' }: B) m. U2 u
- 示例代码36.2.1.1 /drivers/leds/Makefile文件代码段
( `0 E: M- _* k* m - 1 # SPDX-License-Identifier: GPL-2.03 C4 r9 e" t6 I3 |
- 2 & M% y8 `" H% H+ m
- 3 # LED Core
+ \* x; H% f# a; A" [ - 4 obj-$(CONFIG_NEW_LEDS) += led-core.o H# h/ c( B3 X! t) q
- ......& x; v- b: P7 f4 Q. s
- 31 obj-$(CONFIG_LEDS_PCA9532) += leds-pca9532.o
5 S% |) J! h S: q* q - 32 obj-$(CONFIG_LEDS_GPIO_REGISTER) += leds-gpio-register.o
8 R& S" i. C) n0 s/ r, Z5 z3 M/ C - 33 obj-$(CONFIG_LEDS_GPIO) += leds-gpio.o1 ~' j# R2 b6 P8 H
- 34 obj-$(CONFIG_LEDS_LP3944) += leds-lp3944.o
$ M1 k3 b/ m5 u) A ^/ C* j0 a - ......
复制代码 6 e) b3 E8 s0 O0 c
第33行,如果定义了CONFIG_LEDS_GPIO的话就会编译leds-gpio.c这个文件,在上一小节我们选择将LED驱动编译进Linux内核,在.config文件中就会有“CONFIG_LEDS_GPIO=y”这一行,因此leds-gpio.c驱动文件就会被编译。
& _7 f$ Q3 c& P" y# Z' Z1 f T7 G接下来我们看一下leds-gpio.c这个驱动文件,找到如下所示内容:
4 ~+ Y0 Y; b. s) |9 r: n" l# S' x0 b
) C8 L( t7 x4 ~' [' `- 示例代码36.2.1.2 leds-gpio.c文件代码段
& Q% s l6 |1 p- b - 203 static const struct of_device_id of_gpio_leds_match[] = {
7 t3 J8 w9 U7 _ - 204 { .compatible = "gpio-leds", },, x# u/ K- I4 P6 v; j
- 205 {},
$ k( f9 m* m. e4 f# B2 U. G - 206 };
) ?# D. n) y4 ^/ b2 z - 2070 i5 u& u7 O0 f' O. K ]6 ]
- ......
0 e( O2 _0 y) k2 j% h - 316 static struct platform_driver gpio_led_driver = {0 p% c; B7 h+ z* U, p) y3 Y" j3 A
- 317 .probe = gpio_led_probe,) H. {" N9 R$ r$ o
- 318 .shutdown = gpio_led_shutdown,% ^: w! Z! Q& I
- 319 .driver = {
4 u& P t9 |* C6 g. v+ N: u - 320 .name = "leds-gpio",
! ?8 C; m$ a5 {6 I n - 321 .of_match_table = of_gpio_leds_match,6 P3 c/ [1 J1 P- x. H5 w
- 322 },
! f, n: @1 q! D - 323 };
! Y4 T2 p7 o3 U. v; r9 j* O - 324# A5 p( E7 w( {5 i
- 325 module_platform_driver(gpio_led_driver);
复制代码
8 r ^) \) C; }& }% \) H第203~206行,LED驱动的匹配表,此表只有一个匹配项,compatible内容为“gpio-leds”,因此设备树中的LED灯设备节点的compatible属性值也要为“gpio-leds”,否则设备和驱动匹配不成功,驱动就没法工作。1 c* j5 P# B6 E$ T. d9 p
第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所示:$ A6 w8 W u% s( z: x6 v
$ J$ y) Y8 E+ q6 _* d, U
5 v! K8 O* D g# u0 J1 ?
" C% C3 V' [0 Z/ d
图36.2.1.1 leds-gpio驱动文件1 w: w$ I( t9 _6 x* h, f6 G
第326行通过module_platform_driver函数向Linux内核注册gpio_led_driver这个platform驱动。/ N+ N/ s0 C" A' Z) R
36.2.2 module_platform_driver函数简析
' `; q! e. W3 Y; q% O/ ~在上一小节中我们知道LED驱动会采用module_platform_driver函数向Linux内核注册platform驱动,其实在Linux内核中会大量采用module_platform_driver来完成向Linux内核注册platform驱动的操作。module_platform_driver定义在include/linux/platform_device.h文件中,为一个宏,定义如下:! c" O1 f! X4 |6 @( g" Q& ?9 [
- 示例代码36.2.2.1 module_platform_driver函数 ]8 O4 C% {( D5 A. Q& k8 |
- 237 #define module_platform_driver(__platform_driver) \9 \5 |$ r j9 t# D+ D, e6 r8 W
- 238 module_driver(__platform_driver, platform_driver_register, \6 H% |3 \% {6 R9 i1 E+ i/ c& o0 [% [
- 239 platform_driver_unregister)
复制代码 . |) w" _5 [: u) F! u2 z& S8 O
可以看出,module_platform_driver依赖module_driver,module_driver也是一个宏,定义在include/linux/device.h文件中,内容如下:5 G/ @. E2 m$ i) U. [9 N; b+ F
- N& f+ B+ ^0 P+ y ` B
- 示例代码36.2.2.2 module_driver函数
4 B5 F4 B" I, w2 b3 d% A - 1898 #define module_driver(__driver, __register, __unregister, ...) \% B% z- u1 n# x7 ?7 W. D
- 1899 static int __init __driver##_init(void) \; V0 S! {# e4 X: f4 v
- 1900 { \
* `% W( h3 p' f( L1 O8 V& @6 k9 s - 1901 return __register(&(__driver) , ##__VA_ARGS__); \
: V$ C6 \# E/ ^6 g8 {9 G S - 1902 } \
o( Q* g( `' ^% w) I - 1903 module_init(__driver##_init); \
1 `) v& O# N+ H( |+ X - 1904 static void __exit __driver##_exit(void) \
( m. d; |( d% L d - 1905 { \% M, h+ Z: N2 a( v) S9 {
- 1906 __unregister(&(__driver) , ##__VA_ARGS__); \
! t. B, N3 I$ Y6 \ - 1907 } \
4 }5 l6 c6 a/ M - 1908 module_exit(__driver##_exit);
复制代码 6 X5 A2 N/ M2 K& i! W
借助示例代码36.2.2.1和示例代码36.2.2.2,将:! S! ?5 M8 L4 F" \- C! {2 c
4 C7 P: R0 ~* x$ S
module_platform_driver(gpio_led_driver) V0 B v, d9 i2 B3 e) {$ Z4 Z
展开以后就是:
( j' x: h% L: y3 T+ y, }6 \static int __init gpio_led_driver_init(void)
& o1 _' q6 S3 ]: P I- \. I: B{
. J- V3 s; B( _! J( z, H8 F0 Q& _ Yreturn platform_driver_register (&(gpio_led_driver));
" l. {& a6 s2 I& k9 ^$ ~}4 `$ e' A; i* }) h) ~
module_init(gpio_led_driver_init);7 S6 V" H- R0 A. c0 P
. j# ?& @1 W# ^8 Y [3 f3 [, k
static void __exit gpio_led_driver_exit(void)
4 _; ^8 v2 z0 ~. ^! `+ K{
; x# C r1 n3 K ?; w8 Vplatform_driver_unregister (&(gpio_led_driver) );
$ l7 Z# D8 I- r}% M u1 O# t2 n1 f4 y& b4 K
module_exit(gpio_led_driver_exit); D& w2 I6 A$ f: e( b& S2 |
上面的代码不就是标准的注册和删除platform驱动吗?因此module_platform_driver函数的功能就是完成platform驱动的注册和删除。: g) B5 ?" V* u1 h2 P
% M) a: X' I. \. H# X! N
36.2.3 gpio_led_probe函数简析! u8 S/ A9 s/ B3 q+ h* S' r; S1 j# d( R
当驱动和设备匹配以后gpio_led_probe函数就会执行,此函数主要是从设备树中获取LED灯的GPIO信息,缩减后的函数内容如下所示:3 x R+ S* c; i- d! I
8 b/ c; o u2 S: g- 示例代码36.2.3.1 gpio_led_probe函数5 d' a# K- Y" N( \" @- U
- 256 static int gpio_led_probe(struct platform_device *pdev)) d4 z; V: v; C8 l3 z
- 257 {, V7 p' O, B$ _7 G: H1 S: a
- 258 struct gpio_led_platform_data *pdata =
1 `# B' i% S. w* @4 X - dev_get_platdata(&pdev->dev);5 \; d0 G/ f( Z5 L! u- D
- 259 struct gpio_leds_priv *priv;8 s$ T, n1 ]9 y( J7 Z
- 260 int i, ret = 0;! D7 p7 \* |0 a/ x" u
- 261
7 ]- F1 p. s4 M& Z9 D; x8 J. q/ S0 H; T, P - 262 if (pdata && pdata->num_leds) { /* 非设备树方式 */$ d" ^1 l% C8 d+ p+ r8 l) D
- /* 获取platform_device信息 */
4 t9 P+ d# n' \" a3 j% R2 y - .....7 t3 a4 M+ g" P' E* \
- 292 } else { /* 采用设备树 */
2 X6 x$ W8 ?) G/ z - 293 priv = gpio_leds_create(pdev);+ @ {* ]5 K1 T& k: w( I. ~( K) y; k
- 294 if (IS_ERR(priv))
# w9 B, F: c1 a+ o/ _ - 295 return PTR_ERR(priv);
5 e a2 v8 E( \/ n, D - 296 }: j+ i0 A9 b6 Z
- 2979 ]9 x5 n0 }) o
- 298 platform_set_drvdata(pdev, priv);
6 ?2 E% Z# E8 y) Q8 a - 299
% u" T$ }8 c/ Q) Q$ K5 S- Z - 300 return 0;
2 D+ X' s: t( y. k f - 301 }
复制代码 ! \0 E9 t+ A7 s5 }% N
第293~295行,如果使用设备树的话,使用gpio_leds_create函数从设备树中提取设备信息,获取到的LED灯GPIO信息保存在返回值中,gpio_leds_create函数内容如下:' p2 I6 p }% Z! e& q
: U" I. W; x7 ^" n+ \# r) U- 示例代码36.2.3.2 gpio_leds_create函数
4 e$ }: j Q# r2 w! F6 O% } - 134 static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev)) R, \9 q8 L" s' s- H5 v# I. ~
- 135 {7 n1 O! c3 E! U4 o: x( J
- 136 struct device *dev = &pdev->dev;: C6 W& H* p! a
- 137 struct fwnode_handle *child;$ \: ]% M n( v; b0 n2 [
- 138 struct gpio_leds_priv *priv;
5 d+ P0 n" A8 n5 w" z- a - 139 int count, ret;& j2 Y% [( D" x0 Z" v& y" ]7 r
- 140/ x1 o1 n* }: B, ^5 V
- 141 count = device_get_child_node_count(dev); \2 w% e: x+ F# {2 o/ Q
- 142 if (!count)! e0 M ?, y5 z2 {3 k
- 143 return ERR_PTR(-ENODEV);4 y4 \7 F9 A) t( N/ v( X
- 144
) m. w. @( Z% _8 c3 `; A - 145 priv = devm_kzalloc(dev, sizeof_gpio_leds_priv(count), ' x1 z( P( t0 T: F5 C
- GFP_KERNEL);
0 ~9 b* y* C" b, T, q: b" k - 146 if (!priv). B" P8 j% D. d9 A3 W) v
- 147 return ERR_PTR(-ENOMEM);3 {( ^- j; r% Z. V0 g, l# u
- 148 R9 ] ~) M- z5 r; c1 W
- 149 device_for_each_child_node(dev, child) {* L9 q6 W9 g& d) _, a
- 150 struct gpio_led_data *led_dat = &priv->leds[priv->num_leds];. N2 k* C5 v% ~+ b
- 151 struct gpio_led led = {};
3 ^- l" D( d) _3 I8 i7 N) g7 f - 152 const char *state = NULL;
$ F# y0 X# ^' c% X# q - 153
1 N. d5 O+ u. M0 T" i - 154 /*
8 a# C m7 x" J4 f - 155 * Acquire gpiod from DT with uninitialized label, which5 }9 {/ {; \9 j8 u5 _( l
- 156 * will be updated after LED class device is registered,
$ L6 F/ j" c2 @3 \. w - 157 * Only then the final LED name is known.
. a8 _( k6 B+ i$ F8 C8 Q6 A - 158 */: V- o1 j+ \9 F4 q! k
- 159 led.gpiod = devm_fwnode_get_gpiod_from_child(dev, NULL, + g r% I, {, A
- child,: ], B, a4 d( Z) M6 L$ d
- 160 GPIOD_ASIS,
- [& ~# Z1 m. [6 ` - 161 NULL);
3 ]! R. {1 h0 K( a% [7 P4 U - 162 if (IS_ERR(led.gpiod)) {
D* c4 d2 f3 r. V - 163 fwnode_handle_put(child);, r3 o' T, _4 \ ]& `+ M" i
- 164 return ERR_CAST(led.gpiod);
8 W& g; C: ]2 e4 J - 165 }% n+ u C$ ~/ w6 z
- 166
6 i6 e% s. V7 p' W+ t - 167 led_dat->gpiod = led.gpiod;
0 a4 L: N' i( v& A - 168: N! E% ^9 @/ s
- 169 fwnode_property_read_string(child, "linux,default-trigger",: K& `4 `. a- H" e+ Q
- 170 &led.default_trigger);
) E9 t" s: p: r" B- V/ E) H& ? - 171
2 I! |5 _' S' t% V5 {* u - 172 if (!fwnode_property_read_string(child, "default-state",
2 \8 n7 u0 g. j: z" [1 k - 173 &state)) {: }8 {, S, d+ S$ R( e
- 174 if (!strcmp(state, "keep"))8 v* Q# Q' @! w5 q, ?
- 175 led.default_state = LEDS_GPIO_DEFSTATE_KEEP;
; I/ u- L+ S# I" d - 176 else if (!strcmp(state, "on"))$ J- K$ Y+ k7 e, Q# T" I: e! T: l
- 177 led.default_state = LEDS_GPIO_DEFSTATE_ON;
9 H+ n7 z* D* ]$ n8 {2 t: G$ I - 178 else3 O# u% G4 s* |# z" _& d2 n
- 179 led.default_state = LEDS_GPIO_DEFSTATE_OFF;4 I$ U. s* x; ~; J4 d4 d4 ^! o
- 180 }: D( v- q' q3 f0 F6 R* E# R
- 181
4 }2 w( t8 m/ E, P6 C3 U4 O - 182 if (fwnode_property_present(child, "retain-state-suspended"))* v% `% h* c# A1 K( h+ P
- 183 led.retain_state_suspended = 1;
+ S4 ]$ A" n* \ C$ Z - 184 if (fwnode_property_present(child, "retain-state-shutdown"))$ V" F) O2 \6 p0 N
- 185 led.retain_state_shutdown = 1;2 r1 { N6 a0 n! q* p% K# [6 Z
- 186 if (fwnode_property_present(child, "panic-indicator"))
@3 u/ n1 @1 N4 d* w - 187 led.panic_indicator = 1;
+ N6 u+ R: T( ^0 _( V6 I - 188
7 Y; O8 q1 B- c$ o - 189 ret = create_gpio_led(&led, led_dat, dev, child, NULL);3 G" o. J( I- P F0 T
- 190 if (ret < 0) {
8 p( r; n" o* ]0 I) y - 191 fwnode_handle_put(child);
) B+ J+ t/ g7 {# {0 z6 W: I - 192 return ERR_PTR(ret);, Y! |" A) j8 Q5 v. |* U
- 193 }
: k3 Z2 D" ^$ K5 m5 P1 D - 194 /* Set gpiod label to match the corresponding LED name. */
/ q: ^2 t" N' o( I8 k) p Q - 195 gpiod_set_consumer_name(led_dat->gpiod,
2 k# @5 }! _4 M$ f6 D, R! I( Q - 196 led_dat->cdev.dev->kobj.name);
! M, v/ M& z' I3 S3 T" f8 E1 B - 197 priv->num_leds++;
2 H* F. O9 \4 Y/ \! \$ v$ \ - 198 }
- s' p! n: A, e2 W6 b - 199- p7 a! \; E3 r* C) s4 F2 f
- 200 return priv;
' m$ V9 T- ]5 w2 l! K9 f8 ~# f - 201 }
复制代码
! |' p/ y( L+ `2 r第141行,调用device_get_child_node_count函数统计子节点数量,一般在设备树中创建一个节点表示LED灯,然后在这个节点下面为每个LED灯创建一个子节点。因此子节点数量也是LED灯的数量。
' d3 O/ p# K( q6 J9 s, y第149行,遍历每个子节点,获取每个子节点的信息。
7 \5 q6 u6 r' C8 H第159行,获取LED灯所使用的GPIO信息。
& F9 A. N- t; M6 E$ z1 c第169~170行,获取“linux,default-trigger”属性值,可以通过此属性设置某个LED灯在Linux系统中的默认功能,比如作为系统心跳指示灯等等。
8 R2 _$ M/ Q& k: `) P! t; w* ^& F- ~% d第172~173行,获取“default-state”属性值,也就是LED灯的默认状态属性。( q5 h4 }7 X& C* w8 W R3 E, [. s
第189行,调用create_gpio_led函数创建LED相关的io,其实就是设置LED所使用的io为输出之类的。create_gpio_led函数主要是初始化led_dat这个gpio_led_data结构体类型变量,led_dat保存了LED的操作函数等内容。* Z: P1 O: H: s
第195~196行,使用label属性作为LED的名字,led_dat->cdev.dev->kobj.name指向设备树里的LED灯节点下的label属性。
Q! z" E5 L2 m1 {/ j! ?$ C关于gpio_led_probe函数就分析到这里,gpio_led_probe函数主要功能就是获取LED灯的设备信息,然后根据这些信息来初始化对应的IO,设置为输出等。
D$ M8 b$ h) i" M# B3 m' c4 E% r8 P) M9 A& x+ B9 u
36.3 设备树节点编写" R- h5 L9 [ s% A& @! l
打开文档Documentation/devicetree/bindings/leds/leds-gpio.txt,此文档详细的讲解了Linux自带驱动对应的设备树节点该如何编写,我们在编写设备节点的时候要注意以下几点:4 I' l+ u2 d, H9 J3 U
①、创建一个节点表示LED灯设备,比如dtsleds,如果板子上有多个LED灯的话每个LED灯都作为dtsleds的子节点。
- l4 `- o+ q( S4 x; u+ B②、dtsleds节点的compatible属性值一定要为“gpio-leds”。* O0 Y1 W. t @$ s( L
③、设置label属性,此属性为可选,每个子节点都有一个label属性,label属性一般表示LED灯的名字,比如以颜色区分的话就是red、green等等。# a4 S7 |9 y+ r% k+ W* i i2 E
④、每个子节点必须要设置gpios属性值,表示此LED所使用的GPIO引脚!
; @2 p% k& _/ h' C⑤、可以设置“linux,default-trigger”属性值,也就是设置LED灯的默认功能,查阅Documentation/devicetree/bindings/leds/common.txt这个文档来查看可选功能,比如:
- p7 }, |: p# k+ W4 B, ^backlight:LED灯作为背光。
4 f6 I& _1 ?1 Wdefault-on:LED灯打开。
0 I* G% S# x# V& ] _; ~heartbeat:LED灯作为心跳指示灯,可以作为系统运行提示灯。
, ]9 ^4 T. P- N: ldisk-activity:LED灯作为磁盘活动指示灯。
9 K# B. q! I$ z" R' iide-disk:LED灯作为硬盘活动指示灯。: _+ o# ~/ I% {4 }
timer:LED灯周期性闪烁,由定时器驱动,闪烁频率可以修改。
; Y+ }3 T& ^1 Y⑥、可以设置“default-state”属性值,可以设置为on、off或keep,为on的时候LED灯默认打开,为off的话LED灯默认关闭,为keep的话LED灯保持当前模式。
* S- H; d0 u* `6 r' d9 s另外还有一些其他的可选属性,比如led-sources、color、function等属性,这些属性的用法在Documentation/devicetree/bindings/leds/common.txt里面有详细的讲解,大家自行查阅。
7 _2 u+ S- w1 T% ^2 O本节实验我们把STM32MP1开发板上的2个LED灯都用上,其中LED0(红色)连接到PI0引脚上,LED1(绿色)连接到PF3。首先是创建这两个LED灯对应的pinctrl节点,直接在示例代码35.1.2.1上修改即可,修改完成以后如下所示:' S! ]6 X, @- o- e. ^+ k, G$ l6 J
! U- b2 E7 f* o0 A% r% B- 示例代码36.3.1 pinctrl子节点7 T% a# z6 ^$ q& i7 D0 h
- 1 led_pins_a: gpioled-0 {
2 A+ X0 C; B9 k2 _5 H$ K! T; i - 2 pins {
+ A( A/ |" |; s2 W' W5 B1 m5 c% g3 G - 3 pinmux = <STM32_PINMUX('I', 0, GPIO)>, /* LED0 */
2 ~/ ]( v% b6 v7 a6 W8 ?6 Q+ h - 4 <STM32_PINMUX('F', 3, GPIO)>; /* LED1 */
8 e. a% m; _" m5 S! w+ m: k - 5 drive-push-pull;
* p9 N' j* _" R - 6 bias-pull-up;
) `8 A% m( [( @2 p. w - 7 output-high;6 v/ Y5 z4 [: M1 o/ h! d- A
- 8 slew-rate = <0>;
" p7 z8 w7 k* I" i, T - 9 };
, t& }6 u# W+ e4 x) Q% W - 10 };
复制代码
7 {/ p9 N( V4 B上述代码仅仅只是在示例代码35.1.2.1只添加了第4行,也就是LED1对应的PF3引脚,其他的属性配置没变。/ o3 q7 K% p3 E. c- c" a
最后,根据前面的绑定文档要求添加LED设备子节点,打开stm32mp157d-atk.dts,在“/”根节点下添加如下所示LED灯设备子节点:
# {2 Q( Q8 h4 S& x1 \
& k3 P- ^6 W5 P: y6 X, _7 _- 示例代码36.3.2 dtsleds设备节点+ j" N6 Z/ r5 Y; j
- 1 dtsleds {
6 c0 ?1 _% y9 O3 [1 g. t$ T - 2 compatible = "gpio-leds";
2 A( i# w& L) A+ x - 3 pinctrl-0 = <&led_pins_a>;8 B8 M: O( \9 r8 W1 G' @
- 4
! L, \, |# S1 o6 h4 t. h& c - 5 led0 {5 ?( `$ V2 t! |: b
- 6 label = "red";
+ D* \& P0 B7 J r4 o4 ^: o - 7 gpios = <&gpioi 0 GPIO_ACTIVE_LOW>;
" S: A# j1 Z, u3 s9 H) r6 x - 8 default-state = "off";# F. l- L0 O" |, d* |
- 9 };
: @# Z7 t. t4 E8 [4 a( w - 10 % g2 m# t; X8 S% B# s6 A7 T
- 11 led1 {$ e" R+ A# q: m( v; F5 n
- 12 label = "green";
8 F" ]( k! u& ^2 P' l I, Q - 13 gpios = <&gpiof 3 GPIO_ACTIVE_LOW>;' K J: s0 f/ l0 H7 m/ h
- 14 default-state = "off";
, a1 c% E! f9 d! [4 l9 B - 15 };5 H: @2 r8 e* { w. k J2 ?
- 16 };
复制代码 " ?# w8 G/ U7 v& n6 d
第3行,设置LED的pinctrl节点为led_pins_a,也就是示例代码36.3.1。第4~8行是开发板上的LED0,第10~13行是开发板上的LED1。修改完成以后保存并重新编译设备树,然后用新的设备树启动开发板。
8 `% Q& f& \3 x; x
- [9 n; i: `% S; W4 ^- @* E36.4 运行测试 K7 W% d8 k4 u2 f4 P5 b' p/ U
用新的uImage和stm32mp157d-atk.dtb启动开发板,启动以后查看/sys/bus/platform/devices/dtsleds这个目录是否存在,如图36.4.1所示:% D4 k' S* H' {. l
2 o- O& W: @2 i. G
4 M8 S t! g" w2 G o+ [
+ ]# ?1 f7 Y& E( n- }图36.4.1 dtsleds目录
, {8 B T. b0 r4 ~" I进入到dtsleds/leds目录中,此目录中的内容如图36.4.2所示:+ i! X& S3 m7 o: @, L. z
+ E* `" z( C* l: ]
7 h- t0 z. U) T
; p6 w* _0 S# N; n1 J1 R图36.4.2 leds目录内容
) X; J% c# l' u. {9 V2 K从图36.4.2可以看出,在leds目录下有两个子目录,分别为:green和red,其中green就是LED1,red就是LED0,这两个子目录的名字就是我们在示例代码36.3.2中第5行和第11行设置的label属性值。2 q7 e) V- I: I( w6 W2 D/ d9 u
我们的设置究竟有没有用,最终是要通过测试才能知道的,首先查看一下系统中有没有“sys/class/leds/red/brightness”和“sys/class/leds/green/brightness”这两个文件,这两个文件分别对应LED0和LED1,通过操作这两个文件即可实现LED0和LED1的打开和关闭。
9 F2 A; P* t1 l& r6 j* @% G3 ~& f7 Q% K% e( K* n* K9 ~- t" q
果有的话就输入如下命令打开LED0(RED)和LED1(GREEN)这两个LED灯:2 |( }6 a( \ c; \* Q& q
echo 1 > /sys/class/leds/red/brightness //打开LED0( P$ K8 I' a) E3 W9 ?
echo 1 > /sys/class/leds/green/brightness //打开LED1
* J7 Y. w$ h. }8 N4 }4 c关闭这两个LED灯的命令如下:
6 `- U5 K! p7 Wecho 0 > /sys/class/leds/red/brightness //关闭LED0' L7 X. e+ m5 `% R, n9 N
echo 0 > /sys/class/leds/green/brightness //关闭LED1
, ]- ~$ x! A8 h$ k% \: y/ D如果能正常的打开和关闭两个LED灯话就说明我们Linux内核自带的LED灯驱动工作正常。我们一般会将一个LED灯作为系统指示灯,系统运行正常的话这个LED指示灯就会一闪一闪的。里我们设置LED0作为系统指示灯,在dtsleds/led0这个设备节点中加入“linux,default-trigger”属性信息即可,属性值为“heartbeat”,修改完以后的dtsleds节点内容如下:
" Z9 d; Z* F$ ^1 M; O4 ~" G& @9 ]6 T- H$ G3 a* P) m
- 示例代码36.4.1 dtsleds设备节点
* [: q) i2 |% k0 f - 1 dtsleds {
7 J9 J. O, {- a2 V/ \4 x: { - 2 compatible = "gpio-leds";
6 ]2 J. f& T# z# e - 3 pinctrl-0 = <&led_pins_a>;
) `4 t* P. }. h* ^+ J - 48 [" @. l: S p$ w/ ^
- 5 led0 {' r8 l8 i& `1 P3 E
- 6 label = "red";
7 _- }4 t; m7 C# V' { - 7 gpios = <&gpioi 0 GPIO_ACTIVE_LOW>;0 m$ w+ Z ]9 \. w/ }
- 8 linux,default-trigger = "heartbeat";1 \) L% u' X# V4 V2 h
- 9 default-state = "on";
$ f T7 N% A9 b* I - 10 };1 M3 c' f2 }! [$ D7 O/ i" J+ o+ a. B
- 11 1 b: a6 R9 a7 v- |7 k
- 12 led1 {
g' J. k4 y8 P; G9 p4 a& M* I+ U - 13 label = "green";( r/ V# \+ g3 x/ \( O8 w, f
- 14 gpios = <&gpiof 3 GPIO_ACTIVE_LOW>;
2 V4 A' j$ M" ^- j% Z% ]+ j - 15 default-state = "off"; a8 }" `# \% F- O8 _
- 16 };" ^& l8 E# y+ y" r4 s! A. G% [
- 17 };
复制代码
. |' K" @8 x, g! M- W9 n" @第8行,设置LED0为heartbeat。
g9 x, r2 ?9 `4 T7 x. m; c第9行,默认打开LED0。
" r6 G1 e" F# [2 n. [) \! m, o6 j重新编译设备树并且使用新的设备树启动Linux系统,启动以后LED0就会闪烁,作为系统心跳指示灯,表示系统正在运行。
% _8 z" K& ?# ^0 g5 T2 t: _0 Y; [————————————————7 @! {/ u8 i4 ]/ a2 z. {
版权声明:正点原子 t, o4 p! ]) F2 H& L. A
8 l+ _( n4 B6 v6 s
! y6 q9 d) k2 [. W! s M" p |