Linux嵌入式驱动开发12——pinctl和gpio子系统实践操作

文章目录

  • 全系列传送门
  • 什么是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

全系列传送门

Linux嵌入式驱动开发01——第一个驱动Hello World(附源码)

Linux嵌入式驱动开发02——驱动编译到内核

Linux嵌入式驱动开发03——杂项设备驱动(附源码)

Linux嵌入式驱动开发04——应用层和内核层数据传输

Linux嵌入式驱动开发05——物理地址到虚拟地址映射

Linux嵌入式驱动开发06——第一个相对完整的驱动实践编写

Linux嵌入式驱动开发07——GPIO驱动过程记录(飞凌开发板)

Linux嵌入式驱动开发08——字符设备(步步为营)

Linux嵌入式驱动开发09——平台总线详解及实战

Linux嵌入式驱动开发10——设备树开发详解

Linux嵌入式驱动开发11——平台总线模型修改为设备树实例

Linux嵌入式驱动开发12——pinctl和gpio子系统实践操作

Linux嵌入式驱动开发13——ioctl接口(gpio控制使用)

Linux嵌入式驱动开发14——中断的原理以及按键中断的实现(tasklet中断下文)

Linux嵌入式驱动开发15——等待队列和工作队列

Linux嵌入式驱动开发16——按键消抖实验(内核定时器)

Linux嵌入式驱动开发17——输入子系统

Linux嵌入式驱动开发18——I2C通信

什么是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子系统功能

  1. 管理系统中所有的可以控制的pin,在系统初始化的时候,枚举所有可以控制的pin,并标识这些pin
  2. 管理这些pin的复用,对于SOC而言,其引脚除了配置成普通的GPIO之外,若干个引脚还可以组成一个pin group,形成特定的功能
  3. 配置这些pin的特性,例如使能关闭引脚上的pull-up、pull-down电阻,配置引脚的driver strength

不同SOC厂家的pin controller的节点

这些节点里面都是把某些引脚复用成某些功能
Linux嵌入式驱动开发12——pinctl和gpio子系统实践操作_第1张图片

查看属性表达

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

/Documentation/devicetree/bindings

Linux嵌入式驱动开发12——pinctl和gpio子系统实践操作_第2张图片

/Documentation/devicetree/bindings/pinctrl

进入到pinctrl文件中,可以看到txt文件,我们的型号是im6q,找到对应的文件就可以打开查看信息
Linux嵌入式驱动开发12——pinctl和gpio子系统实践操作_第3张图片
Linux嵌入式驱动开发12——pinctl和gpio子系统实践操作_第4张图片

查看pinctrl

打开我们板子对应的dts文件,这里的我是imx6q-c-sabresd.dts
Linux嵌入式驱动开发12——pinctl和gpio子系统实践操作_第5张图片
打开之后发现没有我们想要看到的pinctrl相关的代码,都是一些引用添加节点信息
Linux嵌入式驱动开发12——pinctl和gpio子系统实践操作_第6张图片
根据头文件,我们继续打开imx6qdl-sabresd.dtsi文件
Linux嵌入式驱动开发12——pinctl和gpio子系统实践操作_第7张图片
在imx6qdl-sabresd.dtsi文件里就可以看到详细的pinctrl的信息了
Linux嵌入式驱动开发12——pinctl和gpio子系统实践操作_第8张图片

gpio子系统

gpio子系统功能

Linux嵌入式驱动开发12——pinctl和gpio子系统实践操作_第9张图片
也就是,我们的pinctrl子系统配置完成后,我们接下来就是要交给gpio子系统来进行控制

在设备树文件中代码示例如下
Linux嵌入式驱动开发12——pinctl和gpio子系统实践操作_第10张图片

	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子系统之后,就可以替代之前的这种写法

Linux嵌入式驱动开发12——pinctl和gpio子系统实践操作_第11张图片
在之前,我们控制引脚需要对寄存器进行控制,是reg = <0x20ac000 0x0000004>;
这样的形式进行描述,而现在使用了gpio子系统之后,就可以更方便规范的使用。

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

gpio_request函数

Linux嵌入式驱动开发12——pinctl和gpio子系统实践操作_第12张图片
在这里我们看到了一个of函数来获取gpio的编号,就是of_get_named_gpio函数,这个函数的详情如下
Linux嵌入式驱动开发12——pinctl和gpio子系统实践操作_第13张图片

gpio_free函数

Linux嵌入式驱动开发12——pinctl和gpio子系统实践操作_第14张图片

gpio_direction_input函数

Linux嵌入式驱动开发12——pinctl和gpio子系统实践操作_第15张图片

gpio_dierction_output函数

Linux嵌入式驱动开发12——pinctl和gpio子系统实践操作_第16张图片

gpio_get_value函数

Linux嵌入式驱动开发12——pinctl和gpio子系统实践操作_第17张图片

gpio_set_value函数

Linux嵌入式驱动开发12——pinctl和gpio子系统实践操作_第18张图片

设备树节点

添加节点信息

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

现在首先还是在设备树文件中添加我们的节点信息。
Linux嵌入式驱动开发12——pinctl和gpio子系统实践操作_第19张图片

pinctrl_user: usergrp {
			fsl,pins = <
				MX6QDL_PAD_EIM_DA15__GPIO3_IO15 0x1b0b0
				MX6QDL_PAD_EIM_A17__GPIO2_IO21	0x1b0b0
			>;
		};

我们要控制的gpio引脚是EIM_A17
Linux嵌入式驱动开发12——pinctl和gpio子系统实践操作_第20张图片
那么,我们是如何找到EIM_A17对应的宏定义MX6QDL_PAD_EIM_A17__GPIO2_IO21呢?

pinfunc.h文件查找宏定义

在我们的dts文件夹下,有一个对应imx6q-pinfunc.h文件,里面定义着所需要的宏定义
Linux嵌入式驱动开发12——pinctl和gpio子系统实践操作_第21张图片
这样,我们只需要按照需要查找就可以了
Linux嵌入式驱动开发12——pinctl和gpio子系统实践操作_第22张图片
通过搜索EIM_A17.这样就找到了对应的宏定义,通过后面的后缀名称,可以看到有很多的功能
Linux嵌入式驱动开发12——pinctl和gpio子系统实践操作_第23张图片
我们也可以通过查找芯片手册,看到EIM_A17对应的引脚的说明IOMUXC_SW_MUX_CTL_PAD_EIM_ADDR17
Linux嵌入式驱动开发12——pinctl和gpio子系统实践操作_第24张图片
回到我们刚才说的,根据名称,我们需要一个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
Linux嵌入式驱动开发12——pinctl和gpio子系统实践操作_第25张图片
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	0x1b0b0

MX6QDL_PAD_EIM_A17__GPIO2_IO21 我们上面已经分析了,就剩下了一个 0x0b0b1反应快的同学应该已经猜出来了, 0x0b0b1就是 conf_reg寄存器值!

此值由用户自行设置,通过此值来设置一个 IO的上 /下拉、驱动能力和速度等。在这里就相当于设置寄存器IOMUXC_SW_MUX_CTL_PAD_EIM_ADDR17的值为 0x0b0b1。

如果不知道要设置成多少,这里我是当作普通gpio来进行操作,所以只需要参考led灯和按键的配置信息就可以了。

这里还有一个点需要注意,就是一个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口没有被使用过,不然就注释掉。

Linux嵌入式驱动开发12——pinctl和gpio子系统实践操作_第26张图片
然后对这些信息继续进行配置,在我们的test节点中,添加如下代码,test-gpios = <&gpio2 21 1>;对应的就是MX6QDL_PAD_EIM_A17__GPIO2_IO21,gpio2组中的第21个io口,默认置高。
Linux嵌入式驱动开发12——pinctl和gpio子系统实践操作_第27张图片
下面就该是driver驱动程序的代码了,依然是在probe函数中进行添加修改

测试程序源码

app.c

#include 
#include 
#include 
#include 
#include 

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 
#include 
#include 
#include 
#include 

#include 
#include 
#include 
#include 

#include 
#include 

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模块生成驱动

Linux嵌入式驱动开发12——pinctl和gpio子系统实践操作_第28张图片
在dev文件中查看驱动生成的节点
Linux嵌入式驱动开发12——pinctl和gpio子系统实践操作_第29张图片

执行app

我们可以看到对应的信息打印了出来,说明程序都得到了顺利的执行,接下来就是测试EIM_A17引脚实际的高低电平,通过万用表的测量,与打印信息一致。
Linux嵌入式驱动开发12——pinctl和gpio子系统实践操作_第30张图片

你可能感兴趣的:(i.MX6,linux,c++,嵌入式,飞凌)