在Linux驱动中使用gpio子系统

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子系统有两套接口

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

  2. 另一种是老(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 <linux/gpio/consumer.h>
// 更多相关的说明可以参考 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_LOWGPIO_OPEN_DRAINGPIO_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 = <&gpio2 12 GPIO_ACTIVE_LOW>;

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

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

led-gpios = <&gpio 15 GPIO_ACTIVE_HIGH>, /* red */
            <&gpio 16 GPIO_ACTIVE_HIGH>, /* green */
            <&gpio 17 GPIO_ACTIVE_HIGH>; /* 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指针)。

返回值:成功返回一个GPIO描述符;失败返回错误编码,可以使用IS_ERR()进行检查错误原因。

  • 返回-ENOENT只会发生在当且仅当没有为设备/功能/索引三元组成功分配GPIO的时候。
  • 其他错误代码用于已成功分配GPIO,但在试图获得它的时候发生了错误的情况:这可以用于区分错误原因是可选GPIO参数错误还是GPIO缺失这两种情况。

释放

void gpiod_put(struct gpio_desc *desc);
void gpiod_put_array(struct gpio_descs *descs); // 对应 get*array

void devm_gpiod_put(struct device *dev, struct gpio_desc *desc);
void devm_gpiod_put_array(struct device *dev, struct gpio_descs *descs);

描述:释放之前通过get获取的GPIO描述符

注意:在释放之后,严格禁止使用被释放的描述符;也不允许在使用gpiod_get_array()获取的数组中单独使用gpiod_put()释放描述符。

设置方向

注意:GPIO没有默认方向。因此,使用GPIO前必须首先设置其方向,否则将导致未定义的行为!

// 设置GPIO为输入还是输出
int gpiod_direction_input(struct gpio_desc *desc);
int gpiod_direction_output(struct gpio_desc *desc, int value);

// 查询GPIO的当前方向:返回0表示输出,1表示输入,或错误代码(如果出错)
int gpiod_get_direction(const struct gpio_desc *desc);

描述:使用设备驱动必须首先确定GPIO的方向。如果在调用gpiod_get* ()时,flag指定了nodirection,就可以调用上面的某个gpiod_direction_*()函数来设置方向:

参数解析:

  • value:对于输出GPIO,提供的值将成为初始输出值;用于避免系统启动期间的信号故障。

返回值:成功返回值为零,否则返回值为负的错误代码。

该返回值应该被检查,因为之后获取/设置GPIO引脚值get/set调用不会返回错误,所以错误的配置是有可能的。您通常应该在任务上下文进行这些调用。但是,对于自旋锁安全(Spinlock-Safe)的GPIO,可以作为板级设置初期的一部分,在启用任务之前使用它们。

使用单个GPIO

/* 
Spinlock-Safe的GPIO访问 
    意义:如果操作GPIO可能导致sleep,那么同步机制不能采用spinlock,因为spinlock要求不能sleep
*/

// 读取输出引脚的值时,返回的值应该是引脚上的值。由于包括开漏信号和输出延迟在内的问题,它并不总是匹配指定的输出值。
int gpiod_get_value(const struct gpio_desc *desc);

void gpiod_set_value(struct gpio_desc *desc, int value);

描述:大多数GPIO控制器可通过存储器读/写指令访问。在不能睡眠的环境下调用。

不能睡眠的环境:内部hard(非线程的)IRQ handler、类似的上下文中完成的操作(即原子操作中)。

参数解析:

  • value:布尔值,零为低,非零为高。

返回值:get/set调用不会返回错误,因为“无效的GPIO”应该在这之前就从gpiod_direction_*()中得知。

但请注意,并非所有平台都可以读取输出引脚的值;对于那些不能读取的平台,函数永远返回零。另外,使用这些函数访问需要睡眠才能安全访问的GPIO(见下文)是错误的操作。

/* 允许睡眠的GPIO访问 */

// 判断是否允许睡眠:返回非零 代表 可以睡眠:
int gpiod_cansleep(const struct gpio_desc *desc);

// 获取、设置GPIO的值。
int gpiod_get_value_cansleep(const struct gpio_desc * desc);
void gpiod_set_value_cansleep(struct gpio_desc * desc,int value);

描述:有些GPIO控制器必须使用基于消息的总线(如I2C或SPI)访问。读取或写入这些GPIO值的命令需要等待到达队列的头部以传输命令并获得其响应。这样就需要允许睡眠,导致这类GPIO的访问不能在内部IRQ处理程序内(原子上下文)完成。

访问这样的GPIO需要一个可以休眠的上下文,例如一个threaded IRQ处理程序,并且必须使用上述访问函数访问函数(而不是没有带cansleep()后缀的)。

除了可以睡眠,无法在hardIRQ处理程序访问的特点以外,这些调用与Spinlock-Safe的调用相同。

使用gpio的时候需要了解一下低有效和开漏语义,见附录。

使用一组GPIO

## 获取值
int gpiod_get_array_value(unsigned int array_size,
        struct gpio_desc **desc_array,
        int *value_array);
int gpiod_get_raw_array_value(unsigned int array_size,
        struct gpio_desc **desc_array,
        int *value_array);
int gpiod_get_array_value_cansleep(unsigned int array_size,
        struct gpio_desc **desc_array,
        int *value_array);
int gpiod_get_raw_array_value_cansleep(unsigned int array_size,
        struct gpio_desc **desc_array,
        int *value_array);

## 设置值
void gpiod_set_array_value(unsigned int array_size,
        struct gpio_desc **desc_array,
        int *value_array);
void gpiod_set_raw_array_value(unsigned int array_size,
        struct gpio_desc **desc_array,
        int *value_array);
void gpiod_set_array_value_cansleep(unsigned int array_size,
        struct gpio_desc **desc_array,
        int *value_array);
void gpiod_set_raw_array_value_cansleep(unsigned int array_size,
        struct gpio_desc **desc_array,
        int *value_array);

描述:如果相应的芯片驱动器支持,这些函数将尝试同时访问属于同一存储体或芯片的GPIO。在这种情况下,可以预期显著改善的性能。如果无法同时访问,GPIO将按顺序访问。用来获取、设置GPIO的值。

参数解析:

  • array_size - 数组元素的数量
  • desc_array - GPIO描述符数组,可以是任意一组GPIO

如何理解“任意”:

我们可以先使用gpiod_get()gpiod_get_array()的任意组合来获得描述符后,放入一个我们自己构建数组中,再将其传递给上述函数)。

