嵌入式爱好者

查看: 7565|回复: 0

[知识库投稿] pinctl和gpio子系统实践操作

[复制链接]

1

主题

1

帖子

20

积分

i.MX6UL通行证i.MX6Q通行证

扫一扫,手机访问本帖
发表于 2021-2-21 23:53:20 | 显示全部楼层 |阅读模式
​https://blog.csdn.net/szm1234/article/details/113836382

复制过来发现格式不太对

文章目录
什么是pinctrl和gpio子系统

pinctel子系统

pinctel子系统功能

查看属性表达

查看pinctrl

gpio子系统

gpio子系统功能

常用gpio子系统提供的api函数

gpio_request函数

gpio_free函数

gpio_direction_input函数

gpio_dierction_output函数

gpio_get_value函数

gpio_set_value函数

设备树节点

添加节点信息

pinfunc.h文件查找宏定义

测试程序源码

app.c

driver.c

结果验证

查看设备树节点

安装ko模块生成驱动

执行app



什么是pinctrl和gpio子系统
在学习单片机(比如51单片机和STM32)的时候,我们可以直接对单片机的寄存器进行操作,进而达到控制pin脚的目的。

而Linux系统相较于一个单片机系统,要庞大而复杂得多,因此在Linux系统中我们不能直接对pin脚进行操作。

Linux系统讲究驱动分层,pinctrl子系统和GPIO子系统就是驱动分层的产物。如果我们要操作pin脚,就必须要借助pinctrl子系统和GPIO子系统。

pinctrl子系统的作用是pin config(引脚配置)和pin mux(引脚复用),而如果pin脚被复用为了GPIO(注意:GPIO功能只是pin脚功能的一种),就需要再借助GPIO子系统对pin脚进行控制了,GPIO子系统提供了一系列关于GPIO的API函数,供我们调用。

pinctel子系统
pinctel子系统功能
管理系统中所有的可以控制的pin,在系统初始化的时候,枚举所有可以控制的pin,并标识这些pin

管理这些pin的复用,对于SOC而言,其引脚除了配置成普通的GPIO之外,若干个引脚还可以组成一个pin group,形成特定的功能

配置这些pin的特性,例如使能关闭引脚上的pull-up、pull-down电阻,配置引脚的driver strength

不同SOC厂家的pin controller的节点

这些节点里面都是把某些引脚复用成某些功能


查看属性表达
不同的SOC厂家,他们的pin controller的节点里面的属性表达的含义都是不同的,所以,要想查看这些属性的含义,去到Linux源码目录

/Documentation/devicetree/bindings


/Documentation/devicetree/bindings/pinctrl
进入到pinctrl文件中,可以看到txt文件,我们的型号是im6q,找到对应的文件就可以打开查看信息



查看pinctrl
打开我们板子对应的dts文件,这里的我是imx6q-c-sabresd.dts

打开之后发现没有我们想要看到的pinctrl相关的代码,都是一些引用添加节点信息

根据头文件,我们继续打开imx6qdl-sabresd.dtsi文件

在imx6qdl-sabresd.dtsi文件里就可以看到详细的pinctrl的信息了


gpio子系统
gpio子系统功能

也就是,我们的pinctrl子系统配置完成后,我们接下来就是要交给gpio子系统来进行控制

在设备树文件中代码示例如下


        gpio_user: gpios{
                pinctrl-names = "default";
                pinctrl-0 = <&pinctrl_user>;                // default的状态就是对应pinctrl_user的节点
                compatible = "gpio-user";
                gpio0{
                        label = "D01";
                        gpios = <&gpio3 15 1>;
                        default-direction = "out";
                };
                gpio1{
                        label = "D02";
                        gpios = <&gpio2 21 1>;
                        default-direction = "out";
                };
        };
针对gpios = <&gpio3 15 1>;代表的含义就是,gpio3组中的第15个IO口,给配置为1

那么这里使用了pinctrl和gpio子系统之后,就可以替代之前的这种写法


在之前,我们控制引脚需要对寄存器进行控制,是reg = <0x20ac000 0x0000004>;
这样的形式进行描述,而现在使用了gpio子系统之后,就可以更方便规范的使用。

常用gpio子系统提供的api函数
gpio_request函数

在这里我们看到了一个of函数来获取gpio的编号,就是of_get_named_gpio函数,这个函数的详情如下


gpio_free函数


gpio_direction_input函数


gpio_dierction_output函数


gpio_get_value函数


gpio_set_value函数


设备树节点
添加节点信息
我们从上次的代码,设备树的驱动代码进行修改,本次使用了pinctrl和gpio子系统,本质上是替代了之前的寄存器操作,使用函数来进行电平的控制。

现在首先还是在设备树文件中添加我们的节点信息。


pinctrl_user: usergrp {
                        fsl,pins = <
                                MX6QDL_PAD_EIM_DA15__GPIO3_IO15 0x0b0b1
                                MX6QDL_PAD_EIM_A17__GPIO2_IO21        0x0b0b1
                        >;
                };
我们要控制的gpio引脚是EIM_A17

那么,我们是如何找到EIM_A17对应的宏定义MX6QDL_PAD_EIM_A17__GPIO2_IO21呢?

pinfunc.h文件查找宏定义
在我们的dts文件夹下,有一个对应imx6q-pinfunc.h文件,里面定义着所需要的宏定义

这样,我们只需要按照需要查找就可以了

通过搜索EIM_A17.这样就找到了对应的宏定义,通过后面的后缀名称,可以看到有很多的功能

我们也可以通过查找芯片手册,看到EIM_A17对应的引脚的说明IOMUXC_SW_MUX_CTL_PAD_EIM_ADDR17

回到我们刚才说的,根据名称,我们需要一个gpio的功能,所以选择的宏定义是

#define MX6QDL_PAD_EIM_A17__GPIO2_IO21              0x0f0 0x404 0x000 0x5 0x0
MX6QDL_PAD_EIM_A17__GPIO2_IO21所代表的数 0x0f0 0x404 0x000 0x5 0x0 实际上就是电器属性,都已经配置好了,这样就不用我们一个一个的去手动的查找芯片手册然后进行配置。

0x0f0 0x404 0x000 0x5 0x0 这 5个值的含义如下所示:

<mux_reg conf_reg input_reg mux_mode input_val>
0x0f0 :mux_reg寄存器偏移地址,设备树中的 iomuxc节点就是 IOMUXC外设对应的节点,根据其 reg属性可知 IOMUXC外设寄存器起始地址为 0x020e0000。因此20E_0000h base + F0h offset = 20E_00F0h, IOMUXC_SW_MUX_CTL_PAD_EIM_ADDR17寄存器地址正好是 20E_00F0h,如图所示,所以这里的mux_reg = 0x0f0

0x404 conf_reg寄存器偏移地址,和 mux_reg一样, 0x020e0000+0x404=0x020e0404这个就 是寄存器IOMUXC_SW_MUX_CTL_PAD_EIM_ADDR17的地址。

0x000 input_reg寄存器偏移地址,有些外设有 input_reg寄存器,有 input_reg寄存器的外设需要配置 input_reg寄存器。没有的话就不需要设置

0x5 mux_reg寄存器值,在这里就相当于设置IOMUXC_SW_MUX_CTL_PAD_EIM_ADDR17寄存器为 0x5,也即是设置这个 PIN复用为 GPIO2_IO21。

0x0 input_reg寄存器值,在这里无效。

这就是宏 IOMUXC_SW_MUX_CTL_PAD_EIM_ADDR17的含义,看的比较仔细的同学应该会发现并没有 conf_reg寄存器的值, config_reg寄存器是设置一个 PIN的电气特性的,这么重要的寄存器怎么没有值呢?

MX6QDL_PAD_EIM_A17__GPIO2_IO21        0x0b0b1
MX6QDL_PAD_EIM_A17__GPIO2_IO21 我们上面已经分析了,就剩下了一个 0x0b0b1反应快的同学应该已经猜出来了, 0x0b0b1就是 conf_reg寄存器值!此值由用户自行设置,通过此值来设置一个 IO的上 /下拉、驱动能力和速度等。在这里就相当于设置寄存器IOMUXC_SW_MUX_CTL_PAD_EIM_ADDR17的值为 0x0b0b1。

这里还有一个点需要注意,就是一个io引脚只能定义一个功能,就例如我们的EIM_A17,有这么多

#define MX6QDL_PAD_EIM_A17__EIM_ADDR17              0x0f0 0x404 0x000 0x0 0x0
#define MX6QDL_PAD_EIM_A17__IPU1_DISP1_DATA12       0x0f0 0x404 0x000 0x1 0x0
#define MX6QDL_PAD_EIM_A17__IPU2_CSI1_DATA12        0x0f0 0x404 0x8b8 0x2 0x1
#define MX6QDL_PAD_EIM_A17__GPIO2_IO21              0x0f0 0x404 0x000 0x5 0x0
#define MX6QDL_PAD_EIM_A17__SRC_BOOT_CFG17          0x0f0 0x404 0x000 0x7 0x0
所以我们就要在设备树文件中,搜索MX6QDL_PAD_EIM_A17_,来确定io口没有被使用过,不然就注释掉。


然后对这些信息继续进行配置,在我们的test节点中,添加如下代码,test-gpios = <&gpio2 21 1>;对应的就是MX6QDL_PAD_EIM_A17__GPIO2_IO21,gpio2组中的第21个io口,默认置高。

下面就该是driver驱动程序的代码了,依然是在probe函数中进行添加修改

测试程序源码
app.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
    int fd;

    char buff[64] = {0};

    fd = open("/dev/hello_misc", O_RDWR);       // 打开节点

    if(fd < 0){

        perror("open error\n");                // perror在应用中打印
        return fd;
    }

    buff[0] = atoi(argv[1]);                    //字符串转化成整形                           

    // read(fd, buff, sizeof(buff));
    write(fd, buff,sizeof(buff));               //在write中就传数据到底层,这样可以调用驱动的mis_write的操作了,直接操作 ./app 1或者./app 0

    // printf("buf is:%s\n", buff);
   
    close(fd);

    return 0;
}

driver.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>

#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>

#include <linux/gpio.h>
#include <linux/of_gpio.h>

struct device_node  *test_device_node;
u32 out_values[2] = {0};
int beep_gpio = 0;                                                      // 返回到的gpio编号

struct resource *beep_mem;
struct resource *beep_mem_tmp;

int misc_open (struct inode *inode, struct file *file){
   
    printk("hello misc_open!!!\n");
    return 0;
}

int misc_release(struct inode *inode, struct file *file){

    printk("bye bye misc_release!!!\n");

    return 0;

}

ssize_t misc_read(struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t){
   
    char kbuf[64] = "copy to user!!!\n";

    if( copy_to_user(ubuf, kbuf, size) != 0 ){
        printk("copy_to_user error!!!\n");
        return -1;
    }

    printk("hello misc_read!!!\n");

    return 0;
}

ssize_t misc_write(struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t){
   
    char kbuf[64] = {0};
    printk("hello misc_write!!!\n");

    if( copy_from_user(kbuf, ubuf, size) != 0 ){
        printk("copy_from_user error!!!\n");
        return -1;
    }
    printk("buf is:%s\n", kbuf);

/*********实现IO的逻辑功能*********************************************************************/
    if(kbuf[0] == 1){                           //对蜂鸣器的控制,如果是1,控制gpio口
        // *vir_gpio5_dr |= (1 << 1);           //因为是gpio5的01,所以左移一位就可以,给一个高电平,使蜂鸣器工作
        gpio_set_value(beep_gpio, 1);           //Linux体现出来了通用性,不需要寄存器地址了
        printk("gpio_set_value is high!!!\n");
    }else if(kbuf[0] == 0){
        // *vir_gpio5_dr &= ~(1 << 1);
        gpio_set_value(beep_gpio, 0);   
        printk("gpio_set_value is low!!!\n");            
    }

    return 0;
}

struct file_operations misc_fops = {
    .owner = THIS_MODULE,
    .open = misc_open,
    .release = misc_release,
    .read = misc_read,
    .write = misc_write,
};

struct miscdevice misc_dev = {
    .minor = MISC_DYNAMIC_MINOR,                // 次设备号
    .name = "hello_misc",                       // 生成一个名字叫做hello_misc的设备节点
    .fops = &misc_fops                          // 完善file_operations
};


