pinctrl和gpio子系统
1、pincrtl子系统
传统配置pin的方式是直接操作寄存器,这种方式比较繁琐和容易出问题,pinctrl子系统就是为了解决这个问题而引入的。pinctrl子系统的主要工作内容如下:
●获取设备树中的pin信息
●根据获取到的pin信息来设置pin的复用功能
●根据获取到的pin信息来设置pin的电气特性,比如上下拉,速度,驱动能力等
对于使用者来说,在设备树提供相应的信息就可以了,pinctrl子系统会来完成剩下的工作。
使用pinctrl的另外一个好处是,驱动代码与单板无关。
pinctrl涉及到两个概念,pin controller和client device
前者提供服务:可以用它来复用引脚、配置引脚
后者使用服务:声明自己要使用哪些引脚的哪些功能,怎么配置他们。
pin controller节点的格式没有统一标准!!!!每家芯片都不一样,由芯片BSP厂商定义。pinctrl子系统,芯片相关的操作也是需要BSP厂商编写代码并注册给内核。所以pin controller节点属性的格式,跟BSP厂商的风格关联比较大。
如imx6ull的引脚配置由iomuxc模块负责,pin controller就在该节点下实现
&iomuxc {
……
pinctrl_test: testgrp {
fsl,pins = <
MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 config /*config是具体设置值*/
>;
};
上面的fsl,pins属性的值如下:
#define MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x0090 0x031C 0x0000 0x5 0x0
展开相应的宏发现是5个值,实际上是对应了
<mux_reg conf_reg input_reg mux_mode input_val>
IMX芯片IO配置寄存器的地址和设置的值。
从这个宏的名字也能想到就是配置PAD_UART1_RTS_B这个引脚的功能为GPIO1_IO19。后面跟着的config是配置上下拉,驱动能力,速度等电气特性。
上面这个pinctrl的节点信息,IMX有个Pins_Tool_for_i.MX_Processors软件可以通过UI界面生成。
下面就是不同芯片的pin controller的定义,可以发现属性名称不太一样,但是使用的概念是一样的。都是用controller节点配置引脚,client device设备使用引脚。
pinctrl子系统在用户driver的作用流程
●设置了pin controller的引脚配置
●在client device引用了引脚配置
●在设备状态切换的时候,pinctrl会被自动调用如:
2、gpio子系统
pinctrl子系统重点是配置PAD的复用和电气特性,如果pinctrl子系统将一个PAD复用为GPIO的话,接下来就要用到gpio子系统。gpio子系统的主要目的是方便驱动开发这使用gpio,开发出来的驱动也可以做到和单板无关。
在BSP工程师实现了GPIO子系统后,我们可以:
- 在设备树里指定GPIO引脚
- 在驱动代码中:使用GPIO子系统的标准函数控制IO
a、设备树中指定GPIO的格式
BSP定义了GPIO Controller如:imx芯片的定义
在设备树的模块节点加入IO的引用:
格式为gpios属性,或者name-gpios属性
如上面的reset引脚,用的是gpio5的引脚2
b、gpio接口函数
获取GPIO | gpiod_get |
gpiod_get_index | |
gpiod_get_array | |
devm_gpiod_get | |
devm_gpiod_get_index | |
devm_gpiod_get_array | |
设置方向 | gpiod_direction_input |
gpiod_direction_output | |
读值写值 | gpiod_get_value |
gpiod_set_value | |
释放GPIO | gpio_free |
gpiod_put | |
gpiod_put_array | |
devm_gpiod_put | |
devm_gpiod_put_array |
3、pinctrl和gpio子系统使用例子(IMX6ULL SOC芯片为例子)
●设备树相关节点编写
3.1添加pinctrl节点
用Pins_Tool_for_i.MX_Processors_v6_x64.exe工具生成pinctrl配置
或者自己手动根据厂家的规则编写
pinctrl_io:iogrp{
fsl,pins=<
MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0 /* IO */
>;
};
将该节点添加到iomuxc节点
3.2添加IO设备节点
gpiotest{
#address-cells = <1>;
#size-cells = <1>;
compatible = “test-io”;
pinctrl-names = “default”;
pinctrl-0 = <&pinctrl_io>; /* 引用IO配置*/
io-gpios = <&gpio1 3 GPIO_ACTIVE_LOW>; /* 引用GPIO*/
status = “okay”;
}
●IO驱动编写
a.定义、注册一个platform_driver
b.在probe函数里面:
根据platform_device的设备树信息确定GPIO:gpiod_get
c在file_operations的函数中使用GPIO子系统的函数操作GPIO:
gpiod_direction_output、gpiod_set_value
a.定义、注册platform_driver的参考代码
static const struct of_device_id test_io[] = {
{ .compatible = "test-io" },
{ },
};
/* 1. 定义platform_driver */
static struct platform_driver chip_demo_gpio_driver = {
.probe = chip_demo_gpio_probe,
.remove = chip_demo_gpio_remove,
.driver = {
.name = "test_io",
.of_match_table = test_io,
},
};
/* 2. 在入口函数注册platform_driver */
static int __init io_init(void)
{
int err;
err = platform_driver_register(&chip_demo_gpio_driver);
}
b. 在probe函数中获得GPIO参考代码
/* 从platform_device获得GPIO
* 把file_operations结构体告诉内核:注册驱动程序
*/
Static struct gpio_desc *io_gpio;
static int chip_demo_gpio_probe(struct platform_device *pdev)
{
//int err;
/* 4.1 设备树中定义有: io-gpios=<...>; */
io_gpio = gpiod_get(&pdev->dev, "io", 0);
//这里注意是io而不是io-gpios,跟踪代码发现gpiod_get后面会自动补全-gpios后缀
}
c. 使用GPIO子系统的函数操作GPIO
在open函数中调用GPIO函数设置引脚方向:
static int io_drv_open (struct inode *node, struct file *file)
{
gpiod_direction_output(io_gpio, 0);
return 0;
}
在write函数中调用GPIO函数设置引脚值:
/* write(fd, &val, 1); */
static ssize_t io_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
int err;
char status;
err = copy_from_user(&status, buf, 1);
/* 设置IO的状态 */
gpiod_set_value(io_gpio, status);
return 1;
}
释放GPIO:
gpiod_put(io_gpio);