【嵌入式Linux驅動開發】九、瞭解重要的Pinctrl和GPIO子系統使用,點亮一盞真的LED

   愛,就能使一個人到如此的地步。一次邂逅,一次目光的交融,就是永遠的合二爲一,就是與上帝的契約;縱使風暴雷電,也無法分解這種心靈的粘合。


2020年05月03日更:6ULL的GPIO子系統還不太完善,GPIO_ACTIVE_LOW沒有效果,讀到的實際值,並不是邏輯值!


總結:pinctrl 子系統重點是設置 PIN的複用和電氣屬性; gpio 子系統用於初始化 GPIO 並且提供相應的 API 函數如設置 GPIO爲輸入輸出,讀取 GPIO 的值等。

  前面的幾節,直接使用操作寄存器的方法編寫驅動。這只是爲了更好掌握驅動程序的本質,在實際開發過程中可不這樣做,太低效了!如果驅動開發都是這樣去查找寄存器,那我們就變成“寄存器工程師”了,即使是做單片機的都不執着於裸寫寄存器了。

一、Pinctrl 子系統

1.1 引入

  無論是哪種芯片,都有類似下圖的結構:

在這裏插入圖片描述
  要想讓 pinA、 B 用於 GPIO,需要設置 IOMUX 讓它們連接到 GPIO 模塊;
  要想讓 pinA、 B 用於 I2C,需要設置 IOMUX 讓它們連接到 I2C 模塊。
  所以 GPIO、 I2C 應該是並列的關係,它們能夠使用之前,需要設置 IOMUX。 有時候並不僅僅是設置 IOMUX,還要配置引腳,比如上拉、下拉、開漏等等。
  現在的芯片動輒幾百個引腳,在使用到 GPIO 功能時,讓你一個引腳一個引腳去找對應的寄存器,這要瘋掉。術業有專攻,這些累活就讓芯片廠家做吧──他們是 BSP 工程師。我們在他們的基礎上開發,我們是驅動工程師。開玩笑的, BSP 工程師是更懂他自家的芯片。

  所以,要把引腳的複用、配置抽出來,做成 Pinctrl 子系統,給 GPIO、 I2C 等模塊使用。BSP 工程師要做什麼? 看下圖:

在這裏插入圖片描述
  等 BSP 工程師在 GPIO 子系統、 Pinctrl 子系統中把自家芯片的支持加進去後,我們就可以非常方便地使用這些引腳了:點燈簡直太簡單了。

  等等, GPIO 模塊在圖中跟 I2C 不是並列的嗎?幹嘛在講 Pinctrl 時還把 GPIO 子系統拉進來?大多數的芯片,沒有單獨的 IOMUX 模塊,引腳的複用、配置等等,就是在 GPIO 模塊內部實現的。在硬件上 GPIO 和 Pinctrl 是如此密切相關,在軟件上它們的關係也非常密切。所以這 2 個子系統要一起講解。

1.2 重要概念

  從設備樹開始學習 Pintrl 會比較容易,主要參考文檔是:內核 Documentation\devicetree\bindings\pinctrl\pinctrl-bindings.txt

  這會涉及 2 個對象: pin controller、 client device。前者提供服務:可以用它來複用引腳、配置引腳。後者使用服務聲明自己要使用哪些引腳的哪些功能,怎麼配置它們。

  • A) pin controller

    • 在芯片手冊裏你找不到 pin controller,它是一個軟件上的概念,你可以認爲它對應IOMUX──用來複用引腳,還可以配置引腳(比如上下拉電阻等)。
    • 注意, Pin controller 和 GPIO Controller 不是一回事,前者控制的引腳可用於 GPIO 功能、 I2C 功能;後者只是把引腳配置爲輸出、輸入等簡單的功能。
  • B) client device

    • “客戶設備”,誰的客戶? Pinctrl 系統的客戶,那就是使用 Pinctrl 系統的設備,使用引腳的設備。它在設備樹裏會被定義爲一個節點,在節點裏聲明要用哪些引腳。

下圖就可以把幾個重要概念理清楚:

