pinctrl子系统
pinctrl 子系统主要用于管理芯片的引脚。
iomuxc节点介绍
首先我们在/ebf-buster-linux/arch/arm/boot/dts/imx6ull.dtsi 文件中查找 iomuxc 节点,可以看到如下定义
iomuxc: iomuxc@20e0000 {
compatible = "fsl,imx6ul-iomuxc";
reg = <0x20e0000 0x4000>;
};
• compatible: 修饰的是与平台驱动做匹配的名字, 这里则是与 pinctrl 子系统的平台驱动做匹配。
• reg: 表示的是引脚配置寄存器的基地址。
我们的设备树主要的配置文件在./arch/arm/boot/dts/imx6ull-seeed-npi.dts 中,打开 imx6ull-seeed-npi.dts,在文件中搜索“&iomuxc”找到设备树中引用“iomuxc”节点的位置如下所示。
&iomuxc {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_hog_1>;
pinctrl_gpmi_nand: gpmi-nand {
fsl,pins = <
MX6UL_PAD_NAND_CLE__RAWNAND_CLE 0xb0b1
MX6UL_PAD_NAND_ALE__RAWNAND_ALE 0xb0b1
MX6UL_PAD_NAND_WP_B__RAWNAND_WP_B 0xb0b1
/*------------以下省略-----------------*/
>;
};
pinctrl_hog_1: hoggrp-1 {
fsl,pins = <
MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059 /* SD1 CD */
MX6UL_PAD_GPIO1_IO05__USDHC1_VSELECT 0x17059 /* SD1 VSELECT */
MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0x17059 /* SD1 RESET */
>;
};
pinctrl_uart1: uart1grp {
fsl,pins = <
MX6UL_PAD_UART1_TX_DATA__UART1_DCE_TX 0x1b0b1
MX6UL_PAD_UART1_RX_DATA__UART1_DCE_RX 0x1b0b1
>;
};
}
&iomuxc {
pinctrl-names = "default","sleep","init";
pinctrl-0 = <&pinctrl_uart1>;
pinctrl-1 =<&pinctrl_xxx>;
pinctrl-2 =<&pinctrl_yyy>;
。。。。
pinctrl_uart1: uart1grp {
fsl,pins = <
MX6UL_PAD_UART1_TX_DATA__UART1_DCE_TX 0x1b0b1
MX6UL_PAD_UART1_RX_DATA__UART1_DCE_RX 0x1b0b1
>;
};
pinctrl_xxx: xxx_grp {
... 这里设置将引脚设置为其他模式
}
pinctrl_yyy: yyy_grp {
... 这里设置将引脚设置为其他模式
}
}
• pinctrl-names: 定义引脚状态。
• pinctrl-0: 定义第 0 种状态需要使用到的引脚配置,可引用其他节点标识。
• pinctrl-1: 定义第 1 种状态需要使用到的引脚配置。
• pinctrl-2: 定义第 2 种状态需要使用到的引脚配置。
pinctrl子节点编写格式
接下来以“pinctrl_uart1”节点源码为例介绍 pinctrl 子节点格式规范编写:
pinctrl_uart1: uart1grp {
fsl,pins = <
MX6UL_PAD_UART1_TX_DATA__UART1_DCE_TX 0x1b0b1
MX6UL_PAD_UART1_RX_DATA__UART1_DCE_RX 0x1b0b1
>;
};
pinctrl 子节点格式规范,格式框架如下:
pinctrl_ 自定义名字: 自定义名字 {
fsl,pins = <
引脚复用宏定义 PAD(引脚)属性
引脚复用宏定义 PAD(引脚)属性
>;
};
这里我们需要知道每个芯片厂商的 pinctrl 子节点的编写格式并不相同,这不属于设备树的规范,是芯片厂商自定义的。如果我们想添加自己的 pinctrl 节点,只要依葫芦画瓢按照上面的格式编写即可
引脚配置信息介绍
引脚的配置信息一眼看去由两部分组成,一个宏定义和一个 16 进制数组成。这实际上定义已经配置控制引脚所需要用到的各个寄存器的地址及应写入寄存器值的信息,以上图的第一条配置信息为例说明。
MX6UL_PAD_UART1_TX_DATA__UART1_DCE_TX 0x1b0b1
MX6UL_PAD_UART1_TX_DATA__UART1_DCE_TX 是定义在“./arch/arm/boot/dts/imx6ul-pinfunc.h”文件内的一个宏定义。
**特别说明的是:**我们在找这个宏定义是,比如我们使用imx6ull芯片,那么首先就要找 imx6ull-pinfunc.h,若没有,之后再找 imx6ull-pinfunc-snvs.h,最后找 imx6ul-pinfunc.h
#define MX6UL_PAD_UART1_TX_DATA__UART1_DCE_TX 0x0084 0x0310 0x0000 0 0
#define MX6UL_PAD_UART1_TX_DATA__UART1_DTE_RX 0x0084 0x0310 0x0624 0 2
#define MX6UL_PAD_UART1_TX_DATA__ENET1_RDATA02 0x0084 0x0310 0x0000 1 0
#define MX6UL_PAD_UART1_TX_DATA__I2C3_SCL 0x0084 0x0310 0x05b4 2 0
#define MX6UL_PAD_UART1_TX_DATA__CSI_DATA02 0x0084 0x0310 0x04c4 3 1
#define MX6UL_PAD_UART1_TX_DATA__GPT1_COMPARE1 0x0084 0x0310 0x0000 4 0
#define MX6UL_PAD_UART1_TX_DATA__GPIO1_IO16 0x0084 0x0310 0x0000 5 0
#define MX6UL_PAD_UART1_TX_DATA__SPDIF_OUT 0x0084 0x0310 0x0000 8 0
每个宏定义后面有 5 个参数,名字依次为 mux_reg、 conf_reg、 input_reg、 mux_mode、input_val。
<mux_reg conf_reg input_reg mux_mode input_val>
0x0084 0x0310 0x0000 0x0 0x0
如果将宏定义展开则在设备树中每条配置信息实际是 6 个参数,由于第 6 个参数设置较为复杂需要根据实际需要设置因此并没有把它放到宏定义里面。以MX6UL_PAD_UART1_TX_DATA__UART1_DCE_TX 为例,宏定义中 5 个参数参数介绍如下:
- mux_reg 和 mux_mode :mux_reg 是引脚复用选择寄存器偏移地址, mux_mode 是引脚复用选择寄存器模式选择位的值。对应用户手册中的:IOMUXC_SW_MUX_CTL_PAD_xxx
- conf_reg ,引脚(PAD)属性控制寄存器偏移地址。与引脚复用选择寄存器不同,引脚属性寄存器应当根据实际需要灵活的配置,所以它的值并不包含在宏定义中,它的值是我们上面所说的“第六个”参数。对应用户手册中的:(IOMUXC_SW_PAD_CTL_PAD_xxx
- input_reg 和 input_val , input_reg 暂且称为输入选择寄存器偏移地址。 input_val 是输入选择寄存器的值。这个寄存器只有某些用作输入的引脚才有,正如本例所示, UART1_TX 用作输出,所以这两个参数都是零。“输入选择寄存器”理解稍微有点复杂,结合下图介绍如下。对应用户手册IOMUXC_xxx_SELECT_INPUT
若以UART1_RX_DATA为例,则为:IOMUXC_UART1_RX_DATA_SELECT_INPUT
<mux_reg conf_reg input_reg mux_mode input_val>
#define MX6UL_PAD_UART1_RX_DATA__UART1_DCE_RX 0x0088 0x0314 0x0624 0 3
将RGB灯引脚添加到pinctrl子系统
#define MX6UL_PAD_GPIO1_IO04__GPIO1_IO04 0x006c 0x02f8 0x0000 5 0
#define MX6UL_PAD_CSI_HSYNC__GPIO4_IO20 0x01e0 0x046c 0x0000 5 0
#define MX6UL_PAD_CSI_VSYNC__GPIO4_IO19 0x01dc 0x0468 0x0000 5 0
在iomuxc节点中添加pinctrl子节点
在 &iomuxc 节点末尾添加
&iomuxc{
.......
pinctrl_rgb_led: rgb_led{
fsl,pins = <
MX6UL_PAD_GPIO1_IO04__GPIO1_IO04 0x000010B1
MX6UL_PAD_CSI_HSYNC__GPIO4_IO20 0x000010B1
MX6UL_PAD_CSI_VSYNC__GPIO4_IO19 0x000010B1
>;
};
};
GPIO子系统
gpio4: gpio@20a8000 {
compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
reg = <0x20a8000 0x4000>;
interrupts = <GIC_SPI 72 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 73 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_GPIO4>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
gpio-ranges = <&iomuxc 0 94 17>, <&iomuxc 17 117 12>;
};
• compatible :与 GPIO 子系统的平台驱动做匹配。
• reg : GPIO 寄存器的基地址, GPIO4 的寄存器组是的映射地址为 0x20a8000-0x20ABFFF
• interrupts :描述中断相关的信息
• clocks :初始化 GPIO 外设时钟信息
• gpio-controller :表示 gpio4 是一个 GPIO 控制器
• #gpio-cells :表示有多少个 cells 来描述 GPIO 引脚
• interrupt-controller :表示 gpio4 也是个中断控制器
• #interrupt-cells : 表示用多少个 cells 来描述一个中断
• gpio-ranges :将 gpio 编号转换成 pin 引脚, <&iomuxc 0 94 17>,表示将 gpio4 的第 0 个引脚引脚映射为94, 17 表示的是引脚的个数。
gpio4 这个节点对整个 gpio4 进行了描述。使用 GPIO 子系统时需要往设备树中添加设备节点,在驱动程序中使用 GPIO 子系统提供的 API 实现控制 GPIO 的效果。
在设备树中添加RGB灯的设备树节点
只需要增加 GPIO 属性定义,基于 GPIO 子系统的 rgb_led 设备树节点添加到“./arch/arm/boot/dts/imx6ull-seeed-npi.dts”设备树的根节点内。添加完成后的设备树如下所示。
1 /* 添加 rgb_led 节点 */
2 rgb_led{
3 #address-cells = <1>;
4 #size-cells = <1>;
5 pinctrl-names = "default";
6 compatible = "fire,rgb-led";
7 pinctrl-0 = <&pinctrl_rgb_led>;
8 rgb_led_red = <&gpio1 4 GPIO_ACTIVE_LOW>;
9 rgb_led_green = <&gpio4 20 GPIO_ACTIVE_LOW>;
10 rgb_led_blue = <&gpio4 19 GPIO_ACTIVE_LOW>;
11 status = "okay";
12 };
• 第 6 行,设置“compatible”属性值,与 led 的平台驱动做匹配。
• 第 7 行,指定 RGB 灯的引脚 pinctrl 信息,上一小节我们定义了 pinctrl 节点,并且标签设置为“pinctrl_rgb_led”,在这里我们引用了这个 pinctrl 信息。
• 第 8-10 行,指定引脚使用的哪个 GPIO, 编写格式如下所示。
• 标号 ①,设置引脚名字,如果使用 GPIO 子系统提供的 API 操作 GPIO, 在驱动程序中会用到这个名字,名字是自定义的。
• 标号 ②,指定 GPIO 组。
• 标号 ③,指定 GPIO 编号。
• 编号 ④,这是一个宏定义,指定有效电平,低电平有效选择“GPIO_ACTIVE_LOW”高电平有效选择“GPIO_ACTIVE_HIGH”。
编译、下载设备树验证修改结果
前两小节我们分别在设备树中将 RGB 灯使用的引脚添加到 pinctrl 子系统,然后又在设备树中添加了 rgb_led设备树节点。这一小节将会编译、下载修改后的设备树,用新的设备树启动系统,然后检查是否有 rgb_led 设备树节点产生。
只编译设备树:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs
如果执行了“make distclean”清理了内核,那么就需要在内核目录下执行如下命令重新配置内核(如果编译设备树出错也可以先清理内核然后执行如下命令尝试重新编译)。
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- npi_v7_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs
编 译 成 功 后 会 在 “./arch/arm/boot/dts” 目 录 下 生 成 “imx6ull-seeed-npi.dtb” 文 件, 将 其 替 换 掉 板子/boot/dtbs/4.19.71-imx-r1/ 目录下的 imx6ull-seeed-npi.dtb 文件 并重启开发板。
// 将生成的设备树拷贝到共享文件夹
cp arch/arm/boot/dts/imx6ull-seeed-npi.dtb /home/Embedfire/wokdfir
// 挂载 nfs 共享文件夹 (在开发板上)
sudo mount -f nfs 192.168.0.231:/home/Embedfire/wokdfir /mnt
// 复制设备树到共享文件夹 (在开发板上)
cp /mnt/imx6ull-seeed-npi.dtb /boot/dtbs/4.19.71-imx-r1/
// 命令同步,确保复制的dtb文件写入磁盘
sync
// 重启开发板
reboot
使用新的设备树重新启动之后正常情况下会在开发板的“/proc/driver-tree”目录下生成“rgb_led”设备树节点。如下所示。
GPIO子系统常用API函数
GPIO子系统函数声明:内核源码 include/linux/of_gpio.h
获取GPIO编号 of_get_named_gpio
GPIO 子系统大多数 API 函数会用到 GPIO 编号。 GPIO 编号可以通过 of_get_named_gpio 函数从设备树中获取。
static inline int of_get_named_gpio(struct device_node *np, const char *propname, int index)
参数:
• np: 指定设备节点。
• propname: GPIO 属性名,与设备树中定义的属性名对应。
• index: 引脚索引值,在设备树中一条引脚属性可以包含多个引脚,该参数用于指定获取那个引脚。
返回值:
• 成功: 获取的 GPIO 编号(这里的 GPIO 编号是根据引脚属性生成的一个非负整数),
• 失败: 返回负数。
例:
gpioled_red = of_get_named_gpio(pdevice_node, "rgb_led_red ", 0);
if(gpioled_red < 0)
{
//失败
}
GPIO申请函数 gpio_request
源码:内核源码 drivers/gpio/gpiolib-legacy.c
static inline int gpio_request(unsigned gpio, const char *label);
参数:
• gpio: 要申请的 GPIO 编号,该值是函数 of_get_named_gpio 的返回值。
• label: 引脚名字,相当于为申请得到的引脚取了个别名。
返回值:
• 成功: 返回 0,
• 失败: 返回负数
注:失败一般是被其他驱动使用,可以检查设备树,一个是检查宏定义,一个检查gpio组
GPIO释放函数
内核源码 drivers/gpio/gpiolib-legacy.c
static inline void gpio_free(unsigned gpio);
gpio_free 函数与 gpio_request 是一对相反的函数,一个申请,一个释放。一个 GPIO 只能被申请一次,当不再
使用某一个引脚时记得将其释放掉。
参数:
• gpio: 要释放的 GPIO 编号。
返回值:无
GPIO输出设置函数 gpio_direction_output
内 核 源 码 include/asmgeneric/gpio.h
static inline int gpio_direction_output(unsigned gpio , int value);
函数参数:
• gpio: 要设置的 GPIO 的编号。
• value: 输出值, 1,表示高电平。 0 表示低电平。
返回值:
• 成功: 返回 0
• 失败: 返回负数
GPIO输入设置函数gpio_direction_input
内 核 源 码 include/asmgeneric/gpio.h
static inline int gpio_direction_input(unsigned gpio)
函数参数:
• gpio: 要设置的 GPIO 的编号。
返回值:
• 成功: 返回 0
• 失败: 返回负数
获取 GPIO 引脚值函数 gpio_get_value
无论引脚被设置为输出或者输入都可以用该函数获取引脚的当前状态。内核源码 include/asm-generic/gpio.h
static inline int gpio_get_value(unsigned gpio);
函数参数:
• gpio: 要获取的 GPIO 的编号。
返回值:
• 成功: 获取得到的引脚状态
• 失败: 返回负数
设置 GPIO 输出值 gpio_set_value
该函数只用于那些设置为输出模式的 GPIO. 内 核 源 码 include/asmgeneric/gpio.h
static inline int gpio_direction_output(unsigned gpio, int value);
函数参数
• gpio: 设置的 GPIO 的编号。
• value: 设置的输出值,为 1 输出高电平,为 0 输出低电平。
返回值:
• 成功: 返回 0
• 失败: 返回负数