Linux学习笔记--GPIO控制器驱动

1. 核心数据结构

gpio_chip 结构体

这是GPIO子系统的核心结构,代表一个GPIO控制器:

复制代码
struct gpio_chip {
    const char *label;                    // 控制器名称
    int (*direction_input)(...);          // 设置为输入模式
    int (*direction_output)(...);         // 设置为输出模式
    int (*get)(...);                      // 读取GPIO值
    void (*set)(...);                     // 设置GPIO值
    struct device *parent;                // 父设备
    struct module *owner;                 // 模块所有者
    int base;                            // 起始GPIO编号
    u16 ngpio;                           // GPIO数量
    // ... 其他成员
};

2. 全局变量

复制代码
static struct gpio_chip *g_virt_gpio;  // GPIO控制器实例
static int g_gpio_val = 0;            // 虚拟的GPIO状态寄存器

3. GPIO操作函数实现

方向设置函数

复制代码
static int virt_gpio_direction_output(struct gpio_chip *gc,
        unsigned offset, int val)
{
    printk("set pin %d as output %s\n", offset, val ? "high" : "low");
    return 0;
}

static int virt_gpio_direction_input(struct gpio_chip *chip,
                    unsigned offset)
{
    printk("set pin %d as input\n", offset);
    return 0;
}

电平读写函数

复制代码
static int virt_gpio_get_value(struct gpio_chip *gc, unsigned offset)
{
    int val;
    val = (g_gpio_val & (1<<offset)) ? 1 : 0;  // 从虚拟寄存器读取
    printk("get pin %d, it's val = %d\n", offset, val);
    return val;
}

static void virt_gpio_set_value(struct gpio_chip *gc,
        unsigned offset, int val)
{
    printk("set pin %d as %d\n", offset, val);
    if (val)
        g_gpio_val |= (1 << offset);    // 设置对应位为1
    else
        g_gpio_val &= ~(1 << offset);   // 设置对应位为0
}

4. 平台驱动探测函数

复制代码
static int virtual_gpio_probe(struct platform_device *pdev)
{
    int ret;
    unsigned int val;
    
    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    
    /* 1. 分配gpio_chip */
    g_virt_gpio = devm_kzalloc(&pdev->dev, sizeof(*g_virt_gpio), GFP_KERNEL);
    
    /* 2. 设置gpio_chip */
    
    /* 2.1 设置函数 */
    g_virt_gpio->label = pdev->name;
    g_virt_gpio->direction_output = virt_gpio_direction_output;
    g_virt_gpio->direction_input  = virt_gpio_direction_input;
    g_virt_gpio->get = virt_gpio_get_value;
    g_virt_gpio->set = virt_gpio_set_value;
    
    g_virt_gpio->parent = &pdev->dev;
    g_virt_gpio->owner = THIS_MODULE;
    
    /* 2.2 设置base、ngpio值 */
    g_virt_gpio->base = -1;  // 自动分配起始编号
    ret = of_property_read_u32(pdev->dev.of_node, "ngpios", &val);
    g_virt_gpio->ngpio = val;  // 从设备树读取GPIO数量
    
    /* 3. 注册gpio_chip */
    ret = devm_gpiochip_add_data(&pdev->dev, g_virt_gpio, NULL);
    
    return 0;
}
  • pdev:平台设备指针,包含设备信息和设备树节点

  • ret:用于存储函数返回值,检查错误

  • val:临时变量,用于从设备树读取数值

分配 gpio_chip 内存

复制代码
g_virt_gpio = devm_kzalloc(&pdev->dev, sizeof(*g_virt_gpio), GFP_KERNEL);

详细解释:

  • devm_kzalloc:设备资源管理的内存分配函数

    • 自动管理内存生命周期

    • 设备卸载时自动释放内存

    • 避免内存泄漏

  • &pdev->dev:父设备指针,用于资源管理

  • sizeof(*g_virt_gpio) :分配 gpio_chip 结构体大小的内存

  • GFP_KERNEL:内存分配标志,表示在内核空间分配可睡眠的内存

等效的传统代码:

复制代码
// 如果没有使用devm_系列函数
g_virt_gpio = kzalloc(sizeof(*g_virt_gpio), GFP_KERNEL);
// 需要在remove函数中手动释放:kfree(g_virt_gpio);

设置 gpio_chip 操作函数

复制代码
/* 2.1 设置函数 */
g_virt_gpio->label = pdev->name;
g_virt_gpio->direction_output = virt_gpio_direction_output;
g_virt_gpio->direction_input  = virt_gpio_direction_input;
g_virt_gpio->get = virt_gpio_get_value;
g_virt_gpio->set = virt_gpio_set_value;

每个字段的作用:

字段 作用 对应函数
label GPIO控制器名称 使用平台设备名称
direction_output 设置GPIO为输出模式 virt_gpio_direction_output
direction_input 设置GPIO为输入模式 virt_gpio_direction_input
get 读取GPIO电平值 virt_gpio_get_value
set 设置GPIO电平值 virt_gpio_set_value

设置设备关系信息

复制代码
g_virt_gpio->parent = &pdev->dev;
g_virt_gpio->owner = THIS_MODULE;
  • parent:设置父设备,建立设备层次关系

    • 在sysfs中体现为:/sys/devices/.../virtual_gpio/gpio/
  • owner:模块所有者,用于模块引用计数

    • 防止模块在使用中被卸载

配置GPIO数量和基址

复制代码
/* 2.2 设置base、ngpio值 */
g_virt_gpio->base = -1;  // 自动分配起始编号
ret = of_property_read_u32(pdev->dev.of_node, "ngpios", &val);
g_virt_gpio->ngpio = val;  // 从设备树读取GPIO数量

