pinctrl 和 gpio 子系统实验学习

pinctrl 子系统主要工作内容如下:
①、获取设备树中 pin 信息。
②、根据获取到的 pin 信息来设置 pin 的复用功能
③、根据获取到的 pin 信息来设置 pin 的电气特性,比如上/下拉、速度、驱动能力等。
对于我们使用者来讲,只需要在设备树里面设置好某个 pin 的相关属性即可,其他的初始化工作均由 pinctrl 子系统来完成, pinctrl 子系统源码目录为 drivers/pinctrl。

I.MX6ULL 的 pinctrl 子系统驱动

I.MX6ULL 的 pinctrl 子系统驱动

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

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

接下来打开imx6ull-alientek-emmc.dts,如下内容:
在这里插入图片描述
上图就是向 iomuxc 节点追加数据,不同的外设使用的 PIN 不同、其配置也不同,

设备树中添加 pinctrl 节点模板

pinctrl 节点添加过程如下:

1、创建对应的节点

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

示例代码 45.1.2.10 test 设备 pinctrl 节点
1 pinctrl_test: testgrp {
2 /* 具体的 PIN 信息 */
3 };
2、添加“fsl,pins”属性

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

示例代码 45.1.2.11 添加"fsl,pins"属性
1 pinctrl_test: testgrp {
2 fsl,pins = <
3 /* 设备所使用的 PIN 配置信息 */
4 >;
5 };
3、在“fsl,pins”属性中添加 PIN 配置信息

最后在“fsl,pins”属性中添加具体的 PIN 配置信息,完成以后如下所示:示例代码 45.1.2.13 完整的 test 设备

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

gpio 子系统

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

1、设备树中的 gpio 信息

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

示例代码 45.2.2.1 SD 卡 CD 引脚 PIN 配置参数
316 pinctrl_hog_1: hoggrp-1 {
317 fsl,pins = <
318 MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059 /* SD1 CD */
......
322 >;
323 };

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

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

“&gpio1”表示 CD 引脚所使用的 IO 属于 GPIO1 组,“19”表示 GPIO1 组的第 19 号 IO,通过这两个值 SD 卡驱动程序就知道 CD 引脚使用了 GPIO1_IO19这 GPIO。“GPIO_ACTIVE_LOW”表示低电平有效,如果改为“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>;
			};

第 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 小节,有如图 45.2.2.1 所示的寄存器地址表:
在这里插入图片描述
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)的话表示低电平有效。

2、 GPIO 驱动程序简介

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

示例代码 45.2.2.3 mxc_gpio_dt_ids 匹配表
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 文件中有如下所示内容:

示例代码 45.2.2.4 mxc_gpio_driver 结构体
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 驱动入口函数。

示例代码 45.2.2.5 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_portmxc_gpio_port 就是对 I.MX6ULL GPIO 的抽象。 mxc_gpio_port 结构体定义如下:

示例代码 45.2.2.6 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 里面有如下代码:

示例代码 45.2.2.7 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 寄存器组,内容如下:

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

示例代码 45.2.2.9 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 的操作,比如输出,输入等等。第 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 函数。

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 节点模板

继续完成 45.1.3 中的 test 设备,在 45.1.3 中我们已经讲解了如何创建 test 设备的 pinctrl 节点。本节我们来学习一下如何创建 test 设备的 GPIO 节点。
1、创建 test 设备节点
在根节点“/”下创建 test 设备子节点,如下所示:

示例代码 45.2.4.1 test 设备节点
1 test {
2 /* 节点内容 */
3 };

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

示例代码 45.2.4.2 向 test 节点添加 pinctrl 信息
1 test {
2 pinctrl-names = "default";
3 pinctrl-0 = <&pinctrl_test>;
4 /* 其他节点内容 */
5 };

