这一篇2月就TODO了,因为之前一直没怎么用到,所以拖到现在。
因为我之前一直用树莓派或者esp32这些,感觉GPIO这些就是直接用就完了,所以Pinctrl的概念一直不明白,最近因为主动请缨申请了点屏幕,要用到这个,所以再看看。这里不写原理了,只写用法。
设备树
&pinctrl {
uart1_pins: uart1 {
pinmux = <0x18 0x1C>;
bias-disable;
};
};
&uart1 {
pinctrl-names = "default";
pinctrl-0 = <0x20 0x24>;
status = "okay";
};
在这个示例中,uart1_pins 定义了 UART1 的引脚配置,指定了 PINMUX_UART1_TX 和 PINMUX_UART1_RX 的引脚复用,同时禁用了引脚的上拉或下拉偏置。uart1 节点引用了这些引脚配置,并将其应用到 UART1 设备。
再给一个详细的例子
引脚控制器节点:
pinctrl: pinctrl@44e10800 {
compatible = "ti,omap4-pinctrl";
reg = <0x44e10800 0x100>;
#address-cells = <1>;
#size-cells = <0>;
pinctrl-single,register-width = <32>;
pinctrl-single,function-mask = <0x7f>;
};
compatible 属性指定了引脚控制器的类型。
reg 属性指定了引脚控制器的寄存器地址和大小。这里的0x100表示0x00到0xff,一个寄存器占4个字节,所以一共差不多64个寄存器。
#address-cells 和 #size-cells 属性定义了子节点的地址和大小单元。
pinctrl-single,register-width 和 pinctrl-single,function-mask 是特定引脚控制器的属性,用于配置引脚复用寄存器的宽度和功能掩码。
所以可以看出,这里的pinctrl支持的是一大组寄存器。
定义引脚复用和配置
&pinctrl {
uart1_pins: uart1_pins {
pinmux = <0x18 0x1C>;
bias-disable;
drive-strength = <2>;
};
i2c1_pins: i2c1_pins {
pinmux = <0x20 0x24>;
bias-pull-up;
};
};
uart1_pins 和 i2c1_pins 是两个引脚配置节点。
pinmux 属性指定了引脚的复用设置(如 UART1 TX 和 RX,引脚具体值由 SoC 数据手册定义)。
bias-disable 和 bias-pull-up 是引脚配置属性,用于配置引脚的上拉/下拉电阻。
drive-strength 属性配置了引脚驱动强度。
这部分的配置不同厂商可能差别很大。
在设备节点中引用引脚配置
&uart1 {
pinctrl-names = "default";
pinctrl-0 = <&uart1_pins>;
status = "okay";
};
&i2c1 {
pinctrl-names = "default";
pinctrl-0 = <&i2c1_pins>;
status = "okay";
};
pinctrl-names 属性指定了引脚配置的名称。
pinctrl-0 属性引用了定义的引脚配置节点(如 uart1_pins 和 i2c1_pins)。
status 属性设置为 "okay",表示设备处于启用状态。
驱动代码
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/i2c.h>
static const struct of_device_id my_i2c_of_match[] = {
{ .compatible = "myvendor,my-i2c-device", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_i2c_of_match);
static int my_i2c_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
// 获取和配置引脚
struct pinctrl *pinctrl = devm_pinctrl_get(&pdev->dev);
struct pinctrl_state *state;
if (IS_ERR(pinctrl)) {
dev_err(&pdev->dev, "Failed to get pinctrl\n");
return PTR_ERR(pinctrl);
}
state = pinctrl_lookup_state(pinctrl, "default");
if (IS_ERR(state)) {
dev_err(&pdev->dev, "Failed to lookup pinctrl state\n");
return PTR_ERR(state);
}
pinctrl_select_state(pinctrl, state);
// 其他初始化代码...
return 0;
}
static int my_i2c_remove(struct platform_device *pdev)
{
// 清理代码
return 0;
}
static struct platform_driver my_i2c_driver = {
.driver = {
.name = "my_i2c_driver",
.of_match_table = my_i2c_of_match,
},
.probe = my_i2c_probe,
.remove = my_i2c_remove,
};
module_platform_driver(my_i2c_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Author");
MODULE_DESCRIPTION("My I2C Driver");
在这个示例中,驱动程序在 probe 函数中获取 pinctrl 句柄,查找 default 状态,并将其应用到设备。这样,设备的引脚配置将在设备初始化时自动设置。
使用代码
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/i2c-dev.h>
int main(int argc, char *argv[])
{
int file;
int adapter_nr = 1; /* usually i2c-1 */
char filename[20];
int addr = 0x50; /* The I2C address of the device */
snprintf(filename, 19, "/dev/i2c-%d", adapter_nr);
file = open(filename, O_RDWR);
if (file < 0) {
perror("Failed to open the i2c bus");
exit(1);
}
if (ioctl(file, I2C_SLAVE, addr) < 0) {
perror("Failed to acquire bus access and/or talk to slave");
exit(1);
}
// Write to the I2C device
char buffer[2] = {0x00, 0xA5}; // Example: register 0x00, value 0xA5
if (write(file, buffer, 2) != 2) {
perror("Failed to write to the i2c device");
} else {
printf("Data written successfully\n");
}
// Read from the I2C device
buffer[0] = 0x00; // Register to read from
if (write(file, buffer, 1) != 1) {
perror("Failed to write to the i2c device");
} else {
if (read(file, buffer, 1) != 1) {
perror("Failed to read from the i2c device");
} else {
printf("Data read from the device: 0x%02x\n", buffer[0]);
}
}
close(file);
return 0;
}
pinctrl
是 Linux 内核中的一个公共子系统模块,用于管理和配置 SoC(System on Chip)上的引脚。它提供了一种统一的方式来配置引脚的功能和属性,如 GPIO、I2C、SPI 等。
pinctrl
子系统的代码位置
pinctrl
子系统的代码主要位于 Linux 内核源码树的以下目录中:
- 核心代码:
drivers/pinctrl/
- 设备树支持:
Documentation/devicetree/bindings/pinctrl/
- 头文件:
include/linux/pinctrl/
I2C的位置可以通过dmesg | grep i2c看到。
小结
Pinctrl主要就是配置了引脚,用了哪些管脚?这些管脚复用为什么功能?这些管脚的初始状态是什么。其实就是为了简化移植而来的,如果整个功能从A板移植到B板,那么驱动代码不用动,使用代码不用动,设备树要动,但是不多,就一点点,就是那个pinmux。重新定义一下引脚就可以了。pinmux其实也就是引脚复用的意思。。
所以我看看pinmux就好。。。
再说一个,有Pinctrl和没有Pinctrl区别在哪呢?
有
/ {
// 定义引脚控制器
pinctrl: pinctrl@fd510000 {
compatible = "qcom,tlmm";
reg = <0xfd510000 0x1000>;
gpio-controller;
#gpio-cells = <2>;
pinctrl-names = "default";
pinctrl-0 = <&tlmm_default>;
};
// 定义引脚配置
tlmm_default: pinmux {
pinctrl-single,pins = <
0x10 0x1 // 引脚 16 配置为功能 1
0x20 0x2 // 引脚 32 配置为功能 2
>;
};
// 使用引脚配置
uart0: serial@78B0000 {
compatible = "qcom,msm-uart";
reg = <0x78B0000 0x1000>;
pinctrl-names = "default";
pinctrl-0 = <&uart0_pins>;
uart0_pins: pinmux {
pinctrl-single,pins = <
0x30 0x1 // 引脚 48 配置为 UART 功能
0x31 0x1 // 引脚 49 配置为 UART 功能
>;
};
};
};
没有
/ {
// 没有引脚控制器和引脚配置节点
uart0: serial@78B0000 {
compatible = "qcom,msm-uart";
reg = <0x78B0000 0x1000>;
// 没有 pinctrl-names 和 pinctrl-0 属性
};
};
很多功能就在驱动代码中:
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#define UART0_BASE 0x78B0000
#define UART0_PIN_CONFIG 0x1234 // 假设的引脚配置寄存器地址
static int uart0_probe(struct platform_device *pdev)
{
void __iomem *base;
base = ioremap(UART0_BASE, 0x1000);
if (!base)
return -ENOMEM;
// 硬编码引脚配置
writel(UART0_PIN_CONFIG, base + 0x00);
// 其他初始化代码
...
return 0;
}
static int uart0_remove(struct platform_device *pdev)
{
// 清理代码
...
return 0;
}
static const struct of_device_id uart0_of_match[] = {
{ .compatible = "qcom,msm-uart", },
{},
};
MODULE_DEVICE_TABLE(of, uart0_of_match);
static struct platform_driver uart0_driver = {
.probe = uart0_probe,
.remove = uart0_remove,
.driver = {
.name = "uart0",
.of_match_table = uart0_of_match,
},
};
module_platform_driver(uart0_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Author");
MODULE_DESCRIPTION("UART0 Driver without pinctrl");
参考: