Skip to content

Latest commit

 

History

History
1000 lines (766 loc) · 41 KB

File metadata and controls

1000 lines (766 loc) · 41 KB

十四、引脚控制和 GPIO 子系统

大多数嵌入式 Linux 驱动和内核工程师使用 GPIOs 编写或使用管脚复用进行游戏。我所说的引脚是指组件的引出线。SoC 确实多路复用引脚,这意味着一个引脚可能有几个功能,例如,arch/arm/boot/dts/imx6dl-pinfunc.h中的MX6QDL_PAD_SD3_DAT1可以是 SD3 数据线 1、UART1 的 cts/rts、Flexcan2 的 Rx 或普通 GPIO。

选择引脚工作模式的机制称为引脚复用。负责的系统称为引脚控制器。在这一章的第二部分,我们将讨论通用输入输出 ( GPIO ),这是一种引脚可以操作的特殊功能(模式)。

在本章中,我们将:

  • 浏览引脚控制子系统,看看如何在 DT 中声明它们的节点
  • 探索传统的基于整数的 GPIO 接口,以及新的基于描述符的接口 API
  • 处理映射到 IRQ 的 GPIO
  • 处理专用于 GPIOs 的 sysfs 接口

引脚控制子系统

引脚控制 ( 引脚控制)子系统允许管理引脚复用。在 DT 中,需要以某种方式多路复用引脚的器件必须声明所需的引脚控制配置。

pinctrl 子系统提供:

  • 引脚多路复用,允许为不同目的重用同一引脚,例如一个引脚是通用异步收发器发送引脚、通用输入输出线或高速接口数据线。多路复用会影响引脚组或单个引脚。
  • 引脚配置,应用引脚的电子特性,如上拉、下拉、驱动强度、去抖周期等。

这本书的目的仅限于使用引脚控制器驱动导出的函数,并不涉及如何编写引脚控制器驱动。

Pinctrl 和设备树

pinctrl 只不过是一种收集引脚(不仅仅是 GPIO)并将它们传递给驱动的方法。引脚控制器驱动负责解析 DT 中的引脚描述,并在芯片中应用它们的配置。驱动通常需要一组两个嵌套节点来描述一组引脚配置。第一个节点描述组的功能(组将用于什么目的),第二个节点保存引脚配置。

如何在 DT 中分配引脚组在很大程度上取决于平台,因此也取决于引脚控制器驱动。每个引脚控制状态都有一个从 0 开始的连续整数标识。可以使用名称属性,该属性将被映射到标识的顶部,以便相同的名称总是指向相同的标识。

每个客户端设备自己的绑定决定了必须在其 DT 节点中定义的状态集,以及是否定义必须提供的状态标识集,或者是否定义必须提供的状态名称集。在任何情况下,引脚配置节点都可以通过两种属性分配给器件:

  • pinctrl-<ID>:这允许给出设备特定状态所需的 pinctrl 配置列表。这是一个显形列表,每个显形都指向一个引脚配置节点。这些被引用的引脚配置节点必须是它们所配置的引脚控制器的子节点。该列表中可能存在多个条目,因此可以配置多个引脚控制器,或者可以从单个引脚控制器的多个节点构建一个状态,每个节点都是整个配置的组成部分。
  • pinctrl-name:这允许给列表中的每个州命名。列表项 0 定义整数状态标识 0 的名称,列表项 1 定义状态标识 1 的名称,依此类推。状态标识 0 通常被命名为默认为。标准化状态列表可在include/linux/pinctrl/pinctrl-state.h中找到。
  • 以下是 DT 的摘录,显示了一些设备节点及其引脚控制节点:
usdhc@0219c000 { /* uSDHC4 */ 
   non-removable; 
   vmmc-supply = <&reg_3p3v>; 
   status = "okay"; 
   pinctrl-names = "default"; 
   pinctrl-0 = <&pinctrl_usdhc4_1>; 
}; 

gpio-keys { 
    compatible = "gpio-keys"; 
    pinctrl-names = "default"; 
    pinctrl-0 = <&pinctrl_io_foo &pinctrl_io_bar>; 
}; 

iomuxc@020e0000 { 
    compatible = "fsl,imx6q-iomuxc"; 
    reg = <0x020e0000 0x4000>; 

    /* shared pinctrl settings */ 
    usdhc4 { /* first node describing the function */ 
        pinctrl_usdhc4_1: usdhc4grp-1 { /* second node */ 
            fsl,pins = < 
                MX6QDL_PAD_SD4_CMD__SD4_CMD    0x17059 
                MX6QDL_PAD_SD4_CLK__SD4_CLK    0x10059 
                MX6QDL_PAD_SD4_DAT0__SD4_DATA0 0x17059 
                MX6QDL_PAD_SD4_DAT1__SD4_DATA1 0x17059 
                MX6QDL_PAD_SD4_DAT2__SD4_DATA2 0x17059 
                MX6QDL_PAD_SD4_DAT3__SD4_DATA3 0x17059 
                MX6QDL_PAD_SD4_DAT4__SD4_DATA4 0x17059 
                MX6QDL_PAD_SD4_DAT5__SD4_DATA5 0x17059 
                MX6QDL_PAD_SD4_DAT6__SD4_DATA6 0x17059 
                MX6QDL_PAD_SD4_DAT7__SD4_DATA7 0x17059 
            >; 
        }; 
    }; 
    [...] 
    uart3 { 
        pinctrl_uart3_1: uart3grp-1 { 
            fsl,pins = < 
                MX6QDL_PAD_EIM_D24__UART3_TX_DATA 0x1b0b1 
                MX6QDL_PAD_EIM_D25__UART3_RX_DATA 0x1b0b1 
            >; 
        }; 
    }; 
    // GPIOs (Inputs) 
   gpios { 
        pinctrl_io_foo: pinctrl_io_foo { 
            fsl,pins = < 
                MX6QDL_PAD_DISP0_DAT15__GPIO5_IO09  0x1f059 
                MX6QDL_PAD_DISP0_DAT13__GPIO5_IO07  0x1f059 
            >; 
        }; 
        pinctrl_io_bar: pinctrl_io_bar { 
            fsl,pins = < 
                MX6QDL_PAD_DISP0_DAT11__GPIO5_IO05  0x1f059 
                MX6QDL_PAD_DISP0_DAT9__GPIO4_IO30   0x1f059 
                MX6QDL_PAD_DISP0_DAT7__GPIO4_IO28   0x1f059 
                MX6QDL_PAD_DISP0_DAT5__GPIO4_IO26   0x1f059 
            >; 
        }; 
    }; 
}; 

在前面的例子中,引脚配置以<PIN_FUNCTION> <PIN_SETTING>的形式给出。例如:

MX6QDL_PAD_DISP0_DAT15__GPIO5_IO09  0x80000000 

MX6QDL_PAD_DISP0_DAT15__GPIO5_IO09代表引脚功能,本例为 GPIO,0x80000000代表引脚设置。

对于这一行,

MX6QDL_PAD_EIM_D25__UART3_RX_DATA 0x1b0b1 

MX6QDL_PAD_EIM_D25__UART3_RX_DATA代表引脚功能,是 UART3 的 RX 线,0x1b0b1代表的是设置。

引脚函数是一个宏,其值仅对引脚控制器驱动有意义。这些通常在位于arch/<arch>/boot/dts/的头文件中定义。例如,如果使用一个 UDOO 四核,它有一个 i.MX6 四核(ARM),那么引脚函数头应该是arch/arm/boot/dts/imx6q-pinfunc.h。以下是对应 GPIO5 控制器第五行的宏:

#define MX6QDL_PAD_DISP0_DAT11__GPIO5_IO05  0x19c 0x4b0 0x000 0x5 0x0 

<PIN_SETTING>可以用来设置引体向上、下拉、守卫者、驱动力等等。如何指定取决于引脚控制器绑定,其值的含义取决于 SoC 数据手册,通常在 IOMUX 部分。在 i.MX6 IOMUXC 上,只有低于 17 位用于此目的。

这些前面的节点是从相应的驱动特定节点调用的。此外,这些引脚在相应的驱动初始化期间配置。在选择引脚组状态之前,必须首先使用pinctrl_get()功能获得引脚控制,调用pinctrl_lookup_state()以检查请求的状态是否存在,最后使用pinctrl_select_state()应用该状态。

下面是一个示例,展示了如何获取 pincontrol 并应用其默认配置:

struct pinctrl *p; 
struct pinctrl_state *s; 
int ret; 

p = pinctrl_get(dev); 
if (IS_ERR(p)) 
    return p; 

s = pinctrl_lookup_state(p, name); 
if (IS_ERR(s)) { 
    devm_pinctrl_put(p); 
    return ERR_PTR(PTR_ERR(s)); 
} 

