Hello! 欢迎来到小浪云!


Linux 自带的耳机拔插检测驱动


linux系统默认的耳机插拔检测驱动程序被整合在声卡驱动中,这使得耳机插拔状态能够通过输入子系统进行报告。

这一功能的具体实现位于kernel-5.15版本的sound/soc/Generic/simple-card-utils.c文件中。

571  int asoc_simple_init_jack(struct snd_soc_card *card, 572       struct asoc_simple_jack *sjack, 573       int is_hp, char *prefix, 574       char *pin) 575  { 576   struct device *dev = card->dev; 577   enum of_gpio_flags flags; 578   char prop[128]; 579   char *pin_name; 580   char *gpio_name; 581   int mask; 582   int det; 583   584   if (!prefix) 585    prefix = ""; 586   587   sjack->gpio.gpio = -ENOENT; 588   589   if (is_hp) { 590    snprintf(prop, sizeof(prop), "%shp-det-gpio", prefix); 591    pin_name = pin ? pin : "Headphones"; 592    gpio_name = "Headphone detection"; 593    mask  = SND_JACK_HEADPHONE; 594   } else { 595    snprintf(prop, sizeof(prop), "%smic-det-gpio", prefix); 596    pin_name = pin ? pin : "Mic Jack"; 597    gpio_name = "Mic detection"; 598    mask  = SND_JACK_MICROPHONE; 599   } 600   601   det = of_get_named_gpio_flags(dev->of_node, prop, 0, &flags); 602   if (det == -EPROBE_DEFER) 603    return -EPROBE_DEFER; 604   605   if (gpio_is_valid(det)) { 606    sjack->pin.pin  = pin_name; 607    sjack->pin.mask  = mask; 608   609    sjack->gpio.name = gpio_name; 610    sjack->gpio.report = mask; 611    sjack->gpio.gpio = det; 612    sjack->gpio.invert = !!(flags & OF_GPIO_ACTIVE_LOW); 613    sjack->gpio.debounce_time = 150; 614   615    snd_soc_card_jack_new(card, pin_name, mask, 616            &sjack->jack, 617            &sjack->pin, 1); 618   619    snd_soc_jack_add_gpios(&sjack->jack, 1, 620             &sjack->gpio); 621   } 622   623   return 0; 624  } 625  EXPORT_SYMBOL_GPL(asoc_simple_init_jack); 

第 589~593 行,【构建】用于查找设备树中 GPIO 属性的属性名称 prop。设置 pin_name 为”Headphones”,表示插孔的名称。设置 gpio_name 为 “Headphone detection”,表示 GPIO 的名称。设置 mask 为 SND_JACK_HEADPHONE,表示这是一个耳机插孔。

第 601 行,使用设备树函数 of_get_named_gpio_flags 获取与属性名称 prop 关联的 GPIO 描述符,并存储在 det 中。如果 GPIO 未定义,det 将为负数。

第 606~613 行,如果设置了检测 GPIO,那么设置结构体指针 sjack 的一些属性。设置插孔的引脚信息,比如 sjack->pin.pin 引脚名字。设置耳机插孔的一些 GPIO 关联信息,如 sjack->gpio.gpio 是表示 GPIO 描述符,sjack->gpio.invert 表示根据设备树中的属性决定是否反转 GPIO 状态,GPIO_ACTIVE_LOW 是低电平表示活动,当耳机插入时,检测脚将被拉低,说明是低有效。debounce_time 这个是设置消抖时间,防止误检测。

第 615 行,这里将耳机插孔与声卡绑定。

第 619 行,绑定 GPIO,就会触发耳机插拨事件

函数名重定义:/include/sound/simple_card_utils.h

14  #define asoc_simple_init_hp(card, sjack, prefix)  15   asoc_simple_init_jack(card, sjack, 1, prefix, NULL) 16  #define asoc_simple_init_mic(card, sjack, prefix)  17   asoc_simple_init_jack(card, sjack, 0, prefix, NULL) 

在声卡驱动 probe 时调用

dts 中配置声卡节点 compatible = “simple-audio-card”