圖一:狀態0和狀態1均稱爲《複用節點》
在這裏插入圖片描述
圖二:狀態0稱爲《複用節點》,狀態1稱爲《配置節點》
在這裏插入圖片描述
圖二中,左邊是 pincontroller 節點,右邊是 client device 節點:

  • a) pin state

    • 對於一個“client device”來說,比如對於一個 UART 設備,它有多個“狀態”: default、 sleep等,那對應的引腳也有這些狀態。
      • 比如默認狀態下, UART 設備是工作的,那麼所用的引腳就要複用爲 UART 功能。
      • 在休眠狀態下, 爲了省電,可以把這些引腳複用爲 GPIO 功能;或者直接把它們配置輸出高電平。
    • 上圖中, pinctrl-names 裏定義了 2 種狀態: default、 sleep。
      • 第 0 種狀態用到的引腳在 pinctrl-0 中定義,它是 state_0_node_a,位於pincontroller 節點中。
      • 第 1 種狀態用到的引腳在 pinctrl-1 中定義,它是 state_1_node_a,位於 pincontroller 節點中。
      • 當這個設備處於 default 狀態時, pinctrl 子系統會自動根據上述信息把所用引腳複用爲uart0 功能。
      • 當這這個設備處於 sleep 狀態時, pinctrl 子系統會自動根據上述信息把所用引腳配置爲高電平。
  • b) groups 和 function:

    • 一個設備會用到一個或多個引腳,這些引腳就可以歸爲一組(group);這些引腳可以複用爲某個功能: function。
    • 當然:一個設備可以用到多組多能引腳,比如 A1、 A2 兩組引腳, A1 組複用爲 F1 功能, A2組複用爲 F2 功能。
  • c) Generic pin multiplexing node 和 Generic pin configuration node

    • 在上圖左邊的 pin controller 節點中,有子節點或孫節點,它們是給 client device 使用的。
    • 可以用來描述複用信息:哪組(group)引腳複用爲哪個功能(function);
    • 可以用來描述配置信息:哪組(group)引腳配置爲哪個設置功能(setting),比如上拉、下拉等.

1.3 使用實例

  注意: pin controller 節點的格式, 沒有統一的標準!(但是《複用節點》和《配置節點》這種概念還是有的)每家芯片都不一樣。甚至上面的 group、 function 關鍵字也不一定有,但是概念是有的。

  client device是有固定格式的!(它是寫到設備樹中的!!)

使用實例如下圖。

在這裏插入圖片描述

1.4 代碼中怎麼引用 pinctrl

  關於代碼中怎麼引用 pinctrl,這是透明的,我們的驅動基本不用管。當設備切換狀態時,對應的 pinctrl 就會被調用。比如在 platform_device 和 platform_driver 的枚舉過程中,流程如下:

在這裏插入圖片描述
  同樣,當系統休眠時,也會去設置該設備 sleep 狀態對應的引腳,不需要我們自己去調用代碼。

  非要自己調用,也有函數:

devm_pinctrl_get_select_default(struct device *dev); // 使用"default"狀態的引腳
pinctrl_get_select(struct device *dev, const char *name); // 根據 name 選擇某種狀態的引腳
pinctrl_put(struct pinctrl *p); // 不再使用, 退出時調用

二、GPIO 子系統重要概念

2.1 引入

  要操作 GPIO 引腳,先把所用引腳配置爲 GPIO 功能,這通過 Pinctrl 子系統來實現。然後就可以根據設置引腳方向(輸入還是輸出)、讀值─獲得電平狀態,寫值─輸出高低電平。以前我們通過寄存器來操作 GPIO 引腳,即使 LED 驅動程序,對於不同的板子它的代碼也完全不同。

  當 BSP 工程師實現了 GPIO 子系統後,我們就可以:

  • 在設備樹裏指定 GPIO 引腳(哪一組的哪一個引腳)
  • 在驅動代碼中:
    • 使用 GPIO 子系統的標準函數獲得 GPIO、設置 GPIO 方向、讀取/設置 GPIO 值。這樣的驅動代碼,將是單板無關的。

