目录
一、pinctrl 子系统
在学习了设备树之后, 如果还按照裸板开发的方式配置寄存器来控制 IO 的方式太过于原始, Linux 内核提供了 pinctrl 子系统和 gpio 子系统用于 GPIO 驱动。
pinctrl 子系统主要工作内容如下:
①获取设备树中 pin 信息,管理系统中所有的可以控制的 pin, 在系统初始化的时候, 枚举所有可以控制的 pin, 并标识这些 pin
②根据获取到的 pin 信息来设置 pin 的复用功能,对于 SOC 而言, 其引脚除了配置成普通的 GPIO 之外,若干个引脚还可以组成一个 pin group, 形成特定的功能
③根据获取到的 pin 信息来设置 pin 的电气特性,比如上/下拉、速度、驱动能力等。
对应使用者来说,只需要在设备树里面设置好某个 pin 的相关属性即可,其他的初始化工作均由 pinctrl 子系统来完成。
pinctrl 子系统要根据你提供的信息来配置 PIN 功能,一般会在设备树里面创建一个节点来描述 PIN 的配置信息。pinctrl 子系统一般在iomuxc子节点下,所有需要配置用户自己的pinctrl需要在该节点下添加。
从图中可以知道UART1_RTS_B 的配置信息如下:
MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059
UART1_RTS_B 的 配 置 信 息 分 为 两 部 分 :MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 和 0x17059。
对于一个 PIN 的配置主要包括两方面:PIN 的复用功能和PIN 的电气特性。这里先给出结论:前面设置UART1_RTS_B 的复用功能,后面用来设置 UART1_RTS_B 的电气特性。
在源码中一共有八个宏定义,对应着将UART1_RTS_B复用为不同的功能。MX6UL_PAD_UART1_RTS_B__GPIO1_IO19是一个宏定义,如下:
#define MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x0090 0x031C 0x0000 0x5 0x0
宏定义的具体值就是后面的5个数值,其含有如下
0x0090 0x031C 0x0000 0x5 0x0
<mux_reg conf_reg input_reg mux_mode input_val>
mux_reg :寄存器偏移地址,设备树中的 iomuxc 节点就是 IOMUXC 外设对应的节点 , 根 据 其 reg 属 性 可 知 IOMUXC 外 设 寄 存 器 起 始 地 址 为 0x020e0000。
因 此0x020e0000+0x0090=0x020e0090, IOMUXC_SW_MUX_CTL_PAD_UART1_RTS_B 寄存器地址是 0x020e0090。
所以,0x020e0000+mux_reg 就是 PIN 的复用寄存器地址。其余也是类似的。
imxuio里面的0x17059 ,此值由用户自行设置,通过此值来设置一个 IO 的上/下拉、驱动能力和速度,配置PIN的电气特性。
设备树中添加 pinctrl 节点模板:
pinctrl_test: testgrp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO00__GPIO1_IO00 config //onfig 是具体设置值
>;
};
同一个外设的 PIN 都放到一个节点里面,在 iomuxc 节点中下添加“pinctrl_test”节点。
注意:节点前缀一定要为“pinctrl_”。
设备树是通过属性来保存信息的,因此需要添加一个属性,属性名字一定要为“fsl,pins”,对于 I.MX 系列 SOC 而言, pinctrl 驱动程序是通过读取“fsl,pins”属性值来获取 PIN 的配置信息。
然后在“fsl,pins”属性中添加具体的 PIN 配置信息即可。通过以上的操作我们就可以完成对一个引脚的复用配置以及初始化。
调用 pinctrl 一般在设备树中进行,在外设节点中调用:
gpioled {
#address-cells = <1>;
#size-cells = <1>;
compatible = "atkalpha-gpioled";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_led>;
led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
status = "okay";
};
只需要关注gpioled设备节点下的“ pinctrl-names ” 和“ pinctrl-0” 两条语句, 这两句就是引用在 iomuxc 中配置的 pinctrl 节点。
pinctrl-names = "default"; 设备的状态, 可以有多个状态, default 为状态 0。
pinctrl-0 = <&pinctrl_led>;第 0 个状态所对应的引脚配置, default 状态对应的引脚在 pin controller 里面定义好的节点 pinctrl_led 里面的管脚配置
二、gpio子系统
当使用 pinctrl 子系统将引脚的复用设置为 GPIO,可以使用 GPIO 子系统来操作GPIO,Linux 内核提供了 pinctrl 子系统和 gpio 子系统用于 GPIO 驱动。
通过 GPIO 子系统功能要实现:
①引脚功能的配置(设置为 GPIO,GPIO 的方向, 输入输出模式,读取/设置 GPIO 的值)
②实现软硬件的分离(分离出硬件差异, 有厂商提供的底层支持; 软件分层。 驱动只需要调用接口 API 即可操作 GPIO)
③iommu 内存管理(直接调用宏即可操作 GPIO)
gpio 子系统的主要目的就是方便驱动开发者使用 gpio,驱动开发者在设备树中添加 gpio 相关信息,然后就可以在驱动程序中使用 gpio 子系统提供的 API函数来操作 GPIO, Linux 内核向驱动开发者屏蔽掉了 GPIO 的设置过程,极大的方便了驱动开发者使用 GPIO。
1.gpio 子系统 API 函数
①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,申请成功;其他值,申请失败。
②gpio_free
如果不使用某个 GPIO ,需要调用 gpio_free 函数进行释放。
void gpio_free(unsigned gpio); // gpio:要释放的 gpio 标号。
③gpio_direction_input
设置某个 GPIO 为输入
int gpio_direction_input(unsigned gpio) //gpio:要设置为输入的 GPIO 标号。
④gpio_direction_output
设置某个 GPIO 为输出,并且设置默认输出值。
int gpio_direction_output(unsigned gpio, int value)
gpio:要设置为输出的 GPIO 标号。
value: GPIO 默认输出值。
返回值: 0,设置成功;负值,设置失败
⑤gpio_get_value
获取某个 GPIO 的值(0 或 1)
#define gpio_get_value __gpio_get_value
int __gpio_get_value(unsigned gpio)
gpio:要获取的 GPIO 标号。
返回值: 非负值,得到的 GPIO 值;负值,获取失败
⑥gpio_set_value
设置某个 GPIO 的值
#define gpio_set_value __gpio_set_value
void __gpio_set_value(unsigned gpio, int value)
gpio:要设置的 GPIO 标号。
value: 要设置的值
2.gpio 相关的 OF 函数
在驱动程序中需要读取 gpio 属性内容, Linux 内核提供了几个与 GPIO 有关的 OF 函数。
①of_gpio_named_count
获取设备树某个属性里面定义了几个GPIO 信息。
int of_gpio_named_count(struct device_node *np, const char *propname)
np:设备节点。
propname:要统计的 GPIO 属性。
返回值: 正值,统计到的 GPIO 数量;负值,失败
注意空的GPIO信息也会被统计到
gpios = <0
&gpio1 1 2
0 &
gpio2 3 4>;
如上GPIO的数量为4。
②of_gpio_count
此函数统计的是gpios属性的 GPIO 数量,而 of_gpio_named_count 函数可以统计任意属性的GPIO 信息
int of_gpio_count(struct device_node *np)
np:设备节点。
返回值: 正值,统计到的 GPIO 数量;负值,失败
③of_get_named_gpio
获取 GPIO 编号,在Linux 内核中关于 GPIO 的 API 函数都要使用 GPIO 编号,此函数会将设备树中类似<&gpio5 7 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.设备树中添加 gpio 节点模板
①调用pinctrl信息
调用pinctrl_test 节点,此节点描述了 test 设备所使用的 PIN 的信息(复用功能,电气特性)。
在根节点“/”下创建 test 设备子节点,并调用pinctrl。
test {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_test>;
/* 其他节点内容 */
};
pinctrl-names = "default", "wake up";
pinctrl-0 = <&pinctrl_hog_1>;
pinctrl-1 = <&pinctrl_hog_2>;
pinctrl-names = "default", "wake up"; 设备的状态, 可以有多个状态, default 为状态 0, wake up 为状态 1。
pinctrl-0 = <&pinctrl_hog_1>;第 0 个状态所对应的引脚配置, 也就是 default 状态对应的引脚在 pin controller 里面定义好的节点 pinctrl_hog_1 里面的管脚配置。
pinctrl-1 = <&pinctrl_hog_2>;第 1 个状态所对应的引脚配置, 也就是 wake up 状态对应的引脚在 pin controller 里面定义好的节点 pinctrl_hog_2 里面的管脚配置。
②配置GPIO 属性信息
在 test 节点中添加 GPIO 属性信息,表明 test 所使用的 GPIO 是哪个引脚,
gpio = <&gpio1 0 GPIO_ACTIVE_LOW>;
test 设备所使用的 gpio1,配置引脚GPIO1_IO00,低电平有效。GPIO_ACTIVE_LOW的宏定义为1,GPIO_ACTIVE_HIGH为0.
小结
需要配置某个外设的引脚为GPIO
①在 IOMUXC/ pinctrl 中对某一个引脚进行配置, 两个部分: 复用、 初始化(电气特性)
②在外设节点中调用(设备树中)
③在驱动中获取设备节点以及 GPIO④对 GPIO 进行配置
模板:
//client端:
@节点名字 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_自定义名字A>;
status = "okay";
};
//pincontroller服务端
pinctrl_自定义名字A: 自定义名字B {
fsl,pins = <
引脚复用宏定义 PAD(引脚)属性, // 引脚 A
引脚复用宏定义 PAD(引脚)属性; // 引脚 B
>;
};
//驱动
gpio_request(leddev.led0, "led0");
gpio_direction_output(leddev.led0, 1);
gpio_set_value(leddev.led0, 0);
gpio_direction_input(key.irqkeydesc[0].gpio);
gpio_get_value(keydesc->gpio);
gpio_free(leddev.led0);
引用复用宏定义一般在arch/arm/boot/dts/imx6ul-pinfunc.h和imx6ull-pinfunc.h里面定义。