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

背景

linux 4.9
armv8

一、关于MFD子系统

关于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);
	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;
}

主要做的工作就是:

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

最终的结果就是:

  1. 为每一个cell,注册了一个platform_dev
  2. 通过platform_device_add()注册设备时,调用到对应的probe函数。
  • 19
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值