7.Linux驱动-pinctrl子系统与gpio子系统


开发板:正点原子阿尔法
由于平台差异性,本文只涉及如何使用,不涉及原理以及驱动源码分析

1.前言

Linux系统为PIN脚的配置提供了pinctrl子系统,用来配置pin的复用功能与电气属性,比如上/下拉、速度、驱动,pinctrl子系统的主要功能为:

  1. 获取设备树的pin信息
  2. 根据获取到的pin信息设置pin的复用功能
  3. 根据获取到的 pin 信息来设置 pin 的电气特性,比如上/下拉、速度、驱动能力等

2.I.MX6ULL的pinctrl子系统

2.1 PIN配置信息详解

要想使用pinctrl子系统,需要在设备树中配置PIN信息,即在设备树中创建一个结点来描述PIN脚的配置信息,用来汇总所有PIN脚的配置信息,IMX6ULL的配置信息在设备树imx6ull.dtsi中,结点为iomuxc

iomuxc: iomuxc@20e0000 {
    
    
			compatible = "fsl,imx6ul-iomuxc";
			reg = <0x20e0000 0x4000>;
};

其中reg为引脚配置寄存器的基地址,打开正点原子创建的设备树文件 imx6ull-alientek-emmc.dts,

&iomuxc {
    
    
	pinctrl-names = "default""init","sleep";
	pinctrl-0 = <&pinctrl_hog_1>;
	pinctrl-1 =<&xxx>;
	pinctrl-2 =<&yyy>;
	imx6ul-evk {
    
    
		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_IO00__ANATOP_OTG1_ID    0x13058 /* USB_OTG1_ID */
			>;
		};

		pinctrl_csi1: csi1grp {
    
    
			fsl,pins = <
				MX6UL_PAD_CSI_MCLK__CSI_MCLK		0x1b008
				MX6UL_PAD_CSI_PIXCLK__CSI_PIXCLK	0x1b008
				MX6UL_PAD_CSI_VSYNC__CSI_VSYNC		0x1b008
				MX6UL_PAD_CSI_HSYNC__CSI_HSYNC		0x1b008
				
			>;
		};
		-----

从上面追加的数据看出,不同的外设使用的pin脚不一样,将某一个外设所使用的所有PIN脚都放在一个子结点里面。结点的配置方式为:

  • pinctrl-names:定义引脚状态(名字)
  • pinctrl-0:定义第0种状态需要使用到的引脚,可引用其他节点标识
  • pinctrl-1:定义第1种状态需要使用到的引脚,以此类推

2.2 结点的配置信息记录

以pinctrl_hog_1 子节点为例子,由fsl,pins以及其属性值构成,fsl,pins会结合imx6ull的pinctrl子系统驱动使用,以该属性值来标识引脚的配置信息,其属性值包括一个和一个16进制数,这里以UART1_RTS_B为例,其配置信息为

MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059

MX6UL_PAD_UART1_RTS_B__GPIO1_IO19宏定义的原型在arch/arm/boot/dts/imx6ul-pinfunc.h中,imx6ull.dtsi 会引用 imx6ull-pinfunc.h 这个头文件,而imx6ull-pinfunc.h 又会引用 imx6ul-pinfunc.h 这个头文件,此宏定义为

····
#define MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x0090 0x031C 0x0000 0x5 0x0
#define MX6UL_PAD_UART1_RTS_B__USDHC2_CD_B 0x0090 0x031C 0x0674 0x8 0x2
····

宏值定义为:

<mux_reg conf_reg input_reg mux_mode input_val>
0x0090   0x031C     0x0000    0x5     0x0
  • 0x0090:mux_reg 寄存器偏移地址,IOMUXC 外 设 寄 存 器 起 始 地 址 为 0x020e0000,所以MX6UL_PAD_UART1_RTS_B__GPIO1_IO19的寄存器地址为0x020e0000+0x0090=0x020e009,
    在这里插入图片描述

  • 0x031C: conf_reg 寄存器偏移地址,和 mux_reg 一样,0x020e0000+0x031c=0x020e031c,这个就是寄存器 IOMUXC_SW_PAD_CTL_PAD_UART1_RTS_B 的地址。
    -在这里插入图片描述

  • 0x0000: input_reg 寄存器偏移地址,有些外设有 input_reg 寄存器,有 input_reg 寄存器的外设需要配置 input_reg 寄存器。没有的话就不需要设置, UART1_RTS_B 这个 PIN 在做GPIO1_IO19 的时候是没有 input_reg 寄存器,因此这里 intput_reg 是无效的。

  • 0x5 : mux_reg 寄 存 器 的值 , 在 这 里 就 相 当 于 设 置IOMUXC_SW_MUX_CTL_PAD_UART1_RTS_B 寄存器为 0x5,也即是设置 UART1_RTS_B 这个 PIN 复用为 GPIO1_IO19。
    在这里插入图片描述

    回到原来的pinctrl定义,其中0x17059则是config_reg寄存器的值,通过此值来设置一个 IO 的上/下拉、驱动能力和速度等。在这里就相当于设置寄存器IOMUXC_SW_PAD_CTL_PAD_UART1_RTS_B 的值为 0x17059

MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059

3.在设备树添加pinctrl结点

关于I.MX系列SOC的pinctrl的设备树绑定信息可以参考文档Documentation/devicetree/bindings/pinctrl/fsl,imx-pinctrl.txt,我们新建一个“test”设备,test设备使用了 GPIO1_IO00,pinctrl结点的添加过程为:

3.1 创建对应的节点

同一个外设的PIN都放到一个节点里面,打开 imx6ull-alientek-emmc.dts,在iomuxc节点中imx6ul-evk子节点下添加pinctrl_test结点,注意节点前缀一定要为pinctrl,添加完成以后如下所示:

pinctrl_test: testgrp {
    
    
	/* 具体的 PIN 信息 */
};

3.2 添加fsl,pins属性

设备树是通过属性来保存信息的,因此需要添加一个属性,属性名字一定要为fsl,pins,对于I.MX系列的SOC来说,pinctrl驱动程序是通过fsl,pins属性值来获取PIN的配置信息,如下所示

pinctrl_test: testgrp {
    
    
 fsl,pins = <
 	/* 设备所使用的 PIN 配置信息 */
 	>;
};

3.3 在fsl.pins属性中添加PIN配置信息

pinctrl_test: testgrp {
    
    
  fsl,pins = <
  	MX6UL_PAD_GPIO1_IO00__GPIO1_IO00 config /*config 是具体设置值*/
>;
};

通过以上步骤,添加好了test设备所需要的PIN配置信息,设置了test设备使用的PIN脚复用与驱动能力,上下拉等

4.gpio子系统

gpio 子系统目的是方便驱动开发者使用 gpio,在设备树中添加 gpio 相关信息,在驱动中调用gpio子系统提供的API函数操作GPIO,可以设置gpio为输入输出,以及读取设置gpio的值。

5. I.MX的gpio子系统

I.MX6ULL-ALPHA 开发板上的 UART1_RTS_B 做为 SD 卡的检测引脚, UART1_RTS_B 复用为 GPIO1_IO19,通过读取这个 GPIO 的高低电平就可以知道 SD 卡有没有插入。首先肯定是将 UART1_RTS_B 这个 PIN 复用为 GPIO1_IO19,并且设置电气属性,打开 imx6ull-alientek-emmc.dts, UART1_RTS_B 这个 PIN 的 pincrtl 设置如下:

 &usdhc1 {
    
    
	pinctrl-names = "default", "state_100mhz", "state_200mhz";
	pinctrl-0 = <&pinctrl_usdhc1>;
	pinctrl-1 = <&pinctrl_usdhc1_100mhz>;
	pinctrl-2 = <&pinctrl_usdhc1_200mhz>;
	/* pinctrl-3 = <&pinctrl_hog_1>; */
	cd-gpios = <&gpio1 19 GPIO_ACTIVE_LOW>;
	keep-power-in-suspend;
	enable-sdio-wakeup;
	vmmc-supply = <&reg_sd1_vmmc>;
	status = "okay";
 };

pinctrl_hog_1: hoggrp-1 {
    
    
 	fsl,pins = <
		 MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059 /* SD1 CD */				......
	>;
 };

  • usdhc1为SD卡设备的总结点,pinctrl_hog_1结点描述SD卡CD引脚的pinctrl信息
  • cd-gpios描述了SD卡的CD引脚使用的是哪个IO,属性值一共有3个,&gpio1表示CD引脚使用的IO属于GPIO1组,19表示GPIO1的19号IO,GPIO_ACTIVE_HIGH表示低电平有效,GPIO_ACTIVE_HIGH表示高电平有效。

在imx6ull.dtsi有以下描述:

 gpio1: gpio@0209c000 {
    
    
 	compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
	 reg = <0x0209c000 0x4000>;
	 interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>,
	 <GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>;
	 gpio-controller;
	 #gpio-cells = <2>;
	 interrupt-controller;
	 #interrupt-cells = <2>;
 };

gpio1结点信息描述了GPIO1控制器的所有信息,关 于 I.MX 系 列 SOC 的 GPIO 控 制 器 绑 定 信 息 请可 看 文 档Documentation/devicetree/bindings/gpio/ fsl-imx-gpio.txt。 gpio-controller表示gpio1 节点是个 GPIO 控制器 ,“#gpio-cells”属性 为2表示一共有两个cell,第一个cell为gpio编号,比如&gpio1 3表示GPIO1_IO03,第二个cell表示gpio的极性,如 果 为 0(GPIO_ACTIVE_HIGH) 的 话 表 示 高 电 平 有 效 , 如 果 为1(GPIO_ACTIVE_LOW)的话表示低电平有效。

6.gpio子系统API函数

6.1 gpio_request

用于申请一共GPIO管脚,在使用一个 GPIO 之前一定要使用 gpio_request进行申请,函数原型如下:

/*
*gpio:要申请的gpio编号,使用 of_get_named_gpio 函数从设备树获取指定 GPIO 属 *性信息,此函数会返回这个 GPIO 的标号
* label:给gpio设置个名字
*return:成功0,失败:其他值
*/
int gpio_request(unsigned gpio, const char *label)

6.2 gpio_free

不使用某个 GPIO ,调用 gpio_free 函数进行释放

//gpio:要释放的gpio编号
//return:NULL
void gpio_free(unsigned gpio)

6.3 gpio_direction_input

设置某一个gpio为输入

//gpio:要设置为输入的gpio编号
//return:成功:0,负值:失败
int gpio_direction_input(unsigned gpio)

6.4 gpio_direction_output

设置某个 GPIO 为输出,并且设置默认输出值

//gpio:要设置为输出的gpio编号
//value:GPIO默认输出值
//return:0:成功,负值:失败
int gpio_direction_output(unsigned gpio, int value)

6.5 gpio_get_value

获取某个 GPIO 的值(0 或 1),此函数是个宏

//gpio:要获取的gpio编号
//return:负值:获取失败,非负值:得到的GPIO值
#define gpio_get_value __gpio_get_value
int __gpio_get_value(unsigned gpio)

6.6 gpio_set_value

用于设置某个 GPIO 的值,此函数是个宏

//gpio:要设置的 GPIO 标号
//value: 要设置的值
//返回值: 无
#define gpio_set_value __gpio_set_value
void __gpio_set_value(unsigned gpio, int value)

7.在设备树添加gpio结点模板

7.1.创建test设备结点

在根节点“/”下创建 test 设备子节点,如下所示:

test {
    
    
	/* 节点内容 */
};

7.2 添加pinctrl信息

iomuxc节点中imx6ul-evk子节点下添加pinctrl_test结点

test {
    
    
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_test>;
	/* 其他节点内容 */
};

​`````
pinctrl_test: testgrp {
    
    
 	fsl,pins = <
 		MX6UL_PAD_GPIO1_IO00__GPIO1_IO00 config /*config 是具体设置值*/
>;
};
  • pinctrl-names 属性,此属性描述 pinctrl 名字为“default”。

添加 pinctrl-0 节点,此节点引用 pinctrl_test 节点,表示 tset 设备的所使用的 PIN 信息保存在 pinctrl_test 节点中

7.3 添加 GPIO 属性信息

在 test 节点中添加 GPIO 属性信息,表明 test 所使用的 GPIO 是哪个引脚

test {
    
    
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_test>;
	gpio = <&gpio1 0 GPIO_ACTIVE_LOW>;
};

8.与gpio相关的OF函数

8.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 数量;负值,失败

8.2 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 编号;负值,失败

9.举例

使用I.MX6ULL开发板的LED灯作为;例子

9.1 在设备中添加pinctrl结点

I.MX6U-ALPHA 开发板上的 LED 灯使用了 GPIO1_IO03 这个 PIN,打开 imx6ull-alientekemmc.dts,在 iomuxc 节点的 imx6ul-evk 子节点下创建一个名为“pinctrl_led”的子节点 ,将 GPIO1_IO03 这个 PIN 复用为 GPIO1_IO03,电气属性值为 0X10B0

pinctrl_led: ledgrp {
    
    
	fsl,pins = <
	MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0 /* LED0 */
>;
};

9.2 添加 LED 设备节点

在根节点“/”下创建 LED 灯节点,节点名为“gpioled”,节点内容如下:

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";
};
  • pinctrl-0 属性设置 LED 灯所使用的 PIN 对应的 pinctrl 节点
  • led-gpio 属性指定了 LED 灯所使用的 GPIO,在这里就是 GPIO1 的 IO03,低电平有效。稍后编写驱动程序的时候会获取 led-gpio 属性的内容来得到 GPIO 编号,因为 gpio 子系统的 API 操作函数需要 GPIO 编号

9.3 检查PIN脚有没有冲突

①、检查 pinctrl 设置。
②、如果这个 PIN 配置为 GPIO 的话,检查这个 GPIO 有没有被别的外设使用。

在实验中 LED 灯使用的 PIN 为 GPIO1_IO03,先检查 GPIO_IO03 这个 PIN 有没有被其他的 pinctrl 节点使用,在 imx6ull-alientek-emmc.dts 中找到如下内容:

pinctrl_tsc: tscgrp {
    
    
	fsl,pins = <
	MX6UL_PAD_GPIO1_IO01__GPIO1_IO01 0xb0
	MX6UL_PAD_GPIO1_IO02__GPIO1_IO02 0xb0
	MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0xb0
	MX6UL_PAD_GPIO1_IO04__GPIO1_IO04 0xb0
>;
};

pinctrl_tsc 节点是 TSC(电阻触摸屏接口)的,默认情况下GPIO1_IO03 作为了 TSC 外设的 PIN。所以需要将此行屏蔽掉,在 imx6ull-alientek-emmc.dts 中搜索“gpio1 3”

 &tsc {
    
    
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_tsc>;
	xnur-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
	measure-delay-time = <0xffff>;
	pre-charge-time = <0xfff>;
	status = "okay";
 };

tsc 是 TSC 的外设节点,tsc 外设使用了 GPIO1_IO03,需要将这一行屏蔽掉

9.4 编译设备树

设备树编写完成以后使用“ make dtbs”命令重新编译设备树,然后使用新编译出来的imx6ull-alientek-emmc.dtb 文件启动 Linux 系统。启动成功以后进入“/proc/device-tree”目录中查看“gpioled”节点是否存在,如果存在的话就说明设备树基本修改成功(具体还要驱动验证)

在这里插入图片描述

9.5 驱动中使用GPIO子系统API函数举例

struct device_node *nd; /* 设备节点 */
int led_gpio; /* led 所使用的 GPIO 编号 */

/* 设置 LED 所使用的 GPIO */
/* 1、获取设备节点: gpioled */
nd = of_find_node_by_path("/gpioled");
if(nd == NULL) {
    
    
	printk("gpioled node cant not found!\r\n");
	return -EINVAL;
} else {
    
    
	printk("gpioled node has been found!\r\n");
}

/* 2、 获取设备树中的 gpio 属性,得到 LED 所使用的 LED 编号 */
led_gpio = of_get_named_gpio(nd, "led-gpio", 0);
if(led_gpio < 0) {
    
    
	printk("can't get led-gpio");
	return -EINVAL;
}
	
//3.申请gpio标号failed
/*ret = gpio_request(led_gpio,  "led-gpio");
if(ret) {
	printk("gpio_request failed!\r\n");
}*/
    
/* 3、设置 GPIO1_IO03 为输出,并且输出高电平,默认关闭 LED 灯 */
ret = gpio_direction_output(led_gpio, 1);
if(ret < 0) {
    
    
	printk("can't set gpio!\r\n");
}	

gpio_set_value(led_gpio, 0); /* 打开 LED 灯 */
gpio_set_value(led_gpio, 1); /* 关闭 LED 灯 */

猜你喜欢

转载自blog.csdn.net/weixin_43824344/article/details/120146537