日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

platform驱动

發布時間:2024/3/26 编程问答 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 platform驱动 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

目錄

1.Linux驅動的分離與分層

1)驅動的分隔與分離

2)驅動的分層

2.platform平臺驅動模型簡介

1)platform總線

2)platform驅動

3)platform設備

3.試驗程序編寫

1)platform設備與驅動程序編寫

2)測試APP編寫

4.運行測試

1)編譯驅動程序和測試APP

2)運行測試

5.設備樹下的platform驅動簡介

6.實驗程序編寫

1)修改設備樹文件

2)platform驅動程序編寫

7.運行測試

1)編譯驅動程序和測試APP

2)運行測試

我們在前面幾章編寫的設備驅動都非常的簡單,都是對IO進行最簡單的讀寫操作。像I2C、SPI、

LCD等這些復雜外設的驅動就不能這么去寫了,Linux系統要考慮到驅動的可重用性,因此提出了

驅動的分離與分層這樣的軟件思路,在這個思路下誕生了我們將來最常打交道的platform設備驅

,也叫做平臺設備驅動。本章我們就來學習一下Linux下的驅動分離與分層,以及platform框架下

的設備驅動該如何編寫。

1.Linux驅動的分離與分層

1)驅動的分隔與分離

對于Linux這樣一個成熟、龐大、復雜的操作系統,代碼的重用性非常重要,否則的話就會在Linux

內核中存在大量無意義的重復代碼。尤其是驅動程序,因為驅動程序占用了Linux內核代碼量的大

頭,如果不對驅動程序加以管理,任由重復的代碼肆意增加,那么用不了多久Linux內核的文件數

量就龐大到無法接受的地步。假如現在有三個平臺A、B和C,這三個平臺(這里的平臺說的是SOC)

上都有MPU6050這個I2C接口的六軸傳感器,按照我們寫裸機I2C驅動的時候的思路,每個平臺都

有一個MPU6050的驅動,因此編寫出來的最簡單的驅動框架如圖所示:

從圖可以看出,每種平臺下都有一個主機驅動和設備驅動,主機驅動肯定是必須要的,畢竟不同的

平臺其I2C控制器不同。但是右側的設備驅動就沒必要每個平臺都寫一個,因為不管對于那個SOC

來說,MPU6050都是一樣,通過I2C接口讀寫數據就行了,只需要一個MPU6050的驅動程序即

。如果再來幾個I2C設備,比如AT24C02、FT5206(電容觸摸屏)等,如果按照圖1中的寫法,那

么設備端的驅動將會重復的編寫好幾次。顯然在Linux驅動程序中這種寫法是不推薦的,最好的做

法就是每個平臺的I2C控制器都提供一個統一的接口(也叫做主機驅動),每個設備的話也只提供一

個驅動程序(設備驅動),每個設備通過統一的I2C接口驅動來訪問,這樣就可以大大簡化驅動文

,比如圖中三種平臺下的MPU6050驅動框架就可以簡化為圖所示:

實際的I2C驅動設備肯定有很多種,不止MPU6050這一個,那么實際的驅動架構如圖所示:

這個就是驅動的分隔,也就是將主機驅動和設備驅動分隔開來,比如I2C、SPI等等都會采用驅動

分隔的方式來簡化驅動的開發。在實際的驅動開發中,一般I2C主機控制器驅動已經由半導體廠家

編寫好了,而設備驅動一般也由設備器件的廠家編寫好了,我們只需要提供設備信息即可,比如

I2C設備的話提供設備連接到了哪個I2C接口上,I2C的速度是多少等等。相當于將設備信息從設備

驅動中剝離開來,驅動使用標準方法去獲取到設備信息(比如從設備樹中獲取到設備信息),然后根

據獲取到的設備信息來初始化設備。這樣就相當于驅動只負責驅動,設備只負責設備,想辦法將兩

者進行匹配即可。這個就是Linux中的總線(bus)、驅動(driver)和設備(device)模型,也就是常說

的驅動分離。總線就是驅動和設備信息的月老,負責給兩者牽線搭橋,如圖所示:

當我們向系統注冊一個驅動的時候,總線就會在右側的設備中查找,看看有沒有與之匹配的設備,

如果有的話就將兩者聯系起來。同樣的,當向系統中注冊一個設備的時候,總線就會在左側的驅動

中查找看有沒有與之匹配的設備,有的話也聯系起來。Linux內核中大量的驅動程序都采用總線、

驅動和設備模式,我們一會要重點講解的platform驅動就是這一思想下的產物。

2)驅動的分層

上一小節講了驅動的分隔與分離,本節我們來簡單看一下驅動的分層,大家應該聽說過網絡的7層

模型,不同的層負責不同的內容。同樣的,Linux下的驅動往往也是分層的,分層的目的也是為了

在不同的層處理不同的內容。以其他書籍或者資料常常使用到的input(輸入子系統,后面會有專門

的章節詳細的講解)為例,簡單介紹一下驅動的分層。input子系統負責管理所有跟輸入有關的驅

動,包括鍵盤、鼠標、觸摸等,最底層的就是設備原始驅動,負責獲取輸入設備的原始值,獲取到

的輸入事件上報給input核心層。input核心層會處理各種IO模型,并且提供file_operations操作集

合。我們在編寫輸入設備驅動的時候只需要處理好輸入事件的上報即可,至于如何處理這些上報的

輸入事件那是上層去考慮的,我們不用管。可以看出借助分層模型可以極大的簡化我們的驅動編

寫,對于驅動編寫來說非常的友好。

2.platform平臺驅動模型簡介

前面我們講了設備驅動的分離,并且引出了總線(bus)、驅動(driver)和設備(device)模型,比如

I2C、SPI、USB等總線。但是在SOC中有些外設是沒有總線這個概念的,但是又要使用總線、驅

動和設備模型該怎么辦呢?為了解決此問題,Linux提出了platform這個虛擬總線,相應的就有

platform_driverplatform_device

1)platform總線

Linux系統內核使用bus_type結構體表示總線,此結構體定義在文件include/linux/device.h,

bus_type結構體內容如下:

1 struct bus_type { 2 const char *name; /* 總線名字 */ 3 const char *dev_name; 4 struct device *dev_root; 5 struct device_attribute *dev_attrs; 6 const struct attribute_group **bus_groups; /* 總線屬性 */ 7 const struct attribute_group **dev_groups; /* 設備屬性 */ 8 const struct attribute_group **drv_groups; /* 驅動屬性 */ 9 10 int (*match)(struct device *dev, struct device_driver *drv); 11 int (*uevent)(struct device *dev, struct kobj_uevent_env *env); 12 int (*probe)(struct device *dev); 13 int (*remove)(struct device *dev); 14 void (*shutdown)(struct device *dev); 15 16 int (*online)(struct device *dev); 17 int (*offline)(struct device *dev); 18 int (*suspend)(struct device *dev, pm_message_t state); 19 int (*resume)(struct device *dev); 20 const struct dev_pm_ops *pm; 21 const struct iommu_ops *iommu_ops; 22 struct subsys_private *p; 23 struct lock_class_key lock_key; 24 };

第10行,match函數,此函數很重要,單詞match的意思就是“匹配、相配”,因此此函數就是完成

設備和驅動之間匹配的,總線就是使用match函數來根據注冊的設備來查找對應的驅動,或者根據

注冊的驅動來查找相應的設備,因此每一條總線都必須實現此函數。match函數有兩個參數:dev

和drv,這兩個參數分別為device和device_driver類型,也就是設備和驅動。

platform總線是bus_type的一個具體實例,定義在文件drivers/base/platform.c,platform總線定義

如下:

1 struct bus_type platform_bus_type = { 2 .name = "platform", 3 .dev_groups = platform_dev_groups, 4 .match = platform_match, 5 .uevent = platform_uevent, 6 .pm = &platform_dev_pm_ops, 7 };

platform_bus_type就是platform平臺總線,其中platform_match就是匹配函數。我們來看一下驅

動和設備是如何匹配的,platform_match()函數定義在文件drivers/base/platform.c中,函數內容

如下所示:

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 /*When driver_override is set,only bind to the matching driver*/ 7 if (pdev->driver_override) 8 return !strcmp(pdev->driver_override, drv->name); 9 10 /* Attempt an OF style match first */ 11 if (of_driver_match_device(dev, drv)) 12 return 1; 13 14 /* Then try ACPI style match */ 15 if (acpi_driver_match_device(dev, drv)) 16 return 1; 17 18 /* Then try to match against the id table */ 19 if (pdrv->id_table) 20 return platform_match_id(pdrv->id_table, pdev) != NULL; 21 22 /* fall-back to driver name match */ 23 return (strcmp(pdev->name, drv->name) == 0); 24 }

驅動和設備的匹配有四種方法,我們依次來看一下:

第11~12行,第一種匹配方式,OF類型的匹配,也就是設備樹采用的匹配方式,

of_driver_match_device函數定義在文件include/linux/of_device.h中。device_driver結構體(表示設

備驅動)中有個名為of_match_table的成員變量,此成員變量保存著驅動的compatible匹配表,設備

樹中的每個設備節點的compatible屬性會和of_match_table表中的所有成員比較,查看是否有相同

的條目,如果有的話就表示設備和此驅動匹配,設備和驅動匹配成功以后probe函數就會執行。

第15~16行,第二種匹配方式,ACPI匹配方式。

第19~20行,第三種匹配方式,id_table匹配,每個platform_driver結構體有一個id_table成員變

量,顧名思義,保存了很多id信息。這些id信息存放著這個platformd驅動所支持的驅動類型。

第23行,第四種匹配方式,如果第三種匹配方式的id_table不存在的話就直接比較驅動和設備的

name字段,看看是不是相等,如果相等的話就匹配成功。

對于支持設備樹的Linux版本號,一般設備驅動為了兼容性都支持設備樹和無設備樹兩種匹配方

式。也就是第一種匹配方式一般都會存在,第三種和第四種只要存在一種就可以,一般用的最多的

還是第四種,也就是直接比較驅動和設備的name字段,畢竟這種方式最簡單了。

2)platform驅動

platform_driver結構體表示platform驅動,此結構體定義在文件include/linux/platform_device.h

中,內容如下:

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 bool prevent_deferred_probe; 10 };

第2行,probe函數,當驅動與設備匹配成功以后probe函數就會執行,非常重要的函數!!一般驅

動的提供者會編寫,如果自己要編寫一個全新的驅動,那么probe就需要自行實現。

第7行,driver成員,為device_driver結構體變量,Linux內核里面大量使用到了面向對象的思維,

device_driver相當于基類,提供了最基礎的驅動框架。plaform_driver繼承了這個基類,然后在此

基礎上又添加了一些特有的成員變量。

第8行,id_table表,也就是我們上一小節講解platform總線匹配驅動和設備的時候采用的第三種方

法,id_table是個表(也就是數組),每個元素的類型為platform_device_id,platform_device_id結構

體內容如下:

1 struct platform_device_id { 2 char name[PLATFORM_NAME_SIZE]; 3 kernel_ulong_t driver_data; 4 };

device_driver 結構體定義在 include/linux/device.h,device_driver結構體內容如下:

1 struct device_driver { 2 const char *name; 3 struct bus_type *bus; 4 5 struct module *owner; 6 const char *mod_name; /* used for built-in modules */ 7 8 bool suppress_bind_attrs; /* disables bind/unbind via sysfs */ 9 10 const struct of_device_id *of_match_table; 11 const struct acpi_device_id *acpi_match_table; 12 13 int (*probe) (struct device *dev); 14 int (*remove) (struct device *dev); 15 void (*shutdown) (struct device *dev); 16 int (*suspend) (struct device *dev, pm_message_t state); 17 int (*resume) (struct device *dev); 18 const struct attribute_group **groups; 19 20 const struct dev_pm_ops *pm; 21 22 struct driver_private *p; 23 };

第10行,of_match_table就是采用設備樹的時候驅動使用的匹配表,同樣是數組,每個匹配項都

為of_device_id結構體類型,此結構體定義在文件include/linux/mod_devicetable.h中,內容如下:

1 struct of_device_id { 2 char name[32]; 3 char type[32]; 4 char compatible[128]; 5 const void *data; 6 };

第4行的compatible非常重要,因為對于設備樹而言,就是通過設備節點的compatible屬性值和

of_match_table中每個項目的compatible成員變量進行比較,如果有相等的就表示設備和此驅動匹

配成功。

在編寫platform驅動的時候,首先定義一個platform_driver結構體變量,然后實現結構體中的各

個成員變量,重點是實現匹配方法以及probe函數。當驅動和設備匹配成功以后probe函數就會執

行,具體的驅動程序在probe函數里面編寫,比如字符設備驅動等等。當我們定義并初始化好

platform_driver結構體變量以后,需要在驅動入口函數里面調用platform_driver_register()函數

向Linux內核注冊一個platform驅動,platform_driver_register函數原型如下所示:

int platform_driver_register (struct platform_driver *driver)

函數參數和返回值含義如下:

driver:要注冊的platform驅動。

返回值:負數,失敗;0,成功。

還需要在驅動卸載函數中通過platform_driver_unregister函數卸載platform驅動,

platform_driver_unregister函數原型如下:

void platform_driver_unregister(struct platform_driver *drv)

函數參數和返回值含義如下:

drv:要卸載的platform驅動。

返回值:無。

platform驅動框架如下所示:

/* 設備結構體 */ 1 struct xxx_dev{ 2 struct cdev cdev; 3 /* 設備結構體其他具體內容 */ 4 }; 5 6 struct xxx_dev xxxdev; /* 定義個設備結構體變量 */ 7 8 static int xxx_open(struct inode *inode, struct file *filp) 9 { 10 /* 函數具體內容 */ 11 return 0; 12 } 13 14 static ssize_t xxx_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) 15 { 16 /* 函數具體內容 */ 17 return 0; 18 } 19 20 /* 21 * 字符設備驅動操作集 22 */ 23 static struct file_operations xxx_fops = { 24 .owner = THIS_MODULE, 25 .open = xxx_open, 26 .write = xxx_write, 27 }; 28 29 /* 30 * platform 驅動的 probe 函數 31 * 驅動與設備匹配成功以后此函數就會執行 32 */ 33 static int xxx_probe(struct platform_device *dev) 34 { 35 ...... 36 cdev_init(&xxxdev.cdev, &xxx_fops); /* 注冊字符設備驅動 */ 37 /* 函數具體內容 */ 38 return 0; 39 } 40 41 static int xxx_remove(struct platform_device *dev) 42 { 43 ...... 44 cdev_del(&xxxdev.cdev);/* 刪除 cdev */ 45 /* 函數具體內容 */ 46 return 0; 47 } 48 49 /* 匹配列表 */ 50 static const struct of_device_id xxx_of_match[] = { 51 { .compatible = "xxx-gpio" }, 52 { /* Sentinel */ } 53 }; 54 55 /* 56 * platform 平臺驅動結構體 57 */ 58 static struct platform_driver xxx_driver = { 59 .driver = { 60 .name = "xxx", 61 .of_match_table = xxx_of_match, 62 }, 63 .probe = xxx_probe, 64 .remove = xxx_remove, 65 }; 66 67 /* 驅動模塊加載 */ 68 static int __init xxxdriver_init(void) 69 { 70 return platform_driver_register(&xxx_driver); 71 } 72 73 /* 驅動模塊卸載 */ 74 static void __exit xxxdriver_exit(void) 75 { 76 platform_driver_unregister(&xxx_driver); 77 } 78 79 module_init(xxxdriver_init); 80 module_exit(xxxdriver_exit); 81 MODULE_LICENSE("GPL"); 82 MODULE_AUTHOR("zuozhongkai");