734  static const struct of_device_id simple_of_match[] = { 735   { .compatible = "simple-audio-card", }, 736   { .compatible = "simple-scu-audio-card", 737     .data = (void *)DPCM_SELECTABLE }, 738   {}, 739  }; 740  MODULE_DEVICE_TABLE(of, simple_of_match);  614  static int simple_soc_probe(struct snd_soc_card *card) 615  { 616   struct asoc_simple_priv *priv = snd_soc_card_get_drvdata(card); 617   int ret; 618   619   ret = asoc_simple_init_hp(card, &priv->hp_jack, PREFIX); 620   if (ret return ret; 622   623   ret = asoc_simple_init_mic(card, &priv->mic_jack, PREFIX); 624   if (ret return ret; 626   627   return 0; 628  } 

这个驱动文件负责声卡的初始化,音频流管理,控制接口等。在第 619 行,调用了耳机检测 IO 初始化的代码。

耳机拔插上报 flow

asoc_simple_init_jack 会调用 snd_soc_card_jack_new,添加检测管脚 pins,进而一路调用下来

60  int snd_soc_card_jack_new(struct snd_soc_card *card, const char *id, int type, 61       struct snd_soc_jack *jack, 62       struct snd_soc_jack_pin *pins, unsigned int num_pins) 63  { 64   int ret; 65   66   mutex_init(&jack->mutex); 67   jack->card = card; 68   INIT_LIST_HEAD(&jack->pins); 69   INIT_LIST_HEAD(&jack->jack_zones); 70   BLOCKING_INIT_NOTIFIER_HEAD(&jack->notifier); 71   72   ret = snd_jack_new(card->snd_card, id, type, &jack->jack, false, false); 73   if (ret) 74    goto end; 75   76   if (num_pins) 77    ret = snd_soc_jack_add_pins(jack, num_pins, pins); 78  end: 79   return soc_card_ret(card, ret); 80  } 81  EXPORT_SYMBOL_GPL(snd_soc_card_jack_new); 137  int snd_soc_jack_add_pins(struct snd_soc_jack *jack, int count, 138       struct snd_soc_jack_pin *pins) 139  { 140   int i; 141   142   for (i = 0; i if (!pins[i].pin) { 144     dev_err(jack->card->dev, "ASoC: No name for pin %d ", 145      i); 146     return -EINVAL; 147    } 148    if (!pins[i].mask) { 149     dev_err(jack->card->dev, "ASoC: No mask for pin %d" 150      " (%s) ", i, pins[i].pin); 151     return -EINVAL; 152    } 153   154    INIT_LIST_HEAD(&pins[i].list); 155    list_add(&(pins[i].list), &jack->pins); 156    snd_jack_add_new_kctl(jack->jack, pins[i].pin, pins[i].mask); 157   } 158   159   /* Update to reflect the last reported status; canned jack 160    * implementations are likely to set their state before the 161    * card has an opportunity to associate pins. 162    */ 163   snd_soc_jack_report(jack, 0, 0); 164   165   return 0; 166  } 167  EXPORT_SYMBOL_GPL(snd_soc_jack_add_pins); 34  void snd_soc_jack_report(struct snd_soc_jack *jack, int status, int mask) 35  { 36   struct snd_soc_dapm_context *dapm; 37   struct snd_soc_jack_pin *pin; 38   unsigned int sync = 0; 39   40   if (!jack) 41    return; 42   trace_snd_soc_jack_report(jack, mask, status); 43   44   dapm = &jack->card->dapm; 45   46   mutex_lock(&jack->mutex); 47   48   jack->status &= ~mask; 49   jack->status |= status & mask; 50   51   trace_snd_soc_jack_notify(jack, status); 52   53   list_for_each_entry(pin, &jack->pins, list) { 54    int enable = pin->mask & jack->status; 55   56    if (pin->invert) 57     enable = !enable; 58   59    if (enable) 60     snd_soc_dapm_enable_pin(dapm, pin->pin); 61    else 62     snd_soc_dapm_disable_pin(dapm, pin->pin); 63   64    /* we need to sync for this case only */ 65    sync = 1; 66   } 67   68   /* Report before the DAPM sync to help users updating micbias status */ 69   blocking_notifier_call_chain(&jack->notifier, jack->status, jack); 70   71   if (sync) 72    snd_soc_dapm_sync(dapm); 73   74   snd_jack_report(jack->jack, jack->status); 75   76   mutex_unlock(&jack->mutex); 77  } 78  EXPORT_SYMBOL_GPL(snd_soc_jack_report); 652  void snd_jack_report(struct snd_jack *jack, int status) 653  { 654   struct snd_jack_kctl *jack_kctl; 655   unsigned int mask_bits = 0; 656  #ifdef CONFIG_SND_JACK_input_DEV 657   int i; 658  #endif 659   660   if (!jack) 661    return; 662   663   jack->hw_status_cache = status; 664   665   list_for_each_entry(jack_kctl, &jack->kctl_list, list) 666    if (jack_kctl->sw_inject_enable) 667     mask_bits |= jack_kctl->mask_bits; 668    else 669     snd_kctl_jack_report(jack->card, jack_kctl->kctl, 670            status & jack_kctl->mask_bits); 671   672  #ifdef CONFIG_SND_JACK_INPUT_DEV 673   mutex_lock(&jack->input_dev_lock); 674   if (!jack->input_dev) { 675    mutex_unlock(&jack->input_dev_lock); 676    return; 677   } 678   679   for (i = 0; i key); i++) { 680    int testbit = ((SND_JACK_BTN_0 >> i) & ~mask_bits); 681   682    if (jack->type & testbit) 683     input_report_key(jack->input_dev, jack->key[i], 684        status & testbit); 685   } 686   687   for (i = 0; i if (jack->type & testbit) 691     input_report_switch(jack->input_dev, 692           jack_switch_types[i], 693           status & testbit); 694   } 695   696   input_sync(jack->input_dev); 697   mutex_unlock(&jack->input_dev_lock); 698  #endif /* CONFIG_SND_JACK_INPUT_DEV */ 699  } 700  EXPORT_SYMBOL(snd_jack_report); 

第 683 行,input 上报事件,参数 1 为耳机事件类型,参数 2 为耳机事件键值,参数 3 表示耳机插拨的状态。第 696 行,同步事件。

若你要使用 Linux 自带的耳机拔插检测驱动,则需要在对应的声卡驱动的 dts 节点中声明你所使用的 GPIO 口,加载时就会自动帮你配置好检测逻辑。

Linux 自带的耳机拔插检测驱动

Linux 自带的耳机拔插检测功能有限,大部分平台都有自己的耳机检测逻辑,例如 RK 平台的耳机检测在这:

kernel/drivers/headset_observe/rockchip_headset_core.c

MTK 平台的耳机拔插检测驱动在:

kernel/drivers/misc/mediatek/accdet/

kernel/sound/soc/codecs/mt6xxx-accdet.c

相关阅读