第 2 行,添加 pinctrl-names 属性,此属性描述 pinctrl 名字为“default”。
第 3 行,添加 pinctrl-0 节点,此节点引用 45.1.3 中创建的 pinctrl_test 节点,表示 test 设备的所使用的 PIN 信息保存在 pinctrl_test 节点中。
3、添加 GPIO 属性信息
我们最后需要在 test 节点中添加 GPIO 属性信息,表明 test 所使用的 GPIO 是哪个引脚,添
加完成以后如下所示:
示例代码 45.2.4.3 向 test 节点添加 gpio 属性
1 test {
2 pinctrl-names = “default”;
3 pinctrl-0 = <&pinctrl_test>;
4 gpio = <&gpio1 0 GPIO_ACTIVE_LOW>;
5 };

与 gpio 相关的 OF 函数

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

1、 of_gpio_named_count 函数

of_gpio_named_count 函数用于获取设备树某个属性里面定义了几个 GPIO 信息,要注意的
是空的 GPIO 信息也会被统计到,比如:

gpios = <0&gpio1 1 20 &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>的属性信息转换为对S应的 GPIO 编
号,此函数在驱动中使用很频繁!函数原型如下:

int of_get_named_gpio(struct device_node *np,
const char *propname,
int index)
函数参数和返回值含义如下:
np:设备节点。
propname:包含要获取 GPIO 信息的属性名。

实验程序编写

1、修改设备树文件

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

		pinctrl_led: ledgrp {
			fsl,pins = <
			MX6UL_PAD_GPIO1_IO03__GPIO1_IO03        0x10B0 /* LED0 */
			>;
		};
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";
	};
3、检查 PIN 是否被其他外设使用

①、检查 pinctrl 设置。
②、如果这个 PIN 配置为 GPIO 的话,检查这个 GPIO 有没有被别的外设使用。
设备树编写完成以后使用“ make dtbs”命令重新编译设备树,然后使用新编译出来的imx6ull-alientek-emmc.dtb文件启动 Linux 系统。启动成功以后进入“/proc/device-tree”目录中查看“gpioled”节点是否存在,如果存在的话就说明设备树基本修改成功(具体还要驱动验证),结果如图所示:
在这里插入图片描述

LED驱动程序编写

