Linux Pinctrl子系統(tǒng)使用及說(shuō)明
第一部分:核心概念與工作機(jī)制
1. Pinctrl 子系統(tǒng)概述
Pinctrl(Pin Control)子系統(tǒng)是Linux內(nèi)核中用于管理和配置SoC引腳的標(biāo)準(zhǔn)框架。
它解決了傳統(tǒng)直接操作寄存器配置引腳復(fù)用和電氣特性所帶來(lái)的繁瑣、易錯(cuò)、功能沖突等問(wèn)題。
1.1 主要功能
- 引腳復(fù)用(Pin Muxing):將同一物理引腳切換為不同的功能(如GPIO、I2C、UART、SPI等)。
- 引腳配置(Pin Configuration):設(shè)置引腳的電氣特性,包括上拉/下拉、驅(qū)動(dòng)能力、開(kāi)漏、壓擺率等。
- 引腳分組(Pin Groups):將一組引腳定義為邏輯組,方便整體配置。
- 狀態(tài)管理(State Management):為設(shè)備定義多個(gè)引腳狀態(tài)(如
default、sleep、idle),并在運(yùn)行時(shí)動(dòng)態(tài)切換。
1.2 架構(gòu)分層
Pinctrl子系統(tǒng)采用服務(wù)端(Provider)— 客戶端(Consumer) 模型:
- 服務(wù)端(Provider):由芯片廠商實(shí)現(xiàn),負(fù)責(zé)描述SoC中所有引腳、引腳組、可復(fù)用功能以及硬件操作函數(shù)(
struct pinctrl_desc+ ops)。 - 客戶端(Consumer):由設(shè)備驅(qū)動(dòng)編寫(xiě)者使用,通過(guò)在設(shè)備樹(shù)中聲明所需引腳狀態(tài),讓Pinctrl子系統(tǒng)自動(dòng)完成配置。
下圖展示了子系統(tǒng)內(nèi)部模塊間的協(xié)作關(guān)系:

2. 核心數(shù)據(jù)結(jié)構(gòu)
Pinctrl子系統(tǒng)通過(guò)一系列精心設(shè)計(jì)的數(shù)據(jù)結(jié)構(gòu),將服務(wù)端能力、客戶端需求以及狀態(tài)配置連接起來(lái)。
2.1 服務(wù)端核心結(jié)構(gòu)
struct pinctrl_desc—— 引腳控制器的描述符
每個(gè)Pinctrl控制器驅(qū)動(dòng)都必須提供一個(gè)pinctrl_desc實(shí)例,用于向內(nèi)核描述該控制器的資源與操作能力。
struct pinctrl_desc {
const char *name; // 控制器名稱
const struct pinctrl_pin_desc *pins; // 引腳數(shù)組
unsigned int npins; // 引腳數(shù)量
const struct pinctrl_ops *pctlops; // 全局控制操作
const struct pinmux_ops *pmxops; // 復(fù)用操作
const struct pinconf_ops *confops; // 配置操作
struct module *owner;
// ... 其他參數(shù)
};
三大操作集(ops)是驅(qū)動(dòng)必須實(shí)現(xiàn)的核心函數(shù)表:
pinctrl_ops:提供引腳組信息、設(shè)備樹(shù)節(jié)點(diǎn)到映射的轉(zhuǎn)換(dt_node_to_map)等。pinmux_ops:提供復(fù)用功能數(shù)量、名稱、組以及set_mux回調(diào)(最終寫(xiě)寄存器)。pinconf_ops:提供引腳或引腳組的電氣特性讀取/設(shè)置(如pin_config_set)。
struct pinctrl_dev—— 控制器實(shí)例
內(nèi)核通過(guò)pinctrl_register()將pinctrl_desc注冊(cè)為一個(gè)pinctrl_dev對(duì)象,并掛載到全局鏈表中。
2.2 客戶端核心結(jié)構(gòu)
struct dev_pin_info—— 掛載在struct device中的引腳信息
每個(gè)設(shè)備(struct device)如果啟用了CONFIG_PINCTRL,都會(huì)包含一個(gè)dev_pin_info指針,記錄該設(shè)備所使用的Pinctrl句柄及各種狀態(tài)。
struct dev_pin_info {
struct pinctrl *p; // 該設(shè)備的pinctrl句柄
struct pinctrl_state *default_state; // 默認(rèn)狀態(tài)(如“default”)
struct pinctrl_state *init_state; // 初始化狀態(tài)(可選)
#ifdef CONFIG_PM
struct pinctrl_state *sleep_state; // 睡眠狀態(tài)
struct pinctrl_state *idle_state; // 空閑狀態(tài)
#endif
};
struct pinctrl—— 客戶端句柄
代表一個(gè)設(shè)備獲取到的完整Pinctrl資源,內(nèi)部包含了該設(shè)備所有可能的狀態(tài)(states鏈表)以及從設(shè)備樹(shù)解析出的映射表(dt_maps)。
struct pinctrl {
struct list_head node;
struct device *dev;
struct list_head states; // 該設(shè)備的所有狀態(tài)
struct pinctrl_state *state; // 當(dāng)前激活的狀態(tài)
struct list_head dt_maps;
struct kref users;
};
struct pinctrl_state與struct pinctrl_setting
- 每個(gè)狀態(tài)(如“default”)對(duì)應(yīng)一個(gè)
pinctrl_state對(duì)象,內(nèi)部settings鏈表包含多個(gè)pinctrl_setting。 pinctrl_setting表示一個(gè)具體的配置項(xiàng):要么是復(fù)用設(shè)置(哪個(gè)group切換到哪個(gè)function),要么是配置設(shè)置(某個(gè)引腳的上下拉/驅(qū)動(dòng)能力)。
struct pinctrl_setting {
struct list_head node;
enum pinctrl_map_type type; // MUX_GROUP 或 CONFIGS_PIN/CONFIGS_GROUP
struct pinctrl_dev *pctldev;
union {
struct pinctrl_setting_mux mux; // 復(fù)用信息:group + func
struct pinctrl_setting_configs configs; // 配置信息:引腳/組 + 配置值數(shù)組
} data;
};
3. 設(shè)備樹(shù)描述與解析
3.1 服務(wù)端設(shè)備樹(shù)節(jié)點(diǎn)
芯片廠商會(huì)在.dtsi中定義Pinctrl控制器節(jié)點(diǎn),例如rk3568.dtsi:
pinctrl: pinctrl {
compatible = "rockchip,rk3568-pinctrl";
rockchip,grf = <&grf>;
rockchip,pmu = <&pmugrf>;
#address-cells = <2>;
#size-cells = <2>;
ranges;
gpio0: gpio@fdd60000 {
compatible = "rockchip,gpio-bank";
reg = <0x0 0xfdd60000 0x0 0x100>;
interrupts = <GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
gpio-controller;
#gpio-cells = <2>;
gpio-ranges = <&pinctrl 0 0 32>;
};
// ... 其他 GPIO 組
};
同時(shí),在rk3568-pinctrl.dtsi中定義了所有可用的引腳功能組(groups)和配置(pinconf),例如I2C3的兩種引腳組:
i2c3m0_xfer: i2c3m0-xfer {
rockchip,pins =
<1 RK_PA1 1 &pcfg_pull_none_smt>, // i2c3_sclm0
<1 RK_PA0 1 &pcfg_pull_none_smt>; // i2c3_sdam0
};
i2c3m1_xfer: i2c3m1-xfer {
rockchip,pins =
<3 RK_PB5 4 &pcfg_pull_none_smt>,
<3 RK_PB6 4 &pcfg_pull_none_smt>;
};
每個(gè)rockchip,pins屬性包含四個(gè)字段:
- PIN_BANK:GPIO組(0~4)
- PIN_BANK_IDX:組內(nèi)引腳索引(如RK_PA0=0,RK_PC0=16)
- MUX:復(fù)用值(0表示GPIO,1表示功能1,依芯片手冊(cè))
- phandle:指向引腳配置節(jié)點(diǎn)(如
&pcfg_pull_none_smt),定義上下拉、驅(qū)動(dòng)能力等。
3.2 客戶端設(shè)備樹(shù)節(jié)點(diǎn)
外設(shè)驅(qū)動(dòng)在它的節(jié)點(diǎn)中通過(guò)pinctrl-*屬性聲明所需引腳狀態(tài)。例如一個(gè)I2C控制器:
&i2c3 {
pinctrl-names = "default", "sleep";
pinctrl-0 = <&i2c3m0_xfer>;
pinctrl-1 = <&i2c3m1_xfer>;
status = "okay";
};
pinctrl-names:定義狀態(tài)名稱列表,內(nèi)核預(yù)定義了PINCTRL_STATE_DEFAULT(“default”)、PINCTRL_STATE_SLEEP(“sleep”)等,也可以自定義。pinctrl-0:對(duì)應(yīng)第一個(gè)狀態(tài)(“default”)的引腳組引用,可以是單個(gè)或多個(gè)phandle。pinctrl-1:對(duì)應(yīng)第二個(gè)狀態(tài)(“sleep”)的引腳組。
3.3 從設(shè)備樹(shù)到內(nèi)部映射
當(dāng)內(nèi)核解析客戶端設(shè)備樹(shù)時(shí),會(huì)為設(shè)備創(chuàng)建pinctrl句柄,并調(diào)用服務(wù)端驅(qū)動(dòng)的dt_node_to_map回調(diào),將設(shè)備樹(shù)描述轉(zhuǎn)換為pinctrl_map數(shù)組。
以Rockchip驅(qū)動(dòng)為例,rockchip_dt_node_to_map會(huì)做兩件事:
根據(jù)設(shè)備樹(shù)節(jié)點(diǎn)名找到對(duì)應(yīng)的rockchip_pin_group。
創(chuàng)建兩個(gè)映射條目:
- 第一個(gè)是
PIN_MAP_TYPE_MUX_GROUP,記錄function名稱(取自父節(jié)點(diǎn)名)和group名稱(取自節(jié)點(diǎn)名)。 - 隨后為組內(nèi)每個(gè)引腳創(chuàng)建
PIN_MAP_TYPE_CONFIGS_PIN條目,記錄引腳名和從pcfg_xxx解析出的配置值。
static int rockchip_dt_node_to_map(...)
{
// 找到 group
grp = pinctrl_name_to_group(info, np->name);
// 分配 new_map,數(shù)量為 1(復(fù)用) + npins(配置)
new_map[0].type = PIN_MAP_TYPE_MUX_GROUP;
new_map[0].data.mux.function = parent->name;
new_map[0].data.mux.group = np->name;
// 填充每個(gè)引腳的配置 map
for (i = 0; i < grp->npins; i++) {
new_map[i+1].type = PIN_MAP_TYPE_CONFIGS_PIN;
new_map[i+1].data.configs.configs = grp->data[i].configs;
...
}
}
這些pinctrl_map隨后會(huì)被add_setting轉(zhuǎn)換為pinctrl_setting,并掛載到對(duì)應(yīng)狀態(tài)的settings鏈表中,供后續(xù)激活使用。
4. 控制器驅(qū)動(dòng)初始化流程
以Rockchip pinctrl驅(qū)動(dòng)(pinctrl-rockchip.c)為例,展示一個(gè)Pinctrl服務(wù)端是如何初始化的。
4.1 驅(qū)動(dòng)入口
static struct platform_driver rockchip_pinctrl_driver = {
.probe = rockchip_pinctrl_probe,
.driver = {
.name = "rockchip-pinctrl",
.of_match_table = rockchip_pinctrl_dt_match,
},
};
postcore_initcall(rockchip_pinctrl_drv_register);
驅(qū)動(dòng)使用postcore_initcall,在系統(tǒng)初始化早期完成注冊(cè),確保其他設(shè)備probe時(shí)Pinctrl已經(jīng)可用。
4.2 probe函數(shù)核心步驟
rockchip_pinctrl_probe完成以下關(guān)鍵工作:
分配私有數(shù)據(jù)結(jié)構(gòu):struct rockchip_pinctrl *info
獲取硬件資源:regmap(通過(guò)syscon_node_to_regmap或devm_ioremap_resource)
解析SOC數(shù)據(jù):rockchip_pinctrl_get_soc_data,填充rockchip_pin_ctrl
注冊(cè)Pinctrl控制器:調(diào)用rockchip_pinctrl_register,該函數(shù)內(nèi)部:
- 填充
pinctrl_desc的各個(gè)ops(rockchip_pctrl_ops,rockchip_pmx_ops,rockchip_pinconf_ops) - 調(diào)用
devm_pinctrl_register->pinctrl_init_controller-> 創(chuàng)建pinctrl_dev
注冊(cè)GPIO子設(shè)備:of_platform_populate,讓每個(gè)GPIO bank作為一個(gè)獨(dú)立平臺(tái)設(shè)備被probe。
static int rockchip_pinctrl_probe(struct platform_device *pdev)
{
struct rockchip_pinctrl *info;
// ... 分配內(nèi)存、獲取regmap
ctrl = rockchip_pinctrl_get_soc_data(info, pdev);
info->ctrl = ctrl;
ret = rockchip_pinctrl_register(pdev, info);
ret = of_platform_populate(np, rockchip_bank_match, NULL, NULL);
return 0;
}
4.3pinctrl_init_controller內(nèi)部初始化
該函數(shù)分配struct pinctrl_dev,初始化radix tree(用于快速查找引腳描述符、組、函數(shù)),并調(diào)用pinctrl_check_ops驗(yàn)證驅(qū)動(dòng)是否提供了必要的回調(diào)函數(shù)。
5. 客戶端引腳自動(dòng)綁定流程(核心機(jī)制)
這是Pinctrl最核心的自動(dòng)化機(jī)制:在設(shè)備驅(qū)動(dòng)自身的probe函數(shù)被調(diào)用之前,內(nèi)核已經(jīng)根據(jù)設(shè)備樹(shù)完成了該設(shè)備所有引腳的復(fù)用和配置。
5.1 調(diào)用鏈
當(dāng)設(shè)備和驅(qū)動(dòng)匹配成功后,內(nèi)核的drivers/base/dd.c中的really_probe函數(shù)會(huì)執(zhí)行:
static int really_probe(struct device *dev, struct device_driver *drv)
{
// ...
ret = pinctrl_bind_pins(dev); // 關(guān)鍵!綁定并配置引腳
if (ret)
goto pinctrl_bind_failed;
// ... 然后才調(diào)用驅(qū)動(dòng)的probe
ret = drv->probe(dev);
}
5.2pinctrl_bind_pins實(shí)現(xiàn)
pinctrl_bind_pins位于drivers/base/pinctrl.c,它執(zhí)行以下步驟:
- 調(diào)用
devm_pinctrl_get(dev)獲取設(shè)備的pinctrl句柄(如果尚未獲?。?/li> - 查找
PINCTRL_STATE_DEFAULT和PINCTRL_STATE_INIT狀態(tài)。 - 優(yōu)先選擇init狀態(tài)(若存在),否則選擇default狀態(tài),然后調(diào)用
pinctrl_select_state激活該狀態(tài)。
int pinctrl_bind_pins(struct device *dev)
{
dev->pins = devm_kzalloc(dev, sizeof(*(dev->pins)), GFP_KERNEL);
dev->pins->p = devm_pinctrl_get(dev);
dev->pins->default_state = pinctrl_lookup_state(dev->pins->p, PINCTRL_STATE_DEFAULT);
dev->pins->init_state = pinctrl_lookup_state(dev->pins->p, PINCTRL_STATE_INIT);
if (!IS_ERR(dev->pins->init_state))
ret = pinctrl_select_state(dev->pins->p, dev->pins->init_state);
else
ret = pinctrl_select_state(dev->pins->p, dev->pins->default_state);
// ...
}
5.3pinctrl_select_state激活硬件配置
pinctrl_select_state會(huì)遍歷目標(biāo)狀態(tài)下的所有pinctrl_setting,對(duì)每個(gè)setting:
- 若是MUX_GROUP類型,調(diào)用
pinmux_enable_setting,最終通過(guò)驅(qū)動(dòng)提供的set_mux回調(diào)寫(xiě)入復(fù)用寄存器。 - 若是CONFIGS_GROUP或CONFIGS_PIN類型,調(diào)用
pinconf_apply_setting,通過(guò)pin_config_set回調(diào)寫(xiě)入配置寄存器。
下圖展示了從設(shè)備樹(shù)到硬件配置完成的完整流程:

6. 動(dòng)態(tài)API使用(在驅(qū)動(dòng)中主動(dòng)切換狀態(tài))
除了自動(dòng)綁定,驅(qū)動(dòng)還可以在運(yùn)行時(shí)主動(dòng)切換引腳狀態(tài),常用于電源管理(suspend/resume)。
主要API函數(shù)如下:
| 函數(shù) | 作用 |
|---|---|
| devm_pinctrl_get(dev) / pinctrl_get(dev) | 獲取設(shè)備的pinctrl句柄 |
| pinctrl_put(p) | 釋放句柄(通常用devm_版本自動(dòng)管理) |
| pinctrl_lookup_state(p, name) | 根據(jù)名稱(如“sleep”)查找狀態(tài)對(duì)象 |
| pinctrl_select_state(p, state) | 將指定狀態(tài)應(yīng)用到硬件 |
典型用法示例(攝像頭驅(qū)動(dòng)suspend時(shí)切換為sleep狀態(tài)):
static int ov5670_suspend(struct device *dev)
{
struct ov5670 *ov5670 = dev_get_drvdata(dev);
if (!IS_ERR_OR_NULL(ov5670->pins_sleep)) {
pinctrl_select_state(ov5670->pinctrl, ov5670->pins_sleep);
}
return 0;
}
static int ov5670_resume(struct device *dev)
{
struct ov5670 *ov5670 = dev_get_drvdata(dev);
if (!IS_ERR_OR_NULL(ov5670->pins_default)) {
pinctrl_select_state(ov5670->pinctrl, ov5670->pins_default);
}
return 0;
}
驅(qū)動(dòng)通常在probe中獲取句柄和狀態(tài):
ov5670->pinctrl = devm_pinctrl_get(dev); ov5670->pins_default = pinctrl_lookup_state(ov5670->pinctrl, "default"); ov5670->pins_sleep = pinctrl_lookup_state(ov5670->pinctrl, "sleep");
7. 調(diào)試方法(Debugfs)
Pinctrl子系統(tǒng)提供了豐富的調(diào)試接口,位于/sys/kernel/debug/pinctrl/下,對(duì)排查引腳沖突、配置錯(cuò)誤非常有用。
| 文件 | 內(nèi)容說(shuō)明 |
|---|---|
| pinctrl-devices | 列出所有已注冊(cè)的Pinctrl控制器,顯示是否支持pinmux/pinconf |
| pinctrl-handles | 最重要:顯示每個(gè)客戶端設(shè)備當(dāng)前請(qǐng)求的引腳狀態(tài)、每個(gè)狀態(tài)下的復(fù)用組和配置詳情??梢郧宄吹侥硞€(gè)引腳被哪個(gè)設(shè)備占用。 |
| <controller>/pins | 該控制器下所有引腳的列表(如pin 0 (GPIO_0)) |
| <controller>/pingroups | 該控制器定義的引腳組及其包含的引腳 |
| <controller>/pinmux-functions | 每個(gè)功能(function)對(duì)應(yīng)哪些引腳組(groups) |
| <controller>/pinconf-groups | 每個(gè)引腳組的電氣配置參數(shù)(上下拉、驅(qū)動(dòng)能力等) |
示例輸出片段(pinctrl-handles):
device: 78b6000.i2c
state: i2c_active
type: MUX_GROUP controller 1000000.pinctrl group: gpio6 (6) function: blsp_i2c2 (12)
type: CONFIGS_GROUP controller 1000000.pinctrl group gpio6 (6) 00000000 00020009
state: i2c_sleep
type: MUX_GROUP controller 1000000.pinctrl group: gpio6 (6) function: gpio (1)
type: CONFIGS_GROUP controller 1000000.pinctrl group gpio6 (6) 00010004 00020009
通過(guò)分析pinctrl-handles可以快速定位:
- 哪個(gè)設(shè)備占用了哪個(gè)引腳組
- 該引腳組當(dāng)前處于active還是sleep狀態(tài)
- 復(fù)用功能是否正確(例如期望I2C,實(shí)際卻配置為GPIO)
總結(jié):
Linux Pinctrl子系統(tǒng)通過(guò)標(biāo)準(zhǔn)化的服務(wù)端/客戶端模型,將引腳配置從硬件細(xì)節(jié)中抽象出來(lái)。其核心工作流程可概括為:
- 芯片廠商實(shí)現(xiàn)
pinctrl_desc+ ops,并注冊(cè)控制器。 - 設(shè)備樹(shù)編寫(xiě)者在客戶端節(jié)點(diǎn)中聲明
pinctrl-names和pinctrl-0/1/...。 - 內(nèi)核Pinctrl Core在設(shè)備probe前自動(dòng)解析設(shè)備樹(shù),調(diào)用驅(qū)動(dòng)回調(diào)完成硬件配置。
- 驅(qū)動(dòng)開(kāi)發(fā)者也可以在運(yùn)行時(shí)調(diào)用
pinctrl_select_state動(dòng)態(tài)切換狀態(tài)(如電源管理)。
這種設(shè)計(jì)極大簡(jiǎn)化了驅(qū)動(dòng)開(kāi)發(fā),避免了引腳配置沖突,是Linux SoC支持中的關(guān)鍵基礎(chǔ)設(shè)施。理解Pinctrl,對(duì)于閱讀和編寫(xiě)嵌入式Linux板級(jí)支持包(BSP)至關(guān)重要。
第二部分:Rockchip pinctrl 驅(qū)動(dòng)
1. 引言
在第一部分中,我們介紹了 Pinctrl 子系統(tǒng)的通用框架、核心數(shù)據(jù)結(jié)構(gòu)以及客戶端自動(dòng)綁定的流程。第二部分將以 Rockchip RK3568 平臺(tái)的 pinctrl 驅(qū)動(dòng)(pinctrl-rockchip.c)為例,深入分析 服務(wù)端(Provider) 驅(qū)動(dòng)的具體實(shí)現(xiàn)細(xì)節(jié),包括:
- 驅(qū)動(dòng)數(shù)據(jù)結(jié)構(gòu)設(shè)計(jì)與 SoC 專屬描述
- 如何從設(shè)備樹(shù)構(gòu)建引腳組(groups)和功能(functions)
- 復(fù)用(mux)與配置(conf)硬件寄存器的底層操作
- 設(shè)備樹(shù)節(jié)點(diǎn)到映射(
dt_node_to_map)的完整轉(zhuǎn)換過(guò)程
通過(guò)學(xué)習(xí)這部分,你將掌握如何為新的 SoC 編寫(xiě)或理解 pinctrl 驅(qū)動(dòng)。
2. Rockchip pinctrl 驅(qū)動(dòng)概覽
Rockchip 的 pinctrl 驅(qū)動(dòng)位于 drivers/pinctrl/pinctrl-rockchip.c 及其頭文件 pinctrl-rockchip.h。它實(shí)現(xiàn)了:
- 支持多個(gè) SoC 系列(RK3188, RK3288, RK3399, RK3568 等)
- 通過(guò)
rockchip_pin_ctrl數(shù)據(jù)結(jié)構(gòu)區(qū)分不同 SoC 的引腳布局、寄存器偏移和功能映射 - 利用 regmap 統(tǒng)一訪問(wèn) GRF(通用寄存器文件)和 PMUGRF(電源管理單元寄存器文件)
- 提供
pinctrl_ops,pinmux_ops,pinconf_ops三個(gè)操作集,供 Pinctrl Core 調(diào)用
下圖展示了 Rockchip pinctrl 驅(qū)動(dòng)與內(nèi)核其他模塊的關(guān)系:

3. 關(guān)鍵數(shù)據(jù)結(jié)構(gòu)
3.1rockchip_pinctrl—— 驅(qū)動(dòng)私有數(shù)據(jù)
該結(jié)構(gòu)體保存了驅(qū)動(dòng)運(yùn)行時(shí)的所有上下文信息。
struct rockchip_pinctrl {
struct regmap *regmap_base; // GRF 寄存器映射
int reg_size; // 寄存器區(qū)域大小
struct regmap *regmap_pull; // 拉取寄存器映射(舊綁定)
struct regmap *regmap_pmu; // PMUGRF 寄存器映射
struct device *dev;
struct rockchip_pin_ctrl *ctrl; // SoC 專屬引腳控制描述
struct pinctrl_desc pctl; // 通用 pinctrl 描述符
struct pinctrl_dev *pctl_dev;
struct rockchip_pin_group *groups; // 該 SoC 的所有引腳組
unsigned int ngroups;
struct rockchip_pmx_func *functions; // 該 SoC 的所有復(fù)用功能
unsigned int nfunctions;
};
其中 rockchip_pin_ctrl 是 SoC 相關(guān)的配置核心。
3.2rockchip_pin_ctrl—— SoC 引腳控制器描述
該結(jié)構(gòu)體包含了不同 SoC 的差異化信息,例如引腳總數(shù)、寄存器布局、驅(qū)動(dòng)強(qiáng)度計(jì)算方式等。
struct rockchip_pin_ctrl {
enum rockchip_pinctrl_type type; // 芯片類型(RK3568等)
struct pinctrl_pin_desc *pins; // 引腳描述符數(shù)組
unsigned int npins; // 引腳數(shù)量
unsigned int grf_mux_offset; // 復(fù)用寄存器偏移
unsigned int pmu_mux_offset; // PMU 復(fù)用寄存器偏移
int (*pull_calc_reg)(...); // 計(jì)算上下拉寄存器的函數(shù)
int (*drv_calc_reg)(...); // 計(jì)算驅(qū)動(dòng)能力寄存器的函數(shù)
const struct rockchip_mux_recalced_data *recalced_data;
unsigned int num_recalced;
// ... 更多 SoC 特有回調(diào)
};
以 RK3568 為例,驅(qū)動(dòng)中定義了 rk3568_pin_ctrl 靜態(tài)實(shí)例,其中包含引腳數(shù)組、寄存器偏移、以及專用的計(jì)算函數(shù)。
3.3rockchip_pin_group與rockchip_pmx_func
rockchip_pin_group:描述一個(gè)引腳組(group),包含組內(nèi)引腳編號(hào)列表、每個(gè)引腳的配置數(shù)據(jù)數(shù)組(上下拉、驅(qū)動(dòng)能力等)。rockchip_pmx_func:描述一個(gè)復(fù)用功能(function),包含功能名稱以及屬于該功能的組名列表。
這兩者都是從設(shè)備樹(shù)中解析出來(lái)的。
4. 驅(qū)動(dòng)初始化深入
4.1rockchip_pinctrl_probe步驟詳解
static int rockchip_pinctrl_probe(struct platform_device *pdev)
{
struct rockchip_pinctrl *info;
struct rockchip_pin_ctrl *ctrl;
struct device_node *np = pdev->dev.of_node;
// 1. 分配私有數(shù)據(jù)
info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
// 2. 獲取 SoC 專屬配置(根據(jù) compatible 匹配)
ctrl = rockchip_pinctrl_get_soc_data(info, pdev);
info->ctrl = ctrl;
// 3. 獲取 regmap:首先嘗試 "rockchip,grf" phandle,否則 ioremap
node = of_parse_phandle(np, "rockchip,grf", 0);
if (node)
info->regmap_base = syscon_node_to_regmap(node);
else
// 回退:直接映射 IORESOURCE_MEM
info->regmap_base = devm_regmap_init_mmio(...);
// 同樣處理 PMUGRF
node = of_parse_phandle(np, "rockchip,pmu", 0);
if (node)
info->regmap_pmu = syscon_node_to_regmap(node);
// 4. 注冊(cè) pinctrl 控制器
ret = rockchip_pinctrl_register(pdev, info);
// 5. 創(chuàng)建 GPIO bank 子設(shè)備(每個(gè) GPIO 控制器作為一個(gè) platform_device)
ret = of_platform_populate(np, rockchip_bank_match, NULL, NULL);
return 0;
}
關(guān)鍵點(diǎn):
rockchip_pinctrl_get_soc_data通過(guò)of_match_device獲取匹配的rockchip_pin_ctrl指針(存儲(chǔ)在platform_driver的data字段)。- 寄存器訪問(wèn)使用
regmap,可以統(tǒng)一處理位寬、緩存等,并兼容多個(gè)寄存器區(qū)域。 of_platform_populate會(huì)為每個(gè)gpio子節(jié)點(diǎn)(如gpio0)創(chuàng)建 platform 設(shè)備,其驅(qū)動(dòng)rockchip_gpio_probe會(huì)進(jìn)一步注冊(cè) GPIO 控制器。
4.2rockchip_pinctrl_register—— 填充并注冊(cè) pinctrl_desc
static int rockchip_pinctrl_register(struct platform_device *pdev,
struct rockchip_pinctrl *info)
{
struct pinctrl_desc *ctrldesc = &info->pctl;
struct rockchip_pin_ctrl *ctrl = info->ctrl;
ctrldesc->name = "rockchip-pinctrl";
ctrldesc->pins = ctrl->pins;
ctrldesc->npins = ctrl->npins;
ctrldesc->pctlops = &rockchip_pctrl_ops;
ctrldesc->pmxops = &rockchip_pmx_ops;
ctrldesc->confops = &rockchip_pinconf_ops;
ctrldesc->owner = THIS_MODULE;
// 解析設(shè)備樹(shù),構(gòu)建 groups 和 functions
ret = rockchip_pinctrl_parse_dt(pdev, info);
// 注冊(cè)到 pinctrl core
info->pctl_dev = devm_pinctrl_register(&pdev->dev, ctrldesc, info);
return PTR_ERR_OR_ZERO(info->pctl_dev);
}
4.3rockchip_pinctrl_parse_dt—— 構(gòu)建設(shè)備樹(shù)映射
該函數(shù)遍歷 pinctrl 控制器節(jié)點(diǎn)下的所有子節(jié)點(diǎn)(每個(gè)子節(jié)點(diǎn)代表一個(gè) function,如 i2c3),然后再遍歷每個(gè) function 下的引腳組子節(jié)點(diǎn)(如 i2c3m0_xfer),最終填充 info->groups 和 info->functions。
static int rockchip_pinctrl_parse_dt(struct platform_device *pdev,
struct rockchip_pinctrl *info)
{
struct device_node *np = pdev->dev.of_node;
struct device_node *child;
int ret;
// 第一遍:統(tǒng)計(jì) groups 和 functions 的數(shù)量
for_each_child_of_node(np, child) {
// 每個(gè) child 代表一個(gè) function 節(jié)點(diǎn)(如 i2c3, uart0)
info->nfunctions++;
for_each_child_of_node(child, grp) {
info->ngroups++;
}
}
// 分配內(nèi)存
info->groups = devm_kcalloc(dev, info->ngroups, sizeof(*info->groups), ...);
info->functions = devm_kcalloc(dev, info->nfunctions, sizeof(*info->functions), ...);
// 第二遍:填充實(shí)際內(nèi)容,解析每個(gè) group 的 rockchip,pins 屬性
// 將引腳編號(hào)、復(fù)用值、配置值存入 group 結(jié)構(gòu)
}
在解析 rockchip,pins 屬性時(shí),會(huì)調(diào)用 rockchip_pinctrl_parse_pin 將設(shè)備樹(shù)中的 bank、pin、mux 和 config phandle 轉(zhuǎn)換為:
- 全局引腳編號(hào)(如
(bank-1)*32 + pin_index) - 復(fù)用值(mux)
- 配置參數(shù)(從
&pcfg_pull_none等節(jié)點(diǎn)讀取 bias、drive-strength 等)
這些配置參數(shù)會(huì)最終存儲(chǔ)在 rockchip_pin_group 的 data[i].configs 數(shù)組中。
5. 復(fù)用操作(pinmux_ops)的實(shí)現(xiàn)
Rockchip pinctrl 驅(qū)動(dòng)實(shí)現(xiàn)了 rockchip_pmx_ops 結(jié)構(gòu)體:
static const struct pinmux_ops rockchip_pmx_ops = {
.get_functions_count = rockchip_pmx_get_funcs_count,
.get_function_name = rockchip_pmx_get_func_name,
.get_function_groups = rockchip_pmx_get_groups,
.set_mux = rockchip_pmx_set,
};
其中最核心的是 rockchip_pmx_set,它負(fù)責(zé)將某個(gè)引腳組切換到指定的復(fù)用功能。
5.1rockchip_pmx_set實(shí)現(xiàn)分析
static int rockchip_pmx_set(struct pinctrl_dev *pctldev,
unsigned int func_selector,
unsigned int group_selector)
{
struct rockchip_pinctrl *info = pinctrl_dev_get_drvdata(pctldev);
struct rockchip_pin_group *group = &info->groups[group_selector];
struct rockchip_pmx_func *func = &info->functions[func_selector];
int i;
for (i = 0; i < group->npins; i++) {
unsigned int pin = group->pins[i];
unsigned int mux = group->data[i].mux; // 該引腳在該 group 中的復(fù)用值
int ret = rockchip_pinctrl_set_mux(info, pin, mux);
if (ret)
return ret;
}
return 0;
}
實(shí)際寫(xiě)寄存器的函數(shù)是 rockchip_pinctrl_set_mux,它根據(jù)引腳所在的 bank 和 pin 索引計(jì)算出 IOMUX 寄存器的地址和位域,然后通過(guò) regmap 更新對(duì)應(yīng)位。
static int rockchip_pinctrl_set_mux(struct rockchip_pinctrl *info,
unsigned int pin, int mux)
{
struct rockchip_pin_bank *bank = pin_to_bank(info, pin);
int pin_offset = pin - bank->pin_base;
int reg, bit, ret;
// 根據(jù) SoC 類型計(jì)算復(fù)用寄存器偏移(GRF 或 PMU)
rockchip_get_mux(bank, pin_offset, ®, &bit, &bank->pin_sel_type[pin_offset]);
// 寫(xiě)寄存器:清除原來(lái)的 2 位(或 3 位)復(fù)用值,再寫(xiě)入新值
ret = regmap_update_bits(info->regmap_base, reg, 0x3 << bit, mux << bit);
return ret;
}
不同 Rockchip SoC 的復(fù)用寄存器布局不同,驅(qū)動(dòng)通過(guò) bank->pin_sel_type 數(shù)組(在 GPIO bank 驅(qū)動(dòng)初始化時(shí)填充)來(lái)區(qū)分是 2 位復(fù)用(如 RK3288)還是 3 位復(fù)用(如 RK3399),以及寄存器是否位于 PMU 域。
6. 配置操作(pinconf_ops)的實(shí)現(xiàn)
Rockchip 的 pinconf_ops 提供引腳電氣特性的配置:
static const struct pinconf_ops rockchip_pinconf_ops = {
.pin_config_get = rockchip_pinconf_get,
.pin_config_set = rockchip_pinconf_set,
.is_generic = true,
};
6.1 支持的配置參數(shù)
Rockchip 驅(qū)動(dòng)通過(guò)通用 pinconf 框架支持以下參數(shù)(定義在 pinctrl-generic.h):
PIN_CONFIG_BIAS_DISABLE:關(guān)閉上下拉PIN_CONFIG_BIAS_PULL_UP:上拉PIN_CONFIG_BIAS_PULL_DOWN:下拉PIN_CONFIG_DRIVE_STRENGTH:驅(qū)動(dòng)能力(mA)PIN_CONFIG_SLEW_RATE:壓擺率PIN_CONFIG_INPUT_ENABLE:輸入使能
驅(qū)動(dòng)中通過(guò) rockchip_pinconf_set 解析這些通用參數(shù),并調(diào)用 SoC 專用的計(jì)算函數(shù)(如 pull_calc_reg)找到對(duì)應(yīng)的寄存器地址和位域,最后更新硬件。
6.2rockchip_pinconf_set核心流程
static int rockchip_pinconf_set(struct pinctrl_dev *pctldev, unsigned int pin,
unsigned long *configs, unsigned int num_configs)
{
struct rockchip_pinctrl *info = pinctrl_dev_get_drvdata(pctldev);
struct rockchip_pin_bank *bank = pin_to_bank(info, pin);
int i, ret = 0;
for (i = 0; i < num_configs; i++) {
enum pin_config_param param = pinconf_to_config_param(configs[i]);
u32 arg = pinconf_to_config_argument(configs[i]);
switch (param) {
case PIN_CONFIG_BIAS_PULL_UP:
case PIN_CONFIG_BIAS_PULL_DOWN:
case PIN_CONFIG_BIAS_DISABLE:
ret = rockchip_set_pull(info, bank, pin - bank->pin_base, param);
break;
case PIN_CONFIG_DRIVE_STRENGTH:
ret = rockchip_set_drive(info, bank, pin - bank->pin_base, arg);
break;
// ... 其他參數(shù)
}
}
return ret;
}
rockchip_set_pull 和 rockchip_set_drive 內(nèi)部會(huì)利用 rockchip_pin_ctrl 提供的回調(diào)函數(shù)(如 pull_calc_reg)來(lái)定位寄存器。
例如,RK3568 的上下拉寄存器位于 PMUGRF(部分引腳)或 GRF(其他引腳),驅(qū)動(dòng)通過(guò) info->regmap_pmu 和 info->regmap_base 分別訪問(wèn)。
7. 設(shè)備樹(shù)到映射的轉(zhuǎn)換(dt_node_to_map)
客戶端設(shè)備在解析其 pinctrl-0 屬性時(shí),Pinctrl Core 會(huì)調(diào)用服務(wù)端驅(qū)動(dòng)的 dt_node_to_map 函數(shù)。
Rockchip 驅(qū)動(dòng)實(shí)現(xiàn)了 rockchip_dt_node_to_map,該函數(shù)將設(shè)備樹(shù)中的引腳組描述(如 <&i2c3m0_xfer>)轉(zhuǎn)換為 pinctrl_map 數(shù)組。
7.1 轉(zhuǎn)換步驟(回顧與補(bǔ)充)
根據(jù)設(shè)備樹(shù)節(jié)點(diǎn)名查找已有的 rockchip_pin_group
pinctrl_name_to_group(info, np->name) 返回之前在解析 pinctrl 控制器時(shí)構(gòu)建的 group 對(duì)象。
分配 pinctrl_map 數(shù)組
數(shù)量 = 1(復(fù)用映射) + group->npins(每個(gè)引腳一個(gè)配置映射)。
創(chuàng)建復(fù)用映射
new_map[0].type = PIN_MAP_TYPE_MUX_GROUP new_map[0].data.mux.function = parent->name(即 function 節(jié)點(diǎn)名,如 “i2c3”) new_map[0].data.mux.group = np->name(即 group 節(jié)點(diǎn)名,如 “i2c3m0_xfer”)
創(chuàng)建配置映射
對(duì) group 中的每個(gè)引腳,創(chuàng)建一個(gè) PIN_MAP_TYPE_CONFIGS_PIN 映射,group_or_pin 設(shè)置為引腳名,configs 指向預(yù)先解析好的配置數(shù)組。
new_map++;
for (i = 0; i < grp->npins; i++) {
new_map[i].type = PIN_MAP_TYPE_CONFIGS_PIN;
new_map[i].data.configs.group_or_pin = pin_get_name(pctldev, grp->pins[i]);
new_map[i].data.configs.configs = grp->data[i].configs;
new_map[i].data.configs.num_configs = grp->data[i].nconfigs;
}
這些映射最終會(huì)被 pinctrl_select_state 使用,依次調(diào)用 set_mux 和 pin_config_set 完成硬件配置。
8. 調(diào)試實(shí)踐:利用 debugfs 驗(yàn)證驅(qū)動(dòng)行為
在第二部分最后,我們展示如何利用 debugfs 驗(yàn)證 Rockchip pinctrl 驅(qū)動(dòng)的配置是否生效。
查看所有引腳組
cat /sys/kernel/debug/pinctrl/1000000.pinctrl/pingroups
輸出示例:
registered pin groups: group: i2c3m0-xfer pin 6 (GPIO_6) pin 7 (GPIO_7)
查看復(fù)用功能
cat /sys/kernel/debug/pinctrl/1000000.pinctrl/pinmux-functions
輸出:
function: i2c3, groups = [ i2c3m0-xfer i2c3m1-xfer ] function: uart0, groups = [ uart0-xfer ]
檢查某個(gè)設(shè)備的引腳狀態(tài)
cat /sys/kernel/debug/pinctrl/pinctrl-handles
會(huì)顯示類似:
device: fe5c0000.i2c
state: default
type: MUX_GROUP controller 1000000.pinctrl group: i2c3m0-xfer function: i2c3
type: CONFIGS_GROUP controller 1000000.pinctrl group i2c3m0-xfer 0x00000000 0x00020009
實(shí)時(shí)修改引腳配置(用于測(cè)試)
可以通過(guò) debugfs 直接寫(xiě)入配置,例如:
echo "pin 6 pull-up" > /sys/kernel/debug/pinctrl/1000000.pinctrl/pinconf-pins
(需要驅(qū)動(dòng)支持 debugfs 命令,Rockchip 驅(qū)動(dòng)實(shí)現(xiàn)了該接口)
9. 總結(jié)
第二部分詳細(xì)剖析了 Rockchip pinctrl 驅(qū)動(dòng)的內(nèi)部實(shí)現(xiàn),涵蓋:
- 驅(qū)動(dòng)私有數(shù)據(jù)結(jié)構(gòu)與 SoC 差異化描述
- 初始化的三個(gè)關(guān)鍵步驟:獲取 regmap、解析設(shè)備樹(shù)、注冊(cè) pinctrl 控制器
- 引腳組(groups)和功能(functions)的構(gòu)建過(guò)程
- 復(fù)用操作(
set_mux)如何操作 IOMUX 寄存器 - 配置操作(
pin_config_set)如何設(shè)置上下拉、驅(qū)動(dòng)強(qiáng)度等電氣特性 - 設(shè)備樹(shù)節(jié)點(diǎn)到 Pinctrl 映射的完整轉(zhuǎn)換(
dt_node_to_map) - 調(diào)試方法:利用 debugfs 檢查實(shí)際配置
理解這些細(xì)節(jié),你就能夠:
- 為新的 Rockchip SoC 移植或修改 pinctrl 驅(qū)動(dòng)
- 排查引腳配置不生效的問(wèn)題(例如檢查
rockchip,pins格式是否正確,寄存器偏移是否匹配) - 在自定義硬件上正確添加新的引腳組和功能
第三部分:電源管理、GPIO交互與高級(jí)調(diào)試
1. 引言
在前兩部分中,我們學(xué)習(xí)了 Pinctrl 子系統(tǒng)的通用框架(第一部分)以及 Rockchip 平臺(tái)驅(qū)動(dòng)的具體實(shí)現(xiàn)(第二部分)。第三部分將聚焦于 運(yùn)行時(shí)動(dòng)態(tài)管理 的高級(jí)主題,包括:
- 電源管理(PM)集成:如何在系統(tǒng)休眠/喚醒時(shí)自動(dòng)或手動(dòng)切換引腳狀態(tài)以降低功耗
- 與 GPIO 子系統(tǒng)的交互:pinctrl 如何與 GPIO 框架協(xié)同工作,確保引腳在 GPIO 模式下的正確配置
- Hog 機(jī)制:控制器自身對(duì)引腳的默認(rèn)配置
- Setting 應(yīng)用詳解:
add_setting、pinmux_enable_setting等內(nèi)部函數(shù)的完整流程 - 高級(jí)調(diào)試技巧:利用 debugfs 排查引腳沖突和配置錯(cuò)誤
通過(guò)學(xué)習(xí)本部分,你將掌握如何編寫(xiě)支持電源管理的設(shè)備驅(qū)動(dòng),理解 pinctrl 與 GPIO 的協(xié)同原理,并能獨(dú)立診斷引腳配置問(wèn)題。
2. 電源管理中的引腳狀態(tài)切換
2.1 設(shè)備樹(shù)中定義睡眠狀態(tài)
在客戶端設(shè)備樹(shù)節(jié)點(diǎn)中,可以為設(shè)備定義多個(gè) pinctrl 狀態(tài),常見(jiàn)的包括:
default:正常運(yùn)行狀態(tài)sleep:系統(tǒng) suspend 時(shí)使用的低功耗狀態(tài)idle:運(yùn)行時(shí) idle 狀態(tài)(可選)
&uart0 {
pinctrl-names = "default", "sleep";
pinctrl-0 = <&uart0_xfer>;
pinctrl-1 = <&uart0_sleep>;
status = "okay";
};
在 sleep 狀態(tài)中,通常會(huì)將引腳切換為 GPIO 功能并設(shè)置為輸入或上拉,以降低漏電流。
2.2 內(nèi)核自動(dòng)管理:pinctrl_pm_*接口
Pinctrl 子系統(tǒng)提供了兩個(gè)輔助函數(shù),供設(shè)備驅(qū)動(dòng)在 suspend/resume 時(shí)自動(dòng)切換狀態(tài):
int pinctrl_pm_select_default_state(struct device *dev); int pinctrl_pm_select_sleep_state(struct device *dev); int pinctrl_pm_select_idle_state(struct device *dev);
這些函數(shù)內(nèi)部會(huì)調(diào)用 pinctrl_select_state,前提是設(shè)備已經(jīng)在 probe 階段通過(guò) pinctrl_bind_pins 綁定好了句柄。
典型用法(在設(shè)備驅(qū)動(dòng)中):
static int my_drv_suspend(struct device *dev)
{
return pinctrl_pm_select_sleep_state(dev);
}
static int my_drv_resume(struct device *dev)
{
return pinctrl_pm_select_default_state(dev);
}
static const struct dev_pm_ops my_drv_pm_ops = {
.suspend = my_drv_suspend,
.resume = my_drv_resume,
};
2.3 內(nèi)核自動(dòng)處理的時(shí)機(jī)
實(shí)際上,對(duì)于掛載在標(biāo)準(zhǔn)總線(如 platform、i2c、spi)上的設(shè)備,如果驅(qū)動(dòng)沒(méi)有提供 suspend/resume 回調(diào),內(nèi)核不會(huì)自動(dòng)調(diào)用 pinctrl_pm_* 函數(shù)。因此,驅(qū)動(dòng)開(kāi)發(fā)者需要顯式地在 suspend/resume 回調(diào)中調(diào)用這些函數(shù)。
但有一種例外:當(dāng)整個(gè)系統(tǒng)進(jìn)入 suspend 時(shí),pinctrl 控制器驅(qū)動(dòng)本身會(huì)收到 PM 事件,并可能對(duì)所有已注冊(cè)的設(shè)備嘗試切換狀態(tài)?并非如此——狀態(tài)切換是按設(shè)備進(jìn)行的,每個(gè)設(shè)備的引腳狀態(tài)需要各自管理。
正確的理解:pinctrl_bind_pins 只是在 probe 階段為設(shè)備找到了 default 和 init 狀態(tài)并激活了其中一個(gè)。sleep 和 idle 狀態(tài)雖然被查找并保存在 dev->pins->sleep_state 中,但不會(huì)自動(dòng)應(yīng)用,需要驅(qū)動(dòng)在 suspend 時(shí)主動(dòng)調(diào)用 pinctrl_select_state。
2.4 狀態(tài)切換時(shí)的內(nèi)部操作
當(dāng)調(diào)用 pinctrl_select_state(p, new_state) 時(shí),內(nèi)核執(zhí)行以下步驟(簡(jiǎn)化):
int pinctrl_select_state(struct pinctrl *p, struct pinctrl_state *state)
{
struct pinctrl_setting *setting, *setting2;
struct pinctrl_state *old_state = p->state;
if (p->state) {
// 1. 禁用舊狀態(tài)中的所有復(fù)用設(shè)置
list_for_each_entry(setting, &p->state->settings, node) {
if (setting->type == PIN_MAP_TYPE_MUX_GROUP)
pinmux_disable_setting(setting);
}
}
p->state = NULL;
// 2. 啟用新?tīng)顟B(tài)中的所有設(shè)置
list_for_each_entry(setting, &state->settings, node) {
switch (setting->type) {
case PIN_MAP_TYPE_MUX_GROUP:
ret = pinmux_enable_setting(setting);
break;
case PIN_MAP_TYPE_CONFIGS_PIN:
case PIN_MAP_TYPE_CONFIGS_GROUP:
ret = pinconf_apply_setting(setting);
break;
}
}
p->state = state;
return 0;
}
注意:對(duì)于復(fù)用設(shè)置,禁用舊狀態(tài)時(shí)會(huì)調(diào)用 pinmux_disable_setting,這通常會(huì)將該引腳組的功能設(shè)置為 GPIO(安全默認(rèn)值),避免功能沖突。
3. Pinctrl 與 GPIO 子系統(tǒng)的交互
3.1 GPIO 與 Pinctrl 的關(guān)系
在許多 SoC 中,GPIO 控制器是 pinctrl 硬件的一部分:同一個(gè)物理引腳既可以作為通用 GPIO,也可以作為外設(shè)功能引腳。Pinctrl 驅(qū)動(dòng)通常也負(fù)責(zé) GPIO 控制器的注冊(cè),但 GPIO 子系統(tǒng)和 pinctrl 子系統(tǒng)是獨(dú)立的框架,它們通過(guò)以下方式協(xié)作:
- GPIO 請(qǐng)求時(shí),pinctrl 確保引腳已被配置為 GPIO 模式
- GPIO 釋放時(shí),pinctrl 可以將其恢復(fù)為默認(rèn)功能
- GPIO 方向設(shè)置時(shí),可能需要同時(shí)配置引腳的上拉/下拉
3.2gpio-ranges屬性
設(shè)備樹(shù)中的 gpio-ranges 屬性建立了 GPIO 控制器的 GPIO 號(hào)與 pinctrl 引腳號(hào)之間的映射關(guān)系。例如:
gpio0: gpio@fdd60000 {
compatible = "rockchip,gpio-bank";
reg = <0x0 0xfdd60000 0x0 0x100>;
gpio-controller;
#gpio-cells = <2>;
gpio-ranges = <&pinctrl 0 0 32>;
};
該屬性表示:gpio0 控制器的 GPIO 0~31 對(duì)應(yīng) pinctrl 控制器的引腳 0~31。
內(nèi)核通過(guò) gpiochip_add_pin_range 和 gpiochip_add_pingroup_range 建立映射,這樣 GPIO 子系統(tǒng)在請(qǐng)求引腳時(shí),可以找到對(duì)應(yīng)的 pinctrl 設(shè)備。
3.3 Pinctrl 驅(qū)動(dòng)提供的 GPIO 相關(guān)回調(diào)
pinmux_ops 中包含三個(gè) GPIO 相關(guān)的回調(diào):
struct pinmux_ops {
int (*gpio_request_enable)(struct pinctrl_dev *pctldev,
struct pinctrl_gpio_range *range,
unsigned offset);
void (*gpio_disable_free)(struct pinctrl_dev *pctldev,
struct pinctrl_gpio_range *range,
unsigned offset);
int (*gpio_set_direction)(struct pinctrl_dev *pctldev,
struct pinctrl_gpio_range *range,
unsigned offset, bool input);
};
gpio_request_enable:當(dāng)一個(gè) GPIO 被請(qǐng)求(gpio_request)時(shí),GPIO 子系統(tǒng)會(huì)調(diào)用該回調(diào),使 pinctrl 驅(qū)動(dòng)將對(duì)應(yīng)的引腳切換為 GPIO 功能。gpio_disable_free:當(dāng) GPIO 被釋放時(shí)調(diào)用,可將引腳恢復(fù)為默認(rèn)狀態(tài)。gpio_set_direction:當(dāng) GPIO 方向改變(gpio_direction_input/output)時(shí)調(diào)用,用于配置引腳的上下拉等。
Rockchip 驅(qū)動(dòng)實(shí)現(xiàn)了這些回調(diào):
static const struct pinmux_ops rockchip_pmx_ops = {
// ...
.gpio_request_enable = rockchip_gpio_request_enable,
.gpio_disable_free = rockchip_gpio_disable_free,
.gpio_set_direction = rockchip_gpio_set_direction,
};
在 rockchip_gpio_request_enable 中,會(huì)調(diào)用 rockchip_pinctrl_set_mux 將引腳復(fù)用值設(shè)置為 GPIO(即 mux = 0)。
3.4 GPIO 請(qǐng)求的完整流程

4. Hog 機(jī)制:控制器自身的引腳配置
有時(shí)候,某些引腳在系統(tǒng)啟動(dòng)后始終作為 GPIO 或特定功能使用,不歸屬于任何具體的客戶端設(shè)備。Pinctrl 允許在控制器節(jié)點(diǎn)中直接定義“hog”引腳組,并在控制器 probe 時(shí)自動(dòng)應(yīng)用這些配置。
4.1 設(shè)備樹(shù)中的 Hog 節(jié)點(diǎn)
在 pinctrl 控制器節(jié)點(diǎn)下,可以創(chuàng)建沒(méi)有 pinctrl-* 屬性的子節(jié)點(diǎn),并在其中定義 rockchip,pins,這些節(jié)點(diǎn)會(huì)被視為 hog 組。
&pinctrl {
hog_pins: hog-pins {
rockchip,pins = <0 RK_PA0 RK_FUNC_GPIO &pcfg_pull_up>,
<0 RK_PA1 RK_FUNC_GPIO &pcfg_pull_up>;
};
};
內(nèi)核在注冊(cè) pinctrl 控制器時(shí),會(huì)遍歷所有子節(jié)點(diǎn),對(duì)于沒(méi)有 pinctrl-0 屬性的節(jié)點(diǎn)(即非客戶端引用),會(huì)調(diào)用 pinctrl_hog 將其作為默認(rèn)狀態(tài)應(yīng)用。
4.2 內(nèi)核實(shí)現(xiàn)
pinctrl_register 最后會(huì)調(diào)用 pinctrl_enable,該函數(shù)內(nèi)部會(huì)執(zhí)行:
int pinctrl_enable(struct pinctrl_dev *pctldev)
{
// ...
if (pctldev->desc->pctlops->dt_node_to_map) {
// 遍歷控制器節(jié)點(diǎn)下的所有子節(jié)點(diǎn),找出 hog 節(jié)點(diǎn)
pinctrl_hog_map(pctldev, np);
}
// ...
}
pinctrl_hog_map 為每個(gè) hog 節(jié)點(diǎn)創(chuàng)建一個(gè)虛擬的“設(shè)備”(控制器自身),并調(diào)用 dt_node_to_map 生成映射,然后添加到控制器的 hog 狀態(tài)中,最后激活該狀態(tài)。
5. Setting 應(yīng)用詳解(add_setting與pinmux_enable_setting)
5.1add_setting完整分析
在 create_pinctrl 函數(shù)中,對(duì)每個(gè) pinctrl_map 調(diào)用 add_setting,將其轉(zhuǎn)換為 pinctrl_setting 并添加到對(duì)應(yīng)狀態(tài)的鏈表中。
static int add_setting(struct pinctrl *p, struct pinctrl_dev *pctldev,
const struct pinctrl_map *map)
{
struct pinctrl_state *state;
struct pinctrl_setting *setting;
// 根據(jù) map->name 查找或創(chuàng)建狀態(tài)
state = find_state(p, map->name);
if (!state)
state = create_state(p, map->name);
// 分配 setting 結(jié)構(gòu)體
setting = kzalloc(sizeof(*setting), GFP_KERNEL);
setting->type = map->type;
setting->pctldev = pctldev;
setting->dev_name = map->dev_name;
switch (map->type) {
case PIN_MAP_TYPE_MUX_GROUP:
setting->data.mux.group = map->data.mux.group; // group 選擇器
setting->data.mux.func = map->data.mux.function; // function 選擇器
break;
case PIN_MAP_TYPE_CONFIGS_PIN:
setting->data.configs.group_or_pin = map->data.configs.group_or_pin;
setting->data.configs.configs = map->data.configs.configs;
setting->data.configs.num_configs = map->data.configs.num_configs;
break;
case PIN_MAP_TYPE_CONFIGS_GROUP:
// 類似
break;
default:
break;
}
list_add_tail(&setting->node, &state->settings);
return 0;
}
注意:map->data.mux.group 和 map->data.mux.function 在映射階段是字符串名稱,但在 add_setting 中并未轉(zhuǎn)換為選擇器。
真正的轉(zhuǎn)換發(fā)生在 pinctrl_select_state 調(diào)用 pinmux_enable_setting 時(shí),通過(guò) pinctrl_get_group_selector 和 pinctrl_get_function_selector 動(dòng)態(tài)查找。
5.2pinmux_enable_setting實(shí)現(xiàn)
int pinmux_enable_setting(struct pinctrl_setting *setting)
{
struct pinctrl_dev *pctldev = setting->pctldev;
const struct pinmux_ops *ops = pctldev->desc->pmxops;
unsigned group = 0, func = 0;
int ret;
// 將字符串名稱轉(zhuǎn)換為選擇器
group = pinctrl_get_group_selector(pctldev, setting->data.mux.group);
func = pinctrl_get_function_selector(pctldev, setting->data.mux.func);
// 調(diào)用驅(qū)動(dòng)的 set_mux
ret = ops->set_mux(pctldev, func, group);
return ret;
}
類似地,pinconf_apply_setting 會(huì)調(diào)用 pin_config_group_set 或 pin_config_set。
6. 高級(jí)調(diào)試技巧
6.1 查看引腳當(dāng)前的配置值
除了之前提到的文件,/sys/kernel/debug/pinctrl/<controller>/pinconf-pins 可以查看每個(gè)引腳的當(dāng)前配置(需要驅(qū)動(dòng)實(shí)現(xiàn) pin_config_dbg_show)。
Rockchip 驅(qū)動(dòng)實(shí)現(xiàn)了該接口,輸出類似:
pin 0 (GPIO_0): bias-pull-up, drive-strength = 12 mA pin 1 (GPIO_1): bias-disable, drive-strength = 6 mA
6.2 動(dòng)態(tài)修改引腳配置(測(cè)試用)
通過(guò) debugfs 可以臨時(shí)修改某個(gè)引腳的配置,無(wú)需重新編譯內(nèi)核:
echo "pin 0 bias-pull-up drive-strength=12" > /sys/kernel/debug/pinctrl/1000000.pinctrl/pinconf-pins
這需要驅(qū)動(dòng)實(shí)現(xiàn) pin_config_dbg_parse_modify 回調(diào),Rockchip 驅(qū)動(dòng)未實(shí)現(xiàn)該功能,但其他平臺(tái)可能有。
6.3 檢查引腳占用沖突
當(dāng)出現(xiàn)“pin already requested”錯(cuò)誤時(shí),使用以下命令查找沖突源:
cat /sys/kernel/debug/pinctrl/pinctrl-handles | grep -A 10 "pin 6"
也可以直接查看某個(gè)控制器的引腳占用情況(如果驅(qū)動(dòng)實(shí)現(xiàn)了 pin_dbg_show):
cat /sys/kernel/debug/pinctrl/1000000.pinctrl/pins
輸出會(huì)顯示每個(gè)引腳是否被占用,以及被哪個(gè)設(shè)備占用。
6.4 模擬狀態(tài)切換
手動(dòng)觸發(fā)設(shè)備的狀態(tài)切換,驗(yàn)證電源管理邏輯:
# 先找到設(shè)備在 debugfs 中的句柄 cat /sys/kernel/debug/pinctrl/pinctrl-handles | grep "device:" # 假設(shè)設(shè)備是 78b6000.i2c # 無(wú)法直接通過(guò)命令切換,但可以查看當(dāng)前狀態(tài) cat /sys/kernel/debug/pinctrl/pinctrl-handles
更好的方法是在驅(qū)動(dòng)中增加 sysfs 節(jié)點(diǎn)來(lái)觸發(fā)狀態(tài)切換。
7. 總結(jié)
第三部分涵蓋了 Pinctrl 子系統(tǒng)的運(yùn)行時(shí)管理核心:
- 電源管理:通過(guò)
pinctrl_pm_select_*函數(shù)在 suspend/resume 中切換引腳狀態(tài),降低功耗。驅(qū)動(dòng)需要主動(dòng)調(diào)用這些函數(shù)。 - GPIO 交互:
gpio-ranges建立映射,gpio_request_enable等回調(diào)確保引腳在 GPIO 請(qǐng)求時(shí)正確配置。 - Hog 機(jī)制:控制器自身對(duì)引腳的默認(rèn)配置,無(wú)需客戶端設(shè)備。
- 內(nèi)部流程:
add_setting創(chuàng)建設(shè)置,pinmux_enable_setting在激活時(shí)解析名稱并調(diào)用驅(qū)動(dòng)回調(diào)。 - 調(diào)試:利用 debugfs 查看引腳占用、配置值和狀態(tài)切換。
.2 動(dòng)態(tài)修改引腳配置(測(cè)試用)
通過(guò) debugfs 可以臨時(shí)修改某個(gè)引腳的配置,無(wú)需重新編譯內(nèi)核:
echo "pin 0 bias-pull-up drive-strength=12" > /sys/kernel/debug/pinctrl/1000000.pinctrl/pinconf-pins
這需要驅(qū)動(dòng)實(shí)現(xiàn) pin_config_dbg_parse_modify 回調(diào),Rockchip 驅(qū)動(dòng)未實(shí)現(xiàn)該功能,但其他平臺(tái)可能有。
6.3 檢查引腳占用沖突
當(dāng)出現(xiàn)“pin already requested”錯(cuò)誤時(shí),使用以下命令查找沖突源:
cat /sys/kernel/debug/pinctrl/pinctrl-handles | grep -A 10 "pin 6"
也可以直接查看某個(gè)控制器的引腳占用情況(如果驅(qū)動(dòng)實(shí)現(xiàn)了 pin_dbg_show):
cat /sys/kernel/debug/pinctrl/1000000.pinctrl/pins
輸出會(huì)顯示每個(gè)引腳是否被占用,以及被哪個(gè)設(shè)備占用。
6.4 模擬狀態(tài)切換
手動(dòng)觸發(fā)設(shè)備的狀態(tài)切換,驗(yàn)證電源管理邏輯:
# 先找到設(shè)備在 debugfs 中的句柄 cat /sys/kernel/debug/pinctrl/pinctrl-handles | grep "device:" # 假設(shè)設(shè)備是 78b6000.i2c # 無(wú)法直接通過(guò)命令切換,但可以查看當(dāng)前狀態(tài) cat /sys/kernel/debug/pinctrl/pinctrl-handles
更好的方法是在驅(qū)動(dòng)中增加 sysfs 節(jié)點(diǎn)來(lái)觸發(fā)狀態(tài)切換。
7. 總結(jié)
第三部分涵蓋了 Pinctrl 子系統(tǒng)的運(yùn)行時(shí)管理核心:
- 電源管理:通過(guò)
pinctrl_pm_select_*函數(shù)在 suspend/resume 中切換引腳狀態(tài),降低功耗。驅(qū)動(dòng)需要主動(dòng)調(diào)用這些函數(shù)。 - GPIO 交互:
gpio-ranges建立映射,gpio_request_enable等回調(diào)確保引腳在 GPIO 請(qǐng)求時(shí)正確配置。 - Hog 機(jī)制:控制器自身對(duì)引腳的默認(rèn)配置,無(wú)需客戶端設(shè)備。
- 內(nèi)部流程:
add_setting創(chuàng)建設(shè)置,pinmux_enable_setting在激活時(shí)解析名稱并調(diào)用驅(qū)動(dòng)回調(diào)。 - 調(diào)試:利用 debugfs 查看引腳占用、配置值和狀態(tài)切換。
最后
掌握這些內(nèi)容后,你不僅可以正確編寫(xiě)使用 pinctrl 的設(shè)備驅(qū)動(dòng),還能夠深入調(diào)試引腳相關(guān)的硬件問(wèn)題。Pinctrl 子系統(tǒng)的設(shè)計(jì)體現(xiàn)了 Linux 內(nèi)核“分離機(jī)制與策略”的哲學(xué),是學(xué)習(xí)內(nèi)核子系統(tǒng)的優(yōu)秀范例。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Linux內(nèi)核設(shè)備驅(qū)動(dòng)之系統(tǒng)調(diào)用筆記整理
今天小編就為大家分享一篇關(guān)于Linux內(nèi)核設(shè)備驅(qū)動(dòng)之系統(tǒng)調(diào)用筆記整理,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2018-12-12
Ubuntu系統(tǒng)下擴(kuò)展LVM根目錄的方法
這篇文章主要給大家介紹了關(guān)于Ubuntu系統(tǒng)下擴(kuò)展LVM根目錄的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-05-05
Linux Pinctrl子系統(tǒng)使用及說(shuō)明
這篇文章主要介紹了Linux Pinctrl子系統(tǒng)使用及說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2026-04-04
CentOS虛擬機(jī)開(kāi)機(jī)出現(xiàn)問(wèn)題及解決過(guò)程
文章介紹了CentOS虛擬機(jī)開(kāi)機(jī)出現(xiàn)問(wèn)題的解決方法,通過(guò)輸入命令xfs_repair -v -L /dev/dm-0,強(qiáng)制xfs_repair將日志歸零,解決了無(wú)法掛載/sysroot的問(wèn)題2025-11-11
Nginx虛擬主機(jī)多server_name的順序問(wèn)題
今天在配置Nginx + PHP + MediaWiki中,發(fā)現(xiàn)一個(gè)問(wèn)題:MediaWiki所在的Nginx虛擬主機(jī)綁定了多個(gè)域名,但是不管通過(guò)什么域名訪問(wèn)MediaWiki首頁(yè),都會(huì)被跳轉(zhuǎn)到其中的一個(gè)域名上。2008-12-12
ubuntu開(kāi)啟22端口的實(shí)現(xiàn)
這篇文章主要介紹了ubuntu開(kāi)啟22端口的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08
Linux環(huán)境下完整搭建GitLab私有代碼倉(cāng)庫(kù)的詳細(xì)流程
在現(xiàn)代軟件開(kāi)發(fā)中,代碼版本控制是團(tuán)隊(duì)協(xié)作的基石,GitLab 作為一款功能強(qiáng)大、開(kāi)源免費(fèi)的 DevOps 平臺(tái),無(wú)疑是私有代碼倉(cāng)庫(kù)的最佳選擇之一,本文將帶你從零開(kāi)始,在 Linux 環(huán)境下完整搭建 GitLab 私有代碼倉(cāng)庫(kù),需要的朋友可以參考下2026-04-04
Apache2.4.2編譯安裝2個(gè)常見(jiàn)錯(cuò)誤和解決方法
這篇文章主要介紹了Apache2.4.2編譯安裝2個(gè)常見(jiàn)錯(cuò)誤和解決方法,需要的朋友可以參考下2014-06-06
Ubuntu16.04根目錄磁盤(pán)擴(kuò)容實(shí)現(xiàn)方式
本文詳細(xì)介紹了如何在Ubuntu 16.04系統(tǒng)中使用gparted工具進(jìn)行磁盤(pán)擴(kuò)容和分區(qū)調(diào)整,包括安裝gparted、啟動(dòng)工具、調(diào)整分區(qū)大小以及啟用交換空間等步驟2026-01-01
linux搭建FastDFS文件服務(wù)器的實(shí)現(xiàn)步驟
本文主要介紹在linux服務(wù)器如何搭建FastDFS文件服務(wù)器。文中通過(guò)圖文示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08