第1~27行,傳統的字符設備驅動,所謂的platform驅動并不是獨立于字符設備驅動、塊設備驅動和

網絡設備驅動之外的其他種類的驅動。platform只是為了驅動的分離與分層而提出來的一種框架,

其驅動的具體實現還是需要字符設備驅動、塊設備驅動或網絡設備驅動。

第33~39行,xxx_probe函數,當驅動和設備匹配成功以后此函數就會執行,以前在驅動入口init函

數里面編寫的字符設備驅動程序就全部放到此probe函數里面。比如注冊字符設備驅動、添加

cdev、創建類等等。

第41~47行,xxx_remove函數,platform_driver結構體中的remove成員變量,當關閉platfor備驅動

的時候此函數就會執行,以前在驅動卸載exit函數里面要做的事情就放到此函數中來。比如,使用

iounmap釋放內存、刪除cdev,注銷設備號等等。

第50~53行,xxx_of_match匹配表,如果使用設備樹的話將通過此匹配表進行驅動和設備的匹

第51行設置了一個匹配項,此匹配項的compatible值為“xxx-gpio”,因此當設備樹中設備節點的

compatible屬性值為“xxx-gpio”的時候此設備就會與此驅動匹配。第52行是一個標記,of_device_id

表最后一個匹配項必須是空的。

第58~65行,定義一個platform_driver結構體變量xxx_driver,表示platform驅動,第59~62行設置

paltform_driver中的device_driver成員變量的name和of_match_table這兩個屬性。其中name屬性

用于傳統的驅動與設備匹配,也就是檢查驅動和設備的name字段是不是相同。of_match_table屬

性就是用于設備樹下的驅動與設備檢查。對于一個完整的驅動程序,必須提供有設備樹和無設備樹

兩種匹配方法。最后63和64這兩行設置probe和remove這兩成員變量。

第68~71行,驅動入口函數,調用platform_driver_register函數向Linux內核注冊一個platform驅

動,也就是上面定義的xxx_driver結構體變量。

第74~77行,驅動出口函數,調用platform_driver_unregister函數卸載前面注冊的platform驅動。

總體來說,platform驅動還是傳統的字符設備驅動、塊設備驅動或網絡設備驅動,只是套上了一張

“platform”的皮,目的是為了使用總線、驅動和設備這個驅動模型來實現驅動的分離與分層

3)platform設備

platform驅動已經準備好了,我們還需要platform設備,否則的話單單一個驅動也做不了什么。

platform_device這個結構體表示platform設備,這里我們要注意,如果內核支持設備樹的話就不

要再使用platform_device來描述設備了,因為改用設備樹去描述了。當然了,你如果一定要用

platform_device來描述設備信息的話也是可以的。platform_device結構體定義在文件

include/linux/platform_device.h中,結構體內容如下:

22 struct platform_device { 23 const char *name; 24 int id; 25 bool id_auto; 26 struct device dev; 27 u32 num_resources; 28 struct resource *resource; 29 30 const struct platform_device_id *id_entry; 31 char *driver_override; /* Driver name to force a match */ 32 33 /* MFD cell pointer */ 34 struct mfd_cell *mfd_cell; 35 36 /* arch specific additions */ 37 struct pdev_archdata archdata; 38 };

第23行,name表示設備名字,要和所使用的platform驅動的name字段相同,否則的話設備就無法

匹配到對應的驅動。比如對應的platform驅動的name字段為“xxx-gpio”,那么此name字段也要設置

為“xxx-gpio”。

第27行,num_resources表示資源數量,一般為第28行resource資源的大小。

第28行,resource表示資源,也就是設備信息,比如外設寄存器等。Linux內核使用resource結構

體表示資源,resource結構體內容如下:

18 struct resource { 19 resource_size_t start; 20 resource_size_t end; 21 const char *name; 22 unsigned long flags; 23 struct resource *parent, *sibling, *child; 24 };

start和end分別表示資源的起始和終止信息,對于內存類的資源,就表示內存起始和終止地址,

name表示資源名字,flags表示資源類型,可選的資源類型都定義在了文件include/linux/ioport.h里

面,如下所示:

29 #define IORESOURCE_BITS 0x000000ff /* Bus-specific bits */ 30 31 #define IORESOURCE_TYPE_BITS 0x00001f00 /* Resource type */ 32 #define IORESOURCE_IO 0x00000100 /* PCI/ISA I/O ports */ 33 #define IORESOURCE_MEM 0x00000200 34 #define IORESOURCE_REG 0x00000300 /* Register offsets */ 35 #define IORESOURCE_IRQ 0x00000400 36 #define IORESOURCE_DMA 0x00000800 37 #define IORESOURCE_BUS 0x00001000 ...... 104 /* PCI control bits. Shares IORESOURCE_BITS with above PCI ROM. */ 105 #define IORESOURCE_PCI_FIXED (1<<4) /* Do not move resource */

在以前不支持設備樹的Linux版本中,用戶需要編寫platform_device變量來描述設備信息,然后使

platform_device_register函數將設備信息注冊到Linux內核中,此函數原型如下所示:

int platform_device_register(struct platform_device *pdev)

函數參數和返回值含義如下:

pdev:要注冊的platform設備。

返回值:負數,失敗;0,成功。

如果不再使用platform的話可以通過platform_device_unregister函數注銷掉相應的platform設

備,platform_device_unregister函數原型如下:

void platform_device_unregister(struct platform_device *pdev)

函數參數和返回值含義如下:

pdev:要注銷的platform設備。

返回值:無。

platform設備信息框架如下所示:

1 /* 寄存器地址定義*/ 2 #define PERIPH1_REGISTER_BASE (0X20000000) /* 外設 1 寄存器首地址 */ 3 #define PERIPH2_REGISTER_BASE (0X020E0068) /* 外設 2 寄存器首地址 */ 4 #define REGISTER_LENGTH 4 5 6 /* 資源 */ 7 static struct resource xxx_resources[] = { 8 [0] = { 9 .start = PERIPH1_REGISTER_BASE, 10 .end = (PERIPH1_REGISTER_BASE + REGISTER_LENGTH - 1), 11 .flags = IORESOURCE_MEM, 12 }, 13 [1] = { 14 .start = PERIPH2_REGISTER_BASE, 15 .end = (PERIPH2_REGISTER_BASE + REGISTER_LENGTH - 1), 16 .flags = IORESOURCE_MEM, 17 }, 18 }; 19 20 /* platform 設備結構體 */ 21 static struct platform_device xxxdevice = { 22 .name = "xxx-gpio", 23 .id = -1, 24 .num_resources = ARRAY_SIZE(xxx_resources), 25 .resource = xxx_resources, 26 }; 27 28 /* 設備模塊加載 */ 29 static int __init xxxdevice_init(void) 30 { 31 return platform_device_register(&xxxdevice); 32 } 33 34 /* 設備模塊注銷 */ 35 static void __exit xxx_resourcesdevice_exit(void) 36 { 37 platform_device_unregister(&xxxdevice); 38 } 39 40 module_init(xxxdevice_init); 41 module_exit(xxxdevice_exit); 42 MODULE_LICENSE("GPL"); 43 MODULE_AUTHOR("zuozhongkai");

第7~18行,數組xxx_resources表示設備資源,一共有兩個資源,分別為設備外設1和外設2的寄存

器信息。因此flags都為IORESOURCE_MEM,表示資源為內存類型的。

第21~26行,platform設備結構體變量,注意name字段要和所使用的驅動中的name字段一致,否

則驅動和設備無法匹配成功。num_resources表示資源大小,其實就是數組xxx_resources的元素

