linux驱动:(19)pinctl和gpio子系统讲解+实战蜂鸣器

发布于:2024-09-17 ⋅ 阅读:(174) ⋅ 点赞:(0)

目录

pinctl子系统(引脚控制相关)

区别

功能

如何与设备树关联在一起

在设备树中如何描述

如何使用

例子1

例子2

例子3

gpio子系统(通用输入输出)

功能

如何在设备树中描述gpio

gpio常用api

gpio_request

gpio_free

gpio_direction_input

gpio_direction_output

gpio_get_value

gpio_set_vlaue

实战(用pinctl和gpio来控制蜂鸣器引脚)


pinctl子系统(引脚控制相关)

区别

  • 以前我们都是控制寄存器的值来控制gpio,这是一种方法,但用了设备树后,厂家会提供对应的设备树文件,里面都会定义好了一些功能,也定义好了功能需要用到的gpio,我们调用就可以了,这就是pinctl子系统

功能

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

如何与设备树关联在一起

  • 我们可以把某一个外设要用到的gpio都写到一个节点里
  • 我们要用的时候在从这个节点里取出来
  • 不同厂家的pin controller 的节点都是把某些引脚复用成某些功能

在设备树中如何描述

  • 在根节点中找到一个名为 iomuxc 的节点
    &iomuxc{
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_hog_1>;
        imx6ul-evk{
            pinctrl_hog_1: hoggrp-1{//功能1
                fsl,pins = <//引脚配置
                    MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0X17059//SD1 CD
                    MX6UL_PAD__GPIO1_IO05__USDHC1_VSELECT 0X17059//SD1 VSELECT
                    MX6UL_PAD__GPIO1_IO00__ANATOP_OTG1_ID 0x13058//USB_OTG1_ID
    
                >;
            };
            pinctrl_csi1: csi1grp{
                fsl,pins = <
                ......
                >;
            };
            pinctrl_enet1: enet1grp {
                fsl,pins = <
                ......
                >;
            };
            pinctrl_enet2: enet2grp{
                fsl,pins = <
                ......
                >;
            };
        }
    }
  • 节点中有很多功能,每个功能都定义了相对应的引脚,引脚定义中的前面是宏定义,后面是定义属性
  • 不同开发板定义的结构不一样,有的省去了fsl,pins这个属性,有些是变换了属性的名字,但都是这一个意思,把功能用到的引脚都定义在一起
    • 不同开发板的属性都是什么意思可以在/linux源码目录/Documentation/devicetree/bindings下面的txt文档查看

如何使用

例子1

pinctrl-names = "default";
pinctrl-0 = <&pinctrl_hog_1>;
  • 含义
    • 第一行:设备的状态,可以有多个状态,default为状态0
    • 第二行:第0个状态所对应的引脚配置,也就是default状态对应得引脚在pin controller里面定义好的节点pinctrl_hog_1里面的管脚配置 

例子2

pinctrl_names = "default", "wake up";
pinctrl-0 = <&pinctrl_hog_1>;
pinctrl-1 = <&pinctrl_hog_2>;
  • 含义
    • 第一行:设备的状态,可以有多个状态,default为状态0,wake up为状态1
    • 第二行:第0个状态所对应的引脚配置,也就是 default 状态对应的引脚在 pin controller 里面定义好的节点 pinctrl_hog_1 里面的管脚配置
    • 第三行:第1个状态所对应的引脚配置,也就是 wake up 状态对应的引脚在 pin controller 里面定义好的节点 pinctrl_hog_2 里面的管脚配置

例子3

pinctrl_names = "default";
pinctrl-0 = <&pinctrl_hog_1
             &pinctrl_hog_2>;
  • 含义
    • 第一行:设备的状态,可以有多个状态,default为状态0
    • 第二行:第0个状态所对应的引脚配置,也就是 default 状态对应的引脚在 pin controller 里面定义好的节点,对应pinctrl_hog_1 和 pinctrl_hog_2  这俩个节点的管脚配置

gpio子系统(通用输入输出)

功能

  • Linux 内核提供了 pinctrl 子系统和 gpio 子系统用于 GPIO 驱动,
  • 当然 pinctr! 子系统负责的就不仅仅是 GPIO 的驱动了而是所有 pin脚的配置。
  • pinctr! 子系统是随着设备树的加入而加入的,依赖于设备树。G
  • PIO 子系统在之前的内核中也是存在的,但是 pinctr 子系统的加入 GPIO 子系统也是有很大的改变在以前的内核版本中,
  • 如果要配置 GPIO 的话一般要使用 SOC 厂家实现的配置函数例如三星的配置函 s3c_gpio_cfgpin 等,
  • 这样带来的问题就是各家有各家的接口函数与实现方式,不但内核的代码复用率低而且开发者很难记住这么多的函数,
  • 如果要使用多种平台的话背函数都是很麻烦的,所以在引入设备树后对 GPIO 子系统进行了大的改造,使用设备树来实现并提供统一的接口。
  • 通过 GPIO 子系统功能主要实现引脚功能的配置,如设置为 GPIO特殊功能,GPIO 的方向,设置为中断等。

