文章目录
背景
linux 4.9
armv8
一、关于MFD子系统
关于MFD子系统,资料比较少,所以需要看子系统源码研究一下,当做笔记,分析内容多在注释中。提前了解可以看一下这一篇其他博主写的简要介绍:
简而言之,MFD就是为那些复杂外设设计的。什么算复杂外设?如果一个外设功能特别多,就可以考虑用MFD子系统。以PMIC为例,博主接触过两款PMIC,一款是TI的TPS659xx,一款是全志的AXPxx。复杂如TI,芯片手册甚至有几百页。
这种复杂外设支持很多功能,以PMIC为例,支持多路电压/电流控制功能,支持冷/热重启功能,支持温度监测功能,支持看门狗功能等等。任何一个功能单独拿出来,感觉都可以单独作为一个外设,然后有相应的单独的驱动。MFD子系统就是为这种复杂设备准备的,并且接下来就会发现,MFD子系统的核心思想就是从一个复杂设备中抽象出不同功能,抽象成单独的设备。
二、使用方法
上一节提到,MFD子系统的核心思想就是从一个复杂设备(真实的物理设备)中抽象出不同功能,抽象成单独的设备(处理复杂设备的某一部分功能)。
而对这个设备的抽象,完全取决于使用者想怎么划分,或者说想使用哪一部分功能。所以需要我们实现:
- 功能描述框架,描述一个复杂设备可以抽象出哪些功能,每一个功能最终会注册为一个设备
- 注册的设备所对应的驱动
继续沿用分析regmap子系统时,使用的多功能i2c通信设备(PMIC),我们需要用下面的接口来实现MFD子系统的核心思想:
ret = mfd_add_devices(struct device *dev, 0, struct mfd_cell* cells,
cell_num, NULL, 0, NULL);
传入的第一个参数struct device *dev就是我们的多功能设备,第二个参数是一个struct mfd_cell结构体数组,这个结构体数组需要我们自己定义,该结构体如下:
struct mfd_cell {
const char *name; // g, 设备名字,会在注册platform设备时使用该名字
int id; // g, 同名设备需要使用id进行区分
/* refcounting for multiple drivers to use a single cell */
atomic_t *usage_count;
int (*enable)(struct platform_device *dev);
int (*disable)(struct platform_device *dev);
int (*suspend)(struct platform_device *dev);
int (*resume)(struct platform_device *dev);
/* platform data passed to the sub devices drivers */
void *platform_data; // g, 设备私有数据
size_t pdata_size; // g, platform_data的大小
/* device properties passed to the sub devices drivers */
struct property_entry *properties; // g, 如果需要为新注册的设备添加一个properties的话,可以使用该域
/*
* Device Tree compatible string
* See: Documentation/devicetree/usage-model.txt Chapter 2.2 for details
*/
const char *of_compatible; // g, compatible属性,会在添加cell设备时通过该域寻找设备树生成的设备节点
/* Matches ACPI */
const struct mfd_cell_acpi_match *acpi_match; // g, acpi不管
/*
* These resources can be specified relative to the parent device.
* For accessing hardware you should use resources from the platform dev
*/
int num_resources; // g, 资源数
const struct resource *resources; // g, 设备资源,如果想为platform_device设置resource的话可以使用该域。设备树中的resource一般都是寄存器地址信息。
/* don't check for resource conflicts */
bool ignore_resource_conflicts;
/*
* Disable runtime PM callbacks for this subdevice - see
* pm_runtime_no_callbacks().
*/
bool pm_runtime_no_callbacks;
/* A list of regulator supplies that should be mapped to the MFD
* device rather than the child device when requested
*/
const char * const *parent_supplies;
int num_parent_supplies;
};
比如我把一个复杂设备抽象出了三个功能,我就可以这样设置mfd_cell数组:
static struct resource pmic_pek_resources[] = {
// g, 13,连到controller(中断控制器PMIC)上的硬件中断号。详情请看regmap子系统分析那一篇
// g, 这两个是同一按键然后分配了不同的中断,按键按下和松开各分配了一个中断
// g, struct resource时用来描述platform总线设备的设备资源的结构体.
{
.start = 13, // g, PMIC第13号中断, 按键下降沿中断
.end = 13,
.name = "PEK_DBF",
.flags = IORESOURCE_IRQ,
.desc = IORES_DESC_NONE,
},
{
.start = 14, // g, PMIC第14号中断, 按键上升沿中断
.end = 14,
.name = "PEK_DBR",
.flags = IORESOURCE_IRQ,
.desc = IORES_DESC_NONE,
}
};
...
...
static struct mfd_cell mfd_i2cdev_cells[] = {
{
.name = "axp305-pek", // g, 最终该name会被设置为为该cell注册的platform_device的name
.num_resources = ARRAY_SIZE(pmic_pek_resources), // g, 资源数
.resources = pmic_pek_resources, // g, 设备资源
.of_compatible = "x-powers,axp305-pek", // g, 在注册设备时会通过cell的该域和设备树节点的compatible匹配,来寻找设备树生成的设备树节点of_node,然后添加到注册的platform_device->dev.of_node
},
{
// g, 这个cell啥都没有,但是仍然有.name。
// g, 因为platform bus的匹配方式有一种匹配方式就是直接匹配platform_device->name和driver->name
// g, 所以这也要求,driver注册的时候一定要初始化.name域,而且一定是"axp2101-regulator",否则就没别的匹配方法给这个cell用了
// g, 而这个cell没有of_compatible,说明这个cell可能没有对应的设备树节点,可能不是一个物理设备.
// g, 所以这个cell注册而成的paltform_dev算是个虚拟设备,我们只是希望去调用这个platform_dev所匹配到的probe函数,可能在这个probe函数中去初始化一些真正的设备.
// g, 需要看后面的regulator子系统笔记
.name = "axp305-regulator",
},
{
.of_compatible = "xpower-vregulator,dcdc1",
.name = "reg-virt-consumer",
.id = 1, // g, 这个id会在platform_device_alloc(name, id)的时候传入,作为platform_dev->id,区分同名设备
.platform_data = 根据driver的需求,自己定义, // g, 会被添加到注册的platform_dev->dev.platform_data,该platform_dev对应的driver需要用到时可以取出来
.pdata_size = sizeof(AXP305_DCDC1_NAME),
},
{
.of_compatible = "xpower-vregulator,dcdc2",
.name = "reg-virt-consumer",
.id = 2,
.platform_data = ....,
.pdata_size = sizeof(AXP305_DCDC2_NAME),
},
...
...
};
这样我们就可以进行mfd设备注册工作了:
ret = mfd_add_devices(&i2c->dev, 0, mfd_i2cdev_cells,
ARRAY_SIZE(mfd_i2cdev_cells), NULL, 0, NULL);
因为是复杂设备是个i2c设备,所以该复杂设备的probe函数会传入struct i2c_client i2c,我们要在复杂设备的probe中调用上述mfd注册接口。下面就具体分析一下这个函数做了什么工作:
int mfd_add_devices(struct device *parent, int id,
const struct mfd_cell *cells, int n_devs,
struct resource *mem_base,
int irq_base, struct irq_domain *domain)
{
int i;
int ret;
atomic_t *cnts;
/* initialize reference counting for all cells */
cnts = kcalloc(n_devs, sizeof(*cnts), GFP_KERNEL);
if (!cnts)
return -ENOMEM;
for (i = 0; i < n_devs; i++) {
atomic_set(&cnts[i], 0);
// g, 主要就是调用下面这个函数
ret = mfd_add_device(parent, id, cells + i, cnts + i, mem_base,
irq_base, domain);
if (ret)
goto fail;
}
return 0;
fail:
if (i)
mfd_remove_devices(parent);
else
kfree(cnts);
return ret;
}
EXPORT_SYMBOL(mfd_add_devices);
嗯,看样子就是分配内存,然后遍历cells数组的每个cell,为其调用mfd_add_device函数:
static int mfd_add_device(struct device *parent, int id,
const struct mfd_cell *cell, atomic_t *usage_count,
struct resource *mem_base,
int irq_base, struct irq_domain *domain)
{
struct resource *res;
struct platform_device *pdev;
struct device_node *np = NULL;
int ret = -ENOMEM;
int platform_id;
int r;
if (id == PLATFORM_DEVID_AUTO)
platform_id = id;
else
platform_id = id + cell->id;
pdev = platform_device_alloc(cell->name, platform_id);
if (!pdev)
goto fail_alloc;
// g, 大部分cell的cell->num_resources为0,所以kzalloc分配0大小
// g, 但是kzalloc会检测size是否小于KMALLOC_MIN_SIZE,如果小于则至少分配KMALLOC_MIN_SIZE
// g, 同时kmalloc允许传入值为0,传入size为0时会返回ZERO_SIZE_PTR,为(void *)16,可以借此判断是内存不足还是传入参数为0
// g, 对于返回的ZERO_SIZE_PTR,可以直接把该值传入kfree,没有任何问题,kfree会直接返回的
res = kzalloc(sizeof(*res) * cell->num_resources, GFP_KERNEL);
if (!res)
goto fail_device;
pdev->dev.parent = parent;
pdev->dev.type = &mfd_dev_type;
pdev->dev.dma_mask = parent->dma_mask;
pdev->dev.dma_parms = parent->dma_parms;
pdev->dev.coherent_dma_mask = parent->coherent_dma_mask;
// g, 对于axp806的每一个cell来说,该函数没有起到任何作用。因为num_parent_supplies为0
ret = regulator_bulk_register_supply_alias(
&pdev->dev, cell->parent_supplies,
parent, cell->parent_supplies,
cell->num_parent_supplies);
if (ret < 0)
goto fail_res;
if (parent->of_node && cell->of_compatible) {
// g, 遍历parent的所有子节点node
for_each_child_of_node(parent->of_node, np) {
// g, 如果子节点node与mfd_cell的of_compatible能匹配上
// g, 对于每个cell(除了第2个),确实都能在"pmu"的子结点(设备树pmu下的子结点)中找到匹配的compatible
if (of_device_is_compatible(np, cell->of_compatible)) {
pdev->dev.of_node = np; // g, 就会更新申请的platform_dev的信息,把找到的np赋予of_node
pdev->dev.fwnode = &np->fwnode;
break;
}
}
}
// g, acpi相关的函数依赖于宏CONFIG_ACPI,在我用的平台中没有开启这个CONFIG。
// g, 如果没有开启这个config的话,该函数为空
mfd_acpi_add_device(cell, pdev);
if (cell->pdata_size) {
// g, 修改pdev->dev.platform_data = cell->platform_data
ret = platform_device_add_data(pdev,
cell->platform_data, cell->pdata_size);
if (ret)
goto fail_alias;
}
// g, 我们申请的所有cell都没有初始化这个域,如果存在这个域,则会为创建的platform device添加一个property
if (cell->properties) {
ret = platform_device_add_properties(pdev, cell->properties);
if (ret)
goto fail_alias;
}
// g, 将mfd_cell拷贝到platform_device(为每个cell刚刚创建的)的mfd_cell域
ret = mfd_platform_add_cell(pdev, cell, usage_count);
if (ret)
goto fail_alias;
// g, 对于大部分cell来说根本不会执行,因为num_resources = 0
// g, 只对powerkey这个cell有效果,有两个中断resource
for (r = 0; r < cell->num_resources; r++) {
res[r].name = cell->resources[r].name;
res[r].flags = cell->resources[r].flags;
/* Find out base to use */
// g, mem_base = NULL
if ((cell->resources[r].flags & IORESOURCE_MEM) && mem_base) {
res[r].parent = mem_base;
res[r].start = mem_base->start +
cell->resources[r].start;
res[r].end = mem_base->start +
cell->resources[r].end;
} else if (cell->resources[r].flags & IORESOURCE_IRQ) { // g, 这个else if会执行,flags相等
if (domain) { // g, domain = NULL
/* Unable to create mappings for IRQ ranges. */
WARN_ON(cell->resources[r].start !=
cell->resources[r].end);
res[r].start = res[r].end = irq_create_mapping(
domain, cell->resources[r].start);
} else {
res[r].start = irq_base + // g, irq_base = 0
cell->resources[r].start;
res[r].end = irq_base +
cell->resources[r].end;
}
} else {
res[r].parent = cell->resources[r].parent;
res[r].start = cell->resources[r].start;
res[r].end = cell->resources[r].end;
}
if (!cell->ignore_resource_conflicts) {
if (has_acpi_companion(&pdev->dev)) {
ret = acpi_check_resource_conflict(&res[r]);
if (ret)
goto fail_alias;
}
}
}
// g, 更新platform_device->resource和pdev->num_resources成员。
// g, 这样就能在platform_device对应的驱动模块中,通过pdev->resource获取到dev资源了
ret = platform_device_add_resources(pdev, res, cell->num_resources);
if (ret)
goto fail_alias;
// g, 调用platform_device_add添加platform device,
// g, 然后调用device_add()去匹配probe
ret = platform_device_add(pdev);
if (ret)
goto fail_alias;
if (cell->pm_runtime_no_callbacks)
pm_runtime_no_callbacks(&pdev->dev);
kfree(res);
return 0;
fail_alias:
regulator_bulk_unregister_supply_alias(&pdev->dev,
cell->parent_supplies,
cell->num_parent_supplies);
fail_res:
kfree(res);
fail_device:
platform_device_put(pdev);
fail_alloc:
return ret;
}
主要做的工作就是:
- 申请platform_device
- 根据传入cell的各个域,初始化platform_device的各个域
- 通过platform_device_add()注册设备。
最终的结果就是:
- 为每一个cell,注册了一个platform_dev
- 通过platform_device_add()注册设备时,调用到对应的probe函数。