【深度】韋東山:GPIO和Pinctrl子系統(tǒng)的使用 (附免費(fèi)視頻)

作者:韋東山
GPIO和Pinctrl子系統(tǒng)的使用
參考文檔:
a. 內(nèi)核 Documentation\devicetree\bindings\Pinctrl\ 目錄下:
Pinctrl-bindings.txt
b. 內(nèi)核 Documentation\gpio 目錄下:
Pinctrl-bindings.txt
c. 內(nèi)核 Documentation\devicetree\bindings\gpio 目錄下:
gpio.txt
注意:本章的重點(diǎn)在于“使用”,深入講解放在“驅(qū)動大全”的視頻里。
前面的視頻,我們使用直接操作寄存器的方法編寫驅(qū)動。這只是為了讓大家掌握驅(qū)動程序的本質(zhì),在實(shí)際開發(fā)過程中我們可不這樣做,太低效了!如果驅(qū)動開發(fā)都是這樣去查找寄存器,那我們就變成“寄存器工程師”了,即使是做單片機(jī)的都不執(zhí)著于裸寫寄存器了。
Linux下針對引腳有2個重要的子系統(tǒng):GPIO、Pinctrl。
1 Pinctrl子系統(tǒng)重要概念
1.1 引入
無論是哪種芯片,都有類似下圖的結(jié)構(gòu):

要想讓pinA、B用于GPIO,需要設(shè)置IOMUX讓它們連接到GPIO模塊;
要想讓pinA、B用于I2C,需要設(shè)置IOMUX讓它們連接到I2C模塊。
所以GPIO、I2C應(yīng)該是并列的關(guān)系,它們能夠使用之前,需要設(shè)置IOMUX。有時候并不僅僅是設(shè)置IOMUX,還要配置引腳,比如上拉、下拉、開漏等等。
現(xiàn)在的芯片動輒幾百個引腳,在使用到GPIO功能時,讓你一個引腳一個引腳去找對應(yīng)的寄存器,這要瘋掉。術(shù)業(yè)有專攻,這些累活就讓芯片廠家做吧──他們是BSP工程師。我們在他們的基礎(chǔ)上開發(fā),我們是驅(qū)動工程師。開玩笑的,BSP工程師是更懂他自家的芯片,但是如果驅(qū)動工程師看不懂他們的代碼,那你的進(jìn)步也有限啊。
所以,要把引腳的復(fù)用、配置抽出來,做成Pinctrl子系統(tǒng),給GPIO、I2C等模塊使用。
BSP工程師要做什么?看下圖:

等BSP工程師在GPIO子系統(tǒng)、Pinctrl子系統(tǒng)中把自家芯片的支持加進(jìn)去后,我們就可以非常方便地使用這些引腳了:點(diǎn)燈簡直太簡單了。
等等,GPIO模塊在圖中跟I2C不是并列的嗎?干嘛在講Pinctrl時還把GPIO子系統(tǒng)拉進(jìn)來?
大多數(shù)的芯片,沒有單獨(dú)的IOMUX模塊,引腳的復(fù)用、配置等等,就是在GPIO模塊內(nèi)部實(shí)現(xiàn)的。
在硬件上GPIO和Pinctrl是如此密切相關(guān),在軟件上它們的關(guān)系也非常密切。
所以這2個子系統(tǒng)我們一起講解。
1.2 重要概念
從設(shè)備樹開始學(xué)習(xí)Pintrl會比較容易。
主要參考文檔是:內(nèi)核Documentation\devicetree\bindings\pinctrl\pinctrl-bindings.txt
這會涉及2個對象:pin controller、client device。
前者提供服務(wù):可以用它來復(fù)用引腳、配置引腳。
后者使用服務(wù):聲明自己要使用哪些引腳的哪些功能,怎么配置它們。
a. pin controller:
在芯片手冊里你找不到pin controller,它是一個軟件上的概念,你可以認(rèn)為它對應(yīng)IOMUX──用來復(fù)用引腳,還可以配置引腳(比如上下拉電阻等)。
注意,pin controller和GPIO Controller不是一回事,前者控制的引腳可用于GPIO功能、I2C功能;后者只是把引腳配置為輸出、輸出等簡單的功能。
b. client device
“客戶設(shè)備”,誰的客戶?Pinctrl系統(tǒng)的客戶,那就是使用Pinctrl系統(tǒng)的設(shè)備,使用引腳的設(shè)備。它在設(shè)備樹里會被定義為一個節(jié)點(diǎn),在節(jié)點(diǎn)里聲明要用哪些引腳。
下面這個圖就可以把幾個重要概念理清楚:

上圖中,左邊是pincontroller節(jié)點(diǎn),右邊是client device節(jié)點(diǎn):
a. pin state:
對于一個“client device”來說,比如對于一個UART設(shè)備,它有多個“狀態(tài)”:default、sleep等,那對應(yīng)的引腳也有這些狀態(tài)。
怎么理解?
比如默認(rèn)狀態(tài)下,UART設(shè)備是工作的,那么所用的引腳就要復(fù)用為UART功能。
在休眠狀態(tài)下,為了省電,可以把這些引腳復(fù)用為GPIO功能;或者直接把它們配置輸出高電平。
上圖中,pinctrl-names里定義了2種狀態(tài):default、sleep。
第0種狀態(tài)用到的引腳在pinctrl-0中定義,它是state_0_node_a,位于pincontroller節(jié)點(diǎn)中。
第1種狀態(tài)用到的引腳在pinctrl-1中定義,它是state_1_node_a,位于pincontroller節(jié)點(diǎn)中。
當(dāng)這個設(shè)備處于default狀態(tài)時,pinctrl子系統(tǒng)會自動根據(jù)上述信息把所用引腳復(fù)用為uart0功能。
當(dāng)這這個設(shè)備處于sleep狀態(tài)時,pinctrl子系統(tǒng)會自動根據(jù)上述信息把所用引腳配置為高電平。
b. groups和function:
一個設(shè)備會用到一個或多個引腳,這些引腳就可以歸為一組(group);
這些引腳可以復(fù)用為某個功能:function。
當(dāng)然:一個設(shè)備可以用到多能引腳,比如A1、A2兩組引腳,A1組復(fù)用為F1功能,A2組復(fù)用為F2功能。
c. Generic pin multiplexing node和Generic pin configuration node
在上圖左邊的pin controller節(jié)點(diǎn)中,有子節(jié)點(diǎn)或?qū)O節(jié)點(diǎn),它們是給client device使用的。
可以用來描述復(fù)用信息:哪組(group)引腳復(fù)用為哪個功能(function);
可以用來描述配置信息:哪組(group)引腳配置為哪個設(shè)置功能(setting),比如上拉、下拉等。
注意:pin controller節(jié)點(diǎn)的格式,沒有統(tǒng)一的標(biāo)準(zhǔn)?。。。∶考倚酒疾灰粯?。
甚至上面的group、function關(guān)鍵字也不一定有,但是概念是有的。
1.3 示例

1.4 代碼中怎么引用pinctrl
這是透明的,我們的驅(qū)動基本不用管。當(dāng)設(shè)備切換狀態(tài)時,對應(yīng)的pinctrl就會被調(diào)用。
比如在platform_device和platform_driver的枚舉過程中,流程如下:

當(dāng)系統(tǒng)休眠時,也會去設(shè)置該設(shè)備sleep狀態(tài)對應(yīng)的引腳,不需要我們自己去調(diào)用代碼。
非要自己調(diào)用,也有函數(shù):
devm_pinctrl_get_select_default(struct device *dev);??? ??// 使用"default"狀態(tài)的引腳
pinctrl_get_select(struct device *dev, const char *name); // 根據(jù)name選擇某種狀態(tài)的引腳
pinctrl_put(struct pinctrl *p);?? // 不再使用, 退出時調(diào)用
2.GPIO子系統(tǒng)重要概念
2.1 引入
要操作GPIO引腳,先把所用引腳配置為GPIO功能,這通過Pinctrl子系統(tǒng)來實(shí)現(xiàn)。
然后就可以根據(jù)設(shè)置引腳方向(輸入還是輸出)、讀值──獲得電平狀態(tài),寫值──輸出高低電平。
以前我們通過寄存器來操作GPIO引腳,即使LED驅(qū)動程序,對于不同的板子它的代碼也完全不同。
當(dāng)BSP工程師實(shí)現(xiàn)了GPIO子系統(tǒng)后,我們就可以:
a. 在設(shè)備樹里指定GPIO引腳
b. 在驅(qū)動代碼中:
使用GPIO子系統(tǒng)的標(biāo)準(zhǔn)函數(shù)獲得GPIO、設(shè)置GPIO方向、讀取/設(shè)置GPIO值。
這樣的驅(qū)動代碼,將是單板無關(guān)的。
2.2 在設(shè)備樹中指定引腳
在幾乎所有ARM芯片中,GPIO都分為幾組,每組中有若干個引腳。所以在使用GPIO子系統(tǒng)之前,就要先確定:它是哪組的?組里的哪一個?
在設(shè)備樹中,“GPIO組”就是一個GPIO Controller,這通常都由芯片廠家設(shè)置好。我們要做的是找到它名字,比如“gpio1”,然后指定要用它里面的哪個引腳,比如<&gpio1 0>。
有代碼更直觀,下圖是一些芯片的GPIO控制器節(jié)點(diǎn),它們一般都是廠家定義好,在xxx.dtsi文件中:

我們暫時只需要關(guān)心里面的這2個屬性:
gpio-controller;
#gpio-cells = <2>;
“gpio-controller”表示這個節(jié)點(diǎn)是一個GPIO Controller,它下面有很多引腳。
“#gpio-cells = <2>”表示這個控制器下每一個引腳要用2個32位的數(shù)(cell)來描述。
為什么要用2個數(shù)?其實(shí)使用多個cell來描述一個引腳,這是GPIO Controller自己決定的。比如可以用其中一個cell來表示那是哪一個引腳,用另一個cell來表示它是高電平有效還是低電平有效,甚至還可以用更多的cell來示其他特性。
普遍的用法是,用第1個cell來表示哪一個引腳,用第2個cell來表示有效電平:
GPIO_ACTIVE_HIGH : 高電平有效
GPIO_ACTIVE_LOW : 低電平有效
定義GPIO Controller是芯片廠家的事,我們怎么引用某個引腳呢?在自己的設(shè)備節(jié)點(diǎn)中使用屬性"[<name>-]gpios",示例如下:

上圖中,可以使用gpios屬性,也可以使用name-gpios屬性。
2.3 在驅(qū)動代碼中調(diào)用GPIO子系統(tǒng)
在設(shè)備樹中指定了GPIO引腳,在驅(qū)動代碼中如何使用?
也就是GPIO子系統(tǒng)的接口函數(shù)是什么?
GPIO子系統(tǒng)有兩套接口:基于描述符的(descriptor-based)、老的(legacy)。前者的函數(shù)都有前綴“gpiod_”,它使用gpio_desc結(jié)構(gòu)體來表示一個引腳;后者的函數(shù)都有前綴“gpio_”,它使用一個整數(shù)來表示一個引腳。
要操作一個引腳,首先要get引腳,然后設(shè)置方向,讀值、寫值。
驅(qū)動程序中要包含頭文件,
#include <linux/gpio/consumer.h>?? // descriptor-based
或
#include <linux/gpio.h>??????????? // legacy
下表列出常用的函數(shù):

有前綴“devm_”的含義是“設(shè)備資源管理”(Managed Device Resource),這是一種自動釋放資源的機(jī)制。它的思想是“資源是屬于設(shè)備的,設(shè)備不存在時資源就可以自動釋放”。
比如在Linux開發(fā)過程中,先申請了GPIO,再申請內(nèi)存;如果內(nèi)存申請失敗,那么在返回之前就需要先釋放GPIO資源。如果使用devm的相關(guān)函數(shù),在內(nèi)存申請失敗時可以直接返回:設(shè)備的銷毀函數(shù)會自動地釋放已經(jīng)申請了的GPIO資源。
建議使用“devm_”版本的相關(guān)函數(shù)。
舉例,假設(shè)備在設(shè)備樹中有如下節(jié)點(diǎn):
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>;
?????? };
那么可以使用下面的函數(shù)獲得引腳:
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設(shè)置的值是“邏輯值”,不一定等于物理值。
什么意思?

