我是靠谱客的博主 拉长宝贝,最近开发中收集的这篇文章主要介绍Linux 驱动开发 二十二:pinctrl子系统和gpio子系统分析一、pinctrl子系统二、gpio子系统三、gpio 子系统API 函数四、添加 pinctrl 节点模板五、添加 gpio 节点模板六、测试修改后设备树七、与 gpio 相关的OF函数八、总结九、通过led进行测试十、说明,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

一、pinctrl子系统

分析基于 NXP 提供源码。

1、设备树分析

imx6ull.dtsi 文件中有以下节点:

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

gpr: iomuxc-gpr@020e4000 {
	compatible = "fsl,imx6ul-iomuxc-gpr",
		"fsl,imx6q-iomuxc-gpr", "syscon";
	reg = <0x020e4000 0x4000>;
};

iomuxc_snvs: iomuxc-snvs@02290000 {
	compatible = "fsl,imx6ull-iomuxc-snvs";
	reg = <0x02290000 0x10000>;
};

imx6ull-14x14-evk.dts 中有以下节点:

&iomuxc {
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_hog_1>;
	imx6ul-evk {
		……	// 内容太多,只摘取部分数据
		pinctrl_usdhc1: usdhc1grp {
			fsl,pins = <
				MX6UL_PAD_SD1_CMD__USDHC1_CMD     0x17059
				MX6UL_PAD_SD1_CLK__USDHC1_CLK     0x10071
				MX6UL_PAD_SD1_DATA0__USDHC1_DATA0 0x17059
				MX6UL_PAD_SD1_DATA1__USDHC1_DATA1 0x17059
				MX6UL_PAD_SD1_DATA2__USDHC1_DATA2 0x17059
				MX6UL_PAD_SD1_DATA3__USDHC1_DATA3 0x17059
			>;
		};

		pinctrl_usdhc1_100mhz: usdhc1grp100mhz {
			fsl,pins = <
				MX6UL_PAD_SD1_CMD__USDHC1_CMD     0x170b9
				MX6UL_PAD_SD1_CLK__USDHC1_CLK     0x100b9
				MX6UL_PAD_SD1_DATA0__USDHC1_DATA0 0x170b9
				MX6UL_PAD_SD1_DATA1__USDHC1_DATA1 0x170b9
				MX6UL_PAD_SD1_DATA2__USDHC1_DATA2 0x170b9
				MX6UL_PAD_SD1_DATA3__USDHC1_DATA3 0x170b9
			>;
		};

		pinctrl_usdhc1_200mhz: usdhc1grp200mhz {
			fsl,pins = <
				MX6UL_PAD_SD1_CMD__USDHC1_CMD     0x170f9
				MX6UL_PAD_SD1_CLK__USDHC1_CLK     0x100f9
				MX6UL_PAD_SD1_DATA0__USDHC1_DATA0 0x170f9
				MX6UL_PAD_SD1_DATA1__USDHC1_DATA1 0x170f9
				MX6UL_PAD_SD1_DATA2__USDHC1_DATA2 0x170f9
				MX6UL_PAD_SD1_DATA3__USDHC1_DATA3 0x170f9
			>;
		};
		……	// 内容太多,只摘取部分数据
	}
}

&iomuxc_snvs {
	pinctrl-names = "default_snvs";
    pinctrl-0 = <&pinctrl_hog_2>;
    imx6ul-evk {
    	……	// 内容太多,只摘取部分数据
    	ts_reset_pin: ts_reset_pin_mux {
			fsl,pins = <
				MX6ULL_PAD_SNVS_TAMPER9__GPIO5_IO09	0x49
			>;
		};
		……	// 内容太多,只摘取部分数据
    }
}

pinctrl_usdhc1pinctrl_usdhc1_100mhzpinctrl_usdhc1_200mhz 操作引脚相同,区别在于对相应引脚赋值不同。

imx6ull.dtsi 文件中定义节点,在 imx6ull-14x14-evk.dts 文件中追加内容。

2、PIN信息分析

通过以下引脚为例进行分析:

MX6UL_PAD_SD1_CMD__USDHC1_CMD     0x17059	// 0x17059 为引脚配置寄存器设置值

MX6UL_PAD_SD1_CMD__USDHC1_CMD 定义在 archarmbootdtsimx6ul-pinfunc.h 文件中定义,定义如下:

