目录
一、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");