pinctrl 和 gpio 子系统实验学习

news2024/11/21 0:22:17

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");
}

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

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

相关文章

Python私教张大鹏 Vue3整合AntDesignVue之Steps 步骤条

引导用户按照流程完成任务的导航条。 何时使用 当任务复杂或者存在先后关系时&#xff0c;将其分解成一系列步骤&#xff0c;从而简化任务。 案例&#xff1a;步骤条组件 核心代码&#xff1a; <template><a-steps:current"1":items"[{title: Fin…

如何从 Mac Studio 恢复丢失的数据?以下是 4 种 Mac Studio 恢复方法

今&#xff0c;意外丢失计算机数据已变得十分常见。无论您是 Mac 用户还是 Windows 计算机&#xff0c;都是如此。 造成这种现象的意外原因有很多。意外点击Mac 的“清空垃圾”命令可能会让您大吃一惊&#xff0c;因为您可能还没有准备好从垃圾中删除这些数据。 您想使用Comm…

【GD32F303红枫派使用手册】第十二节 ADC-双轴按键摇杆多通道循环采样实验

12.1 实验内容 本实验是通过ADC规则组多通道循环采样方式实现双轴按键摇杆传感器x和y轴电压值的读取&#xff0c;通过本实验主要学习以下内容&#xff1a; 双轴按键摇杆传感器工作原理 DMA原理 规则组多通道循环采样 12.2 实验原理 12.2.1 双轴按键摇杆传感器工作原理 摇…

远程咨询的好处都有哪些呢?

随着科技的飞速发展&#xff0c;远程咨询正逐渐成为人们获取医疗服务的一种新方式。那么什么是远程咨询呢&#xff1f;其又有哪些好处呢&#xff1f;下面就给大家详细地说说。 远程咨询的概念 远程咨询&#xff0c;顾名思义&#xff0c;是指通过互联网技术&#xff0c;实现患…

ISO 19115-3:2023 基本概念的 XML模式实现

前言 ISO(国际标准化组织)是由各国标准化机构(ISO 成员机构)组成的全球性联合会。制定国际标准的工作通常由 ISO 技术委员会完成。对某一技术委员会所关注的主题感兴趣的每个成员机构都有权在该委员会中派代表。与 ISO 联络的国际组织、政府和非政府组织也参与工作。ISO 与…

算法:11. 盛最多水的容器

11. 盛最多水的容器 给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳最多的水。 返回容器可以储存的最大水量。 说明&#xff1a;你…

哪里有宣传海报制作模板?盘点可以套用的海报软件

不论是精心筹备的盛会、充满爱意的婚礼仪式&#xff0c;还是家庭聚会的温馨时光&#xff0c;一份设计精巧的邀请函都是主人诚挚邀请的最好证明。它不仅传递着邀请&#xff0c;更承载着对宾客的尊重与期待。但你知道在哪里可以找到那些让人眼前一亮的邀请函海报制作模板吗&#…

【Go语言精进之路】构建高效Go程序:了解map实现原理并高效使用

&#x1f525; 个人主页&#xff1a;空白诗 &#x1f525; 热门专栏&#xff1a;【Go语言精进之路】 文章目录 引言一、什么是map1.1 map的基本概念与特性1.2 map的初始化与零值问题1.3 map作为引用类型的行为 二、map的基本操作2.1 插入数据2.2 获取数据个数2.3 查找和数据读取…

Python 使用scrapy框架

1、安装scrapy 2、使用scrapy创建项目,在终端命令行 执行如下命令&#xff0c;会创建一个myproject项目 scrapy startproject myproject 3、创建完成后&#xff0c;目录结构如下 4、cd myproject进入项目 ,执行scrapy genspider weather ******&#xff0c;会在spiders下创建…

数据库期末设计——图书管理系统

目录 1.前置软件以及开发环境&#xff1a; 2.开发过程讲解 代码环节&#xff1a; 数据库代码 1.BookDao.java 2.BookTypeDao.java 3.UserDao.java 4.Book.java 5.BookType.java 6.User.java 7.DbUtil.java 8.Stringutil.java 9.BookAddInterFrm.java 10.BookMan…

知识图谱的应用---新零售

