Linux 系统中 platform 平台框架包括总线、 设备和驱动, 其中总线不用我们去操心, Linux 内核中会自动管理, 我们只需要关系设备和驱动如何实现。 在不支持设备树的内核中, 我们需要分别实现platform_device 和 platform_driver, 其中 platform_device 是在平台文件中实现的。 在支持设备树的内核中,我们就不用实现 platform_device 了, 而是在设备树文件中添加设备信息。 下面看一下在设备树文件中添加设备信息。
在之前关于设备树语法的章节中, 我们学习了如何在根节点“/” 下去添加一个设备节点信息。 其中最重要的就是 compatible 属性值, compatible 属性使用来和驱动进行匹配的。 下面是本实验用到的设备的设备节点:
在编写驱动以前, 有一个地方需要注意一下, 我们在加载 driver.ko 之前, 一定要在开发板上已经成功地添加了 test 的节点, 你可以在 linux 系统里面查看到你添加的节点, 查看到 test 节点的 comtabile 属性的值为 test1234, 如下图所示:
Platform 驱动程序
driver.c 文件
#include //初始化头文件
#include //最基本的文件, 支持动态添加和卸载模块。
#include //platform 平台设备相关的头文件
/**
* @description: platform 驱动的 probe 函数, 当驱动与设备匹配以后此函数就会执行
* @param {*}pdev : platform 设备
* @return {*}0, 成功;其他负值,失败
*/
int beep_probe(struct platform_device *pdev)
{
//匹配成功以后, 进入到 probe 函数
printk("beep_probe\n");
return 0;
}
/**
* @description: platform 驱动的 remove 函数, 移除 platform 驱动的时候此函数会执行
* @param {structplatform_device} *pdev: platform 设备
* @return {*} 0, 成功;其他负值,失败
*/
int beep_remove(struct platform_device *pdev)
{
printk("beep_remove\n");
return 0;
}
const struct platform_device_id beep_idtable = {
.name = "beep_test",
};
//与设备树的 compatible 匹配
const struct of_device_id of_match_table_test[] = {
{
.compatible = “test1234”},
{},
};
struct platform_driver beep_driver = {
//3. 在 beep_driver 结构体中完成了 beep_probe 和 beep_remove
.probe = beep_probe,
.remove = beep_remove,
.driver = {
.owner = THIS_MODULE,
.name = "beep_test",
//接下来我们改一下驱动, 让他来匹配设备树里面 test 的节点, of_match_table 优先级高id_table
//设置 platform 驱动匹配表
.of_match_table = of_match_table_test},
//4 .id_table 的优先级要比 driver.name 的优先级要高, 优先与.id_table 进行匹配
.id_table = &beep_idtable
};
static int beep_driver_init(void)
{
//1.我们看驱动文件要从 init 函数开始看
int ret = 0;
//2.在 init 函数里面注册了 platform_driver
ret = platform_driver_register(&beep_driver);
if (ret < 0)
{
printk("platform_driver_register error \n");
}
//打印平台设备驱动注册成功
printk("platform_driver_register ok \n");
return 0;
}
static void beep_driver_exit(void)
{
platform_driver_unregister(&beep_driver);
printk("gooodbye! \n");
}
module_init(beep_driver_init);
module_exit(beep_driver_exit);
MODULE_LICENSE("GPL");
保存 driver.c 文件, 编译 driver.c 为驱动模块,加载刚刚编译好的 driver.ko, 如下图所示:
cd /mnt/nfs/imx6ull/25driver/
insmod driver.ko
如上图所示, 已经匹配成功进入到 probe 函数中。 如果没有进入 probe 函数, 可能出现匹配不成功的原因是 1 device 或者设备树根本没有加到我们系统里面 2 名字不一样导致匹配不成功。
获取资源
进入了 probe 函数, 可以在 probe 函数中获取资源, 如下所示:
//匹配成功以后, 进入到 probe 函数
int beep_probe(struct platform_device *pdev){
printk("beep_probe\n");
/*********************方法一: 直接获取节点**************************/
printk("node name is %s\n",pdev->dev.of_node->name);
return 0;
}
编译驱动, 然后加载驱动后, 如下图所示:
如上图所示, 加载驱动以后, 设备树上的节点和驱动程序匹配成功, 进入了 probe 函数, 并打印了节点的名字。
通过of 操作函数来获取我们的设备资源, 修改 driver.c 为如下所示:
int beep_probe(struct platform_device *pdev)
{ //匹配成功以后, 进入到 probe 函数
printk("beep_probe\n");
/*********************方法一: 直接获取节点**************************/
//printk("node name is %s\n",pdev->dev.of_node->name);
/*********************方法二: 通过函数获取硬件资源**************************/
//获得设备节点
test_device_node = of_find_node_by_path("/test");
if (test_device_node == NULL)
{
printk("of_find_node_by_path is error \n");
return -1;
}
//获取 reg 属性
ret = of_property_read_u32_array(pdev->dev.of_node, "reg", out_values, 2);
if (ret < 0)
{
printk("of_property_read_u32_array is error \n");
return -1;
}
printk("out_values[0] is 0x%08x\n", out_values[0]);
printk("out_values[1] is 0x%08x\n", out_values[1]);
return 0;
}
编译驱动, 然后加载驱动后, 如下图所示:
如上图所示, 我们已经成功的获得设备树里面的 reg 属性。
获取节点属性
修改 driver.c 如下所示:
int beep_probe(struct platform_device *pdev)
{ //匹配成功以后, 进入到 probe 函数
printk("beep_probe\n");
ret = of_property_read_u32_array(pdev->dev.of_node, "reg", out_values, 2);
if (ret < 0)
{
printk("of_property_read_u32_array is error \n");
return -1;
}
printk("out_values[0] is 0x%08x\n", out_values[0]);
printk("out_values[1] is 0x%08x\n", out_values[1]);
return 0;
}
如上图所示, 可以直接通过节点获取到 reg 属性的值。
映射物理地址
现在我们已经拿到了寄存器的地址, 接下来可以注册杂项设备或者字符设备, 我们先将获取到的物理地址映射为虚拟地址, 修改 driver.c 代码如下:
int beep_probe(struct platform_device *pdev)
{
printk("beep_probe\n");
ret = of_property_read_u32_array(pdev->dev.of_node, "reg", out_values, 2);
if (ret < 0)
{
printk("of_property_read_u32_array is error \n");
return -1;
}
printk("out_values[0] is 0x%08x\n", out_values[0]);
printk("out_values[1] is 0x%08x\n", out_values[1]);
/*********************映射物理地址**************************/
vir_gpio_dr = of_iomap(pdev->dev.of_node, 0);
if (vir_gpio_dr == NULL)
{
printk("of_iomap is error \n");
return -1;
}
return 0;
}
如上图所示, 物理地址已经映射为虚拟地址, 接下来可以注册字符设备和杂项设备, 流程和我们前面学习到的内容是一模一样的。