引言
在许多soc内部都包含有pin控制器,通过pin控制器的寄存器,我们可以配置一个或者一组引脚的功能和特性。在软件方面,Linux内核提供了pinctrl子系统,目的是为了统一各soc厂商的pin脚管理。
Pinctrl子系统
一、Pinctrl子系统说明
在许多soc内部都包含有pin控制器,通过pin控制器的寄存器,我们可以配置一个或者一组引脚的功能和特性。各个厂商soc的pin脚在使用中,都有许多共同的特性,要么配置,要么复用pin脚。所以内核提供了一套代码来管理这些pin,这就是pinctrl子系统。主要实现的功能:
1、管理系统中所有的可以控制的pin,在系统初始化的时候,枚举所有可以控制的pin,并标识这些pin。
2、管理这些pin的复用(Multiplexing)。对于SOC而言,其引脚除了配置成普通的GPIO之外,若干个引脚还可以组成一个pin group,行程特定的功能。pin control subsystem要管理所有的pin group。
3、配置这些pin的特性。例如使能或关闭引脚上的pull-up、pull-down电阻,配置引脚的driver strength。
实现pinctrl子系统的功能:core在初始化时,由处理器抽象pin描述,在pinctrl core中枚举所有的pin描述。当我们的驱动层driver使用pinctrl时,会进入pinctrl core会去分析这些pin的枚举,映射,设置,配置pin和pin group。
pinctrl相关概念
普通driver调用pin control subsystem的主要目标有两个:
(1)设定该设备的功能复用。
(2)设定该device对应的pin的电气特性。
1、设定设备的功能复用需要了解两个概念,一个是function,另外一个是pin group。
function是功能抽象,对应一个HW逻辑block,例如SPI0.虽然给定了具体的function name,我们并不能确定其使用的pins的情况。例如为了设计灵活,芯片内部的SPI0的功能引出到pin group{C6,C7,C8,C9},也可能引出的另外一个pin group{C22.C23,C24,C25},但毫无疑问,这两个pin group不能同时active,毕竟芯片内部的SPI0的逻辑功能电路只有一个,因此只有给出function selector以及function的pin group selector才能进行function mux 的设定。
此外,由于电源管理的要求,某个device可能处于某个电源管理状态,例如idle或者sleep,这时候,属于device的所有pin就会需要处于另外的状态。
综合上述的需求,就定义了pin control state的概念,也就是说设备可能处于非常多的状态中的一个,device driver可以切换设备处于的状态。为了方便管理pin control state 就有了pin control state holder的概念,用来管理一个设备的所有的pin control状态。
综上所述,普通的driver调用pin control subsystem 的接口就是只有三个步骤:
(1)驱动加载或是运行时,获取pin control state holder的句柄
(2)设定pin control的状态
(3)驱动卸载或是退出时,释放pin control state holder的句柄
与GPIO子系统的关系
上图显示了gpio子系统和pinctrl子系统之间的关系,即pinctrl子系统实际上也把gpio一起管理起来,所有的gpio操作也需要透过pinctrl子系统来完成,这样,如果一个pin已经被申请为gpio,再通过pinctrl子系统申请为某个function时就会返回错误。
二、pinctrl 在dts文件中的描述
1、在使用pincrtl时,就涉及pinctrl在设备树中是如何描述的,在dts文件中,将一个处理器所使用的的pin用bank和group来描述。
a、何为bank?
所谓pin bank,就是以引脚名为依据,就是一组GPIO控制器的描述,如s3c2440A这块芯片,阅读它的datasheet就可以知道有9组GPIO,如图所示:
所以我们在dts中就把这9组GPIO枚举为pin bank。这里分析一部分,其他的类似。
gpa: gpa {
gpio-controller;
#gpio-cells = <2>;
};
.........
gpf: gpf {
gpio-controller;
interrupt-controller;
#gpio-cells = <2>;
#interrupt-cells = <2>;
};
如gpa: gpa 这个child node 就是描述GPA这个组,也就是gpa bank.。当然了 gpio-controller;表示这是一个GPIO控制器,有的GPIO控制器也可以是中断控制器,如gpf: gpf。#gpio-cells = <2>;表示使用这个bank的GPIO时,需要用两个32位数去描述。肯定是GPIO number和初始电平,具体可查阅
linux-4.19.8\Documentation\devicetree\bindings\pinctrl\samsung-pinctrl.txt
b、何为group?
从字面意思理解就是一个类似集群,将不同的pin number组合在一个。group以功能为依据,我们在JZ2440的LCD驱动中,需要使用gpc bank中的(1、2、3、4、8、9、10、11、12、13、14、15)gpd bank中的(0、1、2、3、4、5、6、7、8、9、10、11、12、13、14、15)来读写数据,我们把这些具体pin number组合在一起,我们称之为一个group。
同时这些组合在一起的pin number又可能实现不同的功能,就用samsung,pin-function、samsung,pin-val等来描述区分。这样子samsung,pins和samsung,pin-function构成child node。来描述设备使用pinctrl时的具体功能。
lcd_pinctrl: lcd_pinctrl {
samsung,pins = "gpc-8", "gpc-9", "gpc-10", "gpc-11", "gpc-12", "gpc-13", "gpc-14", "gpc-15",
"gpd-0", "gpd-1", "gpd-2", "gpd-3", "gpd-4", "gpd-5", "gpd-6", "gpd-7",
"gpd-8", "gpd-9", "gpd-10", "gpd-11", "gpd-12", "gpd-13", "gpd-14", "gpd-15",
"gpc-1", "gpc-2", "gpc-3", "gpc-4";
samsung,pin-function = <2>;
};
samsung,pins:描述了LCD读写数据所使用的pin number,
samsung,pin-function:将这些GPIO初始值设置为2,具体是什么功能,有datasheet解释。这一个关于初始值的描述并不只是 samsung,pin-function,对于samsung来说,还有如下几种:具体阅读linux-4.19.8\Documentation\devicetree\bindings\pinctrl\samsung-pinctrl.txt
- samsung,pin-val: Initial value of pin output buffer.
- samsung,pin-pud: Pull up/down configuration.
- samsung,pin-drv: Drive strength configuration.
- samsung,pin-pud-pdn: Pull up/down configuration in power down mode.
- samsung,pin-drv-pdn: Drive strength configuration in power down mode.
2、设备节点引用pinctrl
在lcd这个节点中dts文件是如下描述的,省略其他不需要的东西。
fb0: fb@4d000000{
compatible = "jz2440,lcd";
...
pinctrl-names = "default";
pinctrl-0 = <&lcd_pinctrl &lcd_backlight>;
...
}
pinctrl-names其实就是设置设备的某种初始状态,比如内核自己定义的"default",“init”,“idel”,"sleep"状态;也可以是其他自己定义的状态, 比如串口的"flow_ctrl"状态(使用流量控制)。
pinctrl-0 就是正常引用pin config,也就是调用lcd_pinctrl这个group配置LCD读写数据的GPIO。
三、在设备驱动模型中的应用
驱动模型中调用driver的probe函数的地方:
static int really_probe(struct device *dev, struct device_driver *drv)
{
int ret = 0;
......
/* If using pinctrl, bind pins now before probing */
ret = pinctrl_bind_pins(dev); //对该device涉及的pin进行pin control相关设定
if (ret)
goto probe_failed;
if (driver_sysfs_add(dev)) {
printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n",
__func__, dev_name(dev));
goto probe_failed;
}
if (dev->bus->probe) { //下面是真正的probe过程。
ret = dev->bus->probe(dev);
if (ret)
goto probe_failed;
} else if (drv->probe) {
ret = drv->probe(dev);
if (ret)
goto probe_failed;
}
driver_bound(dev);
ret = 1;
pr_debug("bus: '%s': %s: bound device %s to driver %s\n",
.....
}
pinctrl_bind_pins函数定义:
#define PINCTRL_STATE_DEFAULT "default"
#define PINCTRL_STATE_IDLE "idle"
#define PINCTRL_STATE_SLEEP "sleep"
int pinctrl_bind_pins(struct device *dev)
{
int ret;
dev->pins = devm_kzalloc(dev, sizeof(*(dev->pins)), GFP_KERNEL);
if (!dev->pins)
return -ENOMEM;
dev->pins->p = devm_pinctrl_get(dev); //获取pinctrl
if (IS_ERR(dev->pins->p)) {
dev_dbg(dev, "no pinctrl handle\n");
ret = PTR_ERR(dev->pins->p);
goto cleanup_alloc;
}
dev->pins->default_state = pinctrl_lookup_state(dev->pins->p, //查找这个pin的default状态
PINCTRL_STATE_DEFAULT);
if (IS_ERR(dev->pins->default_state)) {
dev_dbg(dev, "no default pinctrl state\n");
ret = 0;
goto cleanup_get;
}
ret = pinctrl_select_state(dev->pins->p, dev->pins->default_state); //设置pin的default状态
if (ret) {
dev_dbg(dev, "failed to activate default pinctrl state\n");
goto cleanup_get;
}
return 0;
从驱动模型的实现中,不难看出在驱动probe前,就已经申请到default的pin配置了,当然pinctrl的计数已经+1了。
Driver的probe函数可以通过devm_pinctrl_get获得pinctrl的句柄,再自行调用pinctrl_select_state设置pin state。
五、与device tree的关系
在device tree source 文件中可以在驱动节点中定义该驱动需要用到的pin配置。
device-node-name{
compatible = "xxxxxxx";
......
//定义该device自己的属性
pinctrl-names= "sleep","default","idle";
pinctrl-0 = &xxx_State_sleep;
pinctrl-1 = &xxx_state_default;
pinctrl-2 = &xxx_state_idle;
}
pinctrl-0 pinctrl-1 pinctrl-2 …表示了该设备的一个个状态,这里我们定义了三个pinctrl-0 pinctrl-1 pinctrl-2,数字0、1、2就是pinctrl-names中对应的字符串数组的index。其中pinctrl-0就是“sleep”状态,pinctrl-1就是“default”状态,pinctrl-2就是“idle”状态。
xxx_state_sleep,xxx_state_default,xxx_state_idel就是驱动具体的pin配置项了,需要在pinctrl设备节点处定义:
pinctrl@e01b0000{
pinctrl-names = "default";
pinctrl-0 = <&state_default>;
state_default:pinctrl_default{
};
xxx_state_sleep:xxx_sleep{
xxx_mfp{
actions,groups = "fmp1_3_1spi0_ss","mfp1_3_1_spi0_miso","mfp1_5_4";
actions,function = "spi0";
};
};
Pinctrl子系统在加载时,会调用pinctrl_dt_to_map函数将dts文件中有关pinctrl的配置项解析出来,并根据dts各驱动节点对pinctrl的引用关系,将phandle挂到各个驱动的device tree子节点,各个驱动就可以通过自己的dev句柄获得pinctrl的配置了。
六、与主控驱动的关系
在kernel的machine driver中,需要将主控的pinctrl相关硬件操作形象成一个符合linux pinctrl子系统规范的结构pinctrl_desc,并调用pinctrl_register注册到pinctrl子系统中,这样pinctrl子系统就可以将上层行为转换成具体的硬件操作了:
struct pinctrl_desc {
const char *name;
struct pinctrl_pin_desc const *pins; //描述每个pin的信息为何。
unsigned int npins; //表示可以控制多少个pin。pins和npins这两个成员就确定了一个pin controller所能控制的引脚信息。
const struct pinctrl_ops *pctlops;//全局的控制函数
const struct pinmux_ops *pmxops;//复用引脚的相关的操作函数
const struct pinconf_ops *confops; //用来配置引脚特性(如pull-up/pull-down)
struct module *owner;
};
pctlops成员callback函数说明:
struct pinctrl_ops {
int (*get_groups_count) (struct pinctrl_dev *pctldev); //该pin controller支持多少个pin group
const char *(*get_group_name) (struct pinctrl_dev *pctldev,//给定一个selector(index),获取指定的pin group的name
unsigned selector);
int (*get_group_pins) (struct pinctrl_dev *pctldev, //给定一个selector(index)获取指定的pin group中pin的信息(该pin group
unsigned selector,, //包括多少个pin,每个pin的ID是什么)
const unsigned **pins,
unsigned *num_pins);
void (*pin_dbg_show) (struct pinctrl_dev *pctldev, struct seq_file *s,//debug fs的callback接口
unsigned offset);
int (*dt_node_to_map) (struct pinctrl_dev *pctldev, //分析一个pin configuration node并把分析结果保存成mapping table
struct device_node *np_config, //entry 每一个entry表示一个setting(一个功能复用设定,或者电气特性设定)
struct pinctrl_map **map, unsigned *num_maps);
void (*dt_free_map) (struct pinctrl_dev *pctldev, //dt_node_to_map的逆函数。
struct pinctrl_map *map, unsigned num_maps);
};
pmxops成员callback函数说明:
struct pinmux_ops {
int (*request) (struct pinctrl_dev *pctldev, unsigned offset);//pinctrl子系统进行具体的复用设定之前需要调用该函数,主要用来请底层的driver判断某个引脚的复用设定是否ok
int (*free) (struct pinctrl_dev *pctldev, unsigned offset);//request的逆函数,调用request函数请求占用了某些pin的资源,调用free可以释放这些资源。
int (*get_functions_count) (struct pinctrl_dev *pctldev);//返回pin controller支持的function的数目。
const char *(*get_function_name) (struct pinctrl_dev *pctldev,//给定一个selector(index)获取指定function的name。
unsigned selector);
int (*get_function_groups) (struct pinctrl_dev *pctldev,//给定一个selector(index)获取指定的function 的pin group信息。
unsigned selector,
const char * const **groups,
unsigned * const num_groups);
int (*enable) (struct pinctrl_dev *pctldev, unsigned func_selector, //enable一个function当然要给我出function selector和pin group的selector
unsigned group_selector);
void (*disable) (struct pinctrl_dev *pctldev, unsigned func_selector,//enable的逆函数
unsigned group_selector);
int (*gpio_request_enable) (struct pinctrl_dev *pctldev,//request并且enable一个单独的gpio pin
struct pinctrl_gpio_range *range,
unsigned offset);
void (*gpio_disable_free) (struct pinctrl_dev *pctldev,//gpio_request_free的逆函数
struct pinctrl_gpio_range *range,
unsigned offset);
int (*gpio_set_direction) (struct pinctrl_dev *pctldev,//设定gpio方向的回调函数
struct pinctrl_gpio_range *range,
unsigned offset,
bool input);
};
confops成员的callback函数说明:
struct pinconf_ops {
#ifdef CONFIG_GENERIC_PINCONF
bool is_generic;
#endif
int (*pin_config_get) (struct pinctrl_dev *pctldev,//给定一个pin ID以及config type ID,获取该引脚上指定的type的配置。
unsigned pin,
unsigned long *config);
int (*pin_config_set) (struct pinctrl_dev *pctldev,//设定一个指定pin的配置
unsigned pin,
unsigned long config);
int (*pin_config_set_bulk) (struct pinctrl_dev *pctldev,
unsigned pin,
unsigned long *configs,
unsigned num_configs);
int (*pin_config_group_get) (struct pinctrl_dev *pctldev,//以pin group为单位,获取pin上的配置信息。
unsigned selector,
unsigned long *config);
int (*pin_config_group_set) (struct pinctrl_dev *pctldev,//以pin group为单位,设定pin group 的特性配置。
unsigned selector,
unsigned long config);
int (*pin_config_group_set_bulk) (struct pinctrl_dev *pctldev,
unsigned selector,
unsigned long *configs,
unsigned num_configs);
int (*pin_config_dbg_parse_modify) (struct pinctrl_dev *pctldev, //debug接口
const char *arg,
unsigned long *config);
void (*pin_config_dbg_show) (struct pinctrl_dev *pctldev, //debug接口
struct seq_file *s,
unsigned offset);
void (*pin_config_group_dbg_show) (struct pinctrl_dev *pctldev, //debug接口
struct seq_file *s,
unsigned selector);
void (*pin_config_config_dbg_show) (struct pinctrl_dev *pctldev,//debug接口
struct seq_file *s,
unsigned long config);
};
七、dts文件中的pinctrl关键词表
pin命名表
pin的命名遵循IC spec(集成电路标准)上的命名,以下命名表以每个pin默认的功能命名,但实际使用中各个pin的功能会随着配置发生变化。
在dts中使用关键词“actions(不同厂商不同,这是炬芯的),pins”后跟名字数组来定义需要使用哪些pin,如:
i2c0_over_uart0_pull_up{
actions,pins = “P_UART0_RX”,“P_UART0_TX”;
actions,pull = <2>;
}
MUX Group-Function表
1、Mux Group命名表
pin group按照寄存器的名字和bit来命名。比如:MFP_CTL1 bit22:21,定义了10个pin的mux:P_LVDS_OEP,P_LVDS_OEN,P_LVDS_ODP,P_LVDS_ODN,P_LVDS_OCP,P_LVDS_OCN,P_LVDS_OBP,P_LVDS_OBN,P_LVDS_OAP,P_LVDS_OAN。该pin group的名字为mfp1_22_21.
有些mfp寄存器的cell中,设置某一个值会将多个pin配置为不同的功能。那么这个cell中的pin就不能归为同一个pin group。需按情况拆解开,那么拆解开的pin group的名字还会加上一些后缀。
比如:根据IC SPEC,mfp1 bit[31:29]可控制3根pin:P_KS_IN0,P_KS_IN1,P_KS_IN2.该cell的0x11选项会将这2跟pin分别定义为pwm0,pwm1,pwm4.
那么就将其分拆为3个group:分别为“mfp1_31_29_ks_in2”、“mfp1_31_29_ks_in2”、“mfp1_31_29_ks_in0”。这几个mfp cell分割以后,他们之间仍然存在硬件上的互斥关系,比如将mfp1_31_29_ks_in2的function选为pwm0,mfp1_31_29_ks_in1的function选为pwm1,可以工作。但是将mfp1_31_29_ks_in2的function选为pwm0,mfp1_31_29_ks_in1的function选为jtag,就会返回错误。
在pin group的命名表中还会看到“xxx_dummy”的命名,这种pin group在pinmux设置中可能会用到。这些pin group只有一种mux功能,所以在mfp寄存器中不会表示,但是这些pin可能和gpio复用,申请这些pin有助于发现它和gpio存在的潜在复用错误。
在dts中使用关键词“actions,groups”后跟mux Group名字数组来定义需要使用哪些Mux group,如:
sd0_mfp_cmd_clk{
actions,groups = "mfp2_8_7","mfp2_6_5";
actions,function = "sd0";
};
MUX Group-function
Mux Group所控制的pin,可以通过设置Mux Function的方式改变IC内部连通性,使得同一组pin用作不同的功能,例如:
sd0_mfp_cmd_clk{
actions,groups ="mfp2_8_7","mfp2_6_5";
actions,function = "jtag";
};
Drive Group
paddrv使用的配置值完全等同于IC spec中关于PAD_DRVx寄存器的定义。
对于某些pin可以设置pin的驱动能力(即供电能力),可以通过配置driver group的等级对pin的驱动能力进行配置。
在dts中使用关键词“actions,paddrv”后跟驱动能力等级来定义“actions,groups”指定drive group所代表的pin组使用哪种驱动能力,如:
sd0_d0_d3_cmd_clk_paddrv{
actions,groups="paddrv1_19_18","paddrv1_17_16";
actions,paddrv = "1" /*level 1, range :0 ~ 3*/
}
表示“paddrv1_19_18”所代表的P_SD0_CMD和paddrv1_17_16 所代表的P_SD0_CLK,使用驱动能力1来提升数据传输稳定性。
Pin Pull Up/Down
在dts中使用关键词“actions,pull”后跟上下拉数据定义“actions,pin”指定的pin组使用哪种上下拉,如:
sd0_pull_d0_d3_cmd{
actions,pins = "P_SD0_CMD","P_SD0_CLK";
actions,pull = <1>;
};
表示将P_SD0_CMD和P_SD0_CLK这两个pin下拉。
actions,pull = <0>,表示上下拉功能关闭
actions,pull = <1>,表示下拉
actions,pull = <2>,表示上拉
GPIO-PIN
pin除了可以复用作各种功能外,还可以配置成GPIO使用,pinctrl子系统将GPIO子系统也管理起来了,因此申请GPIO的时候会去检查该gpio所对应的pin是否已经被其他驱动申请作其他功能了。如果已经被申请则申请时会报错,反之亦然。
七、使用dts描述pinctrl配置
dts中pinctrl配置方法
所有的pinctrl-state都定义在pin controller device节点中:
在kernel/arch/arm64/boot/dts/s700_pinctrl.dtsi 中
/ {
pinctrl@e01b0000 {
compatible = "actions,s700-pinctrl";
reg = <0 0xe01b0000 0 0x1000>;
pinctrl-names = "default";
pinctrl-0 = <&state_default>;
clocks = <&clock CLK_GPIO>;
clock-names = "mfp";
state_default: pinctrl_default {
};
而个驱动引用定义在pin controller device 节点中的子节点,即pinctrl-state节点。
各驱动使用如下方法引用pinctrl-state节点:
pinctrl-N:描述该设备需要使用的pin的一个状态(pin state),相当于上述的state。N的数值必须从0开始顺序递增。pinctrl-N属性的值为pin configuration nodes的phandle。pinctrl-N属性引用的pin configuration nodes必须是pin controller device node的直接子节点。
pinctrl-names:为每个pin state定义一个名字。每个名字顺序对应一个pin state。比如pinctrl-0的名字为“default”,pinctrl-1的名字对应“idle”。若不能指定pinctrl-names属性,这样的话,pin state的名字就是该属性的“N”字符。比如pinctrl-0的名字为字符“0”
mmc@e0330000{
pinctrl-names = "pinctrl_mmc0","share_uart2_5";
pinctrl-0 = <&mm0_pinctrl_state>;
pinctrl-1 = <&mmc_share_uart_state>;
}
上面的例子中,mmc驱动定义的pinctrl-state有两个,其中mmc0_state_default是定义pin controller device node下的直接子节点,表示sd0 的pin group 作为sd功能使用,而mmc_share_uart_state则表示作为serial功能使用,其中mmc_state_default对应的pinctrl-names 属性为default,而mmc_share_uart_state对应的pinctrl-names属性为share_uart2_5。
八、常用API分析
实例公用DTS节点
xxx {
....
pinctrl-names = "turnon_tes", "turnoff_tes";
pinctrl-0 = <&disp_teson>;
pinctrl-1 = <&disp_tesoff>;
};
&disp_teson_pinctrl { //#define disp_teson_pinctrl pinctrl_2
disp_teson: disp_teson {
samsung,pins = disp_teson_pin; //#define disp_teson_pin "gpg0-1"
samsung,pin-function = <disp_teson_con>;//#define disp_teson_con 2 -- 对应0x2 = TEDECON_INT
};
};
&disp_tesoff_pinctrl {
disp_tesoff: disp_tesoff {
samsung,pins = disp_tesoff_pin; //#define disp_teson_pin "gpg0-1"
samsung,pin-function = <disp_tesoff_con>;//#define disp_teson_con 0
};
};
1、获取一个pinctrl句柄,参数是dev是包含这个pin的device结构体即xxx这个设备的device
/**
* struct devm_pinctrl_get() - Resource managed pinctrl_get()
* @dev: the device to obtain the handle for
*
* If there is a need to explicitly destroy the returned struct pinctrl,
* devm_pinctrl_put() should be used, rather than plain pinctrl_put().
*/
struct pinctrl *devm_pinctrl_get(struct device *dev)
2、获取这个pin对应pin_state(引脚状态-turnon_tes/turnoff_tes)
/**
* pinctrl_lookup_state() - retrieves a state handle from a pinctrl handle
* @p: the pinctrl handle to retrieve the state from
* @name: the state name to retrieve
*/
struct pinctrl_state *pinctrl_lookup_state(struct pinctrl *p, const char *name)
3、设置引脚为为某个stata – turnon_tes/turnoff_tes
/**
* pinctrl_select_state() - select/activate/program a pinctrl state to HW
* @p: the pinctrl handle for the device that requests configuration
* @state: the state handle to select/activate/program
*/
int pinctrl_select_state(struct pinctrl *p, struct pinctrl_state *state)
具体操作:
/* 获取pin control state holder 的句柄 */
pinctrl = devm_pinctrl_get(dev);
/* 得到名字为turnon_tes和turnoff_tes对应的pin state */
struct pinctrl_state * turnon_tes = pinctrl_lookup_state(pinctrl, "turnon_tes");
struct pinctrl_state * turnoff_tes = pinctrl_lookup_state(pinctrl, "turnoff_tes");
/* 设置名字为turnon_tes这个pinctrl对应引脚(gpg0-1)的pin state,即gpg0_1对应的寄存器位域设置为2 */
pinctrl_select_state(pinctrl, turnon_tes)。
九、总结
pincrtl子系统,就是在soc初始化时枚举所有的gpio。设备注册时,分析mux和configs。mux:设备以功能为依据,所用的一组GPIO;configs:一个GPIO。
来源:CSDN
作者:文艺小少年
链接:https://blog.csdn.net/weixin_38019025/article/details/104153698