#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 设备 */
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;
}
/*
73 * @description : 向设备写数据
74 * @param - filp : 设备文件,表示打开的文件描述符
75 * @param - buf : 要写给设备写入的数据
76 * @param - cnt : 要写入的数据长度
77 * @param – offt : 相对于文件首地址的偏移
78 * @return : 写入的字节数,如果为负值,表示写入失败
79 */
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;
}
/*
104 * @description : 关闭/释放设备
105 * @param – filp : 要关闭的设备文件(文件描述符)
106 * @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,
};
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 cant not found!\r\n");
        return -EINVAL;
    }
    else
    {
        printk("gpioled node has been found!\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);
    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("newcheled major: %d minor: %d", gpioled.major, gpioled.minor);
    gpioled.cdev.owner = THIS_MODULE;
    cdev_init(&gpioled.cdev, &gpioled_fops);
    cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);
    gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
    if (IS_ERR(gpioled.class))
    {
        return PTR_ERR(gpioled.class);
    }
    gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);
    if (IS_ERR(gpioled.device))
    {
        return PTR_ERR(gpioled.device);
    }
    return 0;
}
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("wyw");

与前面编写程序没有复杂寄存器映射以及复制啥的。直接用函数去操作这个gpio端口。
主要函数在以下这些:

 struct gpioled_dev *dev = filp->private_data;
  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;
gpioled.nd = of_find_node_by_path("/gpioled");
if (gpioled.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 编号 */
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);
ret = gpio_direction_output(gpioled.led_gpio, 1);
if (ret < 0)
{
    printk("can't set gpio!\r\n");
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【计算机网络基础】OSI七层网络模型 TCPIP四层网络模型

文章目录 ISO介绍网络模型介绍OSI七层模型OSI七层模型介绍OSI七层特点一、TCP/IP四层模型介绍二、TCP/IP四层模型TCP/IP协议簇一次C/S通信 &#x1f308;你好呀&#xff01;我是 山顶风景独好 &#x1f388;欢迎踏入我的博客世界&#xff0c;能与您在此邂逅&#xff0c;真是缘分…

人工智能和机器学习这两个概念有什么区别?

什么是人工智能&#xff1f; 先来说下人工智能&#xff0c;人工智能&#xff08;Artificial Intelligence&#xff09;&#xff0c;英文缩写为AI&#xff0c;通俗来讲就是用机器去做在过去只有人能做的事。 人工智能最早是由图灵提出的&#xff0c;在1950年&#xff0c;计算机…

【背就有效】软考中项计算公式汇总,简答题轻松拿下

宝子们注意啦&#xff01;系统集成管理师的报名8月就要开始啦&#xff01;你准备好迎接挑战了吗&#xff1f; 备考下半年软考的小伙伴们&#xff0c;在这里给大家准备了一份超实用的备考秘籍——系统集成项目管理工程师计算公式汇总。亲测有效&#xff0c;每天只需抽出一点时间…

数据库错误[ERR] 1071 - Specified key was too long; max key length is 1000 bytes

环境&#xff1a;phpstudy的mysql8 索引长度问题&#xff1a; 试了很多解决办法&#xff0c;例如需改配置&#xff1a; set global innodb_large_prefixON; set global innodb_file_formatBARRACUDA; 试了还是有问题&#xff0c;直接启动不了了。因为mysql8取消了这个配置。…

[linux]基于Ubuntu24.04原内核6.8.0升级到6.9.0

物理机操作系统&#xff1a; 虚拟机操作系统&#xff1a; Ubuntu 24.04 下载地址&#xff1a; https://mirror.nju.edu.cn/ubuntu-releases/24.04/ubuntu-24.04-desktop-amd64.iso VM版本信息&#xff1a; 内核源代码来源&#xff1a; https://ftp.sjtu.edu.cn/sites/ftp.kern…

React+TS前台项目实战(六)-- 全局常用组件Button封装

文章目录 前言Button组件1. 功能分析2. 代码注释说明3. 使用方式4. 效果展示 总结 前言 今天这篇主要讲全局按钮组件封装&#xff0c;可根据UI设计师要求自定义修改。 Button组件 1. 功能分析 &#xff08;1&#xff09;可以通过className属性自定义按钮样式&#xff0c;传递…

ZDH-智能营销-标签模块

目录 主题 项目源码 预览地址 安装包下载地址 标签模块 什么是标签 标签场景分类 标签设计 标签按照场景做了分类&#xff0c;但是运营人员需要感知到吗 标签按照场景做了分类&#xff0c;底层的计算引擎是否需要划分&#xff1f; 标签模块&#xff0c;是否需要涉及…

利安科技上市首日股价大涨:2023营收净利润下滑,募资金额大幅缩水

《港湾商业观察》施子夫 6月7日&#xff0c;宁波利安科技股份有限公司&#xff08;以下简称&#xff0c;利安科技&#xff09;正式在深交所创业板挂牌上市&#xff0c;股票简称为利安科技&#xff0c;股票代码300784。 上市当天&#xff0c;利安科技股价大涨348.76%。 2022年…

DriveWorld:一个预训练模型大幅提升检测+地图+跟踪+运动预测+Occ多个任务性能

1. 写在前面 以视觉为中心的自动驾驶技术近期因其较低的成本而引起了广泛关注&#xff0c;而预训练对于提取通用表示至关重要。然而&#xff0c;当前的以视觉为中心的预训练通常依赖于2D或3D预训练任务&#xff0c;忽视了自动驾驶作为4D场景理解任务的时序特征。这里通过引入一…

Golang | Leetcode Golang题解之第135题分发糖果

题目&#xff1a; 题解&#xff1a; func candy(ratings []int) int {n : len(ratings)ans, inc, dec, pre : 1, 1, 0, 1for i : 1; i < n; i {if ratings[i] > ratings[i-1] {dec 0if ratings[i] ratings[i-1] {pre 1} else {pre}ans preinc pre} else {decif dec…

mmdetection使用未定义backbone训练

首先找到你需要用到的 backbone&#xff0c;一般有名的backbone 都会在github有相应的代码开源和预训练权重提供 本文以mobilenetv3 fastercnn 作为举例&#xff0c;在mmdetection中并未提供 mobilenetv3&#xff0c;提供的仅有 mobilenetv2&#xff1b; 在github上找到 mobil…

Selenium三种等待方式的使用!

UI自动化测试&#xff0c;大多都是通过定位页面元素来模拟实际的生产场景操作。但在编写自动化测试脚本中&#xff0c;经常出现元素定位不到的情况&#xff0c;究其原因&#xff0c;无非两种情况&#xff1a;1、有frame&#xff1b;2、没有设置等待。 因为代码运行速度和浏览器…

【Vue】购物车案例-构建项目

脚手架新建项目 (注意&#xff1a;勾选vuex) 版本说明&#xff1a; vue2 vue-router3 vuex3 vue3 vue-router4 vuex4/pinia vue create vue-cart-demo需要勾选上vuex&#xff0c;由于这个项目只有一个页面&#xff0c;vuex可勾可不勾 将原本src内容清空&#xff0c;替换成教学…

VISIO安装教程+安装包

文章目录 01、什么是VISIO&#xff1f;02、安装教程03、常见安装问题解析 01、什么是VISIO&#xff1f; Visio是由微软开发的流程图和图表绘制软件&#xff0c;它是Microsoft Office套件的一部分。Visio提供了各种模板和工具&#xff0c;使用户能够轻松创建和编辑各种类型的图…

SwiftUI七使用UI控件

代码下载 在应用中&#xff0c;用户可以创建一个简介来描述他们自已的个人情况。为了让用户可以编辑自己的简介&#xff0c;需要添加一个编辑模式并设计一个偏好设置界面。这里使用多种通用控件来展示用户的各种数据&#xff0c;并在用户保存他们所做的数据修改时更新地标数据…

PCB 蚀刻因子(Etch Factor)

The standard definition for Etch Factor is to specify it as the ratio of trace thickness / amount of over-etching. This gives the following formula: Etch Factor T/[0.5(W1-W2)] 例如&#xff0c;W1 5.2mil&#xff0c; W1 4.2mil&#xff0c; T 3.7mil&#…

四维轻云|支持多源数据融合、城市级实景三维模型展示

四维轻云是一款轻量化的地理空间数据管理云平台&#xff0c;具有项目管理、数据上传、场景搭建、发布分享、素材库等功能模块&#xff0c;支持多用户在线协作管理&#xff0c;实现了轻量化、便捷化的空间数据应用。 1、多源数据融合 平台支持管理、展示各类空间数据&#xff…

差动放大器

差动器的出现是为了解决直接耦合电路存在的零点漂移问题&#xff0c;另外&#xff0c;差动放大器还有灵活的输入&#xff0c;输出方式。 一&#xff0c;基本差动放大器 差动放大器在电路结构上具有对称性&#xff0c;三极管VT1&#xff0c;VT2同型号&#xff0c;R1R2,R3R4,R5…

【天池科普】1. 为啥人人都要学AI

大家好&#xff01;欢迎来到天池的AI科普系列课程&#xff0c;本期是第一期内容。在这个信息爆炸的时代&#xff0c;人工智能&#xff08;AI&#xff09;不仅是技术进步的标志&#xff0c;更是推动社会向前发展的强大引擎。无论你是AI领域的新手&#xff0c;还是有一定基础的学…

助力高考,一组彩色的文字

1、获取文本内容 首先&#xff0c;获取每个<div>元素的文本内容&#xff0c;并清空其内部HTML&#xff08;innerHTML ""&#xff09;。 2、创建<span>元素 然后&#xff0c;它遍历文本的每个字符&#xff0c;为每个字符创建一个新的<span>元素…