LV040-gpio与pinctrl实例
这里我是看的 rk3568 的教程学习的,这里就以 rk3568 为例,虽然芯片不同,但是设备树的语法都是一样的。
一、GPIO
1. gpio 相关属性
下面的是 iTOP-RK3568 开发板 SDK 源码中的 ft5x06 设备树中关于 gpio 相关的描述, 包括了 interrupts、 interrupt-controller、 #interrupt-cells、 interrupt-parent 四种常见属性:
gpio0: gpio@fdd60000 {
compatible = "rockchip,gpio-bank";
reg = <0x0 0xfdd60000 0x0 0x100>;
interrupts = <GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&pmucru PCLK_GPI00>, <&pmucru DBCLK_GPI00>;
gpio-controller;
#gpio-cells = <2>;
gpio-ranges = <&pinctrl 0 0 32>;
interrupt-controller;
#interrupt-cells = <2>;
};
ft5x06: ft5x06@38 {
status = "disabled";
compatible = "edt,edt-ft5306";
reg = <0x38>;
touch-gpio = <&gpio0 RK_PB5 IRQ_TYPE_EDGE_RISING>;
interrupt-parent = <&gpio0>;
interrupts = <RK_PB5 IRQ_TYPE_LEVEL_LOW>;
reset-gpios = <&gpio0 RK_PB6 GPIO_ACTIVE_LOW>;
touchscreen-size-x = <800>;
touchscreen-size-y = <1280>;
touch_type = <1>;
};1.1 gpio-controller
gpio-controller 属性用于标识一个设备节点作为 GPIO 控制器。 GPIO 控制器是负责管理和控制 GPIO 引脚的硬件模块或驱动程序。通常作为设备节点的一个属性出现, 位于设备节点的属性列表中。
当一个设备节点被标识为 GPIO 控制器时, 它通常会定义一组 GPIO 引脚, 并提供相关的 GPIO 控制和配置功能。 其他设备节点可以使用该 GPIO 控制器来控制和管理其 GPIO 引脚。
通过使用 gpio-controller 属性, 设备树可以明确标识出 GPIO 控制器设备节点, 使系统可以正确识别和管理 GPIO 引脚的配置和控制。
1.2 #gpio-cells
#gpio-cells 属性用于指定 GPIO 引脚描述符的编码方式。 GPIO 引脚描述符是用于标识和配置 GPIO 引脚的一组值, 例如引脚编号、 引脚属性等。这个属性的属性值是一个整数, 表示用于编码 GPIO 引脚描述符的单元数。 通常,这个值为 2。
在示例中有 1 个 gpio 引脚描述属性, 由于#gpio-cells 属性被设置为了 2, 所以每个引脚描述属性中会有两个整数, 具体内容如下所示:
ft5x06: ft5x06@38 {
//.....
reset-gpios = <&gpio0 RK_PB6 GPIO_ACTIVE_LOW>;
//.....
};通过使用#gpio-cells 属性, 设备树可以指定 GPIO 引脚描述符的编码方式, 使系统能够正确识别和解析 GPIO 引脚的配置和控制。
1.3 gpio-ranges
gpio-ranges 属性是设备树中一个用于描述 GPIO 范围映射的属性。 它通常用于描述具有大量 GPIO 引脚的 GPIO 控制器, 以简化 GPIO 引脚的编码和访问。
在设备树中, GPIO 控制器的每个引脚都有一个本地编号, 用于在控制器内部进行引脚寻址。 然而, 这些本地编号并不一定与外部引脚的物理编号或其他系统中使用的编号一致。 为了解决这个问题, 可以使用 gpio-ranges 属性将本地编号映射到实际的引脚编号。
gpio-ranges 属性是一个包含一系列整数值的列表, 每个整数值对应于设备树中的一个 GPIO 控制器。 列表中的每个整数值按照特定的顺序提供以下信息:
(1) 外部引脚编号的起始值。
(2) GPIO 控制器内部本地编号的起始值。
(3) 引脚范围的大小(引脚数量) 。
gpio0: gpio@fdd60000 {
//......
gpio-controller;
#gpio-cells = <2>;
gpio-ranges = <&pinctrl 0 0 32>;
//......
};gpio-ranges 属性的值为 <&pinctrl 0 0 32 >, 其中 <&pinctrl > 表示引用了名为 pinctrl 的引脚控制器节点, 0 0 32 表示外部引脚从 0 开始, 控制器本地编号从 0 开始, 共映射了 32 个引脚。
这样, gpio-ranges 属性将 GPIO 控制器的本地编号直接映射到外部引脚编号, 使得 GPIO 引脚的编码和访问更加简洁和直观。
1.4 gpio 引脚描述属性
示例中设备树中关于 gpio 引脚描述属性相关内容如下所示:
gpio0: gpio@fdd60000 {
//......
#gpio-cells = <2>;
//......
};
ft5x06: ft5x06@38 {
//.....
reset-gpios = <&gpio0 RK_PB6 GPIO_ACTIVE_LOW>;
//.....
};gpio 引脚描述属性个数由#gpio-cells 所决定, 因为 gpio0 节点中的#gpio-cells 属性设置为了 2, 所以上面设备树 gpio 引脚描述属性个数也为 2。 RK_PB6 是引脚名,GPIO_ACTIVE_LOW 表示设置为低电平。
1.5 其他属性
根据下面的设备树示例学习一下 gpio 的其他重要属性, 设备树具体内容如下所示:
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",
"voD eth", "RST eth", "LED R",
"LED G", "LED B", "col A",
"col B", "col C", "col D",
"NMI button", "Row A", "Row B",
"Row C", "Row D", "poweroff",
"reset";
};第 6 行的 ngpios 属性指定了 GPIO 控制器所支持的 GPIO 引脚数量。 它表示该设备上可用的 GPIO 引脚的总数。 在这个例子中, ngpios 的值为 18, 意味着该 GPIO 控制器支持 18 个 GPIO 引脚。
第 7 行的 gpio-reserved-ranges 属性定义了保留的 GPIO 范围。每个范围由两个整数值表示,用尖括号括起来。 保留的 GPIO 范围意味着这些 GPIO 引脚不可用或已被其他设备或功能保留。在这个例子中, 有两个保留范围: <0 4> 和 <12 2>。 <0 4> 表示从第 0 个引脚开始的连续 4 个引脚被保留, 而 <12 2> 表示从第 12 个引脚开始的连续 2 个引脚被保留。
第 8 行的 gpio-line-names 属性定义了 GPIO 引脚的名称, 以逗号分隔。 每个名称对应一个 GPIO 引脚。 这些名称用于标识和识别每个 GPIO 引脚的作用或连接的设备。 在这个例子中, gpio-line-names 属性列出了多个 GPIO 引脚的名称, 如 "MMC-CD"、 "MMC-WP"、 "voD eth" 等等。 通过这些名称, 可以清楚地了解每个 GPIO 引脚的功能或用途。
2. GPIO 实例
2.1 硬件原理图

