Linux系统之Platform设备驱动

Linux系统之Platform设备驱动https://mp.weixin.qq.com/s?__biz=Mzg3NDkwMjc2NA==&mid=2247483754&idx=1&sn=222dcdd1d30fa541b40bdf29c440f6c0&chksm=cec8e4e9f9bf6dffd6213094f1dee1d3a6bec6a8e08c4dedeff3dab6e4ba854d11d03898d2ca#rd

在Linux系统中,驱动的设计充分体现了“高内聚、低耦合”的软件设计思想。这种思想体现在驱动设计的分层、分离上,下文将简单介绍platform设备驱动的实现,让我们来感受一下驱动设计的哲学。

platform设备驱动包含platform总线、platform设备和platform驱动三个部分,下面将分别介绍这三个部分。

1 platform 设备

platform设备通常包含设备的硬件资源。

未引入设备树之前,片上外设的硬件资源通常在arch/arm/mach-xxx/board-xxx.c定义,并由内核自动注册为platform设备。

引入设备树后,硬件资源不再集成到Linux源码中,而是按照设备树语法编写成设备树文件,并进行单独编译(.dtb)。把板级的硬件信息从源码中分离出来,并对其使用统一的方式管理,我认为既降低了系统维护的难度,也有利于保持代码的干净。

在内核启动的过程中,会对设备树进行解析,把对应的设备节点转换成platform设备。

flatform设备在Linux系统中的抽象如下:

//linux-4.1.15/include/linux/platform_device.h
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;
};

其中resource结构体就是用于登记相关的硬件资源信息,如资源的类型、资源的地址空间等等,这里将不展开叙述,有兴趣的朋友可以查阅注册platform设备的相关代码。

2 platform 驱动

platform驱动一般由驱动工程师编写。当我们把platform驱动注册到platform总线时,platform总线会根据platform驱动的参数,查找与之匹配的设备。当存在与驱动匹配的设备时,总线将会把设备和驱动绑定。驱动可调用相关的接口获取设备资源,并驱动设备。

flatform设备在Linux系统中的抽象如下:

//linux-4.1.15/include/linux/platform_device.h
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;
};

将在示例中详细说明这个结构体的各个元素的作用。

3 platform 总线

platform总线是Linux系统发明的虚拟总线。如同I2C、SPI等总线,其作用是建立主机和从机通信的通道。platform总线的作用是根据相关规则匹配设备和驱动,使驱动与对应的设备建立联系。

flatform总线在Linux系统中的抽象如下:

//linux-4.1.15/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,
};
EXPORT_SYMBOL_GPL(platform_bus_type);

其中 platform_match 就是负责设备与驱动的匹配工作。

/** -> linux-4.1.15/drivers/base/platform.c
 * platform_match - bind platform device to platform driver.
 * @dev: device.
 * @drv: driver.
 *
 * Platform device IDs are assumed to be encoded like this:
 * "<name><instance>", where <name> is a short description of the type of
 * device, like "pci" or "floppy", and <instance> is the enumerated
 * instance of the device, like '0' or '42'.  Driver IDs are simply
 * "<name>".  So, extract the <name> from the platform_device structure,
 * and compare it against the name of the driver. Return whether they match
 * or not.
 */
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)) /* --> 匹配设备树的compatible属性和设备驱动的compatible属性 */
		return 1;

	/* Then try ACPI style match */
	if (acpi_driver_match_device(dev, drv))
		return 1;

	/* Then try to match against the id table */
	if (pdrv->id_table) 
		return platform_match_id(pdrv->id_table, pdev) != NULL;/* --> 匹配设备和驱动的id table */

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

可以看到这个接口根据struct platform_device和struct platform_driver的信息进行匹配绑定。

4 示例

