前段时间分析了Intel的lpc驱动,里面涉及了mfd,但网络上关于mfd的资料少之又少。所以就自己分析一下代码。本文对Linux内核的mfd设备注册过程进行分析,并结合2个实例,加深对其概念。
一、概述
mfd是Multifunction device的简称,即多功能设备,是许多有共性的设备的集合,mfd由核心层(core)以及其下的“子设备”组成。从下文将会看到,mfd只是将设备注册到platform总线——因此,其子设备属于platform设备。它并没有对涉及到的设备或驱动做实质性改变。但是,因为某些设备的共性,所以可以在mfd中提供共同的函数给其下子设备进行调用。如本文提到的ADP5520便是如此设计。
下面就分析mfd设备注册过程,并结合2个实例讲解。但不会涉及很细节的细节,因为这样篇幅太大了,而且还会纠结于语言本身,不利于全局把握。所以最好自行阅读内核源代码。
本文基于linux 3.17.1版本内核分析。 内核配置(make menuconfig)信息如下:
1 | Device Drivers ---> |
里面有众多可选的器件,根据实际来选择。
二、mfd设备添加
mfd核心代码位于drivers/mfd/mfd-core.c文件中。对外提供添加设备和删除设备的接口:mfd_add_devices、mfd_remove_devices。设备添加函数原型如下:
1 | int mfd_add_devices(struct device *parent, int id, |
下面主要分析其中一部分参数。
id:即设备ID号。它指示着设备的个数。一般可以设置为-1。即表示系统有且仅有一个这样的设备。如果有多个foo设备,则需要使用id来区别。在/sys/bus/platform/devices目录下会产生foo.0,foo.1等设备。详情可以看platform设备添加函数过程。
celss:即mfd_cell结构体数组,n_devs为其数组大小,即设备数量。
mem_base:资源resource结构体。如果没有,可置为NULL。
描述mfd设备单元称为“cell”,mfd_cell定义如下:
1 | /* |
部分常见的成员介绍如下:
name:设备平台。
platform_data:平台私有数据指针,数据大小使用pdata_size表示。
resources:资源结构体,资源数量使用num_resources表示。
ignore_resource_conflicts:为true表示不检查资源冲突。
mfd_add_devices函数内部根据设备数量n_devs循环调用mfd_add_device添加设备。该函数完成下面的工作:
1、申请platform_device空间。申请resource空间。
2、调用platform_device_add_data添加platform设备私有数据,亦即platform_data。
3、调用mfd_platform_add_cell将mfd_cell拷贝到platform_device的mfd_cell成员。(使用kmemdup实现)
4、根据参数,设置申请到的resource空间。并调用platform_device_add_resources添加到platform_device的resource成员。这样就能在platform驱动模块中获取到resource资源了。
5、调用platform_device_add添加platform设备。此过程中会调用到对应驱动的probe函数——当然,提前是已经存在对应的驱动。
至此,mfd设备的添加就完成了,最终调用驱动的probe函数。从这个过程中知道,mfd实质上就是封装一个接口,将一些可以归纳到一起的platform设备注册到platform总线上。它就是一个收纳盒子。里面的设备该是怎样处理就怎样处理。
三、mfd实例1:lpc驱动
本节介绍一下LPC驱动中WDT设备添加的过程。——因为前面讲了GPIO设备的添加。 e3800系列的WDT隐藏于ACPI中。后续文章将会进行介绍。这里有个概念即可。 在LPC探测函数lpc_ich_probe对ACPI基地址进行赋值,代码如下:
1 | priv->abase = ACPIBASE; // ACPI基地址 |
其定义是:
1 | #define ACPIBASE 0x40 |
所有地址都可以在手册对应章节中找到。 初始化WDT在函数lpc_ich_init_wdt中。这个函数获取ACPI基地址。并初始化wdt_ich_res资源结构体数组,数组包含了ICH_RES_IO_TCO和ICH_RES_IO_SMI。TCO就是WDT使用到的部分。主要代码功能描述如下。
1、读取ACPI基地址值,即通过LPC这个PCI设备的配置空间偏移值ACPIBASE。
1 | pci_read_config_dword(dev, priv->abase, &base_addr_cfg); |
2、设置resource,即把前面获取到的base_addr地址加上TCO偏移值赋给resource的start成员变量。
1 | res = wdt_io_res(ICH_RES_IO_TCO); |
3、添加mfd设备。
1 | lpc_ich_finalize_cell(dev, &lpc_ich_cells[LPC_WDT]); |
到这里就完成了mfd的添加。
四、mfd实例2:ADP5520驱动
[待写]
后记:mfd网上资料太少,本文根据代码分析而成。难免有错误。欢迎一起讨论。
参考资源: 1、baytrail手册:http://www.intel.com/content/www/us/en/embedded/products/bay-trail/atom-e3800-family-datasheet.html
2、ADP5520介绍:https://www.wiki.analog.com/resources/tools-software/linux-drivers/multifunction-device/adp5520
3、内核源码官网:https://www.kernel.org
4、内核源码查询:http://lxr.free-electrons.com/source/?v=3.17
李迟 2016.12.05 周一 夜