#define MX6UL_PAD_SD1_CMD__USDHC1_CMD   0x01BC 0x0448 0x0000 0x0 0x0

MX6UL_PAD_SD1_CMD__USDHC1_CMD 对应一个引脚的一种复用功能,0x01BC 0x0448 0x0000 0x0 0x05个数字的具体含义如下:

<mux_reg  conf_reg  input_reg  mux_mode  input_val>

mux_reg:复用配置寄存器偏移地址
conf_reg:引脚配置寄存器偏移地址
input_reg:输入配置寄存器偏移地址
mux_mode:复用配置寄存器值
input_val:输入配置寄存器值

3、pinctrl 驱动

设备树中的设备节点如何和驱动匹配?

通过 compatible 属性,相关驱动进行匹配。

static struct imx_pinctrl_soc_info imx6ul_pinctrl_info = {
	.pins = imx6ul_pinctrl_pads,	// pinmux子系统的Pad名称
	.npins = ARRAY_SIZE(imx6ul_pinctrl_pads),
};

static struct imx_pinctrl_soc_info imx6ull_snvs_pinctrl_info = {
	.pins = imx6ull_snvs_pinctrl_pads,
	.npins = ARRAY_SIZE(imx6ull_snvs_pinctrl_pads),
	.flags = ZERO_OFFSET_VALID,
};

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 */ }
};

imx6ul_pinctrl_of_match 结构体数组用于为设备树和驱动相匹配imx6ul_pinctrl_of_match 结构体数组一共有两个兼容性字符串,分别为 “fsl,imx6ul-iomuxc”“fsl,imx6ull-iomuxc-snvs”

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

imx6ul_pinctrl_probepinctrl 处理函数

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,
};

imx6ul_pinctrl_driverstruct platform_driver 数据类型,主要作用是将 imx6ul_pinctrl_of_matchimx6ul_pinctrl_probe 进行绑定

static int __init imx6ul_pinctrl_init(void)
{
	return platform_driver_register(&imx6ul_pinctrl_driver);
}
arch_initcall(imx6ul_pinctrl_init);

static void __exit imx6ul_pinctrl_exit(void)
{
	platform_driver_unregister(&imx6ul_pinctrl_driver);
}
module_exit(imx6ul_pinctrl_exit);

imx6ul_pinctrl_init 函数主要作用是向系统注册 imx6ul_pinctrl_driver 结构体变量。arch_initcall 为系统初始化函数,arch_initcall(imx6ul_pinctrl_init) 在系统启动过程中被调用执行

4、imx6ul_pinctrl_probe分析

函数调用:

imx6ul_pinctrl_probe
	--> imx_pinctrl_probe
		--> platform_get_resource // 从设备树中获取节点名中 @ 后的地址信息
		--> devm_ioremap_resource // 将获取的物理地址转化为虚拟地址
		--> imx_pinctrl_desc 初始化
		--> imx_pinctrl_probe_dt  // 设备树相关操作
		--> pinctrl_register	  // 向系统注册 imx_pinctrl_desc

注:详细分析目前未完成

二、gpio子系统

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

关于 I.MX 系列 SOCGPIO 控 制 器 绑 定 信 息 请 查 看 文 档 Documentation/devicetree/bindings/gpio/ fsl-imx-gpio.txt

1、设备树中的 gpio 信息

MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 引脚为例进行相关说明。

1、引脚复用

通过 iomuxc 相关节点进行查找,引脚相关配置。

&iomuxc {
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_hog_1>;	// 默认引脚复用 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_IO00__ANATOP_OTG1_ID    0x13058 /* USB_OTG1_ID */
			>;
		};
		…… // 其他内容未复制
	};
};

设置 UART1_RTS_B 这个 PIN 复用为 GPIO1_IO19

2、gpio操作

MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 作为 SD1cd 引脚。

&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>;		// 引脚比较特殊(iomuxc中会配置,其他引脚必须被显示调用) 
	keep-power-in-suspend;
	enable-sdio-wakeup;
	vmmc-supply = <&reg_sd1_vmmc>;
	no-1-8-v;
	status = "okay";
};

usdhc1 中引用 gpio1 节点。

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

通过 gpio1compatible 查找相关驱动。fsl,imx6ul-gpio 无相关匹配驱动,fsl,imx35-gpio 有相关匹配驱动。

2、GPIO驱动程序

