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); // 设置对应位为1elseg_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
:私有数据指针(这里没有使用)
注册后的效果:
创建sysfs接口:
/sys/class/gpio/gpiochipXXX/
分配GPIO编号:如 GPIO 480-495(如果ngpios=16)
可用性:其他驱动可以通过标准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个虚拟GPIOstatus = "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. 工作流程总结
模块加载 → 注册平台驱动
设备匹配 → 设备树中找到 compatible="100ask,virtual_gpio" 的节点
驱动探测:
分配并初始化 gpio_chip
设置操作函数
从设备树读取 GPIO 数量
注册到 GPIO 子系统
用户访问:
通过 /sys/class/gpio 接口
或其他驱动通过 GPIO 子系统 API
GPIO操作 → 调用对应的虚拟函数,操作内存变量