pinctl和gpio子系统

以下内容来自正点原子Linux驱动

pinctl子系统

  • 设置引脚的复用和电气属性。

传统的对引脚复用和电气属性设置都是直接对寄存器操作,完成IO的初始化,这种方法很繁琐,而且容易出问题(比如pin功能冲突)。在Linux系统中使用这种繁琐的操作不现实,所以就有了pinctl子系统。
简单的说就是不用自己去设置引脚复用和电气属性了,只要在设备树中添加相应的节点并描述,pinctrl系统就会帮我们设置(它是内核中的一段程序)

pinctrl 子系统会干这些事:

  1. 获取设备树中pin信息。
  2. 根据获取到的pin信息来设置引脚复用。
  3. 根据获取到的pin信息来设置电气属性(比如上/下拉、速度、驱动能力等)。

对于使用者来讲,只需要在设备树里面设置好某个 io 的相关属性即可,其他的初始化工作均由 pinctrl 子系统来完成, pinctrl 子系统源码目录为 drivers/pinctrl。

pinctl的配置信息

要想使用pinctl,那么就需要在设备树中对pin的信息描述,pinctl子系统根据描述信息来配置pin的功能,一般会在设备树下为一组 io (通常是某一个外设上使用的所有io在一个节点中配置)创建一个节点来描述,打开imx6ull.dtsi(设备树也支持头文件,这是imx6ull的头文件):

可以找到以下节点:这些是imx6ull对复用控制器的描述:

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

半导体厂商在头文件中描述的:

  • IOMUXC SNVS控制器
  • IOMUXC控制器
  • gpr控制器
    半导体厂商的描述比较简单,我们需要对他进行一些追加

这里描述的很少,但是我们在 imx6ull-alientek-emmc.dts 文件中可以找到对它追加的信息:

&iomuxc {
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_hog_1>;
	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_IO09__GPIO1_IO09        0x17059 SD1 RESET */
				MX6UL_PAD_GPIO1_IO00__ANATOP_OTG1_ID	0x13058	/* USB_OTG1_ID */
			>;
		};
        /* zuozhongkai LED */
        pinctrl_led: ledgrp {
            fsl,pins = <
            	MX6UL_PAD_GPIO1_IO03__GPIO1_IO03        0x10B0 /* LED0 */
            >;
        };
		
		/* zuozhongkai BEEP */
		pinctrl_beep: beepgrp {
			fsl,pins = <
				MX6ULL_PAD_SNVS_TAMPER1__GPIO5_IO01 	0x10B0 /* beep */	
			>;
		};
	……后续还有

以上是对引脚所使用外设的描述,也就是将这个外设所需要使用的io全部放在一个自己的节点里。我们加入我们想要编写一个led外设,那么就把led需要用到的io放在pinctl_led节点下描述:
pinctrl_led: ledgrp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0 /* LED0 */
>;
};

将头文件中的IOMUXC节点描述和追加的整合后就可以得到:

1 iomuxc: iomuxc@020e0000 {
2 compatible = "fsl,imx6ul-iomuxc";			//匹配驱动程序的
3 reg = <0x020e0000 0x4000>;					//IOMUXC控制器的内存地址
4 pinctrl-names = "default";						
pinctrl-0 = <&pinctrl_hog_1>;
	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_IO09__GPIO1_IO09        0x17059 SD1 RESET */
				MX6UL_PAD_GPIO1_IO00__ANATOP_OTG1_ID	0x13058	/* USB_OTG1_ID */
			>;
		};
		……中间还有很多子节点
	};
};
};

“MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059” 为例来讲解,首先这个引脚的功能是用来检测sd卡的(用一个gpio来检测sd是否插入)。
MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059 分为了MX6UL_PAD_UART1_RTS_B__GPIO1_IO19和0x17059两部分。我们知道引脚的初始化分为复用和电气属性两部分,这两个可能就是用来设置复用和电气属性的。

MX6UL_PAD_UART1_RTS_B__GPIO1_IO19宏分析

这是一个宏,在arch/arm/boot/dts/imx6ul-pinfunc.h 中定义,imx6ull.dtsi会引用此文件。

