基于设备树的驱动编写
和平台总线实现的驱动不同的地方在于,设备树将平台设备,也就是 platform_device 进行了完全的抽象,对于不同的设备并不需要去写一个个平台设备。而只是需要修改设备树就行了。设备树是一个用节点描述系统中设备的树状结构。
一,设备树相关定义
1,设备树文件定义
• DTS :dts 文件是对 Device Tree 的描述,放置在内核的/arch/arm64/boot/dts 目录,描述了一个板子的硬件
资源。以前写在 mach-xxx 文件中的内容被转成了 dts 文件。
• DTC :编译工具,存放在目录 scripts/dtc 位置,它可以将.dts 文件编译成.dtb 文件。
• DTB :DTC 编译*.dts 生成的二进制文件(.dtb),bootloader 在引导内核时,会预先读取.dtb 到内存,进而由内核解析。
• DTSI :由于同一系列 SOC 很多相同的地方,为了减少代码的冗余,设备树将这些共同部分提炼保存在.dtsi 文件中,供不同的 dts 共同使用。
2,设备树文件类型
一根节点多个子节点,子节点又可以包含多个子节点,以树状的结构散开。 在编写驱动程序时,我们只需关心设备树和平台驱动两样东西,使用平台驱动直接调用设备树的内容。
3, dts 节点node和文件结构
设备树节点是用节点名和单元地址和一个用大括号作为节点定义的开始和结尾来定义的。
• [lable:]:设备树文件允许标签附加在任何节点或者属性上。
• node-name:是指节点的名字。
• [@unit-address]:是指节点所在的基地址。
• [properties definitions]:是指相关属性的定义。
• [child nodes]:是指相关的子节点。
如果父节点中有子节点相同的属性,那么以设备树的父节点的属性为主。所有的 dts 文件都需要有一个 root 节点,并且 root 节点内必须有一个 cpus 节点和至少一个的 memory 节点。
4,aliases节点
设备树文件有一个 root 节点,所有的其他设备节点都是它的子节点。下面展示的是它的一些必须属性和可选属性。 设备树可以有一个别名节点,也就是 aliases 节点。用来定义一个或者更多的属性。别名节点只能存在于设备树的root节点中,并且节点名字为 aliaes。客户端程序会通过 alias 的属性名来引用设备的全路径,或者部分路径。
5,chosen节点和memory节点
/chosen 节点并不代表系统中的一个真实的设备,但是,描述了当系统固件在运行的时候会被选择或者指定的参数。这个节点一定得在 root 节点下。所有的设备树文件都需要内存设备节点,用来描述系统物理内存的布局。。。。如果一个系统有多个范围的内存,多个内存节点将会被创建。或者可以在一个单独的内存节点的 reg 属性中指定多个范围的内存。chosen节点和memory节点可选和必须属性如下:
6,cpus节点
设备树所必须的 cpus 节点,它不代表系统中一个真实的设备。它作为一个系统 cpus 的子 cpu 节点的容器存在。一个 cpu 节点就代表一个硬件的可执行块。能够运行操作系统而不会受到运行其他操作系统 cpu 的干扰。
二,设备树节点分析:在设备树中添加 gpio-led节点
gpio-led
{
compatible = "xlnx,zynqmp-led-1.0";
gpios = <&gpio0 0x33 0x0>;
};
具体分析:
• gpio-led:设备节点名,也就是 node-name。
• compatible:兼容名,驱动将会通过这个在设备树中找到该节点。
• gpio:定义的 led 的管脚,MIO51。
三,驱动程序分析
1,int platform_driver_register(struct platform_driver *driver) //向平台总线注册平台驱动。
• pdrv:注册的平台驱动。
• 返回值:0,设置成功;负值,设置失败。
2,of_get_gpio
static inline int of_get_gpio(struct device_node *np, int index) //从节点出通过index获取GPIO号
{
return of_get_gpio_flags(np, index, NULL); //np:从设备树处获得的节点
}
3,platform_driver结构体:构建平台注册的结构体
当驱动在设备中找到 name 之后,进行配对获取 resource 资源,进入 probe 函数
struct platform_driver gpio_drv =
{
.driver =
{
.name = "zynqmp_led",
.of_match_table = gpio_of_match_table, //用来在设备树中找到对应 node 的函数。
},
.probe = gpio_pdrv_probe, //gpio 初始化函数。
.remove = gpio_pdrv_remove, //当移除 gpio 时会执行的函数。
};
4,int gpio_request(unsigned gpio, const char *label)
含义:gpio_request 函数用来向系统申请一个 GPIO 管脚,每一个管脚在使用前都需要使用这个函数初始化,若不使用这个函数直接使用 GPIO,则 GPIO 不会反应。
• 参数:gpio,需要申请的 gpio 标号;lable,gpio 的名字,可自由设定。
• 返回值:0,申请成功;其他值,申请失败。
5,static inline int gpio_direction_output(unsigned gpio, int value) //gpio号和0 为灭灯/1 为开灯
//设置 gpio 为输出模式,并设置 0 或 1,来控制输出的开关。
6,static inline void gpio_set_value(unsigned gpio, int value)//设置 gpio 的引脚值。
7,makefile文件
# 已经编译过的内核源码路径
KERNEL_DIR = /home/.............xlnx/sources/kernel
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-
# 当前路径
CURRENT_DIR = $(shell pwd)
MODULE = platform_gpio_drv
all :
# 进入并调用内核源码目录中 Makefile 的规则 , 将当前的目录中的源码编译成模块
make -C $(KERNEL_DIR) M=$(CURRENT_DIR) modules
rm -rf *.mod.c *.mod.o *.symvers *.order *.o
ifneq ($(APP), )
$(CROSS_COMPILE)gcc $(APP).c -o $(APP)
endif
clean :
make -C $(KERNEL_DIR) M=$(CURRENT_DIR) clean
# 指定编译哪个文件
obj-m += $(MODULE).o
8,驱动代码
//1、添加头文件
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/mod_devicetable.h>
static unsigned int led_major;
static struct class *led_class;
struct led_driver
{
int gpio1;
// int gpio2;
int irq;
struct device dev;
};
struct led_driver *led_dri = NULL;
const struct file_operations led_fops = {
};
//在probe函数中打印获取数据包里面的名字及GPIO
int gpio_pdrv_probe(struct platform_device *pdev)
{
struct device_node *node;
// unsigned int gpio1, gpio2;
unsigned int gpio1;
unsigned int ret = 0;
printk("gpio pdrv probe!\n");
printk("pdrv name = %s!\n", pdev->name);
//申请主设备号
led_major = register_chrdev(0, "led_drv", &led_fops);
if (led_major < 0)
{
printk("register chrdev led major error!\n");
return -ENOMEM;
}
//创建类
led_class = class_create(THIS_MODULE, "led_class");
//创建设备
device_create(led_class, NULL, MKDEV(led_major, 0), NULL, "led_device%d", 0);
//硬件初始化
//申请空间
led_dri = devm_kmalloc(&pdev->dev, sizeof(struct led_driver), GFP_KERNEL);
if (led_dri == NULL)
{
printk("devm kmalloc led_driver error!\n");
return -1;
}
//获取从设备节点传过来的pdev中的dev及node节点
led_dri->dev = pdev->dev;
node = pdev->dev.of_node;
//从node节点处获得GPIO号
gpio1 = of_get_gpio(node, 0);
printk("of get gpio1 number = %d\n", gpio1);
if (gpio1 < 0)
{
printk("of get gpio error!\n");
return -1;
}
//申请GPIO
ret = gpio_request(gpio1, "plattree_led");
if (ret < 0)
{
printk("plattree led gpio request error!\n");
return ret;
}
//设置GPIO为输出模式,并设备为0,灭灯
gpio_direction_output(gpio1, 0);
// gpio_direction_output(gpio2, 0);
led_dri->gpio1 = gpio1;
// led_dri->gpio2 = gpio2;
return 0;
}
int gpio_pdrv_remove(struct platform_device *pdev)
{
printk("led pdrv remove!\n");
gpio_set_value(led_dri->gpio1, 1);
// gpio_set_value(led_dri->gpio2, 1);
gpio_free(led_dri->gpio1);
// gpio_free(led_dri->gpio2);
device_destroy(led_class, MKDEV(led_major, 0));
class_destroy(led_class);
unregister_chrdev(led_major, "led_drv");
return 0;
}
//of_match_table实现
const struct of_device_id gpio_of_match_table[] = {
{
.compatible = "xlnx,zynqmp-led-1.0",
},
{}};
//当驱动在设备中找到name之后,进行配对获取resource资源,进入probe函数
struct platform_driver gpio_drv = {
.driver = {
.name = "zynqmp_led",
.of_match_table = gpio_of_match_table,
},
.probe = gpio_pdrv_probe,
.remove = gpio_pdrv_remove,
};
//实现装载入口函数和卸载入口函数
static __init int platform_gpio_drv_init(void)
{
//创建pdrv,并且注册到总线中
return platform_driver_register(&gpio_drv);
}
static __exit void platform_gpio_drv_exit(void)
{
//注销设备
platform_driver_unregister(&gpio_drv);
}
//声明装载入口函数和卸载入口函数
module_init(platform_gpio_drv_init);
module_exit(platform_gpio_drv_exit);
//添加GPL协议
MODULE_LICENSE("GPL");
MODULE_AUTHOR("subomb");
四,驱动验证
先加载insmod platform_gpio_drv.ko,然后输入dmesg