1.platform_device:修改设备树,添加设备(device)节点,设备树节点部分会被转换为 platform_device。
2.platform_driver:
首先定义入口,出口函数;构建platform_driver结构体;在入口函数注册platform_driver(采用platform_driver_register()函数),卸载驱动程序时,就会去调用这个出口函数;
构建file_operations结构体为APP层提供open,read等函数接口。
在platform_driver.probe里面注册file_operations。
3.platform_device与platform_driver如何匹配(一般方法):
使用设备树信息来判断 dev 和 drv 是否配对:
如果 of_match_table 中含有 compatible 值,就跟 dev 的 compatile 属性比较,若一致则成功,否则返回失败;
eg:
//设备树
#define GROUP_PIN(g,p) ((g<<16) | (p))
/ {
100ask_led@0 {
compatible = "sym,leddrv";
pin = <GROUP_PIN(3, 1)>;
};
100ask_led@1 {
compatible = "sym,leddrv";
pin = <GROUP_PIN(5, 8)>;
};
};
//驱动
static struct platform_driver chip_demo_gpio_driver = {
.probe = chip_demo_gpio_probe,
.remove = chip_demo_gpio_remove,
.driver = {
.name = "100ask_led", /*这个对应的就是dts文件里面的名字*/
.of_match_table = sym_leds,
},
};
static const struct of_device_id sym_leds[] = {
{ .compatible = "sym,leddrv" },
{ },
};
static int chip_demo_gpio_probe(struct platform_device *pdev)
{
//int err;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
/* 4.1 设备树中定义有: led-gpios=<...>; led就是那个name*/
led_gpio = gpiod_get(&pdev->dev, "led", 0);
if (IS_ERR(led_gpio)) {
dev_err(&pdev->dev, "Failed to get GPIO for led\n");
return PTR_ERR(led_gpio);
}
/* 4.2 注册file_operations */
major = register_chrdev(0, "100ask_led", &led_drv); /* /dev/led */
led_class = class_create(THIS_MODULE, "100ask_led_class");
if (IS_ERR(led_class)) {
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
unregister_chrdev(major, "led");
gpiod_put(led_gpio);
return PTR_ERR(led_class);
}
// MKDEV 是一个宏,用于将主设备号和次设备号组合成一个 dev_t 类型的设备号
device_create(led_class, NULL, MKDEV(major, 0), NULL, "100ask_led%d", 0); /* /dev/100ask_led0 */
return 0;
}
PS:
1. 没有转换为 platform_device 的节点,如何使用呢设备树里面定义的信息呢?
任意驱动程序里,都可以采用操作设备树的常用函数直接访问设备树,从而获得我们想要的节点数据。
2.platform总线的匹配规则是什么?在具体应用上要不要先注册驱动再注册设备?有先后顺序没?
总线,设备,驱动。匹配规则就是当有一个新的设备挂起时,总线被唤醒,match函数被调用,用device名字去跟本总线下的所有驱动名字去比较。相反就是用驱动的名字去device链表中和所有device的名字比较。如果匹配上,才会调用驱动中的probe函数,否则不调用。至于先后顺序,鉴于个人理解,不会有影响,不管谁先谁后,bus都会完成匹配工作。
3. 设备线驱动模型的出现主要有三个好处,设备与驱动分离,驱动可移植性增强;设备驱动抽象结构以总线结构表示看起来更加清晰明了,谁是属于哪一条bus的;最后,就是大家最熟悉的热插拔了,设备与驱动分离,很好的奠定了热插拔机制。
4. 注意: 所谓的platform_device并不是与字符设备、块设备和网络设备并列的概念,而是Linux系统提供的一种附加手段,例如,把内部集成的I2C、RTC、SPI、LCD、看门狗等控制器都归纳为platform_device,而它们本身就是字符设备。我们要记住,platform驱动只是在字符设备驱动外套一层platform_driver的外壳。引入platform模型符合Linux设备模型――总线、设备、驱动,设备模型中配套的sysfs节点都可以用,方便我们的开发;当然你也可以选择不用,不过就失去了一些platform带来的便利。
5.设备号
register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)
具体可以看register_chrdev()内部实现。
第一个参数是主设备号,0代表动态分配,当然也可以由我们指定,第二个参数name任取。
dev是设备号,包含有主设备号和次设备号的信息。主设备号用于区分设备的类型,次设备号用于标记相同类型的设备的不同个体。如串口1和串口2使用同一驱动程序,则其主设备号相同,但次设备号不同。Linux内核中使用dev_t类型来定义设备号,dev_t这种类型其实质为32位的unsigned int,其中高12位为主设备号,低20位为次设备号。主设备号相同的设备都使用同一个 file_operations 来操作。
1.知道主设备号与次设备号,可通过dev_t dev = MKDEv(主设备号,次设备号)获得设号; 2.从设备号分解出主设备号:主设备号=MAJOR(dev_t dev)
3.从设备号分解出次设备号:次设备号=MINOR(dev_t dev)