在对控制器追加的描述当中,分门别类的对所有功能汇总描述,按不同功能在不同节点描述。具体定义如下:

195 #define MX6UL_PAD_UART1_RTS_B__ENET2_1588_EVENT1_OUT 0x0090 0x031C 0x0000 0x4 0x0

196 #define MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x0090 0x031C 0x0000 0x5 0x0

197 #define MX6UL_PAD_UART1_RTS_B__USDHC2_CD_B 0x0090 0x031C 0x0674 0x8 0x2

此宏定义后面跟着 5 个数字,也就是这个宏定义的具体值,如下所示:
0x0090 0x031C 0x0000 0x5 0x0
这五个值分别对应:

我们来分析mux_reg对应的0x0090这个值

在参考手册中我们找到IOMUXC控制器基地址:
在这里插入图片描述
接着找到对UART1_RTS_B__GPIO1_IO19这个引脚复用控制的寄存器地址:
可以发现这个寄存器地址就是:IOMUXC控制基地址+0x90,那么第一个值就是控制复用无疑了。
pinctl和gpio子系统_第1张图片

conf_reg

conf_reg 寄存器偏移地址,和 mux_reg 一样, 0x020e0000+0x031c=0x020e031c,这个就是寄存器 IOMUXC_SW_PAD_CTL_PAD_UART1_RTS_B 的地址(控制电气属性)。

input_reg

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

mux_mode

这个值就是写入复用控制器的值,也就是设置UART1_RTS_B这个引脚的复用。
如图0x5是设置为GPIO1_IO19:
pinctl和gpio子系统_第2张图片

input_reg

0x0: input_reg 寄存器值(写入input_reg寄存器的值),在这里无效。

这就是宏 MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 的含义,看完后我们发现宏里面并没有定义写入电气属性寄存器的值,其实这个值就是宏后面跟着的0x17059。
此值由用户自行设置,通过此值来设置一个 IO 的上/下拉、驱动能力和速度等。在这里就相当于设置寄存器
IOMUXC_SW_PAD_CTL_PAD_UART1_RTS_B 的值为 0x17059。

pin驱动程序讲解

(内核源码,有个概念就行)

设备树中所有的值(复用寄存器地址、复用值、电气属性寄存器地址、电气属性值)都已经准备好了,那么需要pinctl驱动程序会根据这些值来配置IO。接下来我们看一下pinctl程序的源码。
首先在iomuxc 节点中 compatible 属性的值为“fsl,imx6ul-iomuxc”,它会匹配相对应的驱动程序,我们可以在Linux内核源码中全局搜索“fsl,imx6ul-iomuxc”,就可以找到相应的.c文件。
在文件drivers/pinctrl/freescale/pinctrl-imx6ul.c中有如下内容:

static struct of_device_id imx6ul_pinctrl_of_match[] = {
	{ .compatible = "fsl,imx6ul-iomuxc", .data = &imx6ul_pinctrl_info, },
	{ .compatible = "fsl,imx6ull-iomuxc-snvs", .data = &imx6ull_snvs_pinctrl_info, },
	{ /* sentinel */ }
};


static int imx6ul_pinctrl_probe(struct platform_device *pdev)
{
	const struct of_device_id *match;
	struct imx_pinctrl_soc_info *pinctrl_info;

	match = of_match_device(imx6ul_pinctrl_of_match, &pdev->dev);

	if (!match)
		return -ENODEV;

	pinctrl_info = (struct imx_pinctrl_soc_info *) match->data;

	return imx_pinctrl_probe(pdev, pinctrl_info);
}

static struct platform_driver imx6ul_pinctrl_driver = {
	.driver = {
		.name = "imx6ul-pinctrl",
		.owner = THIS_MODULE,
		.of_match_table = of_match_ptr(imx6ul_pinctrl_of_match),
	},
	.probe = imx6ul_pinctrl_probe,
	.remove = imx_pinctrl_remove,
};
of_device_id结构体

pinctl和gpio子系统_第3张图片
of_device_id结构体里面保存着这个驱动文件的兼容性值,设备树中的 compatible 属性值会和 of_device_id 中的所
有兼容性字符串比较,查看是否可以使用此驱动。