int beep_probe(struct platform_device *pdev){
   
    int ret;

    printk("beep_probe ok!!!\n");
   
    /*直接获取设备节点信息*/
    // printk("node name is %s\n", pdev->dev.of_node->name);            //pdev->dev.of_node获取节点,和下面的test_device_node功能相同

    /*间接获取设备节点信息*/
    /********查找指定路径的节点***********/
    test_device_node = of_find_node_by_path("/test");

    if(test_device_node == NULL) {
        printk("of_find_node_by_path error!!!\n");
        return -1;
    }else {
        printk("of_find_node_by_path ok!!!\n");
        printk("test_device_node name is %s\n", test_device_node->name);
    }

    beep_gpio = of_get_named_gpio(test_device_node, "test-gpios", 0);       // test-gpios是设备树文件中包含我们定义的gpio信息属性名test-gpios = <&gpio2 21 1>;
   
    if(beep_gpio < 0) {
        printk("of_get_named_gpio error!!!\n");
    }else{
        printk("of_get_named_gpio is %d\n", beep_gpio);
    }
   
    ret = gpio_request(beep_gpio, "beep");                                  // beep是我们现在起的名字

    if(ret != 0) {
        printk("gpio_request error!!!\n");
    }else{
        printk("gpio_request ok!!!\n");
    }

    /*gpio子系统,控制输出为高电平*/
    gpio_direction_output(beep_gpio, 1);                                           // 使用gpio_direction直接对编号beep_gpio对应的io口拉高

    /*注册杂项设备*/
    ret = misc_register(&misc_dev);
        if(ret < 0){
        printk("misc_register failed!!!\n");
        return -1;
    }else{
        printk("misc_register succeed!!!\n");
    }

    return 0;
}

int beep_remove(struct platform_device *pdev){
    printk("beep_remove ok!!!\n");
    return 0;
}

const struct platform_device_id beep_id_table = {
    .name = "beep_device_test",
};

const struct of_device_id of_match_table_test[] = {
    {.compatible = "test12345"},
    {}                                              // 不写会提示警告
};

struct platform_driver beep_device = {
    .probe = beep_probe,                            //  这个probe函数其实和  device_driver中的是一样的功能,但是一般是使用device_driver中的那个
    .remove = beep_remove,                          //  卸载平台设备驱动的时候会调用这个函数,但是device_driver下面也有,具体调用的是谁这个就得分析了
    .driver = {
        .owner = THIS_MODULE,      
        .name  = "beep_device_test",                //  有了id_table,这个driver->name可有可无,优先级低
        .of_match_table = of_match_table_test,      // 最优先匹配of_match_table其次是id_table最后是name
    },                                              //   内置的device_driver 结构体
    .id_table = &beep_id_table,                     //  该设备驱动支持的设备的列表  他是通过这个指针去指向  platform_device_id 类型的数组
};

static int beep_driver_init(void)
{
    int ret = 0;
    printk("beep_driver_init ok!!!\n");             // 在内核中无法使用c语言库,所以不用printf
   
    ret = platform_driver_register(&beep_device);

    if(ret < 0){
        printk("platform_driver_register error!!!\n");
        return ret;
    }else{
        printk("platform_driver_register ok!!!\n");
    }
   
    return 0;
}

static void beep_driver_exit(void)
{
    printk("beep_driver_exit bye!!!\n");

    gpio_free(beep_gpio);                               // 与gpio_request对应起来,把获取到的gpio编号释放掉
    misc_deregister(&misc_dev);
    platform_driver_unregister(&beep_device);
}

module_init(beep_driver_init);
module_exit(beep_driver_exit);


MODULE_LICENSE("GPL");                                  //声明模块拥有开源许可

结果验证
查看设备树节点
看看申请的gpio是不是在设备树中



安装ko模块生成驱动

在dev文件中查看驱动生成的节点


执行app
我们可以看到对应的信息打印了出来,说明程序都得到了顺利的执行,接下来就是测试EIM_A17引脚实际的高低电平,通过万用表的测量,与打印信息一致。


回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|小黑屋| 飞凌嵌入式 ( 冀ICP备12004394号-1 )

GMT+8, 2024-3-28 19:03

Powered by Discuz! X3.4

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表