同时,如果为了获得最佳性能,属于同一芯片的GPIO应该在描述符数组中是连续的。

  • value_array - 存储GPIO值(get)的数组或要分配给GPIO的值数组(set)

返回值:

  • gpiod_get_array_value()及其变体成功时返回0,错误返回负数。
  • gpiod_get_value()在成功传递GPIO值时返回0或1。使用数组函数时,GPIO值存储在value_array中,而不是作为返回值传回。

小例子:

struct gpio_descs *my_gpio_descs = gpiod_get_array(...);

if(!my_gpio_descs) 
    return ERROR...

gpiod_set_array_value(my_gpio_descs->ndescs, my_gpio_descs->desc,
        my_gpio_values);

配置为中断(可选)

int gpiod_to_irq(const struct gpio_desc *desc);

描述:获取与给定GPIO相对应的IRQ编号。

返回值:返回IRQ编号或负的errno代码(很可能是因为该特定GPIO不能用作IRQ)。

注意:

  • 使用未使用gpiod_direction_input()设置为输入的GPIO,或者使用最初不是来自gpiod_to_irq()的IRQ编号,是错误的操作。
  • gpiod_to_irq()不允许休眠。

gpiod_to_irq()返回的非错误值可以传递给request_irq()free_irq()

它们通常通过特定于板的初始化代码存储到平台设备的IRQ资源中。

注意,IRQ触发选项是IRQ接口的一部分,例如, IRQF_TRIGGER_FALLING,

导出到应用空间(可选)

drivers/gpio/gpiolib.c

