platform 设备驱动

1. platform 总线、设备与驱动

    在嵌入式系统里面,在 SoC 系统中集成的独立外设控制器、挂接在 SoC 内存空间的外设等却不依附于此类总线。基于这一背景,Linux 发明了一种虚拟的总线,称为 platform 总线,相应的设备称为 platform_device,而驱动称为 platform_driver。

    注意:所谓的 platform_device 并不是与字符设备、块设备和网络设备并列的概念,而是 Linux 系统提供的一种附加手段,例如,我们通常把在 SoC 内核集成的 I2C、RTC、LCD、看门狗等控制器都归纳为 platform_device,而它们本身就是字符设备。

(1)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;
};

(2)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 platform_device_id *id_table;
	bool prevent_deferred_probe;
};

    直接填充 platform_driver 的 suspend()、resume() 做电源管理回调的方法目前已经过时,较好的做法是实现 platform_driver 的 device_driver 中的 dev_pm_ops 结构体成员。

(3)device_driver 结构体

/* 设备驱动 */
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 */
	enum probe_type probe_type;

	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;
}

    与 platform_driver 地位对等的 i2c_driver、spi_driver、usb_driver、pci_driver 中都包含了 device_driver 结构体实例成员。它其实描述了各种  xxx_driver(xxx 是总线名)在驱动意义上的一些共性。

(4)系统为 platform 总线定义了一个 bus_type 的实例 platform_bus_type,其定义位于 drivers/base/platform.c 下。

/* 总线定义 */
struct bus_type platform_bus_type = {
	.name		= "platform",
	.dev_groups	= platform_dev_groups,
	.match		= platform_match,
	.uevent		= platform_uevent,
	.pm		= &platform_dev_pm_ops,
};

    这里要重点关注其 match() 成员函数,正式此成员函数确定了 platform_device 和 platform_driver 之间是如何进行匹配的。

(5)platform_match() 函数

/* 匹配函数 */
static int platform_match(struct device *dev, struct device_driver *drv)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct platform_driver *pdrv = to_platform_driver(drv);

	/* When driver_override is set, only bind to the matching driver */
	if (pdev->driver_override)
		return !strcmp(pdev->driver_override, drv->name);

	/* Attempt an OF style match first */		/* 基于设备树风格的匹配 */
	if (of_driver_match_device(dev, drv))
		return 1;

	/* Then try ACPI style match */				/* 基于 ACPI 风格的匹配 */
	if (acpi_driver_match_device(dev, drv))
		return 1;

	/* Then try to match against the id table */	/* 匹配 ID 表 */
	if (pdrv->id_table)
		return platform_match_id(pdrv->id_table, pdev) != NULL;

	/* fall-back to driver name match */			/* 匹配 platform_device 设备名和驱动的名字 */
	return (strcmp(pdev->name, drv->name) == 0);
}

    匹配 platform_device 和 platform_driver 有 4 种可能性,一是基于设备树风格的匹配,二是基于 ACPI 风格的匹配,三是匹配 ID 表(即 platform_device 设备名是否出现在 platform_driver 的 ID 表内);第四种是匹配 platform_device 设备名和驱动的名字。

(6)对于 Linux 2.6 ARM 平台而言,对 platform_device 的定义通常在 BSP 的板文件中实现,在板文件中,将 platform_device 归纳为一个数组,最终通过 platform_add_devices() 函数统一注册。

Linux 3.x 之后,ARM Linux 不太喜欢人们以编码的形式去填写 platform_device 和注册,而倾向于根据设备树中的内容自动展开 platform_device。

注:platform_add_devices() 函数可以将平台设备添加到系统中,这个函数的原型为:

int platform_add_devices(struct platform_device **devs, int num)
该函数的第一个参数为平台设备数组的指针,第二个参数为平台设备的数量,它内部调用了 platform_device_register() 函数以注册单个的平台设备。


2. 将驱动挂接到 platform 总线上

将驱动挂接到 platform 总线上需要完成两个工作:

1)将驱动移植为 platform 驱动

2)在板文件中添加 platform 设备


3. platform 设备资源和数据

(1)platform 设备资源

platform 设备资源由 resource 结构体描述,定义如下:

struct resource {
	resource_size_t start;
	resource_size_t end;
	const char *name;
	unsigned long flags;
	unsigned long desc;
	struct resource *parent, *sibling, *child;
};

    通常需要关心的是 start、end 和 flags 这 3 个字段,它们分别标明了资源的开始值、结束值和类型。flags 可以为 IORESOURCE_IO、IORESOURCE_MEM、IORESOURCE_IRQ、IORESOURCE_DMA等,start、end 的含义会随着 flags 而变更。对于同种类型的资源而言,可以有多份。

    对 resource 的定义也通常在 BSP 的板文件中进行,而在具体的设备驱动中通过 platform_get_resource() 这样的 API 来获取,此 API 原型为:

struct resource *platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num);

(2)platform 设备数据

    设备除了可以在 BSP 中定义资源以外,还可以附加一些数据信息,因为对设备的硬件描述除了中断、内存等标准资源以外,可能还会有一些配置信息,而这些配置信息也依赖于板,不适宜直接放置在设备驱动上。因此,platform 也提供了 platform_data 的支持,platform_data 的形式是有每个驱动子定义的。


4. 总结

在设备驱动中引入 platform 的概念至少有如下好处:

    1)使得设备被挂接在一个总线上,符合 Linux 2.6 以后内核的设备模型。其结果是时配套的 sysfs 节点、设备电源管理都称为可能。

    2)隔离 BSP 驱动。在 BSP 中定义 platform 设备和设备使用的资源、设备的具体配置信息,而在驱动中,只需要通过调用 API 去获取资源和数据,做到了板相关代码和驱动代码的分离,使得驱动具有更好的可扩展性和跨平台性。

    3)让一个驱动支持多个设备实例。


猜你喜欢

转载自blog.csdn.net/linuxweiyh/article/details/79373856
今日推荐