详细解释:

base = -1

  • 作用:让系统自动分配GPIO起始编号

  • 示例:如果系统已有GPIO 0-479,则自动分配480为起始

  • 手动指定 :也可以设置具体值,如 base = 480

of_property_read_u32

  • 函数:从设备树节点读取32位无符号整数属性

  • 参数

    • pdev->dev.of_node:设备树节点

    • "ngpios":要读取的属性名

    • &val:存储读取值的变量指针

对应的设备树配置:
复制代码
virtual_gpio {
    compatible = "100ask,virtual_gpio";
    ngpios = <16>;  // 这里读取的就是这个值
    status = "okay";
};

注册GPIO控制器

复制代码
/* 3. 注册gpio_chip */
ret = devm_gpiochip_add_data(&pdev->dev, g_virt_gpio, NULL);

详细解释:

devm_gpiochip_add_data

  • 作用:向GPIO子系统注册GPIO控制器

  • 参数

    • &pdev->dev:父设备

    • g_virt_gpio:要注册的gpio_chip结构体

    • NULL:私有数据指针(这里没有使用)

注册后的效果:

  1. 创建sysfs接口/sys/class/gpio/gpiochipXXX/

  2. 分配GPIO编号:如 GPIO 480-495(如果ngpios=16)

  3. 可用性:其他驱动可以通过标准GPIO API使用这些GPIO

注册后的系统状态

复制代码
/sys/class/gpio/
├── gpiochip480/           # 新注册的GPIO控制器
│   ├── base               # 起始编号:480
│   ├── label              # 控制器名称
│   ├── ngpio              # GPIO数量:16
│   └── subsystem -> ../../../../class/gpio
└── export                 # 可以导出GPIO 480-495

5. 设备树匹配

复制代码
static const struct of_device_id virtual_gpio_of_match[] = {
    { .compatible = "100ask,virtual_gpio", },  // 匹配设备树节点
    { },
};

6. 平台驱动定义

复制代码
static struct platform_driver virtual_gpio_driver = {
    .probe      = virtual_gpio_probe,
    .remove     = virtual_gpio_remove,
    .driver     = {
        .name   = "100ask_virtual_gpio",
        .of_match_table = of_match_ptr(virtual_gpio_of_match),
    }
};

7. 模块初始化和退出

复制代码
static int __init virtual_gpio_init(void)
{   
    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    return platform_driver_register(&virtual_gpio_driver);
}

static void __exit virtual_gpio_exit(void)
{
    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    platform_driver_unregister(&virtual_gpio_driver);
}

8. 设备树配置示例

复制代码
virtual_gpio: virtual_gpio {
    compatible = "100ask,virtual_gpio";
    ngpios = <32>;          // 32个虚拟GPIO
    status = "okay";
};

9. 用户空间使用

驱动加载后,可以通过标准GPIO接口访问:

复制代码
# 查看GPIO控制器
cat /sys/kernel/debug/gpio

# 使用GPIO
echo 480 > /sys/class/gpio/export        # 导出GPIO(假设base=480)
echo out > /sys/class/gpio/gpio480/direction
echo 1 > /sys/class/gpio/gpio480/value   # 设置高电平
echo 0 > /sys/class/gpio/gpio480/value   # 设置低电平

10. 在驱动中使用虚拟GPIO

其他内核驱动可以这样使用:

复制代码
struct gpio_desc *virt_gpio;

virt_gpio = gpio_to_desc(480);  // 获取GPIO描述符
gpiod_direction_output(virt_gpio, 1);  // 设置为输出高电平
gpiod_set_value(virt_gpio, 0);         // 设置为低电平

11. 工作流程总结

  1. 模块加载 → 注册平台驱动

  2. 设备匹配 → 设备树中找到 compatible="100ask,virtual_gpio" 的节点

  3. 驱动探测

    • 分配并初始化 gpio_chip

    • 设置操作函数

    • 从设备树读取 GPIO 数量

    • 注册到 GPIO 子系统

  4. 用户访问

    • 通过 /sys/class/gpio 接口

    • 或其他驱动通过 GPIO 子系统 API

  5. GPIO操作 → 调用对应的虚拟函数,操作内存变量

相关推荐
我的xiaodoujiao3 小时前
使用 Python 语言 从 0 到 1 搭建完整 Web UI自动化测试学习系列 18--测试框架Pytest基础 2--插件和参数化
python·学习·测试工具·pytest
yuxb734 小时前
Ceph 分布式存储学习笔记(四):文件系统存储管理
笔记·ceph·学习
ouliten4 小时前
cuda编程笔记(29)-- CUDA Graph
笔记·深度学习·cuda
Larry_Yanan4 小时前
QML学习笔记(四十一)QML的ColorDialog和FontDialog
笔记·学习
润 下4 小时前
C语言——深入解析C语言指针:从基础到实践从入门到精通(四)
c语言·开发语言·人工智能·经验分享·笔记·程序人生·其他
koo3644 小时前
李宏毅机器学习笔记25
人工智能·笔记·机器学习
m0_678693334 小时前
深度学习笔记39-CGAN|生成手势图像 | 可控制生成(Pytorch)
深度学习·学习·生成对抗网络
小年糕是糕手4 小时前
【数据结构】双向链表“0”基础知识讲解 + 实战演练
c语言·开发语言·数据结构·c++·学习·算法·链表
将车2444 小时前
C++实现二叉树搜索树
开发语言·数据结构·c++·笔记·学习