/**
 * gpiod_export - export a GPIO through sysfs
 * @gpio: gpio to make available, already requested
 * @direction_may_change: true if userspace may change gpio direction
 * Context: arch_initcall or later
 *
 * When drivers want to make a GPIO accessible to userspace after they
 * have requested it -- perhaps while debugging, or as part of their
 * public interface -- they may use this routine.  If the GPIO can
 * change direction (some can't) and the caller allows it, userspace
 * will see "direction" sysfs attribute which may be used to change
 * the gpio's direction.  A "value" attribute will always be provided.
 *
 * Returns zero on success, else an error.
 */
int gpiod_export(struct gpio_desc *desc, bool direction_may_change)

gpiod_export提供了用户层的访问,主要用于驱动工程师调试或者应用程序控制。

描述:将该gpio的信息通过sys文件系统导出,这样应用层可以直接查看状态、设置状态等。

参数解析:

  • direction_may_change: 用来标记这个gpio的输入输出方向是否可以改变。

如果该gpio已经设置了输入或者输出,那么它的direction_may_change为false。

兼容

旧的GPIO系统使用基于标号的结构而不是基于描述符。可以使用如下两个函数进行相互转换:

int desc_to_gpio(const struct gpio_desc *desc);
struct gpio_desc *gpio_to_desc(unsigned gpio);

注意:不能使用一套API的方法释放另一套API获取的设备。

附录:GPIO子系统其他内容

低有效和开漏语义

介绍

一般情况下,使用GPIO子系统的开发者并不需要关心GPIO对外的实际电平,因此,gpiod_set_value_xxx()gpiod_set_array_value_xxx() 这样的函数都以逻辑值操作。

这些函数会将低电平有效的性质考虑在内。也就是说,低电平有效,物理值0对应逻辑值的1。

如果我们事先告知内核某一个GPIO是低电平有效(active_low)这些函数内部会进行处理,就不再需要我们关心“到底是不是电平1有效还是电平0有效”,

例如,如果设置了GPIO的低电平有效属性,并且gpiod_set_(array)_value_xxx()传递了逻辑值1(“asserted”),则物理线路电平将被驱动为低电平。

同样适用于开漏或开源输出:它们并不输出高电平(开漏)或低电平(开源),它们只是将输出切换到高阻抗值。使用者应该不需要关注。

有关的详细信息,请参阅driver.txt中关于开漏的细节。

总结:

函数(示例) 线路属性 物理线路
gpiod_set_raw_value(desc, 0); - 低电平
gpiod_set_raw_value(desc, 0); - 高电平
gpiod_set_value(desc, 0); 默认(高电平有效) 低电平
gpiod_set_value(desc, 1); 默认(高电平有效) 高电平
gpiod_set_value(desc, 0); 低电平有效 高电平
gpiod_set_value(desc, 1); 低电平有效 低电平
gpiod_set_value(desc, 0); 开漏 低电平
gpiod_set_value(desc, 1); 开漏 高阻态
gpiod_set_value(desc, 0); 开漏 高阻态
gpiod_set_value(desc, 1); 开漏 高电平

接口

当然,如果你硬是要知道GPIO此时的电平值(的确需要管理GPIO线路物理状态),可以使用下面的一组函数来达到你要的目的。

但应尽可能避免去读原始值,尤其是系统无关的驱动程序,它们只需要关心逻辑值。

下面的一组调用忽略GPIO的低有效或开漏属性,设置什么值,物理值就是什么什么值:

raw-value的意思就是不在乎DTS里面的ACTIVE,我set 高电平,就是高电平。

int gpiod_get_raw_value(const struct gpio_desc *desc);
void gpiod_set_raw_value(struct gpio_desc *desc, int value);
int gpiod_get_raw_value_cansleep(const struct gpio_desc *desc);
void gpiod_set_raw_value_cansleep(struct gpio_desc *desc, int value);
int gpiod_direction_output_raw(struct gpio_desc *desc, int value);

还可以使用以下方法查询GPIO的低有效属性:

int gpiod_is_active_low(const struct gpio_desc *desc);

请注意,这些函数只能在使用者明白自己在做什么的情况下使用;驱动程序一般不应该关心线路物理状态或开漏语义。

在设备树中设置低有效

假设我们在DTS里面这样设置

reset-gpios = <&gpio3 RK_PA3 GPIO_ACTIVE_LOW>;

然后我们这样调用:

gpiod_set_value_cansleep(gc5025->reset_gpio, 1);

因为DTS里面的active 状态是 GPIO_ACTIVE_LOW,所以这个代码输出的是 低电平。

系统唤醒功能

这个功能与ACPI有关。

有关详细信息,请参阅Documentation/acpi/gpio-properties.txt

在ACPI系统上,GPIO由设备的_CRS配置对象列出的GpioIo()/ GpioInt()资源描述。这些资源不提供GPIO的连接ID(名称),因此有必要为此目的使用附加机制。

符合ACPI 5.1或更新版本的系统可能可以提供_DSD配置对象,它可以用于提供_CRS中的GpioIo()/ GpioInt()资源描述的特定GPIO的连接ID。如果是这种情况,它将由GPIO子系统自动处理。但是,如果不存在_DSD,则GpioIo()/ GpioInt()资源与GPIOconnection ID之间的映射需要由设备驱动程序提供。

附录:legacy-api

#include <linux/gpio.h>

还有一组用于允许睡眠场景的api没有给出,更多相关的说明可以参考Documentation/gpio/gpio-legacy.txt

使用流程:

  • 申请、释放:gpio_requestgpio_free
  • 设置GPIO方向:gpio_direction_inputgpio_direction_output
  • 获取设置GPIO值:gpio_get_valuegpio_set_value
  • (可选)设置为中断:gpio_to_irq
  • (可选)导出到sys文件系统:gpio_export

判断

/*
 * "valid" GPIO numbers are nonnegative and may be passed to
 * setup routines like gpio_request().  only some valid numbers
 * can successfully be requested and used.
 *
 * Invalid GPIO numbers are useful for indicating no-such-GPIO in
 * platform data and other tables.
 */
static inline bool gpio_is_valid(int number)
{
    return number >= 0 && number < ARCH_NR_GPIOS;
}

描述:来判断获取到的gpio号是否是有效的,只有有效的gpio号,才能向内核中进行申请使用,因此,当我们从设备树的设备节点获取到gpio号,可以使用该函数进行判断是否有效。

参数解析:

  • numb:需要判断的GPIO号。

返回值:合法为1,否则为0。

申请、释放

/* Always use the library code for GPIO management calls,
 * or when sleeping may be involved.
 */
extern int gpio_request(unsigned gpio, const char *label);
extern void gpio_free(unsigned gpio);

/**
 * struct gpio - a structure describing a GPIO with configuration
 * @gpio:    the GPIO number
 * @flags:    GPIO configuration as specified by GPIOF_*
 * @label:    a literal description string of this GPIO
 */
struct gpio {
    unsigned    gpio;
    unsigned long    flags;
    const char    *label;
};

int gpio_request_one(unsigned gpio, unsigned long flags, const char *label);
int gpio_request_array(struct gpio *array, size_t num);
void gpio_free_array(const struct gpio *array, size_t num);

/* CONFIG_GPIOLIB: bindings for managed devices that want to request gpios */
int devm_gpio_request(struct device *dev, unsigned gpio, const char *label);
int devm_gpio_request_one(struct device *dev, unsigned gpio,
              unsigned long flags, const char *label);
void devm_gpio_free(struct device *dev, unsigned int gpio);

gpio_request为例,gpio_request_onegpio_request_array是它的扩展,devm_为前缀的是gpio devres机制的实现。

描述:请求一个/一组gpio。

参数解析:

  • gpio:gpio号,可以通过sdk开发包说明文档查看,或者查看设备树文件,也可以在gpio chip驱动的实现中找到。
  • flags:可以指定GPIOF_OPEN_DRAINGPIOF_OPEN_SOURCEGPIOF_DIR_INGPIOF_EXPORT等标志
    • 如果指定了GPIOF_DIR_IN,那么后面就不需要自己再额外调用gpio_direction_input或者gpio_direction_output了,
    • 如果指定了GPIOF_EXPORT,后面就不需要自己调用gpio_export了。
  • label:向系统中申请GPIO使用的标签,类似于GPIO的名称
  • array,num:是gpio_request_arraygpio_request_one的封装,用于处理同时申请多个gpio的情形。
  • dev:带有devm_前缀,用于带设备资源管理版本的函数,因此在使用上面的函数时,需要指定设备的struct device指针,生命周期与设备相同。

返回值:成功返回0。

意义gpio_request主要做了以下动作:

  1. 检查是否已经被申请,没有的话,标记为已申请
  2. 填充label到该pin数据结构,用于debug
  3. 如果chip driver提供了request回调,调用它
  4. 如果chip driver提供了get_direction回调,调用它,通过它更新pin数据结构,标明gpio方向

用法举例:

static struct gpio leds_gpios[] = {
    { 32, GPIOF_OUT_INIT_HIGH, "Power LED" }, /* default to ON */
    { 33, GPIOF_OUT_INIT_LOW,  "Green LED" }, /* default to OFF */
    { 34, GPIOF_OUT_INIT_LOW,  "Red LED"   }, /* default to OFF */
    { 35, GPIOF_OUT_INIT_LOW,  "Blue LED"  }, /* default to OFF */
    { ... },
};

err = gpio_request_one(31, GPIOF_IN, "Reset Button");
if (err)
    ...;

err = gpio_request_array(leds_gpios, ARRAY_SIZE(leds_gpios));
if (err)
    ...;

gpio_free_array(leds_gpios, ARRAY_SIZE(leds_gpios)); 

设置方向

//设置gpio方向为输入/输出
gpio_direction_input 或者gpio_direction_output        ---------<2>  ;

static inline int gpio_direction_input(unsigned gpio)
{
    return gpiod_direction_input(gpio_to_desc(gpio));
}

static inline int gpio_direction_output(unsigned gpio, int value)
{
    return gpiod_direction_output_raw(gpio_to_desc(gpio), value);
}

描述:当我们使用gpio_request()函数族向系统中申请了GPIO资源后,可以使用上面的函数进行GPIO的方向设置:

  • 函数gpio_direction_input()用来设置GPIO的方向为输入
  • 函数gpio_direction_output()用来设置GPIO的方向为输出,并且通过value值可以设置输出的电平。

意义: gpio_direction_input或者gpio_direction_output主要是回调gpio chip driver提供的direction_input或者direction_output来设置该gpio寄存器为输入、输出。

导出

// include/asm-generic/gpio.h

/*
 * A sysfs interface can be exported by individual drivers if they want,
 * but more typically is configured entirely from userspace.
 */

static inline int gpio_export(unsigned gpio, bool direction_may_change)
{
    return gpiod_export(gpio_to_desc(gpio), direction_may_change);
}

gpio_export提供了用户层的访问,主要用于驱动工程师调试或者应用程序控制。

描述:将该gpio的信息通过sys文件系统导出,这样应用层可以直接查看状态、设置状态等。

参数解析:

  • direction_may_change: 用来标记这个gpio的输入输出方向是否可以改变。

如果该gpio已经设置了输入或者输出,那么它的direction_may_change为false。

使用

static inline int gpio_get_value(unsigned int gpio)
{
    return __gpio_get_value(gpio);
}

static inline void gpio_set_value(unsigned int gpio, int value)
{
    __gpio_set_value(gpio, value);
}

描述:当我们将GPIO的方向设置为输入时,可以使用上面的函数gpio_get_value()来获取当前的IO口电平值,当GPIO的方向设置为输出时,使用函数gpio_set_value()可以设置IO口的电平值。

申请中断

static inline int gpio_to_irq(unsigned int gpio)
{
    return __gpio_to_irq(gpio);
}

