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/
- 在sysfs中体现为:
-
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个虚拟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. 工作流程总结
-
模块加载 → 注册平台驱动
-
设备匹配 → 设备树中找到 compatible="100ask,virtual_gpio" 的节点
-
驱动探测:
-
分配并初始化 gpio_chip
-
设置操作函数
-
从设备树读取 GPIO 数量
-
注册到 GPIO 子系统
-
-
用户访问:
-
通过 /sys/class/gpio 接口
-
或其他驱动通过 GPIO 子系统 API
-
-
GPIO操作 → 调用对应的虚拟函数,操作内存变量