在这个示例中,

  • 调用<late_initcall>接口,

    注册一个module:<pwm_backlight_init>

    在Linux启动过程中将会加载这个module

  • 在这个module中注册一个platform设备驱动

    (这个驱动抽象为<pwm_backlight_driver>结构体)

    <platform_driver_register(&pwm_backlight_driver); >

  • <pwm_backlight_driver>中可以看到该驱动的名字<.name= "pwm-backlight",>和设备树的匹配属性<{ .compatible = "pwm-backlight" },>

    platform总线将会根据这两个参数匹配已注册的设备

  • 当我们的platform驱动注册后,platform总线将驱动与总线上的设备进行匹配,当匹配属性一致时,驱动将与该设备进行绑定,并执行<pwm_backlight_driver>中的<pwm_backlight_probe>函数

  • 在probe函数中,我们将解析设备节点的资源,并以此初始化相关的硬件外设(GPIO、PWM等)

  • 完成初始化的外设可以通过系统调用的方式供上层应用使用,也可以导出符号表供其他驱动模块使用

  • 当我们卸载驱动(insmod加载,rmmod卸载)后

    将会执行<pwm_backlight_driver>中的

    <pwm_backlight_remove>函数,这个函数将会释放驱动所占用的硬件和内存资源,如注销pwm,释放申请的内存空间等

/linux-4.1.15/drivers/video/backlight/pwm_bl.c
/*
 * linux/drivers/video/backlight/pwm_bl.c
 *
 * simple PWM based backlight control, board code has to setup
 * 1) pin configuration so PWM waveforms can output
 * 2) platform_data being correctly configured
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
......

struct pwm_bl_data {
	struct device		*dev;
	......
};

static void pwm_backlight_power_on(struct pwm_bl_data *pb, int brightness)
{
	......
}

static void pwm_backlight_power_off(struct pwm_bl_data *pb)
{
	......
}

static int compute_duty_cycle(struct pwm_bl_data *pb, int brightness)
{
	......
}

static int pwm_backlight_update_status(struct backlight_device *bl)
{

	......
}

static int pwm_backlight_check_fb(struct backlight_device *bl,
				  struct fb_info *info)
{
	......
}
}

static const struct backlight_ops pwm_backlight_ops = {
	.update_status	= pwm_backlight_update_status,
	.check_fb	= pwm_backlight_check_fb,
};

#ifdef CONFIG_OF
static int pwm_backlight_check_fb_name(struct device *dev, struct fb_info *info)
{
	struct backlight_device *bl = dev_get_drvdata(dev);
	struct pwm_bl_data *pb = bl_get_data(bl);

	if (strcmp(info->fix.id, pb->fb_id) == 0)
		return true;

	return false;
}

static int pwm_backlight_parse_dt(struct device *dev,
				  struct platform_pwm_backlight_data *data)
{
	struct device_node *node = dev->of_node;
	struct property *prop;
	int length;
	u32 value;
	const char *names;
	......
	/* determine the number of brightness levels */
	prop = of_find_property(node, "brightness-levels", &length);
	......

	/* read brightness levels from DT property */
	if (data->max_brightness > 0) {	
		......
		ret = of_property_read_u32_array(node, "brightness-levels",
						 data->levels,
						 data->max_brightness);
		......

		ret = of_property_read_u32(node, "default-brightness-level",
					   &value);
		......
	}

	if (!of_property_read_string(node, "fb-names", &names)){
		strcpy(data->fb_id, names);
		......
	}
	......
	return 0;
}

static struct of_device_id pwm_backlight_of_match[] = {
	{ .compatible = "pwm-backlight" },
	{ }
};

MODULE_DEVICE_TABLE(of, pwm_backlight_of_match);
#else
static int pwm_backlight_parse_dt(struct device *dev,
				  struct platform_pwm_backlight_data *data)
{
	return -ENODEV;
}
#endif

static int pwm_backlight_probe(struct platform_device *pdev)
{
	struct platform_pwm_backlight_data *data = dev_get_platdata(&pdev->dev);
	struct platform_pwm_backlight_data defdata;
	struct backlight_properties props;
	struct backlight_device *bl;
	struct pwm_bl_data *pb;
	int ret;

	if (!data) {
		ret = pwm_backlight_parse_dt(&pdev->dev, &defdata);
		......
		data = &defdata;
	}

	if (data->init) {
		ret = data->init(&pdev->dev);
		......
	}

	......
	if (data->levels) {
		unsigned int i;

		for (i = 0; i <= data->max_brightness; i++)
			if (data->levels[i] > pb->scale)
				pb->scale = data->levels[i];

		pb->levels = data->levels;
	} else
		pb->scale = data->max_brightness;

	......
	pb->dev = &pdev->dev;
	......

	pb->enable_gpio = devm_gpiod_get_optional(&pdev->dev, "enable");
	......

	/*
	 * Compatibility fallback for drivers still using the integer GPIO
	 * platform data. Must go away soon.
	 */
	if (!pb->enable_gpio && gpio_is_valid(data->enable_gpio)) {
		ret = devm_gpio_request_one(&pdev->dev, data->enable_gpio,
					    GPIOF_OUT_INIT_HIGH, "enable");
		......
		pb->enable_gpio = gpio_to_desc(data->enable_gpio);
	}

	if (pb->enable_gpio)
		gpiod_direction_output(pb->enable_gpio, 1);

	pb->power_supply = devm_regulator_get(&pdev->dev, "power");
	......

	pb->pwm = devm_pwm_get(&pdev->dev, NULL);
	......

	dev_dbg(&pdev->dev, "got pwm for backlight\n");

	/*
	 * The DT case will set the pwm_period_ns field to 0 and store the
	 * period, parsed from the DT, in the PWM device. For the non-DT case,
	 * set the period from platform data if it has not already been set
	 * via the PWM lookup table.
	 */
	pb->period = pwm_get_period(pb->pwm);
	if (!pb->period && (data->pwm_period_ns > 0)) {
		pb->period = data->pwm_period_ns;
		pwm_set_period(pb->pwm, data->pwm_period_ns);
	}

	pb->lth_brightness = data->lth_brightness * (pb->period / pb->scale);

	memset(&props, 0, sizeof(struct backlight_properties));
	props.type = BACKLIGHT_RAW;
	props.max_brightness = data->max_brightness;
	bl = backlight_device_register(dev_name(&pdev->dev), &pdev->dev, pb,
				       &pwm_backlight_ops, &props);
	if (IS_ERR(bl)) {
		dev_err(&pdev->dev, "failed to register backlight\n");
		ret = PTR_ERR(bl);
		goto err_alloc;
	}

	if (data->dft_brightness > data->max_brightness) {
		dev_warn(&pdev->dev,
			 "invalid default brightness level: %u, using %u\n",
			 data->dft_brightness, data->max_brightness);
		data->dft_brightness = data->max_brightness;
	}

	bl->props.brightness = data->dft_brightness;
	backlight_update_status(bl);

	platform_set_drvdata(pdev, bl);
	return 0;

......
	return ret;
}

static int pwm_backlight_remove(struct platform_device *pdev)
{
	struct backlight_device *bl = platform_get_drvdata(pdev);
	struct pwm_bl_data *pb = bl_get_data(bl);

	backlight_device_unregister(bl);
	pwm_backlight_power_off(pb);

	if (pb->exit)
		pb->exit(&pdev->dev);
	if (pb->legacy)
		pwm_free(pb->pwm);

	return 0;
}

static void pwm_backlight_shutdown(struct platform_device *pdev)
{
	......
}

#ifdef CONFIG_PM_SLEEP
static int pwm_backlight_suspend(struct device *dev)
{
	......
	return 0;
}

static int pwm_backlight_resume(struct device *dev)
{
	......
	return 0;
}
#endif

static const struct dev_pm_ops pwm_backlight_pm_ops = {
#ifdef CONFIG_PM_SLEEP
	.suspend = pwm_backlight_suspend,
	.resume = pwm_backlight_resume,
	.poweroff = pwm_backlight_suspend,
	.restore = pwm_backlight_resume,
#endif
};

static struct platform_driver pwm_backlight_driver = {
	.driver		= {
		.name		= "pwm-backlight",
		.pm		= &pwm_backlight_pm_ops,
		.of_match_table	= of_match_ptr(pwm_backlight_of_match),
	},
	.probe		= pwm_backlight_probe,
	.remove		= pwm_backlight_remove,
	.shutdown	= pwm_backlight_shutdown,
};

static int __init pwm_backlight_init(void) 
{ 
        return platform_driver_register(&pwm_backlight_driver); 
} 
//module_init(pwm_backlight_init); 
late_initcall(pwm_backlight_init); 

static void __exit pwm_backlight_exit(void) 
{ 
        platform_driver_unregister(&pwm_backlight_driver); 
} 
module_exit(pwm_backlight_exit);

//module_platform_driver(pwm_backlight_driver);

MODULE_DESCRIPTION("PWM based Backlight Driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:pwm-backlight");

5 结束语

至此,Platform设备驱动已介绍完毕,希望本文可以帮助到大家!

欢迎大家通过后台与我交流学习心得,水平有限,如有错漏的地方请不吝指出,谢谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值