在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设备驱动已介绍完毕,希望本文可以帮助到大家!
欢迎大家通过后台与我交流学习心得,水平有限,如有错漏的地方请不吝指出,谢谢!