數量,這里用ARRAY_SIZE來測量一個數組的元素個數。

第29~32行,設備模塊加載函數,在此函數中調用platform_device_register向Linux內核注冊

platform設備。

第35~38行,設備模塊卸載函數,在此函數中調用platform_device_unregister從Linux內核中卸載

platform設備。

以上代碼主要是在不支持設備樹的Linux版本中使用的,當Linux內核支持了設備樹以后就不需要用

戶手動去注冊platform設備了。因為設備信息都放到了設備樹中去描述,Linux內核啟動的時候會從

設備樹中讀取設備信息,然后將其組織成platform_device形式,至于設備樹到platform_device的具

體過程就不去詳細的追究了,感興趣的可以去看一下,網上也有很多博客詳細的講解了整個過程。

關于platform下的總線、驅動和設備就講解到這里,我們接下來就使用platform驅動框架來編寫一

個LED燈驅動,本章我們不使用設備樹來描述設備信息,我們采用自定義platform_device這種“古

老”方式來編寫LED的設備信息。下一章我們來編寫設備樹下的platform驅動,這樣我們就掌握了無

設備樹和有設備樹這兩種platform驅動的開發方式。

3.試驗程序編寫

本章實驗我們需要編寫一個驅動模塊和一個設備模塊,其中驅動模塊是platform驅動程序,設備模

塊是platform的設備信息。當這兩個模塊都加載成功以后就會匹配成功,然后platform驅動模塊中

的probe函數就會執行,probe函數中就是傳統的字符設備驅動那一套。

1)platform設備與驅動程序編寫

新建名為“17_platform”的文件夾,然后在17_platform文件夾里面創建vscode工程,工作區命名為

“platform”。新建名為leddevice.c和leddriver.c這兩個文件,這兩個文件分別為LED燈的platform設

備文件和LED燈的platform的驅動文件。在leddevice.c中輸入如下所示內容:

1 #include <linux/types.h> 2 #include <linux/kernel.h> 3 #include <linux/delay.h> 4 #include <linux/ide.h> 5 #include <linux/init.h> 6 #include <linux/module.h> 7 #include <linux/errno.h> 8 #include <linux/gpio.h> 9 #include <linux/cdev.h> 10 #include <linux/device.h> 11 #include <linux/of_gpio.h> 12 #include <linux/semaphore.h> 13 #include <linux/timer.h> 14 #include <linux/irq.h> 15 #include <linux/wait.h> 16 #include <linux/poll.h> 17 #include <linux/fs.h> 18 #include <linux/fcntl.h> 19 #include <linux/platform_device.h> 20 #include <asm/mach/map.h> 21 #include <asm/uaccess.h> 22 #include <asm/io.h> 23 /*************************************************************** 24 Copyright ? ALIENTEK Co., Ltd. 1998-2029. All rights reserved. 25 文件名 : leddevice.c 26 作者 : 左忠凱 27 版本 : V1.0 28 描述 : platform 設備 29 其他 : 無 30 論壇 : www.openedv.com 31 日志 : 初版 V1.0 2019/8/13 左忠凱創建 32 ***************************************************************/ 33 34 /* 35 * 寄存器地址定義 36 */ 37 #define CCM_CCGR1_BASE (0X020C406C) 38 #define SW_MUX_GPIO1_IO03_BASE (0X020E0068) 39 #define SW_PAD_GPIO1_IO03_BASE (0X020E02F4) 40 #define GPIO1_DR_BASE (0X0209C000) 41 #define GPIO1_GDIR_BASE (0X0209C004) 42 #define REGISTER_LENGTH 4 43 44 /* @description : 釋放 flatform 設備模塊的時候此函數會執行 45 * @param - dev : 要釋放的設備 46 * @return : 無 47 */ 48 static void led_release(struct device *dev) 49 { 50 printk("led device released!\r\n"); 51 } 52 53 /* 54 * 設備資源信息,也就是 LED0 所使用的所有寄存器 55 */ 56 static struct resource led_resources[] = { 57 [0] = { 58 .start = CCM_CCGR1_BASE, 59 .end = (CCM_CCGR1_BASE + REGISTER_LENGTH - 1), 60 .flags = IORESOURCE_MEM, 61 }, 62 [1] = { 63 .start = SW_MUX_GPIO1_IO03_BASE, 64 .end = (SW_MUX_GPIO1_IO03_BASE + REGISTER_LENGTH - 1), 65 .flags = IORESOURCE_MEM, 66 }, 67 [2] = { 68 .start = SW_PAD_GPIO1_IO03_BASE, 69 .end = (SW_PAD_GPIO1_IO03_BASE + REGISTER_LENGTH - 1), 70 .flags = IORESOURCE_MEM, 71 }, 72 [3] = { 73 .start = GPIO1_DR_BASE, 74 .end = (GPIO1_DR_BASE + REGISTER_LENGTH - 1), 75 .flags = IORESOURCE_MEM, 76 }, 77 [4] = { 78 .start = GPIO1_GDIR_BASE, 79 .end = (GPIO1_GDIR_BASE + REGISTER_LENGTH - 1), 80 .flags = IORESOURCE_MEM, 81 }, 82 }; 83 84 85 /* 86 * platform 設備結構體 87 */ 88 static struct platform_device leddevice = { 89 .name = "imx6ul-led", 90 .id = -1, 91 .dev = { 92 .release = &led_release, 93 }, 94 .num_resources = ARRAY_SIZE(led_resources), 95 .resource = led_resources, 96 }; 97 98 /* 99 * @description : 設備模塊加載 100 * @param : 無 101 * @return : 無 102 */ 103 static int __init leddevice_init(void) 104 { 105 return platform_device_register(&leddevice); 106 } 107 108 /* 109 * @description : 設備模塊注銷 110 * @param : 無 111 * @return : 無 112 */ 113 static void __exit leddevice_exit(void) 114 { 115 platform_device_unregister(&leddevice); 116 } 117 118 module_init(leddevice_init); 119 module_exit(leddevice_exit); 120 MODULE_LICENSE("GPL"); 121 MODULE_AUTHOR("zuozhongkai");

leddevice.c文件內容就是按照之前的示例代碼的platform設備模板編寫的。

第56~82行,led_resources數組,也就是設備資源,描述了LED所要使用到的寄存器信息,也就是

IORESOURCE_MEM資源。

第88~96,platform設備結構體變量leddevice,這里要注意name字段為“imx6ul-led”,所以稍后編

寫platform驅動中的name字段也要為“imx6ul-led”,否則設備和驅動匹配失敗。

第103~106行,設備模塊加載函數,在此函數里面通過platform_device_register向Linux內核注冊

leddevice這個platform設備。

第113~116行,設備模塊卸載函數,在此函數里面通過platform_device_unregister從Linux內核中

刪除掉leddevice這個platform設備。

leddevice.c文件編寫完成以后就編寫leddriver.c這個platform驅動文件,在leddriver.c里面輸入如下

內容:

1 #include <linux/types.h> 2 #include <linux/kernel.h> 3 #include <linux/delay.h> 4 #include <linux/ide.h> 5 #include <linux/init.h> 6 #include <linux/module.h> 7 #include <linux/errno.h> 8 #include <linux/gpio.h> 9 #include <linux/cdev.h> 10 #include <linux/device.h> 11 #include <linux/of_gpio.h> 12 #include <linux/semaphore.h> 13 #include <linux/timer.h> 14 #include <linux/irq.h> 15 #include <linux/wait.h> 16 #include <linux/poll.h> 17 #include <linux/fs.h> 18 #include <linux/fcntl.h> 19 #include <linux/platform_device.h> 20 #include <asm/mach/map.h> 21 #include <asm/uaccess.h> 22 #include <asm/io.h> 23 /*************************************************************** 24 Copyright ? ALIENTEK Co., Ltd. 1998-2029. All rights reserved. 25 文件名 : leddriver.c 26 作者 : 左忠凱 27 版本 : V1.0 28 描述 : platform 驅動 29 其他 : 無 30 論壇 : www.openedv.com 31 日志 : 初版 V1.0 2019/8/13 左忠凱創建 32 ***************************************************************/ 33 34 #define LEDDEV_CNT 1 /* 設備號長度 */ 35 #define LEDDEV_NAME "platled" /* 設備名字 */ 36 #define LEDOFF 0 37 #define LEDON 1 38 39 /* 寄存器名 */ 40 static void __iomem *IMX6U_CCM_CCGR1; 41 static void __iomem *SW_MUX_GPIO1_IO03; 42 static void __iomem *SW_PAD_GPIO1_IO03; 43 static void __iomem *GPIO1_DR; 44 static void __iomem *GPIO1_GDIR; 45 46 /* leddev 設備結構體 */ 47 struct leddev_dev{ 48 dev_t devid; /* 設備號 */ 49 struct cdev cdev; /* cdev */ 50 struct class *class; /* 類 */ 51 struct device *device; /* 設備 */ 52 int major; /* 主設備號 */ 53 }; 54 55 struct leddev_dev leddev; /* led 設備 */ 56 57 /* 58 * @description : LED 打開/關閉 59 * @param - sta : LEDON(0) 打開 LED,LEDOFF(1) 關閉 LED 60 * @return : 無 61 */ 62 void led0_switch(u8 sta) 63 { 64 u32 val = 0; 65 if(sta == LEDON){ 66 val = readl(GPIO1_DR); 67 val &= ~(1 << 3); 68 writel(val, GPIO1_DR); 69 }else if(sta == LEDOFF){ 70 val = readl(GPIO1_DR); 71 val|= (1 << 3); 72 writel(val, GPIO1_DR); 73 } 74 } 75 76 /* 77 * @description : 打開設備 78 * @param – inode : 傳遞給驅動的 inode 79 * @param - filp : 設備文件,file 結構體有個叫做 private_data 的成員變量 80 * 一般在 open 的時候將 private_data 指向設備結構體。 81 * @return : 0 成功;其他 失敗 82 */ 83 static int led_open(struct inode *inode, struct file *filp) 84 { 85 filp->private_data = &leddev; /* 設置私有數據 */ 86 return 0; 87 } 88 89 /* 90 * @description : 向設備寫數據 91 * @param – filp : 設備文件,表示打開的文件描述符 92 * @param - buf : 要寫給設備寫入的數據 93 * @param - cnt : 要寫入的數據長度 94 * @param - offt : 相對于文件首地址的偏移 95 * @return : 寫入的字節數,如果為負值,表示寫入失敗 96 */ 97 static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) 98 { 99 int retvalue; 100 unsigned char databuf[1]; 101 unsigned char ledstat; 102 103 retvalue = copy_from_user(databuf, buf, cnt); 104 if(retvalue < 0) { 105 return -EFAULT; 106 } 107 108 ledstat = databuf[0]; /* 獲取狀態值 */ 109 if(ledstat == LEDON) { 110 led0_switch(LEDON); /* 打開 LED 燈 */ 111 }else if(ledstat == LEDOFF) { 112 led0_switch(LEDOFF); /* 關閉 LED 燈 */ 113 } 114 return 0; 115 } 116 117 /* 設備操作函數 */ 118 static struct file_operations led_fops = { 119 .owner = THIS_MODULE, 120 .open = led_open, 121 .write = led_write, 122 }; 123 124 /* 125 * @description : flatform 驅動的 probe 函數,當驅動與設備 126 * 匹配以后此函數就會執行 127 * @param - dev : platform 設備 128 * @return : 0,成功;其他負值,失敗 129 */ 130 static int led_probe(struct platform_device *dev) 131 { 132 int i = 0; 133 int ressize[5]; 134 u32 val = 0; 135 struct resource *ledsource[5]; 136 137 printk("led driver and device has matched!\r\n"); 138 /* 1、獲取資源 */ 139 for (i = 0; i < 5; i++) { 140 ledsource[i] = platform_get_resource(dev, IORESOURCE_MEM, i); 141 if (!ledsource[i]) { 142 dev_err(&dev->dev, "No MEM resource for always on\n"); 143 return -ENXIO; 144 } 145 ressize[i] = resource_size(ledsource[i]); 146 } 147 148 /* 2、初始化 LED */ 149 /* 寄存器地址映射 */ 150 IMX6U_CCM_CCGR1 = ioremap(ledsource[0]->start, ressize[0]); 151 SW_MUX_GPIO1_IO03 = ioremap(ledsource[1]->start, ressize[1]); 152 SW_PAD_GPIO1_IO03 = ioremap(ledsource[2]->start, ressize[2]); 153 GPIO1_DR = ioremap(ledsource[3]->start, ressize[3]); 154 GPIO1_GDIR = ioremap(ledsource[4]->start, ressize[4]); 155 156 val = readl(IMX6U_CCM_CCGR1); 157 val &= ~(3 << 26); /* 清除以前的設置 */ 158 val |= (3 << 26); /* 設置新值 */ 159 writel(val, IMX6U_CCM_CCGR1); 160 161 /* 設置 GPIO1_IO03 復用功能,將其復用為 GPIO1_IO03 */ 162 writel(5, SW_MUX_GPIO1_IO03); 163 writel(0x10B0, SW_PAD_GPIO1_IO03); 164 165 /* 設置 GPIO1_IO03 為輸出功能 */ 166 val = readl(GPIO1_GDIR); 167 val &= ~(1 << 3); /* 清除以前的設置 */ 168 val |= (1 << 3); /* 設置為輸出 */ 169 writel(val, GPIO1_GDIR); 170 171 /* 默認關閉 LED1 */ 172 val = readl(GPIO1_DR); 173 val |= (1 << 3) ; 174 writel(val, GPIO1_DR); 175 176 /* 注冊字符設備驅動 */ 177 /*1、創建設備號 */ 178 if (leddev.major) { /* 定義了設備號 */ 179 leddev.devid = MKDEV(leddev.major, 0); 180 register_chrdev_region(leddev.devid, LEDDEV_CNT,LEDDEV_NAME); 181 } else { /* 沒有定義設備號 */ 182 alloc_chrdev_region(&leddev.devid, 0, LEDDEV_CNT,LEDDEV_NAME); 183 leddev.major = MAJOR(leddev.devid); 184 } 185 186 /* 2、初始化 cdev */ 187 leddev.cdev.owner = THIS_MODULE; 188 cdev_init(&leddev.cdev, &led_fops); 189 190 /* 3、添加一個 cdev */ 191 cdev_add(&leddev.cdev, leddev.devid, LEDDEV_CNT); 192 193 /* 4、創建類 */ 194 leddev.class = class_create(THIS_MODULE, LEDDEV_NAME); 195 if (IS_ERR(leddev.class)) { 196 return PTR_ERR(leddev.class); 197 } 198 199 /* 5、創建設備 */ 200 leddev.device = device_create(leddev.class, NULL, leddev.devid,NULL, LEDDEV_NAME); 201 if (IS_ERR(leddev.device)) { 202 return PTR_ERR(leddev.device); 203 } 204 205 return 0; 206 } 207 208 /* 209 * @description :移除 platform 驅動的時候此函數會執行 210 * @param - dev : platform 設備 211 * @return : 0,成功;其他負值,失敗 212 */ 213 static int led_remove(struct platform_device *dev) 214 { 215 iounmap(IMX6U_CCM_CCGR1); 216 iounmap(SW_MUX_GPIO1_IO03); 217 iounmap(SW_PAD_GPIO1_IO03); 218 iounmap(GPIO1_DR); 219 iounmap(GPIO1_GDIR); 220 221 cdev_del(&leddev.cdev); /* 刪除 cdev */ 222 unregister_chrdev_region(leddev.devid, LEDDEV_CNT); 223 device_destroy(leddev.class, leddev.devid); 224 class_destroy(leddev.class); 225 return 0; 226 } 227 228 /* platform 驅動結構體 */ 229 static struct platform_driver led_driver = { 230 .driver = { 231 .name = "imx6ul-led", /* 驅動名字,用于和設備匹配 */ 232 }, 233 .probe = led_probe, 234 .remove = led_remove, 235 }; 236 237 /* 238 * @description : 驅動模塊加載函數 239 * @param : 無 240 * @return : 無 241 */ 242 static int __init leddriver_init(void) 243 { 244 return platform_driver_register(&led_driver); 245 } 246 247 /* 248 * @description : 驅動模塊卸載函數 249 * @param : 無 250 * @return : 無 251 */ 252 static void __exit leddriver_exit(void) 253 { 254 platform_driver_unregister(&led_driver); 255 } 256 257 module_init(leddriver_init); 258 module_exit(leddriver_exit); 259 MODULE_LICENSE("GPL"); 260 MODULE_AUTHOR("zuozhongkai");

