目录
1、为什么会有平台总线?
2、平台总线三要素
3、平台总线编程接口
4、编写能在多平台下使用的led驱动
1、为什么会有平台总线?
1 用于平台升级:三星: 2410, 2440, 6410, s5pc100 s5pv210 4412 2 硬件平台升级的时候,部分的模块的控制方式,基本上是类似的 3 但是模块的地址是不一样 4 5 gpio控制逻辑: 1, 配置gpio的输入输出功能: gpxxconf 6 2, 给gpio的数据寄存器设置高低电平: gpxxdata 7 逻辑操作基本上是一样的 8 但是地址不一样 9 10 uart控制:1,设置8n1(数据位、奇偶校验位、停止位),115200, no AFC(流控) 11 UCON,ULCON, UMODOEN, UDIV 12 13 逻辑基本上是一样的 14 但是地址不一样 15 16 问题: 17 当soc升级的时候, 对于相似的设备驱动,需要编写很多次(如果不用平台总线) 18 但是会有大部分重复代码 19 20 解决:引入平台总线 21 device(中断/地址)和driver(操作逻辑) 分离 22 在升级的时候,只需要修改device中信息即可(中断/地址) 23 实现一个driver代码能够驱动多个平台相似的模块,并且修改的代码量很少
2、平台总线三要素 —— platform_bus、device、driver·
platform会存在/sys/bus/里面
如下图所示, platform目录下会有两个文件,分别就是platform设备和platform驱动
device设备
挂接在platform总线下的设备, 使用结构体platform_device描述
driver驱动
挂接在platform总线下,是个与某种设备相对于的驱动, 使用结构体platform_driver描述
platform总线
是个全局变量,为platform_bus_type,属于虚拟设备总线,通过这个总线将设备和驱动联系起来,属于Linux中bus的一种。
以下依次介绍其数据结构:
1)device
1 struct platform_device { 2 const char * name; //设备名称,要与platform_driver的name一样,这样总线才能匹配成功 3 u32 id; //插入总线下相同name的设备编号(一个驱动可以有多个设备),如果只有一个设备填-1 4 struct device dev; //具体的device结构体,继承了device父类 5 //成员platform_data可以给平台driver提供各种数据(比如:GPIO引脚等等) 6 u32 num_resources;//资源数目 7 struct resource * resource;//资源描述,用来描述io,内存等 8 }; 9 10 //资源文件 ,定义在include\linux\ioport.h 11 struct resource { 12 resource_size_t start; //起始资源,如果是地址的话,必须是物理地址 13 resource_size_t end; //结束资源,如果是地址的话,必须是物理地址 14 const char *name; //资源名 15 unsigned long flags; //资源类型,可以是io/irq/mem等 16 struct resource *parent, *sibling, *child; //链表结构,可以构成链表 17 }; 18 // type 19 #define IORESOURCE_IO 0x00000100 /* Resource type */ 20 #define IORESOURCE_MEM 0x00000200 21 #define IORESOURCE_IRQ 0x00000400 22 #define IORESOURCE_DMA 0x00000800
注册和注销
1 int platform_device_register(struct platform_device * pdev); 2 void platform_device_unregister(struct platform_device * pdev)
2)driver
1 struct platform_driver { 2 int (*probe)(struct platform_device *); //匹配成功之后被调用的函数 3 int (*remove)(struct platform_device *);//device移除的时候调用的函数 4 struct device_driver driver; //继承了driver父类 5 | 6 const char *name; 7 const struct platform_device_id *id_table; //如果driver支持多个平台,在列表中写出来 8 }
注册与注销
1 int platform_driver_register(struct platform_driver *drv); 2 void platform_driver_unregister(struct platform_driver *drv);
3)platform_bus
1 struct bus_type platform_bus_type = { 2 .name = "platform", //设备名称 3 .dev_attrs = platform_dev_attrs, //设备属性、含获取sys文件名,该总线会放在/sys/bus下 4 .match = platform_match, //匹配设备和驱动,匹配成功就调用driver的.probe函数 5 .uevent = platform_uevent, //消息传递,比如热插拔操作 6 .suspend = platform_suspend, //电源管理的低功耗挂起 7 .suspend_late = platform_suspend_late, 8 .resume_early = platform_resume_early, 9 .resume = platform_resume, //恢复 10 }; 11
匹配方法:match
1 static int platform_match(struct device *dev, struct device_driver *drv) 2 { 3 //1,优先匹配pdriver中的id_table,里面包含了支持不同的平台的名字 4 //2,直接匹配driver中名字和device中名字 5 6 struct platform_device *pdev = to_platform_device(dev); 7 struct platform_driver *pdrv = to_platform_driver(drv); 8 9 if (pdrv->id_table) // 如果pdrv中有idtable,平台列表名字和pdev中的名字 10 return platform_match_id(pdrv->id_table, pdev) != NULL; 11 12 /* fall-back to driver name match */ 13 return (strcmp(pdev->name, drv->name) == 0); 14 15 }
如何实现?
在pdev与pdrv指向的device、driver结构体中,各自都有一个成员 -- 父类结构体,实际上向总线注册的是这个父类结构体指针。通过container_of,可以通过成员找到包含该成员的整个结构体。
1 #define to_platform_device(x) container_of((x), struct platform_device, dev) 3 #define to_platform_driver(drv) (container_of((drv), struct platform_driver, driver))
3、平台总线编程接口
1) pdev 注册和注销
1 int platform_device_register(struct platform_device * pdev); 2 void platform_device_unregister(struct platform_device * pdev);
2)pdrv注册与注销
1 int platform_device_register(struct platform_device * pdev); 2 void platform_device_unregister(struct platform_device * pdev);
3)获取资源数据(对资源的定义可以参考内核/arch/arm/mach-xxx.c文件)
1 int platform_get_irq(struct platform_device * dev,unsigned int num); 2 struct resource * platform_get_resource_byname(struct platform_device * dev,unsigned int type,const char * name);
4、编写能在多平台下使用的LED驱动
1)注册一个platform_device,定义资源:地址和中断
1 struct resource { 2 resource_size_t start;// 开始 3 resource_size_t end; //结束 4 const char *name; //描述,自定义 5 unsigned long flags; //区分当前资源描述的是中断(IORESOURCE_IRQ)还是内存(IORESOURCE_MEM) 6 struct resource *parent, *sibling, *child; 7 };
2)注册一个platform_driver,实现操作失败的代码
1 注册完毕,同时如果和pdev匹配成功,自动调用probe方法: 2 probe方法: 对硬件进行操作 3 a,注册设备号,并且注册fops--为用户提供一个设备标示,同时提供文件操作io接口 4 b,创建设备节点 5 c,初始化硬件 6 ioremap(地址); //地址从pdev需要获取 7 readl/writle(); 8 d,实现各种io接口: xxx_open, xxx_read, .. 9
1 获取资源的方式: 2 //获取资源 3 struct resource *platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num) 4 { 5 int i; 6 7 for (i = 0; i < dev->num_resources; i++) { 8 struct resource *r = &dev->resource[i]; 9 10 if (type == resource_type(r) && num-- == 0) 11 return r; 12 } 13 return NULL; 14 } 15 // 参数1: 从哪个pdev中获取资源 16 // 参数2: 资源类型 17 // 参数3: 表示获取同种资源的第几个(0,1,2,3.....) 18 //返回值:返回一个指针,指向想获取的资源项
示例:编写平台驱动,实现最基本的匹配
1 #include <linux/init.h> 2 #include <linux/module.h> 3 #include <linux/ioport.h> 4 #include <plat/irqs.h> 5 #include <linux/platform_device.h> 6 7 8 static int led_pdrv_probe(struct platform_device * pdev) 9 { 10 printk("--------------%s-------------\n",__FUNCTION__); 11 return 0; 12 } 13 14 15 static int led_pdrv_remove(struct platform_device * pdev) 16 { 17 18 return 0; 19 } 20 21 22 //id_table:平台的id列表,包含支出不同平台的名字 23 const struct platform_device_id led_id_table[] = { 24 {"exynos_4412_led", 0x4444}, 25 {"s3c2410_led", 0x2244}, 26 {"s5pv210_led", 0x3344}, 27 }; 28 29 struct platform_driver led_pdrv = { 30 .probe = led_pdrv_probe, 31 .remove = led_pdrv_remove, 32 .driver = { 33 .name = "samsung_led_drv", 34 //可以用作匹配 35 // /sys/bus/platform/drivers/samsung_led_drv 36 }, 37 .id_table = led_id_table, 38 39 }; 40 41 42 static int __init plat_led_drv_init(void) 43 { 44 printk("---------------%s---------------\n",__FUNCTION__); 45 //注册一个平台驱动 46 return platform_driver_register(&led_pdrv); 47 48 } 49 50 static void __exit plat_led_drv_exit(void) 51 { 52 53 platform_driver_unregister(&led_pdrv); 54 } 55 56 57 58 module_init(plat_led_drv_init); 59 module_exit(plat_led_drv_exit); 60 61 MODULE_LICENSE("GPL");