2.2 在設備樹中指定引腳

  在幾乎所有 ARM 芯片中, GPIO 都分爲幾組,每組中有若干個引腳。 所以在使用 GPIO子系統之前,就要先確定:它是哪組的?組裏的哪一個?

  在設備樹中,“GPIO 組”就是一個 GPIO Controller,這通常都由芯片廠家設置好。我們要做的是找到它名字,比如“gpio1”,然後指定要用它裏面的哪個引腳,比如<&gpio1 0>。

有代碼更直觀,下圖是一些芯片的 GPIO 控制器節點,它們一般都是廠家定義好,在
xxx.dtsi 文件中:

在這裏插入圖片描述

  我們暫時只需要關心裏面的這 2 個屬性:

  • gpio-controller;
    • 表示這個節點是一個 GPIO Controller,它下面有很多引腳
  • #gpio-cells = <2>;
    • 表示這個控制器下每一個引腳要用 2 個 32 位的數(cell)來描述(除了第一個參數之外,還需要兩個參數描述這個GPIO)
    • 使用多少個 cell 來描述一個引腳,這是 GPIO Controller 自己決定的。比如可以用其中一個 cell 來表示那是哪一個引腳,用另一個 cell 來表示它是高電平有效還是低電平有效,甚至還可以用更多的 cell 來示其他特性。
    • 普遍的用法是,用第 1 個 cell 來表示哪一個引腳,用第 2 個 cell 來表示有效電平:
GPIO_ACTIVE_HIGH : 高電平有效
GPIO_ACTIVE_LOW  :  低電平有效

  定義 GPIO Controller 是芯片廠家的事,我們怎麼引用某個引腳呢?在自己的設備節點中使用屬性"[<name>-]gpios",示例如下:

在這裏插入圖片描述
  上圖中,可以使用 gpios 屬性,也可以使用 name-gpios 屬性。(注意,必須是gpios)

2.3、 在驅動代碼中調用 GPIO 子系統

  在設備樹中指定了 GPIO 引腳,在驅動代碼中如何使用?也就是 GPIO 子系統的接口函數是什麼?

  GPIO 子系統有兩套接口: 基於描述符的(descriptor-based)、老的(legacy)。前者的函數都有前綴“gpiod_”,它使用 gpio_desc 結構體來表示一個引腳;後者的函數都有前綴“gpio_”,它使用一個整數來表示一個引腳。

  要操作一個引腳,首先要 get 引腳,然後設置方向,讀值、寫值。

  驅動程序中使用不同的GPIO子系統接口要包含不同的頭文件:

#include <linux/gpio/consumer.h> // descriptor-based#include <linux/gpio.h> // legacy

下表列出常用的函數:

descriptor-based legacy
獲得 GPIO 獲得 GPIO
gpiod_get gpio_request
gpiod_get_index
gpiod_get_array gpio_request_array
devm_gpiod_get
devm_gpiod_get_index
devm_gpiod_get_array
設置方向 設置方向
gpiod_direction_input gpio_direction_input
gpiod_direction_output gpio_direction_output
讀值、寫值 讀值、寫值
gpiod_get_value gpio_get_value
gpiod_set_value gpio_set_value
釋放 GPIO 釋放 GPIO
gpio_free gpio_free
gpiod_put gpio_free_array
gpiod_put_array
devm_gpiod_put
devm_gpiod_put_array

  有前綴“devm_”的含義是“設備資源管理”(Managed Device Resource),這是一種自動釋放資源的機制。它的思想是“資源是屬於設備的,設備不存在時資源就可以自動釋放”。
  比如在 Linux 開發過程中,先申請了 GPIO,再申請內存;如果內存申請失敗,那麼在返回之前就需要先釋放 GPIO 資源。如果使用 devm 的相關函數,在內存申請失敗時可以直接返回:設備的銷燬函數會自動地釋放已經申請了的 GPIO 資源。
  故,建議使用“devm_”版本的相關函數。

舉例,假設備在設備樹中有如下節點:

foo_device {
	compatible = "acme,foo";
	...
	led-gpios = <&gpio 15 GPIO_ACTIVE_HIGH>, /* red */
				<&gpio 16 GPIO_ACTIVE_HIGH>, /* green */
				<&gpio 17 GPIO_ACTIVE_HIGH>; /* blue */
	power-gpios = <&gpio 1 GPIO_ACTIVE_LOW>;
};

那麼可以使用下面的函數獲得引腳:

struct gpio_desc *red, *green, *blue, *power;

red   = gpiod_get_index(dev, "led", 0, GPIOD_OUT_HIGH);
green = gpiod_get_index(dev, "led", 1, GPIOD_OUT_HIGH);
blue  = gpiod_get_index(dev, "led", 2, GPIOD_OUT_HIGH);
power = gpiod_get(dev, "power", GPIOD_OUT_HIGH);

   要注意的是, gpiod_set_value 設置的值是“邏輯值”,不一定等於物理值。

  什麼意思?看下圖:

在這裏插入圖片描述

  舊的“gpio_”函數沒辦法根據設備樹信息獲得引腳,它需要先知道引腳號。引腳號怎麼確定?
  在 GPIO 子系統中,每註冊一個 GPIO Controller 時會確定它的“base number”,那麼這個控制器裏的第 n 號引腳的號碼就是: base number + n。但是如果硬件有變化、設備樹有變化,這個 base number 並不能保證是固定的,應該查看 sysfs 來確定 base number。

2.4 sysfs 中的訪問方法(老的legacy操作方法,不看也罷)

  在 sysfs 中訪問 GPIO,實際上用的就是引腳號,老的方法(legacy )

  • a) 先確定某個 GPIO Controller 的基準引腳號(base number),再計算出某個引腳的號碼。方法如下:
    • ① 先在開發板的/sys/class/gpio 目錄下,找到各個 gpiochipXXX 目錄:
    • 在這裏插入圖片描述
    • ② 然後進入某個 gpiochip 目錄,查看文件 label 的內容
    • ③ 根據 label 的內容對比設備樹
      • label 內容來自設備樹,比如它的寄存器基地址。用來跟設備樹(dtsi 文件)比較,就可以知道這對應哪一個 GPIO Controller。
      • 下圖是在 100ask_imx6ull 上運行的結果,通過對比設備樹可知 gpiochip96 對應 gpio4:
        在這裏插入圖片描述

所以 gpio4 這組引腳的基準引腳號就是 96,這也可以“cat base”來再次確認。

  • b) 基於 sysfs 操作引腳
  • 對於輸入引腳,假設引腳號爲 N,可以通過如下方法讀取引腳值
echo N> /sys/class/gpio/export
echo in > /sys/class/gpio/gpio110/direction
cat /sys/class/gpio/gpio110/value
echo N> /sys/class/gpio/unexport
  • 對於輸出引腳,假設引腳號爲 N,可以通過如下方法設置它的值爲1:
echo N > /sys/class/gpio/export
echo out > /sys/class/gpio/gpioN/direction
echo 1 > /sys/class/gpio/gpioN/value
echo N > /sys/class/gpio/unexport
  • 注意:如果驅動程序已經使用了該引腳,那麼將會 export 失敗,會提示下面的錯誤:

在這裏插入圖片描述

三、基於 GPIO 子系統的 LED 驅動程序

3.1 編寫思路

  GPIO 的地位跟其他模塊,比如 I2C、 UART 的地方是一樣的,要使用某個引腳,需要先把引腳配置爲 GPIO 功能,這要使用 Pinctrl 子系統, 只需要在設備樹裏指定就可以。在驅動代碼上不需要我們做任何事情。

  GPIO 本身需要確定引腳,這也需要在設備樹裏指定。設備樹節點會被內核轉換爲 platform_device。對應的, 驅動代碼中要註冊一個 platform_driver,在 probe 函數中:獲得引腳、 註冊file_operations。在 file_operations 中: 設置方向、讀值/寫值。

下圖就是一個設備樹的例子:

在這裏插入圖片描述

