当前位置: 首页 > news >正文

Linux驱动学习day21(GPIO子系统)

一、通用属性 

二、GPIO子系统的作用

管理GPIO,既能支持芯片体身的GPIO,也能支持扩展的GPIO。提供统一的、简便的访问接口,实现:输入、输出、中断。 

设备树中会有节点对应着GPIO控制器,里面含有cell属性,cell属性的意思是,以后如果使用到本组的GPIO,除组别之外还需要两个参数描述这个引脚。

1.1 复习基于设备树的led驱动程序 

#include <linux/module.h>     // 最基本模块宏
#include <linux/kernel.h>     // printk
#include <linux/init.h>       // __init/__exit
#include <linux/fs.h>         // register_chrdev 等
#include <linux/uaccess.h>    // copy_to_user, copy_from_user
#include <linux/types.h>      // dev_t, bool 等类型
#include <linux/device.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/gpio/consumer.h>
#include <linux/of_device.h>static int major = 0;
static struct class *xupt_led_class;
static struct gpio_desc *gpio;static const struct of_device_id led_based_gpio_dt_ids[] = {{ .compatible = "xupt,led_based_gpio", .data = (void *)NULL},{ /* sentinel */ }
};static ssize_t led_based_gpio_read (struct file *file, char __user *buf, size_t size, loff_t *off)
{int val;val = gpiod_get_value(gpio);printk("%s %s %d get gpio value %d\n" , __FILE__ , __FUNCTION__ , __LINE__ , val);return 0;
}static ssize_t led_based_gpio_write (struct file *file, const char __user *buf, size_t size, loff_t * off)
{int val;char status;val = copy_from_user(&status, buf, size);// if(led_buf[0] == 1){//     gpiod_set_value(gpio, 1);//     printk("led_light OK\n");// }// else{//     gpiod_set_value(gpio, 0);//     printk("led_unlight\n");// }gpiod_set_value(gpio, status);printk("%s %s %d\n" , __FILE__ , __FUNCTION__ , __LINE__);return 0;
}static int led_based_gpio_open (struct inode *inode, struct file *file)
{printk("%s %s %d\n" , __FILE__ , __FUNCTION__ , __LINE__);gpiod_direction_output(gpio, 1); return 0;
}static int led_based_gpio_release (struct inode *inode, struct file *file)
{printk("%s %s %d\n" , __FILE__ , __FUNCTION__ , __LINE__);return 0;
}static struct file_operations led_based_gpio_ops = {.owner     = THIS_MODULE,.read      = led_based_gpio_read,.write     = led_based_gpio_write,.open      = led_based_gpio_open,.release   = led_based_gpio_release,
};static int led_based_gpio_probe(struct platform_device *pdev)
{   /* register chrdev *//* class create */gpio = gpiod_get(&pdev->dev, "led" , 0);return 0;
}static int led_based_gpio_remove(struct platform_device *pdev)
{gpiod_put(gpio);device_destroy(xupt_led_class, MKDEV(major, 0));class_destroy(xupt_led_class);unregister_chrdev(major, "xupt_led");return 0;
}static struct platform_driver led_drv_based_gpio_driver = {.driver		= {.name	= "led_based_gpio",.of_match_table = led_based_gpio_dt_ids,},.probe		= led_based_gpio_probe,.remove		= led_based_gpio_remove,
};static int __init led_init(void)
{major = register_chrdev(0, "xupt_led", &led_based_gpio_ops);/* struct device *device_create(struct class *class, struct device *parent,dev_t devt, void *drvdata, const char *fmt, ...) */xupt_led_class = class_create(THIS_MODULE, "xupt_led_class");if (IS_ERR(xupt_led_class)) {pr_err("Failed to create class: %ld\n", PTR_ERR(xupt_led_class));return PTR_ERR(xupt_led_class);}/* device create */device_create(xupt_led_class, NULL, MKDEV(major, 0), NULL, "xupt_led_dev"); /* /dev/xupt_led_dev *///p_led_opr = get_board_led_opr();return platform_driver_register(&led_drv_based_gpio_driver);
}/* entry function */
static void __exit led_exit(void)
{/* distroy void device_destroy(struct class *class, dev_t devt)*/gpiod_put(gpio);platform_driver_unregister(&led_drv_based_gpio_driver);device_destroy(xupt_led_class, MKDEV(major, 0));class_destroy(xupt_led_class);if(xupt_led_class){class_destroy(xupt_led_class);}unregister_chrdev(major, "xupt_led");return;
}module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");

三、GPIO子系统的层次与数据结构

3.1 层次

 

构造gpio_chip结构体,构造好之后使用gpiochip_add_data()函数向上层gpiolib注册结构体,该函数会自动构造出gpio_device结构体

3.2 重要数据结构

3.2.1 gpio_chip结构体

 

3.2.2 gpio_device结构体

对于该GPIO控制器中的每个引脚,都会对应一个gpio_desc结构体。

 3.2.3 gpio_desc结构体

