本文轉載自;http://blog.csdn.net/oqqHuTu12345678/article/details/72783903
以下內容源於朱有鵬《物聯網大講堂》課程的學習,如有侵權,請告知刪除。
一、什么是驅動框架?
1、驅動是誰寫的?
(1)驅動開發工程師;
(2)內核維護者;
2、驅動編程協作要求
(1)接口標准化;
(2)內核開發者應該盡量降低驅動開發者難度;
3、到底什么是驅動框架?
(1)驅動框架
- 內核中驅動部分維護者,針對每個種類(比如LED、LCD、蜂鳴器等等)的設備,都設計有一套成熟的、標准的、典型的驅動實現;
- 它是把不同廠家的同類硬件驅動中相同的部分抽出來自己實現好,再把不同部分留出接口給具體的驅動開發工程師來實現。
- 降低了難度,也標准化。
(2)內核維護者在內核中設計了一些(統一管控系統資源的)體系
- 這些體系讓內核能夠(對資源在各個驅動之間的使用)統一協調和分配,保證整個內核的穩定健康運行。
- 譬如系統中所有的GPIO就屬於系統資源,每個驅動模塊如果要使用某個GPIO就要先調用特殊的接口先申請,申請到后使用,使用完后要釋放。
- 又譬如中斷號也是一種資源,驅動在使用前也必須去申請。
- 這體系也是驅動框架的組成部分。
(3)一些特定的接口函數、一些特定的數據結構,這些是驅動框架的直接表現。
二、內核驅動框架中LED的基本情況
1、相關文件
(1)drivers/leds目錄
- 這個目錄就是驅動框架規定的LED這種硬件的驅動應該待的地方。
(2)drivers/leds目錄下有led-class.c和led-core.c
- 這兩個文件加起來屬於LED驅動框架的第一部分,這兩個文件是內核開發者提供的,他們描述的是內核中所有廠家的不同LED硬件的相同部分的邏輯。
(3)此目錄下有leds-xxxx.c
- 這個文件是LED驅動框架的第2部分,是由不同廠商的驅動工程師編寫添加的;
- 廠商驅動工程師結合自己公司的硬件的不同情況來對LED進行操作,使用第一部分提供的接口來和驅動框架進行交互,最終實現驅動的功能。
2、九鼎移植的內核中led驅動
- 九鼎實際未使用內核推薦的led驅動框架;
- drivers/char/led/x210-led.c;
3、案例分析驅動框架的使用
(1)以leds-s3c24xx.c為例。
- leds-s3c24xx.c中通過調用led_classdev_register來完成LED驅動的注冊,而led_classdev_register是在drivers/leds/led-class.c中定義的。
- 所以其實SoC廠商的驅動工程師是調用內核開發者在驅動框架中提供的接口來實現自己的驅動的。
(2)驅動框架的關鍵點
- 內核開發者提供了什么?
- 驅動開發者要完成什么?
4、典型的驅動開發行業現狀
(1)內核開發者對驅動框架進行開發和維護、升級,對應led-class.c和led-core.c;
(2)SoC廠商的驅動工程師對設備驅動源碼進行編寫、調試,提供參考版本,對應leds-s3c24xx.c;(全志、三星、華為等芯片廠商)
(3)做產品的廠商的驅動工程師以SoC廠商提供的驅動源碼為基礎,來做移植和調試;
三、.初步分析led驅動框架源碼
1、涉及到的文件
- led-core.c;(一些宏與頭文件包含而已)
- led-class.c;
(1)分析發現,LED驅動框架中,內核開發者實現的部分主要是led-class.c;
(2)led-class.c其實就是一個內核模塊(明顯的特征是,有安裝和卸載函數)
- 那么對led-class.c的分析應該從下往上,遵從對模塊的基本分析方法。
(3)為什么LED驅動框架中,內核開發者實現的部分,要實現成一個模塊?
- 因為內核開發者希望這個驅動框架是可以被裝載/卸載的。
- 這樣當內核使用者不需要這個驅動框架時可以完全去掉,需要時可以隨時加上。
(4)led_init在/sys/class目錄下創建“leds”這個類名;led_exit銷毀“leds”這個類名。
2、subsys_initcall


(1)subsys_initcall是一個宏,定義在Linux/init.h中。
- 這個宏的功能是:將其聲明的函數放到一個特定的段:.initcall4.init。
(2)分析module_init宏,可以看出它將函數放到了.initcall6.init段中。
- module_init
- __initcall
- device_initcall
- __define_initcall("6",fn,6)
(3)內核在啟動過程中,需要按照順序執行很多事情。內核如何實現按照先后順序去做很多初始化操作?
- 內核的解決方案就是將內核啟動時要調用的所有函數歸類,然后每個類按照一定的次序去調用執行。
- 這些分類名就叫.initcalln.init,n的值從1到8。
- 內核開發者在編寫內核代碼時只要將函數設置合適的級別,鏈接的時候,這些函數就會被放入特定的段,內核啟動時再按照(內核鏈接腳本中指定的)段順序去依次執行各個段即可。內核鏈接腳本(編譯之后才有)在arch/arm/kernel/vmlinux.lds中。

(4)經過分析可以看出,subsys_initcall和module_init的作用是一樣的,只不過前者所聲明的函數要比后者在內核啟動時的執行順序更早。
3、led_class_attrs



(1)什么是attribute?
- 對應將來/sys/class/leds/目錄里的內容,一般是文件和文件夾。
- 這些文件其實就是sysfs開放給應用層的一些操作接口(非常類似於/dev/目錄下的那些設備文件,對這些設備文件的操作API,對應file_operations里面的函數)。
(2)attribute有什么用?
- 讓應用程序可以通過/sys/class/leds/目錄下面的屬性文件來操作驅動進而操作硬件設備。
(3)attribute其實是另一條驅動實現的路線(不再有c_dev相關的函數操作),有區別於之前講的file_operations那條線。
4、led_classdev_register設備注冊函數

- led_classdev_register函數創建一個屬於leds這個類的一個設備,其實就是去注冊一個設備。
- 這個函數是led驅動框架中,內核開發者提供給SoC廠家驅動開發者的一個注冊驅動的接口。
- 當使用led驅動框架去編寫驅動的時候,這個led_classdev_register函數的作用類似於之前使用file_operations方式去注冊字符設備驅動時的register_chrdev函數。
- 之前使用file_operations方式時,在sys/class目錄下創建一個類,然后再創建屬於這個類的一個設備。
5、led_classdev結構體

- 在leds.h文件中
四、在內核中添加或去除某個驅動
1、去除九鼎移植的LED驅動
(1)九鼎移植的驅動(在應用層的接口)在/sys/devices/platform/x210-led/目錄下,有led1、led2、led3、led4四個設備文件,各自管理一個led。

- echo 1 > led1可以點亮其中的led1;
(2)要去掉九鼎自己移植的led驅動,要在make menucofig中去掉選擇項,然后重新make得到zImage,然后重啟時啟動這個新的zImage即可。
- 新的內核啟動后,如果/sys/devices/platform/目錄下已經沒有了x210-led這個目錄,就說明我們去掉這個驅動成功了。
(3)為什么make menuconfig就能去掉這個驅動?
- 理解make menuconfig的功能。
2、添加led驅動框架支持
當前內核中沒有LED驅動框架,要去添加它。(/sys/class目錄下沒有此類,因此要去添加此類)
- 主要是menuconfig的操作。
3、sysfs中的內容分析
4、后續展望:完成leds-x210.c
五、基於驅動框架寫led驅動1
1、分析
(1)參考哪里? drivers/leds/leds-s3c24xx.c文件
(2)關鍵點?led_classdev_register函數
2、動手寫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>
- #define GPJ0CON S5PV210_GPJ0CON
- #define GPJ0DAT S5PV210_GPJ0DAT
- static struct led_classdev mydev; // 定義結構體變量
- // 這個函數就是要去完成具體的硬件讀寫任務的
- static void s5pv210_led_set(struct led_classdev *led_cdev,enum led_brightness value)
- {
- printk(KERN_INFO "s5pv210_led_set\n");
- // 在這里根據用戶設置的值來操作硬件
- // 用戶設置的值就是value
- if (value == LED_OFF)
- {
- // 用戶給了個0,希望LED滅
- writel(0x11111111, GPJ0CON);
- writel(((1<<3) | (1<<4) | (1<<5)), GPJ0DAT);
- }
- else
- {
- // 用戶給的是非0,希望LED亮
- writel(0x11111111, GPJ0CON);
- writel(((0<<3) | (0<<4) | (0<<5)), GPJ0DAT);
- }
- }
- static int __init s5pv210_led_init(void)
- {
- // 用戶insmod安裝驅動模塊時會調用該函數
- // 該函數的主要任務就是去使用led驅動框架提供的設備注冊函數來注冊一個設備
- int ret = -1;
- mydev.name = "myled";//設備的名字
- mydev.brightness = 255;
- mydev.brightness_set = s5pv210_led_set;
- ret = led_classdev_register(NULL, &mydev);
- if (ret < 0) {
- printk(KERN_ERR "led_classdev_register failed\n");
- return ret;
- }
- return 0;
- }
- static void __exit s5pv210_led_exit(void)
- {
- led_classdev_unregister(&mydev);
- }
- module_init(s5pv210_led_init);
- module_exit(s5pv210_led_exit);
- // MODULE_xxx這種宏作用是用來添加模塊描述信息
- MODULE_LICENSE("GPL"); // 描述模塊的許可證
- MODULE_AUTHOR("aston <1264671872@qq.com>"); // 描述模塊的作者
- MODULE_DESCRIPTION("s5pv210 led driver"); // 描述模塊的介紹信息
- MODULE_ALIAS("s5pv210_led"); // 描述模塊的別名信息
六、基於驅動框架寫led驅動2
1、代碼實踐
(1)調試
(2)分析





- 我們寫的驅動確實工作了,被加載了,/sys/class/leds/目錄下確實多出來了一個表示設備的文件夾。
- 文件夾里面有相應的操控led硬件的2個屬性brightness和max_brightness。
- led-class.c中brightness方法有一個show方法和store方法,這兩個方法對應用戶在/sys/class/leds/myled/brightness目錄下直接去讀寫這個文件時實際執行的代碼。
- 當我們show brightness時,實際就會執行led_brightness_show函數。
- 當我們echo 1 > brightness時,實際就會執行led_brightness_store函數。
(3)show方法實際要做的就是讀取LED硬件信息,然后把硬件信息返回
- 因此show方法和store方法會去操控硬件;
- 但是led-class.c文件又屬於驅動框架中的文件,它本身無法直接讀取具體硬件,因此在show和store方法中使用函數指針的方式調用了struct led_classdev結構體中的相應的讀取/寫入硬件信息的方法。
(4)struct led_classdev結構體中的實際用來讀寫硬件信息的函數,就是我們自己寫的驅動文件leds-s5pv210.c中要提供的。
2、添加硬件操作
七、基於驅動框架寫led驅動3
1、在驅動中將4個LED分開
(1)好處
- 驅動層實現對各個LED設備的獨立訪問,並向應用層展示出4個操作接口led1、led2、led3、led4,這樣應用層可以完全按照自己的需要對LED進行控制。
- 驅動的設計理念:不要對最終需求功能進行假定(不能假定用戶進行什么操作,比如是幾個led一起操作還是一個操作而已?),而應該只是直接的對硬件的操作。
- 有一個概念就是:機制和策略的問題。在硬件操作上驅動只應該提供機制(具體實現)而不是策略(方法、主意、解決方案)。策略由應用程序來做。
(2)如何實現
- #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>
- #define GPJ0CON S5PV210_GPJ0CON
- #define GPJ0DAT S5PV210_GPJ0DAT
- static struct led_classdev mydev1; // 定義結構體變量
- static struct led_classdev mydev2; // 定義結構體變量
- static struct led_classdev mydev3; // 定義結構體變量
- // 這個函數就是要去完成具體的硬件讀寫任務的
- static void s5pv210_led1_set(struct led_classdev *led_cdev,
- enum led_brightness value)
- {
- printk(KERN_INFO "s5pv210_led1_set\n");
- writel(0x11111111, GPJ0CON);
- // 在這里根據用戶設置的值來操作硬件
- // 用戶設置的值就是value
- if (value == LED_OFF)
- {
- // 用戶給了個0,希望LED滅
- //writel(0x11111111, GPJ0CON);
- // 讀改寫三部曲
- writel((readl(GPJ0DAT) | (1<<3)), GPJ0DAT);
- }
- else
- {
- // 用戶給的是非0,希望LED亮
- //writel(0x11111111, GPJ0CON);
- writel((readl(GPJ0DAT) & ~(1<<3)), GPJ0DAT);
- }
- }
- static void s5pv210_led2_set(struct led_classdev *led_cdev,
- enum led_brightness value)
- {
- printk(KERN_INFO "s5pv2102_led_set\n");
- writel(0x11111111, GPJ0CON);
- // 在這里根據用戶設置的值來操作硬件
- // 用戶設置的值就是value
- if (value == LED_OFF)
- {
- // 用戶給了個0,希望LED滅
- //writel(0x11111111, GPJ0CON);
- // 讀改寫三部曲
- writel((readl(GPJ0DAT) | (1<<4)), GPJ0DAT);
- }
- else
- {
- // 用戶給的是非0,希望LED亮
- //writel(0x11111111, GPJ0CON);
- writel((readl(GPJ0DAT) & ~(1<<4)), GPJ0DAT);
- }
- }
- static void s5pv210_led3_set(struct led_classdev *led_cdev,
- enum led_brightness value)
- {
- printk(KERN_INFO "s5pv210_led3_set\n");
- writel(0x11111111, GPJ0CON);
- // 在這里根據用戶設置的值來操作硬件
- // 用戶設置的值就是value
- if (value == LED_OFF)
- {
- // 用戶給了個0,希望LED滅
- //writel(0x11111111, GPJ0CON);
- // 讀改寫三部曲
- writel((readl(GPJ0DAT) | (1<<5)), GPJ0DAT);
- }
- else
- {
- // 用戶給的是非0,希望LED亮
- //writel(0x11111111, GPJ0CON);
- writel((readl(GPJ0DAT) & ~(1<<5)), GPJ0DAT);
- }
- }
- static int __init s5pv210_led_init(void)
- {
- // 用戶insmod安裝驅動模塊時會調用該函數
- // 該函數的主要任務就是去使用led驅動框架提供的設備注冊函數來注冊一個設備
- int ret = -1;
- // led1
- mydev1.name = "led1";
- mydev1.brightness = 255;
- mydev1.brightness_set = s5pv210_led1_set;
- ret = led_classdev_register(NULL, &mydev1);
- if (ret < 0) {
- printk(KERN_ERR "led_classdev_register failed\n");
- return ret;
- }
- // led2
- mydev2.name = "led2";
- mydev2.brightness = 255;
- mydev2.brightness_set = s5pv210_led2_set;
- ret = led_classdev_register(NULL, &mydev2);
- if (ret < 0) {
- printk(KERN_ERR "led_classdev_register failed\n");
- return ret;
- }
- // led3
- mydev3.name = "led3";
- mydev3.brightness = 255;
- mydev3.brightness_set = s5pv210_led3_set;
- ret = led_classdev_register(NULL, &mydev3);
- if (ret < 0) {
- printk(KERN_ERR "led_classdev_register failed\n");
- return ret;
- }
- return 0;
- }
- static void __exit s5pv210_led_exit(void)
- {
- led_classdev_unregister(&mydev1);
- led_classdev_unregister(&mydev2);
- led_classdev_unregister(&mydev3);
- }
- module_init(s5pv210_led_init);
- module_exit(s5pv210_led_exit);
- // MODULE_xxx這種宏作用是用來添加模塊描述信息
- MODULE_LICENSE("GPL"); // 描述模塊的許可證
- MODULE_AUTHOR("aston <1264671872@qq.com>"); // 描述模塊的作者
- MODULE_DESCRIPTION("s5pv210 led driver"); // 描述模塊的介紹信息
- MODULE_ALIAS("s5pv210_led"); // 描述模塊的別名信息
2、和leds-s3c24xx.c的不同

