大多数嵌入式Linux驱动程序和内核工程师使用gpio编写或使用pin多路复用。所谓引脚,我指的是组件的引出线。SoC做多引脚复用,这意味着一个引脚可能有几个功能; 例如,arch/arm/boot/dts/imx6dl- pinfunction .h中的MX6QDL_PAD_SD3_DAT1可以是SD3数据线1,UART1的cts/rts, Flexcan2的Rx,或者普通的GPIO。
你选择一个引脚应该工作的模式的机制被称为引脚muxing。负责这个的系统被称为引脚控制器。在本章的第二部分,我们将讨论通用输入输出(GPIO),它是一种特殊的功能(模式),一个引脚可以在其中工作。
在本章中,我们将:
- 遍历引脚控制子系统,看看如何在DT中声明它们的节点
- 探索传统的基于整数的GPIO接口和新的基于描述符的接口API
- 处理映射到IRQ的GPIO
- 处理专用于gpio的sysfs接口
Pin Control 子系统
pin control (pinctrl)子系统允许管理pin muxing。在DT中,需要以某种方式多路复用引脚的设备必须声明它们需要的引脚控制配置。
pinctrl子系统提供:
- 引脚复用,它允许为不同的目的重用相同的引脚,例如一个引脚作为UART TX引脚,GPIO线,或HSI数据线。多路复用可以影响一组引脚或单个引脚。
- 引脚配置,应用引脚的电子特性,如上拉、下拉、驱动器强度、反弹周期等。
这篇文章的目的仅限于使用由引脚控制器驱动程序导出的函数,而不包括如何编写引脚控制器驱动程序。
Pinctrl and the device tree
pinctrl子系统只是一种收集引脚(不仅仅是GPIO)的方式,并将它们传递给驱动程序。引脚控制器驱动程序负责解析DT中的引脚描述,并将其配置应用到芯片中。驱动程序通常需要一组两个嵌套节点来描述一组引脚配置。第一个节点描述组的功能(组将用于什么目的),第二个节点保存引脚的配置。
在DT中如何分配引脚组很大程度上取决于平台,因此也取决于引脚控制器驱动程序。每个引脚控制状态都有一个从0开始的连续整数ID。
您可以使用name属性,它将映射到ID的顶部,这样相同的名称总是指向相同的ID。
每个客户机设备自己的绑定决定了必须在其DT节点中定义的一组状态,以及是否定义必须提供的一组状态id,还是定义必须提供的一组状态名称。在任何情况下,一个pin配置节点可以通过两个属性分配到一个设备:
- pinctrl-<ID>:这允许我们给出设备特定状态所需的pinctrl配置列表。它是一个句柄列表,每个句柄都指向一个pin配置节点。这些被引用的引脚配置节点必须是它们配置的引脚控制器的子节点。在这个列表中可能存在多个条目,以便可以配置多个引脚控制器,或者从多个节点为单个引脚控制器构建一个状态,每个节点都是整个配置的一部分。
- pinctrl-name:这允许为列表中的每个状态提供一个名称。列表项0定义了整数状态ID 0的名称,列表项1定义了状态ID 1的名称,以此类推。状态ID 0通常被命名为default。标准化状态列表可以在include/linux/pinctrl/pinctrl-state.h中找到。
以下是DT的摘录,显示了一些设备节点及其引脚控制节点:
1 usdhc@0219c000 { /* uSDHC4 */ 2 non-removable; 3 vmmc-supply = <®_3p3v>; 4 status = "okay"; 5 pinctrl-names = "default"; 6 pinctrl-0 = <&pinctrl_usdhc4_1>; 7 }; 8 gpio-keys { 9 compatible = "gpio-keys"; 10 pinctrl-names = "default"; 11 pinctrl-0 = <&pinctrl_io_foo &pinctrl_io_bar>; 12 }; 13 iomuxc@020e0000 { 14 compatible = "fsl,imx6q-iomuxc"; 15 reg = <0x020e0000 0x4000>; 16 /* shared pinctrl settings */ 17 usdhc4 { /* first node describing the function */ 18 pinctrl_usdhc4_1: usdhc4grp-1 { /* second node */ 19 fsl,pins = < 20 MX6QDL_PAD_SD4_CMD__SD4_CMD 0x17059 21 MX6QDL_PAD_SD4_CLK__SD4_CLK 0x10059 22 MX6QDL_PAD_SD4_DAT0__SD4_DATA0 0x17059 23 MX6QDL_PAD_SD4_DAT1__SD4_DATA1 0x17059 24 MX6QDL_PAD_SD4_DAT2__SD4_DATA2 0x17059 25 MX6QDL_PAD_SD4_DAT3__SD4_DATA3 0x17059 26 MX6QDL_PAD_SD4_DAT4__SD4_DATA4 0x17059 27 MX6QDL_PAD_SD4_DAT5__SD4_DATA5 0x17059 28 MX6QDL_PAD_SD4_DAT6__SD4_DATA6 0x17059 29 MX6QDL_PAD_SD4_DAT7__SD4_DATA7 0x17059 30 >; 31 }; 32 }; 33 [...] 34 uart3 { 35 pinctrl_uart3_1: uart3grp-1 { 36 fsl,pins = < 37 MX6QDL_PAD_EIM_D24__UART3_TX_DATA 0x1b0b1 38 MX6QDL_PAD_EIM_D25__UART3_RX_DATA 0x1b0b1 39 >; 40 }; 41 }; 42 // GPIOs (Inputs) 43 44 45 gpios { 46 pinctrl_io_foo: pinctrl_io_foo { 47 fsl,pins = < 48 MX6QDL_PAD_DISP0_DAT15__GPIO5_IO09 0x1f059 49 MX6QDL_PAD_DISP0_DAT13__GPIO5_IO07 0x1f059 50 >; 51 }; 52 pinctrl_io_bar: pinctrl_io_bar { 53 fsl,pins = < 54 MX6QDL_PAD_DISP0_DAT11__GPIO5_IO05 0x1f059 55 MX6QDL_PAD_DISP0_DAT9__GPIO4_IO30 0x1f059 56 MX6QDL_PAD_DISP0_DAT7__GPIO4_IO28 0x1f059 57 MX6QDL_PAD_DISP0_DAT5__GPIO4_IO26 0x1f059 58 >; 59 }; 60 }; 61 };
在上面的例子中,pin配置以<PIN_FUNCTION> < PIN_SETTING > 的形式给出,例如:
MX6QDL_PAD_DISP0_DAT15__GPIO5_IO09 0x1f059
MX6QDL_PAD_DISP0_DAT15__GPIO5_IO09代表pin function,在本例中是GPIO, 0x1f059表示引脚设置。
以下行:
MX6QDL_PAD_EIM_D25__UART3_RX_DATA 0x1b0b1
MX6QDL_PAD_EIM_D25__UART3_RX_DATA表示pin function,即UART3的RX线,0x1b0b1表示设置。
pin function是一个宏,它的值只对pin控制器驱动有意义。它们通常定义在位于arch/<arch>/boot/dts/中的头文件中。如果你使用UDOO四轴,例如,它有一个i.MX6四轴核心(ARM), pin函数头将是arch/ ARM /boot/dts/imx6q- pinfunct .h。下面是GPIO5控制器第5行对应的宏:
/*
* The pin function ID is a tuple of <mux_reg conf_reg input_reg mux_mode input_val>
*/
#define MX6QDL_PAD_DISP0_DAT11__GPIO5_IO05 0x19c 0x4b0 0x000 0x5 0x0
<PIN_SETTING>可以用来设置诸如上拉,下拉,保持器,驱动强度,等等。它应该如何指定取决于pin控制器绑定,它的值的含义取决于SoC数据表,通常在IOMUX部分。在i.MX6 IOMUXC上,少于17位专用于此目的。
前面的这些节点是从相应的特定于驱动程序的节点调用的。此外,这些引脚是在相应的驱动初始化过程中配置的。在选择一个pin组状态之前,您必须首先使用pinctrl_get()函数获得pin控制,调用pinctrl_lookup_state()来检查请求的状态是否存在,最后使用pinctrl_select_state()来应用状态。
下面的示例展示了如何获取pin control并应用它的默认配置:
1 struct pinctrl *p; 2 struct pinctrl_state *s; 3 int ret; 4 p = pinctrl_get(dev); 5 if (IS_ERR(p)) 6 return p; 7 s = pinctrl_lookup_state(p, name); 8 if (IS_ERR(s)) { 9 devm_pinctrl_put(p); 10 return ERR_PTR(PTR_ERR(s)); 11 } 12 ret = pinctrl_select_state(p, s); 13 if (ret < 0) { 14 devm_pinctrl_put(p); 15 return ERR_PTR(ret); 16 }
通常在驱动程序初始化期间执行这些步骤。这段代码的合适位置可能是probe()函数内部。
pinctrl_select_state() 在内部调用 pinmux_enable_setting() ,而 pinmux_enable_setting() 依次调用pin控制节点中的每个pin上的 pin_request() 。
pin控件可以通过 pinctrl_put() 函数释放。您可以使用该API的resource-managed版本。也就是说,您可以使用 pinctrl_get_select() ,给定要选择的状态名,来配置pinmux。该函数在 include/linux/pinctrl/consumer.h 中定义如下:
static struct pinctrl *pinctrl_get_select(struct device *dev, const char *name)
这里,*name是在pinctrl-name属性中写入的状态名。如果状态的名称是default,你可以调用pinctr_get_select_default()函数,它是对pinctl_get_select()的包装:
1 static struct pinctrl * pinctrl_get_select_default(struct device *dev) 2 { 3 return pinctrl_get_select(dev, PINCTRL_STATE_DEFAULT); 4 }
让我们看一个板子特有的DTS文件中的实际示例(am335x-evm.dts):
1 dcan1: d_can@481d0000 { 2 status = "okay"; 3 pinctrl-names = "default"; 4 pinctrl-0 = <&d_can1_pins>; 5 };
对应的驱动程序示例如下:
1 pinctrl = devm_pinctrl_get_select_default(&pdev->dev); 2 if (IS_ERR(pinctrl)) 3 dev_warn(&pdev->dev,"pins are not configured from the driver ");
当设备被探测(probe)时,pin 控制核心将自动为我们声明默认的 pinctrl 状态。如果您定义了一个 init 状态,那么 pinctrl 核心会在 probe() 函数之前自动将 pinctrl 设置为这个状态,然后在 probe() 之后切换到默认状态(除非驱动程序已经显式地改变了状态)。
The GPIO subsystem
从硬件的角度来看,GPIO 是一种功能,一种引脚可以操作的模式。从软件的角度来看,GPIO 只是一条数字线,它可以作为输入或输出,并且只能有两个值(1表示高,0表示低)。内核 GPIO 子系统提供了您可以想象的设置和处理的所有函数从你的驱动程序内部的 GPIO 行:
- 在从驱动程序中使用 GPIO 之前,您应该向内核声明它。这是一种获得 GPIO 所有权的方法,防止其他驱动程序访问同一 GPIO。在获得 GPIO 的所有权后,你可以:
- 设置方向。
- 如果用作输出,则切换输出状态(驱动线高或低)。
- 设置 debounce-interval,如果作为输入,则读取状态。对于映射到 IRQ 的 GPIO,你可以定义中断应该在什么边缘/级别被触发,并且注册一个处理程序,当中断发生时就会运行。
实际上,在内核中有两种不同的方式来处理 GPIO,如下:
- 旧的基于整数的接口,其中gpio用整数表示。
- 新的和推荐的基于描述符的(descriptor-based)接口,其中 GPIO 由一个带有专用 API 的不透明结构表示和描述。
基于整数的GPIO接口-遗留
基于整数的接口是最著名的。GPIO 由一个整数标识,这个整数用于需要在 GPIO 上执行的每一个操作。下面是包含传统 GPIO 访问函数的头文件:
#include <linux/gpio.h>
在内核中有一些处理 GPIO 的知名函数。
声明和配置GPIO
你可以使用 gpio_request() 函数来分配和获得 GPIO 的所有权:
static int gpio_request(unsigned gpio, const char *label)
gpio 表示我们感兴趣的gpio号, label 是内核为sysfs中的gpio所使用的标签,我们可以在 /sys/kernel/debug/gpio 中看到。您必须检查返回的值,其中0表示成功,一个错误产生一个负的错误代码。一旦你用 GPIO 完成了这些,应该用 gpio_free() 函数释放它:
void gpio_free(unsigned int gpio)
如果有疑问,可以使用 gpio_is_valid() 函数在分配这个 GPIO 号之前检查它在系统上是否有效:
static bool gpio_is_valid(int number)
一旦我们拥有了GPIO,我们可以根据需要改变它的方向,以及它应该是输入还是输出,使用 gpio_direction_input() 或 gpio_direction_output() 函数:
static int gpio_direction_input(unsigned gpio) static int gpio_direction_output(unsigned gpio, int value)
gpio是我们需要设定方向的 GPIO 号。第二个参数是配置 GPIO 作为output, value,即输出方向有效时GPIO的状态。同样,返回值是零或负的错误数。这些函数在内部映射到底层回调函数之上,这些回调函数由提供我们使用的GPIO的GPIO控制器的驱动程序导出。
在处理GPIO控制器驱动时,我们将看到GPIO控制器,通过它的结构gpio_chip结构,必须导出一组通用的回调函数来使用它的GPIO。
一些 GPIO 控制器提供了改变GPIO 防抖(debounc-interval) 的可能性(这只在 GPIO 被配置为输入时有用)。该特性与平台相关。你可以使用 int gpio_set_debounce() 来实现:
static int gpio_set_debounce(unsigned gpio, unsigned debounce)
在这里,debounce的值是ms。
前面所有的函数都应该在可能休眠的上下文中调用。从驱动程序的探测函数(probe)中声明和配置gpio是一个很好的实践。
访问GPIO -获取/设置值
在访问 GPIO 时要注意。在原子上下文中,特别是在中断处理程序中,您必须确保 GPIO 控制器回调函数不会休眠。一个设计良好的控制器驱动程序应该能够通知其他驱动程序(实际上是客户端)对其方法的调用是否会休眠。这可以通过 gpio_cansleep() 函数来检查。
所有用于访问 GPIO 的函数都没有返回错误代码。这就是为什么在 GPIO 分配和配置过程中要注意并检查返回值。
在原子上下文中
有一些 GPIO 控制器可以通过简单的内存读写操作来访问和管理。它们通常嵌入在 SoC 中,不需要休眠。对于这些控制器, gpio_cansleep() 总是返回false。对于这样的 GPIO,你可以使用众所周知的 gpio_get_value() 或 gpio_set_value() 从IRQ处理程序中获取/设置它们的值,这取决于 GPIO 被配置为输入还是输出:
1 static int gpio_get_value(unsigned gpio) 2 void gpio_set_value(unsigned int gpio, int value);
当 GPIO 被配置为 input (使用 gpio_direction_input() )时,应该使用 gpio_get_value() ,并返回 GPIO 的实际值(state)。另一方面, gpio_set_value() 将影响 GPIO 的值,GPIO 应该使用 gpio_direction_output() 将其配置为输出。对于这两个函数,value 都可以被认为是布尔值,其中零表示低,非零值表示高。
在非原子上下文中(可能会休眠)
另一方面,有 GPIO 控制器连接在总线上,如SPI和I2C。因为访问这些总线的函数可能会导致睡眠, gpio_cansleep() 函数应该总是返回true(这取决于GPIO控制器返回true)。在这种情况下,您不应该从IRQ中断处理内部访问这些gpio,至少不能在其上半部分访问(硬IRQ)。此外,你必须使用的访问器作为你的通用访问应该以_cansleep为后缀:
static int gpio_get_value_cansleep(unsigned gpio); void gpio_set_value_cansleep(unsigned gpio, int value);
它们的行为与不带 _cansleep() 后缀的访问器完全一样,唯一的区别是它们防止内核在 gpio 被访问时打印警告。
映射到IRQ的gpio
输入 gpio 经常被用作 IRQ 信号。这种 irq 可以是边缘触发或水平触发。配置取决于您的需要。GPIO 控制器负责提供 GPIO 和它的 IRQ 之间的映射。你可以使用 gpio_to_irq() 将给定的 GPIO 值映射到它的 IRQ 值:
int gpio_to_irq(unsigned gpio);
返回值是 IRQ 编号,你可以调用 request_irq() (或者线程版本 request_threaded_irq() )来为这个 IRQ 注册一个处理程序:
1 static irqreturn_t my_interrupt_handler(int irq, void *dev_id) 2 { 3 [...] 4 return IRQ_HANDLED; 5 } 6 [...] 7 int gpio_int = of_get_gpio(np, 0); 8 int irq_num = gpio_to_irq(gpio_int); 9 int error = devm_request_threaded_irq(&client->dev, irq_num, 10 NULL, my_interrupt_handler, 11 IRQF_TRIGGER_RISING | IRQF_ONESHOT, 12 input_dev->name, my_data_struct); 13 if (error) { 14 dev_err(&client->dev, "irq %d requested failed, %d ", 15 client->irq, error); 16 return error; 17 }
汇总
下面的代码是一个总结,将讨论的所有关于基于整数的接口的概念付诸实践。这个驱动程序管理四个gpio:两个按钮(btn1和btn2)和两个led(绿色和红色)。btn1被映射到IRQ,当它的状态变为低,btn2的状态应用于led。例如,如果btn1的状态是低的,而btn2的状态是高的,绿色和红色led将被驱动到高:
1 #include <linux/init.h> 2 #include <linux/module.h> 3 #include <linux/kernel.h> 4 #include <linux/gpio.h> /* For Legacy integer based GPIO */ 5 #include <linux/interrupt.h> /* For IRQ */ 6 static unsigned int GPIO_LED_RED = 49; 7 static unsigned int GPIO_BTN1 = 115; 8 static unsigned int GPIO_BTN2 = 116; 9 static unsigned int GPIO_LED_GREEN = 120; 10 static int irq; 11 12 static irqreturn_t btn1_pushed_irq_handler(int irq, void *dev_id) 13 { 14 int state; 15 16 /* read BTN2 value and change the led state */ 17 state = gpio_get_value(GPIO_BTN2); 18 gpio_set_value(GPIO_LED_RED, state); 19 gpio_set_value(GPIO_LED_GREEN, state); 20 pr_info("GPIO_BTN1 interrupt: Interrupt! GPIO_BTN2 state is %d) ", state); 21 22 return IRQ_HANDLED; 23 } 24 25 static int __init helloworld_init(void) 26 { 27 int retval; 28 /* 29 * One could have checked whether the GPIO is valid on the controller 30 or not, 31 * using gpio_is_valid() function. 32 * Ex: 33 * if (!gpio_is_valid(GPIO_LED_RED)) { 34 * pr_infor("Invalid Red LED "); 35 * return -ENODEV; 36 * } 37 */ 38 gpio_request(GPIO_LED_GREEN, "green-led"); 39 gpio_request(GPIO_LED_RED, "red-led"); 40 gpio_request(GPIO_BTN1, "button-1"); 41 gpio_request(GPIO_BTN2, "button-2"); 42 /* 43 * Configure Button GPIOs as input 44 * 45 * After this, one can call gpio_set_debounce() 46 * only if the controller has the feature 47 * 48 * For example, to debounce a button with a delay of 200ms 49 * gpio_set_debounce(GPIO_BTN1, 200); 50 */ 51 gpio_direction_input(GPIO_BTN1); 52 gpio_direction_input(GPIO_BTN2); 53 /* 54 * Set LED GPIOs as output, with their initial values set to 0 55 */ 56 gpio_direction_output(GPIO_LED_RED, 0); 57 gpio_direction_output(GPIO_LED_GREEN, 0); 58 irq = gpio_to_irq(GPIO_BTN1); 59 60 retval = request_threaded_irq(irq, NULL, 61 btn1_pushed_irq_handler, 62 IRQF_TRIGGER_LOW | IRQF_ONESHOT, 63 "device-name", NULL); 64 pr_info("Hello world! "); 65 return 0; 66 } 67 68 static void __exit hellowolrd_exit(void) 69 { 70 free_irq(irq, NULL); 71 gpio_free(GPIO_LED_RED); 72 gpio_free(GPIO_LED_GREEN); 73 gpio_free(GPIO_BTN1); 74 gpio_free(GPIO_BTN2); 75 pr_info("End of the world "); 76 } 77 78 module_init(hellowolrd_init); 79 module_exit(hellowolrd_exit); 80 MODULE_AUTHOR("aw aw@email.com"); 81 MODULE_LICENSE("GPL");
基于描述符的GPIO接口——新的和推荐的方式
使用新的基于描述符的GPIO接口,GPIO的特征是一个一致的结构gpio_desc结构:
1 struct gpio_desc { 2 struct gpio_chip *chip; 3 unsigned long flags; 4 const char *label; 5 };
你应该使用以下头文件来使用新的接口:
#include <linux/gpio/consumer.h>
对于基于描述符的接口,在分配和获得GPIOs所有权之前,这些GPIOs必须已经映射到某个地方。通过映射,我的意思是他们应该被分配给你的设备,然而,与传统的基于整数的接口,你只需要在任何地方获取一个数字,并请求它作为GPIO。实际上,在内核中有三种映射:
- Platform data mapping: 映射在board文件中完成。
- Device tree: 映射是用DT风格完成的,这就是我们将在本文中讨论的映射。
- Advanced Configuration and Power Interface mapping (ACPI): 映射是以ACPI风格完成的。通常用于基于x86的系统。
GPIO描述符映射-设备树
GPIO描述符映射在消费设备的节点中定义。包含GPIO描述符映射的属性必须命名为<name>-gpios 或 <name>-gpio,其中<name>足够有意义来描述这些GPIO将要使用的功能。
你应该总是在属性名后面加上 -gpio 或 -gpios,因为每个基于描述符的接口函数都依赖于 gpio_suffix[] 变量,该变量在 drivers/gpio/gpiolib.h 中定义,如下所示:
/* gpio suffixes used for ACPI and device tree lookup */ static const char * const gpio_suffixes[] = { "gpios", "gpio" };
让我们来看看如何在DT的设备中查找GPIO描述符映射:
static struct gpio_desc *of_find_gpio(struct device *dev, const char *con_id, unsigned int idx, enum gpio_lookup_flags *flags) { char prop_name[32]; /* 32 is max size of property name */ enum of_gpio_flags of_flags; struct gpio_desc *desc; unsigned int i; for (i = 0; i < ARRAY_SIZE(gpio_suffixes); i++) { if (con_id) snprintf(prop_name, sizeof(prop_name), "%s-%s", con_id, gpio_suffixes[i]); else snprintf(prop_name, sizeof(prop_name), "%s", gpio_suffixes[i]); desc = of_get_named_gpiod_flags(dev->of_node, prop_name, idx, &of_flags); if (!IS_ERR(desc) || (PTR_ERR(desc) == -EPROBE_DEFER)) break; } if (IS_ERR(desc)) return desc; if (of_flags & OF_GPIO_ACTIVE_LOW) *flags |= GPIO_ACTIVE_LOW; return desc; }
现在,让我们考虑以下节点,它是从 Documentation/gpio/board.txt 节选的:
foo_device { compatible = "acme,foo"; [...] led-gpios = <&gpio 15 GPIO_ACTIVE_HIGH>, /* red */ <&gpio 16 GPIO_ACTIVE_HIGH>, /* green */ <&gpio 17 GPIO_ACTIVE_HIGH>; /* blue */ power-gpios = <&gpio 1 GPIO_ACTIVE_LOW>; reset-gpios = <&gpio 1 GPIO_ACTIVE_LOW>; };
映射应该是这样的,具有一个有意义的名称。
分配和使用GPIO
你可以使用gpiog_get()或者gpiod_get_index()来分配一个GPIO描述符:
struct gpio_desc *gpiod_get_index(struct device *dev, const char *con_id, unsigned int idx, enum gpiod_flags flags)
struct gpio_desc *gpiod_get(struct device *dev, const char *con_id, enum gpiod_flags flags)
在出现错误时,如果给定函数没有指定GPIO,或者出现可以使用IS_ERR()宏处理的错误,这些函数将返回-ENOENT。第一个函数返回与给定索引处的GPIO对应的GPIO描述符结构,而第二个函数返回索引为0处的GPIO(对于单GPIO映射很有用)。
dev是GPIO描述符所属的设备。这是你的设备。con_id 对应于DT中属性名的<name>前缀。idx是需要一个描述符的GPIO的索引(从0开始)。flags是一个可选参数,它决定了GPIO初始化标志,用来配置方向和/或输出值。它是enum gpiod_flags的一个实例,定义在include/linux/gpio/consumer.h中:
enum gpiod_flags { GPIOD_ASIS = 0, GPIOD_IN = GPIOD_FLAGS_BIT_DIR_SET, GPIOD_OUT_LOW = GPIOD_FLAGS_BIT_DIR_SET | GPIOD_FLAGS_BIT_DIR_OUT, GPIOD_OUT_HIGH = GPIOD_FLAGS_BIT_DIR_SET | GPIOD_FLAGS_BIT_DIR_OUT | GPIOD_FLAGS_BIT_DIR_VAL, };
现在,让我们为之前DT中定义的映射分配GPIO描述符:
struct gpio_desc *red, *green, *blue, *power;
red = gpiod_get_index(dev, "led", 0, GPIOD_OUT_HIGH); green = gpiod_get_index(dev, "led", 1, GPIOD_OUT_HIGH); blue = gpiod_get_index(dev, "led", 2, GPIOD_OUT_HIGH);
power = gpiod_get(dev, "power", GPIOD_OUT_LOW);
LED GPIO将为active-high,而power GPIO将为active-low(即gpiod_is_active_low(power)将为true)。分配的反向操作是通过gpiod_put()函数完成的:
gpiod_put(struct gpio_desc *desc);
让我们看看如何释放红色和蓝色GPIO led:
gpiod_put(blue);
gpiod_put(red);
在进一步讨论之前,请记住,除了gpiod_get()/gpiod_get_index() 和 gpio_put() 函数(它们与 gpio_request() 和 gpio_free() 完全不同)之外,您还可以通过将gpio_前缀更改为gpiod_来执行从基于整数的接口到基于描述符的API转换。
要改变方向,应该使用 gpiod_direction_input() 和 gpiod_direction_output() 函数:
int gpiod_direction_input(struct gpio_desc *desc); int gpiod_direction_output(struct gpio_desc *desc, int value);
value是当方向设置为输出时应用到GPIO的状态。如果GPIO控制器有这个特性,你可以使用GPIO的描述符来设置它的超时:
int gpiod_set_debounce(struct gpio_desc *desc, unsigned debounce);
为了访问给定描述符的GPIO,必须像访问基于整数的接口一样注意它。换句话说,你应该注意你是在一个原子上下文(不能睡眠)还是非原子上下文中,然后使用适当的函数:
int gpiod_cansleep(const struct gpio_desc *desc); /* Value get/set from sleeping context */ int gpiod_get_value_cansleep(const struct gpio_desc *desc); void gpiod_set_value_cansleep(struct gpio_desc *desc, int value); /* Value get/set from non-sleeping context */ int gpiod_get_value(const struct gpio_desc *desc); void gpiod_set_value(struct gpio_desc *desc, int value);
对于映射到IRQ的GPIO描述符,您可以使用gpiod_to_irq()来获取对应于给定GPIO描述符的IRQ编号,可以与request_irq()函数一起使用:
int gpiod_to_irq(const struct gpio_desc *desc);
在代码中的任何给定点,都可以使用 desc_to_gpio() 或 gpio_to_desc() 函数,从基于描述符的接口切换到基于整数的传统接口,反之亦然:
/* Convert between the old gpio_ and new gpiod_ interfaces */ struct gpio_desc *gpio_to_desc(unsigned gpio);
int desc_to_gpio(const struct gpio_desc *desc);
汇总
这里的驱动程序总结了基于描述符的接口中引入的概念。和基于整数的gpio接口原理是一样的:
#include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/platform_device.h> /* For platform devices */ #include <linux/gpio/consumer.h> /* For GPIO Descriptor */ #include <linux/interrupt.h> /* For IRQ */ #include <linux/of.h> /* For DT*/ /* * Let us consider the below mapping in device tree: * * foo_device { * compatible = "packt,gpio-descriptor-sample"; * led-gpios = <&gpio2 15 GPIO_ACTIVE_HIGH>, // red * <&gpio2 16 GPIO_ACTIVE_HIGH>, // green * * btn1-gpios = <&gpio2 1 GPIO_ACTIVE_LOW>; * btn2-gpios = <&gpio2 31 GPIO_ACTIVE_LOW>; * }; */ static struct gpio_desc *red, *green, *btn1, *btn2; static int irq;
static irqreturn_t btn1_pushed_irq_handler(int irq, void *dev_id) { int state; /* read the button value and change the led state */ state = gpiod_get_value(btn2); gpiod_set_value(red, state); gpiod_set_value(green, state); pr_info("btn1 interrupt: Interrupt! btn2 state is %d) ", state);
return IRQ_HANDLED; }
static const struct of_device_id gpiod_dt_ids[] = { { .compatible = "packt,gpio-descriptor-sample", }, { /* sentinel */ } };
static int my_pdrv_probe (struct platform_device *pdev) { int retval; struct device *dev = &pdev->dev;
/* * We use gpiod_get/gpiod_get_index() along with the flags * in order to configure the GPIO direction and an initial * value in a single function call. * * One could have used: * red = gpiod_get_index(dev, "led", 0); * gpiod_direction_output(red, 0); */
red = gpiod_get_index(dev, "led", 0, GPIOD_OUT_LOW); green = gpiod_get_index(dev, "led", 1, GPIOD_OUT_LOW); /* * Configure GPIO Buttons as input * * After this, one can call gpiod_set_debounce() * only if the controller has the feature * For example, to debounce a button with a delay of 200ms * gpiod_set_debounce(btn1, 200); */ btn1 = gpiod_get(dev, "led", 0, GPIOD_IN); btn2 = gpiod_get(dev, "led", 1, GPIOD_IN); irq = gpiod_to_irq(btn1); retval = request_threaded_irq(irq, NULL, btn1_pushed_irq_handler, IRQF_TRIGGER_LOW | IRQF_ONESHOT, "gpio-descriptor-sample", NULL); pr_info("Hello! device probed! ");
return 0; }
static void my_pdrv_remove(struct platform_device *pdev) { free_irq(irq, NULL); gpiod_put(red); gpiod_put(green); gpiod_put(btn1); gpiod_put(btn2); pr_info("good bye reader! "); } static struct platform_driver mypdrv = {
.probe = my_pdrv_probe, .remove = my_pdrv_remove, .driver = { .name = "gpio_descriptor_sample", .of_match_table = of_match_ptr(gpiod_dt_ids), .owner = THIS_MODULE, }, };
module_platform_driver(mypdrv); MODULE_AUTHOR("A W <aw@email.com>"); MODULE_LICENSE("GPL");
GPIO接口和设备树
无论您需要为哪个接口使用GPIO,如何指定GPIO取决于提供它们的控制器,特别是关于它的 #gpio-cells 属性,它决定了GPIO说明符使用的单元数。一个GPIO说明符至少包含控制器句柄和一个或多个参数,其中包含提供GPIO的控制器的 #gpio-cells 属性上的参数数量。第一个单元通常是控制器上的GPIO偏移数,第二个代表GPIO标志。
GPIO属性应该命名为[<name>-]gpios], <name>是设备的GPIO的目的。请记住,这个规则对于基于描述符的接口是必须的,并且变成了<name>-gpios(注意没有方括号,意味着<name>前缀是必须的):
gpio1: gpio1 { gpio-controller; #gpio-cells = <2>; }; gpio2: gpio2 { gpio-controller; #gpio-cells = <1>; }; [...]
cs-gpios = <&gpio1 17 0>, <&gpio2 2>; <0>, /* holes are permitted, means no GPIO 2 */ <&gpio1 17 0>;
reset-gpios = <&gpio1 30 0>; cd-gpios = <&gpio2 10>;
在上述示例中,CS GPIOs同时包含controller-1和controller-2 GPIOs。如果你不需要在列表中给定的索引处指定GPIO,你可以使用<0>。reset GPIO有两个cell(控制器phandle后的两个参数),而CD GPIO只有一个cell。您可以看到我给GPIO说明符的名称是多么有意义。
传统的基于整数的接口和设备树
该接口依赖于以下头文件:
#include <linux/of_gpio.h>
当你需要在驱动中使用基于整数的接口来支持DT时,你应该记住两个函数; 它们是 of_get_named_gpio() 和 of_get_named_gpio_count():
int of_get_named_gpio(struct device_node *np, const char *propname, int index) int of_get_named_gpio_count(struct device_node *np, const char* propname)
int n_gpios = of_get_named_gpio_count(dev.of_node, "cs-gpios"); /* return 4 */ int second_gpio = of_get_named_gpio(dev.of_node, "cs-gpio", 1); int rst_gpio = of_get_named_gpio("reset-gpio", 0); gpio_request(second_gpio, "my-gpio);
仍然有驱动程序支持旧的指定符,其中GPIO属性被命名[<name>-gpio] 或 gpios。在这种情况下,你应该使用未命名的API版本,通过 of_get_gpio() 和 of_gpio_count():
int of_gpio_count(struct device_node *np) int of_get_gpio(struct device_node *np, int index)
- DT节点如下所示:
my_node@addr { compatible = "[...]"; gpios = <&gpio1 2 0>, /* INT */ <&gpio1 5 0>; /* RST */ [...] };
- 驱动程序中的代码是这样的:
struct device_node *np = dev->of_node;
if (!np) return ERR_PTR(-ENOENT);
int n_gpios = of_gpio_count(); /* Will return 2 */ int gpio_int = of_get_gpio(np, 0); if (!gpio_is_valid(gpio_int)) { dev_err(dev, "failed to get interrupt gpio "); return ERR_PTR(-EINVAL); }
gpio_rst = of_get_gpio(np, 1); if (!gpio_is_valid(pdata->gpio_rst)) { dev_err(dev, "failed to get reset gpio "); return ERR_PTR(-EINVAL); }
你可以通过重写第一个驱动(一个基于整数的接口)来总结这一点,为了符合平台驱动结构,并使用DT API:
#include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/platform_device.h> /* For platform devices */ #include <linux/interrupt.h> /* For IRQ */ #include <linux/gpio.h> /* For Legacy integer based GPIO */ #include <linux/of_gpio.h> /* For of_gpio* functions */ #include <linux/of.h> /* For DT*/
/* * Let us consider the following node * * foo_device { * compatible = "packt,gpio-legacy-sample"; * led-gpios = <&gpio2 15 GPIO_ACTIVE_HIGH>, // red * <&gpio2 16 GPIO_ACTIVE_HIGH>, // green * * btn1-gpios = <&gpio2 1 GPIO_ACTIVE_LOW>; * btn2-gpios = <&gpio2 1 GPIO_ACTIVE_LOW>; * }; */ static unsigned int gpio_red, gpio_green, gpio_btn1, gpio_btn2; static int irq; static irqreturn_t btn1_pushed_irq_handler(int irq, void *dev_id) { /* The content of this function remains unchanged */ [...] }
static const struct of_device_id gpio_dt_ids[] = { { .compatible = "packt,gpio-legacy-sample", }, { /* sentinel */ } };
static int my_pdrv_probe (struct platform_device *pdev) { int retval; struct device_node *np = &pdev->dev.of_node;
if (!np) return ERR_PTR(-ENOENT);
gpio_red = of_get_named_gpio(np, "led", 0); gpio_green = of_get_named_gpio(np, "led", 1); gpio_btn1 = of_get_named_gpio(np, "btn1", 0); gpio_btn2 = of_get_named_gpio(np, "btn2", 0);
gpio_request(gpio_green, "green-led"); gpio_request(gpio_red, "red-led"); gpio_request(gpio_btn1, "button-1"); gpio_request(gpio_btn2, "button-2");
/* Code to configure GPIO and request IRQ remains unchanged */ [...] return 0; }
static void my_pdrv_remove(struct platform_device *pdev) { /* The content of this function remains unchanged */ [...] } static struct platform_driver mypdrv = { .probe = my_pdrv_probe, .remove = my_pdrv_remove, .driver = { .name = "gpio_legacy_sample", .of_match_table = of_match_ptr(gpio_dt_ids), .owner = THIS_MODULE, }, };
module_platform_driver(mypdrv); MODULE_AUTHOR("A W <aw@email.com>"); MODULE_LICENSE("GPL");
GPIO映射到设备树中的IRQ
您可以很容易地将GPIO映射到设备树中的IRQ。有两个属性用于指定中断:
- interrupt-parent: 这是GPIO的GPIO控制器
- interrupts: 这是中断说明符列表
这些应用于遗留的和基于描述符的接口。IRQ指示符取决于 #interrupt-cel l的GPIO控制器属性。#interrupt-cell 确定指定中断时使用的单元数。通常,第一个单元代表映射到IRQ的GPIO号,第二个单元代表电平/边缘触发中断机制。在任何情况下,中断指示符总是依赖于它的父(拥有中断控制器集的父),所以请参考内核源代码中的绑定文档:
gpio4: gpio4 { gpio-controller; #gpio-cells = <2>; interrupt-controller; #interrupt-cells = <2>; };
my_label: node@0 { reg = <0>; spi-max-frequency = <1000000>; interrupt-parent = <&gpio4>; interrupts = <29 IRQ_TYPE_LEVEL_LOW>; };
有两种方法可以获得相应的IRQ:
- 设备位于一个已知的总线上(I2C或SPI): IRQ映射将为你完成,并且可以通过 struct i2c_client 或 struct spi_device 结构提供给 probe() 函数(通过i2c_client.irq 或 spi_device.irq)
- 设备位于虚拟平台总线上: probe() 函数会给出一个 struct platform_device,在这个结构上你可以调用 platform_get_irq():
int platform_get_irq(struct platform_device *dev, unsigned int num);
GPIO和sysfs
sysfs GPIO接口允许人们通过设置或文件来管理和控制GPIO。它位于 /sys/class/gpio 目录下。设备模型在这里被大量使用,有三种类型的入口:
- /sys/class/gpio/: 这是一切开始的地方。这个目录包含两个特殊文件,export和unexport:
- export: 这允许我们要求内核导出给定的控制GPIO将其编号写入用户空间。一个例子是echo 21 > export,它将为GPIO #21创建一个GPIO21节点,如果该GPIO没有被内核代码请求过的话。
- unexport: 这与导出到用户空间的效果相反。示例:echo 21 > unexport将删除使用导出文件导出的任何GPIO21节点。
- /sys/class/gpio/gpioN/:
- direction 文件用于获取/设置GPIO方向。允许的值是in或out字符串。这个值通常可以写入。写为out默认将值初始化为low。为了确保无故障的运行,低值和高值可能会被写入,以配置GPIO作为具有该初始值的输出。如果内核代码导出了这个GPIO,禁用方向(参见gpiod_export()或gpio_export()函数),这个属性将不存在。
- value 属性让我们可以根据方向、输入或输出来获取/设置GPIO行的状态。如果GPIO被配置为输出,任何写入的非零值都将被视为高状态。如果配置为输出,写0将输出设置为低,而写1将输出设置为高。如果该引脚可以配置为一个中断生成行,并且如果它已经配置为生成,您可以调用poll(2)系统对该文件的调用,并且poll(2)将在中断被触发时返回。使用poll(2)将需要设置事件POLLPRI和POLLERR。
- edge 决定让poll()或select()函数返回的信号边缘。允许的值是none, rising, falling, or both。这个文件是可读/可写的,并且只有当该引脚可以配置为一个产生中断的输入引脚时才存在。
- active_low读取为0 (false)或1 (true)。写入任何非零值都将反转读取和写入的value属性。现有的和后续的poll(2)支持配置通过边缘属性的上升和下降边缘将遵循此设置。内核中设置这个值的相关函数是 gpio_sysf_set_active_low()。
从内核代码导出GPIO
除了使用/sys/class/gpio/export文件导出gpio到用户空间外,您可以从内核代码中使用诸如gpio_export(用于遗留接口)或gpioD_export(新接口)这样的函数,以便显式管理使用gpio_request()或gpiod_get()已经请求的gpio的导出:
int gpio_export(unsigned gpio, bool direction_may_change); int gpiod_export(struct gpio_desc *desc, bool direction_may_change);
direction_may_change 参数决定是否可以将信号方向从输入改为输出,反之亦然。内核的反向操作是gpio_unexport()或gpiod_unexport():
void gpio_unexport(unsigned gpio); /* Integer-based interface */ void gpiod_unexport(struct gpio_desc *desc) /* Descriptor-based */
导出后,您可以使用gpio_export_link()(或gpiod_export_link()用于基于描述符的接口)从sysfs中的其他地方创建符号链接,它将指向GPIO sysfs节点。在sysfs中,驱动程序可以用一个描述性的名称在自己的设备下提供接口:
int gpio_export_link(struct device *dev, const char *name, unsigned gpio) int gpiod_export_link(struct device *dev, const char *name, struct gpio_desc *desc)
你可以在probe()函数中使用它来实现基于描述符的接口,如下所示:
static struct gpio_desc *red, *green, *btn1, *btn2;
static int my_pdrv_probe (struct platform_device *pdev) { [...] red = gpiod_get_index(dev, "led", 0, GPIOD_OUT_LOW); green = gpiod_get_index(dev, "led", 1, GPIOD_OUT_LOW); gpiod_export(&pdev->dev, "Green_LED", green); gpiod_export(&pdev->dev, "Red_LED", red);
[...] return 0; }
对于基于整数的接口,代码如下所示:
static int my_pdrv_probe (struct platform_device *pdev) { [...]
gpio_red = of_get_named_gpio(np, "led", 0); gpio_green = of_get_named_gpio(np, "led", 1); [...]
int gpio_export_link(&pdev->dev, "Green_LED", gpio_green) int gpio_export_link(&pdev->dev, "Red_LED", gpio_red) return 0; }
总结
在内核中处理GPIO是一项简单的任务,如本文所示。我们讨论了遗留的和新的接口,从而可以选择适合您需要的接口,以便编写增强的GPIO驱动程序。您现在应该能够处理IRQs映射到gpio。下一篇文章将讨论提供和公开的芯片GPIO lines,称为GPIO控制器。