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 灯。

相关推荐
sukalot44 分钟前
window显示驱动开发—可选内容保护 DDI 函数
驱动开发
sukalot11 小时前
window显示驱动开发—内容保护的资源
驱动开发
车载操作系统---攻城狮19 小时前
[驱动开发篇] Can通信进阶 --- CanFD 的三次采样
驱动开发
sukalot1 天前
window显示驱动开发—将加密会话与 DirectX 视频加速器 2.0 解码器配合使用
数据库·驱动开发·音视频
努力自学的小夏1 天前
RK3568 Linux驱动学习——字符设备驱动开发
linux·驱动开发·笔记·学习
sukalot1 天前
window显示驱动开发—内容保护 DDI
驱动开发
努力做小白1 天前
Linux驱动25 --- RkMedia音频API使用&&增加 USB 音视频设备
linux·驱动开发·单片机·嵌入式硬件·音视频
花小璇学linux2 天前
imx6ull-驱动开发篇8——设备树常用 OF 操作函数
linux·驱动开发·imx6ull·嵌入式软件
花小璇学linux2 天前
imx6ull-驱动开发篇7——如何编写设备树
linux·驱动开发·imx6ull·嵌入式软件