嵌入式Linux中的 gpio、gpiod基本分析

简介: 嵌入式Linux中的 gpio、gpiod基本分析

GPIO 应该是每个嵌入式设备都避免不了的。最近在做项目的时候,也遇到这方面的问题,所以简单总结一下。

现在内核里面多了 gpiod 的来控制 gpio 口,相对于原来的形式,使用 gpiod 的好处是我们申请后不进行 free 也没有什么问题。但是你要是使用原来的方式后,一定要记得释放。不释放的话可能会有问题。

36c35506638142748ccab476edfd588d.png


#旧的 GPIO 使用实例


DTS 文件

det-gpios = <&gpio3 RK_PA6 IRQ_TYPE_EDGE_BOTH>;


驱动文件调用

    gc5025->det_pin = of_get_named_gpio_flags(node, "det-gpios", 0, &det_flags);
    camera_det_irq = gpio_to_irq(gc5025->det_pin);
    gc5025->det_value = gpio_get_value(gc5025->det_pin);
    /*判断注册终端*/
    if(camera_det_irq){
        if (gpio_request(gc5025->det_pin, "camera-irq-gpio")) {
            printk("gpio %d request failed!\n", gc5025->det_pin);
            gpio_free(gc5025->det_pin);
            return IRQ_NONE;
        }
        ret = request_irq(camera_det_irq, camera_det_irq_handler, IRQ_TYPE_EDGE_BOTH, "det-gpio", NULL);
        if (ret != 0) {
            free_irq(camera_det_irq, NULL);
            dev_err(dev, "Failed to request IRQ: %d\n", ret);
            return ret;
        }
    }


# 新的 GPIOD 文档


Linux 内核文档

https://www.kernel.org/doc/Documentation/gpio/consumer.txt


#头文件


我们需要包含头文件

#include <linux/gpio/consumer.h>


看头文件里面包含的函数列表

desc_to_gpio
devm_get_gpiod_from_chi
devm_gpiod_get
devm_gpiod_get_array
devm_gpiod_get_array_op
devm_gpiod_get_index
devm_gpiod_get_index_op
devm_gpiod_get_optional
devm_gpiod_put
devm_gpiod_put_array
fwnode_get_named_gpiod
gpio_to_desc
gpiod_cansleep
gpiod_count
gpiod_direction_input
gpiod_direction_output
gpiod_direction_output_
gpiod_export
gpiod_export_link
gpiod_get
gpiod_get_array
gpiod_get_array_optiona
gpiod_get_direction
gpiod_get_index
gpiod_get_index_optiona
gpiod_get_optional
gpiod_get_raw_value
gpiod_get_raw_value_can
gpiod_get_value
gpiod_get_value_canslee
gpiod_is_active_low
gpiod_put
gpiod_put_array
gpiod_set_array_value
gpiod_set_array_value_c
gpiod_set_debounce
gpiod_set_raw_array_val
gpiod_set_raw_array_val
gpiod_set_raw_value
gpiod_set_raw_value_can
gpiod_set_value
gpiod_set_value_canslee
gpiod_to_irq
gpiod_unexport


#获取 gpio 描述符和释放


使用一下两个函数获取 GPIO 设备,多个设备时需要附带 index 参数。函数返回一个 GPIO 描述符,或一个错误编码,可以使用 IS_ERR() 进行检查:

