第十六讲 platform设备驱动实验
一、Linux驱动分离与分层
1、以前的驱动实验都很简单,就是对IO的操作。
目的:方便驱动的编写,提高软件的重用以及跨平台性能。
1.1、驱动的分隔与分离
主机驱动,设备驱动
1、单片机驱动开发,IIC设备MPU6050,
2、将驱动分离:主机控制器驱动和设备驱动,主机控制器驱动一般是半导体厂商写的。在linux驱动框架下编写具体的设备驱动。
3、中间的联系就是核心层。
1.2、驱动分层
二、总线-驱动-设备
驱动-总线-设备。
根据驱动的分离与分层衍生出了总线(bus)-驱动(driver)-设备(device)驱动框架。
总线代码我们不需要编写,linux内核提供给我们使用的。我们需要编写驱动和设备,当向总线注册驱动的时候,总线会从现有的所有设备中查找,看看哪个设备和此驱动匹配。同理,当向总线注册设备的时候总线也会在现有的驱动中查看与之匹配的驱动。
驱动:是具体的设备驱动
设备:设备属性,包括地址范围、如果是IIC的话还有IIC器件地址、速度
2.1、总线
总线数据类型为:bus_type。向内核注册总线使用bus_register。
struct bus_type {
const char *name;
const char *dev_name;
struct device *dev_root;
struct device_attribute dev_attrs; / use dev_groups instead */
const struct attribute_group **bus_groups;
const struct attribute_group **dev_groups;
const struct attribute_group **drv_groups;
*int (*match)(struct device *dev, struct device_driver *drv);*
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev);
int (*remove)(struct device *dev);
void (*shutdown)(struct device *dev);
int (*online)(struct device *dev);
int (*offline)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
const struct dev_pm_ops *pm;
const struct iommu_ops *iommu_ops;
struct subsys_private *p;
struct lock_class_key lock_key;
};
向linux内核注册总线,使用bus_register函数。bus_unregister函数卸载。
1、总线主要工作就是完成总线下的设备和驱动之间的匹配。
2.2、驱动
驱动数据类型为device_driver,驱动程序向内核注册驱动采用driver_register。
struct device_driver {
const char *name;
*struct bus_type* *bus;**
struct module *owner;
const char mod_name; / used for built-in modules */
bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
*const struct of_device_id* *of_match_table;**
const struct acpi_device_id *acpi_match_table;
*int (*probe) (struct device *dev);*
*int (*remove) (struct device *dev);*
*void (*shutdown) (struct device *dev);*
*int (*suspend) (struct device *dev, pm_message_t state);*
*int (*resume) (struct device *dev);*
const struct attribute_group **groups;
const struct dev_pm_ops *pm;
struct driver_private *p;
};
驱动和设备匹配以后驱动里面的probe函数就会执行。
使用driver_register注册驱动。
driver_register
-> bus_add_driver
-> driver_attach //查找bus下所有设备,找预期匹配的。
->bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
->__driver_attach //每个设备都调用此函数,
//查看每个设备是否与驱动匹配
-> driver_match_device //检查是否匹配。
-> driver_probe_device
->really_probe
-> drv->probe(dev); //执行driver的probe函数。
向总线注册驱动的时候,会检查当前总线下的所有设备,有没有与此驱动匹配的设备,如果有的话就执行驱动里面的probe函数。
2.2、设备
设备数据类型为device,通过device_register向内核注册设备。
struct device {
struct device *parent;
struct device_private *p;
struct kobject kobj;
const char init_name; / initial name of the device */
const struct device_type *type;
struct mutex mutex; /* mutex to synchronize calls to
* its driver.
*/
*struct bus_type* *bus;** */* type of bus device is on */*
*struct device_driver *driver;* */* which driver has allocated this*
*device */*
……
};
向总线注册设备的时候,使用device_register。
device_register
-> device_add
-> bus_add_device
-> bus_probe_device
->device_attach
->bus_for_each_drv(dev->bus, NULL, dev, __device_attach);
-> __device_attach
-> driver_match_device //匹配驱动
->bus->match
->driver_probe_device //执行此函数
-> really_probe
*-**>*** *drv->probe(dev);*
驱动与设备匹配以后驱动的probe函数就会执行,probe函数就是驱动编写人员去编写的!!!!
三、platform平台驱动模型
根据总线-驱动-设备驱动模型,IIC、SPI、USB这样实实在在的总线是完全匹配的,但是要有一些外设是没法归结为具体的总线:比如定时器、RTC、LCD等。为此linux内核创造了一个虚拟的总线:platform总线。
1、方便开发,linux提出了驱动分离与分层。
2、进一步引出了驱动-总线-设备驱动模型,或者框架。
3、对于SOC内部的RTC,timer等等不好归结为具体的总线,为此linux内核提出了一个虚拟总线:platform总线,platform设备和platform驱动。
3.1、platform总线注册
platform_bus_init
-> bus_register
注册的内容就是:
struct bus_type platform_bus_type = {
*.name* *= "platform",*
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
}
对于platform平台而言,platform_match函数就是月老,负责驱动和设备的匹配。
3.2 platform驱动
结构体为platform_driver,结构体内容为:
struct platform_driver {
*int (*probe)(struct platform_device *);*
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
*struct device_driver driver;*
*->* *const struct of_device_id* *of_match_table;**
*->* *const char* *name;**
*const struct platform_device_id *id_table;*
bool prevent_deferred_probe;
};
使用platform_driver_register向内核注册platform驱动。
platform_driver_register
-> __platform_driver_register (platform_driver)
-> 设置driver的probe为platform_drv_probe, //如果platform_driver的
// probe函数有效的话。
-> driver_register
->执行device_drive->probe,对于platform总线,也就是platform_drv_probe函数。而platform_drv_probe函数会执行platform_driver下的probe函数。
结论:向内核注册platform驱动的时候,如果驱动和设备匹配成功,最终会执行platform_driver的probe函数。
3.3 platform设备
结构体platform_device:
struct platform_device {
const char *name;
int id;
bool id_auto;
struct device dev;
u32 num_resources;
struct resource *resource;
const struct platform_device_id *id_entry;
char driver_override; / Driver name to force a match */
/* MFD cell pointer */
struct mfd_cell *mfd_cell;
/* arch specific additions */
struct pdev_archdata archdata;
};
1、无设备树的时候,此时需要驱动开发人员编写设备注册文件,使用platform_device_register函数注册设备。
2,有设备树,修改设备树的设备节点即可。
当设备与platform的驱动匹配以后,就会执行platform_driver->probe函数。
3.4 platform匹配过程
根据前面的分析,驱动和设备匹配是通过bus->match函数,platform总线下的match函数就是:platform_match。
platform_match
-> of_driver_match_device,设备树
-> acpi_driver_match_device ACPI类型的
-> platform_match_id 根据platform_driver-> id_table
-> strcmp(pdev->name, drv->name) //最终的就是比较字符串,就是platform_device->name,和platform_driver->driver->name。无设备树情况下使用。
1、有设备树的时候:
of_driver_match_device
-> of_match_device(drv->of_match_table, dev) //of_match_table非常重要,
类型为of_device_id。
//compatible属性
->
四、实验程序编写
4.1、无设备树
两部分:platform_driver、platform_device。以点灯为例。
1、编写向platform总线注册设备。编写驱动需要寄存器地址信息,地址信息使用设备信息,定义在platform_device里面,因此需要在驱动里面获取设备中的信息,或者叫资源。使用函数platform_get_resource()
4.2、有设备树
有设备树的时候设备是由设备树描述的,因此不需要向总线注册设备,而是直接修改设备树。只需要修改设备树,然后编写驱动。
驱动和设备匹配成功以后,设备信息就会从设备树节点转为platform_device结构体。
platform提供了很多API函数去获取设备相关信息的。
五 、运行测试