gpio-export配置默认gpio
gpio-export配置默认gpio
用于设置gpio的默认状态和导出用户空间借口。只需要在设备树中配置节点即可。
参考链接:
https://devicetree.vger.kernel.narkive.com/hUDm3uhy/patch-gpio-add-export-with-name-from-dts
1. 驱动源码分析
导入头文件:
#include <linux/device.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/module.h>
#include <linux/io.h>
#include <linux/gpio/consumer.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/pinctrl/pinctrl.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/platform_device.h>
定义驱动的数据结构:
struct gpio_export_gpio{
char *name; // 节点名称
struct gpio_desc *desc; // gpio描述
};
struct gpio_export{
struct device *dev; // 用于绑定相关的设备
int gpios_num; // 记录一共需要导出的节点数量,在卸载驱动时用于取消导出
struct gpio_export_gpio *gpios; // 导出的每一个gpio的信息
};
定义驱动的 of_match_table
:
static struct of_device_id gpio_export_ids[] = {
{ .compatible = "linux-gpio-export" },
{ /* sentinel */}
};
当找到设备树节点中的 compatible
属性为 linux-gpio-export
时,执行 probe
函数。
定义 platform_driver
的一些信息和回调:
static struct platform_driver gpio_export_driver = {
.driver = {
.name = "linux-gpio-export",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(gpio_export_ids),
},
.probe = gpio_export_probe,
.remove = gpio_export_remove,
};
优先匹配 of_match_table
中的 compatible
属性,匹配不上时,再使用 name
进行匹配。然后注册 probe
和 remove
回调。
实现 probe
函数:
static int gpio_export_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node; // 获取整个平台设备节点
struct device_node *cnp;
int nb = 0;
int val;
struct gpio_export *ge;
struct device *dev = &pdev->dev; // 获取设备
ge = devm_kzalloc(dev, sizeof(struct gpio_export), GFP_KERNEL); // 为驱动结构分配内存空间
if (IS_ERR(ge))
return PTR_ERR(ge);
ge->dev = dev; // 绑定设备
dev_set_drvdata(dev, ge); // 赞存驱动结构到设置中
for_each_child_of_node(np, cnp) { // 获取总共需要导出的gpio节点数量
++nb;
}
ge->gpios_num = nb;
nb = 0;
ge->gpios = devm_kzalloc(dev,
sizeof(struct gpio_export_gpio) * ge->gpios_num,
GFP_KERNEL); // 为每一个gpio都分配存放信息结构的空间
if (IS_ERR(ge->gpios))
return PTR_ERR(ge->gpios);
for_each_child_of_node(np, cnp) { // 读取每一个节点的内部子节点,然后对每一个子节点依次进行导出操作
const char *name = NULL;
int gpio;
bool dmc;
of_property_read_string(cnp, "gpio-export,name", &name);
if (!name) {
name = of_node_full_name(np);
}
ge->gpios[nb].name = devm_kzalloc(dev, strlen(name) + 1, GFP_KERNEL);
strncpy(ge->gpios[nb].name, name, strlen(name));
gpio = of_get_gpio(cnp, 0);
ge->gpios[nb].desc = gpio_to_desc(gpio);
// 执行 gpiod_export 前,需要先进行 gpio_request 操作
if (devm_gpio_request(&pdev->dev, gpio, ge->gpios[nb].name) {
++nb;
continue;
}
// 默认电平
if (!of_property_read_u32(cnp, "gpio-export,output", &val))
gpio_direction_output(gpio, val);
else
gpio_direction_input(gpio);
// 是否允许用户配置输入/输出方向
dmc = of_property_read_bool(cnp, "gpio-export,direction-may-change");
gpiod_export(ge->gpios[nb].desc, dmc); // 导出gpio到 /sys/class/gpio/ 目录下
gpiod_export_link(&pdev->dev, ge->gpios[nb].name, ge->gpios[nb].desc); // 创建符号链接到 /sys/devices/gpio-export/ 下
++nb;
}
dev_info(&pdev->dev, "%d gpio(s) exported\n", nb);
return 0;
}
注意,这里都是使用 devm 类的借口,使用这类接口,会把分配的内存的操作和 dev 对象绑定,在设备创建失败,或者移除卸载驱动时,自动释放分配的内存。但是,devm 接口尽量只在 probe 回调中使用,不能在 open
回调中使用。使用devm 分配内存后,在确认不需要使用内存的情况下,也可以通过 devm_kfree
借口对分配的内存进行主动释放操作。
实现 remove
函数:
static int gpio_export_remove(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct gpio_export *ge = dev_get_drvdata(dev); // 从设备结构中获取之前暂存的驱动数据
int i;
for (i = 0; i < ge->gpios_num; i++) {
sysfs_remove_link(&ge->dev->kobj, ge->gpios[i].name);
gpiod_unexport(ge->gpios[i].desc); // 对已经的导出的所有gpio进行取消导出的操作
}
return 0;
}
由于使用devm类接口,因此在 remove 回调中,不需要去进行资源释放。在这里,只需要对已经导出的gpio,进行取消导出操作即可。
注册平台设备:
将驱动注册到平台设备上。
static int __init gpio_export_init(void)
{
return platform_driver_register(&gpio_export_driver);
}
static void __exit gpio_export_exit(void)
{
platform_driver_unregister(&gpio_export_driver);
}
module_init(gpio_export_init);
module_exit(gpio_export_exit);
MODULE_LICENSE("GPL");
加载驱动时,会将驱动注册到平台设备上。下载驱动时,会取消注册。
也可以使用下面的宏完成这个操作:
module_platform_driver(gpio_exporter_driver);
MODULE_LICENSE("GPL");
至此,一个完整的驱动的代码就完成了。
2. 设备树
设备树需要添加导出节点到根节点:
/ {
model = "Qualcomm Technologies, Inc. MDM 9607";
compatible = "qcom,mdm9607";
..................
soc: soc { };
gpio-export {
compatible = "linux-gpio-export";
#size-cells = <0>;
4G_LDO_1V8 {
gpio-export,name = "4G_LDO_1V8";
gpio-export,output = <1>;
// gpio-export,direction-may-change;
gpios = <&tlmm_pinmux 16 0>;
};
};
};
3. 编译
需要将编写的驱动加入到内核构建的系统中,才能在编译的时候将驱动编译进内核或者编译成内核模块。
添加Kconfig
在当前文件路径下,找到Kconfig文件,打开编辑,将下面的内容添加到if GPIOLIB
宏定义之间:
config GPIO_EXPORT
tristate "Export GPIO to userspace by DT"
depends on OF_GPIO && GPIO_SYSFS
help
Export GPIO to /sys/class/gpio/ direction. And set the
default state by DT.
gpio-export 依赖于 OF_GPIO
和 GPIO_SYSFS
,以及 GPIOLIB
。
tristate
:三态,支持 n
,m
,y
。
添加Makefile
在当前文件路径下,找到Makefile文件,打开编辑,将下面的内容添加到文件末尾:
obj-$(CONFIG_GPIO_EXPORT) += gpio-export.o
选择编译 gpio-export
驱动
Kconfig 和 Makefile 已经对 gpio-export 驱动进行支持了,最后还需要配置编译 gpio-export 驱动,驱动才能真正被编译到内核或者编译成内核模块。
找到kernel构建使用的默认 defconfig 配置文件,将 CONFIG_GPIO_EXPORT=m
添加到文件中。
我这里是 arch/arm/configs/mdm9607_defconfig
,因此可以将其添加到这里面。添加后需要clean,重新进行编译。为了避免clean重编,再将其添加到 .config
中,我这里的 .config
文件在 build/.config
中,也将 CONFIG_GPIO_EXPORT=m
添加到文件末尾,直接make即可编译 gpio-export 驱动。
4. 加载驱动
modprobe gpio-export
lsmod
加载驱动后,驱动就能通过设备树,导出我们设置GPIO了:
[ 26.689436] of_get_named_gpiod_flags: parsed 'gpios' property of node '/gpio-export/4G_LDO_1V8[0]' - status (0)
[ 26.689646] linux-gpio-export gpio-export: 1 gpio(s) exported
然后会在 /sys/class/gpio/
下生成节点。
/sys/class/gpio # cd gpio16/
/sys/devices/1000000.pinctrl/gpio/gpio16 # ls
active_low device power subsystem uevent value