1.配置dts文件
添加引脚配置到开发板所使用的dts文件中
//我的路径为/home/xt/Desktop/proj/rk3308_linux_release_v1.5.0a_20221212/kernel/arch/arm64/boot/dts/rockchip/rk3308b-roc-cc-plus-amic_emmc.dts
gpio_demo: gpio_demo {
status = "okay";
compatible = "firefly,rk3308-gpio";
firefly-gpio = <&gpio4 RK_PA6 GPIO_ACTIVE_HIGH>; /* GPIO4_A6 */
};
status让gpio引脚处于可使用状态。而compatible实现与设备节点的直接配对。接下来写的驱动程序就会通过compatible的内容来找所要使用的gpio引脚。最后的一行则是写了名称和对应的引脚编号及其状态。
2.编写调用该gpio的驱动程序
首先我们要进入该目录下/home/xt/Desktop/proj/rk3308_linux_release_v1.5.0a_20221212/kernel/drivers/gpio。然后创建一个c文件放这个驱动程序,名字叫什么都可以。我的就按照官方教程叫做gpio-firefly.c
2.1 补充知识——Linux内核模块程序结构
Linux内核的整体架构本就非常庞大,其包含的组件也非常多。而我们怎样把需要的部分都包含在内核中呢?一种方法是把所有需要的功能都编译到Linux内核中。这会导致两个问题,一是生成的内核会很大,二是如果我们要在现有的内核中新增或删除功能,将不得不重新编译内核。
Linux提供了这样的机制,这种机制被称为模块(Module)。可使得编译出的内核本身并不需要包含所有功能,而在这些功能需要被使用的时候,其对应的代码被动态地加载到内核中。
模块本身不被编译入内核映像,从而控制了内核的大小。模块一旦被加载,它就和内核中的其他部分完全一样。
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/module.h>
MODULE_LICENSE("GPL");
/*模块加载函数:当使用insmod命令加载模块时,该函数会自动被执行,完成加载工作*/
static int __init example_init(void)
{
printk(KERN_EMERG "example, init\n");
return 0;
}
/*模块卸载函数:当使用rmmod命令卸载模块时……*/
static void __exit hello_exit(void)
{
printk(KERN_EMERG "example, exit\n");
}
/*模块家在函数以module_init()的方式被指定*/
module_init(example_init);
module_exit(example_exit);
2.2 补充知识——驱动注册
新的linux内核开始使用设备树方式,注册的不是平台设备而是平台的驱动,平台驱动在注册时会自动匹配设备节点,匹配成功后会调用probe函数。这一行为,简化了设备驱动的开发,让内核驱动模型管理设备驱动。
简单理解就是,有了一个platform_driver的结构体,可以利用里面的所填写的信息(compatible)自动去dst找对应,完成注册
static int firefly_gpio_probe(struct platform_device *pdev){}
static struct of_device_id firefly_match_table[] = {
{ .compatible = "firefly,rk3308-gpio",},
{},
};
static struct platform_driver firefly_gpio_driver = {
.driver = {
.name = "firefly-gpio",/*再对应名字,查看是否正确*/
.owner = THIS_MODULE,
.of_match_table = firefly_match_table,/*首先匹配它,即根据上面结构体的compatible的内容去dts找对应节点*/
},
.probe = firefly_gpio_probe,/*对应成功则调用probe函数*/
};
static int firefly_gpio_init(void)
{
return platform_driver_register(&firefly_gpio_driver);
/*使用 platform_driver_register(&dev_driver)注册了设备驱动*/
}
module_init(firefly_gpio_init);
static void firefly_gpio_exit(void)
{
platform_driver_unregister(&firefly_gpio_driver);
/*退出时要删掉*/
}
module_exit(firefly_gpio_exit);
2.3 probe函数
/*定义了一个包含gpio信息的结构体*/
struct firefly_gpio_info
{
int firefly_gpio;
int gpio_enable_value;
};
static int firefly_gpio_probe(struct platform_device *pdev)
{
int gpio;
enum of_gpio_flags flag;
struct firefly_gpio_info *gpio_info;
struct device_node *firefly_gpio_node = pdev->dev.of_node;
printk("Firefly GPIO Test Program Probe\n");
/*简单类比一下用户空间里c语言中给指针分配空间的malloc,一个道理*/
gpio_info = devm_kzalloc(&pdev->dev, sizeof(struct firefly_gpio_info *),GFP_KERNEL);
if (!gpio_info)
{
dev_err(&pdev->dev, "devm_kzalloc failed!\n");
return -ENOMEM;
}
/*通过device节点以及name申请gpio资源*/
gpio = of_get_named_gpio_flags(firefly_gpio_node, "firefly-gpio", 0, &flag);
/*测试gpio号是否有效*/
if (!gpio_is_valid(gpio))
{
dev_err(&pdev->dev, "firefly-gpio: %d is invalid!\n", gpio);
return -ENODEV;
}
/*申请使用*/
if (gpio_request(gpio, "firefly-gpio"))
{
dev_err(&pdev->dev, "firefly_gpio: %d request failed!\n", gpio);
gpio_free(gpio);
return -ENODEV;
}
/*申请成功后,便利用gpio_direction_output给该gpio进行定义*/
gpio_info->firefly_gpio = gpio;
gpio_info->gpio_enable_value = (flag == OF_GPIO_ACTIVE_LOW) ? 0:1;
gpio_direction_output(gpio_info->firefly_gpio, gpio_info->gpio_enable_value);
printk("Firefly gpio putout\n");
return 0;
}
2.4 合起来的版本
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/err.h>
#include <linux/gpio.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
struct firefly_gpio_info
{
int firefly_gpio;
int gpio_enable_value;
};
static int firefly_gpio_probe(struct platform_device *pdev)
{
int gpio;
enum of_gpio_flags flag;
struct firefly_gpio_info *gpio_info;
struct device_node *firefly_gpio_node = pdev->dev.of_node;
printk("Firefly GPIO Test Program Probe\n");
gpio_info = devm_kzalloc(&pdev->dev, sizeof(struct firefly_gpio_info *),GFP_KERNEL);
if (!gpio_info)
{
dev_err(&pdev->dev, "devm_kzalloc failed!\n");
return -ENOMEM;
}
gpio = of_get_named_gpio_flags(firefly_gpio_node, "firefly-gpio", 0, &flag);
if (!gpio_is_valid(gpio))
{
dev_err(&pdev->dev, "firefly-gpio: %d is invalid!\n", gpio);
return -ENODEV;
}
if (gpio_request(gpio, "firefly-gpio"))
{
dev_err(&pdev->dev, "firefly_gpio: %d request failed!\n", gpio);
gpio_free(gpio);
return -ENODEV;
}
gpio_info->firefly_gpio = gpio;
gpio_info->gpio_enable_value = (flag == OF_GPIO_ACTIVE_LOW) ? 0:1;
gpio_direction_output(gpio_info->firefly_gpio, gpio_info->gpio_enable_value);
printk("Firefly gpio putout\n");
return 0;
}
static struct of_device_id firefly_match_table[] = {
{ .compatible = "firefly,rk3308-gpio",},
{},
};
static struct platform_driver firefly_gpio_driver = {
.driver = {
.name = "firefly-gpio",
.owner = THIS_MODULE,
.of_match_table = firefly_match_table,
},
.probe = firefly_gpio_probe,
};
static int firefly_gpio_init(void)
{
return platform_driver_register(&firefly_gpio_driver);
}
module_init(firefly_gpio_init);
static void firefly_gpio_exit(void)
{
platform_driver_unregister(&firefly_gpio_driver);
}
module_exit(firefly_gpio_exit);
MODULE_AUTHOR("linjc <service@t-firefly.com>");
MODULE_DESCRIPTION("Firefly GPIO driver");
MODULE_ALIAS("platform:firefly-gpio");
MODULE_LICENSE("GPL");
3.Kconfig与makefile文件
注意!!必须添加该内容,否则c文件不会被编译且加入内核!
内核编译需要.config文件,来告诉内核是否需要编译进内核。而Kconfig会帮助内核发现我们写的模块,Makefile则是告诉编译器来编译生成这个模块。
3.1 Kconfig
说明了模块的名称、用途、依赖的模块名、说明等。
config GPIO_FIREFLY
tristate "Firefly GPIO Demo support"
default y
help
Sample code of gpio
endif
3.2 makefile
obj-$(CONFIG_GPIO_FIREFLY) += gpio-firefly.o
4. 烧录进开发板,检查是否成功
具体烧录方法根据开发板来定,可在官方手册看到。
利用 cat /sys/kernel/debug/gpio命令可以看到引脚是否被启用以及其状态。