一,platform平臺總線簡介
1、何爲平臺總線
(1)相對於usb、pci、i2c等物理總線來說,platform總線是虛擬的、抽象出來的。
(2)回顧裸機中講的,CPU與外部通信的2種方式:地址總線式連接和專用接口式連接。平臺總線對應地址總線式連接設備,也就是SoC內部集成的各種內部外設。
2、平臺總線下管理的2員大將
(1)platform工作體系都定義在drivers/base/platform.c中
(2)兩個結構體:platform_device和platform_driver
platform_device
struct platform_device {
const char * name; // 平臺總線下設備的名字
int id;
struct device dev; // 所有設備通用的屬性部分
u32 num_resources; // 設備使用到的resource的個數
struct resource * resource; // 設備使用到的資源數組的首地址
const struct platform_device_id *id_entry; // 設備ID表
/* arch specific additions */
struct pdev_archdata archdata; // 自留地,用來提供擴展性的
};
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平臺總線工作原理
1、平臺總線體系的工作流程
- 第一步:系統啓動時在bus系統中註冊platform。
- 第二步:內核移植的人負責提供platform_device
- 第三步:寫驅動的人負責提供platform_driver
- 第四步:platform的match函數發現driver和device匹配後,調用driver的probe函數來完成驅動的初始化和安裝,然後設備就工作起來了
2、平臺總線的匹配方法match
static int platform_match(struct device *dev, struct device_driver *dr
{
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);
/* Attempt an OF style match first */
if (of_driver_match_device(dev, drv))
return 1;
/* Then try ACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1;
/* Then try to match against the id table */
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL
/* fall-back to driver name match */
return (strcmp(pdev->name, drv->name) == 0);
}
(1)每種總線(不光是platform,usb、i2c那些也是)都會帶一個match方法,match方法用來對總線下的device和driver進行匹配。理論上每種總線的匹配算法是不同的,但是實際上一般都是看name的。
(2)platform_match函數就是平臺總線的匹配方法。該函數的工作方法是:
- 如果有id_table就說明驅動可能支持多個設備,所以這時候要去對比id_table中所有的name,只要找到一個相同的就匹配上了不再找了
- 如果找完id_table都還沒找到就說明每匹配上
- 如果沒有id_table或者每匹配上,那就直接對比device和driver的name,如果匹配上就匹配上了,如果還沒匹配上那就匹配失敗
3、platform設備和驅動的註冊過程
(1)兩個接口函數:platform_device_register和platform_driver_register
platform_driver_register
int platform_driver_register(struct platform_driver *drv)
{
drv->driver.bus = &platform_bus_type;
if (drv->probe)
drv->driver.probe = platform_drv_probe;
if (drv->remove)
drv->driver.remove = platform_drv_remove;
if (drv->shutdown)
drv->driver.shutdown = platform_drv_shutdown;
return driver_register(&drv->driver);
}
platform_device_register
int platform_device_register(struct platform_device *pdev)
{
device_initialize(&pdev->dev);
return platform_device_add(pdev);
}
2、platdata
(1)platdata其實就是設備註冊時提供的設備有關的一些數據(譬如設備對應的gpio、使用到的中斷號、設備名稱····)
(2)這些數據在設備和驅動match之後,會由設備方轉給驅動方。驅動拿到這些數據後,通過這些數據得知設備的具體信息,然後來操作設備。
(3)這樣做的好處是:
驅動源碼中不攜帶數據,只負責算法(對硬件的操作方法)。現代驅動設計理念就是算法和數據分離,這樣最大程度保持驅動的獨立性和適應性。
3、probe函數的功能和意義
probe在設備驅動被註冊到內核中的時候,被總線型驅動調用。
總線驅動類似於用輪訓方法bai探測總線上的所有設備,將設備的識別型信息和關鍵數據結構 (pci ids, usb ids, i2c ids and etc.)傳遞給probe函數,probe就會識別是否是自己負責驅動的設備,並負責完成該設備的初始化操作。
三,使用platform平臺總線的LED驅動代碼實踐
#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit
#include <linux/fs.h>
#include <linux/leds.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <mach/gpio.h>
#include <linux/platform_device.h>
#include <mach/leds-gpio.h>
#include <linux/slab.h>
#define X210_LED_OFF 1 // X210中LED是正極接電源,負極節GPIO
#define X210_LED_ON 0 // 所以1是滅,0是亮
struct s5pv210_gpio_led {
struct led_classdev cdev;
struct s5pv210_led_platdata *pdata;
};
static inline struct s5pv210_gpio_led *pdev_to_gpio(struct platform_device *dev)
{
return platform_get_drvdata(dev);
}
static inline struct s5pv210_gpio_led *to_gpio(struct led_classdev *led_cdev)
{
return container_of(led_cdev, struct s5pv210_gpio_led, cdev);
}
// 這個函數就是要去完成具體的硬件讀寫任務的
static void s5pv210_led_set(struct led_classdev *led_cdev,
enum led_brightness value)
{
struct s5pv210_gpio_led *p = to_gpio(led_cdev);
printk(KERN_INFO "s5pv210_led_set\n");
// 在這裏根據用戶設置的值來操作硬件
// 用戶設置的值就是value
if (value == LED_OFF)
{
// 用戶給了個0,希望LED滅
gpio_set_value(p->pdata->gpio, X210_LED_OFF);
}
else
{
// 用戶給的是非0,希望LED亮
gpio_set_value(p->pdata->gpio, X210_LED_ON);
}
}
static int s5pv210_led_probe(struct platform_device *dev)
{
// 用戶insmod安裝驅動模塊時會調用該函數
// 該函數的主要任務就是去使用led驅動框架提供的設備註冊函數來註冊一個設備
int ret = -1;
struct s5pv210_led_platdata *pdata = dev->dev.platform_data;
struct s5pv210_gpio_led *led;
printk(KERN_INFO "----s5pv210_led_probe---\n");
led = kzalloc(sizeof(struct s5pv210_gpio_led), GFP_KERNEL);
if (led == NULL) {
dev_err(&dev->dev, "No memory for device\n");
return -ENOMEM;
}
platform_set_drvdata(dev, led);
// 在這裏去申請驅動用到的各種資源,當前驅動中就是GPIO資源
if (gpio_request(pdata->gpio, pdata->name))
{
printk(KERN_ERR "gpio_request failed\n");
}
else
{
// 設置爲輸出模式,並且默認輸出1讓LED燈滅
gpio_direction_output(pdata->gpio, 1);
}
// led1
led->cdev.name = pdata->name;
led->cdev.brightness = 0;
led->cdev.brightness_set = s5pv210_led_set;
led->pdata = pdata;
ret = led_classdev_register(&dev->dev, &led->cdev);
if (ret < 0) {
printk(KERN_ERR "led_classdev_register failed\n");
return ret;
}
return 0;
}
static int s5pv210_led_remove(struct platform_device *dev)
{
struct s5pv210_gpio_led *p = pdev_to_gpio(dev);
led_classdev_unregister(&p->cdev);
gpio_free(p->pdata->gpio);
kfree(p); // kfee放在最後一步
return 0;
}
static struct platform_driver s5pv210_led_driver = {
.probe = s5pv210_led_probe,
.remove = s5pv210_led_remove,
.driver = {
.name = "s5pv210_led",
.owner = THIS_MODULE,
},
};
static int __init s5pv210_led_init(void)
{
return platform_driver_register(&s5pv210_led_driver);
}
static void __exit s5pv210_led_exit(void)
{
platform_driver_unregister(&s5pv210_led_driver);
}
module_init(s5pv210_led_init);
module_exit(s5pv210_led_exit);
// MODULE_xxx這種宏作用是用來添加模塊描述信息
MODULE_LICENSE("GPL"); // 描述模塊的許可證
MODULE_AUTHOR("aston <[email protected]>"); // 描述模塊的作者
MODULE_DESCRIPTION("s5pv210 led driver"); // 描述模塊的介紹信息
MODULE_ALIAS("s5pv210_led"); // 描述模塊的別名信息