15_pinctl和gpio子系统

pinctl和gpio子系统

1.什么是pinctrl和gpio子系统?

​ pinctrl子系统是用来设置引脚的复用关系和电气属性的, gpio子系统是当pinctrl子系统把引脚的复用关系设置为gpio功能以后就可以使用gpio子系统来操作引脚了, 比如引脚的输入输出,高低电平等

2.Linux Pinctrl子系统提供的功能是什么?

(1)管理系统中所有的可以控制的pin,在系统初始化的时候,枚举所有可以控制的pin,并标识这些pin。

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

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

3.不同soc厂家的pin controller的节点

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

NXP 的 pinctrl 子系统如下所示 :

img

fsl,pins 属性主要用于描述设备的引脚和引脚控制信息。它是一个通用的属性,可以用于描述各种类型的设备。在具体的设备树节点中,它通常包含一个复杂的数据结构,用于描述设备的引脚控制信息。

三星pinctrl 子系统如下所示:

image-20240425123300072

瑞星微pinctrl 子系统如下所示:

img

4. 不同soc厂家的pin controller的节点里面的属性都是什么意思?

可以通过Linux源码目录Documentation/devicetree/bindings下的txt文档查看。

5. 怎么在代码里面使用pin controller里面定义好的节点?

例1:

pinctrl-names = "default"; 
pinctrl-0 = <&pinctrl_hog_1>;

含义:

​ <1>pinctrl-names = "default";

​ 设备的状态,可以有多个状态,default为状态0

​ <2>pinctrl-0 = <&pinctrl_hog_1>;

​ 第0个状态所对应的引脚配置,也就是default状态对应的引脚在pin controller里面定义好的节点pinctrl_hog_1里面的管脚配置

例2:

pinctrl-names = "default","wake up"; 
pinctrl-0 = <&pinctrl_hog_1>;
pinctrl-1 = <&pinctrl_hog_2>;

含义

​ <1>pinctrl-names = "default","wake up";

​ 设备的状态,可以有多个状态,default为状态0,wake up为状态1

​ <2>pinctrl-0 = <&pinctrl_hog_1>;

​ 第0个状态所对应的引脚配置,也就是default状态对应的引脚在pin controller里面定义好的节点pinctrl_hog_1里面的管脚配置

​ <3>pinctrl-1 = <&pinctrl_hog_2>;

​ 第1个状态所对应的引脚配置,也就是wake up状态对应的引脚在pin controller里面定义好的节点pinctrl_hog_2里面的管脚配置

例3:

pinctrl-names = "default"; 
pinctrl-0 = <&pinctrl_hog_1
			&pinctrl_hog_2>;

含义

​ <1>pinctrl-names = "default";

​ 设备的状态,可以有多个状态,default为状态0

​ <2>pinctrl-0 = <&pinctrl_hog_1 &pinctrl_hog_2>;

​ 第0个状态所对应的引脚配置,也就是default状态对应的引脚在pin controller里面定义好的节点,对应pinctrl_hog_1和pinctrl_hog_2这俩个节点的管脚配置

​ Linux 内核提供了 pinctrl 子系统和 gpio 子系统用于 GPIO 驱动,当然 pinctrl 子系统负责的就不仅仅是 GPIO 的驱动了而是所有 pin 脚的配置。pinctrl 子系统是随着设备树的加入而加入的,依赖于设备树。GPIO 子系统在之前的内核中也是存在的,但是 pinctrl 子系统的加入 GPIO 子系统也是有很大的改变。

​ 在以前的内核版本中,如果要配置 GPIO 的话一般要使用 SOC 厂家实现的配置函数,例如三星的配置函s3c_gpio_cfgpin等,这样带来的问题就是各家有各家的接口函数与实现方式,不但内核的代码复用率低而且开发者很难记住这么多的函数,如果要使用多种平台的话背函数都是很麻烦的,所以在引入设备树后对 GPIO 子系统进行了大的改造,使用设备树来实现并提供统一的接口。通过 GPIO 子系统功能主要实现引脚功能的配置,如设置为 GPIO,特殊功能,GPIO 的方向,设置为中断等。

​ 那么我们先来看一下怎么在设备树中pinctrl和gpio子系统描述一个gpio。

<1>设备树使用pinctrl和gpio子系统描述一个gpio。

test1:test{
	#address-cells = <1>;
	#size-cells = <1>;
	compatible = "test";
	reg = <0x020ac000 0x00000004>;
	
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_beep>;
	beep-gpio = <&gpio5 1 GPIO_ACTIVE_LOW>;
};

pinctrl_beep: beep {
        fsl,pins = <
                MX6ULL_PAD_SNVS_TAMPER1__GPIO5_IO01     0x17059
        >;
};

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

goio头文件:

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

1 、gpio_request 函数

作用:

gpio_request 函数用于申请一个 GPIO 管脚

函数原型:

int gpio_request(unsigned gpio, const char *label);

参数:

gpio:要申请的 gpio 标号,使用 of_get_named_gpio 函数从设备树获取指定 GPIO 属性信息,此函数会返回这个 GPIO 的标号。

label:给 gpio 设置个名字。

返回值:

0,申请成功;其他值,申请失败。

2 、gpio_free函数

作用:如果不使用某个 GPIO 了,那么就可以调用 gpio_free 函数进行释放。

函数原型:

void gpio_free(unsigned gpio);

参数:

gpio:要释放的 gpio 标号。

返回值:无。

3 、gpio_direction_input 函数

作用:

此函数用于设置某个 GPIO 为输入

函数原型:

int gpio_direction_input(unsigned gpio);

参数:

gpio:要设置为输入的 GPIO 标号。

返回值:设置成功返回0;设置失败返回负值

4 、gpio_direction_output 函数

作用:此函数用于设置某个 GPIO 为输出,并且设置默认输出值

函数原型:

int gpio_direction_output(unsigned gpio, int value);

参数:

gpio:要设置为输出的 GPIO 标号。

value:GPIO 默认输出值。

返回值:设置成功返回0;设置失败返回负值

5 、gpio_get_value 函数

作用:

此函数用于获取某个 GPIO 的值(0 或 1)

函数原型:

int __gpio_get_value(unsigned gpio);

参数:

gpio:要获取的 GPIO 标号。

返回值:成功返回GPIO值,失败返回负值。

6 、gpio_set_value 函数

作用:

此函数用于设置某个 GPIO 的值

函数原型:

void __gpio_set_value(unsigned gpio, int value);

参数:

gpio:要设置的 GPIO 标号。

value:要设置的值。

返回值:无

7 、 of_get_named_gpio 函数

作用:

此函数获取 GPIO 编号,因为 Linux 内核中关于 GPIO 的 API 函数都要使用 GPIO 编号,此函数会将设备树中类似<&gpio1 3 GPIO_ACTIVE_LOW>的属性信息转换为对应的 GPIO 编号,函数原型如下:

int of_get_named_gpio(struct device_node *np,const char *propname,int index);

参数:

np:设备节点。

propname:包含要获取 GPIO 信息的属性名。

index:因为一个属性里面可能包含多个 GPIO,此参数指定要获取哪个 GPIO的编号,如果只有一个 GPIO 信息的话此参数为 0。

返回值:

成功返回到的 GPIO 编号,失败返回一个负数。

Pinctrl 和 GPIO 子系统实验

driver.c

#include <linux/init.h>   //包含宏定义的头文件
#include <linux/module.h> //包含初始化加载模块的头文件
#include <linux/platform_device.h> //平台设备所需要的头文件
#include <linux/of.h> //of函数
#include <linux/of_address.h> //of_iomap函数
#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}; //读取到的数组值
unsigned int *vir_gpio5_dr; //地址映射
int beep_gpio = 0; //gpio标号

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 mise_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) + 1) != 0)
    {
        printk("copy_to_user error\n");
        return -1;
    }
    printk("hello misc_read bye bye\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};
    if (copy_from_user(kbuf, ubuf, size) != 0)
    {
        printk("copy_from_user error\n");
        return -1;
    }
    printk("hello misc_write bye bye\n");
    if(kbuf[0] == 1) //打开蜂鸣器
    {
        __gpio_set_value(beep_gpio, 1);
    }else if(kbuf[0] == 0)//关闭蜂鸣器
    {
        __gpio_set_value(beep_gpio, 0);
    }
    return 0;
}

