platform总线是虚拟的平台总线,是linux设备驱动模型为了保持设备驱动的统一性而虚拟出来的总线。
总线将设备和驱动绑定,系统每注册一个设备的时候,会寻找与之匹配的驱动;相反,系统每注册一个驱动的时候,会寻找与之匹配的设备,而匹配由总线完成。
platform驱动工作流程:
1. 系统开机内核初始化阶段,初始化platform总线;
2. platform_device初始化调用platform_add_devices(一次注册多个设备)
或者platform_device_register(一次注册单个设备)
,这部分一般在arch/arm/mach配置文件中,在上电开机的时候完成platform_device初始化
3. platform_driver
的初始化调用platform_driver_register
或者driver_register
,该过程一般在驱动程序的init函数中
platform总线的初始化
platform总线的初始化是在内核的初始化阶段完成的
整个流程是kernel_init()
–> do_basic_setup()
–> driver_init()
–> platform_bus_init()
下面分析platform_bus_init()
函数
struct device platform_bus = { /*定义一个名为platform的总线设备*/
.init_name = "platform", /*其他的platform设备都是它的子设备*/
};
struct bus_type platform_bus_type = {
.name = "platform",
.dev_attrs = platform_dev_attrs,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
EXPORT_SYMBOL_GPL(platform_bus_type);
int __init platform_bus_init(void)
{
int error;
early_platform_cleanup();
/* platform总线也是设备,对应的sysfs节点为/sys/devices/platform */
error = device_register(&platform_bus);
if (error)
return error;
error = bus_register(&platform_bus_type);//注册平台类型的bus,将出现sys文 件系统在bus目录下,创建一个platform的目录,以及相关属性文件
if (error)
device_unregister(&platform_bus);
return error;
}
注册platform_device
platform_device
的注册一般放在arch/arm/mach配置文件中,在开机时被初始化,所以当注册platform_driver时会在platform总线上查找匹配的设备
struct platform_device {
const char * name; /* 设备名 */
u32 id; //当有多个设备用来 区分 ,只有一个设备,id=-1
struct device dev;
struct resource * resource ; /* 设备所使用各类资源数量 */
struct resource * resource; /* 资源 */
};
这里有个重要的成员resource???该结构存入了资源信息
struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
struct resource *parent, *sibling, *child;
};
下面举 s3c6410 平台的 i2c 驱动作为例子来说明platform_device的注册过程:
static struct platform_device *smdk6410_devices [] __initdata = {
#ifdef CONFIG_SMDK6410_SD_CH0
&s3c_device_hsmmc0,
#endif
#ifdef CONFIG_SMDK6410_SD_CH1
&s3c_device_hsmmc1,
#endif
&s3c_device_i2c0 ,
&s3c_device_i2c1,
&s3c_device_fb,
&s3c_device_usb,
&s3c_device_usb_hsotg,
&smdk6410_lcd_powerdev,
&smdk6410_smsc911x,
};
把一个或几个设备资源放在一起,便于集中管理,其中IIC设备 platform_device如下:
struct platform_device s3c_device_i2c0 = {
.name = "s3c2410-i2c",
#ifdef CONFIG_S3C_DEV_I2C1
.id = 0,
#else
.id = -1,
#endif
.num_resources = ARRAY_SIZE(s3c_i2c_resource ),
.resource = s3c_i2c_resource,
};
具体resource如下:
static struct resource s3c_i2c_resource [] = {
[0] = {
.start = S3C_PA_IIC,
.end = S3C_PA_IIC + SZ_4K - 1,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_IIC,
.end = IRQ_IIC,
.flags = IORESOURCE_IRQ,
},
};
这里定义了两组 resource ,它描述了一个 I2C 设备的资源,第 1 组描述了这个 I2C 设备所占用的总线地址范围, IORESOURCE_MEM
表示第 1 组描述的是内存类型的资源信息,第 2 组描述了这个 I2C 设备的中断号,IORESOURCE_IRQ
表示第 2 组描述的是中断资源信息。设备驱动会根据 flags 来获取相应的资源信息。
定义好了platform_device
结构体后就可以调用函数 platform_add_devices
向系统中添加该设备了,之后可以调用 platform_driver_register()
进行设备注册。
s3c6410-i2c
的platform_device
是在系统启动时,在mach-smdk6410.c
里的smdk6410_machine_init()
函数里进行注册的,这个函数申明为arch_initcall的函数调用,arch_initcall的优先级高于module_init。所以会在Platform驱动注册之前调用。(详细参考imach-smdk6410.c)
static void __init smdk6410_machine_init(void)
{
s3c_i2c0_set_platdata(NULL);
s3c_i2c1_set_platdata(NULL);
s3c_fb_set_platdata(&smdk6410_lcd_pdata);
gpio_request(S3C64XX_GPN(5), "LCD power");
gpio_request(S3C64XX_GPF(13), "LCD power");
gpio_request(S3C64XX_GPF(15), "LCD power");
i2c_register_board_info(0, i2c_devs0, ARRAY_SIZE(i2c_devs0));
i2c_register_board_info(1, i2c_devs1, ARRAY_SIZE(i2c_devs1));
platform_add_devices(smdk6410_devices, ARRAY_SIZE(smdk6410_devices));
//添加多设备
}
下面分析一下platform_add_devices是如何注册platform_device的:
int platform_add_devices(struct platform_device **devs, int num)
{
int i, ret = 0;
for (i = 0; i < num; i++) {
ret = platform_device_register(devs[i]);
if (ret) {
while (--i >= 0)
platform_device_unregister(devs[i]);
break;
}
}
return ret;
}
int platform_device_register(struct platform_device *pdev)
{
device_initialize(&pdev->dev);
return platform_device_add(pdev);
}
int platform_device_add(struct platform_device *pdev)
{
int i, ret = 0;
if (!pdev)
return -EINVAL;
if (!pdev->dev.parent)
pdev->dev.parent = &platform_bus;
pdev->dev.bus = &platform_bus_type;
if (pdev->id != -1)
//如果有id 表示有多个同类设备用 pdev->name和 pdev->id标识该设备
dev_set_name(&pdev->dev, "%s.%d", pdev->name, pdev->id);
else
dev_set_name(&pdev->dev, "%s", pdev->name);
//否则,只用 pdev->name标识该设备
for (i = 0; i < pdev->num_resources; i++) {
struct resource *p, *r = &pdev->resource[i];
if (r->name == NULL)
r->name = dev_name(&pdev->dev);
p = r->parent;
if (!p) {
if (resource_type(r) == IORESOURCE_MEM)
p = &iomem_resource; // 作为 IOMEM 资源分配
else if (resource_type(r) == IORESOURCE_IO)
p = &ioport_resource; // 作为 IO PORT资源分配
}
if (p && insert_resource(p, r)) { // 将新的 resource 插入内核 resource tree
printk(KERN_ERR
"%s: failed to claim resource %d\n",
dev_name(&pdev->dev), i);
ret = -EBUSY;
goto failed;
}
}
pr_debug("Registering platform device '%s'. Parent at %s\n",
dev_name(&pdev->dev), dev_name(pdev->dev.parent));
ret = device_add(&pdev->dev);//添加设备到设备树
if (ret == 0)
return ret;
failed:
while (--i >= 0) {
struct resource *r = &pdev->resource[i];
unsigned long type = resource_type(r);
if (type == IORESOURCE_MEM || type == IORESOURCE_IO)
release_resource(r);
}
return ret;
}
platform_driver注册
platform_driver结构体初始化,以i2c为例
static struct platform_driver s3c24xx_i2c_driver = {
.probe = s3c24xx_i2c_probe,
.remove = s3c24xx_i2c_remove,
.suspend_late = s3c24xx_i2c_suspend_late,
.resume = s3c24xx_i2c_resume,
.id_table = s3c24xx_driver_ids,
.driver = {
.owner = THIS_MODULE,
.name = "s3c-i2c",
},
};
在驱动程序的初始化函数中调用platform_driver_add()
注册platform_driver,中线的匹配函数(platrorm_mach
)会匹配总线上已经存在的设备的名称(name字段),当设备与驱动匹配成功,调用驱动程序中的probe函数。