Linux学习笔记--GPIO子系统和PinCtrl子系统
1. 设备树节点
1.1 PinCtrl
作用:模拟硬件引脚控制器,管理引脚复用和配置
virtual_pincontroller {compatible = "100ask,virtual_pinctrl"; // 匹配虚拟pinctrl驱动myled_pin: myled_pin { // 引脚配置节点,标签为myled_pinfunctions = "gpio"; // 引脚功能:GPIO模式groups = "pin0"; // 引脚组:pin0configs = <0x11223344>; // 电气特性配置值};
};
对应驱动:需要实现 100ask,virtual_pinctrl
兼容的pinctrl驱动
1.2 GPIO控制器
gpio_virt: virtual_gpiocontroller {compatible = "100ask,virtual_gpio"; // 匹配虚拟GPIO驱动gpio-controller; // 声明这是GPIO控制器#gpio-cells = <2>; // GPIO说明符有2个cellngpios = <4>; // 提供4个GPIO
};
作用:提供GPIO操作接口
关键属性:
#gpio-cells = <2>
:引用此GPIO时需要2个参数参数1:GPIO编号(0-3)
参数2:GPIO标志(如GPIO_ACTIVE_LOW)
对应驱动:需要实现 100ask,virtual_gpio
兼容的GPIO驱动
1.3 LED设备
myled {compatible = "100ask,leddrv"; // 匹配LED驱动led-gpios = <&gpio_virt 0 GPIO_ACTIVE_LOW>; // 引用GPIO控制器pinctrl-names = "default"; // 引脚状态名称pinctrl-0 = <&myled_pin>; // 引用引脚配置
};
作用:具体的LED设备,使用前面定义的资源
关键引用:
&gpio_virt
:引用GPIO控制器标签&myled_pin
:引用引脚配置标签
2. 节点关系图
virtual_pincontroller gpio_virt↓ ↓myled_pin GPIO控制器↓ ↓pinctrl-0 ────────┐ ↓↓ ↓myled (LED设备) ──┘↓led-gpios引用
4. 交互流程
4.1 系统启动和驱动加载顺序
1. 虚拟pinctrl驱动加载 → 注册pinctrl设备
2. 虚拟GPIO驱动加载 → 注册GPIO控制器
3. LED驱动加载 → 设备匹配和初始化
4.2 LED驱动probe函数执行流程
static int led_probe(struct platform_device *pdev)
{// 1. 内核自动应用pinctrl配置// 在probe调用前,内核会自动调用pinctrl_select_state()// 将myled_pin配置应用到硬件// 2. 获取GPIO描述符struct gpio_desc *led_gpio;led_gpio = gpiod_get(&pdev->dev, "led", GPIOD_OUT_LOW);// 这会解析: led-gpios = <&gpio_virt 0 GPIO_ACTIVE_LOW>// 3. 配置GPIO方向(如果需要)gpiod_direction_output(led_gpio, 0);// 4. 使用GPIO控制LEDgpiod_set_value(led_gpio, 1); // 根据GPIO_ACTIVE_LOW,实际输出低电平
}
4.3 具体的引用解析过程
设备树引用解析:
led-gpios = <&gpio_virt 0 GPIO_ACTIVE_LOW>;
解析为:
&gpio_virt
→ 找到gpio_virt: virtual_gpiocontroller
节点0
→ GPIO编号0GPIO_ACTIVE_LOW
→ 低电平有效标志
5. 对应的驱动实现框架
5.1 pinctrl驱动
static const struct of_device_id virtual_pinctrl_of_match[] = {{ .compatible = "100ask,virtual_pinctrl" },{}
};static struct platform_driver virtual_pinctrl_driver = {.driver = {.name = "virtual_pinctrl",.of_match_table = virtual_pinctrl_of_match,},.probe = virtual_pinctrl_probe,
};
5.2 虚拟GPIO驱动
static const struct of_device_id virtual_gpio_of_match[] = {{ .compatible = "100ask,virtual_gpio" },{}
};static struct platform_driver virtual_gpio_driver = {.driver = {.name = "virtual_gpio",.of_match_table = virtual_gpio_of_match,},.probe = virtual_gpio_probe,
};
5.3 LED驱动
static const struct of_device_id led_drv_of_match[] = {{ .compatible = "100ask,leddrv" },{}
};static struct platform_driver led_driver = {.driver = {.name = "led_drv",.of_match_table = led_drv_of_match,},.probe = led_probe,
};
6. 在系统中的体现
6.1 加载后的sysfs结构
/sys/class/gpio/
└── gpiochipXXX/ # 虚拟GPIO控制器├── base → 480├── label → "virtual_gpiocontroller"└── ngpio → 4/sys/class/leds/
└── myled/ # LED设备/sys/kernel/debug/pinctrl/
└── virtual_pincontroller/ # 虚拟引脚控制器
6.2 用户空间使用
# 导出GPIO
echo 480 > /sys/class/gpio/export # GPIO 480 (虚拟GPIO0)
echo out > /sys/class/gpio/gpio480/direction
echo 1 > /sys/class/gpio/gpio480/value # 控制LED
7. 完整的工作流程
步骤1:设备树解析
内核解析设备树,建立设备节点关系
识别
virtual_pincontroller
、gpio_virt
、myled
节点
步骤2:驱动匹配和加载
// pinctrl驱动匹配virtual_pincontroller
// GPIO驱动匹配gpio_virt
// LED驱动匹配myled
步骤3:LED设备初始化
// 1. 内核自动调用pinctrl,应用myled_pin配置
// 2. LED驱动probe函数执行
// 3. 获取GPIO描述符,控制LED
步骤4:正常运行
用户空间或内核其他部分可以通过GPIO接口控制LED
所有硬件抽象层正常工作
gpio-ranges
属性
基本语法:
gpio-ranges = <&pinctrl_phandle gpio_base pin_base count>;
参数解释:
&pinctrl_phandle
:指向Pinctrl控制器的句柄gpio_base
:GPIO控制器中的起始GPIO编号pin_base
:Pinctrl控制器中的起始引脚编号count
:映射的引脚数量
完整的设备树
传统方式(需要显式pinctrl配置)
// Pinctrl控制器
pinctrlA: pinctrl@40020000 {compatible = "vendor,pinctrl";reg = <0x40020000 0x1000>;// 引脚配置uart1_pins: uart1-pins {pins = "PIOA0", "PIOA1";function = "uart1";};gpio_pins: gpio-pins {pins = "PIOA2", "PIOA3";function = "gpio";};
};// GPIO控制器
gpioA: gpio@40021000 {compatible = "vendor,gpio";reg = <0x40021000 0x1000>;gpio-controller;#gpio-cells = <2>;ngpios = <16>;// 没有gpio-ranges,需要显式配置pinctrl
};// 使用GPIO的设备
led {compatible = "vendor,led";led-gpios = <&gpioA 2 GPIO_ACTIVE_HIGH>;pinctrl-names = "default";pinctrl-0 = <&gpio_pins>; // 必须显式配置
};
使用gpio-ranges的方式
// Pinctrl控制器
pinctrlA: pinctrl@40020000 {compatible = "vendor,pinctrl";reg = <0x40020000 0x1000>;// 注意:这里不需要定义gpio-pins了
};// GPIO控制器
gpioA: gpio@40021000 {compatible = "vendor,gpio";reg = <0x40021000 0x1000>;gpio-controller;#gpio-cells = <2>;ngpios = <16>;// 建立GPIO和Pinctrl的映射关系gpio-ranges = <&pinctrlA 0 128 12>; // GPIO0-11 → Pinctrl引脚128-139
};// 使用GPIO的设备
led {compatible = "vendor,led";led-gpios = <&gpioA 2 GPIO_ACTIVE_HIGH>;// 不需要pinctrl-0,GPIO子系统自动处理
};
映射关系的内部实现
内核中的映射处理
当GPIO控制器注册时:
int gpiochip_add(struct gpio_chip *chip)
{// 解析gpio-ranges属性ret = gpiochip_add_pin_range(chip, dev_name(&pdev->dev),0, 0, chip->ngpio);// 建立映射关系pinctrl_add_gpio_range(chip->pinctrl, &range);
}
GPIO请求时的自动处理
struct gpio_desc *gpiod_get(struct device *dev, const char *con_id,enum gpiod_flags flags)
{// 1. 获取GPIO描述符desc = of_get_named_gpiod_flags(dev->of_node, con_id, 0, &flags);// 2. 自动通过pinctrl配置引脚为GPIO功能ret = pinctrl_request_gpio(desc);// 3. 配置GPIO方向gpiod_direction_output(desc, flags & GPIOD_OUT_HIGH ? 1 : 0);return desc;
}
复杂的映射场景
多个不连续的映射范围
gpioA: gpio@40021000 {compatible = "vendor,gpio";gpio-controller;#gpio-cells = <2>;ngpios = <32>;// 多个映射范围gpio-ranges =<&pinctrlA 0 128 8>, // GPIO0-7 → 引脚128-135<&pinctrlA 8 200 4>, // GPIO8-11 → 引脚200-203 <&pinctrlA 12 240 16>; // GPIO12-27 → 引脚240-255
};
多个Pinctrl控制器的映射
gpioA: gpio@40021000 {compatible = "vendor,gpio";gpio-controller;#gpio-cells = <2>;ngpios = <32>;// 映射到不同的Pinctrl控制器gpio-ranges =<&pinctrlA 0 128 16>, // GPIO0-15 → PinctrlA的128-143<&pinctrlB 16 64 16>; // GPIO16-31 → PinctrlB的64-79
};
驱动中的实际效果
使用gpio-ranges前的代码
static int led_probe(struct platform_device *pdev)
{struct gpio_desc *led;// 需要确保设备树中有pinctrl-0配置led = gpiod_get(&pdev->dev, "led", GPIOD_OUT_LOW);if (IS_ERR(led)) {// 可能因为pinctrl配置失败return PTR_ERR(led);}return 0;
}
使用gpio-ranges后的代码
static int led_probe(struct platform_device *pdev)
{struct gpio_desc *led;// 直接获取GPIO,不需要关心pinctrl配置led = gpiod_get(&pdev->dev, "led", GPIOD_OUT_LOW);// GPIO子系统自动处理引脚复用return 0;
}
验证映射关系
在sysfs中查看映射
# 查看GPIO范围
cat /sys/kernel/debug/gpio# 查看pinctrl映射
cat /sys/kernel/debug/pinctrl/pinctrl-handles# 查看具体的引脚状态
cat /sys/kernel/debug/pinctrl/<pinctrl>/pinmux-pins
在驱动中调试
// 在GPIO驱动probe函数中添加调试信息
static int gpio_probe(struct platform_device *pdev)
{struct gpio_chip *chip;// 解析gpio-rangesret = gpiochip_add_pin_range(chip, dev_name(&pdev->dev), 0, 0, chip->ngpio);if (ret) {dev_info(&pdev->dev, "Added pin range: GPIO%d-%d -> Pin%d-%d\n",range.base, range.base + range.npins - 1,range.pin_base, range.pin_base + range.npins - 1);}
}
1. 功能定位对比
第一段代码:显式引脚配置
uart1_pins: uart1-pins {pins = "PIOA0", "PIOA1";function = "uart1";
};gpio_pins: gpio-pins {pins = "PIOA2", "PIOA3";function = "gpio";
};
作用:为特定功能显式定义引脚配置
第二段代码:GPIO-Pinctrl映射
gpio-ranges = <&pinctrlA 0 128 12>;
2. 使用方式的根本区别
方式A:功能驱动配置(第一段代码)
// 设备树
my_uart_device {compatible = "vendor,uart";pinctrl-names = "default";pinctrl-0 = <&uart1_pins>; // 显式选择UART功能
};my_led_device {compatible = "vendor,led"; pinctrl-names = "default";pinctrl-0 = <&gpio_pins>; // 显式选择GPIO功能led-gpios = <&gpioA 2 GPIO_ACTIVE_HIGH>;
};
方式B:自动GPIO配置(第二段代码)
// 设备树
my_led_device {compatible = "vendor,led";// 不需要pinctrl-0配置!led-gpios = <&gpioA 2 GPIO_ACTIVE_HIGH>; // 自动配置为GPIO
};
3. 底层机制对比
方式A的底层流程:
设备probe↓
pinctrl-0应用 → 配置为指定功能↓
GPIO操作(如果配置的是GPIO功能)
方式B的底层流程:
设备probe↓
GPIO获取请求↓
通过gpio-ranges自动映射 → 自动配置为GPIO功能↓
GPIO操作
4. 具体关联分析
4.1 它们是互斥的设计选择
在实际项目中,你通常选择其中一种方式:
选择方式A(显式配置)时:
// 需要定义详细的功能配置
pinctrlA: pinctrl {uart1_pins: uart1-pins { ... };gpio_pins: gpio-pins { ... };i2c1_pins: i2c1-pins { ... };
};gpioA: gpio {// 不需要gpio-ranges// 或者有gpio-ranges但不依赖它
};
选择方式B(自动映射)时:
// Pinctrl中可能不定义GPIO功能配置
pinctrlA: pinctrl {uart1_pins: uart1-pins { ... }; // 只有非GPIO功能i2c1_pins: i2c1-pins { ... }; // 只有非GPIO功能// 没有gpio-pins!
};gpioA: gpio {gpio-ranges = <&pinctrlA 0 128 12>; // 关键映射
};
4.2 混合使用的情况
有些系统可能同时使用两种方式:
pinctrlA: pinctrl {// 为复杂功能定义显式配置uart1_pins: uart1-pins {pins = "PIOA0", "PIOA1";function = "uart1";bias-pull-up; // 需要特殊电气特性};// 为GPIO定义特殊配置gpio_strong_pins: gpio-strong {pins = "PIOA2", "PIOA3"; function = "gpio";drive-strength = <20>; // 强驱动强度};
};gpioA: gpio {gpio-ranges = <&pinctrlA 0 128 12>; // 通用映射// 特殊GPIO可以引用显式配置special-gpio {gpio-ranges = <&pinctrlA 4 132 2>; // 特定范围的映射};
};
5. 实际芯片中的差异
芯片类型1:需要显式配置(如某些老款芯片)
// 必须为每个GPIO使用显式pinctrl
pinctrl {led1_pins: led1-pins {pins = "GPIO0_5";function = "gpio";};led2_pins: led2-pins {pins = "GPIO0_6"; function = "gpio";};
};leds {led1 {pinctrl-0 = <&led1_pins>;gpios = <&gpio0 5 GPIO_ACTIVE_HIGH>;};led2 {pinctrl-0 = <&led2_pins>;gpios = <&gpio0 6 GPIO_ACTIVE_HIGH>;};
};
芯片类型2:支持自动映射(如STM32)
// 通过gpio-ranges自动处理
pinctrl {// 可能只有非GPIO功能的配置uart1_pins: uart1-pins {pins = "PA9", "PA10";function = "uart1";};
};gpioA: gpio@50000000 {gpio-ranges = <&pinctrl 0 0 16>; // GPIOA0-15 -> 引脚0-15
};leds {led1 {// 不需要pinctrl-0!gpios = <&gpioA 5 GPIO_ACTIVE_HIGH>; // 自动配置为GPIO};
};
7. 在驱动代码中的体现
使用显式配置的驱动:
static int device_probe(struct platform_device *pdev)
{// 内核会在probe前自动应用pinctrl-0// 驱动假设引脚已正确配置struct gpio_desc *gpio;gpio = gpiod_get(&pdev->dev, "signal", GPIOD_OUT_LOW);// 这里只是使用已配置好的GPIO
}
使用自动映射的驱动:
static int device_probe(struct platform_device *pdev)
{// 驱动不关心引脚如何配置struct gpio_desc *gpio;gpio = gpiod_get(&pdev->dev, "signal", GPIOD_OUT_LOW);// GPIO获取过程中自动配置引脚功能
}