pinctrl 和 gpio 子系统点灯上

文章详细介绍了Linux驱动中pinctrl子系统的作用,包括设置PIN信息、复用功能和电气特性。接着讲解了gpio子系统如何初始化GPIO并提供API。文中还展示了创建字符设备驱动的过程,包括设备结构体、注册驱动、操作集合函数等,并提到了设备树的修改方法。最后,文章提供了添加自动创建设备节点和完善操作集合函数的步骤。
摘要由CSDN通过智能技术生成

目录

一、pinctrl 子系统简介

添加一个PIN的信息

二、gpio 子系统简介

三、工程环境

1、创建工作区

2、 创建gpioled.c,添加头文件

3、 修改makefile

四、修改设备树

1、添加compatible、pinctrl-names

2、pinctrl_gpioled 节点创建

3、添加 led-gpios和status ​编辑

4、 编译验证

五、建立基本的字符设备框架

1、设备结构体

2、gpioled的设备结构体 

3、注册字符设备驱动 

4、操作集合函数 

 5、初始化cdev

 目前总体代码如下

 6、编译 验证

 7、添加自动创建设备节点

 8、完善gpioled操作集合函数

9、编译验证

总体代码如下 


一、pinctrl 子系统简介

传统的配置 pin 的方式就是直接操作相应的寄存器,但是这种配置方式比较繁琐、而且容易出问题(比如 pin 功能冲突)。 pinctrl 子系统就是为了解决这个问题而引入的, pinctrl 子系统主要工作内容如下:
①、获取设备树中 pin 信息。
②、根据获取到的 pin 信息来设置 pin 的复用功能
③、根据获取到的 pin 信息来设置 pin 的电气特性,比如上/下拉、速度、驱动能力等。
对于我们使用者来讲,只需要在设备树里面设置好某个 pin 的相关属性即可,其他的初始化工作均由 pinctrl 子系统来完成, pinctrl 子系统源码目录为 drivers/pinctrl

添加一个PIN的信息

以pinctrl_hog_1: hoggrp-1 为例,如图

在这相当于设置寄存器IOMUXC_SW_PAD_CTL_PAD_UART1_RTS_B电气属性的值为 0x17059
其他配置在imx6ul-pinfunc.h中找对应的宏定义,如图代码

 MX6UL_PAD_UART1_RTS_B__GPIO1_IO19宏定义对应格式如下

<mux_reg    conf_reg    input_reg     mux_mode      input_val>

 0x0090        0x031C     0x0000         0x5                 0x0

mux_reg: 寄存器偏移地址,设备树中的 iomuxc 节点就是 IOMUXC 外设对应的节点。IOMUXC寄 存 器 起 始 地 址 为0x020e0000,因此UART1_RTS_B这个PIN的mux寄存器地址 就是:0x020e0000+0x0090=0x020e 0090

 conf_reg: 寄存器偏移地址,和 mux_reg 一样, 0x020e0000+0x031c=0x020e031c,这个就是电气属性配置寄存器 IOMUXC_SW_PAD_CTL_PAD_UART1_RTS_B 的地址

input_reg: 寄存器偏移地址有些外设有 input_reg 寄存器,有 input_reg 寄存器的外设需要配置 input_reg 寄存器。没有的话就不需要设置, UART1_RTS_B 这个 PIN 在做GPIO1_IO19 的时候是没有 input_reg 寄存器,因此这里 intput_reg 是无效的

mux_mode: 在 这 里 就 相 当 于 设 置IOMUXC_SW_MUX_CTL_PAD_UART1_RTS_B 寄存器为 0x5,也即是设置 UART1_RTS_B 这个 PIN 复用为 GPIO1_IO19

input_val:就是写入input_reg寄存器的值,在这里无效

二、gpio 子系统简介

pinctrl 子系统重点是设置 PIN(有的 SOC 叫做 PAD)的复用和电气属性,如果 pinctrl 子系统将一个 PIN 复用为 GPIO 的话,那么接下来就要用到 gpio 子系统了

gpio子系统用于初始化 GPIO 并且提供相应的 API 函数,比如设置 GPIO为输入输出,读取 GPIO 的值等。 gpio 子系统的主要目的就是方便驱动开发者使用 gpio,驱动开发者在设备树中添加 gpio 相关信息,然后就可以在驱动程序中使用 gpio 子系统提供的 API函数来操作 GPIO

三、工程环境

1、创建工作区

2、 创建gpioled.c,添加头文件

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/slab.h>
#include <linux/of_address.h>

3、 修改makefile

四、修改设备树

1、添加compatible、pinctrl-names

pinctrl-names 属性,此属性描述 pinctrl 名字一般为“default” 

pinctrl-0 节点,此节点引用自己创建的 pinctrl_gpioled 节点,表示 gpioled 设备的所使用的 PIN 信息保存在 pinctrl_gpioled 节点中

2、pinctrl_gpioled 节点创建

在dts文件中iomuxc下添加GPIO1_IO03,在imx6ul-pinfunc.h文件中找到宏定义

“fsl,pins”属性设备树是通过属性来保存信息的,因此我们需要添加一个属性,属性名字一定要为“fsl,pins”,因为对于 I.MX 系列 SOC 而言, pinctrl 驱动程序是通过读取“fsl,pins”属性值来获取 PIN 的配置信息,在其括号内添加设备所使用的 PIN 配置信息,并配置自己需要的电器属性值即可

3、添加 led-gpios和status 

led-gpios是添加 GPIO 属性信息,表明gpioled 所使用的 GPIO 是哪个引脚 ,使用低电平

4、 编译验证

 拷贝到tftpboot目录下,启动开发板查看节点

 

五、建立基本的字符设备框架

1、设备结构体

先定义两个宏定义

#define GPIOLED_CNT 1
#define GPIOLED_NAME "gpioled"

2、gpioled的设备结构体 

因为注册字符设备驱动和使用cdev结构体需要先定义,所以先定义gpioled的设备结构体

struct gpioled_dev{
    dev_t devid;    /* 设备号 	 */
    int major;  /* 主设备号	  */
    int minor;  /* 次设备号   */
    struct cdev cdev;   /* cdev 	*/
}gpioled;/*LED设备*/

3、注册字符设备驱动 

/*注册字符设备驱动*/
    gpioled.major = 0;
    if(gpioled.major){/*给定主设备号*/
        gpioled.devid = MKDEV(gpioled.major,0); /*构建设备号*/
        register_chrdev_region(gpioled.devid,GPIOLED_CNT,GPIOLED_NAME); /*注册设备号*/
    }else{/*没给定设备号*/
        alloc_chrdev_region(&gpioled.devid,0,GPIOLED_CNT,GPIOLED_NAME); /* 申请设备号 */
        gpioled.major = MAJOR(gpioled.devid);   /* 获取分配号的主设备号 */
        gpioled.minor = MINOR(gpioled.devid);   /* 获取分配号的次设备号 */
    }
    printk("gpioled major=%d ,gpioled minor =%d\r\n",gpioled.major,gpioled.minor);

4、操作集合函数 

 因为初始化cdev需要用操作集合函数,所以先定义操作集

/*gpioled操作集合函数*/
static const struct file_operations gpioled_fops = {
    .owner = THIS_MODULE,
};

 5、初始化cdev

/*初始化cdev*/
    gpioled.cdev.owner = THIS_MODULE;
    cdev_init(&gpioled.cdev,&gpioled_fops);/*初始化的 cdev 结构体变量*/
    cdev_add(&gpioled.cdev,gpioled.devid,GPIOLED_CNT);/*添加字符设备*/

 目前总体代码如下

#define GPIOLED_CNT 1
#define GPIOLED_NAME "gpioled"

/*gpioled设备结构体*/
struct gpioled_dev{
    dev_t devid;    /* 设备号 	 */
    int major;  /* 主设备号	  */
    int minor;  /* 次设备号   */
    struct cdev cdev;   /* cdev 	*/
}gpioled;/*LED设备*/
/*gpioled操作集合函数*/
static const struct file_operations gpioled_fops = {
    .owner = THIS_MODULE,
};

/*驱动入口函数*/
static int __init gpioled_init(void){

    /*注册字符设备驱动*/
    gpioled.major = 0;
    if(gpioled.major){/*给定主设备号*/
        gpioled.devid = MKDEV(gpioled.major,0); /*构建设备号*/
        register_chrdev_region(gpioled.devid,GPIOLED_CNT,GPIOLED_NAME); /*注册设备号*/
    }else{/*没给定设备号*/
        alloc_chrdev_region(&gpioled.devid,0,GPIOLED_CNT,GPIOLED_NAME); /* 申请设备号 */
        gpioled.major = MAJOR(gpioled.devid);   /* 获取分配号的主设备号 */
        gpioled.minor = MINOR(gpioled.devid);   /* 获取分配号的次设备号 */
    }
    printk("gpioled major=%d ,gpioled minor =%d\r\n",gpioled.major,gpioled.minor);
    /*初始化cdev*/
    gpioled.cdev.owner = THIS_MODULE;
    cdev_init(&gpioled.cdev,&gpioled_fops);/*初始化的 cdev 结构体变量*/
    cdev_add(&gpioled.cdev,gpioled.devid,GPIOLED_CNT);/*添加字符设备*/

    return 0;
}

/*驱动出口函数*/
static void __exit gpioled_exit(void){
    /*注销字符设备驱动*/
    cdev_del(&gpioled.cdev);
    /*释放设备号*/
    unregister_chrdev_region(gpioled.devid,GPIOLED_CNT);
}
/*注册模块注销模块*/
module_init(gpioled_init);
module_exit(gpioled_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("ba che kai qi lai");

 6、编译 验证

 复制到开发板根文件rootfs/lib/modules/4.1.15/目录下,加载验证

 7、添加自动创建设备节点

 在创建设备之前需要用到class结构体和device结构体,所以要先在gpioled设备结构体中定义

/*gpioled设备结构体*/
struct gpioled_dev{
    dev_t devid;    /* 设备号 	 */
    int major;  /* 主设备号	  */
    int minor;  /* 次设备号   */
    struct cdev cdev;   /* cdev 	*/
    struct class *class; /* 类 */
    struct device *device; /* 设备 */
}gpioled;/*LED设备*/

 创建类和设备,在函数入口后面添加

    /*创建类*/
    gpioled.class = class_create(THIS_MODULE,GPIOLED_NAME);
    if(IS_ERR(gpioled.class)){
        return PTR_ERR(gpioled.class);
    }
    /*创建设备*/
    gpioled.device = device_create(gpioled.class,NULL,
                                    gpioled.devid,NULL,GPIOLED_NAME);
    if(IS_ERR(gpioled.device)){
        return PTR_ERR(gpioled.device);
    }

注销创建类和设备, 在函数出口前面添加

/*摧毁设备*/
    device_destroy(gpioled.class,gpioled.devid);
    /*删除类*/
    class_destroy(gpioled.class);

 8、完善gpioled操作集合函数

/*gpioled操作集合函数*/
static const struct file_operations gpioled_fops = {
    .owner  =   THIS_MODULE,
    .write  =   gpioled_write,
    .open   =   gpioled_open,
    .release =  gpioled_release,
};

对应的打开关闭和写函数都需要在gpioled操作集合函数结构体之前实现 ,代码如下

static int gpioled_open(struct inode *inode, struct file *filp){
    filp->private_data = &gpioled;/*私有数据*/
    return 0;
}
static ssize_t gpioled_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt){
    return 0;
}
static int gpioled_release(struct inode *inode, struct file *filp){
	return 0;
}

9、编译验证

 

总体代码如下 

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/slab.h>
#include <linux/of_address.h>

#define GPIOLED_CNT 1
#define GPIOLED_NAME "gpioled"

/*gpioled设备结构体*/
struct gpioled_dev{
    dev_t devid;    /* 设备号 	 */
    int major;  /* 主设备号	  */
    int minor;  /* 次设备号   */
    struct cdev cdev;   /* cdev 	*/
    struct class *class; /* 类 */
    struct device *device; /* 设备 */
}gpioled;/*LED设备*/
static int gpioled_open(struct inode *inode, struct file *filp){
    filp->private_data = &gpioled;
    return 0;
}
static ssize_t gpioled_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt){
    return 0;
}
static int gpioled_release(struct inode *inode, struct file *filp){
	return 0;
}
/*gpioled操作集合函数*/
static const struct file_operations gpioled_fops = {
    .owner  =   THIS_MODULE,
    .write  =   gpioled_write,
    .open   =   gpioled_open,
    .release =  gpioled_release,
};

/*驱动入口函数*/
static int __init gpioled_init(void){

    /*注册字符设备驱动*/
    gpioled.major = 0;
    if(gpioled.major){/*给定主设备号*/
        gpioled.devid = MKDEV(gpioled.major,0); /*构建设备号*/
        register_chrdev_region(gpioled.devid,GPIOLED_CNT,GPIOLED_NAME); /*注册设备号*/
    }else{/*没给定设备号*/
        alloc_chrdev_region(&gpioled.devid,0,GPIOLED_CNT,GPIOLED_NAME); /* 申请设备号 */
        gpioled.major = MAJOR(gpioled.devid);   /* 获取分配号的主设备号 */
        gpioled.minor = MINOR(gpioled.devid);   /* 获取分配号的次设备号 */
    }
    printk("gpioled major=%d ,gpioled minor =%d\r\n",gpioled.major,gpioled.minor);
    /*初始化cdev*/
    gpioled.cdev.owner = THIS_MODULE;
    cdev_init(&gpioled.cdev,&gpioled_fops);/*初始化的 cdev 结构体变量*/
    cdev_add(&gpioled.cdev,gpioled.devid,GPIOLED_CNT);/*添加字符设备*/

    /*创建类*/
    gpioled.class = class_create(THIS_MODULE,GPIOLED_NAME);
    if(IS_ERR(gpioled.class)){
        return PTR_ERR(gpioled.class);
    }
    /*创建设备*/
    gpioled.device = device_create(gpioled.class,NULL,
                                    gpioled.devid,NULL,GPIOLED_NAME);
    if(IS_ERR(gpioled.device)){
        return PTR_ERR(gpioled.device);
    }
    return 0;
}

/*驱动出口函数*/
static void __exit gpioled_exit(void){
    /*摧毁设备*/
    device_destroy(gpioled.class,gpioled.devid);
    /*删除类*/
    class_destroy(gpioled.class);
    /*注销字符设备驱动*/
    cdev_del(&gpioled.cdev);
    /*释放设备号*/
    unregister_chrdev_region(gpioled.devid,GPIOLED_CNT);
}
/*注册模块注销模块*/
module_init(gpioled_init);
module_exit(gpioled_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ba che kai qi lai");

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值