RK3568学习笔记16:PINCTRL和GPIO子系统
1. PinCtrl 子系统
Linux内核针对 PIN推出了 pinctrl子系统,对于 GPIO的电气属性配置推出了 gpio子
系统。
PinCtrl子系统的工作内容如下:
- 获取设备树中 pin信息。
- 根据获取到的 pin信息来设置 pin的复用功能
- 根据获取到的 pin信息来设置 pin的电气特性, 如驱动能力。
对于使用者来讲,只需要在设备树里面设置好某个 pin的相关属性即可,其他的初始化工作均由 pinctrl子系统来完成, pinctrl子系统源码目录为 drivers/pinctrl。
1.1 RK3568的pinctrl子系统驱动
在rk3568.dtsi中有一个pinctrl的节点,代码如下:
pinctrl: pinctrl {
compatible = "rockchip,rk3568-pinctrl";
rockchip,grf = <&grf>;
rockchip,pmu = <&pmugrf>;
#address-cells = <2>;
#size-cells = <2>;
ranges;
gpio0: gpio@fdd60000 {
compatible = "rockchip,gpio-bank";
reg = <0x0 0xfdd60000 0x0 0x100>;
interrupts = <GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&pmucru PCLK_GPIO0>, <&pmucru DBCLK_GPIO0>;
gpio-controller;
#gpio-cells = <2>;
gpio-ranges = <&pinctrl 0 0 32>;
interrupt-controller;
#interrupt-cells = <2>;
};
gpio1: gpio@fe740000 {
compatible = "rockchip,gpio-bank";
reg = <0x0 0xfe740000 0x0 0x100>;
interrupts = <GIC_SPI 34 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cru PCLK_GPIO1>, <&cru DBCLK_GPIO1>;
gpio-controller;
#gpio-cells = <2>;
gpio-ranges = <&pinctrl 0 32 32>;
interrupt-controller;
#interrupt-cells = <2>;
};
gpio2: gpio@fe750000 {
compatible = "rockchip,gpio-bank";
reg = <0x0 0xfe750000 0x0 0x100>;
interrupts = <GIC_SPI 35 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cru PCLK_GPIO2>, <&cru DBCLK_GPIO2>;
gpio-controller;
#gpio-cells = <2>;
gpio-ranges = <&pinctrl 0 64 32>;
interrupt-controller;
#interrupt-cells = <2>;
};
gpio3: gpio@fe760000 {
compatible = "rockchip,gpio-bank";
reg = <0x0 0xfe760000 0x0 0x100>;
interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cru PCLK_GPIO3>, <&cru DBCLK_GPIO3>;
gpio-controller;
#gpio-cells = <2>;
gpio-ranges = <&pinctrl 0 96 32>;
interrupt-controller;
#interrupt-cells = <2>;
};
gpio4: gpio@fe770000 {
compatible = "rockchip,gpio-bank";
reg = <0x0 0xfe770000 0x0 0x100>;
interrupts = <GIC_SPI 37 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cru PCLK_GPIO4>, <&cru DBCLK_GPIO4>;
gpio-controller;
#gpio-cells = <2>;
gpio-ranges = <&pinctrl 0 128 32>;
interrupt-controller;
#interrupt-cells = <2>;
};
};
#address-cells属性值为 2
#size-cells属性值为 2,
也就是说 pinctrl下的所有子节点的 reg第一位加第二位是起始地址,第三位加第四位为长度。
rk3568有五 组 GPIO GPIO0~GPIO4,每组 GPIO对应的寄存器地址不同。
pinctrl配置文件在rk3568-pinctrl.dtsi中
&pinctrl {
acodec {
/omit-if-no-ref/
acodec_pins: acodec-pins {
rockchip,pins =
/* acodec_adc_sync */
<1 RK_PB1 5 &pcfg_pull_none>,
/* acodec_adcclk */
<1 RK_PA1 5 &pcfg_pull_none>,
/* acodec_adcdata */
<1 RK_PA0 5 &pcfg_pull_none>,
/* acodec_dac_datal */
<1 RK_PA7 5 &pcfg_pull_none>,
/* acodec_dac_datar */
<1 RK_PB0 5 &pcfg_pull_none>,
/* acodec_dacclk */
<1 RK_PA3 5 &pcfg_pull_none>,
/* acodec_dacsync */
<1 RK_PA5 5 &pcfg_pull_none>;
};
};
audiopwm {
/omit-if-no-ref/
audiopwm_lout: audiopwm-lout {
rockchip,pins =
/* audiopwm_lout */
<1 RK_PA0 4 &pcfg_pull_none>;
};
/omit-if-no-ref/
audiopwm_loutn: audiopwm-loutn {
rockchip,pins =
/* audiopwm_loutn */
<1 RK_PA1 6 &pcfg_pull_none>;
};
/omit-if-no-ref/
audiopwm_loutp: audiopwm-loutp {
rockchip,pins =
/* audiopwm_loutp */
<1 RK_PA0 6 &pcfg_pull_none>;
};
/omit-if-no-ref/
audiopwm_rout: audiopwm-rout {
rockchip,pins =
/* audiopwm_rout */
<1 RK_PA1 4 &pcfg_pull_none>;
};
/omit-if-no-ref/
audiopwm_routn: audiopwm-routn {
rockchip,pins =
/* audiopwm_routn */
<1 RK_PA7 4 &pcfg_pull_none>;
};
/omit-if-no-ref/
audiopwm_routp: audiopwm-routp {
rockchip,pins =
/* audiopwm_routp */
<1 RK_PA6 4 &pcfg_pull_none>;
};
};
}
每个 pincrtl节点必须至少包含一个子节点来存放 pincrtl相关信息,也就是 pinctrl集,这个集合里面存放当前外设用到哪些引脚 (PIN)、复用配置、 上下拉、驱动能力等 。一般这个存放 pincrtl集的子节点名字是“ rockchip,pins”。
上面讲了,在 rockchip,pins 子节点里面存放外设的引脚描述信息, 根据 rockchip,pinctrl.txt文档里面的介绍,引脚复用设置的格式如下:
rockchip,pins = <PIN_BANK PIN_BANK_IDX MUX &phandle>
PIN_BANK
: PIN_BANK就是 PIN所属的组, RK3568一共有 5组 PIN GPIO0~GPIO4,分别对应 0~4。所以,如果要设置 GPIO0_C0这个 PIN,那么 PIN_BANK就是 0 。
PIN_BANK_IDX
:PIN_BANK_IDX是组内的编号,以 GPIO0组为例,一共有 A0~A7、 B0~B7、 C0~C7、 D0~D7,这 32个 PIN。瑞芯微已经给这些 PIN编了号,打开 include/dt-bindings/pinctrl/rockchip.h
文件,
定义如下:
#ifndef __DT_BINDINGS_ROCKCHIP_PINCTRL_H__
#define __DT_BINDINGS_ROCKCHIP_PINCTRL_H__
#define RK_GPIO0 0
#define RK_GPIO1 1
#define RK_GPIO2 2
#define RK_GPIO3 3
#define RK_GPIO4 4
#define RK_GPIO6 6
#define RK_PA0 0
#define RK_PA1 1
#define RK_PA2 2
#define RK_PA3 3
#define RK_PA4 4
#define RK_PA5 5
#define RK_PA6 6
#define RK_PA7 7
#define RK_PB0 8
#define RK_PB1 9
#define RK_PB2 10
#define RK_PB3 11
#define RK_PB4 12
#define RK_PB5 13
#define RK_PB6 14
#define RK_PB7 15
#define RK_PC0 16
#define RK_PC1 17
#define RK_PC2 18
#define RK_PC3 19
#define RK_PC4 20
#define RK_PC5 21
#define RK_PC6 22
#define RK_PC7 23
#define RK_PD0 24
#define RK_PD1 25
#define RK_PD2 26
#define RK_PD3 27
#define RK_PD4 28
#define RK_PD5 29
#define RK_PD6 30
#define RK_PD7 31
#endif
如果要设置 GPIO_C0
,则 PIN_BANK_IDX
设置为 RK_PC0
MUX
:MUX是PIN的复用功能,一个PIN最多有16个复用功能,复用功能如下:
#define RK_FUNC_GPIO 0
#define RK_FUNC_0 0
#define RK_FUNC_1 1
#define RK_FUNC_2 2
#define RK_FUNC_3 3
#define RK_FUNC_4 4
#define RK_FUNC_5 5
#define RK_FUNC_6 6
#define RK_FUNC_7 7
#define RK_FUNC_8 8
#define RK_FUNC_9 9
#define RK_FUNC_10 10
#define RK_FUNC_11 11
#define RK_FUNC_12 12
#define RK_FUNC_13 13
#define RK_FUNC_14 14
#define RK_FUNC_15 15
以 GPIO0_C0为例,查看 RK3568数据手册,可以得到GPIO0_C0复用情况如下图所示:
phandle
:,用来描述一些引脚的通用配置信息,打开 scripts/dtc/include-prefixes/arm/rockchip-pinconf.dtsi
文件,此文件就是 phandle可选的配置项,如下所示:
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright (c) 2020~2021 Rockchip Electronics Co., Ltd.
*/
&pinctrl {
/omit-if-no-ref/
pcfg_pull_up: pcfg-pull-up {
bias-pull-up;
};
/omit-if-no-ref/
pcfg_pull_down: pcfg-pull-down {
bias-pull-down;
};
/omit-if-no-ref/
pcfg_pull_none: pcfg-pull-none {
bias-disable;
};
/omit-if-no-ref/
pcfg_pull_none_drv_level_0: pcfg-pull-none-drv-level-0 {
bias-disable;
drive-strength = <0>;
};
/omit-if-no-ref/
pcfg_pull_none_drv_level_1: pcfg-pull-none-drv-level-1 {
bias-disable;
drive-strength = <1>;
};
/omit-if-no-ref/
pcfg_pull_none_drv_level_2: pcfg-pull-none-drv-level-2 {
bias-disable;
drive-strength = <2>;
};
/omit-if-no-ref/
pcfg_pull_none_drv_level_3: pcfg-pull-none-drv-level-3 {
bias-disable;
drive-strength = <3>;
};
/omit-if-no-ref/
pcfg_pull_none_drv_level_4: pcfg-pull-none-drv-level-4 {
bias-disable;
drive-strength = <4>;
};
/omit-if-no-ref/
pcfg_pull_none_drv_level_5: pcfg-pull-none-drv-level-5 {
bias-disable;
drive-strength = <5>;
};
/omit-if-no-ref/
pcfg_pull_none_drv_level_6: pcfg-pull-none-drv-level-6 {
bias-disable;
drive-strength = <6>;
};
/omit-if-no-ref/
pcfg_pull_none_drv_level_7: pcfg-pull-none-drv-level-7 {
bias-disable;
drive-strength = <7>;
};
/omit-if-no-ref/
pcfg_pull_none_drv_level_8: pcfg-pull-none-drv-level-8 {
bias-disable;
drive-strength = <8>;
};
/omit-if-no-ref/
pcfg_pull_none_drv_level_9: pcfg-pull-none-drv-level-9 {
bias-disable;
drive-strength = <9>;
};
/omit-if-no-ref/
pcfg_pull_none_drv_level_10: pcfg-pull-none-drv-level-10 {
bias-disable;
drive-strength = <10>;
};
/omit-if-no-ref/
pcfg_pull_none_drv_level_11: pcfg-pull-none-drv-level-11 {
bias-disable;
drive-strength = <11>;
};
/omit-if-no-ref/
pcfg_pull_none_drv_level_12: pcfg-pull-none-drv-level-12 {
bias-disable;
drive-strength = <12>;
};
/omit-if-no-ref/
pcfg_pull_none_drv_level_13: pcfg-pull-none-drv-level-13 {
bias-disable;
drive-strength = <13>;
};
/omit-if-no-ref/
pcfg_pull_none_drv_level_14: pcfg-pull-none-drv-level-14 {
bias-disable;
drive-strength = <14>;
};
/omit-if-no-ref/
pcfg_pull_none_drv_level_15: pcfg-pull-none-drv-level-15 {
bias-disable;
drive-strength = <15>;
};
/omit-if-no-ref/
pcfg_pull_up_drv_level_0: pcfg-pull-up-drv-level-0 {
bias-pull-up;
drive-strength = <0>;
};
/omit-if-no-ref/
pcfg_pull_up_drv_level_1: pcfg-pull-up-drv-level-1 {
bias-pull-up;
drive-strength = <1>;
};
/omit-if-no-ref/
pcfg_pull_up_drv_level_2: pcfg-pull-up-drv-level-2 {
bias-pull-up;
drive-strength = <2>;
};
/omit-if-no-ref/
pcfg_pull_up_drv_level_3: pcfg-pull-up-drv-level-3 {
bias-pull-up;
drive-strength = <3>;
};
/omit-if-no-ref/
pcfg_pull_up_drv_level_4: pcfg-pull-up-drv-level-4 {
bias-pull-up;
drive-strength = <4>;
};
/omit-if-no-ref/
pcfg_pull_up_drv_level_5: pcfg-pull-up-drv-level-5 {
bias-pull-up;
drive-strength = <5>;
};
/omit-if-no-ref/
pcfg_pull_up_drv_level_6: pcfg-pull-up-drv-level-6 {
bias-pull-up;
drive-strength = <6>;
};
/omit-if-no-ref/
pcfg_pull_up_drv_level_7: pcfg-pull-up-drv-level-7 {
bias-pull-up;
drive-strength = <7>;
};
/omit-if-no-ref/
pcfg_pull_up_drv_level_8: pcfg-pull-up-drv-level-8 {
bias-pull-up;
drive-strength = <8>;
};
/omit-if-no-ref/
pcfg_pull_up_drv_level_9: pcfg-pull-up-drv-level-9 {
bias-pull-up;
drive-strength = <9>;
};
/omit-if-no-ref/
pcfg_pull_up_drv_level_10: pcfg-pull-up-drv-level-10 {
bias-pull-up;
drive-strength = <10>;
};
/omit-if-no-ref/
pcfg_pull_up_drv_level_11: pcfg-pull-up-drv-level-11 {
bias-pull-up;
drive-strength = <11>;
};
/omit-if-no-ref/
pcfg_pull_up_drv_level_12: pcfg-pull-up-drv-level-12 {
bias-pull-up;
drive-strength = <12>;
};
/omit-if-no-ref/
pcfg_pull_up_drv_level_13: pcfg-pull-up-drv-level-13 {
bias-pull-up;
drive-strength = <13>;
};
/omit-if-no-ref/
pcfg_pull_up_drv_level_14: pcfg-pull-up-drv-level-14 {
bias-pull-up;
drive-strength = <14>;
};
/omit-if-no-ref/
pcfg_pull_up_drv_level_15: pcfg-pull-up-drv-level-15 {
bias-pull-up;
drive-strength = <15>;
};
/omit-if-no-ref/
pcfg_pull_down_drv_level_0: pcfg-pull-down-drv-level-0 {
bias-pull-down;
drive-strength = <0>;
};
/omit-if-no-ref/
pcfg_pull_down_drv_level_1: pcfg-pull-down-drv-level-1 {
bias-pull-down;
drive-strength = <1>;
};
/omit-if-no-ref/
pcfg_pull_down_drv_level_2: pcfg-pull-down-drv-level-2 {
bias-pull-down;
drive-strength = <2>;
};
/omit-if-no-ref/
pcfg_pull_down_drv_level_3: pcfg-pull-down-drv-level-3 {
bias-pull-down;
drive-strength = <3>;
};
/omit-if-no-ref/
pcfg_pull_down_drv_level_4: pcfg-pull-down-drv-level-4 {
bias-pull-down;
drive-strength = <4>;
};
/omit-if-no-ref/
pcfg_pull_down_drv_level_5: pcfg-pull-down-drv-level-5 {
bias-pull-down;
drive-strength = <5>;
};
/omit-if-no-ref/
pcfg_pull_down_drv_level_6: pcfg-pull-down-drv-level-6 {
bias-pull-down;
drive-strength = <6>;
};
/omit-if-no-ref/
pcfg_pull_down_drv_level_7: pcfg-pull-down-drv-level-7 {
bias-pull-down;
drive-strength = <7>;
};
/omit-if-no-ref/
pcfg_pull_down_drv_level_8: pcfg-pull-down-drv-level-8 {
bias-pull-down;
drive-strength = <8>;
};
/omit-if-no-ref/
pcfg_pull_down_drv_level_9: pcfg-pull-down-drv-level-9 {
bias-pull-down;
drive-strength = <9>;
};
/omit-if-no-ref/
pcfg_pull_down_drv_level_10: pcfg-pull-down-drv-level-10 {
bias-pull-down;
drive-strength = <10>;
};
/omit-if-no-ref/
pcfg_pull_down_drv_level_11: pcfg-pull-down-drv-level-11 {
bias-pull-down;
drive-strength = <11>;
};
/omit-if-no-ref/
pcfg_pull_down_drv_level_12: pcfg-pull-down-drv-level-12 {
bias-pull-down;
drive-strength = <12>;
};
/omit-if-no-ref/
pcfg_pull_down_drv_level_13: pcfg-pull-down-drv-level-13 {
bias-pull-down;
drive-strength = <13>;
};
/omit-if-no-ref/
pcfg_pull_down_drv_level_14: pcfg-pull-down-drv-level-14 {
bias-pull-down;
drive-strength = <14>;
};
/omit-if-no-ref/
pcfg_pull_down_drv_level_15: pcfg-pull-down-drv-level-15 {
bias-pull-down;
drive-strength = <15>;
};
/omit-if-no-ref/
pcfg_pull_up_smt: pcfg-pull-up-smt {
bias-pull-up;
input-schmitt-enable;
};
/omit-if-no-ref/
pcfg_pull_down_smt: pcfg-pull-down-smt {
bias-pull-down;
input-schmitt-enable;
};
/omit-if-no-ref/
pcfg_pull_none_smt: pcfg-pull-none-smt {
bias-disable;
input-schmitt-enable;
};
/omit-if-no-ref/
pcfg_pull_none_drv_level_0_smt: pcfg-pull-none-drv-level-0-smt {
bias-disable;
drive-strength = <0>;
input-schmitt-enable;
};
/omit-if-no-ref/
pcfg_output_high: pcfg-output-high {
output-high;
};
/omit-if-no-ref/
pcfg_output_high_pull_up: pcfg-output-high-pull-up {
output-high;
bias-pull-up;
};
/omit-if-no-ref/
pcfg_output_high_pull_down: pcfg-output-high-pull-down {
output-high;
bias-pull-down;
};
/omit-if-no-ref/
pcfg_output_high_pull_none: pcfg-output-high-pull-none {
output-high;
bias-disable;
};
/omit-if-no-ref/
pcfg_output_low: pcfg-output-low {
output-low;
};
/omit-if-no-ref/
pcfg_output_low_pull_up: pcfg-output-low-pull-up {
output-low;
bias-pull-up;
};
/omit-if-no-ref/
pcfg_output_low_pull_down: pcfg-output-low-pull-down {
output-low;
bias-pull-down;
};
/omit-if-no-ref/
pcfg_output_low_pull_none: pcfg-output-low-pull-none {
output-low;
bias-disable;
};
};
总之,如果要将 GPIO0_C0设置为 GPIO功能,那么配置就是:
rockchip,pins =<0 RK_PC0 RK_FUNC_GPIO &pcfg_pull_none>;
1.2 设备树中添加pinctrl节点模板
假设要把CPIO_D1这个PIN复用为UART2_TX引脚,节点添加过程如下:
1. 创建对应节点
在 pinctrl
节点下添加一个UART2子节点,在uart2节点里面再创建一个 uart2m0_xfer:uart2m0-xfer
的子节点
&pinctrl{
uart2{
/omit-if-no-ref/
uart2m0_xfer:uart2m0-xfer{
/*** 具体的PIN信息*****/
}
}
}
2. 添加rockchip.pins属性
这个属性是真正用来描述PIN的配置信息,添加UART2的TX引脚,代码如下:
&pinctrl{
uart2{
/omit-if-no-ref/
uart2m0_xfer:uart2m0-xfer{
rockchip,pin=<0 RK_PD 1 & pcfg_pull_up>
}
}
}
按道理来讲,当我们将一个 PIN用作 GPIO功能的时候也需要创建对应的 pinctrl节点,并且将所用的 PIN复用为 GPIO功能,但是!对于 RK3568而言,如果一个 PIN用作 GPIO功能的时候不需要创建对应的 pinctrl节点!
2. gpio子系统
pinctrl子系统重点是设置 PIN(有的 SOC叫做 PAD)的复用和电气属性,如果 pinctrl子系统将一个 PIN复用为 GPIO的话,那么接下来就要用到 gpio子系统了。
gpio子系统顾名思义,就是用于初始化 GPIO并且提供相应的 API函数,比如设置 GPIO为输入输出,读取 GPIO的值等。 gpio子系统的主要目的就是方便驱动开发者使用 gpio,驱动开发者在设备树中添加 gpio相关信息,然后就可以在驱动程序中使用 gpio子系统提供的 API函数来操作 GPIO。 Linux内核向驱动开发者屏蔽掉了 GPIO的设置过程,极大的方便了驱动开发者使用 GPIO。
2.1 设备树中的gpio信息
控制器的节点信息,以 GPIO0_C0这个引脚 所在的 GPIO3为例,打开rk3568.dtsi,在里面找到如下所示内容:
gpio0: gpio@fdd60000 {
compatible = "rockchip,gpio-bank";
reg = <0x0 0xfdd60000 0x0 0x100>;
interrupts = <GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&pmucru PCLK_GPIO0>, <&pmucru DBCLK_GPIO0>;
gpio-controller;
#gpio-cells = <2>;
gpio-ranges = <&pinctrl 0 0 32>;
interrupt-controller;
#interrupt-cells = <2>;
};
GPIO0的控制器信息,属于 pincrtl的子节点,绑定文档 Documentation/devicetree/bindings/gpio/gpio.txt
详细 描述了 gpio控制器节点各个属性信息。
gpio.txt内容如下:
Specifying GPIO information for devices
============================================
1) gpios property
-----------------
Nodes that makes use of GPIOs should specify them using one or more
properties, each containing a 'gpio-list':
gpio-list ::= <single-gpio> [gpio-list]
single-gpio ::= <gpio-phandle> <gpio-specifier>
gpio-phandle : phandle to gpio controller node
gpio-specifier : Array of #gpio-cells specifying specific gpio
(controller specific)
GPIO properties should be named "[<name>-]gpios", with <name> being the purpose
of this GPIO for the device. While a non-existent <name> is considered valid
for compatibility reasons (resolving to the "gpios" property), it is not allowed
for new bindings. Also, GPIO properties named "[<name>-]gpio" are valid and old
bindings use it, but are only supported for compatibility reasons and should not
be used for newer bindings since it has been deprecated.
GPIO properties can contain one or more GPIO phandles, but only in exceptional
cases should they contain more than one. If your device uses several GPIOs with
distinct functions, reference each of them under its own property, giving it a
meaningful name. The only case where an array of GPIOs is accepted is when
several GPIOs serve the same function (e.g. a parallel data line).
The exact purpose of each gpios property must be documented in the device tree
binding of the device.
The following example could be used to describe GPIO pins used as device enable
and bit-banged data signals:
gpio1: gpio1 {
gpio-controller
#gpio-cells = <2>;
};
gpio2: gpio2 {
gpio-controller
#gpio-cells = <1>;
};
[...]
enable-gpios = <&gpio2 2>;
data-gpios = <&gpio1 12 0>,
<&gpio1 13 0>,
<&gpio1 14 0>,
<&gpio1 15 0>;
Note that gpio-specifier length is controller dependent. In the
above example, &gpio1 uses 2 cells to specify a gpio, while &gpio2
only uses one.
gpio-specifier may encode: bank, pin position inside the bank,
whether pin is open-drain and whether pin is logically inverted.
Exact meaning of each specifier cell is controller specific, and must
be documented in the device tree binding for the device.
Most controllers are however specifying a generic flag bitfield
in the last cell, so for these, use the macros defined in
include/dt-bindings/gpio/gpio.h whenever possible:
Example of a node using GPIOs:
node {
enable-gpios = <&qe_pio_e 18 GPIO_ACTIVE_HIGH>;
};
GPIO_ACTIVE_HIGH is 0, so in this example gpio-specifier is "18 0" and encodes
GPIO pin number, and GPIO flags as accepted by the "qe_pio_e" gpio-controller.
Optional standard bitfield specifiers for the last cell:
- Bit 0: 0 means active high, 1 means active low
- Bit 1: 0 mean push-pull wiring, see:
https://en.wikipedia.org/wiki/Push-pull_output
1 means single-ended wiring, see:
https://en.wikipedia.org/wiki/Single-ended_triode
- Bit 2: 0 means open-source, 1 means open drain, see:
https://en.wikipedia.org/wiki/Open_collector
- Bit 3: 0 means the output should be maintained during sleep/low-power mode
1 means the output state can be lost during sleep/low-power mode
1.1) GPIO specifier best practices
----------------------------------
A gpio-specifier should contain a flag indicating the GPIO polarity; active-
high or active-low. If it does, the following best practices should be
followed:
The gpio-specifier's polarity flag should represent the physical level at the
GPIO controller that achieves (or represents, for inputs) a logically asserted
value at the device. The exact definition of logically asserted should be
defined by the binding for the device. If the board inverts the signal between
the GPIO controller and the device, then the gpio-specifier will represent the
opposite physical level than the signal at the device's pin.
When the device's signal polarity is configurable, the binding for the
device must either:
a) Define a single static polarity for the signal, with the expectation that
any software using that binding would statically program the device to use
that signal polarity.
The static choice of polarity may be either:
a1) (Preferred) Dictated by a binding-specific DT property.
or:
a2) Defined statically by the DT binding itself.
In particular, the polarity cannot be derived from the gpio-specifier, since
that would prevent the DT from separately representing the two orthogonal
concepts of configurable signal polarity in the device, and possible board-
level signal inversion.
or:
b) Pick a single option for device signal polarity, and document this choice
in the binding. The gpio-specifier should represent the polarity of the signal
(at the GPIO controller) assuming that the device is configured for this
particular signal polarity choice. If software chooses to program the device
to generate or receive a signal of the opposite polarity, software will be
responsible for correctly interpreting (inverting) the GPIO signal at the GPIO
controller.
2) gpio-controller nodes
------------------------
Every GPIO controller node must contain both an empty "gpio-controller"
property, and a #gpio-cells integer property, which indicates the number of
cells in a gpio-specifier.
Some system-on-chips (SoCs) use the concept of GPIO banks. A GPIO bank is an
instance of a hardware IP core on a silicon die, usually exposed to the
programmer as a coherent range of I/O addresses. Usually each such bank is
exposed in the device tree as an individual gpio-controller node, reflecting
the fact that the hardware was synthesized by reusing the same IP block a
few times over.
Optionally, a GPIO controller may have a "ngpios" property. This property
indicates the number of in-use slots of available slots for GPIOs. The
typical example is something like this: the hardware register is 32 bits
wide, but only 18 of the bits have a physical counterpart. The driver is
generally written so that all 32 bits can be used, but the IP block is reused
in a lot of designs, some using all 32 bits, some using 18 and some using
12. In this case, setting "ngpios = <18>;" informs the driver that only the
first 18 GPIOs, at local offset 0 .. 17, are in use.
If these GPIOs do not happen to be the first N GPIOs at offset 0...N-1, an
additional set of tuples is needed to specify which GPIOs are unusable, with
the gpio-reserved-ranges binding. This property indicates the start and size
of the GPIOs that can't be used.
Optionally, a GPIO controller may have a "gpio-line-names" property. This is
an array of strings defining the names of the GPIO lines going out of the
GPIO controller. This name should be the most meaningful producer name
for the system, such as a rail name indicating the usage. Package names
such as pin name are discouraged: such lines have opaque names (since they
are by definition generic purpose) and such names are usually not very
helpful. For example "MMC-CD", "Red LED Vdd" and "ethernet reset" are
reasonable line names as they describe what the line is used for. "GPIO0"
is not a good name to give to a GPIO line. Placeholders are discouraged:
rather use the "" (blank string) if the use of the GPIO line is undefined
in your design. The names are assigned starting from line offset 0 from
left to right from the passed array. An incomplete array (where the number
of passed named are less than ngpios) will still be used up until the last
provided valid line index.
Example:
gpio-controller@00000000 {
compatible = "foo";
reg = <0x00000000 0x1000>;
gpio-controller;
#gpio-cells = <2>;
ngpios = <18>;
gpio-reserved-ranges = <0 4>, <12 2>;
gpio-line-names = "MMC-CD", "MMC-WP", "VDD eth", "RST eth", "LED R",
"LED G", "LED B", "Col A", "Col B", "Col C", "Col D",
"Row A", "Row B", "Row C", "Row D", "NMI button",
"poweroff", "reset";
}
The GPIO chip may contain GPIO hog definitions. GPIO hogging is a mechanism
providing automatic GPIO request and configuration as part of the
gpio-controller's driver probe function.
Each GPIO hog definition is represented as a child node of the GPIO controller.
Required properties:
- gpio-hog: A property specifying that this child node represents a GPIO hog.
- gpios: Store the GPIO information (id, flags, ...) for each GPIO to
affect. Shall contain an integer multiple of the number of cells
specified in its parent node (GPIO controller node).
Only one of the following properties scanned in the order shown below.
This means that when multiple properties are present they will be searched
in the order presented below and the first match is taken as the intended
configuration.
- input: A property specifying to set the GPIO direction as input.
- output-low A property specifying to set the GPIO direction as output with
the value low.
- output-high A property specifying to set the GPIO direction as output with
the value high.
Optional properties:
- line-name: The GPIO label name. If not present the node name is used.
Example of two SOC GPIO banks defined as gpio-controller nodes:
qe_pio_a: gpio-controller@1400 {
compatible = "fsl,qe-pario-bank-a", "fsl,qe-pario-bank";
reg = <0x1400 0x18>;
gpio-controller;
#gpio-cells = <2>;
line_b {
gpio-hog;
gpios = <6 0>;
output-low;
line-name = "foo-bar-gpio";
};
};
qe_pio_e: gpio-controller@1460 {
compatible = "fsl,qe-pario-bank-e", "fsl,qe-pario-bank";
reg = <0x1460 0x18>;
gpio-controller;
#gpio-cells = <2>;
};
2.1) gpio- and pin-controller interaction
-----------------------------------------
Some or all of the GPIOs provided by a GPIO controller may be routed to pins
on the package via a pin controller. This allows muxing those pins between
GPIO and other functions.
It is useful to represent which GPIOs correspond to which pins on which pin
controllers. The gpio-ranges property described below represents this, and
contains information structures as follows:
gpio-range-list ::= <single-gpio-range> [gpio-range-list]
single-gpio-range ::= <numeric-gpio-range> | <named-gpio-range>
numeric-gpio-range ::=
<pinctrl-phandle> <gpio-base> <pinctrl-base> <count>
named-gpio-range ::= <pinctrl-phandle> <gpio-base> '<0 0>'
pinctrl-phandle : phandle to pin controller node
gpio-base : Base GPIO ID in the GPIO controller
pinctrl-base : Base pinctrl pin ID in the pin controller
count : The number of GPIOs/pins in this range
The "pin controller node" mentioned above must conform to the bindings
described in ../pinctrl/pinctrl-bindings.txt.
In case named gpio ranges are used (ranges with both <pinctrl-base> and
<count> set to 0), the property gpio-ranges-group-names contains one string
for every single-gpio-range in gpio-ranges:
gpiorange-names-list ::= <gpiorange-name> [gpiorange-names-list]
gpiorange-name : Name of the pingroup associated to the GPIO range in
the respective pin controller.
Elements of gpiorange-names-list corresponding to numeric ranges contain
the empty string. Elements of gpiorange-names-list corresponding to named
ranges contain the name of a pin group defined in the respective pin
controller. The number of pins/GPIOs in the range is the number of pins in
that pin group.
Previous versions of this binding required all pin controller nodes that
were referenced by any gpio-ranges property to contain a property named
#gpio-range-cells with value <3>. This requirement is now deprecated.
However, that property may still exist in older device trees for
compatibility reasons, and would still be required even in new device
trees that need to be compatible with older software.
Example 1:
qe_pio_e: gpio-controller@1460 {
#gpio-cells = <2>;
compatible = "fsl,qe-pario-bank-e", "fsl,qe-pario-bank";
reg = <0x1460 0x18>;
gpio-controller;
gpio-ranges = <&pinctrl1 0 20 10>, <&pinctrl2 10 50 20>;
};
Here, a single GPIO controller has GPIOs 0..9 routed to pin controller
pinctrl1's pins 20..29, and GPIOs 10..29 routed to pin controller pinctrl2's
pins 50..69.
Example 2:
gpio_pio_i: gpio-controller@14b0 {
#gpio-cells = <2>;
compatible = "fsl,qe-pario-bank-e", "fsl,qe-pario-bank";
reg = <0x1480 0x18>;
gpio-controller;
gpio-ranges = <&pinctrl1 0 20 10>,
<&pinctrl2 10 0 0>,
<&pinctrl1 15 0 10>,
<&pinctrl2 25 0 0>;
gpio-ranges-group-names = "",
"foo",
"",
"bar";
};
Here, three GPIO ranges are defined wrt. two pin controllers. pinctrl1 GPIO
ranges are defined using pin numbers whereas the GPIO ranges wrt. pinctrl2
are named "foo" and "bar".
compatible
属性值为“ rockchip,gpio-bank”,所以在 linux内核中搜索这个字符串就可以找到对应的 GPIO驱动源文件,为 drivers/pinctrl/pinctrl-rockchip.c
。
reg
属性设置了 GPIO0控制器的寄存器基地址为 0XFDD60000
。
interrupts
属性描述 GPIO0控制器对应的中断信息。
clock
s属性指定这个 GPIO0控制器的时钟。
gpio-controller
表示 gpio0节点是个 GPIO控制器,每个 GPIO控制器节点必
须包含 gpio-controller
属性。
#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)的话表示低电平有效。
控制器节点,当某个具体的引脚作为 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接口上的 设备,如下所示:
imx415: imx415@1a {
status = "okay";
compatible = "sony,imx415";
reg = <0x1a>;
clocks = <&cru CLK_CIF_OUT>;
clock-names = "xvclk";
power-domains = <&power RK3568_PD_VI>;
pinctrl-names = "rockchip,camera_default";
pinctrl-0 = <&cif_clk>;
reset-gpios = <&gpio3 RK_PB6 GPIO_ACTIVE_LOW>;
power-gpios = <&gpio4 RK_PB4 GPIO_ACTIVE_HIGH>;
rockchip,camera-module-index = <0>;
rockchip,camera-module-facing = "back";
rockchip,camera-module-name = "CMK-OT1522-FG3";
rockchip,camera-module-lens-name = "CS-P1150-IRC-8M-FAU";
port {
imx415_out: endpoint {
remote-endpoint = <&mipi_in_ucam1>;
data-lanes = <1 2 3 4>;
};
};
};
reset-gpios = <&gpio3 RK_PB6 GPIO_ACTIVE_LOW>;
描述了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,内容如下
enum gpio_lookup_flags {
GPIO_ACTIVE_HIGH = (0 << 0),
GPIO_ACTIVE_LOW = (1 << 0),
GPIO_OPEN_DRAIN = (1 << 1),
GPIO_OPEN_SOURCE = (1 << 2),
GPIO_PERSISTENT = (0 << 3),
GPIO_TRANSITORY = (1 << 3),
};
2.2 gpio子系统API函数
1. 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
此函数用于设置某个 GPIO为输入,函数原型如下所示:
int gpio_direction_input(unsigned gpio)
4. gpio_direction_output
此函数用于设置某个 GPIO为输出,并且设置默认输出值,函数原型如下:
int gpio_direction_output(unsigned gpio, int value)
5. gpio_get_value
此函数用于获取某个 GPIO的值 (0或 1)函数原型如下
int gpio_get_value(unsigned int gpio)
6. gpio_set_value
此函数用于设置某个 GPIO的值,
void gpio_set_value(unsigned int gpio, int value)
2.3 设备树中添加GPIO节点模板
以LED为例,GIO0_C3引脚
在根节点“ /”下创建 led设备子节点,如下所示
gpioled{
compatible="alientek,led";
led-gpio=<&gpio0 RK_PC0 GPIO_ACTIVE_HIGH>;
status="okey"
}
2.4 与gpio相关的OF函数
1. of_gpio_named_count
函数用于获取设备树某个属性里面定义了几个 GPIO信息,要注意的是空的 GPIO信 息也会被统计到。
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编号;负值,失败。
3. 硬件原理
连接如图所示:
4. 检查程序编写
4.1 检查IO是否已经被使用
首先我们要检查一下 GPIO0_C0对应的 GPIO有没有被其他外设使用,如果这个 GPIO已经被分配给了其他外设,那么我们驱动在申请这个 GPIO就会失败,如图
就是 GPIO申请失败提示,因为当前开发板系统将 GPIO0_C0这个 IO分配 给 了
内核自带的 LED驱动做心跳灯了。 所以需要先关闭 GPIO0_C0作为心跳灯这个功能,也就是将GPIO0_C0对应的 GPIO释放出来。打开 rk3568-evb.dtsi文件,找到如下代码:
leds: leds {
compatible = "gpio-leds";
work_led: work {
gpios = <&gpio0 RK_PC0 GPIO_ACTIVE_HIGH>;
linux,default-trigger = "heartbeat";
};
};
中可以看出,正点原子出厂系统默认将 GPIO0_C0用作心跳灯了,所以系统在
启动的时候就先将 GPIO0_C0给了心跳灯,我们后面再申请肯定就会失败!
解决方法就是关闭心跳灯功能,也就是在以上代码中添加 status改为 disabled也就是禁止 work这个节点,那么禁止心跳灯功能。这样系统启动的时候就不会将 GPIO0_C0分配给心跳灯,我们后面也就可以申请了。
leds: leds {
compatible = "gpio-leds";
work_led: work {
gpios = <&gpio0 RK_PC0 GPIO_ACTIVE_HIGH>;
linux,default-trigger = "heartbeat";
status="disabled"
};
};
4.2 修改设备树文件
在 rk3568-atk-evb1-ddr4-v10.dtsi文件的根节点“ /”下创建 LED灯节点,节点名 为 gpioled:
gpioled{
compatible="alientek,led";
led-gpio=<&gpio0 RK_PC0 GPIO_ACTIVE_HIGH>;
status="okey"
}
设备树写完后使用 ./build.sh kernel
命令重新编译并打包内核,将新编译的boot.img烧写到开发板中。
启动设备后进入 /proc/device-tree
中查看 gpio
节点是否存在。
如下图所示
4.3 驱动程序编写
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
//#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名 : gpioled.c
作者 : 正点原子
版本 : V1.0
描述 : LED驱动文件。
其他 : 无
论坛 : www.openedv.com
日志 : 初版V1.0 2022/12/07 正点原子团队创建
***************************************************************/
#define GPIOLED_CNT 1 /* 设备号个数 */
#define GPIOLED_NAME "gpioled" /* 名字 */
#define LEDOFF 0 /* 关灯 */
#define LEDON 1 /* 开灯 */
/* gpioled设备结构体 */
struct gpioled_dev{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
struct device_node *nd; /* 设备节点 */
int led_gpio; /* led所使用的GPIO编号 */
};
struct gpioled_dev gpioled; /* led设备 */
/*
* @description : 打开设备
* @param – inode : 传递给驱动的inode
* @param - filp : 设备文件,file结构体有个叫做private_data的成员变量
* 一般在open的时候将private_data指向设备结构体。
* @return : 0 成功;其他 失败
*/
static int led_open(struct inode *inode, struct file *filp)
{
filp->private_data = &gpioled; /* 设置私有数据 */
return 0;
}
/*
* @description : 从设备读取数据
* @param - filp : 要打开的设备文件(文件描述符)
* @param - buf : 返回给用户空间的数据缓冲区
* @param - cnt : 要读取的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 读取的字节数,如果为负值,表示读取失败
*/
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
return 0;
}
/*
* @description : 向设备写数据
* @param - filp : 设备文件,表示打开的文件描述符
* @param - buf : 要写给设备写入的数据
* @param - cnt : 要写入的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 写入的字节数,如果为负值,表示写入失败
*/
static ssize_t led_write(struct file *filp, const char __user *buf,
size_t cnt, loff_t *offt)
{
int retvalue;
unsigned char databuf[1];
unsigned char ledstat;
struct gpioled_dev *dev = filp->private_data;
retvalue = copy_from_user(databuf, buf, cnt);
if(retvalue < 0) {
printk("kernel write failed!\r\n");
return -EFAULT;
}
ledstat = databuf[0]; /* 获取状态值 */
if(ledstat == LEDON) {
gpio_set_value(dev->led_gpio, 1); /* 打开LED灯 */
} else if(ledstat == LEDOFF) {
gpio_set_value(dev->led_gpio, 0); /* 关闭LED灯 */
}
return 0;
}
/*
* @description : 关闭/释放设备
* @param - filp : 要关闭的设备文件(文件描述符)
* @return : 0 成功;其他 失败
*/
static int led_release(struct inode *inode, struct file *filp)
{
return 0;
}
/* 设备操作函数 */
static struct file_operations gpioled_fops = {
.owner = THIS_MODULE,
.open = led_open,
.read = led_read,
.write = led_write,
.release = led_release,
};
/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static int __init led_init(void)
{
int ret = 0;
const char *str;
/* 设置LED所使用的GPIO */
/* 1、获取设备节点:gpioled */
gpioled.nd = of_find_node_by_path("/gpioled");
if(gpioled.nd == NULL) {
printk("gpioled node not find!\r\n");
return -EINVAL;
}
/* 2.读取status属性 */
ret = of_property_read_string(gpioled.nd, "status", &str);
if(ret < 0)
return -EINVAL;
if (strcmp(str, "okay"))
return -EINVAL;
/* 3、获取compatible属性值并进行匹配 */
ret = of_property_read_string(gpioled.nd, "compatible", &str);
if(ret < 0) {
printk("gpioled: Failed to get compatible property\n");
return -EINVAL;
}
if (strcmp(str, "alientek,led")) {
printk("gpioled: Compatible match failed\n");
return -EINVAL;
}
/* 4、 获取设备树中的gpio属性,得到LED所使用的LED编号 */
gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);
if(gpioled.led_gpio < 0) {
printk("can't get led-gpio");
return -EINVAL;
}
printk("led-gpio num = %d\r\n", gpioled.led_gpio);
/* 5.向gpio子系统申请使用GPIO */
ret = gpio_request(gpioled.led_gpio, "LED-GPIO");
if (ret) {
printk(KERN_ERR "gpioled: Failed to request led-gpio\n");
return ret;
}
/* 6、设置GPIO为输出,并且输出低电平,默认关闭LED灯 */
ret = gpio_direction_output(gpioled.led_gpio, 0);
if(ret < 0) {
printk("can't set gpio!\r\n");
}
/* 注册字符设备驱动 */
/* 1、创建设备号 */
if (gpioled.major) { /* 定义了设备号 */
gpioled.devid = MKDEV(gpioled.major, 0);
ret = register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);
if(ret < 0) {
pr_err("cannot register %s char driver [ret=%d]\n", GPIOLED_NAME, GPIOLED_CNT);
goto free_gpio;
}
} else { /* 没有定义设备号 */
ret = alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME); /* 申请设备号 */
if(ret < 0) {
pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n", GPIOLED_NAME, ret);
goto free_gpio;
}
gpioled.major = MAJOR(gpioled.devid); /* 获取分配号的主设备号 */
gpioled.minor = MINOR(gpioled.devid); /* 获取分配号的次设备号 */
}
printk("gpioled major=%d,minor=%d\r\n",gpioled.major, gpioled.minor);
/* 2、初始化cdev */
gpioled.cdev.owner = THIS_MODULE;
cdev_init(&gpioled.cdev, &gpioled_fops);
/* 3、添加一个cdev */
cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);
if(ret < 0)
goto del_unregister;
/* 4、创建类 */
gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
if (IS_ERR(gpioled.class)) {
goto del_cdev;
}
/* 5、创建设备 */
gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);
if (IS_ERR(gpioled.device)) {
goto destroy_class;
}
return 0;
destroy_class:
class_destroy(gpioled.class);
del_cdev:
cdev_del(&gpioled.cdev);
del_unregister:
unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);
free_gpio:
gpio_free(gpioled.led_gpio);
return -EIO;
}
/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static void __exit led_exit(void)
{
/* 注销字符设备驱动 */
cdev_del(&gpioled.cdev);/* 删除cdev */
unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);
device_destroy(gpioled.class, gpioled.devid);/* 注销设备 */
class_destroy(gpioled.class);/* 注销类 */
gpio_free(gpioled.led_gpio); /* 释放GPIO */
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ALIENTEK");
MODULE_INFO(intree, "Y");
4.4 测试
略