使用gpiod_get函数,可以获取gpio_desc结构体。在gpio_device结构体下面有gpio_desc结构体,系统会为该GPIO控制器中的每个引脚设置对应一个gpio_desc结构体。假设设备树是<&gpio3 10 xxx>,那么其构建过程:找到gpio控制器3,并且找到其中gpio_desc数组的第10个gpio_desc并返回。如下图所示。

偏移值的计算如下图所示(数组中指针的偏移计算): 

旧API(gpio_get)需要offset偏移值来得到gpio_desc结构体,比如<&gpio3 10 xxx>,该函数需要的是引脚编号,使用引脚编号来判断是属于哪一组gpio控制器。使用该编号减去gpio控制器中的base值,便可以知道是哪一个引脚。

四、设备树

设备树中的gpio控制器有两个属性是必须要的,一个是gpio-controller属性,表明其是一个GPIO控制器,一个是#gpio-cell属性,表明一个gpio引脚需要用几个整数来表示。

五、编写虚拟的GPIO控制器

5.1 设备树信息

​
virtual_led{compatible = "xupt,led_based_gpio";pinctrl-names = "default";pinctrl-0 = <&myled_pin>;led-gpios = <&gpio_virtual RK_PC1 GPIO_ACTIVE_LOW>;status   = "okay";};virtual_pincontroller {compatible = "xupt,virtual_pinctrl";myled_pin:myled_pin{functions = "gpio";groups     = "pin0";configs   = <0x11223344>;};i2cgrp:i2cgrp{functions = "i2c","i2c";groups = "pin0" , "pin1";configs = <0x11223344 0x55667788>;};};gpio_virtual: gpio_virtual{compatible = "xupt,virtual_gpio";gpio-controller;#gpio-cells = <2>;status = "okay";ngpios = <4>;};​

5.2 GPIO控制器代码 

主要部分还是设置gpio_chip内的成员函数,和属性。


#include <linux/module.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/mfd/syscon.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_address.h>
#include <linux/gpio/consumer.h>
#include <linux/gpio/driver.h>
#include <linux/slab.h>
#include <linux/regmap.h>static struct gpio_chip *g_gpio_chip;
static int g_gpio_val = 0;static const struct of_device_id virtual_gpio_dt_ids[] = {{ .compatible = "xupt,virtual_gpio", .data = (void *)NULL},{ /* sentinel */ }
};static int virtual_gpio_direction_input(struct gpio_chip *chip,unsigned offset)
{printk("set pins:%d direction as input\n" , offset);return 0;
}static int virtual_gpio_direction_output(struct gpio_chip *gc,unsigned int offset, int value)
{printk("set gpio:%d direction output as %s\n" , offset , value ? "high" : "low");return 0;
}static int virtual_gpio_get(struct gpio_chip *gc, unsigned int offset)
{int val;val = (g_gpio_val & (1 << offset)) ? 1 : 0;printk("get pins:%d ,value: %d\n" , offset , val);return 0;
}static void virtual_gpio_set(struct gpio_chip *gc, unsigned int offset, int value)
{printk("set pins:%d as %d\n" , offset , value);if(value){g_gpio_val |= (1 << offset);}else{g_gpio_val &= ~(1 << offset);}}static int virtual_gpio_probe(struct platform_device *pdev)
{int ret , val;printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);/*1. alloc gpio_chip */g_gpio_chip = devm_kzalloc(&pdev->dev, sizeof(*g_gpio_chip), GFP_KERNEL);/*2. set gpio_chip *//*2.1 set function */g_gpio_chip->direction_input   = virtual_gpio_direction_input;g_gpio_chip->direction_output  = virtual_gpio_direction_output;g_gpio_chip->get               = virtual_gpio_get;g_gpio_chip->set               = virtual_gpio_set;/*2.2 set base and ngpio*/g_gpio_chip->base = -1;ret = of_property_read_u32(pdev->dev.of_node, "ngpios", &val);g_gpio_chip->ngpio = val;g_gpio_chip->label = pdev->name;g_gpio_chip->parent = &pdev->dev;/*3. register gpio_chip */ret = gpiochip_add_data(g_gpio_chip, NULL);return 0;
}static int virtual_gpio_remove(struct platform_device *pdev)
{gpiochip_remove(g_gpio_chip);return 0;
}static struct platform_driver virtual_gpio_driver = {.probe		= virtual_gpio_probe,.remove		= virtual_gpio_remove,.driver		= {.name	= "xupt_virtual_gpio",.of_match_table = virtual_gpio_dt_ids, }
};/* 1. 入口函数 */
static int __init virtual_gpio_init(void)
{	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);/* 1.1 注册一个platform_driver */return platform_driver_register(&virtual_gpio_driver);
}/* 2. 出口函数 */
static void __exit virtual_gpio_exit(void)
{printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);/* 2.1 反注册platform_driver */platform_driver_unregister(&virtual_gpio_driver);
}module_init(virtual_gpio_init);
module_exit(virtual_gpio_exit);MODULE_LICENSE("GPL");

5.3 结果显示 

echo的用法:

echo "test" > test.txt    # 覆盖写
echo "add" >> test.txt    # 追加

