參考:html
http://blog.csdn.net/qq_28992301/article/details/52385518linux
http://blog.csdn.net/zoe6553/article/details/6372445數組
http://blog.chinaunix.net/uid-25014876-id-111745.html框架
1:什麼是platform總線?
platform總線是區別於實體總線USB、 I2C、SPI 、PIC總線的虛擬總線,一些usb設備選址的話須要經過USB總線來進行尋址,函數
而有些相似於SoC內部外設如led 看門狗 定時器是直接經過內存的尋址空間來進行尋址的,cpu與這些設備通訊是不須要總線的,2.6內核之後要ui
對全部設備進行統一管理,經過kset、kobject來創建層次關係,對這些直接經過內存尋址的設備虛擬了一種總線即platform總線,在硬件上spa
實際是沒有這個總線;platform內核純軟件的總線,全部的直接經過內存尋址的設備都映射到這條總線上;.net
2:platform總線的優勢設計
a:能夠經過platform總線,能夠遍歷全部的platform總線設備;platform本質其實也是kset、kobject,具備kobject的特性unix
b:實現設備與驅動的分離,經過platform總線,設備與驅動是分開註冊的,經過platform總線的probe來隨時檢測與設備匹配的驅動,如匹配上即進行這個設備的驅動註冊;
c:因爲上面這個優點,一個驅動能夠供同類的幾個設備使用;
3:platform總線以及platform總線設備驅動的實現流程
a:platform總線註冊
b:platform_device註冊
c:platform_driver註冊
d:設備與驅動的匹配
e:驅動的註冊
platform總線的工做流程以下圖:
------------------------------------------------------------------------------------------------------------------------------------------------------------------
1:根據上面的流程咱們來分析一下具體代碼:
platform總線的註冊:platform的註冊是linux內核工程師已經設註冊好的;重點看一下.match = platform_match函數;platform_driver和platform_device就是經過這個函數
來匹配的
1 struct bus_type platform_bus_type = { 2 .name = "platform", 3 .dev_attrs = platform_dev_attrs, 4 .match = platform_match, 5 .uevent = platform_uevent, 6 .pm = &platform_dev_pm_ops, 7 };
1 int __init platform_bus_init(void) 2 { 3 int error; 4
5 early_platform_cleanup(); 6
7 error = device_register(&platform_bus); 8 if (error) 9 return error; 10 error = bus_register(&platform_bus_type); 11 if (error) 12 device_unregister(&platform_bus); 13 return error; 14 }
1 static int platform_match(struct device *dev, struct device_driver *drv) 2 { 3 struct platform_device *pdev = to_platform_device(dev); 4 struct platform_driver *pdrv = to_platform_driver(drv); 5
6 /* match against the id table first */
7 if (pdrv->id_table) 8 return platform_match_id(pdrv->id_table, pdev) != NULL; 9
10 /* fall-back to driver name match */
11 return (strcmp(pdev->name, drv->name) == 0); 12 }
由platform_match_id函數來進行匹配的,若是id_table不爲空,則經過id_table來pdev_name匹配,若是爲空,則drv->name與pdev->name來進行匹配,
匹配上之後再執行probe函數,這個函數即註冊這個設備的驅動;
---------------------------------------------------------------------------------------------------------------------------------------------------------------
2:platform_device的註冊
在arch/arm/mach-s3c2440/mach-mini2440.c文件中
這裏注意.name、.dev.platform_data 這兩個變量
platform_driver和platform_device就是經過name來匹配的。name一致則匹配上;
.dev.platform_data這個元素是中的內容是name、gpio flag def_trigger四個元素
1 static struct platform_device mini2440_led1 = { 2 .name = "s3c24xx_led", 3 .id = 1, 4 .dev = { 5 .platform_data = &mini2440_led1_pdata, 6 }, 7 }; 8
9 static struct platform_device mini2440_led2 = { 10 .name = "s3c24xx_led", 11 .id = 2, 12 .dev = { 13 .platform_data = &mini2440_led2_pdata, 14 }, 15 };
設置好platform_device 結構體之後就能夠註冊platform_device設備了,把咱們設置好的platform_device結構體放到mini2440這個結構體數組指針中;
1 static struct platform_device *mini2440_devices[] __initdata = { 2 &s3c_device_ohci, 3 &s3c_device_wdt, 4 &s3c_device_i2c0, 5 &s3c_device_rtc, 6 &s3c_device_usbgadget, 7 &mini2440_device_eth, 8 &mini2440_led1, 9 &mini2440_led2, 10 &mini2440_led3, 11 &mini2440_led4, 12 &mini2440_button_device, 13 &s3c_device_nand, 14 &s3c_device_sdi, 15 &s3c_device_iis, 16 &mini2440_audio, 17 };
在arch/arm/mach-s3c2440/mach-mini2440.c
mini2440_init 函數下
platform_add_devices(mini2440_devices, ARRAY_SIZE(mini2440_devices));
使用的platform_add_devices這個函數把mini2440的全部設備註冊到內核中;內核會自動查找platform_device鏈表以及platform_driver鏈表,當match之後字自動執行platform_driver的probe函數;
在整理一下platform_device的註冊過程:
1:設置好platform_device結構體(對於led驅動來講關鍵是name、dev->platform_data兩個元素)
2:初始化好dev->platform_data結構體,這裏主要涉及到led驅動所要用到的gpio,
這裏咱們能夠看到linux內核platform驅動框架的設計思想:首先設備和驅動是分開的,同類設備有共性的部分,不一樣的部分,不一樣的部分在初始化的即被設置好;共性的部份內核工程師以及設置好;而後在經過一個匹配函數若是內核鏈表的設備與驅動鏈表的驅動匹配,則會自動安裝驅動,不然不會安裝驅動;
3:把設置好的platform_device設備加入到mini2440_devices中
4:在mini2440_device初始化的時候經過platform_add_devices函數把platform設備註冊上去;註冊之後再/sys/bus/platform/devices目錄下會看到dev.name的文件夾
---------------------------------------------------------------------------------------------------------------------------------------------------------
3:platform_driver的註冊
1 struct platform_driver { 2 int (*probe)(struct platform_device *); 3 int (*remove)(struct platform_device *); 4 void (*shutdown)(struct platform_device *); 5 int (*suspend)(struct platform_device *, pm_message_t state); 6 int (*resume)(struct platform_device *); 7 struct device_driver driver; 8 const struct platform_device_id *id_table; 9 };
1 static struct platform_driver s3c24xx_led_driver = { 2 .probe = s3c24xx_led_probe, 3 .remove = s3c24xx_led_remove, 4 .driver = { 5 .name = "s3c24xx_led", 6 .owner = THIS_MODULE, 7 }, 8 }; 9
10 static int __init s3c24xx_led_init(void) 11 { 12 return platform_driver_register(&s3c24xx_led_driver); 13 }
設置好platform_driver 結構體,使用platform_driver_register註冊便可,這裏關鍵的是probe、remove、driver.name 三個變量;
platform_driver_register 使用這個函數註冊之後再 /sys/bus/platform/drivers目錄下會看到 dev.name的文件夾
內核會自動檢測匹配之後會自動執行probe函數;
-----------------------------------------------------------------------------------------------------------------------------------------------------------
代碼實戰:
led_driver.c driver註冊;
1 #include <linux/module.h> // module_init module_exit
2 #include <linux/init.h> // __init __exit
3 #include <linux/fs.h>
4 #include <asm/uaccess.h>
5 #include <plat/map-base.h>
6 #include <plat/map-s5p.h>
7 #include <mach/regs-gpio.h>
8 #include <mach/gpio-bank.h>
9 #include <linux/ioport.h>
10 #include <linux/string.h>
11 #include <asm/io.h>
12 #include <linux/cdev.h>
13 #include <linux/device.h>
14 #include <linux/leds.h>
15 #include <linux/gpio.h>
16 #include <linux/slab.h>
17 #include <linux/platform_device.h>
18 #include <mach/leds-gpio.h>
19
20 struct led_classdev *led_device; 21 struct s5pv210_led_platdata *pdata; 22
23
24 #define x210_led_on 0
25 #define x210_led_off 1
26
27 static void s5pv210_led_set(struct led_classdev *led_cdev, 28 enum led_brightness value) 29 { 30
31 //真正控制硬件的函數
32 if (value == LED_OFF) { 33 gpio_set_value(pdata->gpio, x210_led_off); 34 printk(KERN_INFO "LED1 OFF..."); 35 } 36 else { 37
38 gpio_set_value(pdata->gpio, x210_led_on); 39 printk(KERN_INFO "LED1 ON..."); 40 } 41
42 } 43
44
45
46
47 // 模塊安裝函數
48 static int s5pv210_led_probe(struct platform_device *dev) 49 { 50 int ret = -1; 51 printk(KERN_INFO "led_device init\n"); 52
53
54 led_device = kzalloc(sizeof(struct led_classdev), GFP_KERNEL); 55 if (led_device == NULL) 56 { 57 printk(KERN_ERR "No memory for led_device\n"); 58 return -ENOMEM; 59 } 60
61 pdata = dev->dev.platform_data; 62
63 led_device->name = pdata->name; 64 led_device->brightness_set = s5pv210_led_set; 65
66
67
68 //在這裏進行註冊驅動;
69 ret = led_classdev_register(NULL, led_device); 70 if (ret < 0) 71 { 72 printk(KERN_ERR "led_classdev_register failed\n"); 73 kfree(led_device); 74 return ret; 75 } 76
77
78
79 //初始化gpio
80 ret = gpio_request(pdata->gpio, pdata->name); 81 if (ret < 0) { 82 printk(KERN_ERR "couldn't claim card detect pin \n"); 83 return -1; 84 } 85 gpio_direction_output(pdata->gpio, 1); 86
87 return 0; 88 } 89
90 // 模塊刪除函數
91 static int s5pv210_led_remove(struct platform_device *dev) 92 { 93 printk(KERN_INFO "leddev_dev exit\n"); 94
95 //註銷led設備驅動
96 led_classdev_unregister(led_device); 97 kfree(led_device); 98
99 //刪除gpiolib庫中引腳
100 gpio_free(pdata->gpio); 101
102 printk(KERN_INFO "leddev_dev unregist success\n"); 103
104 return 0; 105 } 106
107 static struct platform_driver s5pv210_led_driver = { 108 .probe = s5pv210_led_probe, 109 .remove = s5pv210_led_remove, 110 .driver = { 111 .name = "s5pv210_led", 112 .owner = THIS_MODULE, 113 }, 114 }; 115
116 static int __init s5pv210_led_init(void) 117 { 118 return platform_driver_register(&s5pv210_led_driver); 119 } 120
121 static void __exit s5pv210_led_exit(void) 122 { 123 platform_driver_unregister(&s5pv210_led_driver); 124 } 125
126
127 module_init(s5pv210_led_init); 128 module_exit(s5pv210_led_exit); 129
130 // MODULE_xxx這種宏做用是用來添加模塊描述信息
131 MODULE_LICENSE("GPL"); // 描述模塊的許可證
132 MODULE_AUTHOR("BHC <BHC>"); // 描述模塊的做者
133 MODULE_DESCRIPTION("led test"); // 描述模塊的介紹信息
134 MODULE_ALIAS("alias xxx"); // 描述模塊的別名信息
device設備註冊
1 static struct s5pv210_led_platdata s5pv210_led1_pdata = { 2 .name = "led0", 3 .gpio = S5PV210_GPJ0(3), 4 .flags = S3C24XX_LEDF_ACTLOW | S3C24XX_LEDF_TRISTATE, 5 .def_trigger = "", 6 }; 7 static struct s5pv210_led_platdata s5pv210_led2_pdata = { 8 .name = "led1", 9 .gpio = S5PV210_GPJ0(4), 10 .flags = S3C24XX_LEDF_ACTLOW | S3C24XX_LEDF_TRISTATE, 11 .def_trigger = "", 12 }; 13 static struct s5pv210_led_platdata s5pv210_led3_pdata = { 14 .name = "led2", 15 .gpio = S5PV210_GPJ0(5), 16 .flags = S3C24XX_LEDF_ACTLOW | S3C24XX_LEDF_TRISTATE, 17 .def_trigger = "", 18 }; 19
20 static struct platform_device s5pv210_led0 = { 21 .name = "s5pv210_led", 22 .id = 1, 23 .dev = { 24 .platform_data = &s5pv210_led1_pdata, 25 }, 26 }; 27
28 static struct platform_device s5pv210_led1 = { 29 .name = "s5pv210_led", 30 .id = 2, 31 .dev = { 32 .platform_data = &s5pv210_led2_pdata, 33 }, 34 }; 35
36 static struct platform_device s5pv210_led2 = { 37 .name = "s5pv210_led", 38 .id = 3, 39 .dev = { 40 .platform_data = &s5pv210_led3_pdata, 41 }, 42 };
static struct platform_device *smdkc110_devices[] __initdata
把下面代碼加入到smdkc110_devices 這個結構體中;便可註冊設備和驅動了
1 //led device
2 &s5pv210_led1, 3 &s5pv210_led2, 4 &s5pv210_led0,