drivers/gpio/gpio-mxc.c 就是 I.MX6ULLGPIO 驱动文件,在此文件中有如下所示 of_device_id 匹配表:

// IMX35_GPIO 等于 3

static struct platform_device_id mxc_gpio_devtype[] = {
	{
		.name = "imx1-gpio",
		.driver_data = IMX1_GPIO,
	}, {
		.name = "imx21-gpio",
		.driver_data = IMX21_GPIO,
	}, {
		.name = "imx31-gpio",
		.driver_data = IMX31_GPIO,
	}, {
		.name = "imx35-gpio",
		.driver_data = IMX35_GPIO,
	}, {
		/* sentinel */
	}
};

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

mxc_gpio_dt_ids 结构体数组用于设备树和驱动进行匹配

static int mxc_gpio_probe(struct platform_device *pdev)
{
	struct device_node *np = pdev->dev.of_node;
	struct mxc_gpio_port *port;
	struct resource *iores;
	int irq_base;
	int err;

	mxc_gpio_get_hw(pdev);

	port = devm_kzalloc(&pdev->dev, sizeof(*port), GFP_KERNEL);
	if (!port)
		return -ENOMEM;

	iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	port->base = devm_ioremap_resource(&pdev->dev, iores);
	if (IS_ERR(port->base))
		return PTR_ERR(port->base);

	port->irq_high = platform_get_irq(pdev, 1);
	port->irq = platform_get_irq(pdev, 0);
	if (port->irq < 0)
		return port->irq;

	/* disable the interrupt and clear the status */
	writel(0, port->base + GPIO_IMR);
	writel(~0, port->base + GPIO_ISR);

	if (mxc_gpio_hwtype == IMX21_GPIO) {
		/*
		 * Setup one handler for all GPIO interrupts. Actually setting
		 * the handler is needed only once, but doing it for every port
		 * is more robust and easier.
		 */
		irq_set_chained_handler(port->irq, mx2_gpio_irq_handler);
	} else {
		/* setup one handler for each entry */
		irq_set_chained_handler(port->irq, mx3_gpio_irq_handler);
		irq_set_handler_data(port->irq, port);
		if (port->irq_high > 0) {
			/* setup handler for GPIO 16 to 31 */
			irq_set_chained_handler(port->irq_high,
						mx3_gpio_irq_handler);
			irq_set_handler_data(port->irq_high, port);
		}
	}

	err = bgpio_init(&port->bgc, &pdev->dev, 4,
			 port->base + GPIO_PSR,
			 port->base + GPIO_DR, NULL,
			 port->base + GPIO_GDIR, NULL, 0);
	if (err)
		goto out_bgio;

	port->bgc.gc.to_irq = mxc_gpio_to_irq;
	port->bgc.gc.base = (pdev->id < 0) ? of_alias_get_id(np, "gpio") * 32 :
					     pdev->id * 32;

	err = gpiochip_add(&port->bgc.gc);
	if (err)
		goto out_bgpio_remove;

	irq_base = irq_alloc_descs(-1, 0, 32, numa_node_id());
	if (irq_base < 0) {
		err = irq_base;
		goto out_gpiochip_remove;
	}

	port->domain = irq_domain_add_legacy(np, 32, irq_base, 0,
					     &irq_domain_simple_ops, NULL);
	if (!port->domain) {
		err = -ENODEV;
		goto out_irqdesc_free;
	}

	/* gpio-mxc can be a generic irq chip */
	mxc_gpio_init_gc(port, irq_base);

	list_add_tail(&port->node, &mxc_gpio_ports);

	return 0;

out_irqdesc_free:
	irq_free_descs(irq_base, 32);
out_gpiochip_remove:
	gpiochip_remove(&port->bgc.gc);
out_bgpio_remove:
	bgpio_remove(&port->bgc);
out_bgio:
	dev_info(&pdev->dev, "%s failed with errno %dn", __func__, err);
	return err;
}

mxc_gpio_probegpio 子系统处理函数

static struct platform_driver mxc_gpio_driver = {
	.driver		= {
		.name	= "gpio-mxc",
		.of_match_table = mxc_gpio_dt_ids,
	},
	.probe		= mxc_gpio_probe,
	.id_table	= mxc_gpio_devtype,
};

主要作用是将 mxc_gpio_dt_idsmxc_gpio_probe 进行绑定