leddriver.c文件內容就是按照之前示例的platform驅動模板編寫的。

第34~122行,傳統的字符設備驅動。

第130~206行,probe函數,當設備和驅動匹配以后此函數就會執行,當匹配成功以后會在終端上

輸出“leddriveranddevicehasmatched!”這樣語句。在probe函數里面初始化LED、注冊字符設備驅

動。也就是將原來在驅動加載函數里面做的工作全部放到probe函數里面完成。

第213~226行,remove函數,當卸載platform驅動的時候此函數就會執行。在此函數里面釋放內

存、注銷字符設備等。也就是將原來驅動卸載函數里面的工作全部都放到remove函數中完成。

第229~235行,platform_driver驅動結構體,注意name字段為"imx6ul-led",和我們在leddevice.c

文件里面設置的設備name字段一致。第242~245行,驅動模塊加載函數,在此函數里面通過

platform_driver_register向Linux內核注冊led_driver驅動。

第252~255行,驅動模塊卸載函數,在此函數里面通過platform_driver_unregister從Linux內核卸載

led_driver驅動。

2)測試APP編寫

測試APP的內容很簡單,就是打開和關閉LED燈,新建ledApp.c這個文件,然后在里面輸入如下內

容:

1 #include "stdio.h" 2 #include "unistd.h" 3 #include "sys/types.h" 4 #include "sys/stat.h" 5 #include "fcntl.h" 6 #include "stdlib.h" 7 #include "string.h" 8 /*************************************************************** 9 Copyright ? ALIENTEK Co., Ltd. 1998-2029. All rights reserved. 10 文件名 : ledApp.c 11 作者 : 左忠凱 12 版本 : V1.0 13 描述 : platform 驅動驅測試 APP。 14 其他 : 無 15 使用方法 :./ledApp /dev/platled 0 關閉 LED 16 ./ledApp /dev/platled 1 打開 LED 17 論壇 : www.openedv.com 18 日志 : 初版 V1.0 2019/8/16 左忠凱創建 19 ***************************************************************/ 20 #define LEDOFF 0 21 #define LEDON 1 22 23 /* 24 * @description : main 主程序 25 * @param - argc : argv 數組元素個數 26 * @param - argv : 具體參數 27 * @return : 0 成功;其他 失敗 28 */ 29 int main(int argc, char *argv[]) 30 { 31 int fd, retvalue; 32 char *filename; 33 unsigned char databuf[2]; 34 35 if(argc != 3){ 36 printf("Error Usage!\r\n"); 37 return -1; 38 } 39 40 filename = argv[1]; 41 /* 打開 led 驅動 */ 42 fd = open(filename, O_RDWR); 43 if(fd < 0){ 44 printf("file %s open failed!\r\n", argv[1]); 45 return -1; 46 } 47 48 databuf[0] = atoi(argv[2]); /* 要執行的操作:打開或關閉 */ 49 retvalue = write(fd, databuf, sizeof(databuf)); 50 if(retvalue < 0){ 51 printf("LED Control Failed!\r\n"); 52 close(fd); 53 return -1; 54 } 55 56 retvalue = close(fd); /* 關閉文件 */ 57 if(retvalue < 0){ 58 printf("file %s close failed!\r\n", argv[1]); 59 return -1; 60 } 61 return 0; 62 }

ledApp.c文件內容很簡單,就是控制LED燈的亮滅,和第四十一章的測試APP基本一致,這里就不

重復講解了。

4.運行測試

1)編譯驅動程序和測試APP

1、編譯驅動程序

編寫Makefile文件,本章實驗的Makefile文件和之前實驗基本一樣,只是將obj-m變量的值改為

“leddevice.oleddriver.o”,Makefile內容如下所示:

1 KERNELDIR := /home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imx- rel_imx_4.1.15_2.1.0_ga_alientek ...... 4 obj-m := leddevice.o leddriver.o ...... 11 clean: 12 $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

第4行,設置obj-m變量的值為“leddevice.oleddriver.o”。

輸入如下命令編譯出驅動模塊文件:

make -j32

編譯成功以后就會生成一個名為“leddevice.koleddriver.ko”的驅動模塊文件。

2、編譯測試APP

輸入如下命令編譯測試ledApp.c這個測試程序:

arm-linux-gnueabihf-gcc ledApp.c -o ledApp

編譯成功以后就會生成ledApp這個應用程序。

2)運行測試

將上一小節編譯出來leddevice.ko、leddriver.ko和ledApp這兩個文件拷貝到

rootfs/lib/modules/4.1.15目錄中,重啟開發板,進入到目錄lib/modules/4.1.15中,輸入如下命令加

載leddevice.ko設備模塊和leddriver.ko這個驅動模塊。

depmod //第一次加載驅動的時候需要運行此命令 modprobe leddevice.ko //加載設備模塊 modprobe leddriver.ko //加載驅動模塊

根文件系統中/sys/bus/platform/目錄下保存著當前板子platform總線下的設備和驅動,其中devices

子目錄為platform設備,drivers子目錄為plartofm驅動。查看/sys/bus/platform/devices/目錄,看看

我們的設備是否存在,我們在leddevice.c中設置leddevice(platform_device類型)的name字段為

“imx6ul-led”,也就是設備名字為imx6ul-led,因此肯定在/sys/bus/platform/devices/目錄下存在一

個名字“imx6ul-led”的文件,否則說明我們的設備模塊加載失敗,結果如圖所示:

同理,查看/sys/bus/platform/drivers/目錄,看一下驅動是否存在,我們在leddriver.c中設置

led_driver(platform_driver類型)的name字段為“imx6ul-led”,因此會在/sys/bus/platform/drivers/目

錄下存在名為“imx6ul-led”這個文件,結果如圖所示:

驅動模塊和設備模塊加載成功以后platform總線就會進行匹配,當驅動和設備匹配成功以后就會輸

出如圖所示一行語句:

驅動和設備匹配成功以后就可以測試LED燈驅動了,輸入如下命令打開LED燈:

./ledApp /dev/platled 1 //打開 LED 燈

在輸入如下命令關閉LED燈:

./ledApp /dev/platled 0 //關閉 LED 燈

觀察一下LED燈能否打開和關閉,如果可以的話就說明驅動工作正常,如果要卸載驅動的話輸入如

下命令即可:

rmmod leddevice.ko rmmod leddriver.ko

上一章我們詳細的講解了Linux下的驅動分離與分層,以及總線、設備和驅動這樣的驅動框架。基

于總線、設備和驅動這樣的驅動框架,Linux內核提出來platform這個虛擬總線,相應的也有

platform設備和platform驅動。上一章我們講解了傳統的、未采用設備樹的platform設備和驅動編寫

