天天看點

嵌入式Linux開發21——pinctrl 和 gpio 子系統

文章目錄

    • I.MX6ULL 的 pinctrl 子系統驅動
      • 1. pin配置資訊詳解
      • 2. 裝置樹中添加 pinctrl 節點模闆
    • GPIO子系統
      • 1.裝置樹中的gpio資訊
      • 2.gpio子系統API函數
        • 2.1 gpio_request 函數
        • 2.2 gpio_free 函數
        • 2.3 gpio_direction_input 函數
        • 2.4 gpio_direction_output 函數
        • 2.5 gpio_get_value 函數
        • 2.6 gpio_set_value 函數
      • 3. 裝置樹中添加 gpio 節點模闆
        • 3.1 建立 test 裝置節點
        • 3.2 添加 pinctrl 資訊
        • 3.3 添加 GPIO 屬性資訊
      • 4.與 gpio 相關的 OF 函數
        • 4.1 of_gpio_named_count 函數
        • 4.2 of_gpio_count 函數
        • 4.3 of_get_named_gpio 函數
    • 程式編寫
      • 1.修改裝置樹檔案
        • 1.1 添加 pinctrl 節點
        • 1.2 添加 LED 裝置節點
        • 1.3 檢查 PIN 是否被其他外設使用
      • 2.LED驅動程式編寫

  我們先來回顧一下上一篇部落格是怎麼初始化 LED 燈所使用的 GPIO,步驟如下:

  ① 修改裝置樹, 添加相應的節點,節點裡面重點是設定 reg 屬性, reg 屬性包括了 GPIO 相關寄存器。

  ② 獲 取 reg 屬 性 中 IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 和IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03 這兩個寄存器位址,并且初始化這兩個寄存器,這兩個寄存器用于設定 GPIO1_IO03 這個 PIN 的複用功能、上下拉、速度等。

  ③ 在②裡面将 GPIO1_IO03 這個 PIN 複用為了 GPIO 功能,是以需要設定 GPIO1_IO03這個 GPIO 相關的寄存器,也就是 GPIO1_DR 和 GPIO1_GDIR 這兩個寄存器。

  總結一下,②中完成對 GPIO1_IO03 這個 PIN 的初始化,設定這個 PIN 的複用功能、上下拉等,比如将 GPIO_IO03 這個 PIN 設定為 GPIO 功能。③中完成對 GPIO 的初始化,設定 GPIO為輸入/輸出等。如果使用過 STM32 的話應該都記得, STM32 也是要先設定某個 PIN 的複用功能、速度、上下拉等,然後再設定 PIN 所對應的 GPIO。其實對于大多數的 32 位 SOC 而言,引腳的設定基本都是這兩方面,是以 Linux 核心針對 PIN 的配置推出了 pinctrl 子系統,對于 GPIO的配置推出了 gpio 子系統。

  大多數 SOC 的 pin 都是支援複用的,比如 I.MX6ULL 的 GPIO1_IO03 既可以作為普通的GPIO 使用,也可以作為 I2C1 的 SDA 等等。此外我們還需要配置 pin 的電氣特性,比如上/下拉、速度、驅動能力等等。傳統的配置 pin 的方式就是直接操作相應的寄存器,但是這種配置方式比較繁瑣、而且容易出問題(比如 pin 功能沖突)。 pinctrl 子系統就是為了解決這個問題而引入的, pinctrl 子系統主要工作内容如下:

  ① 擷取裝置樹中 pin 資訊。

  ② 根據擷取到的 pin 資訊來設定 pin 的複用功能

  ③ 根據擷取到的 pin 資訊來設定 pin 的電氣特性,比如上/下拉、速度、驅動能力等。

  對于我們使用者來講,隻需要在裝置樹裡面設定好某個 pin 的相關屬性即可,其他的初始化工作均由 pinctrl 子系統來完成, pinctrl 子系統源碼目錄為 drivers/pinctrl。

