linux设备驱动 gpio,在Linux驱动中使用gpio子系统

  • Post author:
  • Post category:linux


reference:

内核相关文档

Documentation\devicetree\bindings\pinctrl\pinctrl-bindings.txt

Documentation\gpio\gpio.txt

Documentation\devicetree\bindings\gpio\gpio.txt

背景

随着内核的发展,linux驱动框架在不断的变化。在早期,GPIO子系统存在之前,我们驱动需要在代码中配置寄存器来使用GPIO引脚。

此后,出现了gpio子系统,后来又出现了pinctrl子系统。

有些平台的实现没有使用内核提供的pinctrl子系统,而是继续采用在内核提供pinctrl子系统前自己实现的那套机制来pinmux操作,如Ti的omap平台;

有些平台则基于pinctrl子系统来实现pinmux、pinconf的控制。

介绍

GPIO子系统可以说是Linux中最简单的子系统。

GPIO(General Purpose Input Output):负责管理整个系统各gpio输入输出管脚的使用情况,同时通过sys文件系统导出了调试信息和应用层控制接口。

Pinctrl(Pin Control):负责管理SOC中各pin的状态,比如输出电流能力、是否有内部上拉或者下拉,是否有功能复用等参数。

要想操作GPIO引脚,需要先把所用引脚配置成GPIO功能,这个通过pinctrl子系统来实现。然后可以根据设置的引脚的方向来读取引脚的值和设置输出值。

在BSP工程师实现好GPIO子系统后,我们就可以在设备树中指定GPIO引脚,在驱动中使用GPIO子系统的标准函数来获取GPIO、设置GPIO方向、读取/设置GPIO的值。这样的驱动代码是于单板无关的。

gpio子系统

gpio子系统内部实现主要提供了两类接口:

一类给bsp工程师,用于注册gpio chip(也就是所谓的gpio控制器驱动)

另一部分给驱动工程师使用,为驱动工程师屏蔽了不同gpio chip之间的区别,驱动工程师调用的api的最终操作流程会导向gpio对应的gpio chip的控制代码,也就是bsp的代码。

核心实现

gpio子系统的实现源码在drivers/gpio文件夹下,主要文件有:

在安卓系统中,实现源码在kernel/drivers/gpio

文件

作用

devres.c

针对gpio api增加的devres机制的支持

gpiolib.c

gpio子系统的核心实现

gpiolib-of.c

对设备树的支持

gpiolib-acpi.c

和acpi相关,不分析

gpio-xxx.c

根据平台的不同,所对应的gpio控制

gpio子系统提供了两层接口,一层给上层驱动工程师调用,一层给下层bsp工程师调用。

上层使用前,当然先得bsp工程师完成对应的动作。

Linux内核中GPIO子系统的软件驱动分层图

GPIO子系统有两套接口:

一是基于描述符(descriptor-based)的,相关api函数都是以”gpiod_”为前缀,它使用gpio_desc结构来表示一个引脚。

另一种是老(legency)的,相关api函数都是以”gpio_”为前缀,它使用一个整数来表示一个引脚,强烈建议不要使用legacy的接口函数。

其实,legacy gpio大部分api就是基于描述符api来实现的,我们可以看到很多legacy api内部的实现调用了to_desc。

// 1.获取GPIO

gpiod_get;

gpiod_get_index;

gpiod_get_array;

devm_gpiod_get;

devm_gpiod_get_index;

devm_gpiod_get_array;

// 2.设置方向

gpiod_direction_input;

gpiod_direction_output;

// 3.读值、写值

gpiod_get_value;

gpiod_set_value;

// 4\. 设为中断(如果必要)

request_irq(gpiod_to_irq(gpio_desc)…); //将gpio转为对应的irq,然后注册该irq的中断handler

// 5.释放GPIO

gpiod_put;

gpiod_put_array;

devm_gpiod_put;

devm_gpiod_put_array;

前缀为”devm_”的含义是设备资源管理,这是一种自动释放资源的机制。

思想:“资源是属于设备的,设备不存在时资源就可以自动释放”。

背景:在Linux驱动开发过程中,先申请了GPIO,再申请内存,如果内存申请失败,那么在返回之前就需要先释放GPIO资源。如果使用的是devm相关函数,在内存申请失败时可以直接返回,设备的销毁函数会自动地释放已经申请了的GPIO资源。

因此,建议使用devm相关函数操作GPIO。

gpio控制api( descriptor)

使用基于描述符的接口时,GPIO被作为一个描述符来使用。

#include

// 更多相关的说明可以参考 Documentation/gpio/consumer.txt

获取一个或一组GPIO

struct gpio_desc * gpiod_get(struct device *dev,

const char *con_id,

enum gpiod_flags flags);

/*

在允许GPIO不存在时,可以使用gpiod_get_optional()和gpiod_get_index_optional()函数。

这两个函数在没有成功分配到GPIO的时候返回NULL而不是-ENOENT。

*/

struct gpio_desc * gpiod_get_optional(struct device *dev,

const char *con_id,

enum gpiod_flags flags);

struct gpio_descs {

unsigned int ndescs; // 数量

struct gpio_desc *desc[]; // 每一个 desc 的情况

}

// 返回gpio_descs 注意:不是 gpio_desc

struct gpio_descs * gpiod_get_array(struct device *dev,

const char *con_id,

enum gpiod_flags flags);

/*多个Pin时需要附带index参数。*/

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_index_optional(struct device *dev,

const char *con_id,

unsigned int index,

enum gpiod_flags flags);

struct gpio_desc * devm_gpiod_get(struct device *dev, const char *con_id,

enum gpiod_flags flags);

struct gpio_desc * devm_gpiod_get_index(struct device *dev,

const char *con_id,

unsigned int idx,

enum gpiod_flags flags);

描述:必须通过调用gpiod_get()函数族来获取对应的描述符。

参数解析:

con_id:字符串类型,即GPIO的名字;

一般需要查看设备树中的定义。除此之外,我们还可以在设备树文件里添加参数(GPIO_ACTIVE_LOW、GPIO_OPEN_DRAIN、GPIO_OPEN_SOURCE)来触发该接口内部设置gpio,具体的参数格式和具体的gpio chip driver有关,一般可以在Documentation/devicetree/bindings/gpio里找到对应平台的方法。

有关DeviceTree情况中con_id参数的更详细说明请参阅Documentation/gpio/board.txt

例如:

在SD卡驱动看到的去查找名字为cd-gpios的gpio:

// simple.c:

ctx->cd_gpio = devm_gpiod_get_optional(dev, “cd”, 0);

在使用SD卡驱动的主dts就有cd pin的定义:

// xxx.dts:

cd-gpios = ;

index:逻辑下标。将一个GPIO设备(DESC)下的多个Pin看成一个数组,此时index是数组成员下标。

内核文档有个例子,比如gpio如下定义:

led-gpios = , /* red */

, /* green */

; /* blue */

如果index是0,那么对应的就是gpio 15;如果index是1,那么对应就是gpio 16,以此类推。

flags:用于可选地指定GPIO的方向和初始值,它的值可以是:

GPIOD_ASIS或0表示根本不初始化GPIO。需要随后使用专门的函数设置方向

GPIOD_IN初始化GPIO作为输入。

GPIOD_OUT_LOW将GPIO初始化为输出,值为0。

GPIOD_OUT_HIGH将GPIO初始化为输出,值为1。

GPIOD_OUT_LOW_OPEN_DRAIN:与GPIOD_OUT_LOW相同,但强制以开漏的方式使用

GPIOD_OUT_HIGH_OPEN_DRAIN:与GPIOD_OUT_HIGH相同,但强制以开漏的方式使用

最后两个标志用于必须开漏方式的情况,比如GPIO被用作I2C时,如果该GPIO尚未在映射(参见board.txt)中被配置为开漏方式,将被强制配置为开漏方式并给出WARNING。

这两个函数都返回有效的GPIO描述符或可被IS_ERR()检查的错误代码(它们永远不会返回NULL指针)。