static int __init gpio_mxc_init(void)
{
	return platform_driver_register(&mxc_gpio_driver);
}
postcore_initcall(gpio_mxc_init);

向系统中注册 mxc_gpio_driver

pio-mxc.c 所在的目录为 drivers/gpio,打开这 个目录可以看到很多芯片的 gpio 驱动文件, “gpiolib” 开始的文件是 gpio 驱动的核心文件。

3、gpiolib

保留

4、mxc_gpio_probe分析

保留

三、gpio 子系统API 函数

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

注:详细内容未完成

四、添加 pinctrl 节点模板

关于 I.MX 系列 SOCpinctrl 设备树绑定信息可以参考文档 Documentation/devicetree/bindings/pinctrl/fsl,imx-pinctrl.txt

以正点原子 ALPHA 开发板进行测试。

1、查找引脚是否已经使用

LED 使用 GPIO1_IO03 引脚操作,在设备树 pinctrl 子系统相关配置中查找引脚是否使用。

1、查看 GPIO1_IO03 可复用功能:
在这里插入图片描述
2、在设备树中查找 GPIO1_IO03 引脚是否被使用。
在这里插入图片描述
3、通过查找 GPIO1_IO03 已经被使用,因此需要将其删除(设备树移植于官方开发板,因为在官方开发板中可能被用作其他用途,因此需要查找是否使用)。
在这里插入图片描述
&gpio1 3 在设置默认电平时需要配置,因此这个也需要注释。

2、创建节点

iomuxc 节点中添加 pinctrl_led: gpioledgrp,用来表示 led相关 pinctrl 子系统相关配置。
在这里插入图片描述

3、添加“fsl,pins”属性

在这里插入图片描述

4、添加 PIN 配置信息

在这里插入图片描述

五、添加 gpio 节点模板

1、创建节点

在设备树中创建操作 LED ,为了方便节点创建在根节点下。
在这里插入图片描述

2、引用 pinctrl 信息

在这里插入图片描述

3、添加GPIO属性信息

在这里插入图片描述

六、测试修改后设备树

1、编译

onlylove@ubuntu:~/linux/linux/lq_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga$ make dtbs
  CHK     include/config/kernel.release
  CHK     include/generated/uapi/linux/version.h
  CHK     include/generated/utsrelease.h
make[1]: 'include/generated/mach-types.h' is up to date.
  CHK     include/generated/bounds.h
  CHK     include/generated/asm-offsets.h
  CALL    scripts/checksyscalls.sh
  DTC     arch/arm/boot/dts/imx6ull-lq-evk.dtb
onlylove@ubuntu:~/linux/linux/lq_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga$

2、在系统中测试

/ # cd /sys/firmware/devicetree/base/
/sys/firmware/devicetree/base # ls -al
total 0
-r--r--r--    1 0        0                4 Jan  1 00:01 #address-cells
-r--r--r--    1 0        0                4 Jan  1 00:01 #size-cells
drwxr-xr-x   17 0        0                0 Jan  1 00:01 .
drwxr-xr-x    3 0        0                0 Jan  1 00:01 ..
drwxr-xr-x    2 0        0                0 Jan  1 00:01 aliases
drwxr-xr-x    2 0        0                0 Jan  1 00:01 backlight
drwxr-xr-x    2 0        0                0 Jan  1 00:01 chosen
drwxr-xr-x    6 0        0                0 Jan  1 00:01 clocks
-r--r--r--    1 0        0               34 Jan  1 00:01 compatible
drwxr-xr-x    3 0        0                0 Jan  1 00:01 cpus
drwxr-xr-x    2 0        0                0 Jan  1 00:01 gpioled
drwxr-xr-x    2 0        0                0 Jan  1 00:01 interrupt-controller@00a01000
drwxr-xr-x    2 0        0                0 Jan  1 00:01 lq-led
drwxr-xr-x    2 0        0                0 Jan  1 00:01 memory
-r--r--r--    1 0        0               36 Jan  1 00:01 model
-r--r--r--    1 0        0                1 Jan  1 00:01 name
drwxr-xr-x    2 0        0                0 Jan  1 00:01 pxp_v4l2
drwxr-xr-x    5 0        0                0 Jan  1 00:01 regulators
drwxr-xr-x    3 0        0                0 Jan  1 00:01 reserved-memory
drwxr-xr-x   12 0        0                0 Jan  1 00:01 soc
drwxr-xr-x    2 0        0                0 Jan  1 00:01 sound
drwxr-xr-x    3 0        0                0 Jan  1 00:01 spi4
/sys/firmware/devicetree/base # random: nonblocking pool is initialized