查看原理图会发现 LED0 接到了 GPIO_3 上, 这里的 GPIO_3 就是 GPIO1_IO03,当 GPIO1_IO03 输出低电平(0)的时候发光二极管 LED0 就会导通点亮,当 GPIO1_IO03 输出高电平(1)的时候发光二极管 LED0 不会导通,因此 LED0 也就不会点亮。所以 LED0 的亮灭取决于 GPIO1_IO03 的输出电平,输出 0 就亮,输出 1 就灭。
2.2 led 灯节点
sdev_led {
#address-cells = <1>;
#size-cells = <1>;
compatible = "sdev-led";
status = "okay";
reg = < 0X020C406C 0X04 /* CCM_CCGR1_BASE */
0X020E0068 0X04 /* SW_MUX_GPIO1_IO03_BASE */
0X020E02F4 0X04 /* SW_PAD_GPIO1_IO03_BASE */
0X0209C000 0X04 /* GPIO1_DR_BASE */
0X0209C004 0X04 >; /* GPIO1_GDIR_BASE */
};第 2、 3 行,属性#address-cells 和#size-cells 都为 1,表示 reg 属性中起始地址占用一个字长(cell),地址长度也占用一个字长(cell)。
第 4 行,属性 compatbile 设置 alphaled 节点兼容性为“sdev-led”。
第 5 行,属性 status 设置状态为“okay”。
第 6 ~ 10 行, reg 属性设置了驱动里面所要使用的寄存器物理地址,比如第 6 行的“0X020C406C 0X04”表示 I.MX6ULL 的 CCM_CCGR1 寄存器,其中寄存器首地址为 0X020C406C,长度为 4 个字节。
二、pinctrl
1. pinmux 简介
1.1 什么是 pinmux
Pinmux( 引脚复用) 是指在系统中配置和管理引脚功能的过程。 在许多现代集成电路中,单个引脚可以具有多个功能, 例如作为 GPIO、 UART、 SPI 或 I2C 等。 通过使用引脚复用功能,可以在这些不同的功能之间切换。
引脚复用通过硬件和软件的方式实现。 硬件层面, 芯片设计会为每个引脚提供多个功能的选择。 这些功能通常由芯片厂商在芯片规格文档中定义。 通过编程设置寄存器或开关, 可以选择某个功能来连接引脚。 这种硬件层面的配置通常是由引脚控制器(Pin Controller) 或引脚复用控制器( Pin Mux Controller) 负责管理。
软件层面, 操作系统或设备驱动程序需要了解和配置引脚的功能。 它们使用设备树( Device Tree) 或设备树绑定( Device Tree Bindings) 来描述和配置引脚的功能。 在设备树中, 可以指定引脚的复用功能, 将其连接到特定的硬件接口或功能。 操作系统或设备驱动程序在启动过程中解析设备树, 并根据配置对引脚进行初始化和设置。
1.2 imx6ull 的引脚复用
那我们要怎样知晓每一个管脚都可以复用成什么功能呢,对于我使用的 alpha-imx6ull 开发板,我们可以直接查看《i.MX 6ULL Applications Processor Reference Manual.pdf》手册的 32.6 IOMUXC Memory Map/Register Definition 。例如前面按键是接在 GPIO1_IO18 上,这个引脚默认是 UART1_CTS_B,这个引脚复用情况如下:

1.3 RK3568 的引脚复用
有一些厂家给的原理图甚至会在核心板原理图标注出每个管脚的复用功能,例如讯为的 RK3568 开发板:

从上图可以看到 UART4_RX_M1 对应的引脚可以复用为以下 6 个功能 LCDC_D16、VOP_BT1120_D7、 GMAC1_RXD0_M0、 UART4_RX_M1、 PWM8_M0、 GPIO3_B1_d, 那么对于 RK3568 来说,对应的 BGA 引脚标号为 AG1, 那这里的 AG1 是如何定位的呢。
在 BGA(Ball Grid Array, 球栅阵列) 封装中, 引脚标号是用于唯一标识每个引脚的标识符。这些标号通常由芯片制造商定义, 并在芯片的规格文档或数据手册中提供。
BGA 芯片的引脚标号通常由字母和数字的组合构成。 它们用于在芯片的封装底部的焊盘上进行标记。 每个引脚标号都与芯片内部的功能或信号相对应, 以便正确连接到印刷电路板( PCB) 上的目标位置。 RK3568 的引脚标号图如下:

可以看到纵向为 A-AH 的 28 个字母类型标号, 横向为 1-28 的 28 个字母类型标号, 瑞芯微也在对应的 3568 数据手册中加入了根据 BGA 位置制作的复用功能图, 部分内容如下图