3.2 在設備樹中添加 Pinctrl 信息

  有些芯片提供了設備樹生成工具,在 GUI 界面中選擇引腳功能和配置信息,就可以自動生成 Pinctrl 子結點。把它複製到你的設備樹文件中,再在 client device 結點中引用就可以。

  有些芯片只提供文檔,那就去閱讀文檔,一般在內核源碼目錄:Documentation\devicetree\bindings\pinctrl 下面,保存有該廠家的文檔。

  如果連文檔都沒有,那隻能參考內核源碼中的設備樹文件,在內核源碼目錄arch/arm/boot/dts 目錄下。

  最後一步, 網絡搜索。Pinctrl 子節點的樣式如下:

在這裏插入圖片描述

3.3 在設備樹中添加 GPIO 信息

  先查看電路原理圖確定所用引腳,再在設備樹中指定:添加”[name]-gpios”屬性, 指定使 用 的 是 哪 一 個 GPIO Controller 裏 的 哪 一 個 引 腳 , 還 有 其 他 Flag 信 息 , 比 如GPIO_ACTIVE_LOW 等。 具體需要多少個 cell 來描述一個引腳,需要查看設備樹中這個 GPIO Controller 節點裏的“#gpio-cells”屬性值,也可以查看內核文檔。

示例如下:

在這裏插入圖片描述

3.4 編程示例

  • a. 定義、註冊一個 platform_driver
  • b. 在它的 probe 函數裏:
    • b.1 根據 platform_device 的設備樹信息確定 GPIO: gpiod_get
    • b.2 定義、註冊一個 file_operations 結構體
    • b.3 在 file_operarions 中使用 GPIO 子系統的函數操作 GPIO:
      gpiod_direction_output、 gpiod_set_value

3.4.1、打開I.MX引腳配置工具生成pinctrl

  • 選擇I.MX6ULL的配置文件(第一次使用需要下載一些東西,最好掛上梯子!)
    在這裏插入圖片描述

  • GPIO5_3爲例,操作如圖所示

在這裏插入圖片描述


需要說明的:

  • A) 圖形化操作修改,對應右邊的imx-board.dtsi會有添加相加代碼,並且添加的代碼用綠色標註出來!
    在這裏插入圖片描述
  • B) 路由置×××,,,就是複用成×××功能,前面有紅色標誌的標識選不了
    在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述

3.4.2、修改設備樹文件

  • 放置軟件生成的pinctrl配置到設備樹下

  • 打開設備樹文件,我的在/home/clay/linux/IMX6ULL/Linux_Drivers/linux-4.9.88/arch/arm/boot/dts/100ask_imx6ull_qemu.dts

  • 搜索GPIO5_IO3對應的父節點,即iomuxc_snvs
    在這裏插入圖片描述
    在這裏插入圖片描述

  • 複製軟件生成的部分代碼到該節點下,即

BOARD_InitPinsSnvs: BOARD_InitPinsSnvsGrp {        /*!< Function assigned for the core: Cortex-A7[ca7] */
	fsl,pins = <
		MX6ULL_PAD_SNVS_TAMPER3__GPIO5_IO03        0x000110A0
	>;
};

在這裏插入圖片描述

注意,最好放到imx6ul-evk,這樣寫比較規範!evk是NXP評估板,顯然這個板子的設備樹是在原廠板子基礎上改來的!印證了那句“天下板子一大抄”!

  • BOARD_InitPinsSnvsBOARD_InitPinsSnvsGrp名字可以改成我們想要的(後面要用),比如改成:pinctrl_myled。當然這個名字沒有固定要求,但是爲了規範,一般會寫成pinctrl_xxx: xxxgrp這種形式!

在這裏插入圖片描述

pinctrl_myled: myledgrp {        /*!< Function assigned for the core: Cortex-A7[ca7] */
	fsl,pins = <
		MX6ULL_PAD_SNVS_TAMPER3__GPIO5_IO03        0x000110A0
	>;
};
  • 接着在根目錄下創建一個子節點!依次加入:
myled{
	compatible = "100ask,leddrv";
	pinctrl-names = "default";
	pinctrl-0 = <&myled_for_gpio_subsys>;
	led-gpios = <&gpio5 3 GPIO_ACTIVE_LOW>;
};

在這裏插入圖片描述

