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版本