方法。最新的Linux內核已經支持了設備樹,因此在設備樹下如何編寫platform驅動就顯得尤為重

要,本章我們就來學習一下如何在設備樹下編寫platform驅動。

5.設備樹下的platform驅動簡介

platform驅動框架分為總線、設備和驅動,其中總線不需要我們這些驅動程序員去管理,這個是

Linux內核提供的,我們在編寫驅動的時候只要關注于設備和驅動的具體實現即可。在沒有設備樹

的Linux內核下,我們需要分別編寫并注冊platform_device和platform_driver,分別代表設備和驅

動。在使用設備樹的時候,設備的描述被放到了設備樹中,因此platform_device就不需要我們去

編寫了,我們只需要實現platform_driver即可。在編寫基于設備樹的platform驅動的時候我們需要

注意一下幾點:

1、在設備樹中創建設備節點

毫無疑問,肯定要先在設備樹中創建設備節點來描述設備信息,重點是要設置好compatible屬性的

值,因為platform總線需要通過設備節點的compatible屬性值來匹配驅動!這點要切記。比如,

我們可以編寫如下所示的設備節點來描述我們本章實驗要用到的LED這個設備:

1 gpioled { 2 #address-cells = <1>; 3 #size-cells = <1>; 4 compatible = "atkalpha-gpioled"; 5 pinctrl-names = "default"; 6 pinctrl-0 = <&pinctrl_led>; 7 led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>; 8 status = "okay"; 9 };

以上示例中的gpioled節點其實就是之前中創建的gpioled設備節點,我們可以直接拿過來用。注意

第4行的compatible屬性值為“atkalpha-gpioled”,因此一會在編寫platform驅動的時候

of_match_table屬性表中要有“atkalpha-gpioled”。

2、編寫platform驅動的時候要注意兼容屬性

上一章已經詳細的講解過了,在使用設備樹的時候platform驅動會通過of_match_table來保存兼容

性值,也就是表明此驅動兼容哪些設備。所以,of_match_table將會尤為重要,比如本例程的

platform驅動中platform_driver就可以按照如下所示設置:

1 static const struct of_device_id leds_of_match[] = { 2 { .compatible = "atkalpha-gpioled" }, /* 兼容屬性 */ 3 { /* Sentinel */ } 4 }; 5 6 MODULE_DEVICE_TABLE(of, leds_of_match); 7 8 static struct platform_driver leds_platform_driver = { 9 .driver = { 10 .name = "imx6ul-led", 11 .of_match_table = leds_of_match, 12 }, 13 .probe = leds_probe, 14 .remove = leds_remove, 15 };

第1~4行,of_device_id表,也就是驅動的兼容表,是一個數組,每個數組元素為of_device_id類

型。每個數組元素都是一個兼容屬性,表示兼容的設備,一個驅動可以跟多個設備匹配。這里我們

僅僅匹配了一個設備,那就是之前中創建的gpioled這個設備。第2行的compatible值為“atkalpha-

gpioled”,驅動中的compatible屬性和設備中的compatible屬性相匹配,因此驅動中對應的probe函

數就會執行。注意第3行是一個空元素,在編寫of_device_id的時候最后一個元素一定要為空

第6行,通過MODULE_DEVICE_TABLE聲明一下leds_of_match這個設備匹配表

第11行,設置platform_driver中的of_match_table匹配表為上面創建的leds_of_match,至此我們就

設置好了platform驅動的匹配表了。

3、編寫platform驅動

基于設備樹的platform驅動和上一章無設備樹的platform驅動基本一樣,都是當驅動和設備匹配成

功以后就會執行probe函數。我們需要在probe函數里面執行字符設備驅動那一套,當注銷驅動模

塊的時候remove函數就會執行,都是大同小異的。

6.實驗程序編寫

本章實驗我們編寫基于設備樹的platform驅動,所以需要在設備樹中添加設備節點,然后我們只需

要編寫platform驅動即可。

1)修改設備樹文件

首先修改設備樹文件,加上我們需要的設備信息,本章我們就使用到一個LED燈,因此可以直接使

用之前編寫的gpioled子節點即可,不需要再重復添加。

2)platform驅動程序編寫

新建名為leddriver.c的驅動文件,在leddriver.c中輸入如下所示內容:

1 #include <linux/types.h> 2 #include <linux/kernel.h> 3 #include <linux/delay.h> 4 #include <linux/ide.h> 5 #include <linux/init.h> 6 #include <linux/module.h> 7 #include <linux/errno.h> 8 #include <linux/gpio.h> 9 #include <linux/cdev.h> 10 #include <linux/device.h> 11 #include <linux/of_gpio.h> 12 #include <linux/semaphore.h> 13 #include <linux/timer.h> 14 #include <linux/irq.h> 15 #include <linux/wait.h> 16 #include <linux/poll.h> 17 #include <linux/fs.h> 18 #include <linux/fcntl.h> 19 #include <linux/platform_device.h> 20 #include <asm/mach/map.h> 21 #include <asm/uaccess.h> 22 #include <asm/io.h> 23 /*************************************************************** 24 Copyright ? ALIENTEK Co., Ltd. 1998-2029. All rights reserved. 25 文件名 : leddriver.c 26 作者 : 左忠凱 27 版本 : V1.0 28 描述 : 設備樹下的 platform 驅動 29 其他 : 無 30 論壇 : www.openedv.com 31 日志 : 初版 V1.0 2019/8/13 左忠凱創建 32 ***************************************************************/ 33 #define LEDDEV_CNT 1 /* 設備號長度 */ 34 #define LEDDEV_NAME "dtsplatled" /* 設備名字 */ 35 #define LEDOFF 0 36 #define LEDON 1 37 38 /* leddev 設備結構體 */ 39 struct leddev_dev{ 40 dev_t devid; /* 設備號 */ 41 struct cdev cdev; /* cdev */ 42 struct class *class; /* 類 */ 43 struct device *device; /* 設備 */ 44 int major; /* 主設備號 */ 45 struct device_node *node; /* LED 設備節點 */ 46 int led0; /* LED 燈 GPIO 標號 */ 47 }; 48 49 struct leddev_dev leddev; /* led 設備 */ 51 /* 52 * @description : LED 打開/關閉 53 * @param - sta : LEDON(0) 打開 LED,LEDOFF(1) 關閉 LED 54 * @return : 無 55 */ 56 void led0_switch(u8 sta) 57 { 58 if (sta == LEDON ) 59 gpio_set_value(leddev.led0, 0); 60 else if (sta == LEDOFF) 61 gpio_set_value(leddev.led0, 1); 62 } 63 64 /* 65 * @description : 打開設備 66 * @param – inode : 傳遞給驅動的 inode 67 * @param - filp : 設備文件,file 結構體有個叫做 private_data 的成員變量 68 * 一般在 open 的時候將 private_data 指向設備結構體。 69 * @return : 0 成功;其他 失敗 70 */ 71 static int led_open(struct inode *inode, struct file *filp) 72 { 73 filp->private_data = &leddev; /* 設置私有數據 */ 74 return 0; 75 } 76 77 /* 78 * @description : 向設備寫數據 79 * @param - filp : 設備文件,表示打開的文件描述符 80 * @param - buf : 要寫給設備寫入的數據 81 * @param - cnt : 要寫入的數據長度 82 * @param – offt : 相對于文件首地址的偏移 83 * @return : 寫入的字節數,如果為負值,表示寫入失敗 84 */ 85 static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) 86 { 87 int retvalue; 88 unsigned char databuf[2]; 89 unsigned char ledstat; 90 91 retvalue = copy_from_user(databuf, buf, cnt); 92 if(retvalue < 0) { 93 94 printk("kernel write failed!\r\n"); 95 return -EFAULT; 96 } 97 98 ledstat = databuf[0]; 99 if (ledstat == LEDON) { 100 led0_switch(LEDON); 101 } else if (ledstat == LEDOFF) { 102 led0_switch(LEDOFF); 103 } 104 return 0; 105 } 106 107 /* 設備操作函數 */ 108 static struct file_operations led_fops = { 109 .owner = THIS_MODULE, 110 .open = led_open, 111 .write = led_write, 112 }; 113 114 /* 115 * @description : flatform 驅動的 probe 函數,當驅動與 116 * 設備匹配以后此函數就會執行 117 * @param - dev : platform 設備 118 * @return : 0,成功;其他負值,失敗 119 */ 120 static int led_probe(struct platform_device *dev) 121 { 122 printk("led driver and device was matched!\r\n"); 123 /* 1、設置設備號 */ 124 if (leddev.major) { 125 leddev.devid = MKDEV(leddev.major, 0); 126 register_chrdev_region(leddev.devid, LEDDEV_CNT,LEDDEV_NAME); 127 } else { 128 alloc_chrdev_region(&leddev.devid, 0, LEDDEV_CNT,LEDDEV_NAME); 129 leddev.major = MAJOR(leddev.devid); 130 } 131 132 /* 2、注冊設備 */ 133 cdev_init(&leddev.cdev, &led_fops); 134 cdev_add(&leddev.cdev, leddev.devid, LEDDEV_CNT); 135 136 /* 3、創建類 */ 137 leddev.class = class_create(THIS_MODULE, LEDDEV_NAME); 138 if (IS_ERR(leddev.class)) { 139 return PTR_ERR(leddev.class); 140 } 141 142 /* 4、創建設備 */ 143 leddev.device = device_create(leddev.class, NULL, leddev.devid,NULL, LEDDEV_NAME); 144 if (IS_ERR(leddev.device)) { 145 return PTR_ERR(leddev.device); 146 } 147 148 /* 5、初始化 IO */ 149 leddev.node = of_find_node_by_path("/gpioled"); 150 if (leddev.node == NULL){ 151 printk("gpioled node nost find!\r\n"); 152 return -EINVAL; 153 } 154 155 leddev.led0 = of_get_named_gpio(leddev.node, "led-gpio", 0); 156 if (leddev.led0 < 0) { 157 printk("can't get led-gpio\r\n"); 158 return -EINVAL; 159 } 160 161 gpio_request(leddev.led0, "led0"); 162 gpio_direction_output(leddev.led0, 1); /*設置為輸出,默認高電平 */ 163 return 0; 164 } 165 166 /* 167 * @description : remove 函數,移除 platform 驅動的時候此函數會執行 168 * @param - dev : platform 設備 169 * @return : 0,成功;其他負值,失敗 170 */ 171 static int led_remove(struct platform_device *dev) 172 { 173 gpio_set_value(leddev.led0, 1); /* 卸載驅動的時候關閉 LED */ 174 175 cdev_del(&leddev.cdev); /* 刪除 cdev */ 176 unregister_chrdev_region(leddev.devid, LEDDEV_CNT); 177 device_destroy(leddev.class, leddev.devid); 178 class_destroy(leddev.class); 179 return 0; 180 } 181 182 /* 匹配列表 */ 183 static const struct of_device_id led_of_match[] = { 184 { .compatible = "atkalpha-gpioled" }, 185 { /* Sentinel */ } 186 }; 187 188 /* platform 驅動結構體 */ 189 static struct platform_driver led_driver = { 190 .driver = { 191 .name = "imx6ul-led", /* 驅動名字,用于和設備匹配 */ 192 .of_match_table = led_of_match, /* 設備樹匹配表 */ 193 }, 194 .probe = led_probe, 195 .remove = led_remove, 196 }; 197 198 /* 199 * @description : 驅動模塊加載函數 200 * @param : 無 201 * @return : 無 202 */ 203 static int __init leddriver_init(void) 204 { 205 return platform_driver_register(&led_driver); 206 } 207 208 /* 209 * @description : 驅動模塊卸載函數 210 * @param : 無 211 * @return : 無 212 */ 213 static void __exit leddriver_exit(void) 214 { 215 platform_driver_unregister(&led_driver); 216 } 217 218 module_init(leddriver_init); 219 module_exit(leddriver_exit); 220 MODULE_LICENSE("GPL"); 221 MODULE_AUTHOR("zuozhongkai");

第33~112行,傳統的字符設備驅動,沒什么要說的。

第120~164行,platform驅動的probe函數,當設備樹中的設備節點與驅動之間匹配成功以后此函數

就會執行,原來在驅動加載函數里面做的工作現在全部放到probe函數里面完成。

第171~180行,remobe函數,當卸載platform驅動的時候此函數就會執行。在此函數里面釋放內

存、注銷字符設備等,也就是將原來驅動卸載函數里面的工作全部都放到remove函數中完成。

第183~186行,匹配表,描述了此驅動都和什么樣的設備匹配,第184行添加了一條值為"atkalpha-

gpioled"的compatible屬性值,當設備樹中某個設備節點的compatible屬性值也為“atkalpha-

gpioled”的時候就會與此驅動匹配。

第189~196行,platform_driver驅動結構體,191行設置這個platform驅動的名字為“imx6ul-

led”,因此,當驅動加載成功以后就會在/sys/bus/platform/drivers/目錄下存在一個名為“imx6u-

led”的文件

第192行設置of_match_table為上面的led_of_match。

第203~206行,驅動模塊加載函數,在此函數里面通過platform_driver_register向Linux內核注冊

led_driver驅動。

第213~216行,驅動模塊卸載函數,在此函數里面通過platform_driver_unregister從Linux內核卸載

led_driver驅動。

7.運行測試

1)編譯驅動程序和測試APP

1、編譯驅動程序

編寫Makefile文件,本章實驗的Makefile文件和之前實驗基本一樣,只是將obj-m變量的值改為

“leddriver.o”,Makefile內容如下所示:

1 KERNELDIR := /home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imx- rel_imx_4.1.15_2.1.0_ga_alientek ...... 4 obj-m := leddriver.o ...... 11 clean: 12 $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

第4行,設置obj-m變量的值為“leddriver.o”。

輸入如下命令編譯出驅動模塊文件:

make -j32

編譯成功以后就會生成一個名為“leddriver.o”的驅動模塊文件。

2、編譯測試APP

測試APP直接使用上一章的ledApp這個測試軟件即可。

2)運行測試

將上一小節編譯出來leddriver.ko拷貝到rootfs/lib/modules/4.1.15目錄中,重啟開發板,進入到目錄

lib/modules/4.1.15中,輸入如下命令加載leddriver.ko這個驅動模塊。

depmod //第一次加載驅動的時候需要運行此命令 modprobe leddriver.ko //加載驅動模塊

驅動模塊加載完成以后到/sys/bus/platform/drivers/目錄下查看驅動是否存在,我們在leddriver.c中

設置led_driver(platform_driver類型)的name字段為“imx6ul-led”,因此會

在/sys/bus/platform/drivers/目錄下存在名為“imx6ul-led”這個文件,結果如圖所示:

同理,在/sys/bus/platform/devices/目錄下也存在led的設備文件,也就是設備樹中gpioled這個

節點,如圖所示:

驅動和模塊都存在,當驅動和設備匹配成功以后就會輸出如圖所示一行語句:?

驅動和設備匹配成功以后就可以測試LED燈驅動了,輸入如下命令打開LED燈:

./ledApp /dev/dtsplatled 1 //打開 LED 燈

在輸入如下命令關閉LED燈:

./ledApp /dev/dtsplatled 0 //關閉 LED 燈

觀察一下LED燈能否打開和關閉,如果可以的話就說明驅動工作正常,如果要卸載驅動的話輸入如

下命令即可:

rmmod leddriver.ko

總結

以上是生活随笔為你收集整理的platform驱动的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。