需要說明的是:

  • A) 在imx6ull.dtsi文件中,我們可以找到 GPIO5 控制器節點。屬性gpio-controller;表示它是一個GPIO控制器,屬性#gpio-cells = <2>;表示這個控制器下每一個引腳要用 2 個 32 位的數(cell)來描述(除了第一個參數之外,還需要兩個參數描述這個GPIO)。

在這裏插入圖片描述

  • B) 設備樹的子節點myled,它的compatible屬性取值,要和驅動中的compatible屬性名字一樣,這樣驅動和設備才能匹配!

設備樹文件100ask_imx6ull_qemu.dts
在這裏插入圖片描述
驅動文件leddrv.c
在這裏插入圖片描述

  • C) 設備樹的子節點myled,它的pinctrl-0屬性取值,是從工具複製過來重命名的pinctrl_myled

在這裏插入圖片描述

  • D) 設備樹的子節點myled,它的led-gpios屬性名,要和驅動中的一致!【需要說明的一點,驅動中的名字僅僅是前半部分,比如設備樹中是led-gpios,而驅動中只有led

設備樹文件100ask_imx6ull_qemu.dts
在這裏插入圖片描述
驅動文件leddrv.c
在這裏插入圖片描述

  • E) GPIO_ACTIVE_LOW 這個有效電平需要根據原理圖確定,我們的小燈是低電平點亮,所以有效電平是低!

    • on-紅-1 off-白-0 低電平有效
  • F) 最後排查一下系統中還有沒有其他在使用GPIO_IO3,在設備樹中利用工具生成的MX6ULL_PAD_SNVS_TAMPER3__GPIO5_IO03(該名字是唯一確定)進行搜索即可。

若找到,比如如圖所示

在這裏插入圖片描述

我們可以再搜索pinctrl_leds找到對應的節點,比如如圖所示

在這裏插入圖片描述

我們可以在該節點中加入status="disabled";,禁止它起作用!
在這裏插入圖片描述

  • G) 編譯設備樹!進入Linux源碼目錄,我的是/home/clay/linux/IMX6ULL/Linux_Drivers/linux-4.9.88,然後輸入make dtbs
    在這裏插入圖片描述

編譯沒有問題後,複製100ask_imx6ull_qemu.dtb文件到/home/clay/linux/qemu/new/ubuntu-16.04_imx6ul_qemu_system-release/imx6ull-system-image進行覆蓋!

3.4.3、編寫驅動程序

這個還是老套路了!!!

leddrv.c

#include <linux/module.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>


/* 1. 確定主設備號                                                                 */
static int major = 0;
static struct class *led_class;
static struct gpio_desc *led_gpio;


/* 3. 實現對應的open/read/write等函數,填入file_operations結構體                   */
static ssize_t led_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}

/* write(fd, &val, 1); */
static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
	int err;
	char status;//這個表示邏輯值
	//struct inode *inode = file_inode(file);
	//int minor = iminor(inode);
	
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	err = copy_from_user(&status, buf, 1);

	/* 根據次設備號和status控制LED */
	gpiod_set_value(led_gpio, status);
	
	return 1;
}

static int led_drv_open (struct inode *node, struct file *file)
{
	//int minor = iminor(node);
	
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	/* 根據次設備號初始化LED */
	gpiod_direction_output(led_gpio, 0);//這裏的1 0 表示邏輯值,因爲DTS裏面設置了GPIO_ACTIVE_HIGH
	
	return 0;
}

static int led_drv_close (struct inode *node, struct file *file)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}

/* 定義自己的file_operations結構體                                              */
static struct file_operations led_drv = {
	.owner	 = THIS_MODULE,
	.open    = led_drv_open,
	.read    = led_drv_read,
	.write   = led_drv_write,
	.release = led_drv_close,
};

/* 4. 從platform_device獲得GPIO
 *    把file_operations結構體告訴內核:註冊驅動程序
 */