舊的“gpio_”函數(shù)沒辦法根據(jù)設(shè)備樹信息獲得引腳,它需要先知道引腳號。
引腳號怎么確定?
在GPIO子系統(tǒng)中,每注冊一個GPIO Controller時會確定它的“base number”,那么這個控制器里的第n號引腳的號碼就是:base number + n。
但是如果硬件有變化、設(shè)備樹有變化,這個base number并不能保證是固定的,應(yīng)該查看sysfs來確定base number。
2.4 sysfs中的訪問方法
在sysfs中訪問GPIO,實(shí)際上用的就是引腳號,老的方法。
a. 先確定某個GPIO Controller的基準(zhǔn)引腳號(base number),再計(jì)算出某個引腳的號碼。
方法如下:
① 先在開發(fā)板的/sys/class/gpio目錄下,找到各個gpiochipXXX目錄:

② 然后進(jìn)入某個gpiochip目錄,查看文件label的內(nèi)容
③ 根據(jù)label的內(nèi)容對比設(shè)備樹
label內(nèi)容來自設(shè)備樹,比如它的寄存器基地址。用來跟設(shè)備樹(dtsi文件)比較,就可以知道這對應(yīng)哪一個GPIO Controller。
下圖是在100asK_imx6ull上運(yùn)行的結(jié)果,通過對比設(shè)備樹可知gpiochip96對應(yīng)gpio4:

所以gpio4這組引腳的基準(zhǔn)引腳號就是96,這也可以“cat base”來再次確認(rèn)。
b. 基于sysfs操作引腳:
以100ask_imx6ull為例,它有一個按鍵,原理圖如下:

那么GPIO4_14的號碼是96+14=110,可以如下操作讀取按鍵值:
echo ?110 > /sys/class/gpio/export?
echo in > /sys/class/gpio/gpio110/direction?
cat /sys/class/gpio/gpio110/value?
echo ?110 > /sys/class/gpio/unexport
注意:如果驅(qū)動程序已經(jīng)使用了該引腳,那么將會export失敗,會提示下面的錯誤:

對于輸出引腳,假設(shè)引腳號為N,可以用下面的方法設(shè)置它的值為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
3.基于GPIO子系統(tǒng)的LED驅(qū)動程序
3.1 編寫思路
GPIO的地位跟其他模塊,比如I2C、UART的地方是一樣的,要使用某個引腳,需要先把引腳配置為GPIO功能,這要使用Pinctrl子系統(tǒng),只需要在設(shè)備樹里指定就可以。在驅(qū)動代碼上不需要我們做任何事情。
GPIO本身需要確定引腳,這也需要在設(shè)備樹里指定。
設(shè)備樹節(jié)點(diǎn)會被內(nèi)核轉(zhuǎn)換為platform_device。
對應(yīng)的,驅(qū)動代碼中要注冊一個platform_driver,在probe函數(shù)中:獲得引腳、注冊file_operations。
在file_operations中:設(shè)置方向、讀值/寫值。

下圖就是一個設(shè)備樹的例子:

3.2 在設(shè)備樹中添加Pinctrl信息
有些芯片提供了設(shè)備樹生成工具,在GUI界面中選擇引腳功能和配置信息,就可以自動生成Pinctrl子結(jié)點(diǎn)。把它復(fù)制到你的設(shè)備樹文件中,再在client device結(jié)點(diǎn)中引用就可以。
有些芯片只提供文檔,那就去閱讀文檔,一般在內(nèi)核源碼目錄Documentation\devicetree\bindings\pinctrl下面,保存有該廠家的文檔。
如果連文檔都沒有,那只能參考內(nèi)核源碼中的設(shè)備樹文件,在內(nèi)核源碼目錄arch/arm/boot/dts目錄下。
最后一步,網(wǎng)絡(luò)搜索。
Pinctrl子節(jié)點(diǎn)的樣式如下:

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

