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:取消导出。