- 积分
- 20
贡献148
飞刀48 FD
注册时间2020-11-2
在线时间4 小时
扫一扫,手机访问本帖
|
​https://blog.csdn.net/szm1234/article/details/113836382
复制过来发现格式不太对
文章目录
什么是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
什么是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子系统功能
管理系统中所有的可以控制的pin,在系统初始化的时候,枚举所有可以控制的pin,并标识这些pin
管理这些pin的复用,对于SOC而言,其引脚除了配置成普通的GPIO之外,若干个引脚还可以组成一个pin group,形成特定的功能
配置这些pin的特性,例如使能关闭引脚上的pull-up、pull-down电阻,配置引脚的driver strength
不同SOC厂家的pin controller的节点
这些节点里面都是把某些引脚复用成某些功能
查看属性表达
不同的SOC厂家,他们的pin controller的节点里面的属性表达的含义都是不同的,所以,要想查看这些属性的含义,去到Linux源码目录
/Documentation/devicetree/bindings
/Documentation/devicetree/bindings/pinctrl
进入到pinctrl文件中,可以看到txt文件,我们的型号是im6q,找到对应的文件就可以打开查看信息
查看pinctrl
打开我们板子对应的dts文件,这里的我是imx6q-c-sabresd.dts
打开之后发现没有我们想要看到的pinctrl相关的代码,都是一些引用添加节点信息
根据头文件,我们继续打开imx6qdl-sabresd.dtsi文件
在imx6qdl-sabresd.dtsi文件里就可以看到详细的pinctrl的信息了
gpio子系统
gpio子系统功能
也就是,我们的pinctrl子系统配置完成后,我们接下来就是要交给gpio子系统来进行控制
在设备树文件中代码示例如下
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子系统之后,就可以替代之前的这种写法
在之前,我们控制引脚需要对寄存器进行控制,是reg = <0x20ac000 0x0000004>;
这样的形式进行描述,而现在使用了gpio子系统之后,就可以更方便规范的使用。
常用gpio子系统提供的api函数
gpio_request函数
在这里我们看到了一个of函数来获取gpio的编号,就是of_get_named_gpio函数,这个函数的详情如下
gpio_free函数
gpio_direction_input函数
gpio_dierction_output函数
gpio_get_value函数
gpio_set_value函数
设备树节点
添加节点信息
我们从上次的代码,设备树的驱动代码进行修改,本次使用了pinctrl和gpio子系统,本质上是替代了之前的寄存器操作,使用函数来进行电平的控制。
现在首先还是在设备树文件中添加我们的节点信息。
pinctrl_user: usergrp {
fsl,pins = <
MX6QDL_PAD_EIM_DA15__GPIO3_IO15 0x0b0b1
MX6QDL_PAD_EIM_A17__GPIO2_IO21 0x0b0b1
>;
};
我们要控制的gpio引脚是EIM_A17
那么,我们是如何找到EIM_A17对应的宏定义MX6QDL_PAD_EIM_A17__GPIO2_IO21呢?
pinfunc.h文件查找宏定义
在我们的dts文件夹下,有一个对应imx6q-pinfunc.h文件,里面定义着所需要的宏定义
这样,我们只需要按照需要查找就可以了
通过搜索EIM_A17.这样就找到了对应的宏定义,通过后面的后缀名称,可以看到有很多的功能
我们也可以通过查找芯片手册,看到EIM_A17对应的引脚的说明IOMUXC_SW_MUX_CTL_PAD_EIM_ADDR17
回到我们刚才说的,根据名称,我们需要一个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
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 0x0b0b1
MX6QDL_PAD_EIM_A17__GPIO2_IO21 我们上面已经分析了,就剩下了一个 0x0b0b1反应快的同学应该已经猜出来了, 0x0b0b1就是 conf_reg寄存器值!此值由用户自行设置,通过此值来设置一个 IO的上 /下拉、驱动能力和速度等。在这里就相当于设置寄存器IOMUXC_SW_MUX_CTL_PAD_EIM_ADDR17的值为 0x0b0b1。
这里还有一个点需要注意,就是一个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口没有被使用过,不然就注释掉。
然后对这些信息继续进行配置,在我们的test节点中,添加如下代码,test-gpios = <&gpio2 21 1>;对应的就是MX6QDL_PAD_EIM_A17__GPIO2_IO21,gpio2组中的第21个io口,默认置高。
下面就该是driver驱动程序的代码了,依然是在probe函数中进行添加修改
测试程序源码
app.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 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 <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#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};
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模块生成驱动
在dev文件中查看驱动生成的节点
执行app
我们可以看到对应的信息打印了出来,说明程序都得到了顺利的执行,接下来就是测试EIM_A17引脚实际的高低电平,通过万用表的测量,与打印信息一致。
|
|