如何在设备树中描述gpio

test1:test{
    #address-cells = <1>;
    #size-cells = <1>;
    compatible = "test";
    reg = <0x20ac000 0x0000004>;
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_test>;
    test-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
};
  • 新建一个节点
  • 最后一行表示,在gpio1组里面的第3个gpio口低电平

gpio常用api

  • gpio_request

    • 申请一个GPIO管脚
    • 原型:
      int gpio_request(unsigned gpio,const char* label)
    • 参数:
      • gpio:要申请的 gpio 标号,使用 of_get_named_gpio 函数(前面19章有讲)从设备树获取指定 GPIO 属性信息,此函数会返回这个 GPIO 的标号。
      • label:给 gpio 设置个名字。
    • 返回值:0申请成功,其他值申请失败。
  • gpio_free

    • 如果不使用某个 GPIO 了,那么就可以调用 gpio_free 函数进行释放。
    • 原型:
      void gpio_free(unsigned gpio)
    • 参数:
      • gpio:要释放的 gpio 标号
    • 返回值:无
  • gpio_direction_input

    • 此函数用于设置某个 GPIO 为输入
    • 原型:
      int gpio_direction_input(unsigned gpio)
    • 参数:
      • gpio:要设置的 gpio 标号
    • 返回值:0成功,负值失败
  • gpio_direction_output

    • 此函数用于设置某个 GPIO 为输出,并设置默认输出值
    • 原型:
      int gpio_direction_output(unsigned gpio)
    • 参数:
      • gpio:要设置的 gpio 标号
    • 返回值:0成功,负值失败
  • gpio_get_value

    • 此函数用于获取某个 GPIO 的值(0或1)
    • 原型:
      int gpio_get_value(unsigned gpio)
    • 参数:
      • gpio:要获取的 gpio 标号
    • 返回值:成功返回gpio值,失败负值
  • gpio_set_vlaue

    • 此函数用于设置某个 GPIO 的值(0或1)
    • 原型:
      void gpio_set_vlaue(unsigned gpio,int value)
    • 参数:
      • gpio:要设置的 gpio 标号
      • value:要设置的值
    • 返回值:无

实战(用pinctl和gpio来控制蜂鸣器引脚)

步骤

  • 先在设备树根节点中添加我们测试的节点,我们使用的是前面添加的自定义节点
    • 在之前的自定义节点之前添加pinctl子系统属性
      • 设备状态
      • 定义状态所对应的节点
      • 定义gpio的属性,到时用来将这个属性转换为上面gpio要使用的gpio标号
        /{
            test1:test{ //别名为test1,节点名称为test
                compatible = "test";//匹配的名字
                #address-cells = <1>;
                #size-cells = <1>;
                reg = <0x20ac000 0x0000004>;//寄存器地址
                pinctrl-names = "default";//设备状态
                pinctrl-0 = <&pinctrl_beep>;//状态绑定节点
                beep-gpio = <&gpio5 1 GPIO_ACTIVE_LOW>;//申请gpio所需的属性
            };
        };
        &test1{
                compatible = "test1";//匹配的名字
                status = "okay"; 
        };
  • 添加状态对应的新节点,在iomuxc或者iomuxc_snvs节点中添加
    • 找到功能对应引脚的gpio口名字
    • 在手册上找到对应名字的gpio口复用成gpio第几组的第几个io口
    • 仿照其他的节点来写
    • 但对应的宏定义在对应板中的pinfunc.h中查找到,每个宏定义都代表那个gpio口复用成对应的功能,如iic,usart,spi等等,宏定义其实是地址偏移量
    • 电气属性就是gpio口寄存器配置成什么样的值
    • 检查用到的宏定义对应gpio口的其他功能宏定义是否在其他地方用到,用ctrl+f来查询
      • 如果定义了,就要找到对应的节点,把该功能注释掉,才不影响我们的使用
        pinctrl_beep:beep{
            fsl,pins = <
                对应gpio口要复用功能的宏定义  电气属性
            >;
        };
  • 在driver.c中添加我们调用的代码
    • 在probe函数中
      • 获取节点的名称
      • 申请gpio标签
      • 申请gpio
      • 设置gpio输入还是输出
      • 注册杂项设备或者字符设备来控制gpio(这里示例注册杂项设备)
      • 注册杂项设备
      • 定义miscdevice杂向设备结构体
      • 定义文件操作集结构体和各个函数
      • 其中在写函数中判断写入的字符来进行拉低还是拉高
      • 设置gpio输入还是输出

    • 在exit函数中
      • 注销杂项设备
      • 释放gpio
        #include <linux/init.h>
        #include <linux/module.h>
        #include <linux/miscdevice.h>
        #include <linux/fs.h>
        #include <linux/kdev_t.h>
        #include <linux/uaccess.h>
        #include <linux/cdev.h>
        #include <linux/io.h>
        #include <linux/device.h> 
        #include <linux/platform_device.h> 
        #include <linux/of.h>
        #include <linux/of_address.h>
        #include <linux/gpio.h>
        #include <linux/of_gpio.h>
        int size;
        u32 out_value[2]={0};
        const char* str;
        
        int beep_gpio = 0;
        struct device_node *test_device_node;
        struct property *test_node_property;
        unsigned int *vir_addr;
        
        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("hello misc_release bye bye\n");
            return 0;
        }
        ssize_t misc_read(struct file *file,char __user *ubuf,size_t size,loff_t *loff_t)
        {
            char kbuf[64] = "heheh";
            if (copy_to_user(ubuf,kbuf,strlen(kbuf)) != 0)
            {
                printk("copy_to_user error\n");
                return -1;
            }
            return 0;
        }
        ssize_t misc_write(struct file *file,char __user *ubuf,size_t size,loff_t *loff_t)
        {
            char kbuf[64] = {0};
            if (copy_from_user(kbuf,ubuf,size) != 0)
            {
                printk("copy_from_user error\n");
                return -1;
            }
            printk("kbuf is %s\n",kbuf);
            if (kbuf[0] == 1)
            {
                gpio_set_value(beep_gpio,1);
            }else{
                gpio_set_value(beep_gpio,0);
            }
            
            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",
            .fops = &misc_fops
        }
        int beep_probe(struct platform_device *pdev)
        {
            printk("beep_probe\n");
            int ret = 0;
            //获取节点的名称
            test_device_node = of_find_node_by_path("/test");
            if (test_device_node==NULL)
            {
                printk("of_find_node_by_path error \n");
                return -1;
            }
            printk("test_device_node is %s\n",test_device_node->name);
            //申请gpio标签
            beep_gpio = of_get_named_gpio(test_device_node,"beep-gpio",0);
            if (beep_gpio < 0)
            {
                printk("of_get_named_gpio error \n");
                return -1;
            }
            printk("beep_gpio is %s\n",beep_gpio);
            //申请gpio
            ret = gpio_request(beep_gpio,"beep");//beep为起的名字
            if (ret < 0)
            {
                printk("gpio_request error \n");
                return -1;
            }
            //设置gpio输入还是输出
            gpio_direction_output(beep_gpio,1);
            //注册杂项设备和字符设备来操控gpio
            //杂项
            //注册杂项设备
            return 0;
        }
        
        int beep_remove(struct platform_device *pedv)
        {
            printk("beep_remove\n");
            return 0;
        };
        const struct platform_device_id beep_id_table =
        {
            .name = "123"
        };
        const struct of_device_id *of_match_table_test[] = 
        {
            {.compatible = "test"},
            {}
        };
        
        struct platform_driver beep_device =
        {
            .probe = beep_probe,
            .remove = beep_remove,
            .driver = {
                .owner = THIS_MODULE,
                .name = "beep_test"
                .of_match_table= of_match_table_test;
            },
            .id_table = &beep_id_table
        };
        
         
        static int beep_driver_init(void)
        {
            int ret = 0;
            ret = platform_driver_register(&beep_device);
            if (ret<0)
            {
                printk("platform_driver_register error\n");
                return ret;
            }
            
            printk("platform_driver_register ok\n");
            return 0;
        }
         
        static void beep_driver_exit(void)
        {
            printk("beep goodbye");
            //注销杂项设备
            misc_deregister(&misc_dev);
            //释放gpio
            gpio_free(beep_gpio);
            platform_driver_unregister(&beep_device);
        }
         
        module_init(beep_driver_init);
        module_exit(beep_driver_exit);
        MODULE_LICENSE("GPL");
        
        
        
  • 检查设备树中节点的compatible属性与driver.c中的name匹不匹配
  • 编译driver.c成ko模块,放到开发板中
  • 将修改后的设备树编译成dtb,烧写进开发板
  • 加载ko模块,确保不会报错
  • 查看/dev/目录里有没有生成hello_misc节点
  • 写一个.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 buf[64] = {0};
          fd = open("/dev/hello_misc",O_RDWR);
          if (fd < 0)
          {
              perror("open error\n");
              return fd;
          }
          buf[0] = atoi(argv[1]);
          write(fd,buf,sizrof(buf));
          close(fd);
          return 0;
          
      }
  • 交叉编译,./app 1     ./app 0测试