platform总线的设计目的
逻辑总线的的一个重要功能是发现设备并且找到合适的驱动以操作这个设备。
platform总线是一种虚拟的逻辑总线,满足逻辑互联功能。为了统一管理设备,platform可以把物理上不存在总线的一类设备,用平台总线统一把它们管理起来。
常见的总线如USB,SPI,UART,PCI,I2S等总线,是在物理上实实在在存在的总线。Linux系统为这类总线设计了一种统一管理它们的方法,即bus。同时也为在物理上没有总线的设备也统一设计了platform总线,所以Linux内核设计者就设计了platform总线,也就是说,plarform 总线就是为了SoC内部设备在物理上没有总线的设备设计的,它统一归属bus总线一类。
这样做的好处是,设备(device)端代码的编写者只关心具体的硬件部分,设备共性的部分(稳定不变的部分)被内核设计者完成了,这样降低了驱动编写的难度。
通过platform总线,可以将设备属性(也叫数据)和驱动分离,这样可以使用相同的驱动支持同一功能核心硬件的不同设备。这样避免了一个设备就需要一个驱动的现象,降低了重复性劳动。
platform平台总线相关定义
platform平台总线相关的对象定义在文件 drivers/base/platform.c中。这个文件中实现了平台总线对象和对象操作方法方面的内容。
两个重要的数据结构体
-
1、platform_driver
这是内嵌一个device_driver 的结构体对象,其中定义了操作对象的方法。 -
2、platform_device
它是一个内嵌了device结构体对象,其中定义了设备描述资源的属性。
具体对象定义如下:
1、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; //设备ID表
};
platform_device对象对定义:
struct platform_device
{
const char * name; //平台总线中设备的名字,在平台总线下有多个设备,每个设备都有自己的名称
int id; //设备的排序
struct device dev; //所有设备通用的属性
u32 num_resources; //设备资源,如IO等一些外设等的个数
struct resource * resource; //设备资源的首地址,和上面的个数num_resources一起构成一个数组来表示这个资源
const struct platform_device_id *id_entry; //设备ID表,表示同一种类型的几个设备的ID号,数组表示。
struct pdev_archdata archdata; /* arch specific additions *///用户自定义数据,扩展数据
};
注册平台总线驱动的函数:
int platform_driver_register(struct platform_driver *drv)
注册平台总线设备的函数:
int platform_device_register(struct platform_device *pdev)
platform总线在系统中的注册
系统启动时,在bus中注册platform总线,platform总线是bus总线中的一种总线。可以在/sys/bus目录下查看,如下所示:
[root@li bus]# pwd
/sys/bus
[root@li bus]# ls
hid mmc scsi serio usb
i2c platform sdio spi
[root@li bus]#
platform总线的注册函数是 int __init platform_bus_init(void),它是在系统启动时在bus总线中被注册了。注册之后,在目录/sys/bus下,就能发现platform 目录。
int __init platform_bus_init(void)
{
int error;
early_platform_cleanup();
error = device_register(&platform_bus); //这个函数的注册就会在 /sys/bus目录下出现plarform目录
if (error)
return error;
error = bus_register(&platform_bus_type); //bus中注册platform总线
if (error)
device_unregister(&platform_bus);
return error;
}
下面分析platform总线的在系统中的注册过程
用device_register(&platform_bus),注册后出现目录/sys/bus/platform目录,注册时使用了重要的结构体 platform_bus。
struct device platform_bus =
{
.init_name = "platform",
};
使用 bus_register(&platform_bus_type)注册总线platform。结构体 platform_bus_type 中的 .match函数就是用来把驱动和设备进行匹配的函数。
struct bus_type platform_bus_type =
{
.name = "platform",
.dev_attrs = platform_dev_attrs,
.match = platform_match, //
.uevent = platform_uevent, //
.pm = &platform_dev_pm_ops,
};
platform总线是管理着所有platform上的驱动和设备,它的mach函数时刻在检测着驱动和设备的匹配情况,一旦匹配上了,就执行probe函数完成设备的探测。
下面就是 .match函数的实体:
static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);//由结构体中某个元素的指针,反向推出整个结构体的指针
struct platform_device *pdrv = to_platform_driver(drv);
/* match against the id table first */
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL; //从ID数组中逐个比较进行匹配设备。
return (strcmp(pdev->name, drv->name) == 0); /* fall-back to driver name match */
}
platform_match函数的参数本来应该是 platform_device 和 platform_device ,但是为了统一使用 device 和 device_driver 参数,整个函数中就使用了to_platform_device宏进行反向推出 platform_device 和 platform_device 。
bus_type结构体中的 int (*match)(struct device *dev, struct device_driver *drv); 是通用的总线探测函数,所以不能确定具体的总线类型(即是USB或者是platform总线等)。所以才用了上面的宏定义进行反向推断出具体的总线类型。
分析一个实例
分析一个LED驱动框架实例:
驱动实例:
module_init(s5pv210_led_init);
module_exit(s5pv210_led_exit);
static int __init s5pv210_led_init(void)
{
return platform_driver_register(&s5pv210_led_driver);//注册;patform总线类型的一个驱动
}
static void __exit s5pv210_led_exit(void)
{
platform_driver_unregister(&s5pv210_led_driver); //卸载驱动
}
static struct platform_driver s5pv210_led_driver =
{
.probe = s5pv210_led_probe,
.remove = s5pv210_led_remove,
.driver =
{
.name = "s5pv210_led",
.owner = THIS_MODULE,
},
};
硬件初始化匹配函数
static int s5pv210_led_probe(struct platform_device *dev)
{
//硬件初始化设置
//硬件资源的申请
//......
led_classdev_register(); //注册一个类,使用了led class 提供的框架注册一个led驱动
......
led->cdev.brightness_set = s5pv210_led_set;
......
}
卸载函数
static int s5pv210_led_remove(struct platform_device *dev)
{
led_classdev_unregister(&p->cdev);//删除类
}
硬件的设置
static void s5pv210_led_set(struct led_classdev *led_cdev,enum led_brightness value)
{
//具体硬件操作
}
设备实例:
设备端相关结构体定义:
struct s5pv210_led_platdata
{
unsigned int gpio; //编号
unsigned int flags;
char *name;
char *def_trigger;
};
static struct s5pv210_led_platdata x210_led1_pdata =
{
.name = "led1",
.gpio = S5PV210_GPJ0(3),
.flags = S3C24XX_LEDF_ACTLOW | S3C24XX_LEDF_TRISTATE,
.def_trigger = "",
};
static struct platform_device x210_led1 =
{
.name = "s5pv210_led", /* /sys/bus/platform/devices/s5pv210_led.1 */
.id = 0, /* <s5pv210_led.1> 即: .name == s5pv210_led,.id == 1 */
.dev = {
.platform_data = &x210_led1_pdata, //指针指向一个自定义的数据结构
},
};
设备注册:
在内核初始化阶段进行设备的注册。
static void __init smdkc110_machine_init(void)
{
......
platform_add_devices(smdkc110_devices, ARRAY_SIZE(smdkc110_devices));
......
}
调用注册设备接口 platform_device_register
int platform_add_devices(struct platform_device **devs, int num)
{
int i, ret = 0;
for (i = 0; i < num; i++) //这里循环注册多个设备 num
{
ret = platform_device_register(devs[i]); //注册设备
if (ret)
{
while (--i >= 0)
platform_device_unregister(devs[i]);
break;
}
}
return ret;
}