赞
踩
芯片内部有很多引脚,这些引脚可以接到GPIO模块,也可以接到I2C等模块。
通过Pinctrl子系统来选择引脚的功能(mux function)、配置引脚:
当一个引脚被复用为GPIO功能时,我们可以去设置它的方向、设置/读取它的值。
GPIO名为"General Purpose Input/Output",通用目的输入/输出,就是常用的引脚。
GPIO可能是芯片自带的,也可能通过I2C、SPI接口扩展:
GPIO有一些通用功能、通用属性。
可以设为输出:让它输出高低电平;
可以设为输入,读取引脚当前电平;
可以用来触发中断
对于芯片自带的GPIO,它的访问时很快的,可以在获得spinlocks的情况下操作它。
但是,对于通过I2C、SPI等接口扩展的GPIO,访问它们时可能导致休眠,所以这些"GPIO Expander"就不能在获得spinlocks的情况下使用。
Active-High and Active-Low
以LED为例,需要设置GPIO电平。但是有些电路可能是高电平点亮LED,有些是低电平点亮LED。
可以使用如下代码:
- gpiod_set_value(gpio, 1); // 输出高电平点亮LED
- gpiod_set_value(gpio, 0); // 输出低电平点亮LED
对应同一个目标:点亮LED,对于不同的LED,就需要不同的代码,原因在于上面的代码中1、0表示的是"物理值"。
如果能使用"逻辑值",同样的逻辑值在不同的配置下输出对应的物理值,就可以保持代码一致,比如:
- gpiod_set_value(gpio, 1); // 输出逻辑1
- // 在Active-High的情况下它会输出高电平
- // 在Active-Low的情况下它会输出低电平
Open Drain and Open Source
有多个GPIO驱动同时驱动一个电路时,就需要设置Open Drain或Open Source。
Open Drain:引脚被设置为低电平时才会驱动电路,典型场景是I2C接口。
Open Source:引脚被设置为高电平时才会驱动电路
管理GPIO,既能支持芯片本身的GPIO,也能支持扩展的GPIO。
提供统一的、简便的访问接口,实现:输入、输出、中断。
概述:向上接口主要有几类:获得GPIO,设置方向,读值写值,释放GPIO;
在Linux内核中,GPIOLIB
是一个通用的 GPIO (通用输入输出) 抽象层,它提供了一组 API 供驱动程序和用户空间程序使用。以下是 GPIOLIB
向上提供的主要接口及其说明:
gpio_request
:
gpio_free
:
gpio_direction_input
:
gpio_direction_output
:
gpio_get_value
:
gpio_set_value
:
gpio_get_value_cansleep
:
gpio_get_value
类似,但此函数可以在睡眠上下文中使用。gpio_set_value_cansleep
:
gpio_set_value
类似,但此函数可以在睡眠上下文中使用。gpio_cansleep
:
gpio_to_irq
:
irq_to_gpio
:
gpiochip_add
:
gpio_chip
结构体,它描述了一组GPIO引脚及其操作函数。gpiochip_remove
:
gpio_chip
。gpiochip_line_config
:
gpio_chip
中的特定引脚。gpiochip_request_own
:
gpio_chip
中的引脚的所有权。gpiochip_request_unown
:
gpio_chip
中的引脚的所有权。gpiochip_set
:
gpio_chip
中的多个引脚设置值。gpiochip_clear
:
gpio_chip
中的多个引脚的值。gpiochip_set_direction
:
gpio_chip
中的多个引脚设置方向。gpiochip_get_direction
:
gpio_chip
中引脚的方向。这些接口为内核中的驱动程序提供了一种通用的方式来操作硬件GPIO引脚,同时隐藏了具体的硬件细节。此外,GPIOLIB
还提供了一些辅助函数,用于处理GPIO引脚的请求和释放,以及配置引脚的输入/输出模式。
概述:获得引脚,释放引脚,配置引脚,读取\写入值,清除引脚,获取地址,获取标签(信息),配置中断
chip.request
:
chip.free
:
chip.direction_input
:
chip.direction_output
:
chip.get
:
chip.set
:
chip.clear
:
chip.set_config
:
chip.to_irq
:
chip.dbg_show
:
chip.label
:
chip.base
:
chip.ngpio
:
chip.parent
:
chip.of_node
:
chip.of_xlate
:
这些接口定义在 struct gpio_chip
结构体中,硬件平台的 GPIO 驱动程序需要填充这个结构体并实现相应的函数。这样,GPIOLIB
可以与底层硬件交互,而上层的驱动程序和应用程序则可以使用 GPIOLIB
提供的通用接口来操作 GPIO 引脚。
记住GPIO Controller的要素,这有助于理解它的驱动程序:
一个GPIO Controller里有多少个引脚?有哪些引脚?
需要提供函数,设置引脚方向、读取/设置数值
需要提供函数,把引脚转换为中断
以Linux面向对象编程的思想,一个GPIO Controller必定会使用一个结构体来表示,这个结构体必定含有这些信息:
GPIO引脚信息
控制引脚的函数
中断相关的函数
每个GPIO Controller用一个gpio_device来表示:
里面每一个gpio引脚用一个gpio_desc来表示
gpio引脚的函数(引脚控制、中断相关),都放在gpio_chip里
我们并不需要自己创建gpio_device,编写驱动时要创建的是gpio_chip,里面提供了:
控制引脚的函数
中断相关的函数
引脚信息:支持多少个引脚?各个引脚的名字?
(实现GPIOLIB向下提供的接口函数)
我们去使用GPIO子系统时,首先是获得某个引脚对应的gpio_desc。
gpio_device表示一个GPIO Controller,里面支持多个GPIO。
在gpio_device中有一个gpio_desc数组,每一引脚有一项gpio_desc。
分配、设置、注册gpioc_chip结构体,示例:drivers\gpio\gpio-74x164.c
概述:核心:分配/设置/注册一个gpio_chip结构体。
头文件支持,定义全局变量结构体指针chip()
定义一个全局变量用于模拟GPIO的
- #include <linux/module.h> // 模块化编程支持
- #include <linux/err.h> // 错误处理
- #include <linux/init.h> // 模块初始化和清理宏
- #include <linux/io.h> // IO操作
- #include <linux/mfd/syscon.h> // 系统控制
- #include <linux/of.h> // 设备树操作
- #include <linux/of_device.h> // 设备树设备操作
- #include <linux/of_address.h> // 设备树地址解析
- #include <linux/gpio/consumer.h> // GPIO消费者
- #include <linux/gpio/driver.h> // GPIO驱动
- #include <linux/slab.h> // 内存分配
- #include <linux/regmap.h> // 寄存器映射
-
- static struct gpio_chip * g_virt_gpio; // 定义一个全局的gpio_chip指针
- static int g_gpio_val = 0; // 定义一个全局变量,用于模拟GPIO值
定义设备树匹配表,设置GPIO属性->方向(输入和输出),获取GPIO的值,设置GPIO的值
- // 设备树匹配表
- static const struct of_device_id virtual_gpio_of_match[] = {
- { .compatible = "100ask,virtual_gpio", },
- { },
- };
-
- // 设置GPIO方向为输出
- static int virt_gpio_direction_output(struct gpio_chip *gc,
- unsigned offset, int val)
- {
- printk("set pin %d as output %s\n", offset, val ? "high" : "low");
- return 0;
- }
-
- // 设置GPIO方向为输入
- static int virt_gpio_direction_input(struct gpio_chip *chip,
- unsigned offset)
- {
- printk("set pin %d as input\n", offset);
- return 0;
- }
-
- // 获取GPIO值
- static int virt_gpio_get_value(struct gpio_chip *gc, unsigned offset)
- {
- return (g_gpio_val & (1<<offset)) ? 1 : 0;
- }
-
- // 设置GPIO值
- static void virt_gpio_set_value(struct gpio_chip *gc,
- unsigned offset, int val)
- {
- if (val)
- g_gpio_val |= (1 << offset);
- else
- g_gpio_val &= ~(1 << offset);
- }
平台驱动探测函数:分配,设置(设置函数,设置值),注册(在内核中添加身份信息)
- // 平台设备探测函数
- static int virtual_gpio_probe(struct platform_device *pdev)
- {
- int ret;
-
- printk(KERN_INFO "%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
-
- /* 1. 分配gpio_chip */
- g_virt_gpio = devm_kzalloc(&pdev->dev, sizeof(*g_virt_gpio), GFP_KERNEL);
-
- /* 2. 设置gpio_chip */
-
- /* 2.1 设置函数 */
- g_virt_gpio->label = pdev->name;
- g_virt_gpio->direction_output = virt_gpio_direction_output;
- g_virt_gpio->direction_input = virt_gpio_direction_input;
- g_virt_gpio->get = virt_gpio_get_value;
- g_virt_gpio->set = virt_gpio_set_value;
-
- g_virt_gpio->parent = &pdev->dev;
- g_virt_gpio->owner = THIS_MODULE;
-
- /* 2.2 设置base、ngpio值 */
- g_virt_gpio->base = -1;
- ret = of_property_read_u32(pdev->dev.of_node, "ngpios", &g_virt_gpio->ngpio);
-
- /* 3. 注册gpio_chip */
- ret = devm_gpiochip_add_data(g_virt_gpio, NULL);
-
- return 0;
- }
定义平台设备移除函数,定义虚拟GPIO驱动结构体,定义入口函数(注册平台驱动),出口函数(反注册平台驱动),
- // 平台设备移除函数
- static int virtual_gpio_remove(struct platform_device *pdev)
- {
- printk(KERN_INFO "%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
- return 0;
- }
-
- // 虚拟GPIO驱动结构体
- static struct platform_driver virtual_gpio_driver = {
- .probe = virtual_gpio_probe,
- .remove = virtual_gpio_remove,
- .driver = {
- .name = "100ask_virtual_gpio",
- .of_match_table = of_match_ptr(virtual_gpio_of_match),
- }
- };
-
- /* 1. 入口函数 */
- static int __init virtual_gpio_init(void)
- {
- printk(KERN_INFO "%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
- /* 1.1 注册一个platform_driver */
- return platform_driver_register(&virtual_gpio_driver);
- }
-
- /* 2. 出口函数 */
- static void __exit virtual_gpio_exit(void)
- {
- printk(KERN_INFO "%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
- /* 2.1 反注册platform_driver */
- platform_driver_unregister(&virtual_gpio_driver);
- }
-
- module_init(virtual_gpio_init);
- module_exit(virtual_gpio_exit);
-
- MODULE_LICENSE("GPL");
假设使用这个虚拟的GPIO Controller的pinA来控制LED:
要使用pinA来控制LED,首先要通过Pinctrl子系统把它设置为GPIO功能,然后才能设置它为输出引脚、设置它的输出值。
所以在设备树文件里,应该添加Pinctrl的内容:
- virtual_pincontroller {
- compatible = "100ask,virtual_pinctrl";
- myled_pin: myled_pin {
- functions = "gpio";
- groups = "pin0";
- configs = <0x11223344>;
- };
- };
-
- gpio_virt: virtual_gpiocontroller {
- compatible = "100ask,virtual_gpio";
- gpio-controller;
- #gpio-cells = <2>;
- ngpios = <4>;
- };
-
- myled {
- compatible = "100ask,leddrv";
- led-gpios = <&gpio_virt 0 GPIO_ACTIVE_LOW>;
- pinctrl-names = "default";
- pinctrl-0 = <&myled_pin>;
- };
这段代码包含了三个主要部分,分别定义了三个不同的设备节点,它们在设备树(Device Tree)中用于描述硬件设备的配置和它们之间的关系。以下是对每个部分的解释:
virtual_pincontroller 节点:
compatible
属性指定了设备的兼容字符串,用于确定使用哪个驱动程序。myled_pin
是一个子节点,代表一个具体的引脚配置。functions
属性定义了引脚的复用功能,这里是 "gpio",表示该引脚被用作通用输入输出。groups
属性指定了引脚组,这里是 "pin0"。configs
属性提供了引脚的配置参数,这里使用了一个32位的十六进制数 0x11223344
。gpio_virt 节点:
compatible
属性来指定驱动程序。gpio-controller
表示这是一个GPIO控制器。#gpio-cells
属性定义了每个GPIO引脚需要多少个单元来描述,这里是 <2>
,通常表示一个单元用于GPIO号,另一个用于标志。ngpios
属性定义了控制器拥有的GPIO引脚数量,这里是 <4>
,表示有4个GPIO引脚。virtual_pincontroller
节点相关联,用于控制引脚的GPIO功能。myled 节点:
compatible
属性指定了LED驱动的兼容字符串。led-gpios
属性定义了控制LED的GPIO引脚,格式为 <&gpio_virt 0 GPIO_ACTIVE_LOW>
,表示使用 gpio_virt
控制器的第0号引脚,且为低电平有效。pinctrl-names
和 pinctrl-0
属性用于指定引脚控制的名称和默认配置,这里引用了 myled_pin
节点。设备树节点之间的关系:
gpio_virt
节点定义了一个GPIO控制器,它可能控制着一些物理或虚拟的GPIO引脚。myled
节点定义了一个LED设备,它使用 gpio_virt
控制器的第0号引脚来控制LED的开关。virtual_pincontroller
节点定义了一个虚拟的引脚控制器,它可能用于更复杂的引脚控制逻辑,比如配置引脚的复用功能、配置参数等。代码作用:
在嵌入式Linux系统中,设备树是一种重要的机制,用于描述硬件设备的配置,特别是在没有BIOS或UEFI等传统固件的情况下。设备树允许内核以一种硬件无关的方式来初始化和控制硬件设备。
但是很多芯片,并不要求在设备树中把把引脚复用为GPIO功能。
比如STM32MP157,在它的设备树工具STM32CubeMX
即使把引脚配置为GPIO功能,它也不会在设备树中出现。
原因在于:GPIO走了后门。
现实的芯片中,并没有Pinctrl这样的硬件,它的功能大部分是在GPIO模块中实现的。
Pinctrl是一个软件虚拟处理的概念,它的实现本来就跟GPIO密切相关。
甚至一些引脚默认就是GPIO功能。
按理说:
一个引脚可能被用作GPIO,也可能被用作I2C,GPIO和I2C这些功能时相同低位的。
要用作GPIO,需要先通过Pinctrl把引脚复用为GPIO功能。
但是Pinctrl和GPIO关系密切,当你使用gpiod_get获得GPIO引脚时,它就偷偷地
通过Pinctrl把引脚复用为GPIO功能了。
从上图可知:
左边的Pinctrl支持8个引脚,在Pinctrl的内部编号为0~7
图中有2个GPIO控制器
GPIO0内部引脚编号为0~3,假设在GPIO子系统中全局编号为100~103
GPIO1内部引脚编号为0~3,假设在GPIO子系统中全局编号为104~107
假设我们要使用pin1_1,应该这样做:
根据GPIO1的内部编号1,可以换算为Pinctrl子系统中的编号5
使用Pinctrl的函数,把第5个引脚配置为GPIO功能
数据结构
GPIO子系统中的request函数,用来申请某个GPIO引脚,
它会导致Pinctrl子系统中的这2个函数之一被调用:pmxops->gpio_request_enable
或pmxops->request
调用关系如下:
- gpiod_get
- gpiod_get_index
- desc = of_find_gpio(dev, con_id, idx, &lookupflags);
- ret = gpiod_request(desc, con_id ? con_id : devname);
- ret = gpiod_request_commit(desc, label);
- if (chip->request) {
- ret = chip->request(chip, offset);
- }
这段代码片段是Linux内核中使用GPIO描述符(gpiod)API处理GPIO线的一部分。这个API用于以更抽象的方式处理GPIO,允许更好的硬件抽象,使跨不同硬件平台的GPIO操作变得更容易。以下是每个函数的作用详解:。
- `gpiod_get_index` 函数可能是获取特定索引GPIO线GPIO描述符的包装器或过程的一部分。
- `of_find_gpio` 函数使用设备树根据设备和连接ID找到GPIO描述符。
- `gpiod_request` 函数使用描述符请求GPIO线,并为GPIO线的使用请求一个标签。
- `gpiod_request_commit` 函数提交GPIO请求,使GPIO线准备好使用。
- 如果GPIO芯片有一个自定义的请求函数 (`chip->request`),这里会调用它来执行任何必要的硬件特定设置。
这段代码片段是通常涉及配置GPIO线(例如,将其设置为输入或输出,配置上拉/下拉电阻等)并然后用于其预定目的(例如,从传感器读取输入,驱动LED等)的更大过程中的一部分。确切的实现细节可以根据硬件平台和正在使用的GPIO线的具体要求而有所不同。
我们编写GPIO驱动程序时,所设置chip->request
函数,一般直接调用gpiochip_generic_request
,它导致Pinctrl把引脚复用为GPIO功能。
- gpiochip_generic_request(struct gpio_chip *chip, unsigned offset)
- pinctrl_request_gpio(chip->gpiodev->base + offset)
- ret = pinctrl_get_device_gpio_range(gpio, &pctldev, &range); // gpio是引脚的全局编号
-
- /* Convert to the pin controllers number space */
- pin = gpio_to_pin(range, gpio);
-
- ret = pinmux_request_gpio(pctldev, range, pin, gpio);
- ret = pin_request(pctldev, pin, owner, range);
代码流程解释:
gpiochip_generic_request
函数被调用以请求一个GPIO。pinctrl_request_gpio
函数使用设备的基地址和GPIO偏移量来计算全局GPIO编号。pinctrl_get_device_gpio_range
函数根据全局GPIO编号获取引脚控制器设备和GPIO的编号范围。gpio_to_pin
将全局GPIO编号转换为引脚控制器的编号空间。pinmux_request_gpio
函数请求引脚复用,确保引脚可以被用作所需的功能。pin_request
函数在引脚控制器中实际请求引脚,保留它并标记为已使用。这个过程是Linux内核中处理GPIO请求的标准方式,确保了在不同硬件平台上对GPIO的请求是统一和一致的。通过这种方式,驱动程序可以请求和使用GPIO,而不必担心底层硬件的具体细节。
Pinctrl子系统中的pin_request函数就会把引脚配置为GPIO功能:
- static int pin_request(struct pinctrl_dev *pctldev,
- int pin, const char *owner,
- struct pinctrl_gpio_range *gpio_range)
- {
- const struct pinmux_ops *ops = pctldev->desc->pmxops;
-
- /*
- * If there is no kind of request function for the pin we just assume
- * we got it by default and proceed.
- */
- if (gpio_range && ops->gpio_request_enable)
- /* This requests and enables a single GPIO pin */
- status = ops->gpio_request_enable(pctldev, gpio_range, pin);
- else if (ops->request)
- status = ops->request(pctldev, pin);
- else
- status = 0;
- }
这段代码是一个名为 pin_request
的静态函数,它的作用是请求(reserve)一个特定的引脚(pin),以供驱动程序或其他用途使用。
这个 pin_request
函数是引脚控制子系统中的一个关键组件,它确保了对特定引脚的独占访问,防止多个驱动程序同时使用同一个引脚。这是嵌入式系统和硬件抽象中常见的需求,特别是在多个硬件设备可能需要使用相同物理引脚的情况下。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。