I.MX6ULL_Linux_驱动篇(33) pinctrl与gpio子系统

news2024/11/24 15:28:25

上一章我们编写了基于设备树的 LED 驱动,但是驱动的本质还是没变,都是配置 LED 灯所使用的 GPIO 寄存器,驱动开发方式和裸机基本没啥区别。 Linux 是一个庞大而完善的系统,尤其是驱动框架,像 GPIO 这种最基本的驱动不可能采用“原始”的裸机驱动开发方式,否则就相当于你买了一辆车,结果每天推着车去上班。 Linux 内核提供了 pinctrl 和 gpio 子系统用于GPIO 驱动,本章我们就来学习一下如何借助 pinctrl 和 gpio 子系统来简化 GPIO 驱动开发。

pinctrl 子系统

简介

Linux 驱动讲究驱动分离与分层, pinctrl 和 gpio 子系统就是驱动分离与分层思想下的产物,驱动分离与分层其实就是按照面向对象编程的设计思想而设计的设备驱动框架,关于驱动的分离与分层我们后面会讲。我们先来回顾一下上一章是怎么初始化 LED 灯所使用的 GPIO,步骤如下:
①、修改设备树, 添加相应的节点,节点里面重点是设置 reg 属性, reg 属性包括了 GPIO相关寄存器。
② 、 获 取 reg 属 性 中 IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 和IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03 这两个寄存器地址,并且初始化这两个寄存器,这两个寄存器用于设置 GPIO1_IO03 这个 PIN 的复用功能、上下拉、速度等。
③、在②里面将 GPIO1_IO03 这个 PIN 复用为了 GPIO 功能,因此需要设置 GPIO1_IO03这个 GPIO 相关的寄存器,也就是 GPIO1_DR 和 GPIO1_GDIR 这两个寄存器。

总结一下,②中完成对 GPIO1_IO03 这个 PIN 的初始化,设置这个 PIN 的复用功能、上下拉等,比如将 GPIO_IO03 这个 PIN 设置为 GPIO 功能。③中完成对 GPIO 的初始化,设置 GPIO
为输入/输出等。如果使用过 STM32 的话应该都记得, STM32 也是要先设置某个 PIN 的复用功能、速度、上下拉等,然后再设置 PIN 所对应的 GPIO。其实对于大多数的 32 位 SOC 而言,引
脚的设置基本都是这两方面,因此 Linux 内核针对 PIN 的配置推出了 pinctrl 子系统,对于 GPIO的配置推出了 gpio 子系统。本节我们来学习 pinctrl 和 gpio 子系统。

大多数 SOC 的 pin 都是支持复用的,比如 I.MX6ULL 的 GPIO1_IO03 既可以作为普通的GPIO 使用,也可以作为 I2C1 的 SDA 等等。此外我们还需要配置 pin 的电气特性,比如上/下拉、速度、驱动能力等等。传统的配置 pin 的方式就是直接操作相应的寄存器,但是这种配置方式比较繁琐、而且容易出问题(比如 pin 功能冲突)。 pinctrl 子系统就是为了解决这个问题而引入的, pinctrl 子系统主要工作内容如下:
①、获取设备树中 pin 信息。
②、根据获取到的 pin 信息来设置 pin 的复用功能
③、根据获取到的 pin 信息来设置 pin 的电气特性,比如上/下拉、速度、驱动能力等。
对于我们使用者来讲,只需要在设备树里面设置好某个 pin 的相关属性即可,其他的初始化工作均由 pinctrl 子系统来完成, pinctrl 子系统源码目录为 drivers/pinctrl。

案例详解

要使用 pinctrl 子系统,我们需要在设备树里面设置 PIN 的配置信息,毕竟 pinctrl 子系统要根据你提供的信息来配置 PIN 功能,一般会在设备树里面创建一个节点来描述 PIN 的配置信
息。打开 imx6ull.dtsi 文件,找到一个叫做 iomuxc 的节点,如下所示:

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

iomuxc 节点就是 I.MX6ULL 的 IOMUXC 外设对应的节点,看起来内容很少,没看出什么跟 PIN 的配置有关的内容啊,别急!打开 imx6ull-alientek-emmc.dts,找到如下所示内容:

311 &iomuxc {
312     pinctrl-names = "default";
313     pinctrl-0 = <&pinctrl_hog_1>;
314 imx6ul-evk {
315     pinctrl_hog_1: hoggrp-1 {
316     fsl,pins = <
317         MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059
318         MX6UL_PAD_GPIO1_IO05__USDHC1_VSELECT 0x17059
319         MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0x17059
320         MX6UL_PAD_GPIO1_IO00__ANATOP_OTG1_ID 0x13058
321     >;
322 };
......
371 pinctrl_flexcan1: flexcan1grp{
372     fsl,pins = <
373         MX6UL_PAD_UART3_RTS_B__FLEXCAN1_RX 0x1b020
374         MX6UL_PAD_UART3_CTS_B__FLEXCAN1_TX 0x1b020
375     >;
376 };
......
587 pinctrl_wdog: wdoggrp {
588     fsl,pins = <
589         MX6UL_PAD_LCD_RESET__WDOG1_WDOG_ANY 0x30b0
590     >;
591 };
592 };
593 };

上面示例代码就是向 iomuxc 节点追加数据,不同的外设使用的 PIN 不同、其配置也不同,因此一个萝卜一个坑,将某个外设所使用的所有 PIN 都组织在一个子节点里面。示例代码中 pinctrl_hog_1 子节点就是和热插拔有关的 PIN 集合,比如 USB OTG 的 ID 引脚。pinctrl_flexcan1 子节点是 flexcan1 这个外设所使用的 PIN, pinctrl_wdog 子节点是 wdog 外设所使用的 PIN。如果需要在 iomuxc 中添加我们自定义外设的 PIN,那么需要新建一个子节点,然后将这个自定义外设的所有 PIN 配置信息都放到这个子节点中。

将其与示前面iomuxc节点结合起来就可以得到完成的 iomuxc 节点,如下所示:

1 iomuxc: iomuxc@020e0000 {
2 compatible = "fsl,imx6ul-iomuxc";
3 reg = <0x020e0000 0x4000>;
4 pinctrl-names = "default";
5 pinctrl-0 = <&pinctrl_hog_1>;
6 imx6ul-evk {
7 pinctrl_hog_1: hoggrp-1 {
8 fsl,pins = <
9 MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059
10 MX6UL_PAD_GPIO1_IO05__USDHC1_VSELECT 0x17059
11 MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0x17059
12 MX6UL_PAD_GPIO1_IO00__ANATOP_OTG1_ID 0x13058
13 >;
......
16 };
17 };
18 };

第 2 行, compatible 属性值为“fsl,imx6ul-iomuxc”,前面讲解设备树的时候说过, Linux 内核会根据 compatbile 属性值来查找对应的驱动文件,所以我们在 Linux 内核源码中全局搜索字符串“fsl,imx6ul-iomuxc”就会找到 I.MX6ULL 这颗 SOC 的 pinctrl 驱动文件。稍后我们会讲解这个 pinctrl 驱动文件。
第 9~12 行, pinctrl_hog_1 子节点所使用的 PIN 配置信息,我们就以第 9 行的 UART1_RTS_B这个 PIN 为例,讲解一下如何添加 PIN 的配置信息, UART1_RTS_B 的配置信息如下:

MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059

首先说明一下, UART1_RTS_B 这个 PIN 是作为 SD 卡的检测引脚,也就是通过此 PIN就可以检测到SD卡是否有插入。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,这是一个宏定义,定义在文件arch/arm/boot/dts/imx6ul-pinfunc.h 中, imx6ull.dtsi 会引用 imx6ull-pinfunc.h 这个头文件,而
imx6ull-pinfunc.h 又会引用 imx6ul-pinfunc.h 这个头文件(绕啊绕!)。从这里可以看出,可以在设备树中引用 C 语言中.h 文件中的内容。 MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 的宏定
义内容如下:

190 #define MX6UL_PAD_UART1_RTS_B__UART1_DCE_RTS 0x0090 0x031C 0x0620 0x0 0x3
191 #define MX6UL_PAD_UART1_RTS_B__UART1_DTE_CTS 0x0090 0x031C 0x0000 0x0 0x0
192 #define MX6UL_PAD_UART1_RTS_B__ENET1_TX_ER 0x0090 0x031C 0x0000 0x1 0x0
193 #define MX6UL_PAD_UART1_RTS_B__USDHC1_CD_B 0x0090 0x031C 0x0668 0x2 0x1
194 #define MX6UL_PAD_UART1_RTS_B__CSI_DATA05 0x0090 0x031C 0x04CC 0x3 0x1
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

上述定义中一共有 8 个以“MX6UL_PAD_UART1_RTS_B”开头的宏定义,大家仔细观察应该就能发现,这 8 个宏定义分别对应 UART1_RTS_B 这个 PIN 的 8 个复用 IO。查
阅《I.MX6ULL 参考手册》可以知 UART1_RTS_B 的可选复用 IO 如图所示:

示例代码196 行的宏定义MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 表示将UART1_RTS_B 这个 IO 复用为 GPIO1_IO19。此宏定义后面跟着 5 个数字,也就是这个宏定义的具体值,如下所示:

0x0090 0x031C 0x0000 0x5 0x0

这5个值的含义如下所示:

<mux_reg conf_reg input_reg mux_mode input_val>

综上所述可知:
0x0090: mux_reg 寄存器偏移地址,设备树中的 iomuxc 节点就是 IOMUXC 外设对应的节点 , 根据其reg属性可知IOMUXC外设寄存器起始地址为0x020e0000。因此
0x020e0000+0x0090=0x020e0090, IOMUXC_SW_MUX_CTL_PAD_UART1_RTS_B 寄存器地址正好是0x020e0090,大家可以在《 IMX6ULL 参 考 手 册 》中找到
IOMUXC_SW_MUX_CTL_PAD_UART1_RTS_B 这个寄存器的位域图,如图所示:

因此可知, 0x020e0000+mux_reg 就是 PIN 的复用寄存器地址。
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。
0x0: input_reg 寄存器值,在这里无效。
这就是宏 MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 的含义,看的比较仔细的同学应该会发现并没有 conf_reg 寄存器的值, config_reg 寄存器是设置一个 PIN 的电气特性的,这么重
要的寄存器怎么没有值呢?回顾前面的定义,内容如下所示:

MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059

MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 我们上面已经分析了,就剩下了一个 0x17059,反应快的同学应该已经猜出来了, 0x17059 就是 conf_reg 寄存器值!此值由用户自行设置,通
过此值来设置一个 IO 的上/下拉、驱动能力和速度等。在这里就相当于设置寄存器IOMUXC_SW_PAD_CTL_PAD_UART1_RTS_B 的值为 0x17059。

驱动程序分析

所有的东西都已经准备好了,包括寄存器地址和寄存器值, Linux 内核相应的驱动文件就会根据这些值来做相应的初始化。接下来就找一下哪个驱动文件来做这一件事情, iomuxc 节点
中 compatible 属性的值为“fsl,imx6ul-iomuxc”,在 Linux 内核中全局搜索“fsl,imx6ul-iomuxc”字符串就会找到对应的驱动文件。在文件drivers/pinctrl/freescale/pinctrl-imx6ul.c中有如下内容:

326 static struct of_device_id imx6ul_pinctrl_of_match[] = {
327 { .compatible = "fsl,imx6ul-iomuxc", .data = &imx6ul_pinctrl_info, },
328 { .compatible = "fsl,imx6ull-iomuxc-snvs", .data = &imx6ull_snvs_pinctrl_info, },
329 { /* sentinel */ }
330 };
331
332 static int imx6ul_pinctrl_probe(struct platform_device *pdev)
333 {
334     const struct of_device_id *match;
335     struct imx_pinctrl_soc_info *pinctrl_info;
336
337     match = of_match_device(imx6ul_pinctrl_of_match, &pdev->dev);
338
339     if (!match)
340         return -ENODEV;
341
342     pinctrl_info = (struct imx_pinctrl_soc_info *) match->data;
343
344     return imx_pinctrl_probe(pdev, pinctrl_info);
345 }
346
347 static struct platform_driver imx6ul_pinctrl_driver = {
348     .driver = {
349     .name = "imx6ul-pinctrl",
350     .owner = THIS_MODULE,
351     .of_match_table = of_match_ptr(imx6ul_pinctrl_of_match),
352 },
353     .probe = imx6ul_pinctrl_probe,
354     .remove = imx_pinctrl_remove,
355 };

第 326~330 行, of_device_id 结构体数组,前面讲解设备树的时候说过了, 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 配置工作。
第 347~355 行, platform_driver 是平台设备驱动,这个是我们后面章节要讲解的内容,platform_driver 是个结构体,有个 probe 成员变量。在这里大家只需要知道,当设备和驱动匹配成功以后 platform_driver 的 probe 成员变量所代表的函数就会执行,在 353 行设置 probe 成员变量为 imx6ul_pinctrl_probe 函数,因此在本章实验中 imx6ul_pinctrl_probe 这个函数就会执行,可以认为 imx6ul_pinctrl_probe 函数就是 I.MX6ULL 这个 SOC 的 PIN 配置入口函数。以此为入口,如图所示的函数调用路径:

在图中函数 imx_pinctrl_parse_groups 负责获取设备树中关于 PIN 的配置信息,也就是我们前面分析的那 6 个 u32 类型的值。处理过程如下所示:

488 /*
489 * Each pin represented in fsl,pins consists of 5 u32 PIN_FUNC_ID
490 * and 1 u32 CONFIG, so 24 types in total for each pin.
491 */
492 #define FSL_PIN_SIZE 24
493 #define SHARE_FSL_PIN_SIZE 20
494
495 static int imx_pinctrl_parse_groups(struct device_node *np,
496 struct imx_pin_group *grp,
497 struct imx_pinctrl_soc_info *info,
498 u32 index)
499 {
500     int size, pin_size;
501     const __be32 *list;
502     int i;
503     u32 config;
......
537
538     for (i = 0; i < grp->npins; i++) {
539         u32 mux_reg = be32_to_cpu(*list++);
540         u32 conf_reg;
541         unsigned int pin_id;
542         struct imx_pin_reg *pin_reg;
543         struct imx_pin *pin = &grp->pins[i];
544
......
555
556         pin_id = (mux_reg != -1) ? mux_reg / 4 : conf_reg / 4;
557         pin_reg = &info->pin_regs[pin_id];
558         pin->pin = pin_id;
559         grp->pin_ids[i] = pin_id;
560         pin_reg->mux_reg = mux_reg;
561         pin_reg->conf_reg = conf_reg;
562         pin->input_reg = be32_to_cpu(*list++);
563         pin->mux_mode = be32_to_cpu(*list++);
564         pin->input_val = be32_to_cpu(*list++);
565
566 /* SION bit is in mux register */
567         config = be32_to_cpu(*list++);
568         if (config & IMX_PAD_SION)
569         pin->mux_mode |= IOMUXC_CONFIG_SION;
570         pin->config = config & ~IMX_PAD_SION;
......
574 }
575
576     return 0;
577 }

第 496 和 497 行,设备树中的 mux_reg 和 conf_reg 值会保存在 info 参数中, input_reg、mux_mode、 input_val 和 config 值会保存在 grp 参数中。
第 560~564 行,获取 mux_reg、 conf_reg、 input_reg、 mux_mode 和 input_val 值。
第 570 行,获取 config 值。
接下来看一下函数 pinctrl_register,此函数用于向 Linux 内核注册一个 PIN 控制器,此函数原型如下:

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

参数 pctldesc 非常重要,因为此参数就是要注册的 PIN 控制器, PIN 控制器用于配置 SOC的 PIN 复用功能和电气特性。参数 pctldesc 是 pinctrl_desc 结构体类型指针, pinctrl_desc 结构体如下所示:

128 struct pinctrl_desc {
129     const char *name;
130     struct pinctrl_pin_desc const *pins;
131     unsigned int npins;
132     const struct pinctrl_ops *pctlops;
133     const struct pinmux_ops *pmxops;
134     const struct pinconf_ops *confops;
135     struct module *owner;
136 #ifdef CONFIG_GENERIC_PINCONF
137     unsigned int num_custom_params;
138     const struct pinconf_generic_params *custom_params;
139     const struct pin_config_item *custom_conf_items;
140 #endif
141 };

第 132~134 行,这三个“_ops”结构体指针非常重要!!!因为这三个结构体就是 PIN 控制器的“工具”,这三个结构体里面包含了很多操作函数,通过这些操作函数就可以完成对某一个PIN 的配置。 pinctrl_desc 结构体需要由用户提供,结构体里面的成员变量也是用户提供的。但是这个用户并不是我们这些使用芯片的程序员,而是半导体厂商,半导体厂商发布的 Linux 内核源码中已经把这些工作做完了。比如在 imx_pinctrl_probe 函数中可以找到如下所示代码:

648 int imx_pinctrl_probe(struct platform_device *pdev,
649 struct imx_pinctrl_soc_info *info)
650 {
651     struct device_node *dev_np = pdev->dev.of_node;
652     struct device_node *np;
653     struct imx_pinctrl *ipctl;
654     struct resource *res;
655     struct pinctrl_desc *imx_pinctrl_desc;
......
663
664     imx_pinctrl_desc = devm_kzalloc(&pdev->dev, sizeof(*imx_pinctrl_desc),
665     GFP_KERNEL);
666     if (!imx_pinctrl_desc)
667     return -ENOMEM;
......
705
706     imx_pinctrl_desc->name = dev_name(&pdev->dev);
707     imx_pinctrl_desc->pins = info->pins;
708     imx_pinctrl_desc->npins = info->npins;
709     imx_pinctrl_desc->pctlops = &imx_pctrl_ops;
710     imx_pinctrl_desc->pmxops = &imx_pmx_ops;
711     imx_pinctrl_desc->confops = &imx_pinconf_ops;
712     imx_pinctrl_desc->owner = THIS_MODULE;
......
723     ipctl->pctl = pinctrl_register(imx_pinctrl_desc, &pdev->dev, ipctl);
......
732 }

第 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 进行配置的工具。
imx_pctrl_ops、 imx_pmx_ops 和 imx_pinconf_ops 这三个结构体定义如下:

174 static const struct pinctrl_ops imx_pctrl_ops = {
175     .get_groups_count = imx_get_groups_count,
176     .get_group_name = imx_get_group_name,
177     .get_group_pins = imx_get_group_pins,
178     .pin_dbg_show = imx_pin_dbg_show,
179     .dt_node_to_map = imx_dt_node_to_map,
180     .dt_free_map = imx_dt_free_map,
181
182 };
......
374 static const struct pinmux_ops imx_pmx_ops = {
375     .get_functions_count = imx_pmx_get_funcs_count,
376     .get_function_name = imx_pmx_get_func_name,
377     .get_function_groups = imx_pmx_get_groups,
378     .set_mux = imx_pmx_set,
379     .gpio_request_enable = imx_pmx_gpio_request_enable,
380     .gpio_set_direction = imx_pmx_gpio_set_direction,
381 };
......
481 static const struct pinconf_ops imx_pinconf_ops = {
482     .pin_config_get = imx_pinconf_get,
483     .pin_config_set = imx_pinconf_set,
484     .pin_config_dbg_show = imx_pinconf_dbg_show,
485     .pin_config_group_dbg_show = imx_pinconf_group_dbg_show,
486 };

应用

我们已经对 pinctrl 有了比较深入的了解,接下来我们学习一下如何在设备树中添加某个外设 的 PIN 信 息 。 关 于 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”,因为对于 I.MX 系列 SOC 而言, pinctrl 驱动程序是通过读取“fsl,pins”属性值来获取 PIN 的配置信息,完成以后如下所示:

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

3、在“fsl,pins”属性中添加 PIN 配置信息
最后在“fsl,pins”属性中添加具体的 PIN 配置信息,完成以后如下所示:

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

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

gpio 子系统

简介

上一小节讲解了 pinctrl 子系统, pinctrl 子系统重点是设置 PIN(有的 SOC 叫做 PAD)的复用和电气属性,如果 pinctrl 子系统将一个 PIN 复用为 GPIO 的话,那么接下来就要用到 gpio 子系了。 gpio 子系统顾名思义,就是用于初始化 GPIO 并且提供相应的 API 函数,比如设置 GPIO为输入输出,读取 GPIO 的值等。 gpio 子系统的主要目的就是方便驱动开发者使用 gpio,驱动开发者在设备树中添加 gpio 相关信息,然后就可以在驱动程序中使用 gpio 子系统提供的 API函数来操作 GPIO, Linux 内核向驱动开发者屏蔽掉了 GPIO 的设置过程,极大的方便了驱动开发者使用GPIO。

案例详解

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

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

第 318 行,设置 UART1_RTS_B 这个 PIN 为 GPIO1_IO19。
pinctrl 配置好以后就是设置 gpio 了, SD 卡驱动程序通过读取 GPIO1_IO19 的值来判断 SD卡有没有插入,但是 SD 卡驱动程序怎么知道 CD 引脚连接的 GPIO1_IO19 呢?肯定是需要设备树告诉驱动啊!在设备树中 SD 卡节点下添加一个属性来描述 SD 卡的 CD 引脚就行了, SD卡驱动直接读取这个属性值就知道 SD 卡的 CD 引脚使用的是哪个 GPIO 了。 SD 卡连接在I.MX6ULL 的 usdhc1 接口上,在 imx6ull-alientek-emmc.dts 中找到名为“usdhc1”的节点,这个节点就是 SD 卡设备节点,如下所示:

760 &usdhc1 {
761     pinctrl-names = "default", "state_100mhz", "state_200mhz";
762     pinctrl-0 = <&pinctrl_usdhc1>;
763     pinctrl-1 = <&pinctrl_usdhc1_100mhz>;
764     pinctrl-2 = <&pinctrl_usdhc1_200mhz>;
765     /* pinctrl-3 = <&pinctrl_hog_1>; */
766     cd-gpios = <&gpio1 19 GPIO_ACTIVE_LOW>;
767     keep-power-in-suspend;
768     enable-sdio-wakeup;
769     vmmc-supply = <&reg_sd1_vmmc>;
770     status = "okay";
771 };

第 765 行,此行本来没有,是作者添加的, usdhc1 节点作为 SD 卡设备总节点, usdhc1 节点需要描述 SD 卡所有的信息,因为驱动要使用。本行就是描述 SD 卡的 CD 引脚 pinctrl 信息所在的子节点,因为 SD 卡驱动需要根据 pincrtl 节点信息来设置 CD 引脚的复用功能等。762~764行的 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。
第 766 行,属性“cd-gpios”描述了 SD 卡的 CD 引脚使用的哪个 IO。属性值一共有三个,我们来看一下这三个属性值的含义,“&gpio1”表示 CD 引脚所使用的 IO 属于 GPIO1 组,“19”表示 GPIO1组的第19号IO,通过这两个值SD卡驱动程序就知道CD引脚使用了GPIO1_IO19这 GPIO。“GPIO_ACTIVE_LOW”表示低电平有效,如果改为“GPIO_ACTIVE_HIGH”就表示高电平有效。
根据上面这些信息, SD 卡驱动程序就可以使用 GPIO1_IO19 来检测 SD 卡的 CD 信号了,打开 imx6ull.dtsi,在里面找到如下所示内容:

504 gpio1: gpio@0209c000 {
505     compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
506     reg = <0x0209c000 0x4000>;
507     interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>,
508                 <GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>;
509     gpio-controller;
510     #gpio-cells = <2>;
511     interrupt-controller;
512     #interrupt-cells = <2>;
513 };

gpio1 节点信息描述了 GPIO1 控制器的所有信息,重点就是 GPIO1 外设寄存器基地址以及兼 容 属 性 。 关 于 I.MX 系 列 SOC 的 GPIO 控 制 器 绑 定 信 息 请 查 看 文 档
Documentation/devicetree/bindings/gpio/ fsl-imx-gpio.txt。
第 505 行,设置 gpio1 节点的 compatible 属性有两个,分别为“fsl,imx6ul-gpio”和“fsl,imx35-gpio”,在 Linux 内核中搜索这两个字符串就可以找到 I.MX6UL 的 GPIO 驱动程序。
第 506 行, reg 属性设置了 GPIO1 控制器的寄存器基地址为 0X0209C000,大家可以打开《I.MX6ULL 参考手册》找到“Chapter 28:General Purpose Input/Output(GPIO)”章节第 28.5 小节,有如图所示的寄存器地址表:

从图可以看出, GPIO1 控制器的基地址就是 0X0209C000。
第 509 行,“gpio-controller”表示 gpio1 节点是个 GPIO 控制器。
第 510 行,“#gpio-cells”属性和“#address-cells”类似, #gpio-cells 应该为 2,表示一共有两个 cell,第一个 cell 为 GPIO 编号,比如“&gpio1 3”就表示 GPIO1_IO03。第二个 cell 表示
GPIO 极 性 , 如 果 为 0(GPIO_ACTIVE_HIGH) 的 话 表 示 高 电 平 有 效 , 如 果 为1(GPIO_ACTIVE_LOW)的话表示低电平有效。

驱动程序分析

gpio1 节点的 compatible 属性描述了兼容性,在 Linux 内核中搜索“fsl,imx6ul-gpio”和“fsl,imx35-gpio”这两个字符串,查找 GPIO 驱动文件。 drivers/gpio/gpio-mxc.c 就是 I.MX6ULL
的 GPIO 驱动文件,在此文件中有如下所示 of_device_id 匹配表:

152 static const struct of_device_id mxc_gpio_dt_ids[] = {
153     { .compatible = "fsl,imx1-gpio", .data = &mxc_gpio_devtype[IMX1_GPIO], },
154     { .compatible = "fsl,imx21-gpio", .data = &mxc_gpio_devtype[IMX21_GPIO], },
155     { .compatible = "fsl,imx31-gpio", .data = &mxc_gpio_devtype[IMX31_GPIO], },
156     { .compatible = "fsl,imx35-gpio", .data = &mxc_gpio_devtype[IMX35_GPIO], },
157     { /* sentinel */ }
158 };

第 156 行的 compatible 值为“fsl,imx35-gpio”,和 gpio1 的 compatible 属性匹配,因此 gpiomxc.c 就是 I.MX6ULL 的 GPIO 控制器驱动文件。 gpio-mxc.c 所在的目录为 drivers/gpio,打开这
个目录可以看到很多芯片的 gpio 驱动文件, “gpiolib”开始的文件是 gpio 驱动的核心文件,如图所示:

 我们重点来看一下 gpio-mxc.c 这个文件,在 gpio-mxc.c 文件中有如下所示内容:

496 static struct platform_driver mxc_gpio_driver = {
497     .driver = {
498     .name = "gpio-mxc",
499     .of_match_table = mxc_gpio_dt_ids,
500     },
501     .probe = mxc_gpio_probe,
502     .id_table = mxc_gpio_devtype,
503 };

可以看出 GPIO 驱动也是个平台设备驱动,因此当设备树中的设备节点与驱动的of_device_id 匹配以后 probe 函数就会执行,在这里就是 mxc_gpio_probe 函数,这个函数就是I.MX6ULL 的 GPIO 驱动入口函数。我们简单来分析一下 mxc_gpio_probe 这个函数,函数内容如下:

403 static int mxc_gpio_probe(struct platform_device *pdev)
404 {
405     struct device_node *np = pdev->dev.of_node;
406     struct mxc_gpio_port *port;
407     struct resource *iores;
408     int irq_base;
409     int err;
410
411     mxc_gpio_get_hw(pdev);
412
413     port = devm_kzalloc(&pdev->dev, sizeof(*port), GFP_KERNEL);
414     if (!port)
415         return -ENOMEM;
416
417     iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
418     port->base = devm_ioremap_resource(&pdev->dev, iores);
419     if (IS_ERR(port->base))
420         return PTR_ERR(port->base);
421
422     port->irq_high = platform_get_irq(pdev, 1);
423     port->irq = platform_get_irq(pdev, 0);
424     if (port->irq < 0)
425         return port->irq;
426
427     /* disable the interrupt and clear the status */
428     writel(0, port->base + GPIO_IMR);
429     writel(~0, port->base + GPIO_ISR);
430
431     if (mxc_gpio_hwtype == IMX21_GPIO) {
432 /*
433 * Setup one handler for all GPIO interrupts. Actually
434 * setting the handler is needed only once, but doing it for
435 * every port is more robust and easier.
436 */
437     irq_set_chained_handler(port->irq, mx2_gpio_irq_handler);
438     } else {
439 /* setup one handler for each entry */
440     irq_set_chained_handler(port->irq, mx3_gpio_irq_handler);
441     irq_set_handler_data(port->irq, port);
442     if (port->irq_high > 0) {
443 /* setup handler for GPIO 16 to 31 */
444     irq_set_chained_handler(port->irq_high,
445     mx3_gpio_irq_handler);
446     irq_set_handler_data(port->irq_high, port);
447     }
448 }
449
450     err = bgpio_init(&port->bgc, &pdev->dev, 4,
451     port->base + GPIO_PSR,
452     port->base + GPIO_DR, NULL,
453     port->base + GPIO_GDIR, NULL, 0);
454     if (err)
455         goto out_bgio;
456
457     port->bgc.gc.to_irq = mxc_gpio_to_irq;
458     port->bgc.gc.base = (pdev->id < 0) ? of_alias_get_id(np, "gpio")
459     * 32 : pdev->id * 32;
460
461     err = gpiochip_add(&port->bgc.gc);
462     if (err)
463         goto out_bgpio_remove;
464
465     irq_base = irq_alloc_descs(-1, 0, 32, numa_node_id());
466     if (irq_base < 0) {
467         err = irq_base;
468     goto out_gpiochip_remove;
469 }
470
471     port->domain = irq_domain_add_legacy(np, 32, irq_base, 0,
472     &irq_domain_simple_ops, NULL);
473     if (!port->domain) {
474         err = -ENODEV;
475     goto out_irqdesc_free;
476 }
477
478 /* gpio-mxc can be a generic irq chip */
479     mxc_gpio_init_gc(port, irq_base);
480
481     list_add_tail(&port->node, &mxc_gpio_ports);
482
483     return 0;
......
494 }

第 405 行,设备树节点指针。
第 406 行,定义一个结构体指针 port,结构体类型为 mxc_gpio_port。 gpio-mxc.c 的重点工作就是维护 mxc_gpio_port, mxc_gpio_port 就是对 I.MX6ULL GPIO 的抽象。 mxc_gpio_port 结构体定义如下:

61 struct mxc_gpio_port {
62     struct list_head node;
63     void __iomem *base;
64     int irq;
65     int irq_high;
66     struct irq_domain *domain;
67     struct bgpio_chip bgc;
68     u32 both_edges;
69 };

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

364 static void mxc_gpio_get_hw(struct platform_device *pdev)
365 {
366     const struct of_device_id *of_id =
367     of_match_device(mxc_gpio_dt_ids, &pdev->dev);
368     enum mxc_gpio_hwtype hwtype;
......
383
384     if (hwtype == IMX35_GPIO)
385         mxc_gpio_hwdata = &imx35_gpio_hwdata;
386     else if (hwtype == IMX31_GPIO)
387         mxc_gpio_hwdata = &imx31_gpio_hwdata;
388     else
389         mxc_gpio_hwdata = &imx1_imx21_gpio_hwdata;
390
391     mxc_gpio_hwtype = hwtype;
392 }

注意第 385 行, mxc_gpio_hwdata 是个全局变量,如果硬件类型是 IMX35_GPIO 的话设置mxc_gpio_hwdat 为 imx35_gpio_hwdata。对于 I.MX6ULL 而言,硬件类型就是 IMX35_GPIO,
imx35_gpio_hwdata 是个结构体变量,描述了 GPIO 寄存器组,内容如下:

101 static struct mxc_gpio_hwdata imx35_gpio_hwdata = {
102     .dr_reg = 0x00,
103     .gdir_reg = 0x04,
104     .psr_reg = 0x08,
105     .icr1_reg = 0x0c,
106     .icr2_reg = 0x10,
107     .imr_reg = 0x14,
108     .isr_reg = 0x18,
109     .edge_sel_reg = 0x1c,
110     .low_level = 0x00,
111     .high_level = 0x01,
112     .rise_edge = 0x02,
113     .fall_edge = 0x03,
114 };

大家将 imx35_gpio_hwdata 中的各个成员变量和GPIO 寄存器表对比就会发现, imx35_gpio_hwdata 结构体就是 GPIO 寄存器组结构。这样我们后面就可以通过mxc_gpio_hwdata 这个全局变量来访问 GPIO 的相应寄存器了。

继 续 回 到 示 例 代 码mxc_gpio_probe 函 数 中:

第417行,调用函数platform_get_resource 获取设备树中内存资源信息,也就是 reg 属性值。前面说了 reg 属性指定了 GPIO1 控制器的寄存器基地址为 0X0209C000,在配合前面已经得到的 mxc_gpio_hwdata,这样 Linux 内核就可以访问 gpio1 的所有寄存器了。
第 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 结构体如下所示(有缩减):

74 struct gpio_chip {
75     const char *label;
76     struct device *dev;
77     struct module *owner;
78     struct list_head list;
79
80     int (*request)(struct gpio_chip *chip,
81     unsigned offset);
82     void (*free)(struct gpio_chip *chip,
83     unsigned offset);
84     int (*get_direction)(struct gpio_chip *chip,
85     unsigned offset);
86     int (*direction_input)(struct gpio_chip *chip,
87     unsigned offset);
88     int (*direction_output)(struct gpio_chip *chip,
89     unsigned offset, int value);
90     int (*get)(struct gpio_chip *chip,
91     unsigned offset);
92     void (*set)(struct gpio_chip *chip,
93     unsigned offset, int value);
......
145 };

可以看出, gpio_chip 大量的成员都是函数,这些函数就是 GPIO 操作函数。 bgpio_init 函数主 要 任 务 就 是 初 始 化 bgc->gc 。 bgpio_init 里 面 有 三 个 setup 函 数 : bgpio_setup_io 、
bgpio_setup_accessors 和 bgpio_setup_direction。这三个函数就是初始化 bgc->gc 中的各种有关GPIO 的操作,比如输出,输入等等。

继续回到mxc_gpio_probe函数:

第 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 进行操作。
第461行调用函数gpiochip_add向Linux内核注册gpio_chip,也就是 port->bgc.gc。注册完成以后我们就可以在驱动中使用 gpiolib 提供的各个 API 函数。

gpio 子系统 API 函数

对于驱动开发人员,设置好设备树以后就可以使用 gpio 子系统提供的 API 函数来操作指定的 GPIO, gpio 子系统向驱动开发人员屏蔽了具体的读写寄存器过程。这就是驱动分层与分离的好处,大家各司其职,做好自己的本职工作即可。 gpio 子系统提供的常用的 API 函数有下面几个:

1、 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,申请成功;其他值,申请失败。

2、 gpio_free 函数

如果不使用某个 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 函数就讲这些,这些是我们用的最多的。

应用

在前面我们已经讲解了如何创建 test 设备的 pinctrl 节点。本节我们来学习一下如何创建 test 设备的 GPIO 节点。
在根节点“/”下创建 test 设备子节点,如下所示:

1 test {
2     /* 节点内容 */
3 };

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

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

第 2 行,添加 pinctrl-names 属性,此属性描述 pinctrl 名字为“default”。
第 3 行,添加 pinctrl-0 节点,此节点引用前面创建的 pinctrl_test 节点,表示 tset 设备的所使用的 PIN 信息保存在 pinctrl_test 节点中。
我们最后需要在 test 节点中添加 GPIO 属性信息,表明 test 所使用的 GPIO 是哪个引脚,添加完成以后如下所示:

1 test {
2     pinctrl-names = "default";
3     pinctrl-0 = <&pinctrl_test>;
4     gpio = <&gpio1 0 GPIO_ACTIVE_LOW>;
5 };

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

与 gpio 相关的 OF 函数

在上面test节点中,我们定义了一个名为“gpio”的属性, gpio 属性描述了 test 这个设备所使用的 GPIO。在驱动程序中需要读取 gpio 属性内容, Linux 内核提供了几个与 GPIO 有关的 OF 函数,常用的几个 OF 函数如下所示:

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

2、 of_gpio_count 函数

和 of_gpio_named_count 函数一样,但是不同的地方在于,此函数统计的是“gpios”这个属性的 GPIO 数量,而 of_gpio_named_count 函数可以统计任意属性的 GPIO 信息,函数原型如下
所示:

int of_gpio_count(struct device_node *np)

函数参数和返回值含义如下:
np:设备节点。
返回值: 正值,统计到的 GPIO 数量;负值,失败。

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

应用实验

修改设备树文件

1、添加 pinctrl 节点
I.MX6U-ALPHA 开发板上的 LED 灯使用了 GPIO1_IO03 这个 PIN,打开 imx6ull-alientekemmc.dts,在 iomuxc 节点的 imx6ul-evk 子节点下创建一个名为“pinctrl_led”的子节点,节点内容如下所示:

1 pinctrl_led: ledgrp {
2     fsl,pins = <
3         MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0 /* LED0 */
4     >;
5 };

第 3 行,将 GPIO1_IO03 这个 PIN 复用为 GPIO1_IO03,电气属性值为 0X10B0

2、添加 LED 设备节点
在根节点“/”下创建 LED 灯节点,节点名为“gpioled”,节点内容如下:

1 gpioled {
2     #address-cells = <1>;
3     #size-cells = <1>;
4     compatible = "atkalpha-gpioled";
5     pinctrl-names = "default";
6     pinctrl-0 = <&pinctrl_led>;
7     led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
8     status = "okay";
9 };

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

检查 PIN 是否被其他外设使用

①、检查 pinctrl 设置。
②、如果这个 PIN 配置为 GPIO 的话,检查这个 GPIO 有没有被别的外设使用。
在本章实验中 LED 灯使用的 PIN 为 GPIO1_IO03,因此先检查 GPIO_IO03 这个 PIN 有没有被其他的 pinctrl 节点使用,在 imx6ull-alientek-emmc.dts 中找到如下内容:

480 pinctrl_tsc: tscgrp {
481     fsl,pins = <
482         MX6UL_PAD_GPIO1_IO01__GPIO1_IO01 0xb0
483         MX6UL_PAD_GPIO1_IO02__GPIO1_IO02 0xb0
484         MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0xb0
485         MX6UL_PAD_GPIO1_IO04__GPIO1_IO04 0xb0
486     >;
487 };

pinctrl_tsc 节点是 TSC(电阻触摸屏接口)的 pinctrl 节点,从第 484 行可以看出,默认情况下GPIO1_IO03 作为了 TSC 外设的 PIN。所以我们需要将第 484 行屏蔽掉!和 C 语言一样,在要屏蔽的内容前后加上“/*”和“*/”符号即可。其实在 I.MX6U-ALPHA 开发板上并没有用到 TSC接口,所以第 482~485 行的内容可以全部屏蔽掉。因为本章实验我们将 GPIO1_IO03 这个 PIN 配置为了 GPIO,所以还需要查找一下有没有其他的外设使用了 GPIO1_IO03,在 imx6ull-alientek-emmc.dts 中搜索“gpio1 3”,找到如下内容:

723 &tsc {
724     pinctrl-names = "default";
725     pinctrl-0 = <&pinctrl_tsc>;
726     xnur-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
727     measure-delay-time = <0xffff>;
728     pre-charge-time = <0xfff>;
729     status = "okay";
730 };

tsc 是 TSC 的外设节点,从 726 行可以看出, tsc 外设也使用了 GPIO1_IO03,同样我们需要将这一行屏蔽掉。然后在继续搜索“gpio1 3”,看看除了本章的 LED 灯以外还有没有其他的地方也使用了 GPIO1_IO03,找到一个屏蔽一个。

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

 驱动编写

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define GPIOLED_CNT			1		  	/* 设备号个数 */
#define GPIOLED_NAME		"gpioled"	/* 名字 */
#define LEDOFF 				0			/* 关灯 */
#define LEDON 				1			/* 开灯 */

/* gpioled设备结构体 */
struct gpioled_dev{
	dev_t devid;			/* 设备号 	 */
	struct cdev cdev;		/* cdev 	*/
	struct class *class;	/* 类 		*/
	struct device *device;	/* 设备 	 */
	int major;				/* 主设备号	  */
	int minor;				/* 次设备号   */
	struct device_node	*nd; /* 设备节点 */
	int led_gpio;			/* led所使用的GPIO编号		*/
};

struct gpioled_dev gpioled;	/* led设备 */

/*
 * @description		: 打开设备
 * @param - inode 	: 传递给驱动的inode
 * @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量
 * 					  一般在open的时候将private_data指向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
static int led_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &gpioled; /* 设置私有数据 */
	return 0;
}

/*
 * @description		: 从设备读取数据 
 * @param - filp 	: 要打开的设备文件(文件描述符)
 * @param - buf 	: 返回给用户空间的数据缓冲区
 * @param - cnt 	: 要读取的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
	return 0;
}

/*
 * @description		: 向设备写数据 
 * @param - filp 	: 设备文件,表示打开的文件描述符
 * @param - buf 	: 要写给设备写入的数据
 * @param - cnt 	: 要写入的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 写入的字节数,如果为负值,表示写入失败
 */
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
	int retvalue;
	unsigned char databuf[1];
	unsigned char ledstat;
	struct gpioled_dev *dev = filp->private_data;

	retvalue = copy_from_user(databuf, buf, cnt);
	if(retvalue < 0) {
		printk("kernel write failed!\r\n");
		return -EFAULT;
	}

	ledstat = databuf[0];		/* 获取状态值 */

	if(ledstat == LEDON) {	
		gpio_set_value(dev->led_gpio, 0);	/* 打开LED灯 */
	} else if(ledstat == LEDOFF) {
		gpio_set_value(dev->led_gpio, 1);	/* 关闭LED灯 */
	}
	return 0;
}

/*
 * @description		: 关闭/释放设备
 * @param - filp 	: 要关闭的设备文件(文件描述符)
 * @return 			: 0 成功;其他 失败
 */
static int led_release(struct inode *inode, struct file *filp)
{
	return 0;
}

/* 设备操作函数 */
static struct file_operations gpioled_fops = {
	.owner = THIS_MODULE,
	.open = led_open,
	.read = led_read,
	.write = led_write,
	.release = 	led_release,
};

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static int __init led_init(void)
{
	int ret = 0;

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

	/* 2、 获取设备树中的gpio属性,得到LED所使用的LED编号 */
	gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);
	if(gpioled.led_gpio < 0) {
		printk("can't get led-gpio");
		return -EINVAL;
	}
	printk("led-gpio num = %d\r\n", gpioled.led_gpio);

	/* 3、设置GPIO1_IO03为输出,并且输出高电平,默认关闭LED灯 */
	ret = gpio_direction_output(gpioled.led_gpio, 1);
	if(ret < 0) {
		printk("can't set gpio!\r\n");
	}

	/* 注册字符设备驱动 */
	/* 1、创建设备号 */
	if (gpioled.major) {		/*  定义了设备号 */
		gpioled.devid = MKDEV(gpioled.major, 0);
		register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);
	} else {						/* 没有定义设备号 */
		alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME);	/* 申请设备号 */
		gpioled.major = MAJOR(gpioled.devid);	/* 获取分配号的主设备号 */
		gpioled.minor = MINOR(gpioled.devid);	/* 获取分配号的次设备号 */
	}
	printk("gpioled major=%d,minor=%d\r\n",gpioled.major, gpioled.minor);	
	
	/* 2、初始化cdev */
	gpioled.cdev.owner = THIS_MODULE;
	cdev_init(&gpioled.cdev, &gpioled_fops);
	
	/* 3、添加一个cdev */
	cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);

	/* 4、创建类 */
	gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
	if (IS_ERR(gpioled.class)) {
		return PTR_ERR(gpioled.class);
	}

	/* 5、创建设备 */
	gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);
	if (IS_ERR(gpioled.device)) {
		return PTR_ERR(gpioled.device);
	}
	return 0;
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit led_exit(void)
{
	/* 注销字符设备驱动 */
	cdev_del(&gpioled.cdev);/*  删除cdev */
	unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); /* 注销设备号 */

	device_destroy(gpioled.class, gpioled.devid);
	class_destroy(gpioled.class);
}

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("xxx");

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/425291.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux实战学习

文章目录一、Linux权限信息权限控制信息chmodifconfigpingnmap netstatps killzip unzip常用快捷键二、搭建Java环境yumJDKTomcatMysql三、部署Web项目到服务器一、Linux权限信息 Linux中&#xff0c;拥有最大权限的账户为: root(超级管理员)&#xff0c;而普通用户在很多地方…

UWB成为智慧工厂时代的代表技术

UWB成为智慧工厂时代的代表技术 随着智慧工厂的到来&#xff0c;在人员安全问题较为重要的行业中&#xff0c;为了避免人员安全事故的出现&#xff0c;各家企业都逐步装备了UWB定位系统。UWB信号的辐射非常低&#xff0c;通常只有手机辐射的千分之一&#xff0c;因此在工业上应…

【 Spring MVC 核心功能(二) - 获取参数(上)】

文章目录一、获取单个参数二、获取多个参数三、获取对象四、后端参数重命名4.1 使用 RequestParam 重命名参数4.2 RequestParam 中参数必传4.3 设置非必传参数五、使用 PathVariable 获取URL中参数一、获取单个参数 在 Spring MVC 中可以直接⽤⽅法中的参数来实现传单个参&…

uni-app:登录与支付-- 三秒后自动跳转

三秒后自动跳转 三秒后自动跳转到登录页面 需求描述&#xff1a;在购物车页面&#xff0c;当用户点击 “结算” 按钮时&#xff0c;如果用户没有登录&#xff0c;则 3 秒后自动跳转到登录页面 在 my-settle 组件的 methods 节点中&#xff0c;声明一个叫做 showTips 的方法&am…

Vue:生命周期

1、定义 生命周期函数&#xff08;俗称&#xff1a;钩子函数&#xff09; 根据vue整个渲染机制&#xff0c;在渲染的每个关键点上&#xff0c;提供对应的函数&#xff0c;进行一些相关的业务操作。 2、四个阶段 初始阶段&#xff1a;beforeCreate()&#xff1a;可以加loadi…

vue-qr 生成二维码-使用

1、vue-qr官网说明 vue-qr - npm 2、使用 2.1 安装 vue-qr npm install vue-qr --save 2.2 代码 import vueQr from vue-qr; <el-dialog title"摘要" :visible.sync"openSummary" width"700px" append-to-body> <el-row> <el…

Oracle基础(表空间、用户、授权、表、数据类型、数据导入导出等)

Oracle基础(表空间、用户、授权、表、数据类型、数据导入导出等1 创建表空间1.1 概述1.2 语法&#xff1a;1.3 示例&#xff1a;2 创建用户2.1 语法2.2 示例2.3 用户授权类型3 表的创建、修改、删除3.1 表创建3.1.1 概述3.1.2 语法3.1.3 示例3.1.4 表的数据类型3.2 表修改3.2.1…

Oracle系列之七:表的创建与管理

Oracle表的创建与管理1. 表的创建2. 表的修改3. 表中数据的增删改查4. 表的Merge5. 表的删除6. 表的重命名7. 表的索引8. 表的约束9. dual表表是Oracle数据库中最基础的存储对象&#xff0c;用于存储数据。本文主要介绍了Oracle表的创建与管理&#xff0c;包括表的创建、修改、…

图像处理:双边滤波算法

今天主要是回顾一下双边滤波&#xff0c;我曾经在这篇——图像处理&#xff1a;推导五种滤波算法中推导过它&#xff0c;其中包含了我自己写的草稿图。 目录 双边滤波算法原理 &#xff08;1&#xff09;空间域核 &#xff08;2&#xff09;值域核 理解双边滤波 空域权重​…

Reactor模型在库存指令模块中的运用

Reactor是一种高性能网络模型&#xff0c;在netty、redis、nginx、kafaka、memcached等重要组件&#xff0c;以及唯品会自研的OSP框架都有应用&#xff0c;Reactor模型在提升性能和解耦方面都做得非常好&#xff0c;其编程思想也可以运用到业务系统的开发当中&#xff0c;本文主…

VUE:常见的面试题和答案

1. Vue组件的生命周期有哪些&#xff0c;它们的执行顺序是什么? 答&#xff1a;Vue组件的生命周期包括beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、beforeDestroy和destroyed等。它们的执行顺序如下&#xff1a; beforeCreate -> created ->…

21从零开始学Java之while与do-while循环的用法有什么不同?

作者&#xff1a;孙玉昌&#xff0c;昵称【一一哥】&#xff0c;另外【壹壹哥】也是我哦 千锋教育高级教研员、CSDN博客专家、万粉博主、阿里云专家博主、掘金优质作者 前言 在上一篇文章中&#xff0c;壹哥给大家讲解了循环的概念&#xff0c;并重点给大家讲解了for循环的使用…

腾讯轻联流程运行错误如何排查问题?

我们在使用腾讯轻联时&#xff0c;会发现有些流程并没有运行成功&#xff0c;例如我们希望数据可以及时同步到腾讯文档&#xff0c;流程也有执行&#xff0c;但是却执行失败了&#xff0c;那么如何排查问题呢&#xff1f; 其中有几类常见问题 1. 流程不运行的问题请查看&…

【WCH】基于STM32F1标准库程序烧录到CH32F203中运行方法

【WCH】基于STM32标准库程序烧录到CH32F203中运行方法&#x1f4cc;相关篇《关于CH32F203程序下载方式说明》 ✨看到CH32F203手册上写的该芯片也是ARM Cortex-M3内核&#xff0c;那么上层代码应该也是兼容的&#xff0c;为例证实这一点&#xff0c;开干&#xff0c;先来一个简单…

C++---状态机模型---大盗阿福(每日一道算法2023.4.11)

注意事项&#xff1a; 建议先了解状态机的基本定义&#xff1a;状态机-百度百科。 题目&#xff1a; 阿福是一名经验丰富的大盗。趁着月黑风高&#xff0c;阿福打算今晚洗劫一条街上的店铺。 这条街上一共有 N 家店铺&#xff0c;每家店中都有一些现金。 阿福事先调查得知&…

实验手册 - 第8周DataFrame API/Spark SQL

目录标题实验1实验内容绘制散点图将数据保存到MySQL# import os # os.getcwd() import findspark findspark.init() from pyspark.sql import SparkSessionspark SparkSession.builder.getOrCreate()实验1 实验内容 通过DataFrame API或者Spark SQL对数据源进行修改列类型、…

malloc hook进行内存泄漏检测

记录下使用malloc的hook形式&#xff0c;写个小的demo&#xff0c;并记录遇到的问题 1. 实现代码&#xff1a; CMakeLists.txt和相应的memory_leak.cpp文件 cmake_minimum_required(VERSION 3.14) project(demo)set(_SRCmemory_leak.cpp)add_library(memory_leak SHARED ${_S…

不要轻视Facebook Messenger的客户服务

大多数现代品牌都意识到&#xff0c;在客户最活跃的数字渠道中保持活跃至关重要。如今全球有超过 2亿人使用社交消息应用程序与他人在线联系。特别是Facebook Messenger&#xff0c;每月有1亿用户 -占世界人口的3%&#xff01; 有这么多人使用Facebook Messenger&#xff0c;通…

UE5.1.1创建C++工程失败解决办法

闲来无事&#xff0c;更新了一下UE5.1.1&#xff0c;妈蛋创建C项目居然失败&#xff0c; 错误截图如下&#xff1a; 妈蛋&#xff0c;后面一堆乱码&#xff0c;鬼知道是啥错误&#xff01; 咋解决&#xff1f;步步高打火机&#xff0c;直接复制第一段的Running后面的代码到cmd…

仿真创新大赛—国三省一 智能鱼缸(proteus)(stm32)

⏩ 大家好哇&#xff01;我是小光&#xff0c;嵌入式爱好者&#xff0c;一个想要成为系统架构师的大三学生。 ⏩去年下半年参加了全国仿真创新大赛&#xff0c;也是取得了国赛三等奖&#xff0c;省赛一等奖的好成绩。 ⏩本篇文章对我们的参赛作品《智能鱼缸》做一个简介。 ⏩感…