文章目录 新零售知识图谱构建过程典型应用 新零售 新零售&#xff0c;即个人、企业以互联网为依托&#xff0c;通过运用大数据、人工智能等先进技术手段并运用心理学知识&#xff0c;对商品的生产、流通与销售过程进行升级改造&#xff0c;进而重塑业态结构与生态圈&#xff0c…

LlamaIndex 四 数据连接器

前言 我们通过各项配置&#xff0c;理解了LlamaIndex在构建知识库和基于知识库的推荐两个阶段&#xff0c;怎么和业务相结合。本文&#xff0c;我们将开始深入理解LlamaIndex的各个模块。首先&#xff0c;LlamaIndex强大的Data Connector 数据连接器上场。 LlamaIndex擅长和各…

AI绘画Stable Diffusion必看:如何生成人物的全身照实战教程!

大家好&#xff0c;我是画画的小强 在小伙伴们私下留言中&#xff0c;关于如何实现人物的全身照一直有人咨询&#xff0c;看来这个问题还是困惑着不少小伙伴&#xff0c;今天我们就这个问题详细讨论一下。 一. 宽高比参数设置 在讨论如何生成图片的全身照之前&#xff0c;我…

关于用宽带(拨号)连接VPN无法上网,但是wifi或者热点就可以的问题

参考链接&#xff1a;https://zhuanlan.zhihu.com/p/580929250https://zhuanlan.zhihu.com/p/580929250 https://blog.csdn.net/Yaoyao2024/article/details/132245249文章浏览阅读10w次&#xff0c;点赞161次&#xff0c;收藏515次。很多同学在学习访问学校提供的资源时或者一…

等保一体机案例 | 黑龙江某事业单位档案管理系统借助捷云过二级等保

黑龙江某事业单位&#xff0c;聚焦产业发展的应用技术研究&#xff0c;兼顾重大技术应用的基础研究&#xff0c;满足广东省经济社会发展需要。 由于信创要求&#xff0c;也为了满足《关于加强国家电子政务工程建设项目信息安全风险评估工作的通知》&#xff08;发 改高技〔2008…

[图解]企业应用架构模式2024新译本讲解13-服务层1

1 00:00:00,150 --> 00:00:05,030 接下来我们来看服务层模式 2 00:00:06,070 --> 00:00:11,740 这也是领域逻辑类型的最后一个模式 3 00:00:13,700 --> 00:00:17,110 我们前面实际上已经见过了&#xff0c;事务脚本 4 00:00:17,240 --> 00:00:19,900 的时候&…

citys

城市边界电子围栏 初始化摄像头、灯光等 let renderer, scene, camera, stats, gui, texture;renderer new THREE.WebGLRenderer({logarithmicDepthBuffer: true,});renderer.setPixelRatio(window.devicePixelRatio);renderer.setSize(window.innerWidth, window.innerHeigh…

如何实现单例模式及不同实现方法分析-设计模式

这是 一道面试常考题&#xff1a;&#xff08;经常会在面试中让手写一下&#xff09; 什么是单例模式 【问什么是单例模式时&#xff0c;不要答非所问&#xff0c;给出单例模式有两种类型之类的回答&#xff0c;要围绕单例模式的定义去展开。】 单例模式是指在内存中只会创建…

一颗万能的PD协议芯片,能芯Type-C PD协议芯片“ECP5705”, 它是如何实现PD直流风扇应用呢?

文章目录 文章目录 前言 一、PD风扇方案介绍 二、芯片介绍 三、PD风扇-供电方式 四、能芯科技 PD 协议芯片ECP5705-应用场景 总结 前言 随着USB Type-C接口的普及和PD取电芯片的出现&#xff0c;使得小型家电和电动工具可以通过统一的USB Type-C接口进行充电&#xff0c;极大地…

台灯学生用哪个牌子最好?学生台灯十大名牌排行榜分享

近年来&#xff0c;我们注意到儿童近视的现象呈现出增多且趋于低龄化的趋势。这一变化&#xff0c;部分原因可以归咎于孩子们越来越多地使用电子产品&#xff0c;另一部分则与他们面临的学业压力增加有关。鉴于此&#xff0c;家长们在挑选儿童学习用品时变得格外谨慎&#xff0…