/sys/firmware/devicetree/base # cd gpioled/
/sys/firmware/devicetree/base/gpioled # ls
compatible     name           pinctrl-names
led-gpio       pinctrl-0      status
/sys/firmware/devicetree/base/gpioled # cat compatible
/sys/firmware/devicetree/base/gpioled # cat status
okay
/sys/firmware/devicetree/base/gpioled #
/sys/firmware/devicetree/base/gpioled #
/sys/firmware/devicetree/base/gpioled # cat compatible
gpioled
/sys/firmware/devicetree/base/gpioled #

通过以上日志可以确定 gpioled 节点解析成功。

七、与 gpio 相关的OF函数

保留

八、总结

1、在设备树中添加引脚复用相关配置(也就是 pinctrl 子系统)。

2、在设备树中添加节点信息,在节点中引用复用相关配置(通过节点 pinctrl-x 属性)。

3、在驱动中使用 gpio 子系统(通过 gpio 子系统相关 api 接口)操作 GPIO 引脚。

举例说明(以 usdhc1 为例):

引脚复用

pinctrl_usdhc1: usdhc1grp {
	fsl,pins = <
		MX6UL_PAD_SD1_CMD__USDHC1_CMD     0x17059
		MX6UL_PAD_SD1_CLK__USDHC1_CLK     0x10071
		MX6UL_PAD_SD1_DATA0__USDHC1_DATA0 0x17059
		MX6UL_PAD_SD1_DATA1__USDHC1_DATA1 0x17059
		MX6UL_PAD_SD1_DATA2__USDHC1_DATA2 0x17059
		MX6UL_PAD_SD1_DATA3__USDHC1_DATA3 0x17059
	>;
};

pinctrl_usdhc1_100mhz: usdhc1grp100mhz {
	fsl,pins = <
		MX6UL_PAD_SD1_CMD__USDHC1_CMD     0x170b9
		MX6UL_PAD_SD1_CLK__USDHC1_CLK     0x100b9
		MX6UL_PAD_SD1_DATA0__USDHC1_DATA0 0x170b9
		MX6UL_PAD_SD1_DATA1__USDHC1_DATA1 0x170b9
		MX6UL_PAD_SD1_DATA2__USDHC1_DATA2 0x170b9
		MX6UL_PAD_SD1_DATA3__USDHC1_DATA3 0x170b9
	>;
};

pinctrl_usdhc1_200mhz: usdhc1grp200mhz {
	fsl,pins = <
		MX6UL_PAD_SD1_CMD__USDHC1_CMD     0x170f9
		MX6UL_PAD_SD1_CLK__USDHC1_CLK     0x100f9
		MX6UL_PAD_SD1_DATA0__USDHC1_DATA0 0x170f9
		MX6UL_PAD_SD1_DATA1__USDHC1_DATA1 0x170f9
		MX6UL_PAD_SD1_DATA2__USDHC1_DATA2 0x170f9
		MX6UL_PAD_SD1_DATA3__USDHC1_DATA3 0x170f9
	>;
};

设备树中关于 usdhc1 定义三种状态,如果都进行执行,一定会产生冲突。也就是说,以上只是进行定义,而未使用

引脚配置

&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>;
	no-1-8-v;
	status = "okay";
};

设备树 usdhc1 节点中对 usdhc1 三种状态根据不同配置进行引用(使用)。

总结:引脚复用配置在使用时才会调用。

九、通过led进行测试

1、编写驱动

#include "linux/init.h"
#include "linux/module.h"
#include "linux/kdev_t.h"
#include "linux/fs.h"
#include "linux/cdev.h"
#include "linux/device.h"
#include "asm/io.h"
#include "asm/uaccess.h"
#include "linux/of.h"
#include "linux/of_address.h"
#include "linux/of_gpio.h"
#include "linux/gpio.h"

#define NEWCHRDEV_MAJOR 0   /* 主设备号(如果为0则让系统自动分配,如果大于0则使用指定设备号) */
#define NEWCHRDEV_MINOR 0   /* 次设备号 */
#define NEWCHRDEV_COUNT 1   /* 设备号个数 */
#define NEWCHRDEV_NAME  "newchrdev" /* 名子 */