static int chip_demo_gpio_probe(struct platform_device *pdev)
{
	//int err;
	
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

	/* 4.1 設備樹中定義有: led-gpios=<...>;	*/
    led_gpio = gpiod_get(&pdev->dev, "led", 0);//第三個參數爲0,表示先不設置引腳!
	if (IS_ERR(led_gpio)) {
		dev_err(&pdev->dev, "Failed to get GPIO for led\n");
		return PTR_ERR(led_gpio);
	}
    
	/* 4.2 註冊file_operations 	*/
	major = register_chrdev(0, "100ask_led", &led_drv);  /* /dev/led */

	led_class = class_create(THIS_MODULE, "100ask_led_class");
	if (IS_ERR(led_class)) {
		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		unregister_chrdev(major, "led");
		gpiod_put(led_gpio);
		return PTR_ERR(led_class);
	}

	device_create(led_class, NULL, MKDEV(major, 0), NULL, "100ask_led%d", 0); /* /dev/100ask_led0 */
        
    return 0;
    
}

static int chip_demo_gpio_remove(struct platform_device *pdev)
{
	device_destroy(led_class, MKDEV(major, 0));
	class_destroy(led_class);
	unregister_chrdev(major, "100ask_led");
	gpiod_put(led_gpio);
    
    return 0;
}


static const struct of_device_id ask100_leds[] = {
    { .compatible = "100ask,leddrv" },
    { },
};

/* 1. 定義platform_driver */
static struct platform_driver chip_demo_gpio_driver = {
    .probe      = chip_demo_gpio_probe,
    .remove     = chip_demo_gpio_remove,
    .driver     = {
        .name   = "100ask_led",
        .of_match_table = ask100_leds,
    },
};

/* 2. 在入口函數註冊platform_driver */
static int __init led_init(void)
{
    int err;
    
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	
    err = platform_driver_register(&chip_demo_gpio_driver); 
	
	return err;
}

/* 3. 有入口函數就應該有出口函數:卸載驅動程序時,就會去調用這個出口函數
 *     卸載platform_driver
 */
static void __exit led_exit(void)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

    platform_driver_unregister(&chip_demo_gpio_driver);
}


/* 7. 其他完善:提供設備信息,自動創建設備節點                                     */

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");

ledtest.c


#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

/*
 * ./ledtest /dev/100ask_led0 on
 * ./ledtest /dev/100ask_led0 off
 */
int main(int argc, char **argv)
{
	int fd;
	char status;
	
	/* 1. 判斷參數 */
	if (argc != 3) 
	{
		printf("Usage: %s <dev> <on | off>\n", argv[0]);
		return -1;
	}

	/* 2. 打開文件 */
	fd = open(argv[1], O_RDWR);
	if (fd == -1)
	{
		printf("can not open file %s\n", argv[1]);
		return -1;
	}

	/* 3. 寫文件 */
	if (0 == strcmp(argv[2], "on"))
	{
		status = 1;
		write(fd, &status, 1);
	}
	else
	{
		status = 0;
		write(fd, &status, 1);
	}
	
	close(fd);
	
	return 0;
}

Makefile

KERN_DIR = /home/clay/linux/IMX6ULL/Linux_Drivers/linux-4.9.88# 板子所用內核源碼的目錄

all:
	make -C $(KERN_DIR) M=`pwd` modules 
	$(CROSS_COMPILE)gcc -o ledtest ledtest.c 

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order
	rm -f ledtest
	
obj-m += leddrv.o

四、運行程序

編譯程序沒有問題後,運行qemu虛擬開發板,並做好準備工作!將

  • 拷貝led.ko和ledtest到NFS中
cp *.ko ledtest ~/linux/qemu/NFS/
  • 在qemu終端,加載led.ko文件
insmod led.ko

在qemu中加載最後一個模塊時,會出現下面的提示信息,但是ctrl+c之後,似乎測試還是可以用的,不知道是怎麼回事。知道的朋友,可以在下面留言一起探討!
在這裏插入圖片描述

  • 在qemu終端,運行應用程序打開LED0
./ledtest /dev/100ask_led0 on

在這裏插入圖片描述

  • 在qemu終端,運行應用程序關閉LED0
./ledtest /dev/100ask_led0 off

在這裏插入圖片描述

大功告成,點亮一個LED系列終於完結!哈哈哈~

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