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 <asm/io.h> // readl()
#include <linux/module.h>
#include <linux/of_address.h> // of_iomap
#include <linux/platform_device.h>
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, "!!!", 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=0x%04x\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 '/home/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的设置将无法生效!
|