我是靠谱客的博主 活力毛衣,这篇文章主要介绍RK3568 Android12 gpio驱动实现(三),现在分享给大家,希望可以做个参考。

Platform: RK3568
OS: Android 12
Kernel: v4.19.206
SDK Version:android-12.0-mid-rkr1
Module: gpio


目标

承接上文 RK3568 Android12 gpio驱动实现(二),添加gpio的direction和value节点用于读写。

主要代码

  1. 主要参考kernel源码的drivers/gpio/gpiolib-sysfs.c,实现store和show函数,创建读写节点,填充twgpio_class结构体
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
struct gpio_desc *tw_gpio1; static ssize_t tw_gpio1_direction_show(struct device *dev, struct device_attribute *attr, char *buf){ ssize_t status; if (!tw_gpio1) { dev_err(dev, "tw_gpio1 failed!n"); return -EFAULT; } else { gpiod_get_direction(tw_gpio1); status = sprintf(buf, "%sn", test_bit(FLAG_IS_OUT, &tw_gpio1->flags) ? "out" : "in"); } return status; } static ssize_t tw_gpio1_direction_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size){ ssize_t status; if(sysfs_streq(buf, "out")|| sysfs_streq(buf, "low")) { status = gpiod_direction_output_raw(tw_gpio1, 0); } else if(sysfs_streq(buf, "high")) { status = gpiod_direction_output_raw(tw_gpio1, 1); } else if(sysfs_streq(buf, "in")) { status = gpiod_direction_input(tw_gpio1); } else { pr_warn("%s: tw_gpio1_direction: Invalid argument!n", __func__); return -EINVAL; } return status ? : size; } static DEVICE_ATTR_RW(tw_gpio1_direction); static ssize_t tw_gpio1_value_show(struct device *dev, struct device_attribute *attr, char *buf){ ssize_t status; status = gpiod_get_raw_value_cansleep(tw_gpio1); if (status < 0) { pr_warn("ERROR status = %zd n", status); return -EINVAL; } buf[0] = '0' + status; buf[1] = 'n'; status = 2; return status; } static ssize_t tw_gpio1_value_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count){ if(sysfs_streq(buf, "1")) { gpiod_set_raw_value_cansleep(tw_gpio1, 1); } else if(sysfs_streq(buf, "0")) { gpiod_set_raw_value_cansleep(tw_gpio1, 0); } else { pr_warn("tw_gpio1_value: Invalid argument!n"); return -EINVAL; } return count; } static DEVICE_ATTR_PREALLOC(tw_gpio1_value, S_IWUSR | S_IRUGO, tw_gpio1_value_show, tw_gpio1_value_store); static struct attribute *twgpio_class_attrs[] = { &dev_attr_tw_gpio1_direction.attr, &dev_attr_tw_gpio1_value.attr, NULL, }; ATTRIBUTE_GROUPS(twgpio_class); static struct class twgpio_class = { .name = "twgpio", .owner = THIS_MODULE, .class_groups = twgpio_class_groups, };
  1. 在probe中获取gpio,设置默认的方向和值,注册twgpio_class
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
tw_gpio1 = devm_gpiod_get(dev, "tw1", GPIOD_OUT_LOW); if (IS_ERR(tw_gpio1)) { dev_warn(dev, "Failed to get tw_gpio1n"); } gpiod_direction_output_raw(tw_gpio1,0); pr_info("%s:terry gpio set tw-gpio1 output default lown",__func__); ret = class_register(&twgpio_class); pr_info("%s: ret = %dn", __func__, ret); if(ret < 0) { return -EINVAL; }
  1. dts配置中的gpio属性改写为
    tw1-gpios = <&gpio0 RK_PC3 GPIO_ACTIVE_LOW>;

新接口简介

从上面代码中可以看到,与上篇文章主要用到gpio_get_value和gpio_set_value接口不同,本篇主要是用到了gpiod_ 前缀的接口来实现功能,这是linux的gpio子系统推荐使用的新接口。根据参考资料1 2,目前gpio子系统提供有2套API接口:

  • legacy API:integer-based GPIO interface,形式为 gpio_xxx(),例如 void gpio_set_value(unsigned gpio, int value),不推荐使用该 API;

  • 推荐 API: descriptor-based GPIO interface,形式为 gpiod_xxx(),例如 void gpiod_set_value(struct gpio_desc *desc, int value),新添加的驱动代码一律采用这套 API。

gpiod_xxx() API
在 gpio 子系统中,用 struct gpio_desc 来描述一个 gpio 引脚,gpiod_xxx() 都是围绕着 strcut gpio_desc 进行操作的。

完整的接口定义位于 linux/gpio/consumer.h,大约共有 70个 API。

常用 API:
获得/释放 一个或者一组 gpio:
[devm]_gpiod_get*()
[devm]_gpiod_put*()

设置/查询 输入或者输出:
gpiod_direction_input()
gpiod_direction_output()
gpiod_get_direction()

读写一个 gpio:
gpiod_get_value()
gpiod_set_value()
gpiod_get_value_cansleep()
gpiod_set_value_cansleep()

用上一节代码中用到的几个接口进行简要说明:

  1. devm_gpiod_get()来获取gpio,根据参考资料34和内核源码,该接口是有资源管理功能的,可以自动释放资源。跟一下调用关系:
    devm_gpiod_get ->
    devm_gpiod_get_index-> index=0
    gpiod_get_index