I.MX6ULL 的 pinctrl 子系統驅動

1. pin配置資訊詳解

  要使用 pinctrl 子系統,我們需要在裝置樹裡面設定 PIN 的配置資訊,畢竟 pinctrl 子系統要根據你提供的資訊來配置 PIN 功能,一般會在裝置樹裡面建立一個節點來描述 PIN 的配置資訊。打開 imx6ull.dtsi 檔案,找到一個叫做 iomuxc 的節點,如下所示:

iomuxc: iomuxc@020e0000 {
compatible = "fsl,imx6ul-iomuxc";
reg = <0x020e0000 0x4000>;
};

           

  iomuxc 節點就是 I.MX6ULL 的 IOMUXC 外設對應的節點,看起來内容很少,沒看出什麼跟 PIN 的配置有關的内容啊,别急!打開dts檔案找到如下所示内容:

&iomuxc {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_hog_1>;
imx6ul-evk {
pinctrl_hog_1: hoggrp-1 {
fsl,pins = <
MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059
MX6UL_PAD_GPIO1_IO05__USDHC1_VSELECT 0x17059
MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0x17059
MX6UL_PAD_GPIO1_IO00__ANATOP_OTG1_ID 0x13058
>;
};
......
pinctrl_flexcan1: flexcan1grp{
fsl,pins = <
MX6UL_PAD_UART3_RTS_B__FLEXCAN1_RX 0x1b020
MX6UL_PAD_UART3_CTS_B__FLEXCAN1_TX 0x1b020
>;
};
......
pinctrl_wdog: wdoggrp {
fsl,pins = <
MX6UL_PAD_LCD_RESET__WDOG1_WDOG_ANY 0x30b0
>;
};
};
};

           

  這段代碼就是向 iomuxc 節點追加資料,不同的外設使用的 PIN 不同、其配置也不同,是以一個蘿蔔一個坑,将某個外設所使用的所有 PIN 都組織在一個子節點裡面。代碼中 pinctrl_hog_1 子節點就是和熱插拔有關的 PIN 集合,比如 USB OTG 的 ID 引腳。pinctrl_flexcan1 子節點是 flexcan1 這個外設所使用的 PIN, pinctrl_wdog 子節點是 wdog 外設所使用的 PIN。如果需要在 iomuxc 中添加我們自定義外設的 PIN,那麼需要建立一個子節點,然後将這個自定義外設的所有 PIN 配置資訊都放到這個子節點中。

  pinctrl_hog_1 子節點所使用的 PIN 配置資訊,我們就以UART1_RTS_B這個 PIN 為例,講解一下如何添加 PIN 的配置資訊, UART1_RTS_B 的配置資訊如下:

  首先說明一下, UART1_RTS_B 這個 PIN 是作為 SD 卡的檢測引腳,也就是通過此 PIN 就可以檢測到 SD 卡是否有插入。UART1_RTS_B 的配置資訊分為兩部分:MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 和 0x17059。

  首先來看一下 MX6UL_PAD_UART1_RTS_B__GPIO1_IO19,這是一個宏定義,定義在檔案arch/arm/boot/dts/imx6ul-pinfunc.h 中,内容如下:

#define MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x0090 0x031C 0x0000
0x5 0x0

           

  宏定義 MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 表示将UART1_RTS_B 這個 IO 複用為 GPIO1_IO19。此宏定義後面跟着 5 個數字,也就是這個宏定義的具體值,如下所示:

  這 5 個值的含義如下所示:

  綜上所述可知:0x0090: mux_reg 寄存器偏移位址;0x031C: conf_reg 寄存器偏移位址;0x0000: input_reg 寄存器偏移位址,有些外設有 input_reg 寄存器,有 input_reg 寄存器的外設需要配置 input_reg 寄存器。沒有的話就不需要設定, UART1_RTS_B 這個 PIN 在做GPIO1_IO19 的時候是沒有 input_reg 寄存器,是以這裡 intput_reg 是無效的;0x5:mux_reg 寄存器值,在這裡就相當于設定IOMUXC_SW_MUX_CTL_PAD_UART1_RTS_B 寄存器為0x5,也即是設定 UART1_RTS_B 這個 PIN 複用為 GPIO1_IO19;0x0: input_reg 寄存器值,在這裡無效。

  後面0x17059 就是 conf_reg 寄存器值,此值由使用者自行設定,通過此值來設定一個 IO 的上/下拉、驅動能力和速度等。在這裡就相當于設定寄存器IOMUXC_SW_PAD_CTL_PAD_UART1_RTS_B 的值為 0x17059。