imx6ul_pinctrl_of_match 结构体数组一共有两个兼容性字符串, 分别为“fsl,imx6ul-iomuxc”和“fsl,imx6ull-iomuxc-snvs”,因此 iomuxc 节点与此驱动匹配,所以 pinctrl-imx6ul.c 会完成 I.MX6ULL 的 PIN 配置工作。

platform_driver结构体中的probe变量

pinctl和gpio子系统_第4张图片

platform_driver 是平台设备驱动结构体,有个 probe 成员变量,这是一个函数指针。当设备和驱动匹配成功以后 probe 所指向的函数就会执行。

在 353 行设置 probe 成员变量为 imx6ul_pinctrl_probe 函数,因此在本章实验中 imx6ul_pinctrl_probe 这个函数就会执行,可以认为 imx6ul_pinctrl_probe 函数就是 I.MX6ULL 这个 SOC 的 PIN 配置入口函数。

以此为入口,如图 45.1.2.3 所示的函数调用路径:
pinctl和gpio子系统_第5张图片
imx_pinctrl_probe 调用了pinctrl注册函数注册:

pinctrl_register此函数用于向 Linux 内核注册一个 描述 pinctrl 结构体 ,此函数原型如下:

struct pinctrl_dev *pinctrl_register(struct pinctrl_desc *pctldesc,
													struct device *dev,
													void *driver_data)

pctldesc 原型如下所示:
pinctl和gpio子系统_第6张图片
该结构体描述了pinctrl ,可以发现在132~134行有pinctrl_ops、pinmux_ops和pinconf_ops三个结构体,这三个结构体中就包含了对引脚复用、电气属性设置的操作函数。pinctrl_desc 结构体变量由用户提供(这里的用户是半导体厂商)。半导体厂商发布的Linux内核源码中已经把这些都设置完了。

比如在 imx_pinctrl_probe 函数中可以找到如下所示代码:
pinctl和gpio子系统_第7张图片pinctl和gpio子系统_第8张图片

第 655 行,定义结构体指针变量 imx_pinctrl_desc。
第 664 行,向指针变量 imx_pinctrl_desc 分配内存。
第 706~712 行,初始化 imx_pinctrl_desc 结构体指针变量,重点是 pctlops、 pmxops 和 confops这三个成员变量,分别对应 imx_pctrl_ops、 imx_pmx_ops 和 imx_pinconf_ops 这三个结构体。

第 723 行,调用函数 pinctrl_register 向 Linux 内核注册 imx_pinctrl_desc,注册以后 Linux 内
核就有了对 I.MX6ULL 的 PIN 进行配置的工具。(和驱动框架中注册dev结构体应该差不多)

在图 45.1.2.3 中函数 imx_pinctrl_parse_groups 负责获取设备树中关于 PIN 的配置信息,也就是我们前面分析的那 6 个 u32 类型的值(mux_reg、conf_reg、input_reg、mux_mode、input_val和电气属性的值)。 处理过程如下所示:

/*
 * Each pin represented in fsl,pins consists of 5 u32 PIN_FUNC_ID and
 * 1 u32 CONFIG, so 24 types in total for each pin.
 */
#define FSL_PIN_SIZE 24
#define SHARE_FSL_PIN_SIZE 20

static int imx_pinctrl_parse_groups(struct device_node *np,
				    struct imx_pin_group *grp,
				    struct imx_pinctrl_soc_info *info,
				    u32 index)
{
	int size, pin_size;
	const __be32 *list;
	int i;
	u32 config;

	……中间还有

	for (i = 0; i < grp->npins; i++) {
		u32 mux_reg = be32_to_cpu(*list++);
		u32 conf_reg;
		unsigned int pin_id;
		struct imx_pin_reg *pin_reg;
		struct imx_pin *pin = &grp->pins[i];

……中间有

		pin_id = (mux_reg != -1) ? mux_reg / 4 : conf_reg / 4;
		pin_reg = &info->pin_regs[pin_id];
		pin->pin = pin_id;
		grp->pin_ids[i] = pin_id;
		pin_reg->mux_reg = mux_reg;
		pin_reg->conf_reg = conf_reg;
		pin->input_reg = be32_to_cpu(*list++);
		pin->mux_mode = be32_to_cpu(*list++);
		pin->input_val = be32_to_cpu(*list++);

		/* SION bit is in mux register */
		config = be32_to_cpu(*list++);
		if (config & IMX_PAD_SION)
			pin->mux_mode |= IOMUXC_CONFIG_SION;
		pin->config = config & ~IMX_PAD_SION;

		dev_dbg(info->dev, "%s: 0x%x 0x%08lx", info->pins[pin_id].name,
				pin->mux_mode, pin->config);
	}

	return 0;
}