可以看出该接口实际是对gpiod_get_index 的封装,其中index为0。
gpiod_get_index接口定义:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
struct gpio_desc *__must_check gpiod_get_index(struct device *dev, const char *con_id, unsigned int idx, enum gpiod_flags flags) { struct gpio_desc *desc = NULL; int status; enum gpio_lookup_flags lookupflags = 0; /* Maybe we have a device name, maybe not */ const char *devname = dev ? dev_name(dev) : "?"; dev_dbg(dev, "GPIO lookup for consumer %sn", con_id); if (dev) { /* Using device tree? */ if (IS_ENABLED(CONFIG_OF) && dev->of_node) { dev_dbg(dev, "using device tree for GPIO lookupn"); desc = of_find_gpio(dev, con_id, idx, &lookupflags); } else if (ACPI_COMPANION(dev)) { dev_dbg(dev, "using ACPI for GPIO lookupn"); desc = acpi_find_gpio(dev, con_id, idx, &flags, &lookupflags); } } …… status = gpiod_request(desc, con_id ? con_id : devname); if (status < 0) return ERR_PTR(status); status = gpiod_configure_flags(desc, con_id, lookupflags, flags); if (status < 0) { dev_dbg(dev, "setup of GPIO %s failedn", con_id); gpiod_put(desc); return ERR_PTR(status); }

该接口已经做了3个动作,分别是“of_find_gpio”,“gpiod_request”,“gpiod_configure_flags”;
这3个函数,整合了从devicetree获取gpio,请求gpio和初始化gpio这3步,比较方便。
但是要注意该接口对dts中gpio属性的命名有要求,推荐要写为xxx-gpios的形式,然后con_id中直接填前缀xxx就可以识别到了。具体可以看of_find_gpio()的接口定义:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct gpio_desc *of_find_gpio(struct device *dev, const char *con_id, unsigned int idx, enum gpio_lookup_flags *flags) { char prop_name[32]; /* 32 is max size of property name */ enum of_gpio_flags of_flags; struct gpio_desc *desc; unsigned int i; /* Try GPIO property "foo-gpios" and "foo-gpio" */ for (i = 0; i < ARRAY_SIZE(gpio_suffixes); i++) { if (con_id) snprintf(prop_name, sizeof(prop_name), "%s-%s", con_id, gpio_suffixes[i]); else snprintf(prop_name, sizeof(prop_name), "%s", gpio_suffixes[i]); desc = of_get_named_gpiod_flags(dev->of_node, prop_name, idx, &of_flags); ……

可以看到gpio的属性名prop_name 是由con_id(如果有的话) 和gpio_suffixes[i] 拼接而成,然后是调用of_get_named_gpiod_flags()来获取gpio。gpio_suffixes[]的定义如下,后缀用gpios或者gpio都可以。

复制代码
1
2
3
/* gpio suffixes used for ACPI and device tree lookup */ static __maybe_unused const char * const gpio_suffixes[] = { "gpios", "gpio" };
  1. gpiod_direction_output_raw() 设置gpio输出,输出电平为实际物理电平, 不考虑ACTIVE_LOW 的状态。

  2. gpiod_get_raw_value_cansleep() 和gpiod_set_raw_value_cansleep()
    同样也是不考虑ACTIVE_LOW 的状态,读写gpio的实际物理电平。以 _cansleep 为后缀的函数是可能会睡眠的 API,不可以在 hard (non-threaded) IRQ handlers 中使用。

功能测试

可以看到对value和direction节点进行cat/echo 操作都是基本OK的。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
console:/ # cat /d/gpio |grep tw gpio-19 ( |tw1 ) out lo console:/sys/class/twgpio # ls tw_gpio1_direction tw_gpio1_value console:/sys/class/twgpio # cat tw tw_gpio1_direction tw_gpio1_value console:/sys/class/twgpio # cat tw_gpio1_direction out console:/sys/class/twgpio # cat t tw_gpio1_direction tw_gpio1_value console:/sys/class/twgpio # cat tw_gpio1_value 0 console:/sys/class/twgpio # echo high > tw_gpio1_direction console:/sys/class/twgpio # cat tw_gpio1_direction out console:/sys/class/twgpio # cat tw_gpio1_value 1 console:/sys/class/twgpio # echo in >tw_gpio1_direction console:/sys/class/twgpio # cat tw_gpio1_direction in console:/sys/class/twgpio # cat tw_gpio1_value 0

小结与展望

已实现用新接口在/sys/class/twgpio/目录下生成value和direction 节点用于读写gpio的方向和值。下一步想要在dts中配置子节点,并在class目录下生成对应的sysfs子目录,每个目录均可读写gpio的value和direction。


如有谬误欢迎指正,感谢阅读~

参考资料


  1. Linux 驱动开发 / gpio子系统 / 快速入门 ↩︎

  2. Documentation/driver-api/gpio/ ↩︎

  3. GPIO系列(2)——Linux的GPIO控制“gpiod_”和“gpio_”浅析 ↩︎

  4. [RK3399][Android7.1] 获取gpio函数devm_gpiod_get_optional() ↩︎

最后

以上就是活力毛衣最近收集整理的关于RK3568 Android12 gpio驱动实现(三)的全部内容,更多相关RK3568内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(59)

评论列表共有 0 条评论

立即
投稿
返回
顶部