关于设备树里面pinctrl的属性不能生效的问题
时间:2022-11-30 23:30:00
Linux下gpio(旧API)和gpiod(新API)子系统主要负责配置GPIO输入/输出方向,读取输入电平,设置输出电平。
pinctrl子系统主要负责设置gpio其他方面,如配置复用功能(alternate function),配置上下拉电阻,推挽输出或开漏输出,配置输出速度等等。
我读了正点原子imx6ull阿尔法开发板手册Linux开发板(A盘)-基础资料/09,文档教程(非常重要)/正点原子I.MX6U嵌入式Linux驱动开发指南V1.6.pdf第四十五章 pinctrl 和 gpio 实验设备树文件中有以下句子:
pinctrl_led: ledgrp { fsl,pins = < MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0 /* LED0 */ >; }
笔者将0x10b0修改为0x1031后,发现了操作程序SW_PAD_GPIO1_IO03寄存器(0x020e02f4)读出值后不是0x1031,设备树里面有关pinctrl所有内容都无效!
后来作者研究了linux内核相关源码发现应使用某个节点pinctrl-names和pinctrl-这个节点必须有0属性compatible属性,而且编写的驱动程序必须要绑定到这个compatible属性,例如将struct platform_driver结构体的driver.of_match_table[0].compatible设设备树设置值compatible相同属性的字符串。
(请注意是driver.of_match_table[X].compatible成员和设备树compatible属性匹配,不driver.name成员)
正点原子实验只有led_init函数,没用platform_driver没有框架probe函数,所以pinctrl-names和pinctrl-0属性不能生效!这是正点原子教程中非常大的错误!
用于测试的设备树文件arch/arm/boot/dts/myboard.dts:
#include "imx6ull-14x14-emmc-4.3-800x480-c.dts" / { alphaled { #address-cells = <1>; #size-cells = <1>; compatible = "myleddevice"; status = "okay"; reg = <0x020c406c 0x04 // CCM_CCGR1 0x020e0068 0x04 // SW_MUX_GPIO1_IO03 0x020e02f4 0x04 // SW_PAD_GPIO1_IO03 0x0209c000 0x04 // GPIO1_DR 0x0209c004 0x04 // GPIO1_GDIR >; hello = "hello world"; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_mytest>; gpios = <&gpio1 3 GPIO_ACTIVE_LOW &gpio1 18 GPIO_ACTIVE_LOW>; }; /delete-node/leds; /delete-node/gpio_keys@0; }; &iomuxc { imx6ul-evk { /delete-node/gpio-leds; /delete-node/gpio-keys; pinctrl_mytest: testgrp { fsl,pins = < MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x1031 >; }; }; }; &i2c1 { ap3216c@1e { compatible = "myap3216c"; }; };
设备树用/设备树用/delete-node正点原子原设备树中的语法pinctrl属性和leds所有所有灯节点。
编制设备树文件后,make dtbs编译设备树源代码,生成arch/arm/boot/dts/myboard.dtb在文件中,重启开发板uboot中加载这个dtb设备树。
sudo mount /dev/mmcblk0p2 /mnt/sdlinux -t vfat sudo cp /home/oct1158/learn_drivers/kernel/arch/arm/boot/dts/myboard.dtb /mnt/sdlinux sudo umount /mnt/sdlinux sudo reboot setenv mmcroot "/dev/mmcblk0p4 rootwait rw" setenv loadfdt "fatload mmc 0:2 ${fdt_addr} myboard.dtb" boot
测试用的linux驱动:
#include // readl() #include #include // of_iomap #include struct led5_data { int num; char str[100]; }; static int led5_probe(struct platform_device *pdev) { struct led5_data *data; u32 regval; void *__iomem sw_pad; pr_err("%s(0x%p);\n", __FUNCTION__, pdev); data = devm_kzalloc(&pdev->dev, sizeof(struct led5_data), GFP_KERNEL); if (data == NULL) { pr_err("devm_kzalloc() failed!\n"); return -ENOMEM; } strlcpy(data->str, "hello world", sizeof(data->str)); strlcat(data->str, "!\n"); return -ENOMEM; } strlcpy(data->str, "hello world", sizeof(data->str)); strlcat(data->str, "!!!", sizeof(data->str)); data->num = strlen(data->str); platform_set_drvdata(pdev, data); sw_pad = of_iomap(pdev->dev.of_node, 2); if (sw_pad == NULL) { pr_err("of_iomap() failed!\n"); return -ENODEV; } regval = readl(sw_pad); pr_err("sw_pad=0xx\n", regval); iounmap(sw_pad); return 0; } static int led5_remove(struct platform_device *pdev) { struct led5_data *data = platform_get_drvdata(pdev); pr_err("%s(0x%p);\n", __FUNCTION__, pdev); pr_err("num=%d, str=%s\n", data->num, data->str); return 0; } static const struct of_device_id of_led5_match[] = { {.compatible = "myleddevice"}, {} }; MODULE_DEVICE_TABLE(of, of_led5_match); static struct platform_driver led5_driver = { .driver = { .name = "myled5", .of_match_table = of_led5_match }, .probe = led5_probe, .remove = led5_remove }; module_platform_driver(led5_driver); MODULE_AUTHOR("Oct1158"); MODULE_LICENSE("GPL");
程序运行结果:
(注:开发板安装uboot2016.03 linux4.1.15内核 ubuntu18.04文件系统)
oct1158@alientek:~/learn_drivers/led$ ls Makefile led.o led3.ko led4.mod.o led_noop.sh Module.symvers led2.c led3.mod.c led4.o led_off.sh key_test.sh led2.ko led3.mod.o led5.c led_on.sh led.c led2.mod.c led3.o led5.ko led_test.sh led.ko led2.mod.o led4.c led5.mod.c modules.order led.mod.c led2.o led4.ko led5.mod.o stop_blinking.sh led.mod.o led3.c led4.mod.c led5.o oct1158@alientek:~/learn_drivers/led$ make make -C /home/oct1158/learn_drivers/kernel M=/home/oct1158/learn_drivers/led EXTRA_CFLAGS=-fno-pic modules make[1]: Entering directory '/hom/oct1158/learn_drivers/kernel'
CC [M] /home/oct1158/learn_drivers/led/led5.o
Building modules, stage 2.
MODPOST 5 modules
CC /home/oct1158/learn_drivers/led/led5.mod.o
LD [M] /home/oct1158/learn_drivers/led/led5.ko
make[1]: Leaving directory '/home/oct1158/learn_drivers/kernel'
oct1158@alientek:~/learn_drivers/led$ sudo insmod led5.ko
[sudo] password for oct1158:
[ 1036.945408] led5_probe(0x940ffc00);
[ 1036.948949] sw_pad=0x1031
oct1158@alientek:~/learn_drivers/led$ sudo rmmod led5.ko
[ 1040.441553] led5_remove(0x940ffc00);
[ 1040.445343] num=14, str=hello world!!!
oct1158@alientek:~/learn_drivers/led$
通过实验可以发现,platform_driver_register后,我们去访问0x020e02f4寄存器,读出来的值确实是0x1031。说明pinctrl的设置成功生效了。
pinctrl要点:
(1)在根节点下任选一个节点,设置好compatible属性,pinctrl-names属性以及pinctrl-0属性。
(2)在&iomuxc的imx6ul-evk节点下新建一个节点,通过fsl,pins属性配置GPIO引脚。pinctrl-0属性要引用这个节点。
(3)编写的linux驱动必须要用到platform_driver,通过of_match_table与设备树中的compatible属性匹配。如果匹配不了,pinctrl的设置将无法生效!