Linux驅動開發(7)------- platform平臺總線


一,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");					// 描述模塊的別名信息

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章