3、gpiolib引入
(1)一個事實:很多硬件都要用到GPIO,GPIO會復用;
(2)如果同一個GPIO被2個驅動同時控制了,就會出現bug;
(3)內核提供gpiolib來統一管理系統中所有GPIO;
- 某個驅動需要用到GPIO時,需要申請,被許可后才可以使用,使用完后釋放,其他驅動才能使用該GPIO。
- 建議性的,希望都用gpiolib。
(4)gpiolib體系,屬於驅動框架的一部分。
八、linux內核的gpiolib學習1
1、gpiolib學習重點(主線)
(1)主線一:gpiolib的建立過程;
- 體系是如何建立的?
(2)主線二:gpiolib的使用方法:申請、使用、釋放
(3)主線三:gpiolib的架構:涉及哪些目錄的哪些文件
2、gpiolib的學習方法
(1)以一條主線進去,堅持主線;
(2)中途遇到雜碎知識,徹底搞定之,然后繼續主線;
(3)隨時做筆記以加深理解和記憶;
(4)學習途中注意架構思想,提升自己大腦的空間復雜度;
3、主線1:gpiolib的建立
找到目標函數



- smdkc110_map_io這個函數在靜態映射中曾經經過。
- s5pv210_gpiolib_init函數,是gpiolib初始化的函數。
九、linux內核的gpiolib學習2
1、struct s3c_gpio_chip




(1)在文件gpio-core.h文件中
- 此結構體是一個GPIO端口的抽象,這個結構體的一個變量就可以完全的描述一個IO端口。
(2)端口和IO口是兩個概念
- S5PV210有很多個IO口(160個左右),這些IO口首先被分成N個端口(port group),然后每個端口中又包含了M個IO口。
- 譬如GPA0是一個端口,里面包含了8個IO口,我們一般記作:GPA0_0(或GPA0.0)、GPA0_1……
(3)內核中為每個GPIO分配了一個編號,編號是一個數字(譬如一共有160個IO時編號就可以從1到160連續分布),編號可以讓程序很方便的去識別每一個GPIO。
2、s5pv210_gpio_4bit[ ]數組
- 一個結構體數組,數組中包含了很多個struct s3c_gpio_chip類型的變量
- 填充了結構體中chip這個元素,這個元素是struct gpio_chip類型的,因此進一步細化為填充struct gpio_chip類型中的元素。其他元素好像沒有太深入。


十、linux內核的gpiolib學習3
1、S5PV210_GPA0宏
這個宏的返回值是GPA0端口的某一個IO口的基礎編號值,傳參是這個IO口在GPA0端口中的局部編號。


2、samsung_gpiolib_add_4bit_chips函數
- 進行gpiolib的注冊;
- 接收的參數是當前文件中定義好的結構體數組s5pv210_gpio_4bit(2個參數分別是數組名和數組元素個數);
- 此結構體數組,包含了當前系統中所有的IO端口的信息。
- 包含:端口的名字、端口中所有GPIO的編號、端口操作寄存器組的虛擬地址基地址、端口中IO口的數量、端口上下拉等模式的配置函數、端口中的IO口換算其對應的中斷號的函數(此時不用再查看原理圖)。
十一、linux內核的gpiolib學習4
1、幾個問題
(1)哪個目錄的哪個文件?
(2)函數名中為什么有個4bit?

