1. 前言
GPIO驱动开发可能算是Linux内核设备驱动开发中最为简单、最常见的一个方向,对于开发板的按键、LED、蜂鸣器、电源控制等模块,可能都是使用GPIO实现的。Linux内核的GPIO子系统在内核不断的演进过程中进行了多次的重构,本文的第二章所举的案例依照大家比较熟悉的GPIO开发模式展开,第3章会介绍GPIO架构最新的编程模式(基于4.18内核)。Linux内核GPIO子系统与pinctrl子系统存在的很大的关系,关于pinctrl子系统可以参见文章,关于GPIO子系统的讨论在后续文章中会总结。下面讨论GPIO基本的编程模式。
2. GPIO编程模式
本章所介绍的GPIO驱动demo基于Linux kernel 3.10版本,开发板为OKMX6UL-C2。该demo的功能十分的简单,其主要完成的功能就是通过DTS读取GPIO的配置信息,然后根据配置信息配置每个GPIO端口。之后,应用程序可以通过ioctl控制GPIO的具体行为(IO状态读取、IO状态输出等)。下面首先介绍demo的驱动实现。
2.1 编程接口
Linux内核关于GPIO的控制一般通过GPIOLIB框架,内核配置时需要启用该选项** CONFIG_GPIOLIB**。下面所有接口都是基于GPIOLIB实现的。
本demo中所涉及的GPIO编程接口如下:
(linux/asm-generic/gpio.h): extern int gpio_request(unsigned gpio, const char *label); extern void gpio_free(unsigned gpio); extern int gpio_direction_input(unsigned gpio); extern int gpio_direction_output(unsigned gpio, int value); extern int gpio_set_debounce(unsigned gpio, unsigned debounce); extern int gpio_get_value_cansleep(unsigned gpio); extern void gpio_set_value_cansleep(unsigned gpio, int value); (linux/gpio.h) static inline int gpio_get_value(unsigned int gpio); static inline void gpio_set_value(unsigned int gpio, int value);
2.2 DTS配置
Demo通过DTS中关于GPIO的配置信息完成GPIO各个端口的初始化工作,下面举一个DTS配置例子(关于DTS基本语法可以参考文章):
gpios { compatible = "gpio-user"; status = "okay"; /*input*/ gpio0 { label = "in0"; gpios = <&gpio1 1 0>; default-direction = "in"; }; ... ... /*output*/ gpio17 { label = "out1"; gpios = <&gpio3 3 0>; default-direction = "out"; }; };
上面表示系统配置了两个GPIO,一个作为输入gpio,一个作为输出gpio,其中:
- label:标识该GPIO端口;
- gpios属性的定义格式一般依赖于GPIO controler的#gpio-cells属性字段。对于本开发板来说gpios的格式为:
,bank_num为GPIO的bank编号,offset为bank内的gpio编号偏移,defalut_value表示gpio输出状态下的默认值; 3. default-direction属性表示gpio为输入还是输出模式,"in"表示输入模式,"out"表示输出模式;
##2.3 GPIO驱动程序 该dmeo基于内核的misc设备开发框架实现,而后通过以platform_driver的形式注册到系统中。platform_driver的数据结构如下:
static struct platform_driver gpio_user_driver = { .probe = gpio_user_probe, .remove = gpio_user_remove, .driver = { .owner = THIS_MODULE, .name = "gpio-user", .of_match_table = of_gpio_user_id_table, }, };
其中,of_gpio_user_id_table定义了该驱动的设备兼容性,其与2.2节中的compatible = "gpio-user","gpio-user"字段相对应。下面是of_gpio_user_id_table的定义:
static const struct of_device_id of_gpio_user_id_table[] = { { .compatible = "gpio-user",}, {}, };
如果DTS中所定义的设备与驱动匹配上的话,那么gpio_user_probe将会被执行,下面着重分析一下该函数的实现方式。在讲解之前需要分析一下驱动定义的私有数据结构,其定义如下:
struct gpio_user_data{ const char *label;//DTS中的label字段 bool input;//是否为输入模式 unsigned gpio;//gpio编号 unsigned dft;//gpio输出模式下的默认输出值 }; static struct gpio_misc{ struct miscdevice misc;//misc设备模型 struct gpio_user_data *data;//gpio配置数据 int gpio_count;//gpio配置数据个数 } *gpio_misc;
gpio_user_probe的实现如下:
static int gpio_user_probe(struct platform_device *pdev) { int index; struct device_node *node = pdev->dev.of_node, *child; gpio_misc = devm_kzalloc(&pdev->dev,sizeof(*gpio_misc),GFP_KERNEL);------------------>(1) if(!gpio_misc){ return -ENOMEM; } gpio_misc->gpio_count = of_get_available_child_count(node);------------------>(2) if(!gpio_misc->gpio_count){ return -ENODEV; } if(gpio_misc->gpio_count > MAX_GPIO_NR){ gpio_misc->gpio_count = MAX_GPIO_NR; } gpio_misc->data = devm_kzalloc(&pdev->dev,sizeof(struct gpio_user_data) * gpio_misc->gpio_count,GFP_KERNEL); if(!gpio_misc->data){ return -ENOMEM; } index = 0; for_each_available_child_of_node(node,child){------------------>(3) const char *input; struct gpio_user_data *data = &gpio_misc->data[index++]; data->label = of_get_property(child,"label",NULL) ? : child->name; input = of_get_property(child,"default-direction",NULL) ? : "in"; if(strcmp(input,"in") == 0) data->input = true; data->gpio = of_get_gpio_flags(child,0,&data->dft); } gpio_user_init_default();------------------>(4) gpio_misc->misc.name = "gpio";------------------>(5) gpio_misc->misc.minor = MISC_DYNAMIC_MINOR; gpio_misc->misc.fops = &gpio_user_fops; return misc_register(&gpio_misc->misc); }
下面分步骤对其进行解析:
- (1)使用具有内存回收功能的devm_kzalloc为gpio_misc分配内存空间。
- (2)通过of_get_available_child_count获取DTS的gpio子节点的个数。判断该值的有效性,然后根据gpio子节点个数创建gpio_misc->data数组。
- (3)使用for_each_available_child_of_node变脸DTS中gpio子节点,初始化gpio_misc->data数组。
- (4)初始化gpio配置信息,下面会详细介绍gpio_user_init_default函数。
- (5)初始化misc设备信息,注册gpio_misc->misc设备到系统中。
gpio_user_init_default函数实现方式如下:
static void gpio_user_init_default(void) { int i,ret; struct gpio_user_data *data; data = gpio_misc->data; for(i = 0;i < gpio_misc->gpio_count;i++) { if(!gpio_is_valid(data[i].gpio)) { continue; } ret = gpio_request(data[i].gpio,data[i].label);------------------>(1) if(ret < 0) { continue; } if(data[i].input) { gpio_direction_input(data[i].gpio);---------------------->(2) } else { gpio_direction_output(data[i].gpio,data[i].dft); } } }
下面简单的介绍一下实现步骤:
- (1)检测gpio_num的有效性,并通过gpio_request向系统申请gpio的使用权(如果申请成功,该gpio会被标记为已占用,并且GPIO的功能属性为GPIO);
- (2)配置GPIO的输入、输出模式。
通过上面一系列的初始化工作,DTS中配置的gpio基本都配置完成了。下面分析一下ioctl接口,讲解一下如何控制GPIO的行为。
#define GPIO_U_IOCTL_BASE 'x' #define GPIOC_OPS _IOWR(GPIO_U_IOCTL_BASE,0,int) static const struct file_operations gpio_user_fops = { .owner = THIS_MODULE, .open = gpio_user_open, .release = gpio_user_release, .unlocked_ioctl = gpio_user_ioctl, }; static long gpio_user_ioctl(struct file *filp, unsigned int cmd,unsigned long arg) { int no, offset; unsigned long val; unsigned long __user *p = (void __user *)arg; struct gpio_user_data *data; unsigned long get_value; if(!gpio_misc) return -ENODEV; data = gpio_misc->data; if(_IOC_TYPE(cmd) != GPIO_U_IOCTL_BASE) return -EINVAL; switch(_IOC_NR(cmd)) { case 0: if(get_user(val,p)) return -EFAULT; no = val & (~(1u << 31));------------------>(1) if(data[no].input) { get_value = gpio_get_value(data[no].gpio);------------------>(2) printk("get_value is %d\n", get_value); offset = data[no].gpio % 32; val = get_value >> offset; printk("val is %d\n",val); put_user(val,p); } else { gpio_set_value(data[no].gpio,val >> 31);------------------>(3) } break; default: return -ENOTTY; } return 0; }
首先,通过_IOWR定义了一个ioctl操作命令:GPIOC_OPS,关于_IOWR的具体使用方式可以参考文章。
下面简单的介绍一下实现步骤:
- (1)检测ioctl控制命令有效性,获取gpio编号;
- (2)如果为input模式下的gpio,读取GPIO当前value,并将该状态返回给用户程序;
- (3)如果为output模式下的gpio, 设置GPIO输出value;
上面既是GPIO驱动端的代码实现,完整的代码可以在这里下载。
2.3 GPIO测试程序
GPIO的测试程序通过设备文件(/dev/gpio)完成GPIO的输出状态配置和GPIO输入状态的读取功能。代码可以在这里下载。
3. GPIO子系统的变化
关于GPIO子系统的变化,参考后续的文章,未完待续... ...