设备树中的 mux_reg 和 conf_reg 值会保存在 info 参数中, input_reg、mux_mode、 input_val 和 config 值会保存在 grp 参数中。
pinctl和gpio子系统_第9张图片
第 560~564 行,获取 mux_reg、 conf_reg、 input_reg、 mux_mode 和 input_val 值。
第 570 行,获取 config (配置电气属性的值)值。
pinctl和gpio子系统_第10张图片

设备树中添加 pinctrl 节点模板(某个外设的节点)

接下来我们学习一下如何在设备树中添加某个外设 的 PIN 信 息 (就是把某个外设要用的IO全在这个节点里描述)。

关 于 I.MX 系 列 SOC 的 pinctrl 设 备 树 绑 定 信 息 可 以 参 考绑定 文 档 Documentation/devicetree/bindings/pinctrl/fsl,imx-pinctrl.txt。
这里我们虚拟一个名为“test”的设备, test 使用了 GPIO1_IO00 这个 PIN 的 GPIO 功能, pinctrl 节点添加过程如下:

1、创建对应的节点

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

1 pinctrl_test: testgrp {
2 			/* 具体的 PIN 信息 */
3 };
2、添加“fsl,pins”属性

设备树是通过属性来描述信息的,所以我们要创建一个属性来描述引脚控制信息,属性名字一定要是“fsl,pins”(参考绑定文档),属性的值就是要配置引脚的信息,pinctrl子系统会通过获取“fsl,pins”属性值来获取。

1 pinctrl_test: testgrp {
2 fsl,pins = <
3 MX6UL_PAD_GPIO1_IO00__GPIO1_IO00 config /*config 是电气属性置值*/
4 ……需要多少IO就写多少
5 >;
6 };

至此,我们已经在 imx6ull-alientek-emmc.dts 文件中添加好了 test 设备所使用的 引脚 配置信息。

gpio子系统

gpio 子系统简介

上一小节讲解了 pinctrl 子系统, pinctrl 子系统重点是设置 PIN(有的 SOC 叫做 PAD)的复用和电气属性,如果 pinctrl 子系统将一个 PIN 复用为 GPIO 的话,那么接下来就要用到 gpio 子系统了。

gpio 子系统顾名思义,就是用于初始化 GPIO 并且提供相应的 API 函数 ,比如设置 GPIO为输入输出,读取 GPIO 的值等。 gpio 子系统的主要目的就是方便驱动开发者使用 gpio,驱动开发者在设备树中添加 gpio 相关信息,然后就可以在驱动程序中使用 gpio 子系统提供的 API函数来操作 GPIO , Linux 内核向驱动开发者屏蔽掉了 GPIO 的设置过程,极大的方便了驱动开发者使用 GPIO。

(gpio子系统(内核程序)会提取设备树中gpio组的基地址,在驱动程序里固定了各个外设寄存器(GPIO_DR、GPIO_GDIR等)的偏移地址,并且提供可以设置gpio功能的API(方向、高低电平、中断等),我们只需要调用api就可以操作寄存器)

I.MX6ULL 的 gpio 子系统驱动

1、设备树中的 gpio 信息

I.MX6ULL-ALPHA 开发板上的 UART1_RTS_B 做为 SD 卡的检测引脚, UART1_RTS_B 复用为 GPIO1_IO19,通过读取这个 GPIO 的高低电平就可以知道 SD 卡有没有插入。首先肯定是将 UART1_RTS_B 这个 PIN 复用为 GPIO1_IO19,并且设置电气属性,也就是上一小节讲的pinctrl 节点。