- 三星的CPU中2440的CON寄存器是2bit對應一個IO口,而6410和210以及之后的系列中CON寄存器是4bit對應1個IO口。
- 所以gpiolib在操作2440和210的CON寄存器時是不同的。
2、函數調用關系




- samsung_gpiolib_add_4bit_chips
- samsung_gpiolib_add_4bit
- s3c_gpiolib_add
- samsung_gpiolib_add_4bit內部沒有做gpiolib的注冊工作,而是在做填充,填充的是每一個GPIO被設置成輸入模式/輸出模式的操作方法。
十二、linux內核的gpiolib學習5_6
1、s3c_gpiolib_add


(1)首先檢測並完善chip的direction_input/direction_ouput/set/get這4個方法;
(2)然后調用gpiochip_add方法進行真正的注冊操作。

- 這個注冊就是將(我們的封裝了一個GPIO端口的所有信息的)chip結構體變量,掛接到內核(gpiolib模塊定義的一個)gpio_desc數組中的某一個格子中。
2、從驅動框架角度再來分析一下gpiolib
(1)截至目前(gpiochip_add方法)已經搞清楚了gpiolib的建立工程,即主線一。
- 但是這只是整個gpiolib建立的一部分,是廠商驅動工程師負責的那一部分;
- 還有另一部分是內核開發者提供的驅動框架的那一部分,即第2條主線。(第一條起始見八3)
(2)drivers/gpio/gpiolib.c這個文件中所有的函數構成了第2部分,也就是內核開發者寫的gpiolib框架部分。
- gpiochip_add:是框架開出來的接口,給廠商驅動工程師用(針對某個開發板GPIO的情況,對內核進行一定的修改,注冊),用於向內核注冊gpiolib。(標記有多少組端口,屬性細節等,讓內核知道具體的GPIO信息。)
- gpio_request:是框架開出來的接口,給使用gpiolib來編寫自己的驅動的驅動工程師用的,驅動中要想使用某一個gpio,就必須先調用gpio_request接口來向內核的gpiolib部分申請,得到允許后才可以去使用這個gpio。
- gpio_free:對應gpio_request,用來釋放申請后用完了的gpio。
- gpio_request_one/gpio_request_array:這兩個是gpio_request的變種。
- gpiochip_is_requested:接口用來判斷某一個gpio是否已經被申請了
- gpio_direction_input/gpio_direction_output:接口用來設置GPIO為輸入/輸出模式,注意該函數內部實際並沒有對硬件進行操作,只是通過chip結構體變量的函數指針,調用了(將來SoC廠商的驅動工程師寫的)真正地操作硬件、實現gpio設置成輸出模式的那個函數。
- 以上的接口屬於一類,是給寫其他驅動並且用到了gpiolib的人使用的,剩下的函數是gpiolib內部自己的一些功能實現的代碼。
十三、linux內核的gpiolib學習7
1、gpiolib的attribute部分
(1)CONFIG_GPIO_SYSFS
- 在內核中很多實現方式,都是通過宏來配置的;
- 在.config文件有,則必然在menuconfig中有。
(2)GPIO的attribute演示
- 一般能cat,不能寫。



2、能夠cat的相關代碼分析



(1)gpiolib_sysfs_init:在/sys/class里定義了gpio這個類
(2)gpiochip_export
(3)sysfs_create_group用來創建許多attribute
十四、使用gpiolib完成led驅動
1、流程分析
(1)第1步:使用gpio_request申請要使用的一個GPIO;
(2)第2步:gpio_direction_input/gpio_direction_output 設置輸入/輸出模式;
(3)第3步:設置輸出值gpio_set_value 獲取IO口值gpio_get_value。

2、代碼實踐
(1)在led1上編寫代碼測試通過;
(2)擴展支持led2和led3、led4,可以分開注冊,也可以使用gpio_request_array去一次注冊;
(3)學習linux中查看gpio使用情況的方法



