在这篇文章中,我将讨论如何在Rockchip 3568 11平台上创建自定义的GPIO驱动(理论上所有ARM平台都可以使用,无非就是dts或者driver gpio调用可能有一丢差异,但原理是一样的)。我将从设备树开始,然后深入到驱动的实现。
设备树配置
首先,我需要在设备树中定义我的自定义GPIO。这是我在xxx-evb3568-v1b-hdmi.dts
文件中的配置:
custom_gpio: custom_gpio { status = "okay"; compatible = "custom,gpio"; custom,gpios { custom,gpio0 { custom,gpio = <&gpio0 RK_PB3 GPIO_ACTIVE_HIGH>; custom,config = <2>; // 0: output(LOW) 1: output(HIGH) 2: input }; custom,gpio1 { custom,gpio = <&gpio0 RK_PD4 GPIO_ACTIVE_HIGH>; custom,config = <2>; }; custom,gpio2 { custom,gpio = <&gpio0 RK_PD5 GPIO_ACTIVE_HIGH>; custom,config = <2>; }; custom,gpio3 { custom,gpio = <&gpio0 RK_PD6 GPIO_ACTIVE_HIGH>; custom,config = <2>; }; custom,gpio4 { custom,gpio = <&gpio3 RK_PB3 GPIO_ACTIVE_HIGH>; custom,config = <0>; }; custom,gpio5 { custom,gpio = <&gpio3 RK_PB4 GPIO_ACTIVE_HIGH>; custom,config = <0>; }; custom,gpio6 { custom,gpio = <&gpio3 RK_PB5 GPIO_ACTIVE_HIGH>; custom,config = <0>; }; custom,gpio7 { custom,gpio = <&gpio3 RK_PB6 GPIO_ACTIVE_HIGH>; custom,config = <0>; }; }; };
在这个配置中,我定义了一个名为custom_gpio
的设备,它有8个GPIO,每个GPIO都有一个custom,gpio
属性,用于指定GPIO的引脚和活动电平,以及一个custom,config
属性,用于指定GPIO的初始配置。
驱动实现
接下来,我将看一下驱动的实现。驱动的主要部分在custom_gpio.c
文件中。
首先,我们需要包含一些必要的头文件:
#include <linux/module.h> #include <linux/init.h> #include <linux/device.h> #include <linux/errno.h> #include <linux/err.h> #include <linux/kernel.h> #include <linux/ctype.h> #include <linux/delay.h> #include <linux/idr.h> #include <linux/sched.h> #include <linux/slab.h> #include <linux/interrupt.h> #include <linux/signal.h> #include <linux/pm.h> #include <linux/notifier.h> #include <linux/fb.h> #include <linux/input.h> #include <linux/ioport.h> #include <linux/io.h> #include <linux/clk.h> #include <linux/pinctrl/consumer.h> #include <linux/platform_device.h> #include <linux/kthread.h> #include <linux/time.h> #include <linux/timer.h> #include <linux/regulator/consumer.h> #include <linux/gpio.h> #include <linux/of.h> #include <linux/of_irq.h> #include <linux/of_gpio.h> #include <linux/of_platform.h> #include <linux/miscdevice.h> #include <linux/uaccess.h>
定义了一些常量和数据结构:
#define CUSTOM_GPIO_NAME "custom,gpio" #define CUSTOM_GPIO_MAX 20 #define CUSTOM_GPIO_DIRECTION_MASK 0x01 #define CUSTOM_GPIO_VALUE_MASK 0x02 // ioctl cmd #define CUSTOM_GPIO_IOC_MAGIC 'f' #define CUSTOM_GPIO_IOC_SET_VALUE _IOW(CUSTOM_GPIO_IOC_MAGIC, 1, int) #define CUSTOM_GPIO_IOC_GET_VALUE _IOR(CUSTOM_GPIO_IOC_MAGIC, 2, int) #define CUSTOM_GPIO_IOC_SET_DIRECTION _IOW(CUSTOM_GPIO_IOC_MAGIC, 3, int) #define CUSTOM_GPIO_IOC_REG_KEY_EVENT _IOW(CUSTOM_GPIO_IOC_MAGIC, 4, int) #define CUSTOM_GPIO_IOC_UNREG_KEY_EVENT _IOW(CUSTOM_GPIO_IOC_MAGIC, 5, int) #define CUSTOM_GPIO_IOC_GET_NUMBER _IOW(CUSTOM_GPIO_IOC_MAGIC, 6, int) #define CUSTOM_GPIO_IOC_TEST _IOW(CUSTOM_GPIO_IOC_MAGIC, 7, int) #define CUSTOM_GPIO_IOC_MAXNR 7 #define CUSTOM_GPIO_CONFIG_OUTPUT_LOW 0 #define CUSTOM_GPIO_CONFIG_OUTPUT_HIGHT 1 #define CUSTOM_GPIO_CONFIG_INPUT 2 static int gKeyCode[CUSTOM_GPIO_MAX] = { KEY_GPIO_0, KEY_GPIO_1, KEY_GPIO_2, KEY_GPIO_3, KEY_GPIO_4, KEY_GPIO_5, KEY_GPIO_6, KEY_GPIO_7, KEY_GPIO_8, KEY_GPIO_9}; struct custom_gpio { int gpio; int config; }; struct custom_gpio_data { struct platform_device *platform_dev; struct miscdevice custom_gpio_device; struct input_dev *input_dev; struct custom_gpio gpios[CUSTOM_GPIO_MAX]; int irqs[CUSTOM_GPIO_MAX]; int gpio_number; };
这里,custom_gpio
结构体用于存储每个GPIO的信息,custom_gpio_data
结构体用于存储驱动的全局信息。
实现了一些辅助函数,用于操作GPIO:
static void custom_gpio_free_io_irq(struct custom_gpio_data *custom_gpio, int gpio) { if (gpio >= 0 && gpio < custom_gpio->gpio_number) { if (custom_gpio->irqs[gpio] > 0) { free_irq(custom_gpio->irqs[gpio], custom_gpio); custom_gpio->irqs[gpio] = -1; } } } static void custom_gpio_free_irq(struct custom_gpio_data *custom_gpio) { int i; for (i=0; i<custom_gpio->gpio_number; i++) { custom_gpio_free_io_irq(custom_gpio, i); } } static void custom_gpio_free_io_port(struct custom_gpio_data *custom_gpio) { int i; for (i=0; i<custom_gpio->gpio_number; i++) { if(gpio_is_valid(custom_gpio->gpios[i].gpio)) { gpio_free(custom_gpio->gpios[i].gpio); } } return; } static int custom_gpio_parse_dt(struct device *dev, struct custom_gpio_data *custom_gpio) { int ret = 0, index = 0; struct device_node *np = dev->of_node; struct device_node *root = of_get_child_by_name(np, "custom,gpios"); struct device_node *child; for _each_child_of_node(root, child) { if (index >= CUSTOM_GPIO_MAX) { dev_err(dev, "The number of GPIOs exceeds the maximum:%d value to break", CUSTOM_GPIO_MAX); break; } custom_gpio->gpios[index].gpio = of_get_named_gpio(child, "custom,gpio", 0); if(!gpio_is_valid(custom_gpio->gpios[index].gpio)) { dev_err(dev, "No valid gpio[%d]", index); return -1; } ret = of_property_read_u32(child, "custom,config", &custom_gpio->gpios[index].config); if(ret) { dev_warn(dev, "No valid gpio[%d]'s config, Use default values.", index); custom_gpio->gpios[index].config = CUSTOM_GPIO_CONFIG_INPUT; } index++; } custom_gpio->gpio_number = index; return 0; } static int custom_gpio_request_io_port(struct custom_gpio_data *custom_gpio) { int ret = 0; int i; for (i=0; i<custom_gpio->gpio_number; i++) { if(gpio_is_valid(custom_gpio->gpios[i].gpio)) { ret = gpio_request(custom_gpio->gpios[i].gpio, "custom_gpio"); if(ret < 0) { dev_err(&custom_gpio->platform_dev->dev, "Failed to request GPIO[%d]:%d, ERRNO:%d\n", i, (s32)custom_gpio->gpios[i].gpio, ret); return -ENODEV; } if (custom_gpio->gpios[i].config == CUSTOM_GPIO_CONFIG_INPUT) { gpio_direction_input(custom_gpio->gpios[i].gpio); } else if (custom_gpio->gpios[i].config == CUSTOM_GPIO_CONFIG_OUTPUT_LOW) { gpio_direction_output(custom_gpio->gpios[i].gpio, 0); } else if (custom_gpio->gpios[i].config == CUSTOM_GPIO_CONFIG_OUTPUT_HIGHT) { gpio_direction_output(custom_gpio->gpios[i].gpio, 1); } dev_info(&custom_gpio->platform_dev->dev, "Success request gpio[%d]\n", i); } } return ret; } static s8 custom_gpio_request_input_dev(struct custom_gpio_data *custom_gpio) { s8 ret = -1; custom_gpio->input_dev = input_allocate_device(); if(custom_gpio->input_dev == NULL) { dev_err(&custom_gpio->platform_dev->dev, "Failed to allocate input device\n"); return -ENOMEM; } input_set_capability(custom_gpio->input_dev, EV_KEY, KEY_GPIO_0); input_set_capability(custom_gpio->input_dev, EV_KEY, KEY_GPIO_1); input_set_capability(custom_gpio->input_dev, EV_KEY, KEY_GPIO_2); input_set_capability(custom_gpio->input_dev, EV_KEY, KEY_GPIO_3); input_set_capability(custom_gpio->input_dev, EV_KEY, KEY_GPIO_4); input_set_capability(custom_gpio->input_dev, EV_KEY, KEY_GPIO_5); input_set_capability(custom_gpio->input_dev, EV_KEY, KEY_GPIO_6); input_set_capability(custom_gpio->input_dev, EV_KEY, KEY_GPIO_7); input_set_capability(custom_gpio->input_dev, EV_KEY, KEY_GPIO_8); input_set_capability(custom_gpio->input_dev, EV_KEY, KEY_GPIO_9); ret = input_register_device(custom_gpio->input_dev); if(ret) { dev_err(&custom_gpio->platform_dev->dev, "Register %s input device failed\n", custom_gpio->input_dev->name); input_free_device(custom_gpio->input_dev); return -ENODEV; } return 0; } static int custom_gpio_set_value(int gpio, int value) { if(gpio_is_valid(gpio)) { gpio_set_value(gpio, value); return 0; } return -1; } static int custom_gpio_get_value(int gpio) { if(gpio_is_valid(gpio)) { return gpio_get_value(gpio); } return -1; } static int custom_gpio_set_direction(int gpio, int value) { int direction = 0; int data = 0; if(gpio_is_valid(gpio)) { direction = value & CUSTOM_GPIO_DIRECTION_MASK; data = value & CUSTOM_GPIO_VALUE_MASK; if (direction > 0) { return gpio_direction_output(gpio, data); } else { return gpio_direction_input(gpio); } } return -1; } static int custom_gpio_write(struct custom_gpio_data *custom_gpio, int gpio, int value) { int ret = -1; if (gpio < custom_gpio->gpio_number) { ret = custom_gpio_set_value(custom_gpio->gpios[gpio].gpio, value); } return ret; } static int custom_gpio_read(struct custom_gpio_data *custom_gpio, int gpio) { int ret = -1; if (gpio < custom_gpio->gpio_number) { ret = custom_gpio_get_value(custom_gpio->gpios[gpio].gpio); } return ret; } static int custom_gpio_direction(struct custom_gpio_data *custom_gpio, int gpio, int value) { int ret = -1; if (gpio < custom_gpio->gpio_number) { ret = custom_gpio_set_direction(custom_gpio->gpios[gpio].gpio, value); } return ret; } static irqreturn_t custom_gpio_irq_handle(int irq, void *dev_id) { struct custom_gpio_data *custom_gpio = dev_id; int gpio = -1; int value = -1; int i; for (i=0; i<custom_gpio->gpio_number; i++) { if (irq == custom_gpio->irqs[i]) { gpio = i; } } if (gpio >= 0 && gpio < custom_gpio->gpio_number) { value = custom_gpio_get_value(custom_gpio->gpios[gpio].gpio); if (value >= 0) { input_report_key(custom_gpio->input_dev, gKeyCode[gpio], !value); input_sync(custom_gpio->input_dev); } } return IRQ_HANDLED; } static int custom_gpio_request_irq(struct custom_gpio_data *custom_gpio, int gpio) { int ret = 0; /* use irq */ if(gpio_is_valid(custom_gpio->gpios[gpio].gpio) || custom_gpio->irqs[gpio] > 0) { if(gpio_is_valid(custom_gpio->gpios[gpio].gpio)) custom_gpio->irqs[gpio] = gpio_to_irq(custom_gpio->gpios[gpio].gpio); dev_info(&custom_gpio->platform_dev->dev, "INT num %d, trigger type:%d\n", custom_gpio->irqs[gpio], IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING); ret = request_threaded_irq(custom_gpio->irqs[gpio], NULL, custom_gpio_irq_handle, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, custom_gpio->platform_dev->name, custom_gpio); if(ret < 0) { dev_err(&custom_gpio->platform_dev->dev, "Failed to request irq %d\n", custom_gpio->irqs[gpio]); } } return ret; } static int custom_gpio_dev_open(struct inode *inode, struct file *filp) { int ret = 0; struct custom_gpio_data *custom_gpio = container_of(filp->private_data, struct custom_gpio_data, custom_gpio_device); filp->private_data = custom_gpio; dev_info(&custom_gpio->platform_dev->dev, "device node major=%d, minor=%d\n", imajor(inode), iminor(inode)); return ret; } static long custom_gpio_dev_ioctl(struct file *pfile, unsigned int cmd, unsigned long arg) { int ret = 0; int i,j = 0; int data = 0; int gpio = 0, value = 0; struct custom_gpio_data *custom_gpio = pfile->private_data; if (_IOC_TYPE(cmd) != CUSTOM_GPIO_IOC_MAGIC) return -EINVAL; if (_IOC_NR(cmd) > CUSTOM_GPIO_IOC_MAXNR) return -EINVAL; if (_IOC_DIR(cmd) & _IOC_READ) ret = !access_ok(VERIFY_WRITE, (void *)arg, _IOC_SIZE(cmd)); else if (_IOC_DIR(cmd) & _IOC_WRITE) ret = !access_ok(VERIFY_READ, (void *)arg, _IOC_SIZE(cmd)); if (ret) return -EFAULT; if (copy_from_user(&data, (int *)arg, sizeof(int))) { dev_err(&custom_gpio->platform_dev->dev, "%s, copy from user failed\n", __func__); return -EFAULT; } gpio = (data >> 4) & 0x0f; value = data & 0x0f; dev_info(&custom_gpio->platform_dev->dev, "%s, (%x, %lx): gpio=%d, value=%d\n", __func__, cmd, arg, gpio, value); switch (cmd) { case CUSTOM_GPIO_IOC_SET_VALUE: ret = custom_gpio_write(custom_gpio, gpio, value); break; case CUSTOM_GPIO_IOC_TEST: for(j = 0;j<100;j++){ for(i = 0;i<custom_gpio->gpio_number;i++){ if(j == 0){ custom_gpio_direction(custom_gpio,i, 1); }else{ custom_gpio_write(custom_gpio, i, 1); } } msleep(2000); for(i = 0;i< custom_gpio->gpio_number;i++){ custom_gpio_write(custom_gpio, i, 0); } msleep(2000); } break; case CUSTOM_GPIO_IOC_GET_VALUE: ret = custom_gpio_read(custom_gpio, gpio); if (ret >= 0) { if (copy_to_user((int *)arg, &ret, sizeof(int))) { dev_err(&custom_gpio->platform_dev->dev, "%s, copy to user failed\n", __func__); return -EFAULT; } } else { dev_err(&custom_gpio->platform_dev->dev, "%s, gpio get value failed\n", __func__); return -EFAULT; } break; case CUSTOM_GPIO_IOC_SET_DIRECTION: ret = custom_gpio_direction(custom_gpio, gpio, value); break; case CUSTOM_GPIO_IOC_REG_KEY_EVENT: ret = custom_gpio_direction(custom_gpio, gpio, 0); // set input if (ret >= 0) { ret = custom_gpio_request_irq(custom_gpio, gpio); } else { dev_err(&custom_gpio->platform_dev->dev, "%s, reg key event set gpio input failed\n", __func__); } break; case CUSTOM_GPIO_IOC_UNREG_KEY_EVENT: custom_gpio_free_io_irq(custom_gpio, gpio); break; case CUSTOM_GPIO_IOC_GET_NUMBER: if (copy_to_user((int *)arg, &custom_gpio->gpio_number, sizeof(int))) { dev_err(&custom_gpio->platform_dev->dev, "%s, copy to user failed\n", __func__); return -EFAULT; } break; default: return -EINVAL; } return ret; } static const struct file_operations custom_gpio_dev_fops = { .owner = THIS_MODULE, .open = custom_gpio_dev_open, .unlocked_ioctl = custom_gpio_dev_ioctl, .compat_ioctl = custom_gpio_dev_ioctl }; static int custom_gpio_probe(struct platform_device *pdev) { int ret = 0; struct custom_gpio_data *custom_gpio; printk("custom_gpio_probe\n"); custom_gpio = devm_kzalloc(&pdev->dev, sizeof(*custom_gpio), GFP_KERNEL); if(custom_gpio == NULL) { dev_err(&pdev->dev, "Failed alloc ts memory"); return -ENOMEM; } if(pdev->dev.of_node) { ret = custom_gpio_parse_dt(&pdev->dev, custom_gpio); if(ret) { dev_err(&pdev->dev, "Failed parse dts\n"); goto exit_free_data; } } custom_gpio->platform_dev = pdev; ret = custom_gpio_request_io_port(custom_gpio); if(ret < 0) { dev_err(&pdev->dev, "Failed request IO port\n"); goto exit_free_data; } ret = custom_gpio_request_input_dev(custom_gpio); if(ret < 0) { dev_err(&pdev->dev, "Failed request IO port\n"); goto exit_free_io_port; } platform_set_drvdata(pdev, custom_gpio); custom_gpio->custom_gpio_device.minor = MISC_DYNAMIC_MINOR; custom_gpio->custom_gpio_device.name = "custom_gpio"; custom_gpio->custom_gpio_device.fops = &custom_gpio_dev_fops; ret = misc_register(&custom_gpio->custom_gpio_device); if (ret) { dev_err(&pdev->dev, "Failed misc_register\n"); goto exit_unreg_input_dev; } dev_info(&pdev->dev, "%s, over\n", __func__); return 0; exit_unreg_input_dev: input_unregister_device(custom_gpio->input_dev); exit_free_io_port: custom_gpio_free_io_port(custom_gpio); exit_free_data: devm_kfree(&pdev->dev, custom_gpio); return ret; } static int custom_gpio_remove(struct platform_device *pdev) { struct custom_gpio_data *custom_gpio = platform_get_drvdata(pdev); printk("custom_gpio_remove\n"); custom_gpio_free_irq(custom_gpio); custom_gpio_free_io_port(custom_gpio); kfree(custom_gpio); return 0; } static const struct of_device_id custom_gpio_of_match[] = { { .compatible = "custom,gpio"}, {}, }; MODULE_DEVICE_TABLE(of, custom_gpio_of_match); static struct platform_driver custom_gpio_driver = { .probe = custom_gpio_probe, .remove = custom _gpio_remove, .driver = { .name = "custom_gpio", .owner = THIS_MODULE, .of_match_table = custom_gpio_of_match, }, }; module_platform_driver(custom_gpio_driver); MODULE_AUTHOR("ln28"); MODULE_DESCRIPTION("Custom GPIO driver for Rockchip"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:custom-gpio");
这个驱动程序的主要功能是通过ioctl接口来控制GPIO的输入输出,同时也支持GPIO的中断,当GPIO的电平发生变化时,会生成一个键盘事件。
这个驱动程序在应用空间的使用方法如下:
- 打开设备文件:
fd = open("/dev/custom_gpio", O_RDWR);
- 设置GPIO的值:
ioctl(fd, CUSTOM_GPIO_IOC_SET_VALUE, (gpio << 4) | value);
- 获取GPIO的值:
ioctl(fd, CUSTOM_GPIO_IOC_GET_VALUE, (gpio << 4));
- 设置GPIO的方向:
ioctl(fd, CUSTOM_GPIO_IOC_SET_DIRECTION, (gpio << 4) | direction);
- 注册GPIO的键盘事件:
ioctl(fd, CUSTOM_GPIO_IOC_REG_KEY_EVENT, (gpio << 4));
- 取消GPIO的键盘事件:
ioctl(fd, CUSTOM_GPIO_IOC_UNREG_KEY_EVENT, (gpio << 4));
- 获取GPIO的数量:
ioctl(fd, CUSTOM_GPIO_IOC_GET_NUMBER, &gpio_number);
- 关闭设备文件:
close(fd);
你可以在Android系统源码中新建一个可执行程序通过调用ioctl接口方式来测试。
总结
本篇博客,介绍了如何在用户空间通过ioctl接口来控制GPIO的输入输出功能,以及如何注册GPIO中断来处理按键事件。定义了一些ioctl命令码和数据结构,以及实现了相应的操作函数和中断处理函数。这样就可以在用户空间通过/dev/custom_gpio设备文件来操作GPIO了。