在项目开发初期,很经常会需要临时操作某个GPIO来验证某些功能,可以通过编写一个简单的驱动程序来操作,但更方便的是可以通过命令行直接操作 GPIO ,这样不需要经过编写代码、编译驱动、推入文件、加载驱动那么繁琐的步骤。
以下为全志平台命令行操作 GPIO 的方法
root@sun8i:/# mount -t debugfs debug /proc/sys/debug
mount -t debugfs debug /proc/sys/debug
root@sun8i:/# cd /proc/sys/debug/sunxi_pinctrl
cd /proc/sys/debug/sunxi_pinctrl
root@sun8i:/proc/sys/debug/sunxi_pinctrl# ls -l
ls -l
-rw-rw-r-- 1 root root 0 Jan 1 1970 data
-rw-rw-r-- 1 root root 0 Jan 1 1970 dlevel
-rw-rw-r-- 1 root root 0 Jan 1 1970 function
-rw-rw-r-- 1 root root 0 Jan 1 1970 platform
-rw-rw-r-- 1 root root 0 Jan 1 1970 pull
-rw-rw-r-- 1 root root 0 Jan 1 1970 sunxi_pin
-rw-rw-r-- 1 root root 0 Jan 1 1970 sunxi_pin_configure
data // 引脚的电平状态
dlevel // 引脚的驱动等级(结合芯片手册)
function // 引脚的功能配置(结合芯片手册, 输入\输出\功能复用)
platform // 当前平台
pull // 上下拉功能配置
sunxi_pin // 指定引脚
sunxi_pin_configure // 引脚所有的配置信息
root@sun8i:/proc/sys/debug/sunxi_pinctrl# echo PB2 > sunxi_pin
echo PB2 > sunxi_pin
root@sun8i:/proc/sys/debug/sunxi_pinctrl# cat sunxi_pin_configure
cat sunxi_pin_configure
printf register value...
pin[PB2] function: 6; register addr: 0xf1c20824
pin[PB2] data: 0(default value : 0); register addr: 0xf1c20834
pin[PB2] dleve: 1(default value : 1); register addr: 0xf1c20838
pin[PB2] pull: 0(default value : 0); register addr: 0xf1c20840
pin[PB2] trigger: 4; register addr: 0xf1c20a20
trigger mode in sunxi platform:
0 : Positive edge 1: Negative edge
2 : High level 3: Low level
4 : Double level 5: other
pin[PB2] mask : 1(0:disable 1:enable); register addr: 0xf1c20a30
pin[PB2] pending : 0(0:No Pending 1:Pending); register addr: 0xf1c20a34
root@sun8i:/proc/sys/debug/sunxi_pinctrl# cat sunxi_pin_configure
cat sunxi_pin_configure
printf register value...
pin[PB2] function: 6; register addr: 0xf1c20824
pin[PB2] data: 1(default value : 0); register addr: 0xf1c20834
pin[PB2] dleve: 1(default value : 1); register addr: 0xf1c20838
pin[PB2] pull: 0(default value : 0); register addr: 0xf1c20840
pin[PB2] trigger: 4; register addr: 0xf1c20a20
trigger mode in sunxi platform:
0 : Positive edge 1: Negative edge
2 : High level 3: Low level
4 : Double level 5: other
pin[PB2] mask : 1(0:disable 1:enable); register addr: 0xf1c20a30
pin[PB2] pending : 0(0:No Pending 1:Pending); register addr: 0xf1c20a34
PB02 引脚我们用来作为检测触摸按键使用,当有触摸的时候,GPIO 会被拉高,如果没有触摸,GPIO 会被拉低,因此第一次查看 GPIO 配置信息的时候,data 为 0。
该引脚设置中断触发模式为 IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,因此 pin[PB2] trigger 为 4,即 Double leve (双边沿触发模式)。
这里的 function 为 6,是因为我将这个 GPIO 设置为外部中断,根据 datasheet 描述,外部中断的功能号是 6。
root@sun8i:/proc/sys/debug/sunxi_pinctrl# echo PE24 > sunxi_pin
echo PE24 > sunxi_pin
root@sun8i:/proc/sys/debug/sunxi_pinctrl# cat sunxi_pin_configure
cat sunxi_pin_configure
printf register value...
pin[PE24] function: 1; register addr: 0xf1c2089c
pin[PE24] data: 1(default value : 0); register addr: 0xf1c208a0
pin[PE24] dleve: 1(default value : 1); register addr: 0xf1c208a8
pin[PE24] pull: 0(default value : 0); register addr: 0xf1c208b0
root@sun8i:/proc/sys/debug/sunxi_pinctrl# echo PE24 0 > data
echo PE24 0 > data
root@sun8i:/proc/sys/debug/sunxi_pinctrl# cat sunxi_pin_configure
cat sunxi_pin_configure
printf register value...
pin[PE24] function: 1; register addr: 0xf1c2089c
pin[PE24] data: 0(default value : 0); register addr: 0xf1c208a0
pin[PE24] dleve: 1(default value : 1); register addr: 0xf1c208a8
pin[PE24] pull: 0(default value : 0); register addr: 0xf1c208b0
root@sun8i:/proc/sys/debug/sunxi_pinctrl# echo PE24 1 > data
echo PE24 1 > data
root@sun8i:/proc/sys/debug/sunxi_pinctrl# cat sunxi_pin_configure
cat sunxi_pin_configure
printf register value...
pin[PE24] function: 1; register addr: 0xf1c2089c
pin[PE24] data: 1(default value : 0); register addr: 0xf1c208a0
pin[PE24] dleve: 1(default value : 1); register addr: 0xf1c208a8
pin[PE24] pull: 0(default value : 0); register addr: 0xf1c208b0
学会用还不行,还需要知道如何启用该功能,虽然全志大部分平台都会默认启用这个功能,万一遇到没有启用的呢?通过以 sunxi_pin_configure 关键词搜索代码, 以上调试功能是在该驱动中实现 lichee\linux-3.4\drivers\pinctrl\pinctrl-sunxi.c,并且依赖 CONFIG_DEBUG_FS 配置。
static int sunxi_pinctrl_probe(struct platform_device *pdev)
{
......
#ifdef CONFIG_DEBUG_FS
sunxi_pinctrl_debugfs();
#endif
return 0;
......
}
static struct dentry *debugfs_root;
static void sunxi_pinctrl_debugfs(void)
{
debugfs_root = debugfs_create_dir("sunxi_pinctrl", NULL);
if (IS_ERR(debugfs_root) || !debugfs_root) {
pr_debug("failed to create debugfs directory\n");
debugfs_root = NULL;
return;
}
debugfs_create_file("sunxi_pin_configure",(S_IRUGO | S_IWUSR | S_IWGRP),
debugfs_root, NULL, &sunxi_pin_configure_ops);
debugfs_create_file("sunxi_pin", (S_IRUGO | S_IWUSR | S_IWGRP),
debugfs_root, NULL, &sunxi_pin_ops);
debugfs_create_file("function", (S_IRUGO | S_IWUSR | S_IWGRP),
debugfs_root, NULL, &sunxi_pin_function_ops);
debugfs_create_file("data", (S_IRUGO | S_IWUSR | S_IWGRP),
debugfs_root, NULL, &sunxi_pin_data_ops);
debugfs_create_file("dlevel", (S_IRUGO | S_IWUSR | S_IWGRP),
debugfs_root, NULL, &sunxi_pin_dlevel_ops);
debugfs_create_file("pull", (S_IRUGO | S_IWUSR | S_IWGRP),
debugfs_root, NULL, &sunxi_pin_pull_ops);
debugfs_create_file("platform", (S_IRUGO | S_IWUSR | S_IWGRP),
debugfs_root, NULL, &sunxi_platform_ops);
}
通过 Makefile 和 Kconfig 来确认 make menuconfig 的菜单选项和菜单位置
lichee\linux-3.4\drivers\pinctrl\Makefile:
obj-$(CONFIG_PINCTRL_SUNXI) += pinctrl-sunxi.o
lichee\linux-3.4\drivers\pinctrl\Kconfig:
config PINCTRL_SUNXI
bool "SUNXI pin controller driver"
select PINMUX
select GENERIC_PINCONF
因此要使用此功能需要进行三个步骤:
make kernel_menuconfig
Device Drivers ->
Pin controllers ->
[*] SUNXI pin controller driver
make kernel_menuconfig
Kernel hacking ->
[*] Debug Filesystem
mount -t debugfs debug /proc/sys/debug
有时候我们需要操作的 GPIO 是已经被驱动程序申请占用,那么我们通过这种方式是否会有冲突呢?答案是,不会。
知其然,也要知其所以然。以下代码以操作 GPIO 电平的设备节点入手:/proc/sys/debug/sunxi_pinctrl/data
这里略过 debugfs 的知识点介绍,如果不知道为什么要从这里入手,可以先去了解一下 debugfs 。
注意代码里面的注释信息,该注释信息就是分析记录。
// 从操作 GPIO 的 data 入手
static void sunxi_pinctrl_debugfs(void)
{
...
debugfs_create_file("data", (S_IRUGO | S_IWUSR | S_IWGRP),
debugfs_root, NULL, &sunxi_pin_data_ops);
...
}
// 找到关联的 ops
static const struct file_operations sunxi_pin_data_ops = {
.open = sunxi_pin_data_open,
.write = sunxi_pin_data_write,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
.owner = THIS_MODULE,
};
// 分析实际写操作
static int sunxi_pin_data_write(struct file *file,
const char __user *user_buf, size_t count, loff_t *ppos)
{
int err;
unsigned int data;
long unsigned int config;
// 从命令行解析得到引脚名称以及要设置的电平状态
err = sscanf(user_buf, "%s %u", sunxi_dbg_pinname,&data);
if(err != 2 )
return err;
if (data <= 1)
sunxi_dbg_data = data;
else{
pr_debug("Input Parameters data error!\n");
return -EINVAL;
}
config = SUNXI_PINCFG_PACK(SUNXI_PINCFG_TYPE_DAT,data);
// 实际操作,注意 SUNXI_PINCTRL ,并将引脚名字传入
// 如果是 echo PH02 1 > data, 那么引脚名字就是 "PH02"
pin_config_set(SUNXI_PINCTRL,sunxi_dbg_pinname,config);
return count;
}
// 传入给 pin_config_set() 第一个参数 dev_name
#define SUNXI_PINCTRL "sunxi-pinctrl"
// 引脚配置设置 dev_name:sunxi-pinctrl name: PH02
int pin_config_set(const char *dev_name, const char *name,
unsigned long config)
{
struct pinctrl_dev *pctldev;
int pin, ret;
mutex_lock(&pinctrl_mutex);
// 这里关键,通过名字 pinctrl 设备驱动
pctldev = get_pinctrl_dev_from_devname(dev_name);
if (!pctldev) {
ret = -EINVAL;
goto unlock;
}
// 通过 pinctrl 找名为 name 的引脚
pin = pin_get_from_name(pctldev, name);
if (pin < 0) {
ret = pin;
goto unlock;
}
// 修改指定引脚配置
ret = pin_config_set_for_pin(pctldev, pin, config);
unlock:
mutex_unlock(&pinctrl_mutex);
return ret;
}
EXPORT_SYMBOL(pin_config_set);
// 通过名字找引脚的实现,其实质就是和预先定于好的名字对比
int pin_get_from_name(struct pinctrl_dev *pctldev, const char *name)
{
unsigned i, pin;
/* The pin number can be retrived from the pin controller descriptor */
for (i = 0; i < pctldev->desc->npins; i++) {
struct pin_desc *desc;
pin = pctldev->desc->pins[i].number;
desc = pin_desc_get(pctldev, pin);
/* Pin space may be sparse */
if (desc == NULL)
continue;
if (desc->name && !strcmp(name, desc->name))
return pin;
}
return -EINVAL;
}
// 修改指定引脚配置
static int pin_config_set_for_pin(struct pinctrl_dev *pctldev, unsigned pin,
unsigned long config)
{
const struct pinconf_ops *ops = pctldev->desc->confops;
int ret;
if (!ops || !ops->pin_config_set) {
dev_err(pctldev->dev, "cannot configure pin, missing "
"config function in driver\n");
return -EINVAL;
}
// 调用 pin_config_set 回调接口进行真正的操作
ret = ops->pin_config_set(pctldev, pin, config);
if (ret) {
dev_err(pctldev->dev,
"unable to set pin configuration on pin %d\n", pin);
return ret;
}
return 0;
}
以上分析最关键的有两个地方,通过名字找到具体的 pinctrl 设备驱动,然后调用其回调来进行真正的GPIO配置操作:
get_pinctrl_dev_from_devname(dev_name); 和 ops->pin_config_set(pctldev, pin, config);
// pin_config_set 原型
struct pinconf_ops {
......
int (*pin_config_set) (struct pinctrl_dev *pctldev,
unsigned pin,
unsigned long config);
......
};
// 以名字寻找 pinctrl 设备驱动
struct pinctrl_dev *get_pinctrl_dev_from_devname(const char *devname)
{
struct pinctrl_dev *pctldev = NULL;
bool found = false;
if (!devname)
return NULL;
// 遍历 pinctrldev_list 链表
// 从上面分析的 sunxi_pin_data_write() 传入的参数可知名字为 "sunxi-pinctrl"
list_for_each_entry(pctldev, &pinctrldev_list, node) {
if (!strcmp(dev_name(pctldev->dev), devname)) {
/* Matched on device name */
found = true;
break;
}
}
return found ? pctldev : NULL;
}
要找到 pin_config_set(pctldev, pin, config); 真正实现,首先需要找到名为 "sunxi-pinctrl" 的 pinctrl 驱动,而要找到这个驱动,首先先找到操作 pinctrldev_list,增加节点的地方,即 pinctrl_register();
// 通过搜索 pinctrldev_list 来找增加链表的地方,找到这里。
struct pinctrl_dev *pinctrl_register(struct pinctrl_desc *pctldesc,
struct device *dev, void *driver_data)
{
......
mutex_lock(&pinctrl_mutex);
// 找到加入 pinctrldev_list 的地方
list_add_tail(&pctldev->node, &pinctrldev_list);
pctldev->p = pinctrl_get_locked(pctldev->dev);
if (!IS_ERR(pctldev->p)) {
struct pinctrl_state *s =
pinctrl_lookup_state_locked(pctldev->p,
PINCTRL_STATE_DEFAULT);
if (!IS_ERR(s))
pinctrl_select_state_locked(pctldev->p, s);
}
mutex_unlock(&pinctrl_mutex);
......
}
EXPORT_SYMBOL_GPL(pinctrl_register);
回过头来检查 lichee\linux-3.4\drivers\pinctrl\pinctrl-sunxi.c 有没有调用 pinctrl_register();找到如下:
static int sunxi_pinctrl_probe(struct platform_device *pdev)
{
......
#if defined(CONFIG_OF)
pctl->membase = of_iomap(node, 0);
if (!pctl->membase)
return -ENOMEM;
// 如果采用 dts ,那么就会从 dts 获取引脚的描述信息
device = of_match_device(sunxi_pinctrl_match, &pdev->dev);
if (!device)
return -ENODEV;
pctl->desc = (struct sunxi_pinctrl_desc *)device->data;
#else
// 一般是这里在 sun[?]iw[?].c 提供,如 R58 就是 sun8iw6.c
pctl->desc = (struct sunxi_pinctrl_desc *)(&sunxi_pinctrl_data);
#endif /* CONFIG_OF */
ret = sunxi_pinctrl_build_state(pdev);
if (ret) {
dev_err(&pdev->dev, "dt probe failed: %d\n", ret);
return ret;
}
pins = devm_kzalloc(&pdev->dev,
pctl->desc->npins * sizeof(*pins),
GFP_KERNEL);
if (!pins)
return -ENOMEM;
for (i = 0; i < pctl->desc->npins; i++)
pins[i] = pctl->desc->pins[i].pin;
// 在这里配置, 这里的名字是关键,用的是平台设备的名字,其实就是 "sunxi-pinctrl"
sunxi_pctrl_desc.name = dev_name(&pdev->dev);
sunxi_pctrl_desc.owner = THIS_MODULE;
sunxi_pctrl_desc.pins = pins;
sunxi_pctrl_desc.npins = pctl->desc->npins;
pctl->dev = &pdev->dev;
// 注册进去
pctl->pctl_dev = pinctrl_register(&sunxi_pctrl_desc,
&pdev->dev, pctl);
if (!pctl->pctl_dev) {
dev_err(&pdev->dev, "couldn't register pinctrl driver\n");
return -EINVAL;
}
......
}
从上述所知,pinctrl_register(); 所用的名字是平台设备驱动的名字,因此查看平台设备驱动的结构即可确认,其中 sunxi_pctrl_desc 这个结构的内容就包含了我们一直苦苦寻找的 pin_config_set(pctldev, pin, config); 真正实现所在。
static struct platform_driver sunxi_pinctrl_driver = {
.probe = sunxi_pinctrl_probe,
.driver = {
.name = SUNXI_PINCTRL, // 平台设备驱动匹配的名字
.owner = THIS_MODULE,
#if defined(CONFIG_OF)
.of_match_table = sunxi_pinctrl_match,
#endif
},
};
static struct platform_device sunxi_pinctrl_device = {
.name = SUNXI_PINCTRL, // 平台设备驱动匹配的名字
.id = PLATFORM_DEVID_NONE, /* this is only one device for pinctrl driver */
};
static int __init sunxi_pinctrl_init(void)
{
int ret;
// 注册平台设备驱动
ret = platform_device_register(&sunxi_pinctrl_device);
if (IS_ERR_VALUE(ret)) {
pr_debug("register sunxi platform device failed\n");
return -EINVAL;
}
ret = platform_driver_register(&sunxi_pinctrl_driver);
if (IS_ERR_VALUE(ret)) {
pr_debug("register sunxi platform device failed\n");
return -EINVAL;
}
return 0;
}
postcore_initcall(sunxi_pinctrl_init);
// 这个宏定义就是 "sunxi-pinctrl"
#define SUNXI_PINCTRL "sunxi-pinctrl"
查看 sunxi_pctrl_desc 看看实际是怎么实现的。
static struct pinctrl_desc sunxi_pctrl_desc = {
.confops = &sunxi_pconf_ops,
......
};
static struct pinconf_ops sunxi_pconf_ops = {
.pin_config_get = sunxi_pinconf_get,
.pin_config_set = sunxi_pinconf_set,
.pin_config_group_get = sunxi_pinconf_group_get,
.pin_config_group_set = sunxi_pinconf_group_set,
};
// 真正的实现所在
static int sunxi_pinconf_set(struct pinctrl_dev *pctldev,
unsigned pin,
unsigned long config)
{
struct sunxi_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev);
struct sunxi_pin_bank *bank = sunxi_pin_to_bank(pctl, pin);
unsigned int pin_bias;
void __iomem *reg;
u32 val;
u32 mask;
u16 dlevel;
u16 data;
u16 func;
u16 pull;
pin_reset_bias(&pin_bias, pin);
if (IS_ERR_OR_NULL(bank)) {
pr_debug("invalid pin number [%d] to set pinconf\n", pin);
return -EINVAL;
}
switch (SUNXI_PINCFG_UNPACK_TYPE(config)) {
case SUNXI_PINCFG_TYPE_DRV:
dlevel = SUNXI_PINCFG_UNPACK_VALUE(config);
val = pinctrl_readl_reg(bank->membase + sunxi_dlevel_reg(pin_bias));
mask = DLEVEL_PINS_MASK << sunxi_dlevel_offset(pin_bias);
val=(val & ~mask) | (dlevel << sunxi_dlevel_offset(pin_bias));
reg=bank->membase + sunxi_dlevel_reg(pin_bias);
pinctrl_write_reg(val,reg);
pr_debug("sunxi pconf set pin [%s] drive strength to [LEVEL%d]\n",
pin_get_name(pctl->pctl_dev, pin), dlevel);
break;
case SUNXI_PINCFG_TYPE_PUD:
pull = SUNXI_PINCFG_UNPACK_VALUE(config);
val = pinctrl_readl_reg(bank->membase + sunxi_pull_reg(pin_bias));
mask = PULL_PINS_MASK << sunxi_pull_offset(pin_bias);
val=(val & ~mask) | (pull << sunxi_pull_offset(pin_bias));
reg=bank->membase + sunxi_pull_reg(pin_bias);
pinctrl_write_reg(val,reg);
pr_debug("sunxi pconf set pin [%s] pull to [%d]\n",
pin_get_name(pctl->pctl_dev, pin), pull);
break;
case SUNXI_PINCFG_TYPE_DAT:
data = SUNXI_PINCFG_UNPACK_VALUE(config);
val = pinctrl_readl_reg(bank->membase + sunxi_data_reg(pin_bias));
mask = DATA_PINS_MASK << sunxi_data_offset(pin_bias);
val=(val & ~mask) | (data << sunxi_data_offset(pin_bias));
reg=bank->membase + sunxi_data_reg(pin_bias);
pinctrl_write_reg(val,reg);
pr_debug("sunxi pconf set pin [%s] data to [%d]\n",
pin_get_name(pctl->pctl_dev, pin), data);
break;
case SUNXI_PINCFG_TYPE_FUNC:
func = SUNXI_PINCFG_UNPACK_VALUE(config);
val = pinctrl_readl_reg(bank->membase + sunxi_mux_reg(pin_bias));
mask = MUX_PINS_MASK << sunxi_mux_offset(pin_bias);
val=(val & ~mask) | (func << sunxi_mux_offset(pin_bias));
reg=bank->membase + sunxi_mux_reg(pin_bias);
pinctrl_write_reg(val,reg);
pr_debug("sunxi pconf set pin [%s] func to [%d]\n",
pin_get_name(pctl->pctl_dev, pin), func);
break;
default:
pr_debug("invalid sunxi pconf type for set\n");
return -EINVAL;
}
return 0;
}
从上面可以看到,配置 GPIO 都是通过 pinctrl_write_reg(val,reg); 写寄存器的方式实现:
static inline void pinctrl_write_reg(u32 value,void __iomem *reg)
{
if(is_arisc_pin(reg)) {
sunxi_smc_writel(value, reg);
} else {
writel(value, reg);
}
......
}
综上分析思路,之所有使用这种方式操作 GPIO 不会对已有的驱动造成影响,是因为该方式是直接操作 SOC 的寄存器方式实现,直接绕过来 Linux 驱动的 GPIO 操作接口。
其实如果有学过 pinctrl 驱动,很容易就能定位到最终的 pinctrl_write_reg() 实现方式,没接触过的话,也是可以通过上述思路找到。虽然过程有点繁琐,但是这种分析思路,可以应对很多自己没有接触的驱动框架。
那为什么设定 GPIO 的时候,如 PB2 不能写成 PB02 呢?这是因为跟 GPIO 名字匹配有关,操作哪个 GPIO 是通过传入的名字确定的,注意上述的 pin_get_from_name() 和 sunxi_pinctrl_probe() 的实现,很容易找到下面的代码
struct sunxi_pinctrl_desc sunxi_pinctrl_data = {
.pins = sun8i_w8_pins,
.npins = ARRAY_SIZE(sun8i_w8_pins),
.banks = sun8i_w8_banks,
.nbanks = ARRAY_SIZE(sun8i_w8_banks),
};
static struct sunxi_desc_pin sun8i_w8_pins[]={
......
SUNXI_PIN(SUNXI_PINCTRL_PIN_PB2,
SUNXI_FUNCTION(0x0, "gpio_in"),
SUNXI_FUNCTION(0x1, "gpio_out"),
SUNXI_FUNCTION(0x2, "uart2"), /* RTS */
SUNXI_FUNCTION(0x6, "eint"), /* EINT2 */
SUNXI_FUNCTION(0x7, "io_disable")),
......
};
// 可以看到,这里定义的名字是 PB2 而不是 PB02, 如果传入的是 PB02 就会匹配失败
#define SUNXI_PINCTRL_PIN_PB0 PINCTRL_PIN(SUNXI_PB_BASE + 0, "PB0")
#define SUNXI_PINCTRL_PIN_PB1 PINCTRL_PIN(SUNXI_PB_BASE + 1, "PB1")
#define SUNXI_PINCTRL_PIN_PB2 PINCTRL_PIN(SUNXI_PB_BASE + 2, "PB2")
#define SUNXI_PINCTRL_PIN_PB3 PINCTRL_PIN(SUNXI_PB_BASE + 3, "PB3")
#define SUNXI_PINCTRL_PIN_PB4 PINCTRL_PIN(SUNXI_PB_BASE + 4, "PB4")
#define SUNXI_PINCTRL_PIN_PB5 PINCTRL_PIN(SUNXI_PB_BASE + 5, "PB5")
#define SUNXI_PINCTRL_PIN_PB6 PINCTRL_PIN(SUNXI_PB_BASE + 6, "PB6")
该命令行方式通过 debugfs 暴露操作接口,通过 pinctrl 提供具体的 GPIO 操作接口,最终通过读写寄存器的方式实现。