目錄
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");

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 #define GPIO_REG_BASE 0X11400000 8 9 #define GPF3_CON GPIO_REG_BASE + 0x01E0 10 #define GPF3_SIZE 24 //定義6個(一組)寄存器的連續空間 11 12 #define GPX1_CON GPIO_REG_BASE + 0x01E0 13 #define GPX1_SIZE 24 //定義6個(一組)寄存器的連續空間 14 15 16 //一個設備可能有多個資源 17 struct resource led_res[] = { 18 [0] = { 19 .start = GPF3_CON, 20 .end = GPF3_CON + GPF3_SIZE -1, 21 .flags = IORESOURCE_MEM, 22 }, 23 24 [1] = { 25 .start = GPX1_CON, 26 .end = GPX1_CON + GPX1_SIZE -1, 27 .flags = IORESOURCE_MEM, 28 }, 29 30 //有些設備也有中斷資源,僅說明中斷資源使用 31 [2] = { 32 .start = 4, //#define IRQ_EINT4 S3C2410_IRQ(36) /* 52 */ 33 .end = 4, //中斷沒有連續地址概念,開始與結束都是中斷號 34 .flags = IORESOURCE_IRQ, 35 }, 36 37 }; 38 39 40 41 struct platform_device led_pdev = { 42 .name = "exynos_4412_led", //用於匹配的設備名稱 /sys/bus/platform/devices 43 .id = -1, //表示只有一個設備 44 .num_resources = ARRAY_SIZE(led_res), //資源數量,ARRAY_SIZE()函數:獲取數量 45 .resource = led_res, 46 47 48 }; 49 50 51 52 static int __init plat_led_dev_init(void) 53 { 54 //注冊一個平台設備 55 return platform_device_register(&led_pdev); 56 57 } 58 59 static void __exit plat_led_dev_exit(void) 60 { 61 62 platform_device_unregister(&led_pdev); 63 } 64 65 module_init(plat_led_dev_init); 66 module_exit(plat_led_dev_exit); 67 68 MODULE_LICENSE("GPL");
測試:
進一步完善:在驅動程序中,實現了probe方法,獲取硬件資源,完成led設備初始化,實現了上層調用的IO接口。實現了應用程序對硬件的控制。

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 #include <linux/of.h> 7 #include <linux/of_irq.h> 8 #include <linux/interrupt.h> 9 #include <linux/slab.h> 10 #include <linux/fs.h> 11 #include <linux/device.h> 12 #include <linux/kdev_t.h> 13 #include <linux/err.h> 14 #include <asm/io.h> 15 #include <asm/uaccess.h> 16 17 18 19 //設計一個全局變量 20 struct led_dev{ 21 int dev_major; 22 struct class *cls; 23 struct device *dev; //這個dev是用來創建設備文件的,不同與注冊到bus的dev 24 struct resource *res; //獲取到的某類資源 25 void* reg_base; //映射后的虛擬地址 26 }; 27 28 struct led_dev *samsung_led; 29 30 ssize_t led_pdrv_write(struct file *filp, const char __user * buf, size_t count, loff_t *fops) 31 { 32 int val; 33 int ret; 34 35 ret = copy_from_user(&val, buf, count); 36 if(ret > 0) 37 { 38 printk("copy_from_user failed\n"); 39 return -EFAULT; 40 } 41 42 if(val){ 43 44 writel(readl(samsung_led->reg_base + 4) | (0x3<<4) , samsung_led->reg_base+4); 45 46 }else{ 47 48 writel(readl(samsung_led->reg_base + 4) & ~(0x3<<4) , samsung_led->reg_base+4); 49 50 } 51 52 return 0; 53 54 } 55 56 int led_pdrv_open(struct inode *inode, struct file *filp) 57 { 58 printk("--------------%s-------------\n",__FUNCTION__); 59 //open相關的初始化也可以在open中完成,或在probe 60 return 0; 61 } 62 63 int led_pdrv_close(struct inode *inode, struct file *filp) 64 { 65 printk("--------------%s-------------\n",__FUNCTION__); 66 return 0; 67 } 68 69 70 71 //給上層應用提供文件IO接口 72 const struct file_operations led_fops = { 73 .open = led_pdrv_open, 74 .write = led_pdrv_write, 75 .release = led_pdrv_close, 76 77 }; 78 79 static int led_pdrv_probe(struct platform_device * pdev) 80 { 81 printk("--------------%s-------------\n",__FUNCTION__); 82 83 samsung_led = kzalloc(sizeof(struct led_dev), GFP_KERNEL); 84 if(samsung_led == NULL) 85 { 86 printk("kzalloc failed\n"); 87 return -ENOMEM; 88 } 89 90 91 /* 對硬件進行操作 92 * a,注冊設備號,並且注冊fops--為用戶提供一個設備標示,同時提供文件操作io接口 93 * b,創建設備節點 94 * c,初始化硬件 95 * ioremap(地址); //地址從pdev需要獲取 96 * readl/writle(); 97 * d,實現各種io接口: xxx_open, xxx_read, .. 98 */ 99 100 //a. 注冊設備號,動態 101 samsung_led->dev_major = register_chrdev(0, "led_drv", &led_fops); 102 103 //b. 創建設備節點 104 samsung_led->cls = class_create(THIS_MODULE, "led_new_cls"); 105 samsung_led->dev = device_create(samsung_led->cls, NULL, MKDEV(samsung_led->dev_major,0), NULL, "led0"); 106 107 //c. 初始化硬件 108 109 //獲取資源 110 //參數1:從哪個pdev中獲取資源 111 //參數2:指定資源類型 112 //參數3:表示獲取同種資源的第幾個,(0,1,2,...) 113 samsung_led->res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 114 int irqno = platform_get_irq(pdev, 0); 115 //等同於platform_get_resource(pdev, IORESOURCE_IRQ, 0); 116 printk("-------get irqno = %d-------\n", irqno); 117 118 //大小 = end - start + 1; 加1是因為地址從0讀起,eg: 7~0 = 7-0+1 119 //resource_size : samsung_led->res->end - samsung_led->res->start + 1 120 samsung_led->reg_base = ioremap(samsung_led->res->start,resource_size(samsung_led->res)); 121 122 //對寄存器進行配置 -- 輸出功能 123 writel((readl(samsung_led->reg_base) & ~(0xff<<16)) | (0x11<<16), samsung_led->reg_base); 124 125 126 return 0; 127 } 128 129 //與probe是一對兒,做了與probe相反的事 130 static int led_pdrv_remove(struct platform_device * pdev) 131 { 132 iounmap(samsung_led->reg_base); 133 class_destroy(samsung_led->cls); 134 device_destroy(samsung_led->cls, MKDEV(samsung_led->dev_major,0)); 135 unregister_chrdev(samsung_led->dev_major, "led_drv"); 136 kfree(samsung_led); 137 return 0; 138 } 139 140 141 //id_table:平台的id列表,包含支出不同平台的名字 142 const struct platform_device_id led_id_table[] = { 143 {"exynos_4412_led", 0x4444}, 144 {"s3c2410_led", 0x2244}, 145 {"s5pv210_led", 0x3344}, 146 }; 147 148 struct platform_driver led_pdrv = { 149 .probe = led_pdrv_probe, 150 .remove = led_pdrv_remove, 151 .driver = { 152 .name = "samsung_led_drv", 153 //可以用作匹配 154 //創建/sys/bus/platform/drivers/samsung_led_drv 155 }, 156 .id_table = led_id_table, 157 158 }; 159 160 161 static int __init plat_led_drv_init(void) 162 { 163 printk("---------------%s---------------\n",__FUNCTION__); 164 //注冊一個平台驅動 165 return platform_driver_register(&led_pdrv); 166 167 } 168 169 static void __exit plat_led_drv_exit(void) 170 { 171 172 platform_driver_unregister(&led_pdrv); 173 } 174 175 176 module_init(plat_led_drv_init); 177 module_exit(plat_led_drv_exit); 178 179 MODULE_LICENSE("GPL");

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 #define GPIO_REG_BASE 0X11400000 8 9 #define GPF3_CON GPIO_REG_BASE + 0x01E0 10 #define GPF3_SIZE 24 //定義6個(一組)寄存器的連續空間 11 12 #define GPX1_CON GPIO_REG_BASE + 0x01E0 13 #define GPX1_SIZE 24 //定義6個(一組)寄存器的連續空間 14 15 16 //一個設備可能有多個資源 17 struct resource led_res[] = { 18 [0] = { 19 .start = GPF3_CON, 20 .end = GPF3_CON + GPF3_SIZE -1, 21 .flags = IORESOURCE_MEM, 22 }, 23 24 [1] = { 25 .start = GPX1_CON, 26 .end = GPX1_CON + GPX1_SIZE -1, 27 .flags = IORESOURCE_MEM, 28 }, 29 30 //有些設備也有中斷資源,僅說明中斷資源使用 31 [2] = { 32 .start = 4, //#define IRQ_EINT4 S3C2410_IRQ(36) /* 52 */ 33 .end = 4, //中斷沒有連續地址概念,開始與結束都是中斷號 34 .flags = IORESOURCE_IRQ, 35 }, 36 37 }; 38 39 40 41 struct platform_device led_pdev = { 42 .name = "exynos_4412_led", //用於匹配的設備名稱 /sys/bus/platform/devices 43 .id = -1, //表示只有一個設備 44 .num_resources = ARRAY_SIZE(led_res), //資源數量,ARRAY_SIZE()函數:獲取數量 45 .resource = led_res, 46 47 48 }; 49 50 51 52 static int __init plat_led_dev_init(void) 53 { 54 //注冊一個平台設備 55 return platform_device_register(&led_pdev); 56 57 } 58 59 static void __exit plat_led_dev_exit(void) 60 { 61 62 platform_device_unregister(&led_pdev); 63 } 64 65 module_init(plat_led_dev_init); 66 module_exit(plat_led_dev_exit); 67 68 MODULE_LICENSE("GPL");

1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <sys/types.h> 5 #include <sys/stat.h> 6 #include <fcntl.h> 7 #include <unistd.h> 8 9 10 11 int main(int argc, char * argv[]) 12 { 13 int fd; 14 15 int on = 0; 16 17 fd = open("/dev/led0", O_RDWR); 18 if(fd < 0) 19 { 20 perror("open\n"); 21 exit(1); 22 } 23 24 while(1) 25 { 26 on = 0; 27 write(fd, &on, 4); 28 sleep(1); 29 30 on = 1; 31 write(fd, &on, 4); 32 sleep(1); 33 } 34 35 36 return 0; 37 }

1 ROOTFS_DIR = /home/linux/source/rootfs#根文件系統路徑 2 3 APP_NAME = led_test 4 MODULE_NAME = plat_led_pdev 5 MODULE_NAME1 = plat_led_pdrv 6 7 CROSS_COMPILE = /home/linux/toolchains/gcc-4.6.4/bin/arm-none-linux-gnueabi- 8 CC = $(CROSS_COMPILE)gcc 9 10 ifeq ($(KERNELRELEASE),) 11 12 KERNEL_DIR = /home/linux/kernel/linux-3.14-fs4412 #編譯過的內核源碼的路徑 13 CUR_DIR = $(shell pwd) #當前路徑 14 15 all: 16 make -C $(KERNEL_DIR) M=$(CUR_DIR) modules #把當前路徑編成modules 17 $(CC) $(APP_NAME).c -o $(APP_NAME) 18 @#make -C 進入到內核路徑 19 @#M 指定當前路徑(模塊位置) 20 21 clean: 22 make -C $(KERNEL_DIR) M=$(CUR_DIR) clean 23 24 install: 25 sudo cp -raf *.ko $(APP_NAME) $(ROOTFS_DIR)/drv_module #把當前的所有.ko文件考到根文件系統的drv_module目錄 26 27 else 28 29 obj-m += $(MODULE_NAME).o #指定內核要把哪個文件編譯成ko 30 obj-m += $(MODULE_NAME1).o 31 32 endif
測試結果:
在運行led_test后,可以看見led閃爍,查看platform總線下的devices,drivers目錄,也分別注冊
了設備與驅動
小結:
平台設備驅動模型,主要是用於平台升級的,可以在平台升級時,主要修改設備相關文件即可,對於設備驅動,盡可能重用。
平台設備驅動來源於設備驅動模型,只是平台總線由系統實現了而已。
平台設備代碼里面主要實現:將設備注冊到總線,填充設備相關的硬件資源,例如內存資源,中斷資源;
在平台驅動里,最先做的也是將驅動注冊到總線,然后對硬件進行一系列初始化,那么硬件信息從哪里來?在設備驅動注冊到總線后,總線會將設備的platform_device中的成指針dev傳給設備驅動,設備驅動通過成員就可獲取到結構體的數據,訪問到設備的資源,從而進行硬件相關操作,這些操作都可以在探測函數中完成。此外,設備驅動還要為應用層提供操作設備的接口fops。簡言之:注冊、硬件初始化、獲取設備資源,實現上層接口,注銷。