ret = pinctrl_select_state(p, s); 
if (ret < 0) { 
    devm_pinctrl_put(p); 
    return ERR_PTR(ret); 
} 

人们通常在驱动初始化期间执行这些步骤。该代码的合适位置可以在probe()功能中。

pinctrl_select_state() internally calls pinmux_enable_setting(), which in turn calls the pin_request() on each pin in the pin control node.

可以通过pinctrl_put()功能释放引脚控制。可以使用资源管理版本的应用编程接口。也就是说,可以使用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()的包装:

static struct pinctrl * pinctrl_get_select_default( 
                                struct device *dev) 
{ 
   return pinctrl_get_select(dev, PINCTRL_STATE_DEFAULT); 
} 

让我们在特定于电路板的 dts 文件(am335x-evm.dts)中看到一个真实的例子:

dcan1: d_can@481d0000 { 
    status = "okay"; 
    pinctrl-names = "default"; 
    pinctrl-0 = <&d_can1_pins>; 
}; 

在相应的驱动中:

pinctrl = devm_pinctrl_get_select_default(&pdev->dev); 
if (IS_ERR(pinctrl)) 
    dev_warn(&pdev->dev,"pins are not configured from the driver\n"); 

The pin control core will automatically claim the default pinctrl state for us when the device is probed. If one defines an init state, the pinctrl core will automatically set pinctrl to this state before the probe() function, and then switch to the default state after probe() (unless the driver explicitly changed states already).

GPIO 子系统

从硬件的角度来看,GPIO 是一种功能,一种引脚可以工作的模式。从软件的角度来看,一条 GPIO 无非是一条数字线,可以作为输入或输出,只能有两个值:(1为高或0为低)。内核 GPIO 子系统提供了您能想象到的从驱动内部设置和处理 GPIO 线的所有功能:

  • 在从驱动内部使用 GPIO 之前,应该向内核声明它。这是获得 GPIO 所有权的一种方式,防止其他驱动访问同一个 GPIO。获得 GPIO 的所有权后,您可以:
    • 设定方向
    • 如果用作输出,切换其输出状态(驱动线高或低)
    • 如果用作输入,设置去抖间隔并读取状态。对于映射到 IRQ 的 GPIO 线,可以定义应该在哪个边沿/电平触发中断,并注册一个每当中断发生时都会运行的处理程序。

在内核中处理 GPIO 实际上有两种不同的方式,如下所示:

  • 基于整数的遗留和折旧接口,其中 GPIOs 由整数表示
  • 新的和推荐的基于描述符的接口,其中 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 配置为输出时,还有第二个参数:value,这是一旦输出方向有效,GPIO 应该处于的状态。这里,返回值也是零或负错误号。这些函数在内部映射到由提供我们使用的 GPIO 的 GPIO 控制器的驱动公开的低级回调函数之上。在接下来的第 15 章gpio 控制器驱动- gpio_chip 中,处理 GPIO 控制器驱动,我们会看到一个 GPIO 控制器,通过它的struct gpio_chip结构,必须公开一组通用的回调函数才能使用它的 GPIO。

一些 GPIO 控制器提供了改变 GPIO 去抖间隔的可能性(这仅在 GPIO 线路被配置为输入时有用)。该功能依赖于平台。可以使用int gpio_set_debounce()来实现:

static  int  gpio_set_debounce(unsigned gpio, unsigned debounce) 

其中debounce是去抖时间,单位为毫秒

All the preceding functions should be called in a context that may sleep. It is a good practice to claim and configure GPIOs from within the driver's probe function.

访问 GPIO–获取/设置值

访问 GPIO 的时候要注意。在原子上下文中,尤其是在中断处理程序中,必须确保 GPIO 控制器回调函数不会休眠。设计良好的控制器驱动应该能够通知其他驱动(实际上是客户端)对其方法的调用是否可以休眠。这可以通过gpio_cansleep()功能进行检查。

None of the functions used to access GPIO return an error code. That is why you should pay attention and check return values during GPIO allocation and configuration.

在原子环境中

有一些 GPIO 控制器可以通过简单的内存读/写操作来访问和管理。这些一般嵌入在 SoC 中,不需要休眠。gpio_cansleep()将始终返回那些控制器的false。对于此类 GPIO,您可以使用众所周知的gpio_get_value()gpio_set_value()从一个 IRQ 处理程序中获取/设置它们的值,具体取决于配置为输入或输出的 GPIO 线路:

static int  gpio_get_value(unsigned gpio) 
void gpio_set_value(unsigned int gpio, int value); 