#define LEDOFF 					0			/* 关灯 */
#define LEDON 					1			/* 开灯 */

/* 寄存器物理地址 */
#define CCM_CCGR1_BASE				(0X020C406C)	
#define SW_MUX_GPIO1_IO03_BASE		(0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE		(0X020E02F4)
#define GPIO1_DR_BASE				(0X0209C000)
#define GPIO1_GDIR_BASE				(0X0209C004)

/* 映射后的寄存器虚拟地址指针 */
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;

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

newchrdev_t newchrdev;

/*
 * @description		: LED打开/关闭
 * @param - sta 	: LEDON(0) 打开LED,LEDOFF(1) 关闭LED
 * @return 			: 无
 */
void led_switch(u8 sta)
{
	u32 val = 0;
	if(sta == LEDON) {
		val = readl(GPIO1_DR);
		val &= ~(1 << 3);	
		writel(val, GPIO1_DR);
	}else if(sta == LEDOFF) {
		val = readl(GPIO1_DR);
		val|= (1 << 3);	
		writel(val, GPIO1_DR);
	}	
}

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

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

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

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

    return 0;
}

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

static const struct file_operations newchrdevops = {
    .owner   = THIS_MODULE,
    .open = led_open,
	.read = led_read,
	.write = led_write,
	.release = 	led_release,
};

/* 驱动入口函数 */
static int __init newchrdev_init(void)
{
    /* 驱动入口函数具体内容 */
    int ret;
    struct property;
    
    /* 1、字符设备号分配 */
    newchrdev.major = NEWCHRDEV_MAJOR;
    if(newchrdev.major){
        newchrdev.minor = NEWCHRDEV_MINOR;
        newchrdev.devid = MKDEV(newchrdev.major, newchrdev.minor);
        ret = register_chrdev_region(newchrdev.devid,NEWCHRDEV_COUNT,NEWCHRDEV_NAME);
        printk("newchrdev.major > 0!rn");
    }else{
        ret = alloc_chrdev_region(&newchrdev.devid,0,NEWCHRDEV_COUNT,NEWCHRDEV_NAME);
        newchrdev.major = MAJOR(newchrdev.devid);
        newchrdev.minor = MINOR(newchrdev.devid);
        printk("newchrdev.major = 0!rn");
    }
    if(ret < 0){
        printk("newchrdev xxx_chrdev_region failed!rn");
        goto newchrdev_chrdev_region_failed;
    }
    printk("newchrdev devid = %d newchrdev major=%d,minor=%drn",newchrdev.devid,newchrdev.major,newchrdev.minor);

    /* 2、注册字符设备 */
    newchrdev.dev.owner = THIS_MODULE;
    cdev_init(&newchrdev.dev,&newchrdevops);
    ret = cdev_add(&newchrdev.dev,newchrdev.devid,NEWCHRDEV_COUNT);
    if(ret < 0){
        printk("newchrdev cdev_add failed!rn");
        goto newchrdev_cdev_add_failed;
    }

    /* 3、创建类 */
    newchrdev.class = class_create(THIS_MODULE,NEWCHRDEV_NAME);
    if(IS_ERR(newchrdev.class)) {
        printk("newchrdev class_create failed!rn");
        goto newchrdev_class_create_failed;
    }

    /* 4、创建设备 */
    newchrdev.device = device_create(newchrdev.class,NULL,newchrdev.devid,NULL,NEWCHRDEV_NAME);
    if(IS_ERR(newchrdev.device)){
        printk("newchrdev device_create failed!rn");
        goto neschrdev_device_creat_failed;
    }

    /* 设置 LED 所使用的GPIO */
    /* [1]、获取设备节点 gpioled */
    newchrdev.nd = of_find_node_by_path("/gpioled");
    if(newchrdev.nd == NULL){
        printk("gpioled node cant not found!rn");
        goto neschrdev_device_creat_failed;
    }else{
        printk("gpioled node has been found!rn");
    }

    /* [2]、获取设备树中的 gpio 属性,得到 LED 所使用的 LED 编号 */
    newchrdev.led_gpio = of_get_named_gpio(newchrdev.nd, "led-gpio", 0);
    if(newchrdev.led_gpio < 0){
        printk("can't get led-gpio");
        goto neschrdev_device_creat_failed;
    }
    printk("led-gpio num = %drn", newchrdev.led_gpio);

    /* [3]、申请 IO */
    ret = gpio_request(newchrdev.led_gpio,"gpio-led");
    if(ret){
        printk("newchrdev gpio_request failed!rn");
        goto neschrdev_device_creat_failed;
    }

    /* [4]、设置 GPIO1_IO03 为输出 */
    ret = gpio_direction_output(newchrdev.led_gpio, 1);
    if(ret < 0) {
        printk("can't set gpio!rn");
        goto neschrdev_gpio_request_failed;
    }

    /*[5]、输出高电平,默认关闭 LED 灯*/
    gpio_set_value(newchrdev.led_gpio,1);

    printk("newchrdev_init succed!rn");
    return 0;
neschrdev_gpio_request_failed:
    gpio_free(newchrdev.led_gpio);
neschrdev_device_creat_failed:
    class_destroy(newchrdev.class);
newchrdev_class_create_failed:
    cdev_del(&newchrdev.dev);
newchrdev_cdev_add_failed:
    unregister_chrdev_region(newchrdev.devid,NEWCHRDEV_COUNT);

newchrdev_chrdev_region_failed:   /* 字符设备号分配失败处理函数(未分配资源,因此不做处理) */
    printk("failed!rn");
    return ret;
}