pinctrl 配置好以后就是设置 gpio 了, SD 卡驱动程序通过读取 GPIO1_IO19 的值来判断 SD卡有没有插入,但是 SD 卡驱动程序怎么知道 CD 引脚连接的 GPIO1_IO19 呢?
SD 卡连接在

肯定是需要设备树告诉SD卡驱动程序。只需要在设备树中 SD 卡节点下添加一个属性来描述 SD 卡的 CD 引脚, SD卡驱动直接读取这个属性值就知道 SD 卡的 CD 引脚使用的是哪个 GPIO 了。
如图usdhc1节点中的cd-gpios属性就是描述具体使用哪个GPIO的:
I.MX6ULL 的 usdhc1 接口上,在 imx6ull-alientek-emmc.dts 中找到名为“usdhc1”的节点,这个节点就是 SD 卡设备节点。
pinctl和gpio子系统_第11张图片本来需要一行 pinctrl-3 = <&pinctrl_hog_1>; 此行本来没有,是作者添加的。
usdhc1 节点作为 SD 卡设备总节点, usdhc1 节点需要描述 SD 卡所有的信息,因为驱动要使用。本行就是描述 SD 卡的 CD 引脚 pinctrl 信息所在的子节点,因为 SD 卡驱动需要根据 pincrtl 节点信息来设置 CD 引脚的复用功能等。
pinctrl-0~2 都是 SD 卡其他 PIN 的 pincrtl 节点信息。

但是大家会发现,其实在 usdhc1 节点中并没有“pinctrl-3 = <&pinctrl_hog_1>”这一行,也就是说并没有指定 CD 引脚的 pinctrl 信息,
那么 SD 卡驱动就没法设置 CD 引脚的复用功能啊?
这个不用担心,因为在“iomuxc”节点下引用了 pinctrl_hog_1 这个节点,所以 Linux 内核中的 iomuxc 驱动就会自动初始化 pinctrl_hog_1
节点下的所有 PIN。
pinctl和gpio子系统_第12张图片
以上我们知道cd-gpios是描述了 SD 卡的 CD 引脚使用的哪个 IO。这个属性一共有三个值,分别是&gpio1、19和GPIO_ACTIVE_LOW。

&gpio1表示gpio1这个gpio组,19表示的是19号引脚,GPIO_ACTIVE_LOW表示是低电平有效。 如果换成GPIO_ACTIVE_HIGH那么就是高电平有效。
据上面这些信息, SD 卡驱动程序就可以使用 GPIO1_IO19 来检测 SD 卡的 CD 信号了。

打开 imx6ull.dtsi,在里面找到如下所示内容:
gpio1 节点信息描述了 GPIO1 控制器的所有信息,其中重点信息就是兼容性gpio外设寄存器的基地址
关 于 I.MX 系 列 SOC 的 GPIO 控 制 器 绑 定 信 息 请 查 看 文 档Documentation/devicetree/bindings/gpio/ fsl-imx-gpio.txt。
pinctl和gpio子系统_第13张图片第 516 行,设置 gpio1 节点的 compatible 属性有两个,分别为“fsl,imx6ul-gpio”和“fsl,imx35-gpio”,在 Linux 内核中搜索这两个字符串就可以找到 I.MX6UL 的 GPIO 驱动程序。
第 517 行,的 reg 属性设置了 GPIO1 控制器的寄存器基地址为 0X0209C000 ,可以打开《I.MX6ULL 参考手册》找到“Chapter 28:General Purpose Input/Output(GPIO)”章节,第 28.5 小节,有这个gpio的寄存器地址表:
pinctl和gpio子系统_第14张图片从图中可以看出GPIO外设寄存器组的基地址就是0X0209C000。
第 520 行,“gpio-controller”表示 gpio1 节点是个 GPIO 控制器。
第 521 行,“#gpio-cells”属性和“#address-cells”类似 (cell表示单元(计数)), #gpio-cells 应该为 2,表示一共有两个 cell,第一个 cell 为 GPIO 编号,比如“&gpio1 3”就表示 GPIO1_IO03第二个 cell 表示GPIO 极 性 , 如 果 为 0(GPIO_ACTIVE_HIGH) 的 话 表 示 高 电 平 有 效 , 如 果 为1(GPIO_ACTIVE_LOW)的话表示低电平有效。