struct gpio_desc *gpiod_get(struct device *dev, const char *con_id,
                    enum gpiod_flags flags)
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_optional(struct device *dev,
                         const char *con_id,
                         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_descs *gpiod_get_array(struct device *dev,
                       const char *con_id,
                       enum gpiod_flags flags)


该函数返回一个GPIO描述结构体:

struct gpio_descs {
    unsigned int ndescs;
    struct gpio_desc *desc[];
}


一个GPIO描述符可以使用如下函数释放:

void gpiod_put(struct gpio_desc *desc)
void gpiod_put_array(struct gpio_descs *descs)


需要注意GPIO描述符被释放后不可再使用,而且不允许使用第一个函数来释放通过序列获取得到GPIO描述符。


#举个例子


#dts 文件

gc5025: gc5025@37 {
    status = "okay";
    compatible = "galaxycore,gc5025";
    reg = <0x37>;
    clock-frequency = <400000>;
    pinctrl-names = "default";
    pinctrl-0 = <&cif_clkout_m0>;
    clocks = <&cru SCLK_CIF_OUT>;
    clock-names = "xvclk";
    avdd-supply = <&vcc2v8_dvp>;
    dovdd-supply = <&vcc1v8_dvp>;
    dvdd-supply = <&vdd1v2_dvp>;
    reset-gpios = <&gpio3 RK_PA3 GPIO_ACTIVE_LOW>;
    pwdn-gpios = <&gpio0 RK_PA0 GPIO_ACTIVE_HIGH>;
    det-gpios = <&gpio3 RK_PA6 IRQ_TYPE_EDGE_BOTH>;
    rockchip,camera-module-index = <0>;
    rockchip,camera-module-facing = "front";
    rockchip,camera-module-name = "CMK-CW4191-FG1";
    rockchip,camera-module-lens-name = "CK5502";
    port {
      ucam_out: endpoint {
        remote-endpoint = <&mipi_in_ucam>;
        data-lanes = <1 2>;
      };
    };
  };


驱动文件调用:

    gc5025->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
    if (IS_ERR(gc5025->reset_gpio))
        dev_warn(dev, "Failed to get reset-gpios\n");
    gc5025->pwdn_gpio = devm_gpiod_get(dev, "pwdn", GPIOD_OUT_LOW);
    if (IS_ERR(gc5025->pwdn_gpio))
        dev_warn(dev, "Failed to get pwdn-gpios\n");
    /*新的GPIO子系统方式,这种方式不需要手动释放资源*/
    gc5025->det_gpio = devm_gpiod_get(dev, "det", GPIOD_OUT_LOW);
    if (IS_ERR(gc5025->det_gpio))


#GPIO 使用


#设置 GPIO 口方向

int gpiod_direction_input(struct gpio_desc *desc)
int gpiod_direction_output(struct gpio_desc *desc, int value)


#检查 GPIO 口是方向

int gpiod_get_direction(const struct gpio_desc *desc)


函数返回 GPIOF_DIR_IN 或者 GPIOF_DIR_OUT


#读取 GPIO 口电平

访问分为两种,一种是通过储存器读写实现的,这种操作属于原子操作,不需要等待,所以可以在中断处理程序中使用:

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


还有一种访问必须通过消息总线比如 I2C 或者 SPI,这种访问需要在总线访问队列中等待,所以可能进入睡眠,此类访问不能出现在 IRQ handler。可以使用如下函数分辨这些设备:

int gpiod_cansleep(const struct gpio_desc *desc)


使用如下函数读写:

int gpiod_get_value_cansleep(const struct gpio_desc *desc)
void gpiod_set_value_cansleep(struct gpio_desc *desc, int value)


#active-low和raw-value

active-low & raw value 有些设备采用低电平有效的方式输出逻辑信号。此时低电平输出 1,高电平输出 0。此时可以通过访问 raw_value 的方式来访问实际电路上的值,与逻辑处理无关:假设我们在DTS 里面这样设置

reset-gpios = <&gpio3 RK_PA3 GPIO_ACTIVE_LOW>;


然后我们这样调用

gpiod_set_value_cansleep(gc5025->reset_gpio, 1);


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

gpiod_set_value_cansleep(gc5025->reset_gpio, 0);


输出的是高电平

这几个函数如下:

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)


raw-value 的意思就是不在乎 DTS 里面的 ACTIVE,我 set 高电平,就是高电平。逻辑关系汇总如下:

Function (example) active-low property physical line
gpiod_set_raw_value(desc, 0); don’t care low
gpiod_set_raw_value(desc, 1); don’t care high
gpiod_set_value(desc, 0); default (active-high) low
gpiod_set_value(desc, 1); default (active-high) high
gpiod_set_value(desc, 0); active-low high
gpiod_set_value(desc, 1); active-low low


可以使用如下函数判断一个设备是否是低电平有效的设备。

int gpiod_is_active_low(const struct gpio_desc *desc)


#设置多个输出


这个没使用过 使用如下函数设置一组设备的输出值

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 系统使用基于标号的结构而不是基于描述符。可以使用如下两个函数进行相互转换:

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


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


#和中断IRQ相关


使用如下函数获取一个 GPIO 设备对应的 IRQ 中断号

int gpiod_to_irq(const struct gpio_desc *desc)


返回值时一个 IRQ number,或者一个负数的错误代码。得到的中断号可以传递给函数 request_irq(),free_irq().

#举例子

    /*新的GPIO子系统方式,这种方式不需要手动释放资源*/
  gc5025->det_gpio = devm_gpiod_get(dev, "det", GPIOD_OUT_LOW);
  if (IS_ERR(gc5025->det_gpio))
    dev_warn(dev, "Failed to get det-gpios\n");
  camera_det_irq = gpiod_to_irq(gc5025->det_gpio);
  /*新gpio子系统转成旧gpio子系统*/
  gc5025->det_pin = desc_to_gpio(gc5025->det_gpio);
  /*读取上电gpio电平*/
  gc5025->det_value = gpio_get_value(gc5025->det_pin);
  /*判断注册终端*/
  if(camera_det_irq){
        ret = request_irq(camera_det_irq, camera_det_irq_handler, IRQ_TYPE_EDGE_BOTH, "det-gpio", NULL);
        if (ret != 0) {
      free_irq(camera_det_irq, NULL);
            dev_err(dev, "Failed to request IRQ: %d\n", ret);
      return ret;
    }
  }


#调试


移植驱动阶段或者调试阶段的工程中,难免想知道当前 gpio 的电平状态。当然很 easy。万用表戳上去不就行了。是啊!硬件工程师的思维。作为软件工程师自然是要软件的方法。下面介绍两个 api 接口。自己摸索使用吧。点到为止。

static inline int gpio_export(unsigned gpio, bool direction_may_change);
static inline int gpio_export_link(struct device *dev, const char *name, unsigned gpio);


在你的 driver 中调用以上 api 后,编译下载。去 /sys/class/gpio 目录看看有什么发现。

c78748fa5cd55af89701f18bc9240c0b.png

目录
相关文章
|
3月前
|
存储 IDE Unix
Linux 内核源代码情景分析(四)(上)
Linux 内核源代码情景分析(四)
33 1
Linux 内核源代码情景分析(四)(上)
|
3月前
|
存储 Linux 块存储
Linux 内核源代码情景分析(三)(下)
Linux 内核源代码情景分析(三)
36 4
|
3月前
|
Linux C语言
深度探索Linux操作系统 —— 编译过程分析
深度探索Linux操作系统 —— 编译过程分析
28 2
|
3月前
|
存储 Unix Linux
Linux 内核源代码情景分析(四)(下)
Linux 内核源代码情景分析(四)
23 2
|
2月前
|
存储 传感器 Linux
STM32微控制器为何不适合运行Linux系统的分析
总的来说,虽然技术上可能存在某些特殊情况下将Linux移植到高端STM32微控制器上的可能性,但从资源、性能、成本和应用场景等多个方面考虑,STM32微控制器不适合运行Linux系统。对于需要运行Linux的应用,更适合选择ARM Cortex-A系列处理器的开发平台。
232 0
|
2月前
|
Linux 测试技术 芯片
在Linux中使用GPIO线【ChatGPT】
在Linux中使用GPIO线【ChatGPT】
|
3月前
|
存储 算法 Unix
Linux 内核源代码情景分析(四)(中)
Linux 内核源代码情景分析(四)
44 0
|
3月前
|
存储 算法 Unix
Linux 内核源代码情景分析(三)(中)
Linux 内核源代码情景分析(三)
33 0
|
消息中间件 缓存 Unix
[面试必备]嵌入式Linux内核开发必须了解的三十道题
[面试必备]嵌入式Linux内核开发必须了解的三十道题
|
Linux
嵌入式Linux QT开发之如何实现获取磁盘空间大小的应用逻辑
嵌入式Linux QT开发之如何实现获取磁盘空间大小的应用逻辑
241 0