0%

Linux驱动学习日记(28) GPIO 子系统详解

简单回顾

  GPIO 子系统就是用于初始化 GPIO 并提供相应 API 的函数,比如设置 GPIO 为输入输出、读取 GPIO 的值等等。以下十几个 GPIO 子系统常用的 API 函数。

1
2
3
4
5
6
7
8
9
int gpio_request(unsigned gpio,  const char *label)
void gpio_free(unsigned gpio)
int gpio_direction_input(unsigned gpio)
int gpio_direction_output(unsigned gpio)
#define gpio_get_value __gpio_get_value int __gpio_get_value(unsigned gpio)
#define gpio_set_value __gpio_set_value void __gpio_set_value(unsigned gpio, int value)
int of_gpio_named_count(struct device_node *np, const char *propname)
int of_gpio_count(struct device_node *np)
int of_get_named_gpio(struct device_node const char int *np, *propname, index)

gpio 子系统

  Linux GPIO 的核心思想其实是 统一接口+不同芯片实现,其整体架构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
        Device Tree

│ 解析

platform_driver

│ 调用

gpiolib
┌─────────┴─────────┐
│ │
GPIO Descriptor gpio_chip
│ │
└─────────┬─────────┘

GPIO Controller
(例如 imx gpio driver)

寄存器

  接下来,我们从源码出发,一步一步拆解 GPIO 子系统,看看它到底是如何运行的。

gpiod_get()

  我们先来看看函数原型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* gpiod_get - obtain a GPIO for a given GPIO function
* @dev: GPIO consumer, can be NULL for system-global GPIOs
* @con_id: function within the GPIO consumer
* @flags: optional GPIO initialization flags
*
* Return the GPIO descriptor corresponding to the function con_id of device
* dev, -ENOENT if no GPIO has been assigned to the requested function, or
* another IS_ERR() code if an error occurred while trying to acquire the GPIO.
*/
struct gpio_desc *__must_check gpiod_get(struct device *dev, const char *con_id,enum gpiod_flags flags)
{
return gpiod_get_index(dev, con_id, 0, flags);
}
EXPORT_SYMBOL_GPL(gpiod_get);

  这个函数的作用是从 设备树/ACPI/board file 中获取一个 GPIO,并返回 GPIO descriptor。在新版本中,Linux 内核不再以 GPIO 编号作为 ID,而是一个 struct gpio_desc * 的结构体,其原型如下

1
2
3
4
5
6
7
8
struct gpio_desc {
struct gpio_chip *chip;
unsigned long flags;
/* Connection label */
const char *label;
/* Name of the GPIO */
const char *name;
};

  这其中保存了这个 gpio 属于哪个 gpio_chip、GPIO 编号、GPIO 状态等等。
  gpio_chip 对 GPIO 子系统的作用相当于 file_operations 对 VFS 的作用。gpio_chip 就是某个 GPIO 控制器驱动向 gpiolib 上交的操作表+控制器信息表,包含了一些基本数据和操作函数。比如 IMX6U 里有 GPIO1-6,每一个 GPIO 控制器,在内核里都可以对应一个 gpio_chip,所以可以理解为 gpio_chip 是一组 GPIO 的控制器抽象对象。gpiolib 通过 gpio_chip 中这些函数指针操作底层。
  gpio_chip 中包含了多类信息:身份信息字段、GPIO 操作函数、
批量操作、高级配置和中断、调试和有效位图、设备树相关字段等等。Linux 内核驱动 GPIO 的顺序如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
驱动程序

gpiod_get / gpiod_set_value

gpiolib

gpio_desc

gpio_chip
├─ direction_input
├─ direction_output
├─ get
├─ set
├─ to_irq
└─ of_xlate

SoC GPIO driver

GPIO寄存器

  回到 gpio_get 函数,参数中 dev 就表示使用者设备,Linux 会根据这个设备找到设备树节点。con_id 表示 GPIO 的功能名称,例如 “led”,Linux 就会在刚刚的设备树节点中查找 led-gpios,flags 表示初始化方式,比如常见的 GPIOD_IN、GPIOD_OUT_LOW、GPIOD_OUT_HIGH 等等。
  可以看到函数内部只是调用了 gpiod_get_index(),这是一种 Linux 常见的设计模式,这样设计的原因是 Linux 支持多个 GPIO,这样设计省略了 index 参数。当你只有一个 gpio 的时候,可以直接调用 gpiod_get 函数,当你需要多个 GPIO 的时候,就要使用 gpiod_get_index() 来指定 index。

gpiod_set_value

1
2
3
4
5
6
7
8
void gpiod_set_value(struct gpio_desc *desc, int value)
{
VALIDATE_DESC_VOID(desc);
/* Should be using gpiod_set_value_cansleep() */
WARN_ON(desc->gdev->chip->can_sleep);
gpiod_set_value_nocheck(desc, value);
}
EXPORT_SYMBOL_GPL(gpiod_set_value);

  函数原型如上,我们先检查 desc 是否有效。在 gpio_chip 中有 bool can_sleep,表示这个 GPIO 操作会不会导致 sleep,所以 Linux 提供了两个 api,不会 sleep 的 gpio 使用 gpiod_set_value,可能 sleep 的 GPIO 使用 gpiod_set_value_cansleep。

of_get_named_gpiod_flags

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
static struct gpio_desc *of_get_named_gpiod_flags(const struct device_node *np,
const char *propname, int index, enum of_gpio_flags *flags)
{
struct of_phandle_args gpiospec;
struct gpio_chip *chip;
struct gpio_desc *desc;
int ret;

ret = of_parse_phandle_with_args_map(np, propname, "gpio", index,
&gpiospec);
if (ret) {
pr_debug("%s: can't parse '%s' property of node '%pOF[%d]'\n",
__func__, propname, np, index);
return ERR_PTR(ret);
}

chip = of_find_gpiochip_by_xlate(&gpiospec);
if (!chip) {
desc = ERR_PTR(-EPROBE_DEFER);
goto out;
}

desc = of_xlate_and_get_gpiod_flags(chip, &gpiospec, flags);
if (IS_ERR(desc))
goto out;

if (flags)
of_gpio_flags_quirks(np, propname, flags, index);

pr_debug("%s: parsed '%s' property of node '%pOF[%d]' - status (%d)\n",
__func__, propname, np, index,
PTR_ERR_OR_ZERO(desc));

out:
of_node_put(gpiospec.np);

return desc;
}

  这个函数非常关键,是 Linux 中 GPIO 子系统里设备树到 GPIO descriptor 转换的核心入口之一。
  这个函数一般在驱动调用 gpiod_get 时被调用,其负责从设备树解析 GPIO,并找到对应的 gpio_chip 和 gpio_desc。最终返回 gpio_desc。

gpiod_mxc.c

  这份文件就是 gpio_chip 的具体实现驱动,操作底层寄存器。这个驱动将 IMX GPIO 控制器注册到了 gpiolib 中,也就是注册 gpio_chip 并提供 GPIO 操作函数。
  mxc_gpio_port 结构体为重要结构体,包含了注册给 gpiolib 的控制器等关键信息。在 probe 函数中主要做了以下几件事:创建 GPIO 控制器对象、获取 GPIO 寄存器、初始化 GPIO 操作函数、补充 gpio_chip 信息、最后注册 gpio_chip。

总结

  整个 Linux GPIO 子系统的框架如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
            设备驱动

│ gpiod_get(dev,"led",...)

gpiod_get()


gpiod_get_index()


fwnode_get_named_gpiod()


of_get_named_gpiod_flags()

│ 解析设备树

of_parse_phandle_with_args()


gpiospec
(<&gpio1 3 GPIO_ACTIVE_LOW>)


of_find_gpiochip_by_xlate()


gpio_chip
(GPIO控制器驱动对象)


of_xlate_and_get_gpiod_flags()


gpio_desc
(单个GPIO描述符)

│ 返回给驱动

驱动程序使用

│ gpiod_set_value()

gpiod_set_value()


gpiod_set_value_nocheck()


desc->gdev->chip


gpio_chip->set()


SoC GPIO Driver
(例如 gpio-mxc.c)


写GPIO寄存器