当前位置:   article > 正文

【正点原子Linux连载】 第四十三章 耳机插拨驱动实验摘自【正点原子】ATK-DLRK3568嵌入式Linux驱动开发指南

【正点原子Linux连载】 第四十三章 耳机插拨驱动实验摘自【正点原子】ATK-DLRK3568嵌入式Linux驱动开发指南

1)实验平台:正点原子ATK-DLRK3568开发板
2)平台购买地址:https://detail.tmall.com/item.htm?id=731866264428
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/docs/boards/xiaoxitongban

第四十三章 耳机插拨驱动实验

耳机插拨在我们日常生活中经常遇到,在某些老款安卓手机还可以看到耳机孔的身影,当我们用耳机插到手机的耳机孔时,手机的外放就停止播放声音。逐渐地,手机上的耳机孔被淘汰了,已经被蓝牙耳机取代,现在主要是一些音响设备需要检测耳机插拨。
Linux 耳机插拨驱动实验是一项涉及到 Linux 内核开发和设备驱动编程的实验。该实验的主要目标是开发一个驱动程序,用于检测和响应耳机插拨事件,以便在插入或拔出耳机时执行相应的操作。Linux耳机插拨驱动不用我们写,内核早已有这个驱动。本章我们分析Linux耳机插拨驱动。

43.1 硬件原理图
正点原子的ATK-DLRK3568开发板底板板载了耳机孔接口,原理图如下所示:
在这里插入图片描述

图43.1.1 板载耳机孔接口
在上图图43.1.1中我们可以看到用于耳机插拨使用的引脚HP_DET_L是GPIO1_A4。
ATK-DLRK3568底板的耳机插孔如下图位置。
在这里插入图片描述

图43.1.2 底板耳机孔接口
43.2 实验程序编写
本实验对应的例程路径为:开发板光盘01、程序源码03、Linux驱动例程27_hp_det_test。
43.2.1 修改设备树
打开rk3568-linux.dtsi文件,找到如下代码。
示例代码43.2.1耳机检测GPIO配置

1   rk809_sound: rk809-sound {
2           status = "okay";
3           compatible = "simple-audio-card";
4           simple-audio-card,format = "i2s";
5           //simple-audio-card,hp-det-gpio = <&gpio3 RK_PC2 GPIO_ACTIVE_HIGH>;
6           simple-audio-card,hp-det-gpio = <&gpio1 RK_PA4 GPIO_ACTIVE_LOW>;
7           simple-audio-card,name = "rockchip,rk809-codec";
8           simple-audio-card,widgets = "Headphones", "Headphones Jack";
9           simple-audio-card,mclk-fs = <256>;
10          simple-audio-card,cpu {
11                  sound-dai = <&i2s1_8ch>;
12          };
13          simple-audio-card,codec {
14                  sound-dai = <&rk809_codec>;
15          };
16  };
17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
第6行,设置了耳机检测GPIO为GPIO1_A4。
我们现在从设备树开始,先看看“hp-det-gpio”是在哪个驱动文件设置的。我们从源码中找,发现是在内核源码目录中,是在sound/soc/generic/simple-card-utils.c这个驱动文件。那么我们来看看耳机插拨驱动实现的流程。
  • 1
  • 2

示例代码45.2.2耳机插拨检测GPIO初始化

423 int asoc_simple_card_init_jack(struct snd_soc_card *card,
424                    struct asoc_simple_jack *sjack,
425                    int is_hp, char *prefix)
426 {
427     struct device *dev = card->dev;
428     enum of_gpio_flags flags;
429     char prop[128];
430     char *pin_name;
431     char *gpio_name;
432     int mask;
433     int det;
434
435     if (!prefix)
436         prefix = "";
437
438     sjack->gpio.gpio = -ENOENT;
439
440     if (is_hp) {
441         snprintf(prop, sizeof(prop), "%shp-det-gpio", prefix);
442         pin_name    = "Headphones";
443         gpio_name   = "Headphone detection";
444         mask        = SND_JACK_HEADPHONE;
445     } else {
446         snprintf(prop, sizeof(prop), "%smic-det-gpio", prefix);
447         pin_name    = "Mic Jack";
448         gpio_name   = "Mic detection";
449         mask        = SND_JACK_MICROPHONE;
450     }
451
452     det = of_get_named_gpio_flags(dev->of_node, prop, 0, &flags);
453     if (det == -EPROBE_DEFER)
454         return -EPROBE_DEFER;
455
456     if (gpio_is_valid(det)) {
457         sjack->pin.pin      = pin_name;
458         sjack->pin.mask     = mask;
459
460         sjack->gpio.name    = gpio_name;
461         sjack->gpio.report  = mask;
462         sjack->gpio.gpio    = det;
463         sjack->gpio.invert  = !!(flags & OF_GPIO_ACTIVE_LOW);
464         sjack->gpio.debounce_time = 150;
465
466         snd_soc_card_jack_new(card, pin_name, mask,
467                       &sjack->jack,
468                       &sjack->pin, 1);
469
470         snd_soc_jack_add_gpios(&sjack->jack, 1,
471                        &sjack->gpio);
472     }
473
474     return 0;
475 }
476 EXPORT_SYMBOL_GPL(asoc_simple_card_init_jack);
  • 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
第440~445行,构建用于查找设备树中 GPIO 属性的属性名称 prop。设置 pin_name 为 "Headphones",表示插孔的名称。设置 gpio_name 为 "Headphone detection",表示 GPIO 的名称。设置 mask 为 SND_JACK_HEADPHONE,表示这是一个耳机插孔。
第452行,使用设备树函数 of_get_named_gpio_flags 获取与属性名称 prop 关联的 GPIO 描述符,并存储在 det 中。如果 GPIO 未定义,det 将为负数。
第456~464行,如果设置了检测GPIO,那么设置结构体指针sjack的一些属性。设置插孔的引脚信息,比如sjack->pin.pin引脚名字。设置耳机插孔的一些GPIO关联信息,如sjack->gpio.gpio是表示GPIO 描述符,sjack->gpio.invert表示根据设备树中的属性决定是否反转 GPIO 状态,在上面的设备树中,我们设置了GPIO_ACTIVE_LOW,说明是低电平表示活动。从硬件原理图可知,当耳机插入时,检测脚将被拉低,说明是低有效。debounce_time这个是设置消抖时间,防止误检测。
第466~468行,这里将耳机插孔与声卡绑定。
第470~471行,绑定GPIO,就会触发耳机插拨事件。
打开include/sound/simple_card_utils.h头文件,找到以下内容。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

示例代码45.2.3耳机插拨检测GPIO重新定义

13 #define asoc_simple_card_init_hp(card, sjack, prefix) \
 14         asoc_simple_card_init_jack(card, sjack, 1, prefix)
 15 #define asoc_simple_card_init_mic(card, sjack, prefix) \
 16         asoc_simple_card_init_jack(card, sjack, 0, prefix)
  • 1
  • 2
  • 3
  • 4
从上面的代码可知道,asoc_simple_card_init_jack被重新定义为asoc_simple_card_init_hp。因为有耳机检测也有MIC检测,内核里将它们用不同的函数命名区分,但是用的实际是同一个函数。参数“1”,代表这就是一个耳机设备!
打开sound/soc/generic/simple-card.c文件。找到以下内容。
  • 1
  • 2

示例代码45.2.4耳机插拨检测GPIO初始化声卡

342 static int asoc_simple_soc_card_probe(struct snd_soc_card *card)
343 {
344         struct simple_card_data *priv = snd_soc_card_get_drvdata(card);
345         int ret;
346 
347         ret = asoc_simple_card_init_hp(card, &priv->hp_jack, PREFIX);
348         if (ret < 0)
349                 return ret;
350 
351         ret = asoc_simple_card_init_mic(card, &priv->mic_jack, PREFIX);
352         if (ret < 0)
353                 return ret;
354 
355         return 0;
356 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
这个驱动文件负责声卡的初始化,音频流管理,控制接口等。在第347行,调用了耳机检测IO初始化的代码。
上面的simple-card.c似乎没有上报耳机检测事件数据,所以我们要返回到前面的代码查找是如何实现的。
在示例代码45.2.2中的asoc_simple_card_init_jack这个函数里,查看snd_soc_card_jack_new函数,这个函数定义在sound/soc/soc-jack.c文件中。
  • 1
  • 2
  • 3

示例代码45.2.5 创建一个新的插孔

59  int snd_soc_card_jack_new(struct snd_soc_card *card, const char *id, int type,
60      struct snd_soc_jack *jack, struct snd_soc_jack_pin *pins,
61      unsigned int num_pins)
62  {
63      int ret;
64
65      mutex_init(&jack->mutex);
66      jack->card = card;
67      INIT_LIST_HEAD(&jack->pins);
68      INIT_LIST_HEAD(&jack->jack_zones);
69      BLOCKING_INIT_NOTIFIER_HEAD(&jack->notifier);
70
71      ret = snd_jack_new(card->snd_card, id, type, &jack->jack, false, false);
72      if (ret)
73          return ret;
74
75      if (num_pins)
76          return snd_soc_jack_add_pins(jack, num_pins, pins);
77
78      return 0;
79  }
80  EXPORT_SYMBOL_GPL(snd_soc_card_jack_new);76行,我们看到是snd_soc_jack_add_pins函数添加检测管脚pins。
	找到snd_soc_jack_add_pins函数定义的地方,也是在sound/soc/soc-jack.c文件中。
200 int snd_soc_jack_add_pins(struct snd_soc_jack *jack, int count,
201               struct snd_soc_jack_pin *pins)
202 {
203     int i;
204
205     for (i = 0; i < count; i++) {
206         if (!pins[i].pin) {
207             dev_err(jack->card->dev, "ASoC: No name for pin %d\n",
208                 i);
209             return -EINVAL;
210         }
211         if (!pins[i].mask) {
212             dev_err(jack->card->dev, "ASoC: No mask for pin %d"
213                 " (%s)\n", i, pins[i].pin);
214             return -EINVAL;
215         }
216
217         INIT_LIST_HEAD(&pins[i].list);
218         list_add(&(pins[i].list), &jack->pins);
219         snd_jack_add_new_kctl(jack->jack, pins[i].pin, pins[i].mask);
220     }
221
222     /* Update to reflect the last reported status; canned jack
223      * implementations are likely to set their state before the
224      * card has an opportunity to associate pins.
225      */
226     snd_soc_jack_report(jack, 0, 0);
227
228     return 0;
229 }
230 EXPORT_SYMBOL_GPL(snd_soc_jack_add_pins);
  • 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
第226行,snd_soc_jack_report函数上报声卡关联前的耳机插孔状态。
再找到snd_soc_jack_report函数定义,也是在sound/soc/soc-jack.c文件中。
  • 1
  • 2

示例代码45.2.6 上报接口的当前状态

96  void snd_soc_jack_report(struct snd_soc_jack *jack, int status, int mask)
97  {
98      struct snd_soc_dapm_context *dapm;
99      struct snd_soc_jack_pin *pin;
100     unsigned int sync = 0;
101     int enable;
102
103     if (!jack)
104         return;
105     trace_snd_soc_jack_report(jack, mask, status);
106
107     dapm = &jack->card->dapm;
108
109     mutex_lock(&jack->mutex);
110
111     jack->status &= ~mask;
112     jack->status |= status & mask;
113
114     trace_snd_soc_jack_notify(jack, status);
115
116     list_for_each_entry(pin, &jack->pins, list) {
117         enable = pin->mask & jack->status;
118
119         if (pin->invert)
120             enable = !enable;
121
122         if (enable)
123             snd_soc_dapm_enable_pin(dapm, pin->pin);
124         else
125             snd_soc_dapm_disable_pin(dapm, pin->pin);
126
127         /* we need to sync for this case only */
128         sync = 1;
129     }
130
131     /* Report before the DAPM sync to help users updating micbias status */
132     blocking_notifier_call_chain(&jack->notifier, jack->status, jack);
133
134     if (sync)
135         snd_soc_dapm_sync(dapm);
136
137     snd_jack_report(jack->jack, jack->status);
138
139     mutex_unlock(&jack->mutex);
140 }
141 EXPORT_SYMBOL_GPL(snd_soc_jack_report);
  • 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
第137行,看到上报数据是交给snd_jack_report 处理的。
打开sound/core/jack.c文件,找到snd_jack_report函数如下。
  • 1
  • 2

示例代码45.2.7 上报插拨事件功能代码

365 void snd_jack_report(struct snd_jack *jack, int status)
366 {
367     struct snd_jack_kctl *jack_kctl;
368 #ifdef CONFIG_SND_JACK_INPUT_DEV
369     int i;
370 #endif
371
372     if (!jack)
373         return;
374
375     list_for_each_entry(jack_kctl, &jack->kctl_list, list)
376         snd_kctl_jack_report(jack->card, jack_kctl->kctl,
377                         status & jack_kctl->mask_bits);
378
379 #ifdef CONFIG_SND_JACK_INPUT_DEV
380     if (!jack->input_dev)
381         return;
382
383     for (i = 0; i < ARRAY_SIZE(jack->key); i++) {
384         int testbit = SND_JACK_BTN_0 >> i;
385
386         if (jack->type & testbit)
387             input_report_key(jack->input_dev, jack->key[i],
388                      status & testbit);
389     }
390
391     for (i = 0; i < ARRAY_SIZE(jack_switch_types); i++) {
392         int testbit = 1 << i;
393         if (jack->type & testbit)
394             input_report_switch(jack->input_dev,
395                         jack_switch_types[i],
396                         status & testbit);
397     }
398
399     input_sync(jack->input_dev);
400 #endif /* CONFIG_SND_JACK_INPUT_DEV */
401 }
402 EXPORT_SYMBOL(snd_jack_report);
  • 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
第387和388行,上报事件,参数1为耳机事件类型,参数2为耳机事件键值,参数3表示耳机插拨的状态。
第399行,最后同步事件,上报事件与我们前面的触摸屏驱动类似。
耳机插拨是有个中断服务函数的,详细看代码示例代码45.2.2中的snd_soc_jack_add_gpios,最终也会调用上面的snd_jack_report来上报耳机插拨事件。这里我们就不分析了,有兴趣可以自己一步步查找。
  • 1
  • 2
  • 3

43.2.2 编写测试APP
新建hp_det_test.c文件,然后在里面输入如下所示内容:

/*******************************************************************************
Guangzhou Xingyi Electronic  Technology Co., Ltd 2021-2030. All rights reserved.
* @brief         hp_det_test.c
* @author        正点原子Linux团队
* @date          2023-06-26
* @link          http://www.openedv.com/forum.php
********************************************************************************/
1   #include <stdio.h>
2   #include <stdlib.h>
3   #include <sys/types.h>
4   #include <sys/stat.h>
5   #include <fcntl.h>
6   #include <unistd.h>
7   #include <sys/ioctl.h>
8   #include <linux/input.h>
9   #include <pthread.h>
10  #include <dirent.h>
11
12  int main(void)
13  {
14      struct input_event ev = {0};
15      int fd  = -1;
16      DIR *dir = NULL;
17
18      setbuf(stdout, NULL);
19      printf("=== 耳机插拨测试 ===\n");
20
21      dir = opendir("/dev/input");
22      if(dir != NULL) {
23          struct dirent *de = NULL;
24          char name[64];
25
26          while((de = readdir(dir))) {
27              if(strncmp(de->d_name, "event", 5))
28                  continue;
29
30              fd = openat(dirfd(dir), de->d_name, O_RDONLY);
31              if(fd < 0)
32                  continue;
33
34              if (ioctl(fd, EVIOCGNAME(sizeof(name) - 1), &name) < 1)
35                  name[0] = '\0';
36              if (strcmp(name, "rockchip,rk809-codec Headphones")) {
37                  close(fd);
38                  continue;
39              }
40
41              closedir(dir);
42              goto start_read;
43          }
44
45          closedir(dir);
46          goto err_out;
47      } else
48          goto err_out;
49
50  start_read:
51      for ( ; ; ) {
52
53          if (read(fd, &ev, sizeof(ev)) != sizeof(ev)) {
54              printf("read failed!\n");
55              goto err_out1;
56          }
57
58          if (ev.type == EV_SW && ev.code == SW_HEADPHONE_INSERT) {
59              if (ev.value == 1)
60                  printf("耳机插入\n");
61              else
62                  printf("耳机拔出\n");
63          }
64      }
65
66      return 0;
67
68  err_out1:
69      close(fd);
70
71  err_out:
72      printf("===  耳机测试结束 ===\n");
73      return -1;
74  }
  • 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
App程序也是很简单,耳机插拨也是一种input事件,我们直接在input事件用遍历方法找到耳机插孔事件。
第58~63行,判断事件类型为EV_SW,这是一个开关(Switch)事件,SW_HEADPHONE_INSERT是EV_SW事件类型的一个子事件码,表示耳机插入/拨出,后面再判断事件值,表示耳机的插入状态,1表示插入,0表示拨出。
  • 1
  • 2

43.2.3 运行测试
43.2.3.1 编译测试
输入如下命令编译hp_det_test.c:
/opt/atk-dlrk356x-toolchain/bin/aarch64-buildroot-linux-gnu-gcc hp_det_test.c -o hp_det_test
编译成功以后就会生成hp_det_test。
43.2.3.2 运行测试
在Ubuntu中将上一小节编译出来的hp_det_test通过adb命令发送到开发板的/lib/modules/4.19.232目录下,命令如下:
adb push hp_det_test /lib/modules/4.19.232
本章的驱动默认已经在发布的内核上了,所以我们无需编译驱动,直接在Linux Buildroot根文件系统上测试,我们先测试加速度,输入如下指令。然后使用耳机插头直接插到底板上的耳机孔位。插拨测试会打印“耳机插入”与“耳机拨出”字样。
./hp_det_test

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/634175
推荐阅读
相关标签
  

闽ICP备14008679号