2. 裝置樹中添加 pinctrl 節點模闆

  這裡我們虛拟一個名為“test”的裝置, test 使用了 GPIO1_IO00 這個 PIN 的 GPIO 功能, pinctrl 節點添加過程如下:

  1.建立對應的節點

  同一個外設的 PIN 都放到一個節點裡面,打開 imx6ull-emmc.dts,在 iomuxc 節點中的“imx6ul-evk”子節點下添加“pinctrl_test”節點,注意!節點字首一定要為“pinctrl_”。

  2、添加“fsl,pins”屬性

  裝置樹是通過屬性來儲存資訊的,是以我們需要添加一個屬性,屬性名字一定要為“fsl,pins”,因為對于 I.MX 系列 SOC 而言, pinctrl 驅動程式是通過讀取“fsl,pins”屬性值來擷取 PIN 的配置資訊。

  3、在“fsl,pins”屬性中添加 PIN 配置資訊

  最後在“fsl,pins”屬性中添加具體的 PIN 配置資訊,完成以後如下所示:

pinctrl_test: testgrp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO00__GPIO1_IO00 config /*config 是具體設定值*/
>;
};

           

GPIO子系統

  上一小節講解了 pinctrl 子系統, pinctrl 子系統重點是設定 PIN(有的 SOC 叫做 PAD)的複用和電氣屬性,如果 pinctrl 子系統将一個 PIN 複用為 GPIO 的話,那麼接下來就要用到 gpio 子系統了。 gpio 子系統顧名思義,就是用于初始化 GPIO 并且提供相應的 API 函數,比如設定 GPIO為輸入輸出,讀取 GPIO 的值等。 gpio 子系統的主要目的就是友善驅動開發者使用 gpio,驅動開發者在裝置樹中添加 gpio 相關資訊,然後就可以在驅動程式中使用 gpio 子系統提供的 API函數來操作 GPIO, Linux 核心向驅動開發者屏蔽掉了 GPIO 的設定過程,極大的友善了驅動開發者使用 GPIO。

1.裝置樹中的gpio資訊

  筆者使用的I.MX6ULL開發闆上的 UART1_RTS_B 做為 SD 卡的檢測引腳, UART1_RTS_B 複用為 GPIO1_IO19,通過讀取這個 GPIO 的高低電平就可以知道 SD 卡有沒有插入。首先肯定是将 UART1_RTS_B 這個 PIN 複用為 GPIO1_IO19,并且設定電氣屬性,也就是上一小節講的pinctrl 節點。打開 imx6ull-14x14-emmc.dts, UART1_RTS_B 這個 PIN 的 pincrtl 設定如下:

pinctrl_hog_1: hoggrp-1 {
fsl,pins = <
MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059 /* SD1 CD */
......
>;
};

           

  設定 UART1_RTS_B 這個 PIN 為 GPIO1_IO19。pinctrl 配置好以後就是設定 gpio 了, SD 卡驅動程式通過讀取 GPIO1_IO19 的值來判斷 SD卡有沒有插入,但是 SD 卡驅動程式怎麼知道 CD 引腳連接配接的 GPIO1_IO19 呢?肯定是需要裝置樹告訴驅動啊!在裝置樹中 SD 卡節點下添加一個屬性來描述 SD 卡的 CD 引腳就行了, SD卡驅動直接讀取這個屬性值就知道 SD 卡的 CD 引腳使用的是哪個 GPIO 了。 SD 卡連接配接在I.MX6ULL 的 usdhc1 接口上,在 imx6ull-14x14-emmc.dts 中找到名為“usdhc1”的節點,這個節點就是 SD 卡裝置節點,如下所示:

&usdhc1 {
pinctrl-names = "default", "state_100mhz", "state_200mhz";
pinctrl-0 = <&pinctrl_usdhc1>;
pinctrl-1 = <&pinctrl_usdhc1_100mhz>;
pinctrl-2 = <&pinctrl_usdhc1_200mhz>;
/* pinctrl-3 = <&pinctrl_hog_1>; */
cd-gpios = <&gpio1 19 GPIO_ACTIVE_LOW>;
keep-power-in-suspend;
enable-sdio-wakeup;
vmmc-supply = <&reg_sd1_vmmc>;
status = "okay";
};

           

  屬性“cd-gpios”描述了 SD 卡的 CD 引腳使用的哪個 IO。屬性值一共有三個,我們來看一下這三個屬性值的含義,“&gpio1”表示 CD 引腳所使用的 IO 屬于 GPIO1 組,“19”表示 GPIO1 組的第 19 号 IO,通過這兩個值 SD 卡驅動程式就知道 CD 引腳使用了 GPIO1_IO19這 GPIO。“GPIO_ACTIVE_LOW”表示低電平有效,如果改為“GPIO_ACTIVE_HIGH”就表示高電平有效。

  根據上面這些資訊, SD 卡驅動程式就可以使用 GPIO1_IO19 來檢測 SD 卡的 CD 信号了。

2.gpio子系統API函數

  對于驅動開發人員,設定好裝置樹以後就可以使用 gpio 子系統提供的 API 函數來操作指定的 GPIO, gpio 子系統向驅動開發人員屏蔽了具體的讀寫寄存器過程。這就是驅動分層與分離的好處,大家各司其職,做好自己的本職工作即可。 gpio 子系統提供的常用的 API 函數有下面幾個:

2.1 gpio_request 函數

  gpio_request 函數用于申請一個 GPIO 管腳,在使用一個 GPIO 之前一定要使用 gpio_request進行申請,函數原型如下:

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

  gpio:要申請的 gpio 标号,使用 of_get_named_gpio 函數從裝置樹擷取指定 GPIO 屬性資訊,此函數會傳回這個 GPIO 的标号。

  label:給 gpio 設定個名字。

  傳回值: 0,申請成功;其他值,申請失敗。

2.2 gpio_free 函數

  如果不使用某個 GPIO 了,那麼就可以調用 gpio_free 函數進行釋放。函數原型如下:

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

  gpio:要釋放的 gpio 标号。

  傳回值: 無。

2.3 gpio_direction_input 函數

  此函數用于設定某個 GPIO 為輸入,函數原型如下所示:

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

  gpio:要設定為輸入的 GPIO 标号。

  傳回值: 0,設定成功;負值,設定失敗。

2.4 gpio_direction_output 函數

  此函數用于設定某個 GPIO 為輸出,并且設定預設輸出值,函數原型如下:

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

  gpio:要設定為輸出的 GPIO 标号。

  value: GPIO 預設輸出值。

  傳回值: 0,設定成功;負值,設定失敗。

2.5 gpio_get_value 函數

  此函數用于擷取某個 GPIO 的值(0 或 1),此函數是個宏,定義所示:

#define gpio_get_value __gpio_get_value
int __gpio_get_value(unsigned gpio)
           

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

  gpio:要擷取的 GPIO 标号。

  傳回值: 非負值,得到的 GPIO 值;負值,擷取失敗。

2.6 gpio_set_value 函數

  此函數用于設定某個 GPIO 的值,此函數是個宏,定義如下:

#define gpio_set_value __gpio_set_value
void __gpio_set_value(unsigned gpio, int value)

           

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

  gpio:要設定的 GPIO 标号。

  value: 要設定的值。

  傳回值: 無。

  關于 gpio 子系統常用的 API 函數就講這些,這些是我們用的最多的。

3. 裝置樹中添加 gpio 節點模闆

3.1 建立 test 裝置節點

  在根節點“/”下建立 test 裝置子節點。

3.2 添加 pinctrl 資訊

  前文中我們建立了 pinctrl_test 節點,此節點描述了 test 裝置所使用的 GPIO1_IO00 這個 PIN 的資訊,我們要将這節點添加到 test 裝置節點中,如下所示:

test {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_test>;
/* 其他節點内容 */
 };

           

  先添加 pinctrl-names 屬性,此屬性描述 pinctrl 名字為“default”,而後添加 pinctrl-0 節點,此節點引用前文中建立的 pinctrl_test 節點,表示 tset 裝置的所使用的 PIN 資訊儲存在 pinctrl_test 節點中。

3.3 添加 GPIO 屬性資訊

  我們最後需要在 test 節點中添加 GPIO 屬性資訊,表明 test 所使用的 GPIO 是哪個引腳,添加完成以後如下所示:

test {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_test>;
gpio = <&gpio1 0 GPIO_ACTIVE_LOW>;
};

           

4.與 gpio 相關的 OF 函數

  我們定義了一個名為“gpio”的屬性, gpio 屬性描述了 test 這個裝置所使用的 GPIO。在驅動程式中需要讀取 gpio 屬性内容, Linux 核心提供了幾個與 GPIO 有關的 OF 函數,常用的幾個 OF 函數如下所示:

4.1 of_gpio_named_count 函數

  of_gpio_named_count 函數用于擷取裝置樹某個屬性裡面定義了幾個 GPIO 資訊,要注意的是空的 GPIO 資訊也會被統計到,比如:

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

  np:裝置節點。

  propname:要統計的 GPIO 屬性。

  傳回值: 正值,統計到的 GPIO 數量;負值,失敗。

4.2 of_gpio_count 函數

  和 of_gpio_named_count 函數一樣,但是不同的地方在于,此函數統計的是“gpios”這個屬性的 GPIO 數量,而 of_gpio_named_count 函數可以統計任意屬性的 GPIO 資訊,函數原型如下所示:

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

  np:裝置節點。

  傳回值: 正值,統計到的 GPIO 數量;負值,失敗。

4.3 of_get_named_gpio 函數

  此函數擷取 GPIO 編号,因為 Linux 核心中關于 GPIO 的 API 函數都要使用 GPIO 編号,此函數會将裝置樹中類似<&gpio5 7 GPIO_ACTIVE_LOW>的屬性資訊轉換為對應的 GPIO 編号,此函數在驅動中使用很頻繁!函數原型如下:

int of_get_named_gpio(struct device_node *np,
const char *propname,
int index)

           

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

  np:裝置節點。

  propname:包含要擷取 GPIO 資訊的屬性名。

  index: GPIO 索引,因為一個屬性裡面可能包含多個 GPIO,此參數指定要擷取哪個 GPIO的編号,如果隻有一個 GPIO 資訊的話此參數為 0。

  傳回值: 正值,擷取到的 GPIO 編号;負值,失敗。

程式編寫

1.修改裝置樹檔案

1.1 添加 pinctrl 節點

  開發闆上的 LED 燈使用了 GPIO1_IO03 這個 PIN,打開 imx6ull-14x14-evk.dts,在 iomuxc 節點的 imx6ul-evk 子節點下建立一個名為“pinctrl_led”的子節點,節點内容如下所示:

pinctrl_led: ledgrp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0 /* LED0 */
>;
};

           

  将 GPIO1_IO03 這個 PIN 複用為 GPIO1_IO03,電氣屬性值為 0X10B0。

1.2 添加 LED 裝置節點

  在根節點“/”下建立 LED 燈節點,節點名為“gpioled”,節點内容如下:

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

           

  pinctrl-0 屬性設定 LED 燈所使用的 PIN 對應的 pinctrl 節點。led-gpio 屬性指定了 LED 燈所使用的 GPIO,在這裡就是 GPIO1 的 IO03,低電平有效。稍後編寫驅動程式的時候會擷取 led-gpio 屬性的内容來得到 GPIO 編号,因為 gpio 子系統的 API 操作函數需要 GPIO 編号。

1.3 檢查 PIN 是否被其他外設使用

  很多初次接觸裝置樹的驅動開發人員很容易因為這個小問題栽了大跟頭!因為我們所使用的裝置樹基本都是在半導體廠商提供的裝置樹檔案基礎上修改而來的,而半導體廠商提供的裝置樹是根據自己官方開發闆編寫的,很多 PIN 的配置和我們所使用的開發闆不一樣。比如 A 這個引腳在官方開發闆接的是 I2C 的 SDA,而我們所使用的硬體可能将 A 這個引腳接到了其他的外設,比如 LED 燈上,接不同的外設, A 這個引腳的配置就不同。一個引腳一次隻能實作一個功能,如果 A 引腳在裝置樹中配置為了 I2C 的 SDA 信号,那麼 A 引腳就不能再配置為 GPIO,否則的話驅動程式在申請 GPIO 的時候就會失敗。檢查 PIN 有沒有被其他外設使用包括兩個方面:

  ①、檢查 pinctrl 設定。

  ②、如果這個 PIN 配置為 GPIO 的話,檢查這個 GPIO 有沒有被别的外設使用

2.LED驅動程式編寫

#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define GPIOLED_CNT			1		  	/* 裝置号個數 */
#define GPIOLED_NAME		"gpioled"	/* 名字 */
#define LEDOFF 				0			/* 關燈 */
#define LEDON 				1			/* 開燈 */

/* gpioled裝置結構體 */
struct gpioled_dev{
	dev_t devid;			/* 裝置号 	 */
	struct cdev cdev;		/* cdev 	*/
	struct class *class;	/* 類 		*/
	struct device *device;	/* 裝置 	 */
	int major;				/* 主裝置号	  */
	int minor;				/* 次裝置号   */
	struct device_node	*nd; /* 裝置節點 */
	int led_gpio;			/* led所使用的GPIO編号		*/
};

struct gpioled_dev gpioled;	/* led裝置 */

/*
 * @description		: 打開裝置
 * @param - inode 	: 傳遞給驅動的inode
 * @param - filp 	: 裝置檔案,file結構體有個叫做private_data的成員變量
 * 					  一般在open的時候将private_data指向裝置結構體。
 * @return 			: 0 成功;其他 失敗
 */
static int led_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &gpioled; /* 設定私有資料 */
	return 0;
}

/*
 * @description		: 從裝置讀取資料 
 * @param - filp 	: 要打開的裝置檔案(檔案描述符)
 * @param - buf 	: 傳回給使用者空間的資料緩沖區
 * @param - cnt 	: 要讀取的資料長度
 * @param - offt 	: 相對于檔案首位址的偏移
 * @return 			: 讀取的位元組數,如果為負值,表示讀取失敗
 */
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
	return 0;
}

/*
 * @description		: 向裝置寫資料 
 * @param - filp 	: 裝置檔案,表示打開的檔案描述符
 * @param - buf 	: 要寫給裝置寫入的資料
 * @param - cnt 	: 要寫入的資料長度
 * @param - offt 	: 相對于檔案首位址的偏移
 * @return 			: 寫入的位元組數,如果為負值,表示寫入失敗
 */
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
	int retvalue;
	unsigned char databuf[1];
	unsigned char ledstat;
	struct gpioled_dev *dev = filp->private_data;

	retvalue = copy_from_user(databuf, buf, cnt);
	if(retvalue < 0) {
		printk("kernel write failed!\r\n");
		return -EFAULT;
	}

	ledstat = databuf[0];		/* 擷取狀态值 */

	if(ledstat == LEDON) {	
		gpio_set_value(dev->led_gpio, 0);	/* 打開LED燈 */
	} else if(ledstat == LEDOFF) {
		gpio_set_value(dev->led_gpio, 1);	/* 關閉LED燈 */
	}
	return 0;
}