描述:用于获取该gpio对应的中断号,这个需要设备树里的该gpio节点描述使用哪个中断号

并不是所有的gpio都可以触发中断的。

意义:回调gpio chip driver提供的to_irq

例子

reference:https://www.cnblogs.com/Cqlismy/p/11891789.html

设备树(高通msm平台):

dev_gpio {
    status = "okay";
    compatible = "dev-gpio";
    label = "test_gpio";
    gpios = <&msm_gpio 68 0>;
};

驱动程序:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/sysfs.h>

struct gpio_platform_data {
    const char *label;
    unsigned int gpio_num;
    enum of_gpio_flags gpio_flag;
};

struct gpio_drvdata {
    struct gpio_platform_data *pdata;

    bool gpio_state;
};

static ssize_t ctrl_show(struct device *dev, 
                         struct device_attribute *attr, char *buf)
{
    struct gpio_drvdata *ddata = dev_get_drvdata(dev);
    int ret;

    if (ddata->gpio_state)
        ret = snprintf(buf, PAGE_SIZE - 2, "%s", "enable");
    else
        ret = snprintf(buf, PAGE_SIZE - 2, "%s", "disable");

    buf[ret++] = '\n';
    buf[ret] = '\0';

    return ret;
}

static ssize_t ctrl_store(struct device *dev,
                          struct device_attribute *attr, const char *buf, size_t count)
{
    struct gpio_drvdata *ddata = dev_get_drvdata(dev);
    bool state = ddata->gpio_state;

    if (!strncmp(buf, "enable", strlen("enable"))) {
        if (!state) {
            gpio_set_value(ddata->pdata->gpio_num, !state);
            ddata->gpio_state = !state;
            goto ret;
        }
    } else if (!strncmp(buf, "disable", strlen("disable"))) {
        if (state) {
            gpio_set_value(ddata->pdata->gpio_num, !state);
            ddata->gpio_state = !state;
            goto ret;
        }
    }

    return 0;

    ret:
    return strlen(buf);
}
static DEVICE_ATTR(ctrl, 0644, ctrl_show, ctrl_store);

static ssize_t gpio_show(struct device *dev,
                         struct device_attribute *attr, char *buf)
{
    struct gpio_drvdata *ddata = dev_get_drvdata(dev);
    int ret;

    ret = snprintf(buf, PAGE_SIZE - 2, "gpio-number: GPIO_%d",
                   ddata->pdata->gpio_num - 911);
    buf[ret++] = '\n';
    buf[ret] = '\0';

    return ret;
}
static DEVICE_ATTR(gpio, 0444, gpio_show, NULL);

static struct attribute *gpio_attrs[] = {
    &dev_attr_ctrl.attr,
    &dev_attr_gpio.attr,
    NULL
};

static struct attribute_group attr_grp = {
    .attrs = gpio_attrs,
};

static struct gpio_platform_data *
    gpio_parse_dt(struct device *dev)
{
    int ret;
    struct device_node *np = dev->of_node;
    struct gpio_platform_data *pdata;

    pdata = kzalloc(sizeof(*pdata), GFP_KERNEL);
    if (!pdata) {
        dev_err(dev, "failed to alloc memory of platform data\n");
        return NULL;
    }

    ret = of_property_read_string(np, "label", &pdata->label);
    if (ret) {
        dev_err(dev, "failed to read property of lable\n");
        goto fail;
    }

    pdata->gpio_num = of_get_named_gpio_flags(np, "gpios",
                                              0, &pdata->gpio_flag);
    if (pdata->gpio_num < 0) {
        dev_err(dev, "invalid gpio number %d\n", pdata->gpio_num);
        ret = pdata->gpio_num;
        goto fail;
    }

    return pdata;

    fail:
    kfree(pdata);
    return ERR_PTR(ret);
}

