imx6ull-驱动开发篇11——gpio子系统

目录

前言

[gpio 子系统简介](#gpio 子系统简介)

[I.MX6ULL 的 gpio 子系统驱动](#I.MX6ULL 的 gpio 子系统驱动)

[设备树中的 gpio 信息](#设备树中的 gpio 信息)

[GPIO 驱动程序简介](#GPIO 驱动程序简介)

[of_device_id 匹配表](#of_device_id 匹配表)

gpio-mxc.c文件

mxc_gpio_probe函数

[mxc_gpio_port 结构体](#mxc_gpio_port 结构体)

[mxc_gpio_get_hw 函数](#mxc_gpio_get_hw 函数)

imx35_gpio_hwdata结构体

bgpio_init函数

[gpio_chip 结构体](#gpio_chip 结构体)

gpiochip_add函数

[gpio 子系统 API 函数](#gpio 子系统 API 函数)

[设备树中添加 gpio 节点模板](#设备树中添加 gpio 节点模板)

[创建 test 设备节点](#创建 test 设备节点)

[添加 pinctrl 信息](#添加 pinctrl 信息)

[添加 GPIO 属性信息](#添加 GPIO 属性信息)


前言

在上一讲内容里,驱动开发篇10------pinctrl 子系统,我们已经详细地讲解了pinctrl子系统。

这一讲,我们继续学习gpio子系统,熟悉原理、掌握常用的API函数。

gpio 子系统简介

GPIO子系统架构GPIO子系统采用分层设计,主要包含以下组件:

  • GPIO芯片驱动层:直接操作硬件寄存器
  • GPIO核心层:提供通用接口和框架
  • GPIO用户接口层:向用户空间和内核其他模块提供API

gpio 子系统,用于初始化 GPIO 并且提供相应的 API 函数,比如设置 GPIO为输入输出,读取 GPIO 的值等。

cpp 复制代码
// 申请GPIO
int gpio_request(unsigned gpio, const char *label);

// 设置方向
int gpio_direction_input(unsigned gpio);
int gpio_direction_output(unsigned gpio, int value);

// 读写操作
int gpio_get_value(unsigned gpio);
void gpio_set_value(unsigned gpio, int value);

// 释放GPIO
void gpio_free(unsigned gpio);

gpio 子系统的主要目的就是方便驱动开发者使用 gpio,驱动开发者在设备树中添加 gpio 相关信息,然后就可以在驱动程序中使用 gpio 子系统提供的 API函数来操作 GPIO,。

Linux 内核向驱动开发者屏蔽掉了 GPIO 的设置过程,极大地方便了驱动开发者使用 GPIO。

I.MX6ULL 的 gpio 子系统驱动

设备树中的 gpio 信息

我们使用正点原子的I.MX6ULL-ALPHA 开发板,以SD 卡的检测引脚为例:

打开 imx6ull-alientek-emmc.dts,有如下代码:

cpp 复制代码
pinctrl_hog_1: hoggrp-1 {
    fsl,pins = <
        MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059 /* SD1 CD */
......
    >;
};

将 UART1_RTS_B 这个 PIN 复用为 GPIO1_IO19,并且设置电气属性。

SD 卡连接在I.MX6ULL 的 usdhc1 接口上,在 imx6ull-alientek-emmc.dts 中找到名为"usdhc1"的节点,这个节点就是 SD 卡设备节点,如下所示:

cpp 复制代码
&usdhc1 {
    /* 引脚控制状态名称,对应不同时钟频率下的配置 */
    pinctrl-names = "default", "state_100mhz", "state_200mhz";
    
    /* 各状态对应的具体引脚配置组 */
    pinctrl-0 = <&pinctrl_usdhc1>;        // 默认状态引脚配置
    pinctrl-1 = <&pinctrl_usdhc1_100mhz>; // 100MHz时钟下的引脚配置
    pinctrl-2 = <&pinctrl_usdhc1_200mhz>; // 200MHz时钟下的引脚配置
    // pinctrl-3 = <&pinctrl_hog_1>;      // 注释掉的备用配置

    /* SD卡检测信号配置 */
    cd-gpios = <&gpio1 19 GPIO_ACTIVE_LOW>; // 使用GPIO1_IO19作为卡检测引脚,低电平有效
    
    /* 电源管理相关配置 */
    keep-power-in-suspend;                 // 在挂起状态保持电源
    enable-sdio-wakeup;                    // 允许SDIO唤醒系统
    
    /* 电源供应配置 */
    vmmc-supply = <&reg_sd1_vmmc>;        // 指定3.3V电源调节器
    
    /* 设备状态 */
    status = "okay";                      // 启用该设备
};

其中,属性"cd-gpios"描述了 SD 卡的 CD 引脚使用的哪个 IO。

cpp 复制代码
  /* SD卡检测信号配置 */
    cd-gpios = <&gpio1 19 GPIO_ACTIVE_LOW>; // 使用GPIO1_IO19作为卡检测引脚,低电平有效
  • "&gpio1"表示 CD 引脚所使用的 IO 属于 GPIO1 组,
  • "19" 表示 GPIO1 组的第 19 号 IO,通过这两个值 SD 卡驱动程序就知道 CD 引脚使用了 GPIO1_IO19这 GPIO。
  • "GPIO_ACTIVE_LOW"表示低电平有效,如果改为"GPIO_ACTIVE_HIGH"就表示高电平有效。

根据上面这些信息, SD 卡驱动程序就可以使用GPIO1_IO19 来检测 SD 卡的 CD 信号。

打开 imx6ull.dtsi,关于GPIO1节点有如下所示内容:

cpp 复制代码
gpio1: gpio@0209c000 {
    compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";  // 设备兼容性标识,支持i.MX6UL和i.MX35的GPIO驱动
    reg = <0x0209c000 0x4000>;                        // 寄存器物理地址范围:基地址0x0209c000,长度16KB
    
    // 中断配置:两个中断号,均采用高电平触发
    interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>,    // 第一个中断,SPI中断号66
                 <GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>;    // 第二个中断,SPI中断号67
    
    gpio-controller;                                  // 声明为GPIO控制器
    #gpio-cells = <2>;                                // 使用GPIO时需要2个参数:引脚号和标志位
    
    interrupt-controller;                             // 同时作为中断控制器
    #interrupt-cells = <2>;                           // 中断说明符需要2个参数:GPIO号和中断类型
};

gpio1 节点信息描述了 GPIO1 控制器的所有信息,重点就是 GPIO1 外设寄存器基地址以及兼容 属性 。

关 于 I.MX 系 列 SOC 的 GPIO 控 制 器 绑 定 信 息 请 查 看 文档: Documentation/devicetree/bindings/gpio/ fsl-imx-gpio.txt

reg 属性设置了 GPIO1 控制器的寄存器基地址为 0X0209C000:

cpp 复制代码
reg = <0x0209c000 0x4000>; 

"#gpio-cells"属性:有两个 cell:

cpp 复制代码
#gpio-cells = <2>;  
  • 第一个 cell 为 GPIO 编号,比如"&gpio1 3"就表示 GPIO1_IO03。
  • 第二个 cell 表示GPIO 极 性 , 如 果 为 0(GPIO_ACTIVE_HIGH) 的 话 表 示 高 电 平 有 效 , 如 果 为1(GPIO_ACTIVE_LOW)的话表示低电平有效。

GPIO 驱动程序简介

of_device_id 匹配表

I.MX6ULL的 GPIO 驱动文件drivers/gpio/gpio-mxc.c,有如下所示 of_device_id 匹配表:

cpp 复制代码
/* 
 * i.MX系列GPIO控制器兼容性匹配表
 * 用于设备树匹配和驱动初始化
 */
static const struct of_device_id mxc_gpio_dt_ids[] = {
    /* i.MX1系列GPIO兼容性定义 */
    { 
        .compatible = "fsl,imx1-gpio",                     // 设备树兼容性字符串
        .data = &mxc_gpio_devtype[IMX1_GPIO],              // 指向IMX1的GPIO设备类型数据
    },
    
    /* i.MX21系列GPIO兼容性定义 */  
    {
        .compatible = "fsl,imx21-gpio",                    // i.MX21兼容字符串
        .data = &mxc_gpio_devtype[IMX21_GPIO],             // IMX21设备类型数据指针
    },
    
    /* i.MX31系列GPIO兼容性定义 */
    {
        .compatible = "fsl,imx31-gpio",                    // i.MX31兼容字符串
        .data = &mxc_gpio_devtype[IMX31_GPIO],             // IMX31设备类型数据指针
    },
    
    /* i.MX35系列GPIO兼容性定义 */
    {
        .compatible = "fsl,imx35-gpio",                    // i.MX35兼容字符串
        .data = &mxc_gpio_devtype[IMX35_GPIO],             // IMX35设备类型数据指针
    },
    
    /* 结束标记 */
    { /* sentinel */ }
};

其中,compatible 值为"fsl,imx35-gpio"的代码,和 gpio1 的 compatible 属性匹配,因此 gpio-mxc.c 就是 I.MX6ULL 的 GPIO 控制器驱动文件。

gpio-mxc.c文件

gpio-mxc.c 文件中有如下所示内容:

cpp 复制代码
/*
 * i.MX GPIO平台驱动注册结构体
 * 用于向内核注册GPIO控制器驱动
 */
static struct platform_driver mxc_gpio_driver = {
    .driver = {
        .name = "gpio-mxc",                    /* 驱动名称,用于/sys/bus/platform/drivers/目录 */
        .of_match_table = mxc_gpio_dt_ids,     /* 设备树匹配表,指向之前定义的兼容性表 */
    },
    .probe = mxc_gpio_probe,                   /* 设备探测函数,匹配成功后调用 */
    .id_table = mxc_gpio_devtype,              /* 设备ID表,用于非设备树的平台设备匹配 */
};

GPIO 驱动也是个平台设备驱动,因此当设备树中的设备节点与驱动的of_device_id 匹配以后 probe 函数就会执行,在这里就是mxc_gpio_probe函数。

mxc_gpio_probe函数

mxc_gpio_probe函数就是I.MX6ULL 的 GPIO 驱动入口函数。

cpp 复制代码
/*
 * i.MX GPIO控制器探测函数
 * 完成GPIO控制器的硬件初始化和驱动注册
 */
static int mxc_gpio_probe(struct platform_device *pdev)
{
    struct device_node *np = pdev->dev.of_node;  // 获取设备树节点
    struct mxc_gpio_port *port;                 // GPIO端口数据结构
    struct resource *iores;                     // IO资源指针
    int irq_base;                               // 中断号基址
    int err;                                    // 错误码

    // 获取硬件类型信息
    mxc_gpio_get_hw(pdev);

    // 分配端口内存空间
    port = devm_kzalloc(&pdev->dev, sizeof(*port), GFP_KERNEL);
    if (!port)
        return -ENOMEM;

    /*----- 硬件资源初始化 -----*/
    // 获取并映射寄存器资源
    iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    port->base = devm_ioremap_resource(&pdev->dev, iores);
    if (IS_ERR(port->base))
        return PTR_ERR(port->base);

    // 获取中断资源
    port->irq_high = platform_get_irq(pdev, 1);  // 高16位GPIO中断
    port->irq = platform_get_irq(pdev, 0);       // 基础中断
    if (port->irq < 0)
        return port->irq;

    /*----- 硬件初始化 -----*/
    // 禁用中断并清除状态
    writel(0, port->base + GPIO_IMR);    // 中断屏蔽寄存器
    writel(~0, port->base + GPIO_ISR);   // 中断状态寄存器

    /*----- 中断处理配置 -----*/
    if (mxc_gpio_hwtype == IMX21_GPIO) {
        // i.MX21系列的特殊处理
        irq_set_chained_handler(port->irq, mx2_gpio_irq_handler);
    } else {
        // i.MX3及后续系列的通用处理
        irq_set_chained_handler(port->irq, mx3_gpio_irq_handler);
        irq_set_handler_data(port->irq, port);
        
        // 高16位GPIO中断配置
        if (port->irq_high > 0) {
            irq_set_chained_handler(port->irq_high, mx3_gpio_irq_handler);
            irq_set_handler_data(port->irq_high, port);
        }
    }

    /*----- GPIO控制器初始化 -----*/
    // 使用gpiolib的通用后端初始化
    err = bgpio_init(&port->bgc, &pdev->dev, 4,
                    port->base + GPIO_PSR,    // 引脚状态寄存器
                    port->base + GPIO_DR,     // 数据寄存器
                    NULL,
                    port->base + GPIO_GDIR,   // 方向寄存器
                    NULL, 0);
    if (err)
        goto out_bgio;

    // 设置GPIO芯片特性
    port->bgc.gc.to_irq = mxc_gpio_to_irq;    // 转换GPIO到IRQ的函数
    port->bgc.gc.base = (pdev->id < 0) ?      // GPIO编号基址
                      of_alias_get_id(np, "gpio") * 32 : 
                      pdev->id * 32;

    // 注册GPIO控制器
    err = gpiochip_add(&port->bgc.gc);
    if (err)
        goto out_bgpio_remove;

    /*----- 中断域配置 -----*/
    // 分配中断描述符
    irq_base = irq_alloc_descs(-1, 0, 32, numa_node_id());
    if (irq_base < 0) {
        err = irq_base;
        goto out_gpiochip_remove;
    }

    // 创建IRQ域
    port->domain = irq_domain_add_legacy(np, 32, irq_base, 0,
                                       &irq_domain_simple_ops, NULL);
    if (!port->domain) {
        err = -ENODEV;
        goto out_irqdesc_free;
    }

    // 初始化通用中断芯片
    mxc_gpio_init_gc(port, irq_base);

    // 将端口添加到全局列表
    list_add_tail(&port->node, &mxc_gpio_ports);

    return 0;

    /*----- 错误处理路径 -----*/
    ... 
}

让我们来详细分析一下这段代码:

mxc_gpio_port 结构体

定义一个结构体指针 port,结构体类型为 mxc_gpio_port

cpp 复制代码
struct mxc_gpio_port *port;                 // GPIO端口数据结构

gpio-mxc.c 的重点工作就是维护 mxc_gpio_port, mxc_gpio_port 就是对 I.MX6ULL GPIO 的抽象。

mxc_gpio_port 结构体定义如下:

cpp 复制代码
/*
 * i.MX GPIO端口数据结构
 * 描述一个GPIO控制器的所有硬件和软件状态
 */
struct mxc_gpio_port {
    struct list_head node;        // 链表节点,用于将多个GPIO端口串联到全局列表
    
    void __iomem *base;           // GPIO寄存器基地址指针(I/O映射后的虚拟地址)
    
    int irq;                      // 主中断号(处理GPIO0-15中断)
    int irq_high;                 // 高16位中断号(处理GPIO16-31中断,可选)
    
    struct irq_domain *domain;    // IRQ域,用于GPIO中断号映射管理
    
    struct bgpio_chip bgc;        // 通用GPIO控制器数据结构,包含:
                                 // - gpio_chip 基础结构
                                 // - 寄存器访问方法
                                 // - 状态缓存等
    
    u32 both_edges;              // 双边沿触发状态标志位,用于:
                                 // - 记录配置为双边沿触发的GPIO位
                                 // - 实现模拟双边沿触发功能
};

/* 
 * 结构体功能详解:
 * 1. 硬件资源管理:
 *    - base: 提供对GPIO控制寄存器的访问
 *    - irq/irq_high: 管理两组GPIO中断线
 *
 * 2. 中断系统集成:
 *    - domain: 将GPIO引脚映射到Linux中断号
 *    - both_edges: 特殊处理双边沿触发模式
 *
 * 3. GPIO核心框架:
 *    - bgpio_chip: 集成到Linux GPIO子系统
 *      * 包含gpio_chip基本操作
 *      * 实现方向控制/数值读写等标准接口
 *
 * 4. 系统管理:
 *    - node: 允许系统遍历所有已注册的GPIO控制器
 *
 * 典型使用场景:
 * - 每个i.MX处理器的GPIO bank对应一个port实例
 * - 通过base+偏移访问具体寄存器
 * - 通过bgc提供标准GPIO操作接口
 */
mxc_gpio_get_hw 函数

调用 mxc_gpio_get_hw 函数获取 gpio 的硬件相关数据,其实就是 gpio 的寄存器组。

cpp 复制代码
    // 获取硬件类型信息
    mxc_gpio_get_hw(pdev);

函数 mxc_gpio_get_hw 里面有如下代码:

cpp 复制代码
/*
 * mxc_gpio_get_hw - 获取i.MX GPIO控制器硬件类型和配置数据
 * @pdev: 平台设备指针
 *
 * 根据设备树匹配结果确定具体的GPIO硬件类型,并设置对应的硬件配置数据。
 * 该函数在probe阶段被调用,用于初始化硬件相关参数。
 */
static void mxc_gpio_get_hw(struct platform_device *pdev)
{
    // 通过设备树兼容性字符串匹配硬件类型
    const struct of_device_id *of_id = 
        of_match_device(mxc_gpio_dt_ids, &pdev->dev);
    enum mxc_gpio_hwtype hwtype;

    /* 
     * 硬件类型判断逻辑:
     * 根据of_match_table中设置的.data指针确定具体型号
     */
    if (of_id && of_id->data) {
        hwtype = (enum mxc_gpio_hwtype)of_id->data;
    } else {
        /* 默认回退到最基础的类型 */
        hwtype = IMX1_GPIO;
    }

    // 根据硬件类型选择对应的配置数据结构
    if (hwtype == IMX35_GPIO)
        mxc_gpio_hwdata = &imx35_gpio_hwdata;      // i.MX35系列专用配置
    else if (hwtype == IMX31_GPIO)
        mxc_gpio_hwdata = &imx31_gpio_hwdata;     // i.MX31系列专用配置
    else
        mxc_gpio_hwdata = &imx1_imx21_gpio_hwdata; // i.MX1/i.MX21通用配置

    // 设置全局硬件类型标识
    mxc_gpio_hwtype = hwtype;
}

mxc_gpio_hwdata 是个全局变量,如果硬件类型是 IMX35_GPIO 的话,设置mxc_gpio_hwdat 为 imx35_gpio_hwdata

对于 I.MX6ULL 而言,硬件类型就是 IMX35_GPIO。

imx35_gpio_hwdata结构体

imx35_gpio_hwdata是个结构体变量,描述了 GPIO 寄存器组,内容如下:

cpp 复制代码
/*
 * i.MX35系列GPIO硬件配置数据结构
 * 定义i.MX35处理器的GPIO控制器寄存器布局和中断触发类型配置
 */
static struct mxc_gpio_hwdata imx35_gpio_hwdata = {
    /* GPIO寄存器偏移地址定义 */
    .dr_reg        = 0x00,   // 数据寄存器:存储GPIO输入/输出值
    .gdir_reg      = 0x04,   // 方向寄存器:配置GPIO输入/输出模式
    .psr_reg       = 0x08,   // 引脚状态寄存器:读取GPIO当前电平状态
    .icr1_reg      = 0x0c,   // 中断配置寄存器1:配置GPIO0-15中断触发方式
    .icr2_reg      = 0x10,   // 中断配置寄存器2:配置GPIO16-31中断触发方式
    .imr_reg       = 0x14,   // 中断屏蔽寄存器:使能/禁用GPIO中断
    .isr_reg       = 0x18,   // 中断状态寄存器:查看触发的中断源
    .edge_sel_reg  = 0x1c,   // 边沿选择寄存器:用于双边沿触发配置

    /* 中断触发类型配置值 */
    .low_level     = 0x00,   // 低电平触发配置值
    .high_level    = 0x01,   // 高电平触发配置值
    .rise_edge     = 0x02,   // 上升沿触发配置值
    .fall_edge     = 0x03,   // 下降沿触发配置值
};

imx35_gpio_hwdata 结构体就是 GPIO 寄存器组结构。

这样我们后面就可以通过mxc_gpio_hwdata 这个全局变量来访问 GPIO 的相应寄存器了。

bgpio_init函数

bgpio_init 函数主要任务就是初始化 bgc->gc 。 bgpio_init 里 面 有 三 个 setup 函 数 : bgpio_setup_io 、bgpio_setup_accessors 和 bgpio_setup_direction。

这三个函数就是初始化 bgc->gc 中的各种有关GPIO 的操作,比如输出,输入等等。

cpp 复制代码
    /*----- GPIO控制器初始化 -----*/
    // 使用gpiolib的通用后端初始化
    err = bgpio_init(&port->bgc, &pdev->dev, 4,
                    port->base + GPIO_PSR,    // 引脚状态寄存器
                    port->base + GPIO_DR,     // 数据寄存器
                    NULL,
                    port->base + GPIO_GDIR,   // 方向寄存器
                    NULL, 0);

bgpio_init函数第一个参数为 bgc,是 bgpio_chip 结构体指针。

bgc 既有对 GPIO 的操作函数,又有 I.MX6ULL 有关 GPIO的寄存器,那么只要得到 bgc 就可以对 I.MX6ULL 的 GPIO 进行操作。

gpio_chip 结构体

bgpio_chip结构体有个 gc 成员变量, gc 是个 gpio_chip 结构体类型的变量。 gpio_chip 结构体是抽象出来的GPIO 控制器。

gpio_chip 结构体如下所示(有缩减):

cpp 复制代码
/*
 * GPIO控制器抽象接口
 * 定义GPIO控制器的标准操作方法和属性
 */
struct gpio_chip {
    const char *label;          /* 控制器标签/名称,用于标识 */
    struct device *dev;         /* 关联的设备结构体 */
    struct module *owner;       /* 所属模块(用于引用计数) */
    struct list_head list;      /* 全局GPIO控制器链表节点 */

    /*----- 核心操作函数集 -----*/
    int (*request)(struct gpio_chip *chip, unsigned offset);      /* GPIO引脚申请 */
    void (*free)(struct gpio_chip *chip,  unsigned offset);        /* GPIO引脚释放 */
    
    /* 方向控制 */
    int (*get_direction)(struct gpio_chip *chip,  unsigned offset); /* 获取方向 */
    int (*direction_input)(struct gpio_chip *chip,  unsigned offset); /* 设为输入 */
    int (*direction_output)(struct gpio_chip *chip,
                          unsigned offset, int value); /* 设为输出 */
    
    /* 数值操作 */                        
    int (*get)(struct gpio_chip *chip,  unsigned offset);          /* 获取引脚值 */
    void (*set)(struct gpio_chip *chip,  unsigned offset, int value); /* 设置引脚值 */

......
};

可以看出, gpio_chip 大量的成员都是函数,这些函数就是 GPIO 操作函数。

gpiochip_add函数

调用函数gpiochip_add向Linux内核注册gpio_chip,也就是 port->bgc.gc。

cpp 复制代码
    // 注册GPIO控制器
    err = gpiochip_add(&port->bgc.gc);

注册完成以后我们就可以在驱动中使用 gpiolib 提供的各个 API 函数。

gpio 子系统 API 函数

对于驱动开发人员,设置好设备树以后就可以使用 gpio 子系统提供的 API 函数来操作指定的 GPIO, gpio 子系统向驱动开发人员屏蔽了具体的读写寄存器过程。

gpio 子系统提供的常用的 API 函数有下面几个:

函数名 功能描述
​gpio_request​ 申请GPIO引脚使用权(使用前必须调用)
​gpio_free​ 释放已申请的GPIO资源
​gpio_direction_input​ 将GPIO配置为输入模式
​gpio_direction_output​ 将GPIO配置为输出模式(可设置初始输出值)
​gpio_get_value​ 读取GPIO当前输入电平值
​gpio_set_value​ 设置GPIO输出电平值
cpp 复制代码
// 1. GPIO申请(必须首先调用)
int gpio_request(unsigned int gpio, const char *label);
/* 功能:申请GPIO引脚使用权
 * 参数:
 *   gpio  - 要申请的GPIO编号(通过of_get_named_gpio()从设备树获取)
 *   label - 自定义标识字符串(出现在/sys/kernel/debug/gpio中)
 * 返回:
 *   0成功,非零失败(-EBUSY表示已被占用)
 */

// 2. GPIO释放
void gpio_free(unsigned int gpio);
/* 功能:释放GPIO资源
 * 参数:
 *   gpio - 要释放的GPIO编号
 */

// 3. 设置为输入模式
int gpio_direction_input(unsigned int gpio);
/* 功能:配置GPIO为输入方向
 * 参数:
 *   gpio - 要配置的GPIO编号
 * 返回:
 *   0成功,负值表示错误
 */

// 4. 设置为输出模式(带初始值)
int gpio_direction_output(unsigned int gpio, int value);
/* 功能:配置GPIO为输出方向并设置初始电平
 * 参数:
 *   gpio  - 要配置的GPIO编号
 *   value - 初始输出值(0/1)
 * 返回:
 *   0成功,负值表示错误
 */

// 5. 读取GPIO值(输入模式使用)
#define gpio_get_value __gpio_get_value
int __gpio_get_value(unsigned int gpio);
/* 功能:读取GPIO当前电平值
 * 参数:
 *   gpio - 要读取的GPIO编号
 * 返回:
 *   0/1  - 实际读取的电平
 *   负值  - 读取失败
 */

// 6. 设置GPIO值(输出模式使用)
#define gpio_set_value __gpio_set_value
void __gpio_set_value(unsigned int gpio, int value);
/* 功能:设置GPIO输出电平
 * 参数:
 *   gpio  - 要设置的GPIO编号
 *   value - 要设置的值(0/1)
 */

使用举例:

cpp 复制代码
#include <linux/gpio.h>

// 假设从设备树获取的GPIO编号
#define LED_GPIO    123  // 输出用GPIO
#define KEY_GPIO    456  // 输入用GPIO

// GPIO初始化
void gpio_init(void)
{
    // 1. 申请GPIO
    gpio_request(LED_GPIO, "my_led");
    gpio_request(KEY_GPIO, "my_key");

    // 2. 设置方向
    gpio_direction_output(LED_GPIO, 0); // LED初始低电平
    gpio_direction_input(KEY_GPIO);     // 按键输入模式
}

// GPIO使用示例
void gpio_usage(void) 
{
    // 3. 输出控制(LED闪烁)
    gpio_set_value(LED_GPIO, 1); // LED亮
    gpio_set_value(LED_GPIO, 0); // LED灭

    // 4. 输入读取(按键检测)
    if (gpio_get_value(KEY_GPIO)) {
        printk("Key pressed!\n"); 
    }
}

// GPIO释放
void gpio_release(void)
{
    gpio_free(LED_GPIO);
    gpio_free(KEY_GPIO);
}

设备树中添加 gpio 节点模板

我们来学习一下如何创建 test 设备的 GPIO 节点。

创建 test 设备节点

在根节点"/"下创建 test 设备子节点,如下所示:

cpp 复制代码
test {
     /* 节点内容 */
};

添加 pinctrl 信息

在之前的文章里,pinctrl 子系统,我们创建了 pinctrl_test 节点,此节点描述了 test 设备所使用的 GPIO1_IO00 这个 PIN 的信息。

我们要将这节点添加到 test 设备节点中,如下所示:

cpp 复制代码
test {
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_test>;
    /* 其他节点内容 */
};

添加 GPIO 属性信息

在 test 节点中添加 GPIO 属性信息,表明 test 所使用的 GPIO 是哪个引脚。

添加完成以后如下所示:

cpp 复制代码
test {
      pinctrl-names = "default";
      pinctrl-0 = <&pinctrl_test>;
      gpio = <&gpio1 0 GPIO_ACTIVE_LOW>;
};

关于 pinctrl 子系统和 gpio 子系统就讲解到这里。

下一讲内容我们使用 pinctrl 和 gpio 子系统,来驱动 I.MX6ULL-ALPHA 开发板上的 LED 灯。

相关推荐
雾削木21 小时前
树莓派 ESPHome 固件编译与烧录全攻略(解决超时与串口识别问题)
驱动开发
春日见2 天前
win11 分屏设置
java·开发语言·驱动开发·docker·单例模式·计算机外设
DarkAthena2 天前
【GaussDB】手动编译不同python版本的psycopg2驱动以适配airflow
驱动开发·python·gaussdb
松涛和鸣2 天前
DAY66 SPI Driver for ADXL345 Accelerometer
linux·网络·arm开发·数据库·驱动开发
嵌入式郑工3 天前
# RK3576 平台 RTC 时钟调试全过程
linux·驱动开发·ubuntu
GS8FG3 天前
针对Linux,RK3568平台下,I2C驱动的一点小小的领悟
linux·驱动开发
一路往蓝-Anbo3 天前
第 4 篇:策略模式 (Strategy) —— 算法的热插拔艺术
网络·驱动开发·stm32·嵌入式硬件·算法·系统架构·策略模式
A-花开堪折3 天前
RK3568 Android 11 驱动开发(五):串口驱动适配
驱动开发
bandaoyu4 天前
【RDMA】rdma指令
驱动开发
2301_772204284 天前
LCD驱动开发:行场扫描与时序
驱动开发