• 概述Linux内核驱动之GPIO子系统API接口


    1、前言

    嵌入式Linux开发中,对嵌入式SoC中的GPIO进行控制非常重要,Linux内核中提供了GPIO子系统,驱动开发者在驱动代码中使用GPIO子系统提供的API函数,便可以达到对GPIO控制的效果,例如将IO口的方向设置为输入或输出,当IO口的方向为输入时,可以通过调用API函数获取相应的IO口电平,当IO口设置为输出方向时,可以调用相关的API函数去设置IO口电平,本文将简单描述如何去使用Linux内核中GPIO子系统的API接口。

    下图是Linux内核中GPIO子系统的软件驱动分层图:

    2、常用API接口

    当我们在驱动代码中要使用内核中提供的GPIO子系统,需要在驱动代码中包含头文件,另外,关于API接口函数的实现在内核源码drivers/gpio/gpiolib.c文件中,关于GPIO子系统的使用说明文档为Documentation/gpio.txt,该文档具有更详细的使用说明,接下来,将简单介绍一下常用的API接口。

    1. /*
    2. * "valid" GPIO numbers are nonnegative and may be passed to
    3. * setup routines like gpio_request(). only some valid numbers
    4. * can successfully be requested and used.
    5. *
    6. * Invalid GPIO numbers are useful for indicating no-such-GPIO in
    7. * platform data and other tables.
    8. */
    9. static inline bool gpio_is_valid(int number)
    10. {
    11. return number >= 0 && number < ARCH_NR_GPIOS;
    12. }

    函数gpio_is_valid()用来判断获取到的gpio号是否是有效的,只有有效的gpio号,才能向内核中进行申请使用,因此,当我们从设备树的设备节点获取到gpio号,可以使用该函数进行判断是否有效。

    1. /* Always use the library code for GPIO management calls,
    2. * or when sleeping may be involved.
    3. */
    4. extern int gpio_request(unsigned gpio, const char *label);
    5. extern void gpio_free(unsigned gpio);

    上面这两个函数用来向系统中申请GPIO和释放已经申请的GPIO,在函数gpio_request()中传入的形参中,gpio为IO号,label为向系统中申请GPIO使用的标签,类似于GPIO的名称。

    1. /**
    2. * struct gpio - a structure describing a GPIO with configuration
    3. * @gpio: the GPIO number
    4. * @flags: GPIO configuration as specified by GPIOF_*
    5. * @label: a literal description string of this GPIO
    6. */
    7. struct gpio {
    8. unsigned gpio;
    9. unsigned long flags;
    10. const char *label;
    11. };

    结构体struct gpio用来描述一个需要配置的GPIO。

    1. extern int gpio_request_one(unsigned gpio, unsigned long flags, const char *label);
    2. extern int gpio_request_array(const struct gpio *array, size_t num);
    3. extern void gpio_free_array(const struct gpio *array, size_t num);

    上面的3个函数也是用来向系统申请或者释放GPIO资源,函数gpio_request_one()用来申请单个GPIO,但是在申请的时候可以设置flag标志,例如,该函数在申请GPIO资源的同时,直接将GPIO的方向设置为输入或者输出,函数gpio_request_array()和gpio_free_array()用来向系统中申请或者释放多个GPIO资源。

    1. /* CONFIG_GPIOLIB: bindings for managed devices that want to request gpios */
    2. struct device;
    3. int devm_gpio_request(struct device *dev, unsigned gpio, const char *label);
    4. int devm_gpio_request_one(struct device *dev, unsigned gpio,
    5. unsigned long flags, const char *label);
    6. void devm_gpio_free(struct device *dev, unsigned int gpio);

    上面的3个函数也是用来向系统申请或者释放GPIO资源,但是函数带有devm_前缀,也就是说,这是带设备资源管理版本的函数,因此在使用上面的函数时,需要指定设备的struct device指针。

    1. static inline int gpio_direction_input(unsigned gpio)
    2. {
    3. return gpiod_direction_input(gpio_to_desc(gpio));
    4. }
    5. static inline int gpio_direction_output(unsigned gpio, int value)
    6. {
    7. return gpiod_direction_output_raw(gpio_to_desc(gpio), value);
    8. }

    当我们使用gpio_request()函数向系统中申请了GPIO资源后,可以使用上面的函数进行GPIO的方向设置,函数gpio_direction_input()用来设置GPIO的方向为输入,函数gpio_direction_output()用来设置GPIO的方向为输出,并且通过value值可以设置输出的电平。

    1. static inline int gpio_get_value(unsigned int gpio)
    2. {
    3. return __gpio_get_value(gpio);
    4. }
    5. static inline void gpio_set_value(unsigned int gpio, int value)
    6. {
    7. __gpio_set_value(gpio, value);
    8. }

    当我们将GPIO的方向设置为输入时,可以使用上面的函数gpio_get_value()来获取当前的IO口电平值,当GPIO的方向设置为输出时,使用函数gpio_set_value()可以设置IO口的电平值。

    1. static inline int gpio_cansleep(unsigned int gpio)
    2. {
    3. return __gpio_cansleep(gpio);
    4. }

    使用函数gpio_cansleep()判断是否能处于休眠状态,当该函数返回非零值时,说明读或写GPIO的电平值时能够处于休眠状态。

    1. static inline int gpio_get_value_cansleep(unsigned gpio)
    2. {
    3. return gpiod_get_raw_value_cansleep(gpio_to_desc(gpio));
    4. }
    5. static inline void gpio_set_value_cansleep(unsigned gpio, int value)
    6. {
    7. return gpiod_set_raw_value_cansleep(gpio_to_desc(gpio), value);
    8. }

    上面的函数同样是获取或者设置GPIO的电平值,只不过是带休眠版本的函数。

    1. static inline int gpio_to_irq(unsigned int gpio)
    2. {
    3. return __gpio_to_irq(gpio);
    4. }

    函数gpio_to_irq()用于将当前已经申请GPIO号转换为IRQ号,也就是获取当前GPIO的中断线,函数调用成功后,将返回对应的IRQ号。

    以上就是Linux内核中GPIO子系统的常用的API接口,关于其代码的实现,可以进一步分析Linux内核源码。

    【文章福利】小编推荐自己的Linux内核技术交流群: 【977878001】整理一些个人觉得比较好得学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!前100进群领取,额外赠送一份 价值699的内核资料包(含视频教程、电子书、实战项目及代码)

    内核资料直通车:Linux内核源码技术学习路线+视频教程代码资料

    学习直通车:Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈

    3、实例说明

    在上面,已经分析过了Linux驱动中GPIO子系统的常用API接口函数,接下来,将通过一个具体的实例来讲解GPIO子系统中API接口如何使用。

    首先,先了解一下GPIO的使用思路,如下所示:

    1. #include <linux/module.h>
    2. #include <linux/init.h>
    3. #include <linux/gpio.h>
    4. ...
    5. struct gpio_drvdata {
    6. /* gpio号 */
    7. int gpio_num;
    8. ...
    9. };
    10. static int __init gpio_init(void)
    11. {
    12. struct gpio_drvdata *ddata;
    13. int ret;
    14. ddata = kzalloc(sizeof(*ddata), GFP_KERNEL);
    15. if (!ddata)
    16. return -ENOMEM;
    17. ...
    18. /* gpio初始化 */
    19. if (gpio_is_valid(ddata->gpio_num)) {
    20. /* 申请gpio资源 */
    21. ret = gpio_request(ddata->gpio_num, "test-gpio");
    22. if (ret) {
    23. printk("failed to request gpio\n");
    24. return ret;
    25. }
    26. /* 设置gpio的方向(输出) */
    27. ret = gpio_direction_output(ddata->gpio_num, 0);
    28. if (ret) {
    29. printk("failed to set output direction\n");
    30. return ret;
    31. }
    32. /* 在sysfs中导出gpio(方向能改变) */
    33. ret = gpio_export(ddata->gpio_num, true);
    34. if (ret) {
    35. printk("failed to export gpio in sysfs\n");
    36. return ret;
    37. }
    38. /* 设置gpio电平值(高电平) */
    39. gpio_set_value(ddata->gpio_num, 1);
    40. }
    41. ...
    42. return 0;
    43. }
    44. static void __exit gpio_exit(void)
    45. {
    46. ...
    47. /* 释放已经申请的gpio资源 */
    48. if (gpio_is_valid(ddata->gpio_num))
    49. gpio_free(ddata->gpio_num);
    50. ...
    51. }
    52. module_init(gpio_init);
    53. module_exit(gpio_exit);

    上面的代码已经很清楚地体现了GPIO的使用思路,当驱动模块加载的时候,需要获取要使用的GPIO号,然后需要向系统申请使用GPIO资源,资源申请成功后,我们需要设置GPIO的方向(输入或者输出),此外,还能使用gpio_export()函数在sysfs中导出GPIO,导出的好处在于可以方便地debug代码,当驱动模块卸载时,需要将已经申请的GPIO资源进行释放掉,基本的使用思路就这样,比较简单。

    接下来,给出具体的实例,功能为简单的GPIO控制,驱动程序中嵌入platform_driver框架,另外,在设备节点中导出ctrl和gpio两个属性文件,应用层对ctrl属性文件进行读写操作,能够获取和设置GPIO的电平状态,对gpio读操作,能够获取使用的GPIO号,下面是具体实例的实现过程:

    先定义相关的设备节点,如下:

    1. dev_gpio {
    2. status = "okay";
    3. compatible = "dev-gpio";
    4. label = "test_gpio";
    5. gpios = <&msm_gpio 68 0>;
    6. };

    使用了GPIO_68这个引脚,compatible属性的值用于和驱动程序进行匹配,接下来是驱动代码的实现:

    1. #include <linux/module.h>
    2. #include <linux/init.h>
    3. #include <linux/of.h>
    4. #include <linux/of_gpio.h>
    5. #include <linux/platform_device.h>
    6. #include <linux/kernel.h>
    7. #include <linux/slab.h>
    8. #include <linux/errno.h>
    9. #include <linux/gpio.h>
    10. #include <linux/sysfs.h>
    11. struct gpio_platform_data {
    12. const char *label;
    13. unsigned int gpio_num;
    14. enum of_gpio_flags gpio_flag;
    15. };
    16. struct gpio_drvdata {
    17. struct gpio_platform_data *pdata;
    18. bool gpio_state;
    19. };
    20. static ssize_t ctrl_show(struct device *dev,
    21. struct device_attribute *attr, char *buf)
    22. {
    23. struct gpio_drvdata *ddata = dev_get_drvdata(dev);
    24. int ret;
    25. if (ddata->gpio_state)
    26. ret = snprintf(buf, PAGE_SIZE - 2, "%s", "enable");
    27. else
    28. ret = snprintf(buf, PAGE_SIZE - 2, "%s", "disable");
    29. buf[ret++] = '\n';
    30. buf[ret] = '\0';
    31. return ret;
    32. }
    33. static ssize_t ctrl_store(struct device *dev,
    34. struct device_attribute *attr, const char *buf, size_t count)
    35. {
    36. struct gpio_drvdata *ddata = dev_get_drvdata(dev);
    37. bool state = ddata->gpio_state;
    38. if (!strncmp(buf, "enable", strlen("enable"))) {
    39. if (!state) {
    40. gpio_set_value(ddata->pdata->gpio_num, !state);
    41. ddata->gpio_state = !state;
    42. goto ret;
    43. }
    44. } else if (!strncmp(buf, "disable", strlen("disable"))) {
    45. if (state) {
    46. gpio_set_value(ddata->pdata->gpio_num, !state);
    47. ddata->gpio_state = !state;
    48. goto ret;
    49. }
    50. }
    51. return 0;
    52. ret:
    53. return strlen(buf);
    54. }
    55. static DEVICE_ATTR(ctrl, 0644, ctrl_show, ctrl_store);
    56. static ssize_t gpio_show(struct device *dev,
    57. struct device_attribute *attr, char *buf)
    58. {
    59. struct gpio_drvdata *ddata = dev_get_drvdata(dev);
    60. int ret;
    61. ret = snprintf(buf, PAGE_SIZE - 2, "gpio-number: GPIO_%d",
    62. ddata->pdata->gpio_num - 911);
    63. buf[ret++] = '\n';
    64. buf[ret] = '\0';
    65. return ret;
    66. }
    67. static DEVICE_ATTR(gpio, 0444, gpio_show, NULL);
    68. static struct attribute *gpio_attrs[] = {
    69. &dev_attr_ctrl.attr,
    70. &dev_attr_gpio.attr,
    71. NULL
    72. };
    73. static struct attribute_group attr_grp = {
    74. .attrs = gpio_attrs,
    75. };
    76. static struct gpio_platform_data *
    77. gpio_parse_dt(struct device *dev)
    78. {
    79. int ret;
    80. struct device_node *np = dev->of_node;
    81. struct gpio_platform_data *pdata;
    82. pdata = kzalloc(sizeof(*pdata), GFP_KERNEL);
    83. if (!pdata) {
    84. dev_err(dev, "failed to alloc memory of platform data\n");
    85. return NULL;
    86. }
    87. ret = of_property_read_string(np, "label", &pdata->label);
    88. if (ret) {
    89. dev_err(dev, "failed to read property of lable\n");
    90. goto fail;
    91. }
    92. pdata->gpio_num = of_get_named_gpio_flags(np, "gpios",
    93. 0, &pdata->gpio_flag);
    94. if (pdata->gpio_num < 0) {
    95. dev_err(dev, "invalid gpio number %d\n", pdata->gpio_num);
    96. ret = pdata->gpio_num;
    97. goto fail;
    98. }
    99. return pdata;
    100. fail:
    101. kfree(pdata);
    102. return ERR_PTR(ret);
    103. }
    104. static int gpio_probe(struct platform_device *pdev)
    105. {
    106. struct gpio_drvdata *ddata;
    107. struct gpio_platform_data *pdata;
    108. struct device *dev = &pdev->dev;
    109. struct device_node *np = dev->of_node;
    110. int ret;
    111. printk("[%s]==========gpio_probe start==========\n", __func__);
    112. if (!np) {
    113. dev_err(dev, "failed to find device node of gpio device\n");
    114. return -ENODEV;
    115. }
    116. ddata = kzalloc(sizeof(*ddata), GFP_KERNEL);
    117. if (!ddata) {
    118. dev_err(dev, "failed to alloc memory for driver data\n");
    119. return -ENOMEM;
    120. }
    121. pdata = gpio_parse_dt(dev);
    122. if (IS_ERR(pdata)) {
    123. dev_err(dev, "failed to parse device node\n");
    124. ret = PTR_ERR(pdata);
    125. goto fail1;
    126. }
    127. if (gpio_is_valid(pdata->gpio_num)) {
    128. ret = gpio_request(pdata->gpio_num, pdata->label);
    129. if (ret) {
    130. dev_err(dev, "failed to request gpio number %d\n",
    131. pdata->gpio_num);
    132. goto fail2;
    133. }
    134. ret = gpio_direction_output(pdata->gpio_num, 0);
    135. if (ret) {
    136. dev_err(dev, "failed to set gpio direction for output\n");
    137. goto fail3;
    138. }
    139. ret = gpio_export(pdata->gpio_num, false);
    140. if (ret) {
    141. dev_err(dev, "failed to export gpio %d\n", pdata->gpio_num);
    142. goto fail3;
    143. }
    144. }
    145. ddata->gpio_state = false;
    146. ddata->pdata = pdata;
    147. platform_set_drvdata(pdev, ddata);
    148. ret = sysfs_create_group(&dev->kobj, &attr_grp);
    149. if (ret) {
    150. dev_err(dev, "failed to create sysfs files\n");
    151. goto fail3;
    152. }
    153. printk("[%s]==========gpio_probe over==========\n", __func__);
    154. return 0;
    155. fail3:
    156. gpio_free(pdata->gpio_num);
    157. fail2:
    158. kfree(pdata);
    159. fail1:
    160. kfree(ddata);
    161. return ret;
    162. }
    163. static int gpio_remove(struct platform_device *pdev)
    164. {
    165. struct gpio_drvdata *ddata = platform_get_drvdata(pdev);
    166. struct gpio_platform_data *pdata = ddata->pdata;
    167. sysfs_remove_group(&pdev->dev.kobj, &attr_grp);
    168. if (gpio_is_valid(pdata->gpio_num))
    169. gpio_free(pdata->gpio_num);
    170. kfree(pdata);
    171. pdata = NULL;
    172. kfree(ddata);
    173. ddata = NULL;
    174. return 0;
    175. }
    176. static struct of_device_id device_match_table[] = {
    177. { .compatible = "dev-gpio",},
    178. { },
    179. };
    180. MODULE_DEVICE_TABLE(of, device_match_table);
    181. static struct platform_driver dev_gpio_driver = {
    182. .probe = gpio_probe,
    183. .remove = gpio_remove,
    184. .driver = {
    185. .name = "dev-gpio",
    186. .owner = THIS_MODULE,
    187. .of_match_table = device_match_table,
    188. },
    189. };
    190. module_platform_driver(dev_gpio_driver);
    191. MODULE_AUTHOR("HLY");
    192. MODULE_LICENSE("GPL v2");

    实现的思路和前面给出的模板一样,只不过是嵌入了platform_driver这个驱动框架,另外,在设备节点中导出了ctrl和gpio属性文件,便可以很方便地在应用层进行设备的GPIO控制了。

    接下来,看看实现的效果,首先是生成的设备节点信息,可以使用下面的命令:

    1. # ls -al
    2. # cat uevent

    输出如下:

    通过uevent可以看到设备节点的路径以及驱动和设备匹配的属性值,此外,在上面图片中,也可以看到ctrl和gpio属性文件已经被成功导出到了该设备节点下面,使用下面的命令可以进行GPIO的控制:

    1. ##将GPIO置高电平
    2. # echo "enable" > ctrl
    3. ##将GPIO置低电平
    4. # echo "disable" > ctrl

    控制的效果如下所示:

    另外,在驱动程序中,我们使用了函数gpio_export()在sysfs中导出相关的GPIO信息,我们可以到/sys/class/gpio/gpioN目录下查看相关的GPIO信息,如下:

    属性文件value保存了当前GPIO的电平值,当我们调用gpio_export()函数时,将第二个形参传入为true时,表示GPIO的方向还能改变,将在上面的目录中生成direction属性文件,里面保存了当前GPIO的方向,我们还能使用echo命令对文件进行写操作,从而改变GPIO的方向。

    4、小结

    本文简单介绍了Linux中GPIO子系统中的常用的API接口函数,并且给出了驱动程序中使用GPIO子系统的思路,另外还通过一个简单的实例进行说明。

  • 相关阅读:
    我的创作纪念日
    【EI会议征稿】第三届大数据、信息与计算机网络国际学术会议(BDICN 2024)
    Win11一键重装系统后如何使用自带的故障检测修复功能
    解决Edge游览器龟速下载问题
    【Windows】键盘禁用(屏蔽)Win快捷键
    20省市公布元宇宙路线图
    实验三-----数据库
    【Leetcode】1035. Uncrossed Lines
    Java内存模型(JMM)详解
    国自然中标越来越难,怎样才能赢在起跑线上?
  • 原文地址:https://blog.csdn.net/m0_74282605/article/details/127847838