2、 GPIO 驱动程序简介

gpio1 节点的 compatible 属性描述了兼容性,在 Linux 内核中搜索“fsl,imx6ul-gpio”和“fsl,imx35-gpio”这两个字符串,查找 GPIO 驱动文件。 drivers/gpio/gpio-mxc.c 就是 I.MX6ULL的 GPIO 驱动文件,在此文件中有如下所示 of_device_id 匹配表:
pinctl和gpio子系统_第15张图片
重点的gpio驱动,在 gpio-mxc.c 文件中有如下所示内容:
和pinctrl驱动一样,gpio驱动也是有struct platform_driver这个类型的结构体,其中的probe变量会调用到具体实现的函数mxc_gpio_probe
可以看出 GPIO 驱动也是个平台设备驱动因此当设备树中的设备节点与驱动的of_device_id 匹配以后 probe 函数就会执行
pinctl和gpio子系统_第16张图片

mxc_gpio_probe就是gpio驱动程序的入口,我们简单来分析一下 mxc_gpio_probe 这个函数,函数内容如下:
pinctl和gpio子系统_第17张图片第 405 行,设备树节点指针。
第 406 行,定义一个结构体指针 port,结构体类型为 mxc_gpio_port。 gpio-mxc.c 的重点工作就是维护 mxc_gpio_port, mxc_gpio_port 就是对 I.MX6ULL GPIO 的抽象。 mxc_gpio_port 结构体定义如下:

struct mxc_gpio_port {
	struct list_head node;
	void __iomem *base;
	int irq;
	int irq_high;
	struct irq_domain *domain;
	struct bgpio_chip bgc;			/**/
	u32 both_edges;
};

mxc_gpio_port 的 bgc 成员变量很重要,因为稍后的重点就是初始化 bgc。
继续回到 mxc_gpio_probe 函数函数,第 411 行调用 mxc_gpio_get_hw 函数获取 gpio 的硬件相关数据,其实就是 gpio 的寄存器组 ,函数 mxc_gpio_get_hw 里面有如下代码:

static void mxc_gpio_get_hw(struct platform_device *pdev)
{
	const struct of_device_id *of_id =
			of_match_device(mxc_gpio_dt_ids, &pdev->dev);
	enum mxc_gpio_hwtype hwtype;

	if (of_id)
		pdev->id_entry = of_id->data;
	hwtype = pdev->id_entry->driver_data;

	if (mxc_gpio_hwtype) {
		/*
		 * The driver works with a reasonable presupposition,
		 * that is all gpio ports must be the same type when
		 * running on one soc.
		 */
		BUG_ON(mxc_gpio_hwtype != hwtype);
		return;
	}

	if (hwtype == IMX35_GPIO)
		mxc_gpio_hwdata = &imx35_gpio_hwdata;
	else if (hwtype == IMX31_GPIO)
		mxc_gpio_hwdata = &imx31_gpio_hwdata;
	else
		mxc_gpio_hwdata = &imx1_imx21_gpio_hwdata;

	mxc_gpio_hwtype = hwtype;
}

pinctl和gpio子系统_第18张图片
注意第 385 行, mxc_gpio_hwdata 是个全局变量**,如果硬件类型是 IMX35_GPIO 的话设置mxc_gpio_hwdat 为imx35_gpio_hwdata** 。对于 I.MX6ULL 而言,硬件类型就是 IMX35_GPIO,imx35_gpio_hwdata 是个结构体变量,描述了 GPIO 寄存器组,内容如下:
可以发现其中的dr_reg变量的值加上前面gpio1控制器reg属性的值就是我们要设置的GPIO1_DR(设置gpio输出值)寄存器的地址,gdir_reg 加上gpio1控制器的基地址就是我们要设置的GPIO1_GDIR(设置gpio方向)寄存器的地址
这样我们后面就可以通过mxc_gpio_hwdata 这个全局变量来访问 GPIO 的相应寄存器了。
pinctl和gpio子系统_第19张图片