/* 文件操作集 */
struct file_operations misc_fops = {
    .owner = THIS_MODULE, // 当前模块
    .open = misc_open,
    .release = misc_release,
    .write = misc_write,
    .read = misc_read,
};

/* 杂项设备结构体 */
struct miscdevice misc_dev = {
    .minor = MISC_DYNAMIC_MINOR, // 动态分配次设备号
    .name = "hello_mise",        // 设备节点的名字
    .fops = &misc_fops           // 文件操作集

};

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

    printk("beep_probe\n");
    
    test_device_node = of_find_node_by_path("/test"); //查找根节点下的test节点
    if(test_device_node == NULL)
    {
        printk("of_find_node_by_path is error\n");
        return -1;
    }
    printk("test_device_node name is %s\n", test_device_node->name);
    beep_gpio = of_get_named_gpio(test_device_node, "beep-gpio", 0); //获取GPIO标号
    if(beep_gpio < 0)
    {
        printk("of_get_named_gpio is error\n");
        return -1;
    }
    printk("beep_gpio is %d\n", beep_gpio);
    ret =  gpio_request(beep_gpio, "beep"); //申请一个 GPIO 管脚
    if(ret != 0)
    {
        printk("gpio_request is error\n");
        return -1;
    }
    printk("gpio_request is ok\n");
    ret = gpio_direction_output(beep_gpio, 0); //设置GPIO为输出,并且设置默认输出值
    if(ret < 0)
    {
        printk("gpio_direction_output is error\n");
        return -1;
    }
    printk("gpio_direction_output is ok\n");
    ret = misc_register(&misc_dev); // 注册杂项设备
    if (ret < 0)
    {
        printk("misc register is error!\n");
        return -1;
    }
    printk("mise register is ok!\n");
    vir_gpio5_dr = of_iomap(test_device_node, 0); //直接内存映射
    if(vir_gpio5_dr == NULL)
    {
        printk("of_iomap is error\n");
        return -1;
    }
    printk("of_iomap is ok!\n");
    return 0;
}

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

struct platform_device_id beep_id_table = {
    .name = "123"
};

struct of_device_id	of_match_table_test[] = {  //与设备树的 compatible 匹配
    {
        .compatible = "test1234"
    },
    {
        
    }
};

/* platform 驱动结构体 */
struct platform_driver beep_platform_driver = {
    .probe = beep_probe,
    .remove = beep_remove,
    .driver = {
        .owner = THIS_MODULE,
        .name = "beep_test", //匹配优先级3
        .of_match_table = of_match_table_test, //中的.compatible匹配优先级1
    },
    .id_table = &beep_id_table //中的.name匹配优先级2
};

static int beep_driver_init(void)
{
    int ret = 0;
    printk("hello world\n");
    ret = platform_driver_register(&beep_platform_driver); //注册平台驱动
    if(ret < 0)
    {
        printk("platform_driver_register is error\n");
        return ret;
    }
    return 0;
}

static void beep_driver_exit(void)
{
    printk("byby\n"); // 内核模块卸载的时候打印"byb byb
    gpio_free(beep_gpio); //释放GPIO
    platform_driver_unregister(&beep_platform_driver); //卸载 platform 驱动
    misc_deregister(&misc_dev); //注销杂项设备
    iounmap(vir_gpio5_dr); //释放掉ioremap映射的地址
    
}

module_init(beep_driver_init); // 驱动模块的入口
module_exit(beep_driver_exit); // 驱动模块的出口

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

Makefile

obj-m +=driver.o
KDIR:=/home/mzx/imx6ull/linux-imx-rel_imx_4.1.15_2.1.0_ga 
PWD?=$(shell pwd)

all:
	make -C $(KDIR) M=$(PWD) modules

app.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, const char *argv[])
{
    int fd;
    char buf[64] = {0};
    
    if(argc < 2)
    {
        printf("Usage: %s <1:beep open / 0:beep close>\n", argv[0]);
        return -1;
    }
    
    fd = open("/dev/hello_mise", O_RDWR);
    if (fd < 0)
    {
        perror("open error\n");
        return fd;
    }
    buf[0] = atoi(argv[1]); //字符串转int
    write(fd, buf, strlen(buf)+1);
    close(fd);
    return 0;
}
posted @ 2024-04-25 22:35  爱吃冰激凌的黄某某  阅读(20)  评论(0编辑  收藏  举报