static int gpio_probe(struct platform_device *pdev)
{
    struct gpio_drvdata *ddata;
    struct gpio_platform_data *pdata;
    struct device *dev = &pdev->dev;
    struct device_node *np = dev->of_node;
    int ret;

    printk("[%s]==========gpio_probe start==========\n", __func__);

    if (!np) {
        dev_err(dev, "failed to find device node of gpio device\n");
        return -ENODEV;
    }

    ddata = kzalloc(sizeof(*ddata), GFP_KERNEL);
    if (!ddata) {
        dev_err(dev, "failed to alloc memory for driver data\n");
        return -ENOMEM;
    }

    pdata = gpio_parse_dt(dev);
    if (IS_ERR(pdata)) {
        dev_err(dev, "failed to parse device node\n");
        ret = PTR_ERR(pdata);
        goto fail1;
    }
    /* gpio初始化 */
    if (gpio_is_valid(pdata->gpio_num)) {
        /* 申请gpio资源 */
        ret = gpio_request(pdata->gpio_num, pdata->label);
        if (ret) {
            dev_err(dev, "failed to request gpio number %d\n",
                    pdata->gpio_num);
            goto fail2;
        }
        /* 设置gpio的方向(输出) */
        ret = gpio_direction_output(pdata->gpio_num, 0);
        if (ret) {
            dev_err(dev, "failed to set gpio direction for output\n");
            goto fail3;
        }
        /* 在sysfs中导出gpio(方向不能改变) */
        ret = gpio_export(pdata->gpio_num, false);
        if (ret) {
            dev_err(dev, "failed to export gpio %d\n", pdata->gpio_num);
            goto fail3;
        }
        /* 设置gpio电平值(高电平) */
        gpio_set_value(pdata->gpio_num, 1);
    }

    ddata->gpio_state = false;
    ddata->pdata = pdata;
    platform_set_drvdata(pdev, ddata);

    ret = sysfs_create_group(&dev->kobj, &attr_grp);
    if (ret) {
        dev_err(dev, "failed to create sysfs files\n");
        goto fail3;
    }

    printk("[%s]==========gpio_probe over==========\n", __func__);
    return 0;

    fail3:
    gpio_free(pdata->gpio_num);
    fail2:
    kfree(pdata);
    fail1:
    kfree(ddata);
    return ret;
}

static int gpio_remove(struct platform_device *pdev)
{
    struct gpio_drvdata *ddata = platform_get_drvdata(pdev);
    struct gpio_platform_data *pdata = ddata->pdata;

    sysfs_remove_group(&pdev->dev.kobj, &attr_grp);
    /* 释放已经申请的gpio资源 */
    if (gpio_is_valid(pdata->gpio_num))
        gpio_free(pdata->gpio_num);

    kfree(pdata);
    pdata = NULL;

    kfree(ddata);
    ddata = NULL;

    return 0;
}

static struct of_device_id device_match_table[] = {
    { .compatible = "dev-gpio",},
    { },
};
MODULE_DEVICE_TABLE(of, device_match_table);

static struct platform_driver dev_gpio_driver = {
    .probe = gpio_probe,
    .remove = gpio_remove,
    .driver = {
        .name = "dev-gpio",
        .owner = THIS_MODULE,
        .of_match_table = device_match_table,
    },
};

module_platform_driver(dev_gpio_driver);

MODULE_AUTHOR("HLY");
MODULE_LICENSE("GPL v2");

当驱动模块加载的时候,由于嵌入了platform_driver这个驱动框架中,所以看起来复杂一点,实际上根据上面的注释进行参考即可:

  • 需要获取要使用的GPIO号,然后需要向系统申请使用GPIO资源
  • 资源申请成功后,我们需要设置GPIO的方向(输入或者输出),
  • 此外,还能使用gpio_export()函数在sysfs中导出GPIO,导出的好处在于可以方便地debug代码,
  • 当驱动模块卸载时,需要将已经申请的GPIO资源进行释放掉

另外,在设备节点中导出了ctrl和gpio属性文件,便可以很方便地在应用层进行设备的GPIO控制了。

本文地址:https://www.cnblogs.com/schips/p/linux_subsystem_using_gpio_ss.html

若在页首无特别声明,本篇文章由 Schips 经过整理后发布。
博客地址:https://www.cnblogs.com/schips/

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容