1. Platform device 和dts
很多开发人员只知道配置好dts后,系统会自动匹配相应的compatible,然后调用驱动的probe,但是不明白其具体的创建过程。
以下是dts扫描platform device的调用流程。
需要注意的是,此处只会creat&add其匹配的一级child。比如amba-bus下的i2c master是属于其一级child,会add。但是i2c master下的child,属于二级child,就不在这里creat&add了。I2C master下的child则是有其他驱动creat&add,以后分析。
device_add 时,设置其dev.bus为platform_bus_type,以标识这个设备是platform device。
device_add 之后,kernel device core 会匹配相关的driver,如果匹配成功,会调用 device_driver_attach,实现驱动probe,完成device的初始化(涉及到通用驱动框架,详细流程以后分析)。
2. paltform bus 驱动
以下是platform bus 驱动初始化调用流程。
以下为platform bus type的参数。
static const struct dev_pm_ops platform_dev_pm_ops = {
.runtime_suspend = pm_generic_runtime_suspend,
.runtime_resume = pm_generic_runtime_resume,
USE_PLATFORM_PM_SLEEP_OPS
};
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.dma_configure = platform_dma_configure,
.pm = &platform_dev_pm_ops,
};
EXPORT_SYMBOL_GPL(platform_bus_type);
需要特别关注的是.match,match会在device和driver_attach被调用,用于匹配driver和device。
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 */
/*根据dts的compatible匹配driver的compatible*/
if (of_driver_match_device(dev, drv))
return 1;
/* Then try ACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1;
/* Then try to match against the id table */
/*根据platform device的name匹配driver的platform_drv_data的id_table
*如一个driver创建的另一个platform,也可考虑此处匹配:如pci glue添加devcie,或者mfd创建子device。
*/
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;
/* fall-back to driver name match */
/*根据platform device name和drv name匹配*/
return (strcmp(pdev->name, drv->name) == 0);
}
3. platform driver
以下是列举一个platform driver驱动的典型配置:
static const struct of_device_id axp20x_adc_of_match[] = {
{ .compatible = "x-powers,axp209-adc", .data = (void *)&axp20x_data, },
{ .compatible = "x-powers,axp221-adc", .data = (void *)&axp22x_data, },
{ .compatible = "x-powers,axp813-adc", .data = (void *)&axp813_data, },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, axp20x_adc_of_match);
static const struct platform_device_id axp20x_adc_id_match[] = {
{ .name = "axp20x-adc", .driver_data = (kernel_ulong_t)&axp20x_data, },
{ .name = "axp22x-adc", .driver_data = (kernel_ulong_t)&axp22x_data, },
{ .name = "axp813-adc", .driver_data = (kernel_ulong_t)&axp813_data, },
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(platform, axp20x_adc_id_match);
static struct platform_driver axp20x_adc_driver = {
.driver = {
.name = "axp20x-adc",
.of_match_table = of_match_ptr(axp20x_adc_of_match),
},
.id_table = axp20x_adc_id_match,
.probe = axp20x_probe,
.remove = axp20x_remove,
};
module_platform_driver(axp20x_adc_driver);
其中.of_match_table和.id_table可以任意配置一个。
以上流程便是通过dts来创建platform的流程。
4. platform device 初始参数。
dts参数
/**
* of_device_alloc - Allocate and initialize an of_device
* @np: device node to assign to device
* @bus_id: Name to assign to the device. May be null to use default name.
* @parent: Parent device.
*/
struct platform_device *of_device_alloc(struct device_node *np,
const char *bus_id,
struct device *parent)
{
struct platform_device *dev;
int rc, i, num_reg = 0, num_irq;
struct resource *res, temp_res;
dev = platform_device_alloc("", PLATFORM_DEVID_NONE);
if (!dev)
return NULL;
/* count the io and irq resources */
/*对应dts的reg = <addr len>
*addr和len的宽度,根据#address-cells 和 #size-cells而定;
*/
while (of_address_to_resource(np, num_reg, &temp_res) == 0)
num_reg++;
num_irq = of_irq_count(np);
/* Populate the resource table */
if (num_irq || num_reg) {
res = kcalloc(num_irq + num_reg, sizeof(*res), GFP_KERNEL);
if (!res) {
platform_device_put(dev);
return NULL;
}
dev->num_resources = num_reg + num_irq;
dev->resource = res;
for (i = 0; i < num_reg; i++, res++) {
rc = of_address_to_resource(np, i, res);
WARN_ON(rc);
}
if (of_irq_to_resource_table(np, res, num_irq) != num_irq)
pr_debug("not all legacy IRQ resources mapped for %pOFn\n",
np);
}
/*
* 设置dts的node, 以及设置其父节点,如果没有父节点则挂在root platform bus上。
*/
dev->dev.of_node = of_node_get(np);
dev->dev.fwnode = &np->fwnode;
dev->dev.parent = parent ? : &platform_bus;
if (bus_id)
dev_set_name(&dev->dev, "%s", bus_id);
else
of_device_make_bus_id(&dev->dev);
return dev;
}
EXPORT_SYMBOL(of_device_alloc);
以上的dev->resource在driver probe时,可用以下接口组合,将device的resource map到内核空间,以供driver直接read/write.
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base = devm_ioremap_resource(dev, res );
pdev->dev->platform_data初始化:
platform_data主要用于区别各device的差异或创建者想要传递的数据。
如果platform device是从kernel初始时的of_platform_default_populate来创建的,那么此时platform_data则为空。 如果是module_init什么的函数手动创建的,则通常会传入一些参数。如:
static int __init palmtx_pcmcia_init(void)
{
int ret;
if (!machine_is_palmtx())
return -ENODEV;
palmtx_pcmcia_device = platform_device_alloc("pxa2xx-pcmcia", -1);
if (!palmtx_pcmcia_device)
return -ENOMEM;
ret = platform_device_add_data(palmtx_pcmcia_device, &palmtx_pcmcia_ops,
sizeof(palmtx_pcmcia_ops));
if (!ret)
ret = platform_device_add(palmtx_pcmcia_device);
if (ret)
platform_device_put(palmtx_pcmcia_device);
return ret;
}
static void __exit palmtx_pcmcia_exit(void)
{
platform_device_unregister(palmtx_pcmcia_device);
}
module_init(palmtx_pcmcia_init);
module_exit(palmtx_pcmcia_exit);
或者使用platform_device_register_full 接口配置platform_data数据。
platform_data使用时,可调用接口:dev_get_platdata(&spi->dev)获取。