赞
踩
利用设备树
来使用或者编写驱动程序,需要梳理哪些概念?
首先要理解驱动程序模型是分离的,分层的。
所谓分离是指:硬件的资源信息(地址、中断、DMA等),与软件驱动代码是分离的。硬件的资源信息是通过设备树来描述的,设备树由内核在启动时解析并注册为platform_device。
所谓分层是指:分上下两层,device和driver都注册、挂载在bus(总线)下面。
设备device与设备树device tree相关。满足一定条件的compatible时(这些特殊的compatilbe属性为: “simple-bus”,“simple-mfd”,“isa”,"arm,amba-bus "),设备树被转换并注册成platform_device。
驱动driver代码要重点理解probe函数。因为内核启动时就已经注册device,所以手动insmod是注册driver。也就是说,在利用设备树时,是先注册device,后注册driver。当driver与device的compatible相匹配时,会调用probe。
device与driver相匹配,是通过在drivers/base/dd.c中driver_attach函数来完成。一旦match成功,则调用driver里的probe函数。
对于传统写法,match函数是直接比较name
。对于使用设备树的情况下,match函数通过 platform_driver -> driver -> of_match_table -> compatile
来与设备节点做匹配。
无设备树: 使用 xxx_driver.driver.name 进行设备匹配
有设备树: 使用 xxx_driver.driver.of_match_table 进行设备匹配
另外,bus的注册具体参考下文:
驱动程序分层分离概念-总线设备驱动模型
linux设备驱动——总线、设备、驱动_设备和总线驱动
以led驱动为例。
注意:
.name与driver驱动的名字一样。static struct platform_device led_dev = {
.name = "cbt_led", //对应的platform_driver驱动的名字
.id = -1, //表示只有一个设备
.num_resources = ARRAY_SIZE(led_resource), //资源数量,ARRAY_SIZE()函数:获取数量
.resource = led_resource, //资源数组led_resource
.dev = {
.release = led_release, //释放函数,必须向内核提供一个release函数, 、
//否则卸载时,内核找不到该函数会报错
},
};
struct platform_driver led_drv = {
.probe = led_probe,
.remove = led_remove,
.driver = {
.name = "cbt_led",
.of_match_table = of_match_leds,
}
};
compatile
来与设备节点做匹配。static const struct of_device_id leds_of_match[] = { { .compatible = "cbt4412_led", .data = NULL }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of,leds_of_match); struct platform_driver led_drv = { .owner = THIS_MODULE, .open = led_open, .unlocked_ioctl = led_unlocked_ioctl, .release = led_release, .driver = { .name = "cbt_led", .of_match_table = leds_of_match, /* 能支持哪些来自于dts的platform_device */ } }; module_platform_driver(cbt4412_led_drv); static int led_probe(struct platform_device *pdev) { struct resource *res; /*** * platform_get_resource解析DTS * 根据platform_device的资源进行ioremap * 参数 0代表IORESOURCE_MEM这类资源中的第0个, * 把他取出来后res->start,代表的就是引脚了 * ***/ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); led_pin = res->start; major = register_chrdev(0, "cbt_led", &cbt_led_ops); led_class = class_create(THIS_MODULE, "cbt_led"); device_create(led_class, NULL, MKDEV(major, 0), NULL, "cbt_led"); /* /dev/cbt_led */ return 0; }
参考资料:
字符设备驱动-总线设备驱动模型写法
设备树之字符设备驱动_LED
module_platform_driver是宏,展开之后是驱动模块的初始化和退出函数。
module_platform_driver(gpio_led_driver);
展开之后,是
static int __init gpio_led_driver_init(void)
{
return platform_driver_register (&(gpio_led_driver));
}
module_init(gpio_led_driver_init);
static void __exit gpio_led_driver_exit(void)
{
platform_driver_unregister (&(gpio_led_driver) );
}
module_exit(gpio_led_driver_exit);
参考文献
Linux 有/无设备树下 platform_driver 驱动框架
dts中的节点信息终将经过dts -> dtb -> device_node -> platform_device这样的过程。但并不是所有device_node都会被转换成platform_device,需满足:
假设节点信息定义如下:
test_nod@106E0020 {
compatible = "cbt,led";
reg = <0x106E0020 0x4>;
testprop,mytest;
test_list_string = "red led", "blue led";
interrupt-parent = <&gpv2>;
interrupts = <1 4>;
};
1、从节点路径获取信息
struct device_node *np = NULL;
np = of_find_node_by_path("/test_nod@12345678");
printk("node name = %s\n", np->name);
2、获取到节点中的属性
struct property *prop = NULL;
prop = of_find_property(np, "compatible",NULL);
printk("compatible value = %s\n", prop->value);
3、读取到属性中的整数的数组
u32 regdata[U32_DATA_LEN];
int ret,i=0;
ret = of_property_read_u32_array(np, "reg", regdata, U32_DATA_LEN);
printk("regdata[%d] = 0x%x\n", i,regdata[i]);
4、读取到属性中的字符串的数组
const char *pstr[3];
int i=0;
ret = of_property_read_string_index(np, "test_list_string", i, &pstr[i]);
printk("pstr[%d] = %s\n", i,pstr[i]);
5、属性的值为空,实际可以用于设置标志
if(of_find_property(np, "testprop,mytest", NULL))
{
is_good = 1;
printk("is_good = %d\n", is_good);
}
6、获取到中断的号码
irqno = irq_of_parse_and_map(np, 0);
printk("-----irqno = %d\n", irqno);
//验证中断号码是否有效
ret = request_irq(irqno, key_irq_handler, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
"key_irq", NULL);
if(ret)
{
printk("request_irq error\n");
return -EBUSY;
}
参考文献:
设备树 - 应用实例
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。