# 导出 GPIO 20
echo 20 > /sys/class/gpio/export

# 设置为输出
echo out > /sys/class/gpio/gpio20/direction

# 设置高电平
echo 1 > /sys/class/gpio/gpio20/value

 

我不知道为什么,我一开始使用.of_match_table = of_match_ptr(virtual_gpio_of_match),用来匹配,加载驱动的时候一直进不去probe函数,但是我换成.of_match_table = virtual_gpio_dt_ids,就可以直接匹配上了(头都大了,原来是这个问题,找了一下午,大坑!)。

六、GPIO子系统与pinctrl子系统的交互(GPIO使用pinctrl)

6.1 设备树信息

使用上一天创建的虚拟pinctrl子系统进行引脚复用,设置功能,结果如下图所示:实现了对GPIO引脚的复用,设置输入输出,设置引脚电平。

 

在设备树中可以建立pinctrl《--》gpio的关系(在gpio控制器的设备树中添加这样一项属性即可)。

分析pinctrl和gpio子系统的联系可以从gpiod_get函数入手,进入到最后会使用pinctrl_get_device_gpio_range()函数找出range结构体,根据range结构体使用gpio_to_pin函数就可以把gpio系统中的引脚编号转换成pinctrl子系统中的引脚编号。 

总结:函数调用gpiod_get,会导致gpio_chip中的request被调用,他会把gpio number转化为pinctrl中的pins,然后request会导致pinctrl_desc中的pinmux_ops中的gpio_set_direction或者request函数被调用。

6.2 编程思路

在GPIO子系统中提供gpio_chip->request函数,在pinctrl子系统中提供 pinctrl_desc->pinmux_ops中的gpio_set_direction或者request函数。

6.3 设备树文件

virtual_led{compatible = "xupt,led_based_gpio";led-gpios = <&gpio_virtual RK_PC1 GPIO_ACTIVE_LOW>;status   = "okay";};pinctrl_virtual: virtual_pincontroller {compatible = "xupt,virtual_pinctrl";i2cgrp:i2cgrp{functions = "i2c","i2c";groups = "pin0" , "pin1";configs = <0x11223344 0x55667788>;};};gpio_virtual: gpio_virtual{compatible = "xupt,virtual_gpio";gpio-controller;#gpio-cells = <2>;status = "okay";ngpios = <4>;gpio-rangs = <&pinctrl_virtual 0 0 4>;};

6.4 实验结果

 解释:从led_drv驱动中的gpiod_get函数进入-->gpiod_get_index(获得gpio_desc结构体从of_find_gpio函数)-->gpiod_request-->gpiod_request_commit(得到gpio_chip)-->chip->request函数-->gpiochip_generic_request-->pinctrl_gpio_request(获得range, pinctrl_get_device_gpio_range)-->gpio_to_pin(使用range建立gpio 引脚编号到 pinctrl子系统的pin编号)。

七、GPIO子系统的sysfs接口(可以简单的调试GPIO)

进入/sys/bus/gpio/device可以查看有多少组gpio控制器。

进入/sys/class/gpio可以看到每一组gpio控制器的最小引脚编号。

 

常用命令 

 

/sys/class/gpio/export : 将GPIO控制器从内核空间导出到用户空间,用户可以通过对导出的文件进行读写操作来操作gpio。

/sys/class/gpio/unexport:取消导出。

http://www.lryc.cn/news/584757.html

相关文章:

  • 查看uniapp 项目中没有用到依赖
  • 【IO复用】五种IO模型
  • 1. COLA-DDD的实战
  • 抽象类与接口:Java面向对象设计的两大支柱
  • 中文分词:分词工具及使用实践总结
  • 26-计组-外存
  • Docker-构建镜像并实现LNMP架构
  • 差分和前缀和
  • day01 - 数组part01
  • 如何安装python以及jupyter notebook
  • 黄瓜苦多于意外,苦瓜苦来自本源——“瓜苦”探源
  • BERT模型基本原理及实现示例
  • 强化学习 MDP
  • 从代码生成到智能运维的革命性变革
  • 集成平台业务编排设计器
  • 在虚拟机中安装Linux系统
  • 下一代防火墙-终端安全防护
  • 【数据结构】顺序表(sequential list)
  • Python3邮件发送全指南:文本、HTML与附件
  • 力扣61.旋转链表
  • 【会员专享数据】2013-2024年我国省市县三级逐日SO₂数值数据(Shp/Excel格式)
  • 【Linux基础命令使用】VIM编辑器的使用
  • WinUI3入门17:本地文件存储LocalApplicationData在哪里
  • 企业数据开发治理平台选型:13款系统优劣对比
  • Building Bridges(搭建桥梁)
  • HVV注意事项(个人总结 非技术)
  • 在VMware中安装虚拟机
  • 数据结构 --- 队列
  • XCZU47DR-2FFVG1517I Xilinx FPGA AMD ZynqUltraScale+ RFSoC
  • 超声波刻刀适用于一些对切割精度要求高、材料厚度较薄或质地较软的场景,典型应用场景如下: