创建gpiochip的流程
定义gpio_chip结构: 在GPIO控制器驱动中,首先要定义一个gpio_chip结构体,并初始化其各个字段。
struct gpio_chip my_gpio_chip = {
.label = "my_gpio_chip",
.base = -1, // 让内核自动分配基地址
.ngpio = 32, // 比如该控制器管理32个GPIO引脚
.owner = THIS_MODULE,
.set = my_gpio_set_value,
.get = my_gpio_get_value,
.direction_input = my_gpio_direction_input,
.direction_output = my_gpio_direction_output,
// 其他必需的操作函数指针
};
struct gpio_chip my_gpio_chip = {
.label = "my_gpio_chip",
.base = -1, // 让内核自动分配基地址
.ngpio = 32, // 比如该控制器管理32个GPIO引脚
.owner = THIS_MODULE,
.set = my_gpio_set_value,
.get = my_gpio_get_value,
.direction_input = my_gpio_direction_input,
.direction_output = my_gpio_direction_output,
// 其他必需的操作函数指针
};
实现操作函数: 实现上面结构体中的各个函数,比如设置输入输出方向、设置和获取引脚值。
static void my_gpio_set_value(struct gpio_chip *chip, unsigned offset, int value) {
// 设置GPIO引脚的值
}
static int my_gpio_get_value(struct gpio_chip *chip, unsigned offset) {
// 获取GPIO引脚的值
}
static int my_gpio_direction_input(struct gpio_chip *chip, unsigned offset) {
// 设置GPIO引脚为输入
}
static int my_gpio_direction_output(struct gpio_chip *chip, unsigned offset, int value) {
// 设置GPIO引脚为输出并设置初始值
}
static void my_gpio_set_value(struct gpio_chip *chip, unsigned offset, int value) {
// 设置GPIO引脚的值
}
static int my_gpio_get_value(struct gpio_chip *chip, unsigned offset) {
// 获取GPIO引脚的值
}
static int my_gpio_direction_input(struct gpio_chip *chip, unsigned offset) {
// 设置GPIO引脚为输入
}
static int my_gpio_direction_output(struct gpio_chip *chip, unsigned offset, int value) {
// 设置GPIO引脚为输出并设置初始值
}
注册gpiochip: 在驱动的初始化函数中,使用gpiochip_add函数注册这个gpio_chip。
static int __init my_gpio_driver_init(void) {
int ret;
ret = gpiochip_add(&my_gpio_chip);
if (ret) {
pr_err("Failed to add gpiochip\n");
return ret;
}
pr_info("gpiochip added successfully\n");
return 0;
}
static void __exit my_gpio_driver_exit(void) {
gpiochip_remove(&my_gpio_chip);
pr_info("gpiochip removed successfully\n");
}
module_init(my_gpio_driver_init);
module_exit(my_gpio_driver_exit);
static int __init my_gpio_driver_init(void) {
int ret;
ret = gpiochip_add(&my_gpio_chip);
if (ret) {
pr_err("Failed to add gpiochip\n");
return ret;
}
pr_info("gpiochip added successfully\n");
return 0;
}
static void __exit my_gpio_driver_exit(void) {
gpiochip_remove(&my_gpio_chip);
pr_info("gpiochip removed successfully\n");
}
module_init(my_gpio_driver_init);
module_exit(my_gpio_driver_exit);
设备树支持(可选): 对于设备树支持的驱动,还需要在设备树中添加GPIO控制器的描述,并在驱动中解析这些节点。
设备树节点示例:
dts文件:
gpio@12340000 {
compatible = "my,gpio";
reg = <0x12340000 0x1000>;
gpio-controller;
#gpio-cells = <2>;
};
gpio@12340000 {
compatible = "my,gpio";
reg = <0x12340000 0x1000>;
gpio-controller;
#gpio-cells = <2>;
};
内核解析设备树示例:
static int my_gpio_probe(struct platform_device *pdev) {
struct resource *res;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
// 解析资源,映射寄存器等
return gpiochip_add(&my_gpio_chip);
}
static const struct of_device_id my_gpio_of_match[] = {
{ .compatible = "my,gpio", },
{},
};
MODULE_DEVICE_TABLE(of, my_gpio_of_match);
static struct platform_driver my_gpio_driver = {
.probe = my_gpio_probe,
.remove = my_gpio_remove,
.driver = {
.name = "my_gpio",
.of_match_table = of_match_ptr(my_gpio_of_match),
},
};
module_platform_driver(my_gpio_driver);
static int my_gpio_probe(struct platform_device *pdev) {
struct resource *res;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
// 解析资源,映射寄存器等
return gpiochip_add(&my_gpio_chip);
}
static const struct of_device_id my_gpio_of_match[] = {
{ .compatible = "my,gpio", },
{},
};
MODULE_DEVICE_TABLE(of, my_gpio_of_match);
static struct platform_driver my_gpio_driver = {
.probe = my_gpio_probe,
.remove = my_gpio_remove,
.driver = {
.name = "my_gpio",
.of_match_table = of_match_ptr(my_gpio_of_match),
},
};
module_platform_driver(my_gpio_driver);
使用gpiochip的流程
- 请求和释放GPIO引脚:
在驱动或应用程序中,可以使用gpio_request和gpio_free来请求和释放一个GPIO引脚。
int gpio = 20; // 想要使用的GPIO引脚编号
int ret;
ret = gpio_request(gpio, "my_gpio_user");
if (ret) {
printk(KERN_ERR "Failed to request GPIO %d\n", gpio);
return ret;
}
// 使用完毕后释放GPIO
gpio_free(gpio);
int gpio = 20; // 想要使用的GPIO引脚编号
int ret;
ret = gpio_request(gpio, "my_gpio_user");
if (ret) {
printk(KERN_ERR "Failed to request GPIO %d\n", gpio);
return ret;
}
// 使用完毕后释放GPIO
gpio_free(gpio);
- 设置和读取GPIO引脚值:
使用gpio_set_value和gpio_get_value函数来设置和读取GPIO引脚的值。
gpio_set_value(gpio, 1); // 将引脚设置为高电平
int value = gpio_get_value(gpio); // 读取引脚的当前值
gpio_set_value(gpio, 1); // 将引脚设置为高电平
int value = gpio_get_value(gpio); // 读取引脚的当前值
- 设置GPIO方向:
使用gpio_direction_input和gpio_direction_output函数来设置GPIO引脚的方向。
ret = gpio_direction_input(gpio); // 将引脚设置为输入
if (ret) {
printk(KERN_ERR "Failed to set GPIO %d as input\n", gpio);
return ret;
}
ret = gpio_direction_output(gpio, 1); // 将引脚设置为输出,并初始化为高电平
if (ret) {
printk(KERN_ERR "Failed to set GPIO %d as output\n", gpio);
return ret;
}
ret = gpio_direction_input(gpio); // 将引脚设置为输入
if (ret) {
printk(KERN_ERR "Failed to set GPIO %d as input\n", gpio);
return ret;
}
ret = gpio_direction_output(gpio, 1); // 将引脚设置为输出,并初始化为高电平
if (ret) {
printk(KERN_ERR "Failed to set GPIO %d as output\n", gpio);
return ret;
}
用户空间操作:
在用户空间,可以通过sysfs接口或者/dev/gpiochip字符设备来操作GPIO引脚。
- 使用sysfs接口:
echo 20 > /sys/class/gpio/export # 请求GPIO 20引脚
echo out > /sys/class/gpio/gpio20/direction # 设置方向为输出
echo 1 > /sys/class/gpio/gpio20/value # 设置值为高电平
cat /sys/class/gpio/gpio20/value # 读取值
echo 20 > /sys/class/gpio/unexport # 释放GPIO 20引脚
echo 20 > /sys/class/gpio/export # 请求GPIO 20引脚
echo out > /sys/class/gpio/gpio20/direction # 设置方向为输出
echo 1 > /sys/class/gpio/gpio20/value # 设置值为高电平
cat /sys/class/gpio/gpio20/value # 读取值
echo 20 > /sys/class/gpio/unexport # 释放GPIO 20引脚
使用/dev/gpiochip字符设备(需要编写对应的用户空间程序):
- 用户可以编写程序使用ioctl来操作GPIO引脚,这提供了比sysfs更高效的方式。
// 示例代码需要包含 <linux/gpio.h> 和 <fcntl.h>
int fd = open("/dev/gpiochip0", O_RDONLY);
struct gpiochip_info info;
ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, &info); // 获取芯片信息
// 设置和读取的更多示例可以参考相应的用户空间库或文档
// 示例代码需要包含 <linux/gpio.h> 和 <fcntl.h>
int fd = open("/dev/gpiochip0", O_RDONLY);
struct gpiochip_info info;
ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, &info); // 获取芯片信息
// 设置和读取的更多示例可以参考相应的用户空间库或文档
总结来说,创建和使用gpiochip涉及到内核和用户空间的多层次交互,主要通过定义结构体、实现必要的操作函数、注册芯片、请求和释放GPIO引脚、以及设置和读取引脚状态等步骤来完成。设备树的支持还需要解析设备树节点,并在驱动中根据这些描述进行相应的初始化工作。