- 內核中提供了虛擬文件系統debugfs,里面有一個gpio文件,提供了gpio的使用信息(諸如誰被使用了,誰沒有被使用)。
- 使用方法:mount -t debugfs debugfs /tmp,然后cat /tmp/gpio即可得到gpio的所有信息,使用完后umount /tmp卸載掉debugfs
(4)代碼(驅動申請LED1資源而已)
- #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>
- #define GPIO_LED1 S5PV210_GPJ0(3)
- #define GPIO_LED2 S5PV210_GPJ0(4)
- #define GPIO_LED3 S5PV210_GPJ0(5)
- #define X210_LED_OFF 1 // X210中LED是正極接電源,負極節GPIO
- #define X210_LED_ON 0 // 所以1是滅,0是亮
- static struct led_classdev mydev1; // 定義結構體變量
- static struct led_classdev mydev2; // 定義結構體變量
- static struct led_classdev mydev3; // 定義結構體變量
- // 這個函數就是要去完成具體的硬件讀寫任務的
- static void s5pv210_led1_set(struct led_classdev *led_cdev,enum led_brightness value)
- {
- printk(KERN_INFO "s5pv210_led1_set\n");
- //writel(0x11111111, GPJ0CON);
- // 在這里根據用戶設置的值來操作硬件
- // 用戶設置的值就是value
- if (value == LED_OFF)
- {
- // 用戶給了個0,希望LED滅
- //writel(0x11111111, GPJ0CON);
- // 讀改寫三部曲
- //writel((readl(GPJ0DAT) | (1<<3)), GPJ0DAT);
- gpio_set_value(GPIO_LED1, X210_LED_OFF);
- }
- else
- {
- // 用戶給的是非0,希望LED亮
- //writel(0x11111111, GPJ0CON);
- //writel((readl(GPJ0DAT) & ~(1<<3)), GPJ0DAT);
- gpio_set_value(GPIO_LED1, X210_LED_ON);
- }
- }
- static void s5pv210_led2_set(struct led_classdev *led_cdev,
- enum led_brightness value)
- {
- printk(KERN_INFO "s5pv2102_led_set\n");
- //writel(0x11111111, GPJ0CON);
- // 在這里根據用戶設置的值來操作硬件
- // 用戶設置的值就是value
- if (value == LED_OFF)
- {
- // 用戶給了個0,希望LED滅
- //writel(0x11111111, GPJ0CON);
- // 讀改寫三部曲
- //writel((readl(GPJ0DAT) | (1<<4)), GPJ0DAT);
- }
- else
- {
- // 用戶給的是非0,希望LED亮
- //writel(0x11111111, GPJ0CON);
- //writel((readl(GPJ0DAT) & ~(1<<4)), GPJ0DAT);
- }
- }
- static void s5pv210_led3_set(struct led_classdev *led_cdev,
- enum led_brightness value)
- {
- printk(KERN_INFO "s5pv210_led3_set\n");
- //writel(0x11111111, GPJ0CON);
- // 在這里根據用戶設置的值來操作硬件
- // 用戶設置的值就是value
- if (value == LED_OFF)
- {
- // 用戶給了個0,希望LED滅
- //writel(0x11111111, GPJ0CON);
- // 讀改寫三部曲
- //writel((readl(GPJ0DAT) | (1<<5)), GPJ0DAT);
- }
- else
- {
- // 用戶給的是非0,希望LED亮
- //writel(0x11111111, GPJ0CON);
- //writel((readl(GPJ0DAT) & ~(1<<5)), GPJ0DAT);
- }
- }
- static int __init s5pv210_led_init(void)
- {
- // 用戶insmod安裝驅動模塊時會調用該函數
- // 該函數的主要任務就是去使用led驅動框架提供的設備注冊函數來注冊一個設備
- int ret = -1;
- // 在這里去申請驅動用到的各種資源,當前驅動中就是GPIO資源
- if (gpio_request(GPIO_LED1, "led1_gpj0.3")) //這里是申請失敗
- {
- printk(KERN_ERR "gpio_request failed\n");
- }
- else //申請成功后
- {
- // 設置為輸出模式,並且默認輸出1讓LED燈滅
- gpio_direction_output(GPIO_LED1, 1);
- }
- // led1
- mydev1.name = "led1";
- mydev1.brightness = 0;
- mydev1.brightness_set = s5pv210_led1_set;
- ret = led_classdev_register(NULL, &mydev1);
- if (ret < 0) {
- printk(KERN_ERR "led_classdev_register failed\n");
- return ret;
- }
- // led2
- mydev2.name = "led2";
- mydev2.brightness = 0;
- mydev2.brightness_set = s5pv210_led2_set;
- ret = led_classdev_register(NULL, &mydev2);
- if (ret < 0) {
- printk(KERN_ERR "led_classdev_register failed\n");
- return ret;
- }
- // led3
- mydev3.name = "led3";
- mydev3.brightness = 0;
- mydev3.brightness_set = s5pv210_led3_set;
- ret = led_classdev_register(NULL, &mydev3);
- if (ret < 0) {
- printk(KERN_ERR "led_classdev_register failed\n");
- return ret;
- }
- return 0;
- }
- static void __exit s5pv210_led_exit(void)
- {
- led_classdev_unregister(&mydev1);
- led_classdev_unregister(&mydev2);
- led_classdev_unregister(&mydev3);
- gpio_free(GPIO_LED1);
- }
- module_init(s5pv210_led_init);
- module_exit(s5pv210_led_exit);
- // MODULE_xxx這種宏作用是用來添加模塊描述信息
- MODULE_LICENSE("GPL"); // 描述模塊的許可證
- MODULE_AUTHOR("aston <1264671872@qq.com>"); // 描述模塊的作者
- MODULE_DESCRIPTION("s5pv210 led driver"); // 描述模塊的介紹信息
- MODULE_ALIAS("s5pv210_led"); // 描述模塊的別名信息
十五、將驅動添加到內核中
1、驅動的存在形式
(1)野生,優勢是方便調試開發,所以在開發階段都是這種;
(2)家養,優勢可以在內核配置時make menuconfig決定內核怎么編譯,方便集成。比如DM9000已經集成在內核中了,可以在menuconfig時配置。
2、驅動開發的一般步驟
(1)以模塊的形式在外部編寫、調試;
(2)將調試好的驅動代碼集成到kernel中
3、實踐
(1)關鍵點:Kconfig、Makefile、make menuconfig
(2)操作步驟
- 第1步:將寫好的驅動源文件放入內核源碼中正確的目錄下;比如led的驅動,應該放在/drivers/leds/目錄下;
- 第2步:/drivers/leds/目錄下,在Makefile中添加相應的依賴;
- 第3步:在Kconfig中添加相應的配置項;(因為make menuconfig的原理是讀取Kconfig中的信息,這里要顯示對應的配置項目,則需要添加相應內容)
- 第4步:make menuconfig;(以y為示例,此時會被編譯進去;若以M,則不會被編譯進去,而是被編譯成單獨的模塊;若以N,則不被編譯。)
第2步:

第3步:

第4步:

選擇y之后,保存,然后在.config文件中會有如下的宏

結果如下:

十六、目錄和文件結構
mach-s5pv210/gpiolib.c s5pv210_gpiolib_init
mach-s5pv210/include/mach/gpio.h #define S5PV210_GPA0(_nr)(S5PV210_GPIO_A0_START + (_nr))
arch/arm/plat-samsung/gpiolib.c 里面是210/6410這種4bit CON寄存器類型的操作方法
arch/arm/plat-samsung/gpio.c 里面是24XX這種2bit CON寄存器類型的操作方法
drivers/gpio/gpiolib.c 里面是內核開發者提供的gpiolib的驅動框架部


