1)实验平台:正点原子ATK-DLRK3568开发板
2)平台购买地址:https://detail.tmall.com/item.htm?id=731866264428
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/docs/boards/xiaoxitongban
第十章 pinctrl和gpio子系统实验
上一章我们编写了基于设备树的LED驱动,但是驱动的本质还是没变,都是配置LED灯所使用的GPIO寄存器,驱动开发方式和裸机基本没啥区别。Linux是一个庞大而完善的系统,尤其是驱动框架,像GPIO这种最基本的驱动不可能采用“原始”的裸机驱动开发方式,否则就相当于你买了一辆车,结果每天推着车去上班。Linux内核提供了pinctrl和gpio子系统用于GPIO驱动,本章我们就来学习一下如何借助pinctrl和gpio子系统来简化GPIO驱动开发。
10.1 pinctrl子系统
10.1.1 pinctrl子系统简介
Linux驱动讲究驱动分离与分层,pinctrl和gpio子系统就是驱动分离与分层思想下的产物,驱动分离与分层其实就是按照面向对象编程的设计思想而设计的设备驱动框架,关于驱动的分离与分层我们后面会讲。本来pinctrl和gpio子系统应该放到驱动分离与分层章节后面讲解,但是不管什么外设驱动,GPIO驱动基本都是必须的,而pinctrl和gpio子系统又是GPIO驱动必须使用的,所以就将pintrcl和gpio子系统这一章节提前了。
我们先来回顾一下上一章是怎么初始化LED灯所使用的GPIO,步骤如下:
①、修改设备树,添加相应的节点,节点里面重点是设置reg属性,reg属性包括了GPIO相关寄存器。
②、 获取reg属性中PMU_GRF_GPIO0C_IOMUX_L、PMU_GRF_GPIO0C_DS_0、GPIO0_SWPORT_DR_H和GPIO0_SWPORT_DDR_H这些寄存器的地址,并且初始化它们,这些寄存器用于设置GPIO0_C0这个PIN的复用功能、上下拉、驱动能力等。
③、 在②里面将GPIO0_C0这个PIN设置为通用输出功能(GPIO),也就是设置复用功能。
④、在②里面将GPIO0_C0这个PIN设置为输出模式。
总结一下,②中完成对GPIO0_C0这个PIN相关的寄存器地址获取,③和④设置这个PIN的复用功能、上下拉等,比如将GPIO0_C0这个PIN设置为通用输出功能。如果使用过STM32单片机的话应该都记得,STM32单片机也是要先设置某个PIN的复用功能、速度、上下拉等,然后再设置PIN所对应的GPIO。RK3568和STM32单片机是类似的,其实对于大多数的32位SOC而言,引脚的设置基本都是这两方面:
①、设置PIN的复用功能。
②、如果PIN复用为GPIO功能,设置GPIO相关属性。
因此Linux内核针对PIN推出了pinctrl子系统,对于GPIO的电气属性配置推出了gpio子系统。本小节我们来学习pinctrl子系统,下一小节再学习gpio子系统。
大多数SOC的pin都是支持复用的,比如RK3568的GPIO3_D4既可以作为普通的GPIO使用,也可以作为PWM1_M0引脚、GPU_AVS引脚、UART0_RX引脚。此外我们还需要配置pin的电气特性,比如上/下拉、驱动能力等等。传统的配置pin的方式就是直接操作相应的寄存器,但是这种配置方式比较繁琐、而且容易出问题(比如pin功能冲突)。pinctrl子系统就是为了解决这个问题而引入的,pinctrl子系统主要工作内容如下:
①、获取设备树中pin信息。
②、根据获取到的pin信息来设置pin的复用功能
③、根据获取到的pin信息来设置pin的电气特性,如驱动能力。
对于我们使用者来讲,只需要在设备树里面设置好某个pin的相关属性即可,其他的初始化工作均由pinctrl子系统来完成,pinctrl子系统源码目录为drivers/pinctrl。
10.1.2 rk3568的pinctrl子系统驱动
1、PIN配置信息详解
要使用pinctrl子系统,我们需要在设备树里面设置PIN的配置信息,毕竟pinctrl子系统要根据你提供的信息来配置PIN功能,一般会在设备树里面创建一个节点来描述PIN的配置信息。打开rk3568.dtsi文件,找到一个叫做pinctrl的节点,如下所示:
示例代码10.1.2.1 pinctrl节点内容1
3532 pinctrl: pinctrl {
3533 compatible = "rockchip,rk3568-pinctrl";
3534 rockchip,grf = <&grf>;
3535 rockchip,pmu = <&pmugrf>;
3536 #address-cells = <2>;
3537 #size-cells = <2>;
3538 ranges;
3539
3540 gpio0: gpio@fdd60000 {
3541 compatible = "rockchip,gpio-bank";
3542 reg = <0x0 0xfdd60000 0x0 0x100>;
3543 interrupts = <GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
3544 clocks = <&pmucru PCLK_GPIO0>, <&pmucru DBCLK_GPIO0>;
3545
3546 gpio-controller;
3547 #gpio-cells = <2>;
3548 gpio-ranges = <&pinctrl 0 0 32>;
3549 interrupt-controller;
3550 #interrupt-cells = <2>;
3551 };
......
3604 };
第3536~3537行,#address-cells属性值为2和#size-cells属性值为2,也就是说pinctrl下的所有子节点的reg第一位加第二位是起始地址,第三位加第四位为长度。
第3540~3604行,rk3568有五组GPIO:GPIO0~GPIO4,每组GPIO对应的寄存器地址不同。第3540~3551行是GPIO0这个GPIO组对应的子节点,其中第2619行的reg属性描述了GPIO0对应的寄存器地址,基地址就是0XFDD60000,驱动会得到GPIO0的基地址0XFDD60000,然后加上偏移就得到了GPIO0的其他寄存器地址。
示例代码10.1.2.1看起来,根本没有PIN相关的具体配置,别急!先打开rk3568-pinctrl.dtsi文件,此文件需要编译内核以后才能得到,我们能找到如下内容:
示例代码10.1.2.2 pinctrl节点内容2
13 &pinctrl {
14 acodec {
15 /omit-if-no-ref/
16 acodec_pins: acodec-pins {
17 rockchip,pins =
18 /* acodec_adc_sync */
19 <1 RK_PB1 5 &pcfg_pull_none>,
20 /* acodec_adcclk */
21 <1 RK_PA1 5 &pcfg_pull_none>,
22 /* acodec_adcdata */
23 <1 RK_PA0 5 &pcfg_pull_none>,
24 /* acodec_dac_datal */
25 <1 RK_PA7 5 &pcfg_pull_none>,
26 /* acodec_dac_datar */
27 <1 RK_PB0 5 &pcfg_pull_none>,
28 /* acodec_dacclk */
29 <1 RK_PA3 5 &pcfg_pull_none>,
30 /* acodec_dacsync */
31 <1 RK_PA5 5 &pcfg_pull_none>;
32 };
33 };
34
35 audiopwm {
36 /omit-if-no-ref/
37 audiopwm_lout: audiopwm-lout {
38 rockchip,pins =
39 /* audiopwm_lout */
40 <1 RK_PA0 4 &pcfg_pull_none>;
41 };
42
43 /omit-if-no-ref/
44 audiopwm_loutn: audiopwm-loutn {
45 rockchip,pins =
46 /* audiopwm_loutn */
47 <1 RK_PA1 6 &pcfg_pull_none>;
48 };
49
50 /omit-if-no-ref/
51 audiopwm_loutp: audiopwm-loutp {
52 rockchip,pins =
53 /* audiopwm_loutp */
54 <1 RK_PA0 6 &pcfg_pull_none>;
55 };
56
57 /omit-if-no-ref/
58 audiopwm_rout: audiopwm-rout {
59 rockchip,pins =
60 /* audiopwm_rout */
61 <1 RK_PA1 4 &pcfg_pull_none>;
62 };
63
64 /omit-if-no-ref/
65 audiopwm_routn: audiopwm-routn {
66 rockchip,pins =
67 /* audiopwm_routn */
68 <1 RK_PA7 4 &pcfg_pull_none>;
69 };
70
71 /omit-if-no-ref/
72 audiopwm_routp: audiopwm-routp {
73 rockchip,pins =
74 /* audiopwm_routp */
75 <1 RK_PA6 4 &pcfg_pull_none>;
76 };
77 };
78
79 bt656 {
80 /omit-if-no-ref/
81 bt656m0_pins: bt656m0-pins {
82 rockchip,pins =
83 /* bt656_clkm0 */
84 <3 RK_PA0 2 &pcfg_pull_none>,
85 /* bt656_d0m0 */
86 <2 RK_PD0 2 &pcfg_pull_none>,
87 /* bt656_d1m0 */
88 <2 RK_PD1 2 &pcfg_pull_none>,
89 /* bt656_d2m0 */
90 <2 RK_PD2 2 &pcfg_pull_none>,
91 /* bt656_d3m0 */
92 <2 RK_PD3 2 &pcfg_pull_none>,
93 /* bt656_d4m0 */
94 <2 RK_PD4 2 &pcfg_pull_none>,
95 /* bt656_d5m0 */
96 <2 RK_PD5 2 &pcfg_pull_none>,
97 /* bt656_d6m0 */
98 <2 RK_PD6 2 &pcfg_pull_none>,
99 /* bt656_d7m0 */
100 <2 RK_PD7 2 &pcfg_pull_none>;
101 };
102
103 /omit-if-no-ref/
104 bt656m1_pins: bt656m1-pins {
105 rockchip,pins =
106 /* bt656_clkm1 */
107 <4 RK_PB4 5 &pcfg_pull_none>,
108 /* bt656_d0m1 */
109 <3 RK_PC6 5 &pcfg_pull_none>,
110 /* bt656_d1m1 */
111 <3 RK_PC7 5 &pcfg_pull_none>,
112 /* bt656_d2m1 */
113 <3 RK_PD0 5 &pcfg_pull_none>,
114 /* bt656_d3m1 */
115 <3 RK_PD1 5 &pcfg_pull_none>,
116 /* bt656_d4m1 */
117 <3 RK_PD2 5 &pcfg_pull_none>,
118 /* bt656_d5m1 */
119 <3 RK_PD3 5 &pcfg_pull_none>,
120 /* bt656_d6m1 */
121 <3 RK_PD4 5 &pcfg_pull_none>,
122 /* bt656_d7m1 */
123 <3 RK_PD5 5 &pcfg_pull_none>;
124 };
125 };
126
127 bt1120 {
128 /omit-if-no-ref/
129 bt1120_pins: bt1120-pins {
130 rockchip,pins =
131 /* bt1120_clk */
132 <3 RK_PA6 2 &pcfg_pull_none>,
133 /* bt1120_d0 */
134 <3 RK_PA1 2 &pcfg_pull_none>,
135 /* bt1120_d1 */
136 <3 RK_PA2 2 &pcfg_pull_none>,
137 /* bt1120_d2 */
138 <3 RK_PA3 2 &pcfg_pull_none>,
139 /* bt1120_d3 */
140 <3 RK_PA4 2 &pcfg_pull_none>,
141 /* bt1120_d4 */
142 <3 RK_PA5 2 &pcfg_pull_none>,
143 /* bt1120_d5 */
144 <3 RK_PA7 2 &pcfg_pull_none>,
145 /* bt1120_d6 */
146 <3 RK_PB0 2 &pcfg_pull_none>,
147 /* bt1120_d7 */
148 <3 RK_PB1 2 &pcfg_pull_none>,
149 /* bt1120_d8 */
150 <3 RK_PB2 2 &pcfg_pull_none>,
151 /* bt1120_d9 */
152 <3 RK_PB3 2 &pcfg_pull_none>,
153 /* bt1120_d10 */
154 <3 RK_PB4 2 &pcfg_pull_none>,
155 /* bt1120_d11 */
156 <3 RK_PB5 2 &pcfg_pull_none>,
157 /* bt1120_d12 */
158 <3 RK_PB6 2 &pcfg_pull_none>,
159 /* bt1120_d13 */
160 <3 RK_PC1 2 &pcfg_pull_none>,
161 /* bt1120_d14 */
162 <3 RK_PC2 2 &pcfg_pull_none>,
163 /* bt1120_d15 */
164 <3 RK_PC3 2 &pcfg_pull_none>;
165 };
166 };
167
168 cam {
169 /omit-if-no-ref/
170 cam_clkout0: cam-clkout0 {
171 rockchip,pins =
172 /* cam_clkout0 */
173 <4 RK_PA7 1 &pcfg_pull_none>;
174 };
175
176 /omit-if-no-ref/
177 cam_clkout1: cam-clkout1 {
178 rockchip,pins =
179 /* cam_clkout1 */
180 <4 RK_PB0 1 &pcfg_pull_none>;
181 };
182 };
183
184 can0 {
185 /omit-if-no-ref/
186 can0m0_pins: can0m0-pins {
187 rockchip,pins =
188 /* can0_rxm0 */
189 <0 RK_PB4 2 &pcfg_pull_none>,
190 /* can0_txm0 */
191 <0 RK_PB3 2 &pcfg_pull_none>;
192 };
193
194 /omit-if-no-ref/
195 can0m1_pins: can0m1-pins {
196 rockchip,pins =
197 /* can0_rxm1 */
198 <2 RK_PA2 4 &pcfg_pull_none>,
199 /* can0_txm1 */
200 <2 RK_PA1 4 &pcfg_pull_none>;
201 };
202 };
203
204 can1 {
205 /omit-if-no-ref/
206 can1m0_pins: can1m0-pins {
207 rockchip,pins =
208 /* can1_rxm0 */
209 <1 RK_PA0 3 &pcfg_pull_none>,
210 /* can1_txm0 */
211 <1 RK_PA1 3 &pcfg_pull_none>;
212 };
213
214 /omit-if-no-ref/
215 can1m1_pins: can1m1-pins {
216 rockchip,pins =
217 /* can1_rxm1 */
218 <4 RK_PC2 3 &pcfg_pull_none>,
219 /* can1_txm1 */
220 <4 RK_PC3 3 &pcfg_pull_none>;
221 };
222 };
223
224 can2 {
225 /omit-if-no-ref/
226 can2m0_pins: can2m0-pins {
227 rockchip,pins =
228 /* can2_rxm0 */
229 <4 RK_PB4 3 &pcfg_pull_none>,
230 /* can2_txm0 */
231 <4 RK_PB5 3 &pcfg_pull_none>;
232 };
233
234 /omit-if-no-ref/
235 can2m1_pins: can2m1-pins {
236 rockchip,pins =
237 /* can2_rxm1 */
238 <2 RK_PB1 4 &pcfg_pull_none>,
239 /* can2_txm1 */
240 <2 RK_PB2 4 &pcfg_pull_none>;
241 };
242 };
243
244 cif {
245 /omit-if-no-ref/
246 cif_clk: cif-clk {
247 rockchip,pins =
248 /* cif_clkout */
249 <4 RK_PC0 1 &pcfg_pull_none>;
250 };
251
252 /omit-if-no-ref/
253 cif_dvp_clk: cif-dvp-clk {
254 rockchip,pins =
255 /* cif_clkin */
256 <4 RK_PC1 1 &pcfg_pull_none>,
257 /* cif_href */
258 <4 RK_PB6 1 &pcfg_pull_none>,
259 /* cif_vsync */
260 <4 RK_PB7 1 &pcfg_pull_none>;
261 };
262
......
304 };
示例代码10.1.2.2就是向pinctrl节点追加数据,不同的外设使用的PIN不同、其配置也不同,因此一个萝卜一个坑,将某个外设所使用的所有PIN都组织在一个子节点里面。示例代码10.1.2.2中第184~242行的can子节点就是rk3568的三个CAN接口所使用的引脚配置,canm0_pins、canm1_pins和canm2_pins分别对应CAN0、CAN1和CAN2这三个接口。同理第244行的子节点cif_clk就是CIF0接口对应时钟的引脚。绑定文档Documentation/devicetree/bindings/pinctrl/rockchip,pinctrl.txt描述了如何在设备树中设置rk3568的PIN信息。
每个pincrtl节点必须至少包含一个子节点来存放pincrtl相关信息,也就是pinctrl集,这个集合里面存放当前外设用到哪些引脚(PIN)、复用配置、上下拉、驱动能力等。一般这个存放pincrtl集的子节点名字是“rockchip,pins”。
上面讲了,在“rockchip,pins”子节点里面存放外设的引脚描述信息,根据rockchip,pinctrl.txt文档里面的介绍,引脚复用设置的格式如下:
rockchip,pins = <PIN_BANK PIN_BANK_IDX MUX &phandle>
一共分为四部分:
1、PIN_BANK
PIN_BANK就是PIN所属的组,RK3568一共有5组PIN:GPIO0GPIO4,分别对应04,所以如果你要设置GPIO0_C0这个PIN,那么PIN_BANK就是0。
2、PIN_BANK_IDX
PIN_BANK_IDX是组内的编号,以GPIO0组为例,一共有A0A7、B0B7、C0C7、D0D7,这32个PIN。瑞芯微已经给这些PIN编了号,打开include/dt-bindings/pinctrl/rockchip.h文件,有如下定义:
示例代码10.1.2.3 引脚编号
28 #define RK_PA0 0
29 #define RK_PA1 1
30 #define RK_PA2 2
31 #define RK_PA3 3
32 #define RK_PA4 4
33 #define RK_PA5 5
......
54 #define RK_PD2 26
55 #define RK_PD3 27
56 #define RK_PD4 28
57 #define RK_PD5 29
58 #define RK_PD6 30
59 #define RK_PD7 31
如果要设置GPIO0_C0,那么PIN_BANK_IDX就要设置为RK_PC0。
3、MUX
MUX就是设置PIN的复用功能,一个PIN最多有16个复用功能,include/dt-bindings/pinctrl/rockchip.h文件中有如下内容:
示例代码10.1.2.4 复用编号
61 #define RK_FUNC_GPIO 0
62 #define RK_FUNC_0 0
63 #define RK_FUNC_1 1
64 #define RK_FUNC_2 2
65 #define RK_FUNC_3 3
66 #define RK_FUNC_4 4
67 #define RK_FUNC_5 5
68 #define RK_FUNC_6 6
69 #define RK_FUNC_7 7
70 #define RK_FUNC_8 8
71 #define RK_FUNC_9 9
72 #define RK_FUNC_10 10
73 #define RK_FUNC_11 11
74 #define RK_FUNC_12 12
75 #define RK_FUNC_13 13
76 #define RK_FUNC_14 14
77 #define RK_FUNC_15 15
上面就是16个复用功能编号,以GPIO0_C0为例,查看RK3568数据手册,可以得到GPIO0_C0复用情况如图10.1.2.1所示:
图10.1.2.1 GPIO0_C0复用功能
从图10.1.2.1可以看出GPIO3_D4有4个复用功能:
0:GPIO0_C0
1:PWM1_M0
2:GPU_AVS
3:UART0_RX
如果要将GPIO0_C0设置为GPIO功能,那么MUX就设置0,或者RK_FUNC_GPIO。如果要设置为PWM1_M0,那么MUX就设置为1。
4、phandle
最后一个就是phandle,用来描述一些引脚的通用配置信息,打开scripts/dtc/include-prefixes/arm/rockchip-pinconf.dtsi文件,此文件就是phandle可选的配置项,如下所示:
示例代码10.1.2.5 引脚配置项
5 &pinctrl {
6
7 /omit-if-no-ref/
8 pcfg_pull_up: pcfg-pull-up {
9 bias-pull-up;
10 };
11
12 /omit-if-no-ref/
13 pcfg_pull_down: pcfg-pull-down {
14 bias-pull-down;
15 };
16
17 /omit-if-no-ref/
18 pcfg_pull_none: pcfg-pull-none {
19 bias-disable;
20 };
21
22 /omit-if-no-ref/
23 pcfg_pull_none_drv_level_0: pcfg-pull-none-drv-level-0 {
24 bias-disable;
25 drive-strength = <0>;
26 };
......
344 };
上面的pcfg_pull_up、pcfg_pull_down、pcfg_pull_none和pcfg_pull_none_drv_level_0就是可使用的配置项。比如GPIO0_C0这个PIN用作普通的GPIO,不需要配置驱动能力,那么就可以使用pcfg_pull_none,也就是不设置上下拉。rockchip-pinconf.dtsi里面还有很多其他的配置项,大家自行查阅。
最总,如果要将GPIO0_C0设置为GPIO功能,那么配置就是:
rockchip,pins =<0 RK_PC0 RK_FUNC_GPIO &pcfg_pull_none>;
10.1.3 设备树中添加pinctrl节点模板
我们已经对pinctrl有了比较深入的了解,接下来我们学习一下如何在设备树中添加某个外设的PIN信息。比如我们需要将GPIO0_D1这个PIN复用为UART2_TX引脚,pinctrl节点添加过程如下:
1、创建对应的节点
在pinctrl节点下添加一个“uart2”子节点,然后在uart2节点里面在创建一个“uart2m0_xfer: uart2m0-xfer”子节点:
示例代码10.1.3.1 uart4_pins设备节点
1 &pinctrl {
2 uart2 {
3 /omit-if-no-ref/
4 uart2m0_xfer: uart2m0-xfer {
5 /* 具体的PIN信息 */
6 };
7 };
8 };
第3行的“/omit-if-no-ref/”笔者没有找出说明,但是瑞芯微官方在每个外设的pinctrl引脚设置前都加这一行,所以这里我们也加上去。
第4行,uart2m0_xfer子节点内就是放置具体的PIN配置信息。
2、添加“rockchip,pins”属性
添加一个“rockchip,pins”属性,这个属性是真正用来描述PIN配置信息的,这里我们只添加UART2的TX引脚,所以添加完以后如下所示:
示例代码10.1.3.2 uart2设备节点下的rockchip,pins属性
1 &pinctrl {
2 uart2 {
3 /omit-if-no-ref/
4 uart2m0_xfer: uart2m0-xfer {
5 rockchip,pins =
6 /* uart2_tx_m1 */
7 <0 RK_PD1 1 &pcfg_pull_up>;
8 };
9 };
10 };
按道理来讲,当我们将一个PIN用作GPIO功能的时候也需要创建对应的pinctrl节点,并且将所用的PIN复用为GPIO功能,但是!对于RK3568而言,如果一个PIN用作GPIO功能的时候不需要创建对应的pinctrl节点!
10.2 gpio子系统
10.2.1 gpio子系统简介
上一小节讲解了pinctrl子系统,pinctrl子系统重点是设置PIN(有的SOC叫做PAD)的复用和电气属性,如果pinctrl子系统将一个PIN复用为GPIO的话,那么接下来就要用到gpio子系统了。gpio子系统顾名思义,就是用于初始化GPIO并且提供相应的API函数,比如设置GPIO为输入输出,读取GPIO的值等。gpio子系统的主要目的就是方便驱动开发者使用gpio,驱动开发者在设备树中添加gpio相关信息,然后就可以在驱动程序中使用gpio子系统提供的API函数来操作GPIO,Linux内核向驱动开发者屏蔽掉了GPIO的设置过程,极大的方便了驱动开发者使用GPIO。
10.2.2 rk3568的gpio子系统驱动
1、设备树中的gpio信息
首先肯定是GPIO控制器的节点信息,以GPIO0_C0这个引脚所在的GPIO3为例,打开rk3568.dtsi,在里面找到如下所示内容:
示例代码10.2.2.1 gpio3控制器节点
3540 gpio0: gpio@fdd60000 {
3541 compatible = "rockchip,gpio-bank";
3542 reg = <0x0 0xfdd60000 0x0 0x100>;
3543 interrupts = <GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
3544 clocks = <&pmucru PCLK_GPIO0>, <&pmucru DBCLK_GPIO0>;
3545
3546 gpio-controller;
3547 #gpio-cells = <2>;
3548 gpio-ranges = <&pinctrl 0 0 32>;
3549 interrupt-controller;
3550 #interrupt-cells = <2>;
3551 };
示例代码10.2.2.1就是GPIO0的控制器信息,属于pincrtl的子节点,绑定文档Documentation/devicetree/bindings/gpio/gpio.txt详细描述了gpio控制器节点各个属性信息。
第3514行,compatible属性值为“rockchip,gpio-bank”,所以在linux内核中搜索这个字符串就可以找到对应的GPIO驱动源文件,为drivers/pinctrl/pinctrl-rockchip.c。
第3542行,reg属性设置了GPIO0控制器的寄存器基地址为0XFDD60000。
第3543行,interrupts属性描述GPIO0控制器对应的中断信息。
第3544行,clocks属性指定这个GPIO0控制器的时钟。
第3546行,“gpio-controller”表示gpio0节点是个GPIO控制器,每个GPIO控制器节点必须包含“gpio-controller”属性。
第3547行,“#gpio-cells”属性和“#address-cells”类似,#gpio-cells应该为2,表示一共有两个cell,第一个cell为GPIO编号,比如“&gpio0 RK_PC0”就表示GPIO0_C0。第二个cell表示GPIO极性,如果为0(GPIO_ACTIVE_HIGH)的话表示高电平有效,如果为1(GPIO_ACTIVE_LOW)的话表示低电平有效。
示例代码10.2.2.1中的是GPIO0控制器节点,当某个具体的引脚作为GPIO使用的时候还需要进一步设置。正点原子ATK-DLRK3568开发板将GPIO3_B6用作CSI1摄像头的RESET引脚,GPIO3_B6复用为GPIO功能,通过控制这个GPIO的高低电平就可以复位CSI1摄像头。但是,CSI1摄像头驱动程序怎么知道RESET引脚连接的GPIO3_B6呢?这里肯定需要设备树来告诉驱动,在设备树中的CSI1摄像头节点下添加一个属性来描述摄像头的RESET引脚就行了,CSI1摄像头驱动直接读取这个属性值就知道摄像头的RESET引脚使用的是哪个GPIO了。正点原子ATK-DLRK3568开发板的CSI1摄像头的I2C配置接口连接到RK3568的I2C4上。在rk3568-atk-evb1-ddr4-v10.dtsi中找到名为“i2c4”的节点,这个节点包含了所有连接到I2C5接口上的设备,如下所示:
示例代码10.2.2.2 设备树中I2C5节点
585 imx415: imx415@1a {
586 status = "okay";
587 compatible = "sony,imx415";
588 reg = <0x1a>;
589 clocks = <&cru CLK_CIF_OUT>;
590 clock-names = "xvclk";
591 power-domains = <&power RK3568_PD_VI>;
592 pinctrl-names = "rockchip,camera_default";
593 pinctrl-0 = <&cif_clk>;
594 reset-gpios = <&gpio3 RK_PB6 GPIO_ACTIVE_LOW>;
595 power-gpios = <&gpio4 RK_PB4 GPIO_ACTIVE_HIGH>;
596 rockchip,camera-module-index = <0>;
597 rockchip,camera-module-facing = "back";
598 rockchip,camera-module-name = "CMK-OT1522-FG3";
599 rockchip,camera-module-lens-name = "CS-P1150-IRC-8M-FAU";
......
606 };
第594行,属性“reset-gpios”描述了IMX415这个摄像头的RESET引脚使用的哪个IO。属性值一共有三个,我们来看一下这三个属性值的含义,“&gpio3”表示RESET引脚所使用的IO属于GPIO3组,“RK_PB6”表示GPIO3组的PB6,通过这两个值IMX415摄像头驱动程序就知道RESET引脚使用了GPIO3_B6这个引脚。最后一个是“GPIO_ACTIVE_LOW ”,Linux内核在include/linux/gpio/machine.h文件中定义了枚举类型gpio_lookup_flags,内容如下:
示例代码10.2.2.3 gpio_lookup_flag枚举类型
8 enum gpio_lookup_flags {
9 GPIO_ACTIVE_HIGH = (0 << 0),
10 GPIO_ACTIVE_LOW = (1 << 0),
11 GPIO_OPEN_DRAIN = (1 << 1),
12 GPIO_OPEN_SOURCE = (1 << 2),
13 GPIO_PERSISTENT = (0 << 3),
14 GPIO_TRANSITORY = (1 << 3),
15 };
10.2.3 gpio子系统API函数
对于驱动开发人员,设置好设备树以后就可以使用gpio子系统提供的API函数来操作指定的GPIO,gpio子系统向驱动开发人员屏蔽了具体的读写寄存器过程。这就是驱动分层与分离的好处,大家各司其职,做好自己的本职工作即可。gpio子系统提供的常用的API函数有下面几个:
1、gpio_request函数
gpio_request函数用于申请一个GPIO管脚,在使用一个GPIO之前一定要使用gpio_request进行申请,函数原型如下:
int gpio_request(unsigned gpio, const char label)
函数参数和返回值含义如下:
gpio:要申请的gpio标号,使用of_get_named_gpio函数从设备树获取指定GPIO属性信息,此函数会返回这个GPIO的标号。
label:给gpio设置个名字。
返回值:0,申请成功;其他值,申请失败。
2、gpio_free函数
如果不使用某个GPIO了,那么就可以调用gpio_free函数进行释放。函数原型如下:
void gpio_free(unsigned gpio)
函数参数和返回值含义如下:
gpio:要释放的gpio标号。
返回值:无。
3、gpio_direction_input函数
此函数用于设置某个GPIO为输入,函数原型如下所示:
int gpio_direction_input(unsigned gpio)
函数参数和返回值含义如下:
gpio:要设置为输入的GPIO标号。
返回值:0,设置成功;负值,设置失败。
4、gpio_direction_output函数
此函数用于设置某个GPIO为输出,并且设置默认输出值,函数原型如下:
int gpio_direction_output(unsigned gpio, int value)
函数参数和返回值含义如下:
gpio:要设置为输出的GPIO标号。
value:GPIO默认输出值。
返回值:0,设置成功;负值,设置失败。
5、gpio_get_value函数
此函数用于获取某个GPIO的值(0或1),函数原型如下:
int gpio_get_value(unsigned int gpio)
函数参数和返回值含义如下:
gpio:要获取的GPIO标号。
返回值:非负值,得到的GPIO值;负值,获取失败。
6、gpio_set_value函数
此函数用于设置某个GPIO的值,函数原型如下:
void gpio_set_value(unsigned int gpio, int value)
函数参数和返回值含义如下:
gpio:要设置的GPIO标号。
value:要设置的值。
返回值:无
关于gpio子系统常用的API函数就讲这些,这些是我们用的最多的。
10.2.4 设备树中添加gpio节点模板
本节我们以正点原子ATK-DLRK3568开发板上的LED为例,学习一下如何创建GPIO节点。LED连接到了GPIO0_C0引脚上,首先创建一个“led”设备节点。
1、创建led设备节点
在根节点“/”下创建led设备子节点,如下所示:
示例代码10.2.4.1 led设备节点
1 gpioled {
2 / 节点内容 */
3 };
2、添加GPIO属性信息
在gpioled节点中添加GPIO属性信息,表明所使用的GPIO是哪个引脚,添加完成以后如下所示:
示例代码10.2.4.2 向gpioled节点添加gpio属性
1 gpioled {
2 compatible = "alientek,led";
3 led-gpio = <&gpio0 RK_PC0 GPIO_ACTIVE_HIGH>;
4 status = "okay";
5 };
第3行,LED0设备所使用的gpio。
关于pinctrl子系统和gpio子系统就讲解到这里,接下来就使用pinctrl和gpio子系统来驱动ATK-DLRK3568开发板上的LED灯。
10.2.5 与gpio相关的OF函数
在示例代码10.2.4.2中,我们定义了一个名为“gpio”的属性,gpio属性描述了led这个设备所使用的GPIO。在驱动程序中需要读取gpio属性内容,Linux内核提供了几个与GPIO有关的OF函数,常用的几个OF函数如下所示:
1、of_gpio_named_count函数
of_gpio_named_count函数用于获取设备树某个属性里面定义了几个GPIO信息,要注意的是空的GPIO信息也会被统计到,比如:
gpios = <0
&gpio1 1 2
0
&gpio2 3 4>;
上述代码的“gpios”节点一共定义了4个GPIO,但是有2个是空的,没有实际的含义。通过of_gpio_named_count函数统计出来的GPIO数量就是4个,此函数原型如下:
int of_gpio_named_count(struct device_node *np, const char *propname)
函数参数和返回值含义如下:
np:设备节点。
propname:要统计的GPIO属性。
返回值:正值,统计到的GPIO数量;负值,失败。
2、of_gpio_count函数
和of_gpio_named_count函数一样,但是不同的地方在于,此函数统计的是“gpios”这个属性的GPIO数量,而of_gpio_named_count函数可以统计任意属性的GPIO信息,函数原型如下所示:
int of_gpio_count(struct device_node *np)
函数参数和返回值含义如下:
np:设备节点。
返回值:正值,统计到的GPIO数量;负值,失败。
3、of_get_named_gpio函数
此函数获取GPIO编号,因为Linux内核中关于GPIO的API函数都要使用GPIO编号,此函数会将设备树中类似<&gpio4 RK_PA1 GPIO_ACTIVE_LOW>的属性信息转换为对应的GPIO编号,此函数在驱动中使用很频繁!函数原型如下:
int of_get_named_gpio(struct device_node *np,
const char *propname,
int index)
函数参数和返回值含义如下:
np:设备节点。
propname:包含要获取GPIO信息的属性名。
index:GPIO索引,因为一个属性里面可能包含多个GPIO,此参数指定要获取哪个GPIO的编号,如果只有一个GPIO信息的话此参数为0。
返回值:正值,获取到的GPIO编号;负值,失败。
10.3 硬件原理图分析
本实验的硬件原理参考6.2小节即可。
10.4 实验程序编写
本实验对应的例程路径为:开发板光盘 01、程序源码Linux驱动例程源码05_gpioled。
本章实验我们继续研究LED灯,在第九章实验中我们通过设备树向dtsled.c文件传递相应的寄存器物理地址,然后在驱动文件中配置寄存器。本章实验我们使用gpio子系统来完成LED灯驱动。
10.4.1 检查IO是否已经被使用
首先我们要检查一下GPIO0_C0对应的GPIO有没有被其他外设使用,如果这个GPIO已经被分配给了其他外设,那么我们驱动在申请这个GPIO就会失败,如图10.4.1.1所示:
图10.4.1.1 GPIO申请失败
图10.4.1.1就是GPIO申请失败提示,因为当前开发板系统将GPIO0_C0这个IO分配给了内核自带的LED驱动做心跳灯了。所以需要先关闭GPIO0_C0作为心跳灯这个功能,也就是将GPIO0_C0对应的GPIO释放出来。打开rk3568-evb.dtsi文件,找到如下图10.4.1.2所示代码:
图10.4.1.2 GPIO0_C0用作心跳灯
图10.4.1.2中可以看出,正点原子出厂系统默认将GPIO0_C0用作心跳灯了,所以系统在启动的时候就先将GPIO0_C0给了心跳灯,我们后面再申请肯定就会失败!
解决方法就是关闭心跳灯功能,也就是在图10.4.1.2中第167行添加status改为disabled,也就是禁止work这个节点,那么禁止心跳灯功能。这样系统启动的时候就不会将GPIO0_C0分配给心跳灯,我们后面也就可以申请了,修改后如图10.4.1.3所示:
图10.4.1.3 禁止心跳灯
图10.4.1.3中将status改为disabled就禁止了led,也就是心跳灯功能,我们后面需要禁止哪个功能,只需要将其status属性改为disabled就可以了。
10.4.2 修改设备树文件
在rk3568-atk-evb1-ddr4-v10.dtsi文件的根节点“/”下创建LED灯节点,节点名为“gpioled”,节点内容如下:
示例代码10.4.1.1 创建LED灯节点
1 gpioled {
2 compatible = “alientek,led”;
3 led-gpio = <&gpio0 RK_PC0 GPIO_ACTIVE_HIGH>;
4 status = “okay”;
5 };
第3行,led-gpio属性指定了LED灯所使用的GPIO,在这里就是GPIO0的PC0,高电平有效。稍后编写驱动程序的时候会获取led-gpio属性的内容来得到GPIO编号,因为gpio子系统的API操作函数需要GPIO编号。
设备树编写完成以后使用“./build.sh kernel”命令重新编译并打包内核,然后将新编译出来的boot.img烧写到开发板中。启动成功以后进入“/proc/device-tree”目录中查看“gpioled”节点是否存在,如果存在的话就说明设备树基本修改成功(具体还要驱动验证),结果如图10.4.2.1所示:
图10.4.2.1 gpioled子节点
10.4.3 LED灯驱动程序编写
设备树准备好以后就可以编写驱动程序了,本章实验在第九章实验驱动文件dtsled.c的基础上修改而来。新建名为“05_gpioled”文件夹,然后在05_gpioled文件夹里面创建vscode工程,工作区命名为“gpioled”。工程创建好以后新建gpioled.c文件,在gpioled.c里面输入如下内容:
示例代码10.4.3.1 gpioled.c驱动文件代码
1 #include <linux/types.h>
2 #include <linux/kernel.h>
3 #include <linux/delay.h>
4 #include <linux/ide.h>
5 #include <linux/init.h>
6 #include <linux/module.h>
7 #include <linux/errno.h>
8 #include <linux/gpio.h>
9 #include <linux/cdev.h>
10 #include <linux/device.h>
11 #include <linux/of.h>
12 #include <linux/of_address.h>
13 #include <linux/of_gpio.h>
14 //#include <asm/mach/map.h>
15 #include <asm/uaccess.h>
16 #include <asm/io.h>
17 /***************************************************************
18 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
19 文件名 : gpioled.c
20 作者 : 正点原子
21 版本 : V1.0
22 描述 : LED驱动文件。
23 其他 : 无
24 论坛 : www.openedv.com
25 日志 : 初版V1.0 2022/12/07 正点原子团队创建
26 ***************************************************************/
27 #define GPIOLED_CNT 1 /* 设备号个数 */
28 #define GPIOLED_NAME "gpioled" /* 名字 */
29 #define LEDOFF 0 /* 关灯 */
30 #define LEDON 1 /* 开灯 */
31
32 /* gpioled设备结构体 */
33 struct gpioled_dev{
34 dev_t devid; /* 设备号 */
35 struct cdev cdev; /* cdev */
36 struct class *class; /* 类 */
37 struct device *device; /* 设备 */
38 int major; /* 主设备号 */
39 int minor; /* 次设备号 */
40 struct device_node *nd; /* 设备节点 */
41 int led_gpio; /* led所使用的GPIO编号 */
42 };
43
44 struct gpioled_dev gpioled; /* led设备 */
45
46 /*
47 * @description : 打开设备
48 * @param – inode : 传递给驱动的inode
49 * @param - filp : 设备文件,file结构体有个叫做private_data的成员变量
50 * 一般在open的时候将private_data指向设备结构体。
51 * @return : 0 成功;其他 失败
52 */
53 static int led_open(struct inode *inode, struct file *filp)
54 {
55 filp->private_data = &gpioled; /* 设置私有数据 */
56 return 0;
57 }
58
59 /*
60 * @description : 从设备读取数据
61 * @param - filp : 要打开的设备文件(文件描述符)
62 * @param - buf : 返回给用户空间的数据缓冲区
63 * @param - cnt : 要读取的数据长度
64 * @param - offt : 相对于文件首地址的偏移
65 * @return : 读取的字节数,如果为负值,表示读取失败
66 */
67 static ssize_t led_read(struct file *filp, char __user *buf,
size_t cnt, loff_t *offt)
68 {
69 return 0;
70 }
71
72 /*
73 * @description : 向设备写数据
74 * @param - filp : 设备文件,表示打开的文件描述符
75 * @param - buf : 要写给设备写入的数据
76 * @param - cnt : 要写入的数据长度
77 * @param - offt : 相对于文件首地址的偏移
78 * @return : 写入的字节数,如果为负值,表示写入失败
79 */
80 static ssize_t led_write(struct file *filp, const char __user *buf,
size_t cnt, loff_t *offt)
81 {
82 int retvalue;
83 unsigned char databuf[1];
84 unsigned char ledstat;
85 struct gpioled_dev *dev = filp->private_data;
86
87 retvalue = copy_from_user(databuf, buf, cnt);
88 if(retvalue < 0) {
89 printk("kernel write failed!\r\n");
90 return -EFAULT;
91 }
92
93 ledstat = databuf[0]; /* 获取状态值 */
94
95 if(ledstat == LEDON) {
96 gpio_set_value(dev->led_gpio, 1); /* 打开LED灯 */
97 } else if(ledstat == LEDOFF) {
98 gpio_set_value(dev->led_gpio, 0); /* 关闭LED灯 */
99 }
100 return 0;
101 }
102
103 /*
104 * @description : 关闭/释放设备
105 * @param - filp : 要关闭的设备文件(文件描述符)
106 * @return : 0 成功;其他 失败
107 */
108 static int led_release(struct inode *inode, struct file *filp)
109 {
110 return 0;
111 }
112
113 /* 设备操作函数 */
114 static struct file_operations gpioled_fops = {
115 .owner = THIS_MODULE,
116 .open = led_open,
117 .read = led_read,
118 .write = led_write,
119 .release = led_release,
120 };
121
122 /*
123 * @description : 驱动出口函数
124 * @param : 无
125 * @return : 无
126 */
127 static int __init led_init(void)
128 {
129 int ret = 0;
130 const char *str;
131
132 /* 设置LED所使用的GPIO */
133 /* 1、获取设备节点:gpioled */
134 gpioled.nd = of_find_node_by_path("/gpioled");
135 if(gpioled.nd == NULL) {
136 printk("gpioled node not find!\r\n");
137 return -EINVAL;
138 }
139
140 /* 2.读取status属性 */
141 ret = of_property_read_string(gpioled.nd, "status", &str);
142 if(ret < 0)
143 return -EINVAL;
144
145 if (strcmp(str, "okay"))
146 return -EINVAL;
147
148 /* 3、获取compatible属性值并进行匹配 */
149 ret = of_property_read_string(gpioled.nd, "compatible", &str);
150 if(ret < 0) {
151 printk("gpioled: Failed to get compatible property\n");
152 return -EINVAL;
153 }
154
155 if (strcmp(str, "alientek,led")) {
156 printk("gpioled: Compatible match failed\n");
157 return -EINVAL;
158 }
159
160 /* 4、 获取设备树中的gpio属性,得到LED所使用的LED编号 */
161 gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);
162 if(gpioled.led_gpio < 0) {
163 printk("can't get led-gpio");
164 return -EINVAL;
165 }
166 printk("led-gpio num = %d\r\n", gpioled.led_gpio);
167
168 /* 5.向gpio子系统申请使用GPIO */
169 ret = gpio_request(gpioled.led_gpio, "LED-GPIO");
170 if (ret) {
171 printk(KERN_ERR "gpioled: Failed to request led-gpio\n");
172 return ret;
173 }
174
175 /* 6、设置GPIO为输出,并且输出低电平,默认关闭LED灯 */
176 ret = gpio_direction_output(gpioled.led_gpio, 0);
177 if(ret < 0) {
178 printk("can't set gpio!\r\n");
179 }
180
181 /* 注册字符设备驱动 */
182 /* 1、创建设备号 */
183 if (gpioled.major) { /* 定义了设备号 */
184 gpioled.devid = MKDEV(gpioled.major, 0);
185 ret = register_chrdev_region(gpioled.devid, GPIOLED_CNT,
GPIOLED_NAME);
186 if(ret < 0) {
187 pr_err("cannot register %s char driver [ret=%d]\n",
GPIOLED_NAME, GPIOLED_CNT);
188 goto free_gpio;
189 }
190 } else { /* 没有定义设备号 */
191 ret = alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT,
GPIOLED_NAME); /* 申请设备号 */
192 if(ret < 0) {
193 pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n",
GPIOLED_NAME, ret);
194 goto free_gpio;
195 }
196 gpioled.major = MAJOR(gpioled.devid); /* 获取分配号的主设备号 */
197 gpioled.minor = MINOR(gpioled.devid); /* 获取分配号的次设备号 */
198 }
199 printk("gpioled major=%d,minor=%d\r\n",gpioled.major,
gpioled.minor);
200
201 /* 2、初始化cdev */
202 gpioled.cdev.owner = THIS_MODULE;
203 cdev_init(&gpioled.cdev, &gpioled_fops);
204
205 /* 3、添加一个cdev */
206 cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);
207 if(ret < 0)
208 goto del_unregister;
209
210 /* 4、创建类 */
211 gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
212 if (IS_ERR(gpioled.class)) {
213 goto del_cdev;
214 }
215
216 /* 5、创建设备 */
217 gpioled.device = device_create(gpioled.class, NULL,
gpioled.devid, NULL, GPIOLED_NAME);
218 if (IS_ERR(gpioled.device)) {
219 goto destroy_class;
220 }
221 return 0;
222
223 destroy_class:
224 class_destroy(gpioled.class);
225 del_cdev:
226 cdev_del(&gpioled.cdev);
227 del_unregister:
228 unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);
229 free_gpio:
230 gpio_free(gpioled.led_gpio);
231 return -EIO;
232 }
233
234 /*
235 * @description : 驱动出口函数
236 * @param : 无
237 * @return : 无
238 */
239 static void __exit led_exit(void)
240 {
241 /* 注销字符设备驱动 */
242 cdev_del(&gpioled.cdev);/* 删除cdev */
243 unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);
244 device_destroy(gpioled.class, gpioled.devid);/* 注销设备 */
245 class_destroy(gpioled.class);/* 注销类 */
246 gpio_free(gpioled.led_gpio); /* 释放GPIO */
247 }
248
249 module_init(led_init);
250 module_exit(led_exit);
251 MODULE_LICENSE("GPL");
252 MODULE_AUTHOR("ALIENTEK");
253 MODULE_INFO(intree, "Y");
第41行,在设备结构体gpioled_dev中加入led_gpio这个成员变量,此成员变量保存LED等所使用的GPIO编号。
第55行,将设备结构体变量gpioled设置为filp的私有数据private_data。
第85行,通过读取filp的private_data成员变量来得到设备结构体变量,也就是gpioled。这种将设备结构体设置为filp私有数据的方法在Linux内核驱动里面非常常见。
第96、98行,直接调用gpio_set_value函数来向GPIO写入数据,实现开/关LED的效果。不需要我们直接操作相应的寄存器。
第134行,获取节点“/gpioled”。
第141~146行,获取“status”属性的值,判断属性是否“okay”。
第149~158行,获取compatible属性值并进行匹配。
第161行,通过函数of_get_named_gpio函数获取LED所使用的LED编号。相当于将gpioled节点中的“led-gpio”属性值转换为对应的LED编号。
第169行,通过函数gpio_request向GPIO子系统申请使用GPIO0_C0。
第176行,调用函数gpio_direction_output设置GPIO0_C0这个GPIO为输出,并且默认低电平,这样默认就会关闭LED灯。
可以看出gpioled.c文件中的内容和第九章的dtsled.c差不多,只是取消掉了配置寄存器的过程,改为使用Linux内核提供的API函数。在GPIO操作上更加的规范化,符合Linux代码框架,而且也简化了GPIO驱动开发的难度,以后我们所有例程用到GPIO的地方都采用此方法。
10.4.4 编写测试APP
本章直接使用第九章的测试APP,将上一章的ledApp.c文件复制到本章实验工程下即可。
10.5 运行测试
10.5.1 编译驱动程序和测试APP
1、编译驱动程序
编写Makefile文件,本章实验的Makefile文件和第五章实验基本一样,只是将obj-m变量的值改为gpioled.o,Makefile内容如下所示:
示例代码10.5.1.1 Makefile文件
1 KERNELDIR := /home/alientek/rk3568_linux_sdk/kernel
…
4 obj-m := gpioled.o
…
11 clean:
12 $(MAKE) -C
(
K
E
R
N
E
L
D
I
R
)
M
=
(KERNELDIR) M=
(KERNELDIR)M=(CURRENT_PATH) clean
第4行,设置obj-m变量的值为gpioled.o。
输入如下命令编译出驱动模块文件:
make ARCH=arm64 //ARCH=arm64必须指定,否则编译会失败
编译成功以后就会生成一个名为“gpioled.ko”的驱动模块文件。
2、编译测试APP
输入如下命令编译测试ledApp.c这个测试程序:
/opt/atk-dlrk356x-toolchain/bin/aarch64-buildroot-linux-gnu-gcc ledApp.c -o ledApp
编译成功以后就会生成ledApp这个应用程序。
10.5.2 运行测试
由于在本实验中,我们直接通过修改设备树的方式永久的关闭了LED0心跳灯功能,所以就不需要再输入命令关闭了。
在Ubuntu中将上一小节编译出来的gpioled.ko和ledApp这两个文件通过adb命令发送到开发板的/lib/modules/4.19.232目录下,命令如下:
adb push gpioled.ko ledApp /lib/modules/4.19.232
发送成功以后进入到开发板目录lib/modules/4.19.232中,输入如下命令加载dtsled.ko驱动模块:
depmod //第一次加载驱动的时候需要运行此命令
modprobe gpioled //加载驱动
驱动加载成功以后会在终端中输出一些信息,如图10.5.2.1所示:
图10.5.2.1 驱动加载成功以后输出的信息
从图10.5.2.1可以看出,gpioled这个节点找到了,并且GPIO0_C0这个GPIO的编号为124。驱动加载成功以后就可以使用ledApp软件来测试驱动是否工作正常,输入如下命令打开LED灯:
./ledApp /dev/gpioled 1 //打开LED灯
输入上述命令以后观察开发板上的红色LED灯是否点亮,如果点亮的话说明驱动工作正常。在输入如下命令关闭LED灯:
./ledApp /dev/gpioled 0 //关闭LED灯
输入上述命令以后观察开发板上的红色LED灯是否熄灭。如果要卸载驱动的话输入如下命令即可:
rmmod gpioled.ko