您的位置:首页 > 文旅 > 旅游 > Linux字符设备驱动--MFD子系统

Linux字符设备驱动--MFD子系统

2024/10/5 4:53:42 来源:https://blog.csdn.net/xi_xix_i/article/details/140799061  浏览:    关键词:Linux字符设备驱动--MFD子系统

文章目录

  • 背景
  • 一、关于MFD子系统
  • 二、使用方法

背景

linux 4.9
armv8

一、关于MFD子系统

关于MFD子系统,资料比较少,所以需要看子系统源码研究一下,当做笔记,分析内容多在注释中。提前了解可以看一下这一篇其他博主写的简要介绍:

  • [Linux 基础] – Linux 内核中的 MFD 子系统

简而言之,MFD就是为那些复杂外设设计的。什么算复杂外设?如果一个外设功能特别多,就可以考虑用MFD子系统。以PMIC为例,博主接触过两款PMIC,一款是TI的TPS659xx,一款是全志的AXPxx。复杂如TI,芯片手册甚至有几百页。

这种复杂外设支持很多功能,以PMIC为例,支持多路电压/电流控制功能,支持冷/热重启功能,支持温度监测功能,支持看门狗功能等等。任何一个功能单独拿出来,感觉都可以单独作为一个外设,然后有相应的单独的驱动。MFD子系统就是为这种复杂设备准备的,并且接下来就会发现,MFD子系统的核心思想就是从一个复杂设备中抽象出不同功能,抽象成单独的设备。

二、使用方法

上一节提到,MFD子系统的核心思想就是从一个复杂设备(真实的物理设备)中抽象出不同功能,抽象成单独的设备(处理复杂设备的某一部分功能)。

而对这个设备的抽象,完全取决于使用者想怎么划分,或者说想使用哪一部分功能。所以需要我们实现:

  1. 功能描述框架,描述一个复杂设备可以抽象出哪些功能,每一个功能最终会注册为一个设备
  2. 注册的设备所对应的驱动

继续沿用分析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);elsekfree(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;elseplatform_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为0ret = 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的所有子节点nodefor_each_child_of_node(parent->of_node, np) {// g, 如果子节点node与mfd_cell的of_compatible能匹配上// g, 对于每个cell(除了第2个),确实都能在"pmu"的子结点(设备树pmu下的子结点)中找到匹配的compatibleif (of_device_is_compatible(np, cell->of_compatible)) {pdev->dev.of_node = np;			// g, 就会更新申请的platform_dev的信息,把找到的np赋予of_nodepdev->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_dataret = platform_device_add_data(pdev,cell->platform_data, cell->pdata_size);if (ret)goto fail_alias;}// g, 我们申请的所有cell都没有初始化这个域,如果存在这个域,则会为创建的platform device添加一个propertyif (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有效果,有两个中断resourcefor (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 = NULLif ((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 = 0cell->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()去匹配proberet = 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;
}

主要做的工作就是:

  1. 申请platform_device
  2. 根据传入cell的各个域,初始化platform_device的各个域
  3. 通过platform_device_add()注册设备。

最终的结果就是:

  1. 为每一个cell,注册了一个platform_dev
  2. 通过platform_device_add()注册设备时,调用到对应的probe函数。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com