引脚控制(pinctrl)子系统能够管理引脚复用。设备树需要引脚以某种方式多路复用的设备必须声明它们所需的引脚控制配置。
pinctrl 子系统主要工作内容如下:
①、获取设备树中 pin 信息。
②、根据获取到的 pin 信息来设置 pin 的复用功能
③、根据获取到的 pin 信息来设置 pin 的电气特性,比如上/下拉、速度、驱动能力等
从硬件角度来看,GPIO是功能,是引脚可以运行的模式从。从软件角度来看,GPIO只不过是数字线,可以作为输入或输出使用,并且只能有两个值。内核GPIO子系统提供在驱动程序中可以想象得到的设置和处理GPIO线路的所以功能。内核中实际上有两种不同的GPIO处理方式:基于整数的接口,GPIO由整数表示;基于描述符的接口,GPIO由不透明结构表示和描述。
实现一个通过IMX6UL GPIO1_IO03来控制LED灯。
打开arch/arm/boot/dts/imx6ull.dtsi,可以看到:
/ {
aliases {
};
cpus {
};
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>;
};
iomuxc: iomuxc@020e0000 {
compatible = "fsl,imx6ul-iomuxc";
reg = <0x020e0000 0x4000>;
};
};
打开arch/arm/boot/dts/imx6ull-alientek-emmc.dts
找到iomuxc节点,在imx6ul-evk子节点下创建pinctrl_led,MX6UL_PAD_GPIO1_IO03__GPIO1_IO03可以在arch/arm/boot/dts/imx6ul-pinfunc.h找到其宏定义。
pinctrl_led: ledgrp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0X10B0
};
在"/"根节点下添加gpioled子节点:
gpioled {
#address-cells = <1>;
#size-cells = <1>;
compatible = "atkalpha-gpioled";
pinctrl-name = 'default";
pinctrl-0 = <&pinctrl_led>;
led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
status = "okay";
};
在imx6ull-alientek-emmc.dts文件里查看是否有其他pinctrl使用了删除使用了GPIO1_IO03,将其屏蔽或删除,由于dts文件是半导体厂商写的,里面的外设在我们自己的板子上不一定有或改换了其他管脚,所以搜索gpio1关键词,找到有可能被占用的管脚,将其删除。完成了dts文件的改写,用make dtbs得到新的dtb文件,将其copy到 tftp目录,重启开发板。
进入/proc/device-tree,查看是否有gpioled节点。

修改编写dtsled.c文件,详见正点原子教程的pinctrl和gpio子系统实验。
-
添加头文件#include linux/of_gpio.h>;
-
在设备结构体增加int led_gpio; 这是led所使用的GPIO编号;
-
删除寄存器虚拟地址指针,比如static void __iomem *IMX6U_CCM_CCGR1等;
-
删除led_switch()函数,led_wirte()函数可以调用API函数gpio_set_value()来控制管脚电平;
-
修改led_write()函数,删除其中的led_swtich(),改用gpio_set_value();要使用这个函数,先要定义一个变量:struct gpioled_dev *dev = filp->private_data,filp->private_data就是gpioled,dev指向gpioled,所以gpio_set_value()的输入参数就是gpioled的成员变量led_gpio;
-
在led_init()函数中需要调用of_find_node_by_path()得到设备节点,调用of_get_name_gpio()得到led编号,调用gpio_direction_output()设置端口输出模式。不再使用of_find_node_by_path(),of_find_property(),of_property_read_string(),of_property_read_u32_array()这些函数。删除寄存器地址映射代码,因为pinkctrl节点已经有寄存器的偏移地址和设置值了。不再使用readl()和writel()函数对映射寄存器读写。删除了GPIO1时钟配置,在imx6ull.dtsi有ccm的描述:
soc {
aips1: aips-bus@02000000 { spba-bus@02000000 { }; clks: ccm@020c4000 { compatible = "fsl,imx6ul-ccm" reg = <0x020c4000 0x4000>; interrupts = <GIC_SPI 87 IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI 88 IRQ_TYPE_LEVEL_HIGH>; #clock-cells = <1>; clocks = <&ckil>, <&osc>, <&ipp_di0>, <&ipp_di1>; clock-names = "ckil", "osc", "ipp_di0", "ipp_di1"; }; iomuxc: iomuxc@020e0000 { compatible = "fsl,imx6ul-iomuxc"; reg = <0x020e0000 0x4000>; }; };};
-
led_exit()也不需要iounmap()取消映射了。
总之,整个驱动程序清爽了很多。
编译后copy到nfs/lib/modules/4.1.15/目录下,重启开发板,
depmod
modprobe gpioled.ko
执行 ./ledApp /dev/gpioled 1 打开LED
执行 ./ledApp /dev/gpioled 0 关闭LED

参考资料
-
Linux设备驱动开发 Linux Device Drivers Development 法 John Madieu
-
I.MX6U 嵌入式 Linux 驱动开发指南 V2.0.1 正点原子