GPIOLIB框架下的GPIO驱动

一、GPIOLIB 简介

GPIO(通用目的输入/输出端口)是一种灵活的软件控制的数字信号。大多数的嵌入式 处理器都引出一组或多组的 GPIO,并且部分普通管脚通过配置可以复用为 GPIO。

利用可 编程逻辑器件,或总线(如 I2C、SPI)转 GPIO 芯片,也可以扩展系统的 GPIO。不管是何种 GPIO,GPIOLIB 为内核和用户层都提供了标准的操作方法。

GPIOLIB 的接口十分简洁。在 GPIOLIB,所有的 GPIO 都是用整形的 GPIO 编号标识。 只要获得要操作 GPIO 的编号,就可以调用 GPIOLIB 提供的方法操作 GPIO。

二、GPIOLIB 的内核接口

GPIOLIB 的内核接口是指:若某些 GPIO 在 GPIOLIB 框架下被驱动后,GPIOLIB 为内 核的其它代码操作该 GPIO 而提供的标准接口。

1 GPIO 的申请和释放

GPIO 在使用前,必须先调用 gpio_request()函数申请 GPIO:

int gpio_request(unsigned gpio, const char *label);

该函数的 gpio 参数为 GPIO 编号;label 参数为 GPIO 的标识字符串,可以随意设定。 若该函数调用成功,将返回 0 值;否则返回非 0 值。gpio_request()函数调用失败的原因可能为 GPIO 的编号不存在,或在其它地方已经申请了该 GPIO 编号而还没有释放。

当 GPIO 使用完成后,应当调用 gpio_free()函数释放 GPIO:

void gpio_free(unsigned gpio);

2 GPIO 的输出控制

在操作 GPIO 输出信号前,需要调用 gpio_direction_output()函数把 GPIO 设置为输出方向:

int gpio_direction_output(unsigned gpio, int value);

把 GPIO 设置为输出方向后,参数 value 为默认的输出电平:1 为高电平;0 为低电平。

GPIO 被设置为输出方向后,就可以调用 gpio_set_value()函数控制 GPIO 输出高电平或 低电平:

 void gpio_set_value(unsigned gpio, int value);

该函数的 value 参数可取值为:1 为高电平;0 为低电平。

3 GPIO 的输入控制

当需要从 GPIO 读取输入电平状态前,需要调用 gpio_direction_input()函数设置 GPIO 为 输入方向:

 int gpio_direction_input(unsigned gpio);

在 GPIO 被设置为输入方向后,就可以调用 gpio_get_value()函数读取 GPIO 的输入电平 状态:

 int gpio_get_value(unsigned gpio);

该函数的返回值为 GPIO 的输入电平状态:1 为高电平;0 为低电平。

4 GPIO 的中断映射

大多数的嵌入式处理器的 GPIO 引脚在被设置为输入方向后,可以用于外部中断信号的 输入。这些中断号和 GPIO 编号通常有对应关系,因此 GPIOLIB 为这些 GPIO 提供了 gpio_to_irq()函数用于通过 GPIO 编号而获得该 GPIO 中断号:

 int gpio_to_irq(unsigned gpio);

gpio_to_irq()函数调用完成后,返回 GPIO 中断号。

由于并不是所有的 GPIO 都可以作为外部中断信号输入端口,所以 gpio_to_irq()函数不 是对所有的 GPIO 都强制实现的。

三、GPIOLIB的实现方法

大部分的嵌入式处理器的 GPIO 都是分组的。以 i.MX28 系列的处理器为例,所有 GPIO 被分为 5 组,每组 GPIO 数量从 20 到 32 不等。之所以把 GPIO 分组,是因为每组 GPIO 的 操作寄存器是相同或相近的。若 GPIO 是用可编程逻辑器件或总线(如 I2C、SPI 等)转 GPIO 扩展的,也需要按实现情况对其 GPIO 分组。GPIOLIB 对系统的所有 GPIO 统一编号,而每 组的 GPIO 编号都是连续的。

GPIOLIB 对每组 GPIO 都用一个 gpio_chip 对象来实现其驱动,其定义如所示:

 1 struct gpio_chip {
 2     const char        *label;
 3     struct device     *dev;
 4     struct module     *owner;
 5     struct list_head  list;
 6 
 7     int            (*request)(struct gpio_chip *chip, unsigned offset);
 9     void           (*free)(struct gpio_chip *chip, unsigned offset);
11     int            (*get_direction)(struct gpio_chip *chip, unsigned offset);
13     int            (*direction_input)(struct gpio_chip *chip, unsigned offset);
15     int            (*direction_output)(struct gpio_chip *chip, unsigned offset, int value);
17     int            (*get)(struct gpio_chip *chip, unsigned offset);
19     void           (*set)(struct gpio_chip *chip, unsigned offset, int value);
21     int            (*set_debounce)(struct gpio_chip *chip, unsigned offset, unsigned debounce);
25     int            (*to_irq)(struct gpio_chip *chip, unsigned offset);
28     void           (*dbg_show)(struct seq_file *s, struct gpio_chip *chip);
30     int            base;
31     u16            ngpio;
32     struct gpio_desc    *desc;
33     const char          *const *names;
34     bool            can_sleep;
35     bool            exported;
36 
37 #if defined(CONFIG_OF_GPIO)
38     /*
39      * If CONFIG_OF is enabled, then all GPIO controllers described in the
40      * device tree automatically may have an OF translation
41      */
42     struct device_node *of_node;
43     int of_gpio_n_cells;
44     int (*of_xlate)(struct gpio_chip *gc,
45             const struct of_phandle_args *gpiospec, u32 *flags);
46 #endif
47 #ifdef CONFIG_PINCTRL
48     /*
49      * If CONFIG_PINCTRL is enabled, then gpio controllers can optionally
50      * describe the actual pin range which they serve in an SoC. This
51      * information would be used by pinctrl subsystem to configure
52      * corresponding pins for gpio usage.
53      */
54     struct list_head pin_ranges;
55 #endif
56 };

下面介绍 gpio_chip 的部分成员:

base 该组 GPIO 的起始值。

ngpio 该组 GPIO 的数量。

owner  该成员表示所有者,一般设置为 THIS_MODULE。

request 在对该组的 GPIO 调用 gpio_request()函数时,该成员指向的实现函数将被调用。 在该成员指向的实现函数中,通常需要执行指定 GPIO 的初始化操作。

在实现函数中都是用索引值来区别组内的 GPIO。索引值是指组内的某一 GPIO 编号相 对于该组 GPIO 起始值(base)的偏移量,例如,组内第 1 个 GPIO 的索引值为 0、第 2 个 GPIO 的索引值为 1„„ 实现函数的 offset 参数为要操作 GPIO 的索引值(以下相同)。

free 在对该组的 GPIO 调用 gpio_free()函数时,该成员指向的实现函数将被调用。在该 成员的实现函数中,通常需要执行指定 GPIO 硬件资源的释放操作。

direction_input 在对该组的 GPIO 调用 gpio_direction_input()函数时,该成员指向的实 现函数将被调用。在该成员的实现函数中,需要把指定的 GPIO 设置为输入方向。

get 在对该组的 GPIO 调用 gpio_get_value()函数时,该成员指向的实现函数将被调用。 在该成员的实现函数中,需要返回指定 GPIO 的电平输入状态。

direction_output 在对该组的 GPIO 调用 gpio_direction_output()函数时,该成员指向的实 现函数将被调用。在该成员的实现函数中,需要把指定的 GPIO 设置为输出方向。

set 在对该组的 GPIO 调用 gpio_set_value()函数时,该成员指向的实现函数将被调用。 在该成员的实现函数中,需要把指定的 GPIO 设置为指定的电平输出状态。

当 GPIO 控制器初始化完成后,就可以调用 gpiochip_add()函数注册到内核:

 int gpiochip_add(struct gpio_chip *chip);

该函数调用成功后,将返回 0 值;否则将返回非 0 值。

在给一组 GPIO 安排编号时,注意不要和其它 GPIO 组的编号有重叠,否则会造成注册GPIO 控制器的出错。

对于每种处理器平台,其最大 GPIO 编号值都由 ARCH_NR_GPIOS 宏设定的。在gpiolib.c中有引用

static struct gpio_desc gpio_desc[ARCH_NR_GPIOS];

  ARCH_NR_GPIOS 宏的值会限制系统的 GPIO 数量。当需要为系统添加 GPIO 而 受到该值的制约时,解决办法时是把该值改成足够大,然后重新编译内核,并把新的内核固 件烧写到目标机。

当需要把 GPIO 控制器从内核注销时,可以调用 gpiochip_remove()函数:

int __must_check gpiochip_remove(struct gpio_chip *chip);

该函数也需要检查返回值:0 值为注销成功;非 0 值为注销失败。注销失败的原因可能 是有 GPIO 正被使用。

具体实现,以IMX283为例,其源码是\drivers\gpio\gpio-mxs.c文件。

 四、驱动测试

给 gpio_chip 结构体的 base = 160; ngpio = 2;

编译好驱动之后, 在/sys/class/gpio/目录可看到新添加的控制器

进入 gpiochip160 目录,可以看到新添加的 GPIO 控制器的属性文件

 base 属性文件中可以看到该控制器的 GPIO 始起值;在 ngpio 属性文件中可以看到该 控制器的 GPIO 数量

 

/sys/class/gpio/export 中,可以导出 160  161  GPIO

gpio160 目录包含了 GPIO3_4 的控制属性文件

这时 direction 属性文件的默认值为输入

输入下面命令,在 direction 属性文件设置 GPIO 为输出工作状态

 # echo out >direction 

这时在 value 属性文件分别设置 1  0 值,在 GPIO 分别输出高电平和低电平

五、在 C 程序中操作 GPIO

 使用系统调用实现 GPIO 输入输出操作时,首先需要使用 export 属性文件导出 GPIO: 

 

 可以调用 write 函数向 direction 设备写入方向 in/out 字符串,将 GPIO 设置为输入(输 出):

 

 GPIO 设置为输入时使用 read 系统调用读取 value 属性文件,就可以读取 GPIO 电平值。 GPIO 设置为输出时,使用 write 系统调用向 value 属性文件写入 0  1 字符串,就可以设置 GPIO 电平值:

 

 

注: Linux代码摘取的是3.14版本

 

posted @ 2020-05-19 20:18  on_the_go  阅读(1091)  评论(0编辑  收藏  举报