/* 驱动卸载函数 */
static void __exit newchrdev_exit(void)
{
    /* 驱动卸载函数具体内容 */
    /* 取消映射 */
	iounmap(IMX6U_CCM_CCGR1);
	iounmap(SW_MUX_GPIO1_IO03);
	iounmap(SW_PAD_GPIO1_IO03);
	iounmap(GPIO1_DR);
	iounmap(GPIO1_GDIR);
    /* 5、释放IO*/
    gpio_free(newchrdev.led_gpio);
    /* 4、删除设备 */
    device_destroy(newchrdev.class,newchrdev.devid);
    /* 3、删除类 */
    class_destroy(newchrdev.class);
    /* 2、注销字符设备 */
    cdev_del(&newchrdev.dev);
    /* 1、释放设备号 */
    unregister_chrdev_region(newchrdev.devid,NEWCHRDEV_COUNT);
    printk("newchrdev_exit succed!rn");
}

module_init(newchrdev_init);
module_exit(newchrdev_exit);

MODULE_LICENSE("GPL");

2、测试

/ # pwd
/
/ # ls
bin          gpio-led.ko  lib          proc         sys
dev          led          linuxrc      root         tmp
etc          led_dts.ko   mnt          sbin         usr
/ # lsmod
Module                  Size  Used by    Tainted: G
/ # insmod gpio-led.ko
newchrdev.major = 0!
newchrdev devid = 260046848 newchrdev major=248,minor=0
gpioled node has been found!
led-gpio num = 3
newchrdev_init succed!
/ # lsmod
Module                  Size  Used by    Tainted: G
gpio_led                2992  0
/ # ./led /dev/newchrdev
led_open!
led_write!
led_write!
led_write!
led_write!
led_write!
led_write!
led_write!
^Cled_release!

/ # rmmod gpio-led.ko
newchrdev_exit succed!
/ # lsmod
Module                  Size  Used by    Tainted: G
/ #

通过在开发板上测试,led 正常闪烁,驱动测试通过。

十、说明

知识积累不够,文章逻辑不够清晰,内容有可能不是很准确。后续进行完善。

最后

以上就是拉长宝贝为你收集整理的Linux 驱动开发 二十二:pinctrl子系统和gpio子系统分析一、pinctrl子系统二、gpio子系统三、gpio 子系统API 函数四、添加 pinctrl 节点模板五、添加 gpio 节点模板六、测试修改后设备树七、与 gpio 相关的OF函数八、总结九、通过led进行测试十、说明的全部内容,希望文章能够帮你解决Linux 驱动开发 二十二:pinctrl子系统和gpio子系统分析一、pinctrl子系统二、gpio子系统三、gpio 子系统API 函数四、添加 pinctrl 节点模板五、添加 gpio 节点模板六、测试修改后设备树七、与 gpio 相关的OF函数八、总结九、通过led进行测试十、说明所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(60)

评论列表共有 0 条评论

立即
投稿
返回
顶部