继 续 回 到 示 例 代 码 45.2.2.5 的 mxc_gpio_probe 函 数 中 , 第 417 行 , 调 用 函 数platform_get_resource 获取设备树中内存资源信息,也就是 reg(gpio控制器基地址) 属性值。这样根据前面的偏移值就可以操作gpio啦。

第 418 行,调用 devm_ioremap_resource 函数进行内存映射,得到 0x0209C000 在 Linux 内核中的虚拟地址。
第 422、 423 行,通过 platform_get_irq 函数获取中断号,第 422 行获取高 16 位 GPIO 的中断号,第 423 行获取底 16 位 GPIO 中断号。
第 428、 429 行,操作 GPIO1 的 IMR 和 ISR 这两个寄存器,关闭 GPIO1 所有 IO 中断,并且清除状态寄存器。
第 438~448 行,设置对应 GPIO 的中断服务函数,不管是高 16 位还是低 16 位,中断服务函数都是 mx3_gpio_irq_handler。
第 450~453 行, bgpio_init 函数第一个参数为 bgc,是 bgpio_chip 结构体指针。 bgpio_chip结构体有个 gc 成员变量, gc 是个 gpio_chip 结构体类型的变量。 gpio_chip 结构体是抽象出来的GPIO 控制器 , gpio_chip 结构体如下所示(有缩减):
pinctl和gpio子系统_第20张图片可以看出, gpio_chip 大量的成员都是函数,这些函数就是 GPIO 操作函数。 bgpio_init 函数主 要 任 务 就 是 初 始 化 bgc->gc 。
pinctl和gpio子系统_第21张图片

bgpio_init 里 面 有 三 个 setup 函 数 : bgpio_setup_io 、bgpio_setup_accessors 和 bgpio_setup_direction。这三个函数就是初始化 bgc->gc 中的各种有关GPIO 的操作,比如输出,输入等等。
第 451~453 行的 GPIO_PSR、 GPIO_DR 和 GPIO_GDIR 都是 I.MX6ULL 的 GPIO 寄存器。这些寄存器地址会赋值给 bgc 参数的reg_dat、 reg_set、 reg_clr和 reg_dir 这些成员变量。至此, bgc 既有了对 GPIO 的操作函数,又有了 I.MX6ULL 有关 GPIO的寄存器,那么只要得到 bgc 就可以对 I.MX6ULL 的 GPIO 进行操作。
继续回到mxc_gpio_probe函数,第461行调用函数gpiochip_add向Linux内核注册gpio_chip,也就是 port->bgc.gc。注册完成以后我们就可以在驱动中使用 gpiolib 提供的各个 API 函数。

3、gpio 子系统 API 函数

**获取gpio 编号 相关的of函数:**参考 include/linux/of_gpio.h
获取指定属性的gpio编号
int of_get_named_gpio_flags(struct device_node *np, const char *list_name, int index, enum of_gpio_flags *flags);
获取gpios 属性的gpio编号
static inline int of_get_gpio_flags(struct device_node *np, int index, enum of_gpio_flags *flags)
flags:dtb 中的gpio标志
在这里插入图片描述
在这里插入图片描述
申请与释放 gpio 编号: 参考 include/linux/gpio.h
例如:
int gpio_request(unsigned gpio, const char *label)
void gpio_free(unsigned gpio)
int gpio_request_one(unsigned gpio,unsigned long flags, const char *label)
int devm_gpio_request_one(struct device *dev, unsigned gpio, unsigned long flags, const char *label) //前缀为devm 的函数,在驱动remove 是会自动释放,无需手动释放
flags:表示需要设置的gpio标志(输入、输出、高电平有效、开漏等等)。具体参考下图
pinctl和gpio子系统_第22张图片
label:给gpio 设置个名字。

对于驱动开发人员,设置好设备树以后就可以使用 gpio 子系统提供的 API 函数来操作指定的 GPIO, gpio 子系统向驱动开发人员屏蔽了具体的读写寄存器过程。
gpio 子系统提供的常用的 API 函数有下面几个:

内核为每个gpio 分配编号来方便管理gpio,此函数用于向内核获取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 编号;负值,失败。

1、 gpio_request 函数

gpio_request 函数用于申请一个 GPIO 管脚在使用一个 GPIO 之前一定要使用 gpio_request进行申请。
gpio = <&gpio1 0 GPIO_ACTIVE_LOW>; 和 设置引脚的宏。

int gpio_request(unsigned gpio, const char *label)

参数含义如下:

gpio: 要申请的 gpio 标号,使用 of_get_named_gpio 函数从设备树获取指定 GPIO 属性信息,此函数会返回这个 GPIO 的标号。
label: 给 gpio 设置个名字。
返回值: 0,申请成功;其他值,申请失败。

GPIO 申请失败的原因大多是被其它外设占用了,可以在设备树中搜索相应的pinctrl 描述和 gpio描述。

2、 gpio_free 函数

和gpio_request函数相反,如果不使用某个 GPIO 了,那么就可以调用 gpio_free 函数进行释放。

void gpio_free(unsigned gpio)

函数参数和返回值含义如下:
gpio: 要释放的 gpio 标号。
返回值: 无。

3、 gpio_direction_input 函数

此函数用于设置某个 GPIO 为输入。

int gpio_direction_input(unsigned gpio)

函数参数和返回值含义如下:
gpio: 要设置为输入的 GPIO 标号。
返回值: 0,设置成功;负值,设置失败。

4、 gpio_direction_output 函数

此函数用于设置某个 GPIO 为输出,并且设置默认输出值,函数原型如下:

int gpio_direction_output(unsigned gpio, int value)

函数参数和返回值含义如下:
gpio: 要设置为输出的 GPIO 标号。
value: GPIO 默认输出值。
返回值: 0,设置成功;负值,设置失败。

5、 gpio_get_value 函数

此函数用于获取某个 GPIO 的值(0 或 1),此函数是个宏,定义所示:

#define gpio_get_value __gpio_get_value
int __gpio_get_value(unsigned gpio)

函数参数和返回值含义如下:
gpio: 要获取的 GPIO 标号。
返回值: 非负值,得到的 GPIO 值;负值,获取失败。

6、 gpio_set_value 函数

此函数用于设置某个 GPIO 的值,此函数是个宏,定义如下:

#define gpio_set_value __gpio_set_value
void __gpio_set_value(unsigned gpio, int value)

函数参数和返回值含义如下:
gpio:要设置的 GPIO 标号。
value: 要设置的值。
返回值: 无
关于 gpio 子系统常用的 API 函数就讲这些,这些是我们用的最多的。

设备树中添加 gpio 节点模板

前面我们创建了 pinctrl_test 节点,此节点描述了 test 设备所使用的 GPIO1_IO00 这个 PIN 的信息,我们要将这节点添加到 test 设备节点中,如下所示:

1 test {
2		compatible = "imx6ull-gpioled";			 
2 		pinctrl-names = "default";
4		pinctrl-0 = <&pinctrl_test>;
5 		gpio = <&gpio1 0 GPIO_ACTIVE_LOW>;
6		status = "okay";
7 };

第 2 行,添加 compatible 是为了养成习惯让节点更完整,虽然我们的简单驱动中用不着。
第 3 行,添加 pinctrl-names 属性,此属性描述 pinctrl 名字为“default”。
第 4 行,添加 pinctrl-0 节点,此节点引用 45.1.3 中创建的 pinctrl_test 节点,表示 tset 设备的所使用的 PIN 信息保存在 pinctrl_test 节点中。
第 5 行,我们最后需要在 test 节点中添加 GPIO 属性信息,表明 test 所使用的 GPIO 是哪个引脚。

关于 pinctrl 子系统和 gpio 子系统就讲解到这里,接下来就使用 pinctrl 和 gpio 子系统来驱动 I.MX6ULL-ALPHA 开发板上的 LED 灯。

你可能感兴趣的:(Linux驱动,驱动开发)