/*
 * @description		: 關閉/釋放裝置
 * @param - filp 	: 要關閉的裝置檔案(檔案描述符)
 * @return 			: 0 成功;其他 失敗
 */
static int led_release(struct inode *inode, struct file *filp)
{
	return 0;
}

/* 裝置操作函數 */
static struct file_operations gpioled_fops = {
	.owner = THIS_MODULE,
	.open = led_open,
	.read = led_read,
	.write = led_write,
	.release = 	led_release,
};

/*
 * @description	: 驅動出口函數
 * @param 		: 無
 * @return 		: 無
 */
static int __init led_init(void)
{
	int ret = 0;

	/* 設定LED所使用的GPIO */
	/* 1、擷取裝置節點:gpioled */
	gpioled.nd = of_find_node_by_path("/gpioled");
	if(gpioled.nd == NULL) {
		printk("gpioled node not find!\r\n");
		return -EINVAL;
	} else {
		printk("gpioled node find!\r\n");
	}

	/* 2、 擷取裝置樹中的gpio屬性,得到LED所使用的LED編号 */
	gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);
	if(gpioled.led_gpio < 0) {
		printk("can't get led-gpio");
		return -EINVAL;
	}
	printk("led-gpio num = %d\r\n", gpioled.led_gpio);

	/* 3、設定GPIO1_IO03為輸出,并且輸出高電平,預設關閉LED燈 */
	ret = gpio_direction_output(gpioled.led_gpio, 1);
	if(ret < 0) {
		printk("can't set gpio!\r\n");
	}

	/* 注冊字元裝置驅動 */
	/* 1、建立裝置号 */
	if (gpioled.major) {		/*  定義了裝置号 */
		gpioled.devid = MKDEV(gpioled.major, 0);
		register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);
	} else {						/* 沒有定義裝置号 */
		alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME);	/* 申請裝置号 */
		gpioled.major = MAJOR(gpioled.devid);	/* 擷取配置設定号的主裝置号 */
		gpioled.minor = MINOR(gpioled.devid);	/* 擷取配置設定号的次裝置号 */
	}
	printk("gpioled major=%d,minor=%d\r\n",gpioled.major, gpioled.minor);	
	
	/* 2、初始化cdev */
	gpioled.cdev.owner = THIS_MODULE;
	cdev_init(&gpioled.cdev, &gpioled_fops);
	
	/* 3、添加一個cdev */
	cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);

	/* 4、建立類 */
	gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
	if (IS_ERR(gpioled.class)) {
		return PTR_ERR(gpioled.class);
	}

	/* 5、建立裝置 */
	gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);
	if (IS_ERR(gpioled.device)) {
		return PTR_ERR(gpioled.device);
	}
	return 0;
}

/*
 * @description	: 驅動出口函數
 * @param 		: 無
 * @return 		: 無
 */
static void __exit led_exit(void)
{
	/* 登出字元裝置驅動 */
	cdev_del(&gpioled.cdev);/*  删除cdev */
	unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); /* 登出裝置号 */

	device_destroy(gpioled.class, gpioled.devid);
	class_destroy(gpioled.class);
}

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

           

  與上一篇文章内容嵌入式Linux開發20——裝置樹下的 LED 驅動實驗相比,隻是取消掉了配置寄存器的過程,改為使用 Linux 核心提供的 API 函數。在 GPIO 操作上更加的規範化,符合 Linux代碼架構,而且也簡化了 GPIO 驅動開發的難度。

繼續閱讀