3.4編程示例
在實(shí)際操作過程中也許會碰到意外的問題,現(xiàn)場演示如何解決。
a. 定義、注冊一個platform_driver
b. 在它的probe函數(shù)里:
b.1 根據(jù)platform_device的設(shè)備樹信息確定GPIO:gpiod_get
b.2 定義、注冊一個file_operations結(jié)構(gòu)體
b.3 在file_operarions中使用GPIO子系統(tǒng)的函數(shù)操作GPIO:
gpiod_direction_output、gpiod_set_value
好處:這些代碼對所有的代碼都是完全一樣的!
使用GIT命令載后,源碼leddrv.c位于這個目錄下:
01_all_series_quickstart\
04_快速入門_正式開始\
02_嵌入式Linux驅(qū)動開發(fā)基礎(chǔ)知識\source\
05_gpio_and_pinctrl\
01_led
摘錄重點(diǎn)內(nèi)容:
a. 注冊platform_driver
注意下面第122行的"100ask,leddrv",它會跟設(shè)備樹中節(jié)點(diǎn)的compatible對應(yīng):
121 static const struct of_device_id ask100_leds[] = {
122???? { .compatible = "100ask,leddrv" },
123???? { },
124 };
125
126 /* 1. 定義platform_driver */
127 static struct platform_driver chip_demo_gpio_driver = {
128???? .probe????? = chip_demo_gpio_probe,
129???? .remove???? = chip_demo_gpio_remove,
130???? .driver???? = {
131???????? .name?? = "100ask_led",
132???????? .of_match_table = ask100_leds,
133???? },
134 };
135
136 /* 2. 在入口函數(shù)注冊platform_driver */
137 static int __init led_init(void)
138 {
139???? int err;
140
141???? printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
142
143???? err = platform_driver_register(&chip_demo_gpio_driver);
144
145???? return err;
146 }
b. 在probe函數(shù)中獲得GPIO
核心代碼是第87行,它從該設(shè)備(對應(yīng)設(shè)備樹中的設(shè)備節(jié)點(diǎn))獲取名為“l(fā)ed”的引腳。在設(shè)備樹中,必定有一屬性名為“l(fā)ed-gpios”或“l(fā)ed-gpio”。
77 /* 4. 從platform_device獲得GPIO
78? *??? 把file_operations結(jié)構(gòu)體告訴內(nèi)核:注冊驅(qū)動程序
79? */
80 static int chip_demo_gpio_probe(struct platform_device *pdev)
81 {
82????? //int err;
83
84????? printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
85
86????? /* 4.1 設(shè)備樹中定義有: led-gpios=<...>; */
87???? led_gpio = gpiod_get(&pdev->dev, "led", 0);
88????? if (IS_ERR(led_gpio)) {
89????????????? dev_err(&pdev->dev, "Failed to get GPIO for led\n");
90????????????? return PTR_ERR(led_gpio);
91????? }
92
c. 注冊file_operations結(jié)構(gòu)體:
這是老套路了:
93????? /* 4.2 注冊file_operations????? */
94????? major = register_chrdev(0, "100ask_led", &led_drv);? /* /dev/led */
95
96????? led_class = class_create(THIS_MODULE, "100ask_led_class");
97????? if (IS_ERR(led_class)) {
98????????????? printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
99????????????? unregister_chrdev(major, "led");
100???????????? gpiod_put(led_gpio);
101???????????? return PTR_ERR(led_class);
102???? }
103
104???? device_create(led_class, NULL, MKDEV(major, 0), NULL, "100ask_led%d", 0); /* /dev/100ask_led0 */
105
d. 在open函數(shù)中調(diào)用GPIO函數(shù)設(shè)置引腳方向:
51 static int led_drv_open (struct inode *node, struct file *file)
52 {
53????? //int minor = iminor(node);
54
55????? printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
56????? /* 根據(jù)次設(shè)備號初始化LED */
57????? gpiod_direction_output(led_gpio, 0);
58
59????? return 0;
60 }
e. 在write函數(shù)中調(diào)用GPIO函數(shù)設(shè)置引腳值:
34 /* write(fd, &val, 1); */
35 static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
36 {
37????? int err;
38????? char status;
39????? //struct inode *inode = file_inode(file);
40????? //int minor = iminor(inode);
41
42????? printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
43????? err = copy_from_user(&status, buf, 1);
44
45????? /* 根據(jù)次設(shè)備號和status控制LED */
46????? gpiod_set_value(led_gpio, status);
47
48????? return 1;
49 }
?
f. 釋放GPIO:
gpiod_put(led_gpio);
4.在100ASK_IMX6ULL上機(jī)實(shí)驗(yàn)
4.1 確定引腳并生成設(shè)備樹節(jié)點(diǎn)
NXP公司對于IMX6ULL芯片,有設(shè)備樹生成工具。我們也把它上傳到GIT去了,使用GIT命令載后,在這個目錄下:
01_all_series_quickstart\
04_快速入門_正式開始\
02_嵌入式Linux驅(qū)動開發(fā)基礎(chǔ)知識\source\
05_gpio_and_pinctrl\
tools\
imx\
安裝“Pins_Tool_for_i.MX_Processors_v6_x64.exe”后運(yùn)行,打開IMX6ULL的配置文件“MCIMX6Y2xxx08.mex”,就可以在GUI界面中選擇引腳,配置它的功能,這就可以自動生成Pinctrl的子節(jié)點(diǎn)信息。
100ASK_IMX6ULL使用的LED原理圖如下,可知引腳是GPIO5_3:

在設(shè)備樹工具中,如下圖操作:

把自動生成的設(shè)備樹信息,放到內(nèi)核源碼arch/arm/boot/dts/100ask_imx6ull-14x14.dts中,代碼如下:
a. Pinctrl信息:
&iomuxc_snvs {
……
??????? myled_for_gpio_subsys: myled_for_gpio_subsys{
??????????? fsl,pins = <
??????????????? MX6ULL_PAD_SNVS_TAMPER3__GPIO5_IO03??????? 0x000110A0
??????????? >;
??????? };
?
b. 設(shè)備節(jié)點(diǎn)信息(放在根節(jié)點(diǎn)下):
myled {
??????????? compatible = "100ask,leddrv";
??????????? pinctrl-names = "default";
??????????? pinctrl-0 = <&myled_for_gpio_subsys>;
??????????? led-gpios = <&gpio5 3 GPIO_ACTIVE_LOW>;
??????? };
4.2 編譯程序
編譯設(shè)備樹后,要更新設(shè)備樹。
編譯驅(qū)動程序時,“l(fā)eddrv_未測試的原始版本.c”是有錯誤信息的,“l(fā)eddrv.c”是修改過的。
測試方法,在板子上執(zhí)行命令:
# insmod? leddrv.ko
# ls /dev/100ask_led0
# ./ledtest /dev/100ask_led0 on
# ./ledtest /dev/100ask_led0 off
?
我是韋東山,專注研究嵌入式linux+ARM 10多年,歡迎大家訂閱我的付費(fèi)視頻:100ask.taobao.com

本文已錄成視頻,學(xué)習(xí)起來更形象直觀。學(xué)起來更容易。
pinctrl子系統(tǒng)重要概念:
https://www.bilibili.com/video/av65976587?p=69
gpio子系統(tǒng)重要概念:
https://www.bilibili.com/video/av65976587?p=70
基于GPIO子系統(tǒng)的LED驅(qū)動程序:
https://www.bilibili.com/video/av65976587?p=71
在100ASK_IMX6ULL上機(jī)實(shí)驗(yàn):
https://www.bilibili.com/video/av65976587?p=72

還沒搞懂的同學(xué)可以加我同事微信13163769879加入交流群討論學(xué)習(xí),