其中黑色框代表被保留的引脚, 其他有颜色的框一般为电源和地, 白色的框代表有具体复用功能的引脚。
1.4 总结
引脚复用提高了芯片的灵活性和可重用性, 通过允许同一个引脚在不同的功能之间切换, 可以减少硬件设计的复杂性和成本。 此外, 引脚复用还使得在使用相同芯片的不同应用中可以更加灵活地配置和定制引脚功能。
2. RK3568 中使用 pinctrl 设置复用关系
pinctrl(引脚控制) 用于描述和配置硬件设备上的引脚功能和连接方式。 它是设备树的一部分, 用于在启动过程中传递引脚配置信息给操作系统和设备驱动程序, 以便正确地初始化和控制引脚。
在设备树中, pinctrl(引脚控制) 使用了客户端和服务端的概念来描述引脚控制的关系和配置。
2.1 客户端(client)
接下来将使用三个例子对客户端要用到的属性进行说明。
2.1.1 示例 1
node {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_hog_1>;
}pinctrl-names 属性定义了一个状态名称: default。pinctrl-0 属性指定了第一个状态 default 对应的引脚配置。<&pinctrl_hog_1 > 是一个引脚描述符, 它引用了一个名为 pinctrl_hog_1 的引脚控制器节点。 这表示在 default 状态下, 设备的引脚配置将使用 pinctrl_hog_1 节点中定义的配置。
2.1.2 示例 2
node {
pinctrl-names = "default", "wake up";
pinctrl-0 = <&pinctrl_hog_1>;
pinctrl-1 = <&pinctrl_hog_2>;
}pinctrl-names 属性定义了两个状态名称: default 和 wake up。pinctrl-0 属性指定了第一个状态 default 对应的引脚配置, 引用了 pinctrl_hog_1 节点。pinctrl-1 属性指定了第二个状态 wake up 对应的引脚配置, 引用了 pinctrl_hog_2 节点。
这意味着设备可以处于两个不同的状态之一, 每个状态分别使用不同的引脚配置。
2.1.3 示例 3
node {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_hog_1 &pinctrl_hog_2>;
}这个例子中, pinctrl-names 属性仍然定义了一个状态名称: default。pinctrl-0 属性指定了第一个状态 default 对应的引脚配置, 但与之前的例子不同的是, 它引用了两个引脚描述符: pinctrl_hog_1 和 pinctrl_hog_2。
这表示在 default 状态下, 设备的引脚配置将使用 pinctrl_hog_1 和 pinctrl_hog_2 两个节点中定义的配置。 这种方式可以将多个引脚控制器的配置组合在一起, 以满足特定状态下的引脚需求。
2.1.4 小结
关于客户端的内容, 不同厂家的编写格式是相同的,而服务端每个厂家就有区别了。
2.2 服务端(server)
服务端是设备树中定义引脚配置的部分。 它包含引脚组和引脚描述符, 为客户端提供引脚配置选择。 服务端在设备树中定义了 pinctrl 节点, 其中包含引脚组和引脚描述符的定义。
这里以瑞芯微的 RK3568 为例进行 pinctrl 服务端的学习, 瑞芯微原厂 BSP 工程师为了方便用户通过 pinctrl 设置管脚的复用关系, 将包含所有复用关系的配置写在了内核(5.19.17 版本,主要是因为有些版本还没加入 RK 系列的芯片)目录下的 rk3568-pinctrl.dtsi - arch/arm64/boot/dts/rockchip/rk3568-pinctrl.dtsi - Linux source code v5.19.17 中。我们来看一下这个 pinctrl 节点:
&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>;
};
};
};在 pinctrl 节点中就是每个节点的复用功能, 然后我们以 uart4 的引脚复用为例进行学习,uart4 的 pinctrl 服务端内容如下:
uart4 {
//......
/omit-if-no-ref/
uart4m1_xfer: uart4m1-xfer {
rockchip,pins =
/* uart4_rxm1 */
<3 RK_PB1 4 &pcfg_pull_up>,
/* uart4_txm1 */
<3 RK_PB2 4 &pcfg_pull_up>;
};
};其中 < 3 RK_PB1 4 &pcfg_pull_up > 和 < 3 RK_PB2 4 &pcfg_pull_up > 分别表示将 GPIO3 的 PB1 引脚设置为功能 4, 将 GPIO3 的 PB2 也设置为功能 4, 且电气属性都会设置为上拉。
通过查找原理图可以得到两个引脚在 BGA 封装位置分别为 AG1 和 AF2, 如下图 :

然后在 rk3568 的数据手册中找到引脚复用表对应的位置, 具体内容如下图所示


可以看到功能 4 对应串口 4 的发送端和接收端, pinctrl 服务端的配置和数据手册中的引脚复用功能是一一对应, 那如果要将 RK_PB1 和 RK_PB2 设置为 GPIO 功能要如何设置呢,从上图可以看到 GPIO 对应功能 0, 所以可以通过以下 pinctrl 内容将设置 RK_PB1 和 RK_PB2 设置为 GPIO 功能(事实上如果不对该管脚进行功能复用该引脚默认就会设置为 GPIO 功能) :
<3 RK_PB1 0 &pcfg_pull_up>,
<3 RK_PB2 0 &pcfg_pull_up>;最后来看客户端对 uart4 服务端的引用,可能会这样写:
&uart4 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&uart4m1_xfer>;
};通过在客户端中引用服务端的引脚描述符, 设备树可以将客户端和服务端的引脚配置关联起来。 这样, 在设备树被解析和处理时, 操作系统和设备驱动程序可以根据客户端的需求, 查找并应用适当的引脚配置。
3. RK3568 开发板的一个实例
说明:我没有这个开发板,这里是学习教程的时候直接复制过来作为参考笔记,后面在 imx6ull 开发板中是会再学习 pinctrl 子系统,这里也能帮助理解。
通过上面学到的 pinctrl 相关知识, 将 led 的控制引脚复用为 GPIO 模式。首先来对 rk3568 的设备树结构进行以下介绍, 根据 sdk 源码目录下的“device/rockchip/rk356x/BoardConfig-rk3568-evb1-ddr4-v10.mk” 默认配置文件可以了解到编译的设备树为 rk3568 -evb1-ddr4-v10-linux.dts, 整理好的设备树之间包含关系列表如下所示:
| 顶层设备树 | rk3568-evb1-ddr4-v10-linux.dts | |
| 第二级设备树 | rk3568-evb1-ddr4-v10.dtsi | rk3568-linux.dtsi |
| 第三级设备树 | rk3568.dtsi | |
| rk3568-evb.dtsi | ||
| topeet_screen_choose.dtsi | ||
| topeet_rk3568_lcds.dtsi | ||

这里也并没有配置 pinctrl 呀, 那为什么 led 最后能正常使用呢, 这个原因之前其实已经提到了, 当一个引脚没有被复用为任何功能时, 默认就是 GPIO 功能, 所以这里没有 pinctrl led 功能也可以正常使用。

保存退出之后, 然后进入到 rk3568-evb1-ddr4-v10.dtsi 设备树中, 找到 rk_485_ctl 节点, 如下图 :

这是根节点的最后一个节点, 而且也是用来控制一个 GPIO 的, 我们完全可以仿照该节点,在该节点下方编写 led 控制节点, 仿写完成的设备树内容如下所示 :
my_led: led {
compatible = "topeet,led";
gpios = <&gpio0 RK_PB7 GPIO_ACTIVE_HIGH>;
pinctrl-names = "default";
pinctrl-0 = <&rk_led_gpio>;
};第 1 行: 节点名称为 led, 标签名为 my_led。
第 2 行: compatible 属性指定了设备的兼容性标识, 即设备与驱动程序之间的匹配规则。在这里, 设备标识为 "topeet, led", 表示该 LED 设备与名为 "topeet, led" 的驱动程序兼容。
第 3 行: gpios 属性指定了与 LED 相关的 GPIO(通用输入/输出) 引脚配置。
第 4 行: pinctrl-names 属性指定了与引脚控制相关的命名。 default 表示状态 0
第 5 行: pinctrl-0 属性指定了与 pinctrl-names 属性中命名的引脚控制相关联的实际引脚控制器配置。 <&rk_led_gpio > 表示引用了名为 rk_led_gpio 的引脚控制器配置。
添加完成如下图:

然后继续找到在同一设备树文件的 485 pinctrl 服务端节点, 具体内容如下 :

然后在该节点下方仿写 led 控制引脚 pinctrl 服务端节点, 仿写完成的节点内容如下所示:
rk_led{
rk_led_gpio:rk-led-gpio {
rockchip,pins = <0 RK_PB7 RK_FUNC_GPIO &pcfg_pull_none>;
};
};添加完成之后如下图:

至此, led 的控制引脚就通过 pinctrl 被复用为了 GPIO 功能, 保存退出之后, 重新编译内核,没有报错就证明我们的实例完成了。