当 GPIO 配置为输入(使用gpio_direction_input())时应使用gpio_get_value(),并返回 GPIO 的实际值(状态)。另一方面,gpio_set_value()将影响 GPIO 的值,GPIO 本应使用gpio_direction_output()配置为输出。对于这两个功能,value可以认为是Boolean,其中零表示低,非零值表示高。

在非原子环境中(可能休眠)

另一方面,在 SPI 和 I2C 等总线上连接有 GPIO 控制器。由于访问这些总线的功能可能导致休眠,gpio_cansleep()功能应该总是返回true(由 GPIO 控制器负责返回真)。在这种情况下,您不应该从所处理的 IRQ 中访问这些 GPIOs,至少不要在上面一半(硬 IRQ)中。此外,您必须用作通用访问的访问器应该以_cansleep为后缀。

static int gpio_get_value_cansleep(unsigned gpio); 
void gpio_set_value_cansleep(unsigned gpio, int value); 

它们的行为完全像不带_cansleep()名称后缀的访问器,唯一的区别是它们防止内核在访问 GPIOs 时打印警告。

映射到 IRQ 的 GPIOs

输入 GPIOs 通常可以用作 IRQ 信号。这种 IRQ 可以是边沿触发或电平触发的。配置取决于您的需求。GPIO 控制器负责提供 GPIO 与其 IRQ 之间的映射。可以使用goio_to_irq()将给定的 GPIO 号映射到其 IRQ 号:

int gpio_to_irq(unsigned gpio);

返回值是 IRQ 号,在其上可以调用request_irq()(或线程版本request_threaded_irq())来注册该 IRQ 的处理程序:

static irqreturn_t my_interrupt_handler(int irq, void *dev_id) 
{ 
    [...] 
    return IRQ_HANDLED; 
} 

[...] 
int gpio_int = of_get_gpio(np, 0); 
int irq_num = gpio_to_irq(gpio_int); 
int error = devm_request_threaded_irq(&client->dev, irq_num, 
                               NULL, my_interrupt_handler, 
                               IRQF_TRIGGER_RISING | IRQF_ONESHOT, 
                               input_dev->name, my_data_struct); 
if (error) { 
    dev_err(&client->dev, "irq %d requested failed, %d\n", 
        client->irq, error); 
    return error; 
} 

把它们放在一起

下面的代码是将所有关于基于整数的接口的概念付诸实践的总结。这个驱动管理四个 GPIOs:两个按钮(btn1 和 btn2)和两个指示灯(绿色和红色)。Btn1 被映射到一个 IRQ,每当它的状态变为低电平时,btn2 的状态就被施加到发光二极管上。例如,当 btn2 为高电平时,如果 btn1 的状态变为低电平,则GREENRED led 将被驱动为高电平:

#include <linux/init.h> 
#include <linux/module.h> 
#include <linux/kernel.h> 
#include <linux/gpio.h>        /* For Legacy integer based GPIO */ 
#include <linux/interrupt.h>   /* For IRQ */ 

static unsigned int GPIO_LED_RED = 49; 
static unsigned int GPIO_BTN1 = 115; 
static unsigned int GPIO_BTN2 = 116; 
static unsigned int GPIO_LED_GREEN = 120; 
static unsigned int irq; 

static irq_handler_t btn1_pushed_irq_handler(unsigned int irq, 
                             void *dev_id, struct pt_regs *regs) 
{ 
    int state; 

    /* read BTN2 value and change the led state */ 
    state = gpio_get_value(GPIO_BTN2); 
    gpio_set_value(GPIO_LED_RED, state); 
    gpio_set_value(GPIO_LED_GREEN, state); 

    pr_info("GPIO_BTN1 interrupt: Interrupt! GPIO_BTN2 state is %d)\n", state); 
    return IRQ_HANDLED; 
} 

