怎么樣才能讓自己成功一個(gè)合格的Linux驅(qū)動(dòng)開發(fā)人員,很多人喜歡一頭扎進(jìn)源碼里摳各種細(xì)節(jié),我自己實(shí)踐出來的感受是:這樣去學(xué)習(xí)Linux驅(qū)動(dòng)很累,進(jìn)步很慢。內(nèi)核里設(shè)計(jì)的很多驅(qū)動(dòng)框架其實(shí)是在不斷的完善和抽象的,只有理解前人們?cè)O(shè)計(jì)框架時(shí)的思路和想法,才能融合他們的工作體系里。其實(shí)內(nèi)核的開發(fā)人員們寫了不少的文檔和說明,只是我們往往懶于搜索,這些驅(qū)動(dòng)框架的原創(chuàng)者的一兩句總結(jié)可以很好地幫忙我們理解驅(qū)動(dòng)。下面這篇筆記,會(huì)盡量少一點(diǎn)源碼分析。
一、概述
pinctrl和gpio子系統(tǒng)在內(nèi)核里的使用率非常高,和嵌入式產(chǎn)品的關(guān)聯(lián)非常大。從這兩個(gè)子系統(tǒng)開始學(xué)習(xí)驅(qū)動(dòng)開發(fā)是個(gè)不錯(cuò)的入門選擇。
gpio子系統(tǒng)提供了:
- 讀引腳值,高或者低
- 輸出高低電平;
- 部分gpio還負(fù)責(zé)接收IRQ,涉及irqchip子系統(tǒng);
pinctrl子系統(tǒng)提供了:
- 引腳復(fù)用,大多數(shù)引腳都可以通過配置寄存器來選擇復(fù)用成不同的功能,例如某個(gè)引腳即可用作為普通的gpio,也可以作為UART的TX;
- 引腳配置,一般包括上下拉、驅(qū)動(dòng)能力等;
對(duì)于gpio子系統(tǒng):
- 對(duì)于Linux-4.14,源碼位于:drivers/gpio/*
- 文檔位于:Documentation/gpio/*
- 所有的GPIO都會(huì)被編號(hào),從0~N;
- 內(nèi)核里使用GPIO子系統(tǒng)的代碼被稱為Consumer,Consumer是通過[devm_]gpio_request(gpio, label)來申請(qǐng)GPIO;
- 1個(gè)GPIO可以被設(shè)置為輸入或者輸出(gpiod_direction_output());
- 被設(shè)置出輸入時(shí),可以讀到高或者電平(gpiod_direction_input());
- 被設(shè)置出輸出時(shí),可以輸出高或者低電平(gpiod_set_value());
- GPIO可以被映射為L(zhǎng)inux IRQ,暫時(shí)不關(guān)注這一塊;
- GPIO可以在/sys/class/gpio中被導(dǎo)出,供應(yīng)用層使用;
- struct gpio_chip用于表示一個(gè)gpio控制器,可通過gpiochip_add_data()將其注冊(cè)給gpio子系統(tǒng);
查看gpio子系統(tǒng)的文檔:
$ tree Documentation/gpio/
如何為一款CPU的添加gpio驅(qū)動(dòng):
1. 針對(duì)具體CPU構(gòu)造struct gpio_chip,部分成員如下:
2. 向gpio子系統(tǒng)注冊(cè)CPU的struct gpio_chip(gpiochip_add_data());
對(duì)于pinctrl子系統(tǒng):
- 硬件上的一個(gè)引腳通過軟件的配置可作為不同的功能的行為叫做引腳復(fù)用(pin muxing),gpio(input/output功能)是引腳復(fù)用的子集;
- pinctrl子系統(tǒng)是在3.4內(nèi)核中第一次被正式宣布的;
- 對(duì)于Linux-4.14,源碼位于:drivers/pinctrl/*;
- 文檔位于:Documentation/devicetree/bindings/pinctrl/*
- 會(huì)進(jìn)行一些健全性檢查,確保一個(gè)引腳不能同時(shí)用于兩個(gè)功能;
- 驅(qū)動(dòng)可以選擇實(shí)現(xiàn)引腳復(fù)用接口(pin multiplexing interface)或引腳配置接口(pin configuration interface),或兩者都實(shí)現(xiàn);
- gpio子系統(tǒng)會(huì)引用pinctrl子系統(tǒng);
- struct pinctrl_desc用于表示一個(gè)pin控制器,可通過[devm_]pinctrl_register()將其注冊(cè)進(jìn)pinctrl子系統(tǒng);
- 單板在啟動(dòng)時(shí)注冊(cè)一堆的能自動(dòng)激活引腳復(fù)用配置的行為被稱為“pinmux hogs”;
- 類似regulator的get/enable/disable/put操作,pinctrl子系統(tǒng)提供出來的api也有g(shù)et/enable/disable/put操作;
如何為一款CPU的添加pinctrl驅(qū)動(dòng):
1. 針對(duì)具體CPU構(gòu)造struct gpio_chip,部分成員如下:
2. 向pinctrl子系統(tǒng)注冊(cè)CPU的struct pinctrl_desc(devm_pinctrl_register());
二、分析全志H3的gpio和pinctrl功能
我們以全志H3芯片+Linux-4.14內(nèi)核為例分析一下。對(duì)于H3+Linux-4.14,已經(jīng)沒有獨(dú)立的drivers/gpio/gpio-*.c驅(qū)動(dòng),因?yàn)間pio驅(qū)動(dòng)和pinctrl驅(qū)動(dòng)有著緊密的聯(lián)系,所以在pinctrl驅(qū)動(dòng)里會(huì)同時(shí)向gpio子系統(tǒng)注冊(cè)struct gpio_chip,所以對(duì)于H3,我們只要分析pinctrl驅(qū)動(dòng)就夠了。
(一) H3平臺(tái)的gpio功能
device端(不同的板子各自決定自己該怎么配置引腳):
arch/arm/boot/dts/sun8i-h3.dtsi
driver端(提供配置引腳的能力):
drivers/pinctrl/sunxi/pinctrl-sun8i-h3.c
sun8i_h3_pinctrl_probe
? ? sunxi_pinctrl_init(pdev, &sun8i_h3_pinctrl_data);
? ? ? ? sunxi_pinctrl_init_with_variant(_dev, _desc, 0)
sun8i_h3_pinctrl_data的定義如下:
...,中間省略
正好對(duì)應(yīng)芯片手冊(cè)描述的PA組~PG組的引腳;
struct sunxi_desc_pin包含了1個(gè)struct pinctrl_pin_desc ( 所有pinctrl驅(qū)動(dòng)通用 ) 變量和1個(gè)指向struct sunxi_desc_function ( sunxi pinctrl驅(qū)動(dòng)自定義的 ) 數(shù)組的指針:
* struct pinctrl_pin_desc用于描述一個(gè)引腳,對(duì)于PA0引腳,成員number=0,成員name="PA0",類似一個(gè)map(key=0,value="PA0");
* struct sunxi_desc_function用于描述一個(gè)引腳功能,對(duì)于PA0引腳,共有4個(gè)function(gpio_in、gpio_out、uart2[看來只是功能分組,并沒有指定具體是哪一個(gè)引腳功能]、jtag),第一個(gè)function的muxval=0x0(寄存器里的偏移值),name="gpio_in";
向gpio子系統(tǒng)注冊(cè)平臺(tái)gpio驅(qū)動(dòng):
1. 初始化struct gpio_chip(abstract a GPIO controller):
* pctl的類型為struct sunxi_pinctrl *pctl,它是pinctrl-sunxi.c大管家結(jié)構(gòu)體(wrapper for holding driver data together,跟pinctrl-samsung.c里的struct samsung_pinctrl_drv_data類似),本驅(qū)動(dòng)涉及的所有需要在不同函數(shù)間共享的數(shù)據(jù)都會(huì)保存在此大管家結(jié)構(gòu)體內(nèi)。
* pctl->chip就是struct gpio_chip。
2. 注冊(cè)struct gpio_chip:
gpiochip_add_data(pctl->chip, pctl);
將H3的struct gpio_chip注冊(cè)給gpio子系統(tǒng)后,內(nèi)核就有了控制引腳的能力(設(shè)置輸入輸出、讀寫高低電平),但是還無法感知 CPU 有多少引腳可用。
3. 感知所有引腳:
如何通過gpio子系統(tǒng)讀寫gpio
1. 在device tree里選擇要使用的gpio引腳
以gpio-leds驅(qū)動(dòng)為例:
<&pio 0 10 GPIO_ACTIVE_HIGH>;對(duì)應(yīng)引腳PA0。
2. 在驅(qū)動(dòng)源碼里使用gpio子系統(tǒng)提供的統(tǒng)一接口來控制gpio
以gpio-leds驅(qū)動(dòng)為例:
* gpio子系統(tǒng)API的前綴都是gpiod_,完整的API位于:include/linux/gpio/consumer.h;
(二) H3平臺(tái)的pinctrl功能
向pinctrl子系統(tǒng)注冊(cè)平臺(tái)pintrcl驅(qū)動(dòng):
1. 保存所有引腳的硬件信息:
只需保存引腳索引號(hào)即可,后面要配置引腳功能時(shí),會(huì)通過大管家結(jié)構(gòu)體struct sunxi_pinctrl *pctl找到struct sunxi_pinctrl_desc,然后根據(jù)引腳索引號(hào)獲取到引腳的硬件信息。
2. 初始化struct pinctrl_desc(pin controller descriptor):
1) struct pinconf_ops sunxi_pconf_ops,負(fù)責(zé)提供獲取和設(shè)置引腳驅(qū)動(dòng)能力、上下拉(pin configuration)的能力;
2) struct pinctrl_ops sunxi_pctrl_ops,不是必須的,各種雜活都在這里處理(解析設(shè)備樹等,類似ioctl());
3) struct pinmux_ops sunxi_pmx_ops,負(fù)責(zé)提供獲取和配置引腳(pin muxing)的能力;
3. 注冊(cè)struct pinctrl_desc:
devm_pinctrl_register(&pdev->dev, pctrl_desc, pctl);
如何通過pinctrl子系統(tǒng)配置引腳
1. 在device tree里添加引腳配置節(jié)點(diǎn):
以csi 相關(guān)的引腳配置為例:
PE0~PE11都被要求作為csi功能,在pinctrl-sun8i-h3.c中有填寫csi功能對(duì)應(yīng)的硬件配置信息:
一個(gè)“pin configuration node”必須被具體的驅(qū)動(dòng)引用才能發(fā)揮作用:
2. 在每一個(gè)特定功能的驅(qū)動(dòng)被probe時(shí),它引用的引腳功能會(huì)得到初始化:
以csi控制器為例:
drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c
? ? really_probe
? ? ? ? pinctrl_bind_pins(dev);
? ? ? ? ? ? devm_pinctrl_get(dev);? ? // 通過pinctrl子系統(tǒng) 獲取引腳
? ? ? ? ? ? ? ? sunxi_pctrl_dt_node_to_map
? ? ? ? ? ? pinctrl_select_state(); // 設(shè)置引腳功能
? ? sun6i_csi_probe
* pinctr里子系統(tǒng)API的前綴都是pinctrl_,完整的API位于:include/linux/pinctrl/consumer.h;
總結(jié)
引腳的配置是很常用的功能,內(nèi)核對(duì)引腳管理的抽象卻很值得我們學(xué)習(xí)。一般來說,引腳配置這種基礎(chǔ)驅(qū)動(dòng)是應(yīng)該包含在芯片廠商編寫的BSP里,板級(jí)定制只要掌握如何和pinctrl子系統(tǒng)和gpio子系統(tǒng)交互即可,但是當(dāng)遇到bug時(shí),請(qǐng)仔細(xì)分析芯片廠商提供的驅(qū)動(dòng)代碼,軟硬件上任何細(xì)微的改動(dòng)都可能導(dǎo)致意想不到的的問題。