static int __init helloworld_init(void) 
{ 
    int retval; 

    /* 
     * One could have checked whether the GPIO is valid on the controller or not, 
     * using gpio_is_valid() function. 
     * Ex: 
     *  if (!gpio_is_valid(GPIO_LED_RED)) { 
     *       pr_infor("Invalid Red LED\n"); 
     *       return -ENODEV; 
     *   } 
     */ 
    gpio_request(GPIO_LED_GREEN, "green-led"); 
    gpio_request(GPIO_LED_RED, "red-led"); 
    gpio_request(GPIO_BTN1, "button-1"); 
    gpio_request(GPIO_BTN2, "button-2"); 

    /* 
     * Configure Button GPIOs as input 
     * 
     * After this, one can call gpio_set_debounce() 
     * only if the controller has the feature 
     * 
     * For example, to debounce a button with a delay of 200ms 
     *  gpio_set_debounce(GPIO_BTN1, 200); 
     */ 
    gpio_direction_input(GPIO_BTN1); 
    gpio_direction_input(GPIO_BTN2); 

    /* 
     * Set LED GPIOs as output, with their initial values set to 0 
     */ 
    gpio_direction_output(GPIO_LED_RED, 0); 
    gpio_direction_output(GPIO_LED_GREEN, 0); 

    irq = gpio_to_irq(GPIO_BTN1); 
    retval = request_threaded_irq(irq, NULL,\ 
                            btn1_pushed_irq_handler, \ 
                            IRQF_TRIGGER_LOW | IRQF_ONESHOT, \ 
                            "device-name", NULL); 

    pr_info("Hello world!\n"); 
    return 0; 
} 

static void __exit hellowolrd_exit(void) 
{ 
    free_irq(irq, NULL); 
    gpio_free(GPIO_LED_RED); 
    gpio_free(GPIO_LED_GREEN); 
    gpio_free(GPIO_BTN1); 
    gpio_free(GPIO_BTN2); 

    pr_info("End of the world\n"); 
} 

module_init(hellowolrd_init); 
module_exit(hellowolrd_exit); 

MODULE_AUTHOR("John Madieu <[email protected]>"); 
MODULE_LICENSE("GPL"); 

基于描述符的 GPIO 接口:新的推荐方式

使用新的基于描述符的 GPIO 接口,GPIO 的特征在于连贯的struct gpio_desc结构:

struct gpio_desc { 
   struct gpio_chip  *chip; 
   unsigned long flags; 
   const char *label; 
}; 

应该使用以下标题来使用新接口:

#include <linux/gpio/consumer.h> 

使用基于描述符的接口,在分配和获得 gpio 的所有权之前,这些 gpio 必须已经被映射到某个地方。通过映射,我的意思是它们应该被分配给你的设备,而对于传统的基于整数的接口,你只需要在任何地方获取一个数字,并作为 GPIO 请求它。实际上,内核中有三种映射:

  • 平台数据映射:映射在板卡文件中完成。
  • 设备树:映射是以 DT 风格完成的,与前面章节讨论的相同。这是我们将在本书中讨论的映射。
  • 高级配置和电源接口映射 ( ACPI ):映射采用 ACPI 风格。通常用于基于 x86 的系统。

GPIO 描述符映射-设备树

GPIO 描述符映射在消费设备的节点中定义。包含 GPIO 描述符映射的属性必须命名为<name>-gpios<name>-gpio,其中<name>有足够的意义来描述这些 GPIO 将用于的功能。

应该总是在属性名后面加上-gpio-gpios,因为每个基于描述符的接口函数都依赖于gpio_suffixes[]变量,该变量在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,这些函数将返回-ENOENT,或者出现另一个可以使用IS_ERR()宏的错误。第一个函数返回对应于给定索引处的 GPIO 的 GPIO 描述符结构,而第二个函数返回索引 0 处的 GPIO(对一个 GPIO 映射有用)。dev是 GPIO 描述符所属的设备。这是你的设备。con_id是 GPIO 消费者内部的功能。它对应于 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_HIGH); 

LED GPIOs 将为高电平有效,而电源 GPIO 将为低电平有效(即gpiod_is_active_low(power)为真)。分配的反向操作通过gpiod_put()功能完成:

gpiod_put(struct gpio_desc *desc); 

让我们看看如何释放redblue GPIO 发光二极管:

gpiod_put(blue); 
gpiod_put(red); 

在我们进一步讨论之前,请记住,除了与gpio_request()gpio_free()完全不同的gpiod_get() / gpiod_get_index()gpio_put()功能之外,只需将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); 

把它们放在一起

驱动总结了基于描述符的接口中引入的概念。原理是一样的,GPIOs 也是一样的:

#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 unsigned int irq; 

static irq_handler_t btn1_pushed_irq_handler(unsigned int irq, 
                              void *dev_id, struct pt_regs *regs) 
{ 
    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)\n", 
              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!\n"); 
    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!\n"); 
} 

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("John Madieu <[email protected]>"); 
MODULE_LICENSE("GPL"); 

GPIO 接口和设备树

无论需要使用 GPIO 的接口是什么,如何指定 GPIOs 取决于提供它们的控制器,尤其是其#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 包含控制器-1 和控制器-2 gpio。如果不需要在列表中给定的索引处指定 GPIO,可以使用<0>。复位 GPIO 有两个单元(控制器指针后有两个参数),而 CD GPIO 只有一个单元。你可以看到我给我的 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) 

给定一个设备节点,前者返回位于index位置的属性*propname的 GPIO 号。第二个只是返回属性中指定的 GPIOs 数量:

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>-gpiogpios。在这种情况下,应该使用未命名的 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\n"); 
    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\n"); 
    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 unsigned int irq; 

static irq_handler_t btn1_pushed_irq_handler(unsigned int irq, void *dev_id, 
                            struct pt_regs *regs) 
{ 
    /* 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("John Madieu <[email protected]>"); 
MODULE_LICENSE("GPL"); 

设备树中到 IRQ 的 GPIO 映射

人们可以很容易地在设备树中将 GPIO 映射到 IRQ。两个属性用于指定中断:

  • interrupt-parent:这是 GPIO 的 GPIO 控制器
  • interrupts:这是中断说明符列表

这适用于传统的和基于描述符的接口。IRQ 说明符取决于提供该 GPIO 的 GPIO 控制器的#interrupt-cell属性。#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 有两种解决方案:

  1. 您的设备位于已知的总线(I2C 或 SPI) 上:将为您完成 IRQ 映射,并通过赋予您的probe()功能的struct i2c_clientstruct spi_device结构(通过i2c_client.irqspi_device.irq实现)。
  2. 你的设备坐在伪平台总线:probe()功能会被赋予一个struct platform_device,你可以在上面调用platform_get_irq():
int platform_get_irq(struct platform_device *dev, unsigned int num); 

随意看看第六章设备树的概念

GPIO 和 sysfs

sysfs GPIO 接口让人们可以通过集合或文件来管理和控制 GPIOs。位于/sys/class/gpio下方。这里大量使用设备模型,有三种条目可用:

  • /sys/class/gpio/:这是一切开始的地方。该目录包含两个特殊文件,exportunexport:
    • export:这允许我们要求内核通过将给定 GPIO 的编号写入这个文件来将它的控制权导出到用户空间。示例:echo 21 > export将为 GPIO #21 创建一个 GPIO21 节点,如果内核代码没有请求的话。
    • unexport:这会反转导出到用户空间的效果。示例:echo 21 > unexport将删除使用导出文件导出的任何 GPIO21 节点。
  • /sys/class/gpio/gpioN/:该目录对应于 GPIO 号 N(其中 N 是系统全局的,而不是相对于芯片),使用export文件导出,或者从内核内部导出。例如:/sys/class/gpio/gpio42/(针对 GPIO #42),具有以下读/写属性:
    • direction文件用于获取/设置 GPIO 方向。允许的值是inout字符串。通常可以写入该值。写为输出默认将值初始化为低。为了确保无毛刺操作,可以写入低和高值,以将 GPIO 配置为具有该初始值的输出。如果内核代码已经导出了该 GPIO,禁用方向(参见gpiod_export()gpio_export()功能),则该属性将不存在。
    • value属性让我们根据方向、输入或输出来获取/设置 GPIO 线的状态。如果 GPIO 被配置为输出,则写入的任何非零值都将被视为高状态。如果配置为输出,写入0会将输出设置为低,而1会将输出设置为高。如果该引脚可以被配置为中断生成线路,并且如果它已经被配置为生成,则可以在该文件上调用poll(2)系统调用,poll(2)将在中断被触发时返回。使用poll(2)将需要设置事件POLLPRIPOLLERR。如果用select(2)代替,应该在exceptfds中设置文件描述符。poll(2)返回后,要么lseek(2)回到 sysfs 文件的开头读取新值,要么关闭文件重新打开读取值。这与我们讨论的关于可轮询 sysfs 属性的原理相同。
    • edge确定让poll()select()功能返回的信号沿。允许值为nonerisingfallingboth。该文件是可读/可写的,并且仅当引脚可以配置为产生中断的输入引脚时才存在。
    • active_low为 0(假)或 1(真)。写入任何非零值将反转读取和写入的属性。通过上升沿和下降沿的边缘属性,现有的和后续的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 驱动提供了选择适合您需求的接口的可能性。您将能够处理映射到 GPIOs 的 IRQ。下一章将讨论提供和暴露 GPIO 线的芯片,称为 GPIO 控制器。