本文档主要讨论i.mx6_sabresd板子WM8962 machine driver中耳机(HP)和喇叭(Ext Spk)之间的关系。
主要包括三个部分:
1:Linux 3.10.17版本内核中HP与Ext Spk的关系。
2:Linux 3.0.35版本内核中HP与Ext Spk的关系,以及改进。
3:如何增加kcontrol接口,通过amixer来控制HP和Ext Spk。
上面三种方法基本上包含了目前ASoC中的主流做法。
本文档配套两个patch:
0001-Add-kcontrol-API-for-Headphone-Jack-and-Spk-from-thi.patch
0001-New-hp-Jack-driver.patch
文档里的做法,在这两个patch中都有体现。
1:Kernel 3.10.17中HP和Ext SPK的关系:
286 static int imx_wm8962_gpio_init(struct snd_soc_card *card) {
...
294 if (gpio_is_valid(priv->hp_gpio)) {
295 imx_hp_jack_gpio.gpio = priv->hp_gpio;
296 imx_hp_jack_gpio.jack_status_check = hpjack_status_check;// 相当于耳机插拔的中断回调函数。
297
298 snd_soc_jack_new(codec, "Headphone Jack", SND_JACK_HEADPHONE, &imx_hp_jack);
299 snd_soc_jack_add_pins(&imx_hp_jack,
300 ARRAY_SIZE(imx_hp_jack_pins), imx_hp_jack_pins);
301 snd_soc_jack_add_gpios(&imx_hp_jack, 1, &imx_hp_jack_gpio);//耳机插拔的中断操作,实际上是在这个函数里进行的。
302 }
...
}
87 static int hpjack_status_check(void) {
...
97 hp_status = gpio_get_value(priv->hp_gpio) ? 1 : 0;
99 buf = kmalloc(32, GFP_ATOMIC); //buf 是给android用的
105 if (hp_status != priv->hp_active_low) { //耳机插入
106 snprintf(buf, 32, "STATE=%d", 2);
107 snd_soc_dapm_disable_pin(&priv->codec->dapm, "Ext Spk");
/*************************************************************************************
Ext Spk被定义在imx6qdl-sabresd.dtsi中:
134 audio-routing =
135 "Headphone Jack", "HPOUTL",
136 "Headphone Jack", "HPOUTR",
137 "Ext Spk", "SPKOUTL",
138 "Ext Spk", "SPKOUTR",
139 "MICBIAS", "AMIC",
140 "IN3R", "MICBIAS",
141 "DMIC", "MICBIAS",
142 "DMICDAT", "DMIC";
snd_soc_dapm_disable_pin(&priv->codec->dapm, "Ext Spk")的意思是将wm8962代码中:
"SPKOUTL",
"SPKOUTR",
通路的dapm widget disable 掉。
按道理讲此时应该还要将headphone通路的widget全部打开:
"HPOUTL",
"HPOUTR",
实际上,headphone相关通路的打开和关闭,是在回调函数的另一个地方进行的,下面会讲到。
*************************************************************************************/
108 ret = imx_hp_jack_gpio.report; //ret 返回,用于打开、关闭headphone对应的dapm widget
109 snd_kctl_jack_report(priv->snd_card, priv->headphone_kctl, 1);
110 } else {//耳机拔出
111 snprintf(buf, 32, "STATE=%d", 0);
112 snd_soc_dapm_enable_pin(&priv->codec->dapm, "Ext Spk");//打开spk对应的dapm widget
113 ret = 0; //ret 返回,用于打开、关闭headphone对应的dapm widget
114 snd_kctl_jack_report(priv->snd_card, priv->headphone_kctl, 0);
115 }
117 envp[0] = "NAME=headphone"; //往下是给android用的
118 envp[1] = buf;
119 envp[2] = NULL;
120 kobject_uevent_env(&pdev->dev.kobj, KOBJ_CHANGE, envp);
return ret; //ret 返回,用于打开、关闭headphone对应的dapm widget
}
snd_soc_jack_add_gpios//这个函数里主要用来申请中断相关的东西
---》INIT_DELAYED_WORK(&gpios[i].work, gpio_work);
---》gpio_work
---》snd_soc_jack_gpio_detect(gpio);
---》snd_soc_jack_gpio_detect
---》report = gpio->jack_status_check();//此处的report就是上面返回的ret
---》snd_soc_jack_report(jack, report, gpio->report);
snd_soc_jack_report: {
86 enable = pin->mask & jack->status;
87
88 if (pin->invert)
89 enable = !enable;
90
91 if (enable)
92 snd_soc_dapm_enable_pin(dapm, pin->pin);//打开headphone对应的dapm router
93 else
94 snd_soc_dapm_disable_pin(dapm, pin->pin);//关闭headphone对应的dapm router
...
}
我们8962的machine driver里面,还有一些比较奇怪的东西:
实际上是为了对付开机之后内核里hp的切换已经发生了,上层文件系统收不到底层hp的状态,于是在内核将状态保存在/sys/bus/platform/drivers/imx-wm8962/headphone中,上层可以cat这个headphone来得到底层hp的状态:
插入耳机时
root@freescale /sys/bus/platform/drivers/imx-wm8962$ cat headphone
in show headphone, priv->hp_status = 0
headphone
拔出耳机时
root@freescale /sys/bus/platform/drivers/imx-wm8962$ cat headphone
in show headphone, priv->hp_status = 1
speaker
534 if (gpio_is_valid(priv->hp_gpio)) {
535 ret = driver_create_file(pdev->dev.driver, &driver_attr_headphone);
536 if (ret) {
537 dev_err(&pdev->dev, "create hp attr failed (%d)\n", ret);
538 goto fail_hp;
539 }
540 }
324 static ssize_t show_headphone(struct device_driver *dev, char *buf)
325 {
326 struct imx_priv *priv = &card_priv;
327 int hp_status;
328
329 if (!gpio_is_valid(priv->hp_gpio)) {
330 strcpy(buf, "no detect gpio connected\n");
331 return strlen(buf);
332 }
333
334 /* Check if headphone is plugged in */
335 hp_status = gpio_get_value(priv->hp_gpio) ? 1 : 0;
336
337 if (hp_status != priv->hp_active_low)
338 strcpy(buf, "headphone\n");
339 else
340 strcpy(buf, "speaker\n");
341
342 return strlen(buf);
343 }
344
345 static DRIVER_ATTR(headphone, S_IRUGO | S_IWUSR, show_headphone, NULL);
2: Kernel 3.0.35代码中HP和Ext Spk的关系
FSL基础的3.0.35代码中,HP Router是一直连通的,只不过是拔掉HP时,从HP出来的声音听不到。代码中故意将HP pin的.pin写成"Ext Spk":
70 static struct snd_soc_jack_pin imx_hp_jack_pins[] = {
71 {
72 .pin = "Ext Spk",
74 .mask = SND_JACK_HEADPHONE,
75 },
76 };
731 snd_soc_jack_new(codec, "Ext Spk", SND_JACK_LINEOUT,
732 &imx_hp_jack);
HP插拔时,本来要处理名字为"Headphone Jack"的事件,这里改成了"Ext Spk",在耳机插入的时候关掉Ext Spk的Router,耳机拔出时使能Ext Spk的Router。
这样做导致:
1:代码理解起来有些混淆。
2:HP Router一直开着,不利于系统低功耗。
所以,可以用3.10.17内核的做法,将上述代码做以上改动:
70 static struct snd_soc_jack_pin imx_hp_jack_pins[] = {
71 {
72 // .pin = "Ext Spk"
73 .pin = "Headphone Jack",
74 .mask = SND_JACK_HEADPHONE,
75 },
76 };
729 imx_hp_jack_gpio.jack_status_check = hpjack_status_check;
731 // snd_soc_jack_new(codec, "Ext Spk", SND_JACK_LINEOUT,
732 // &imx_hp_jack);
733 snd_soc_jack_new(codec, "Headphone Jack",
SND_JACK_HEADPHONE,
734 &imx_hp_jack);
hpjack_status_check函数的写法和3.10.17代码的写法一样。但是,我刚开始调试的时候发现,下面两个ret的值要调换,功能才能正常。也就是说插着耳机时,report 0, 拔掉耳机report 1功能才正常。原因是imx_hp_jack_pins.pin我忘了将其由"Ext Spk"改成”Headphone Jack”了。
613 if (hp_status != plat->hp_active_low)
614 snprintf(buf, 32, "STATE=%d", 2);
615 snd_soc_dapm_disable_pin(&priv->codec->dapm, "Ext Spk");
616 ret = imx_hp_jack_gpio.report;
617 } else {
618 snprintf(buf, 32, "STATE=%d", 0);
619 snd_soc_dapm_enable_pin(&priv->codec->dapm, "Ext Spk");
620 ret = 0;
621 }
注意:
后来我尝试将hpjack_status_check里的操作,移到w->event中,也就是下面的imx_event_hp中,但是效果不怎么稳定,因为snd_soc_dapm_disable/enable_pin之后要加snd_soc_dapm_sync操作,而w->event中不能加入snd_soc_dapm_sync。
因为w->event是由snd_soc_dapm_sync调用的,系统进入了类似于死锁的状态:
snd_soc_dapm_sync
--> dapm_power_widgets
--> dapm_seq_run
--> dapm_seq_run_coalesced
--> dapm_seq_check_event
--> imx_event_hp ( w->event )
516 static const struct snd_soc_dapm_widget imx_dapm_widgets[] = {
517 SND_SOC_DAPM_HP("Headphone Jack", imx_event_hp),
518 SND_SOC_DAPM_SPK("Ext Spk", NULL),
519 SND_SOC_DAPM_MIC("AMIC", NULL),
520 SND_SOC_DAPM_MIC("DMIC", imx_event_mic),
521 };
所以snd_soc_dapm_disable/enable_pin的操作,是不能放到w->event中。
3:通过amixer来控制HP和Ext Spk
马维尔和intel,以及我们sgtl5000的audio machine driver中,加入了通过amixer来控制HP和Ext Spk的接口。
WM8962也可以使用这样的做法:
1:增加相关的control接口:
712 ret = snd_soc_add_controls(codec, wm8962_machine_controls,
713 ARRAY_SIZE(wm8962_machine_controls));
714 if (ret)
715 return ret;
2:增加kcontrol的.get与.put操作。
694 static const struct snd_kcontrol_new wm8962_machine_controls[] = {
695 SOC_ENUM_EXT("HP Function", wm8962_enum[0], wm8962_get_jack,
696 wm8962_set_jack),
697 SOC_ENUM_EXT("SPK Function", wm8962_enum[1], wm8962_get_spk,
698 wm8962_set_spk),
699 };
167 #define SOC_ENUM_EXT(xname, xenum, xhandler_get, xhandler_put) \
168 { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
169 .info = snd_soc_info_enum_ext, \
170 .get = xhandler_get, .put = xhandler_put, \
171 .private_value = (unsigned long)&xenum }
上面.get与.put的操作,对应的是amixer cget与amixer cset。需要注意的是,在.put会判断需要写入的值如果和前一次的值一样时,会放弃这次操作,不一样时,会继续写入操作。.put里做完snd_soc_dapm_disable/enable_pin之后,要加上snd_soc_dapm_sync操作。
两个名字为"HP Function"和"SPK Function"的mixer kcontrol,可以设置其为off/on(0/1)状态。
635 static const char *jack_function[] = { "off", "on"};
637 static const char *spk_function[] = { "off", "on" };
639 static const struct soc_enum wm8962_enum[] = {
640 SOC_ENUM_SINGLE_EXT(2, jack_function),
641 SOC_ENUM_SINGLE_EXT(2, spk_function),
642 };
做完上述的操作后,可以通过下面的方法来使用:
1:获取刚刚加入的两个kcontrol的numid。
amixer controls:
numid=62,iface=MIXER,name='HP Function'
numid=63,iface=MIXER,name='SPK Function'
2:根据刚刚获得的numid来操作HP与SPK。
How to enable HeadPhone using this Kcontrol:
amixer cget numid=62
numid=62,iface=MIXER,name='HP Function'
; type=ENUMERATED,access=rw------,values=1,items=2
; Item #0 'off'
; Item #1 'on'
: values=0
amixer cset numid=62 1 //enable HP
numid=62,iface=MIXER,name='HP Function'
; type=ENUMERATED,access=rw------,values=1,items=2
; Item #0 'off'
; Item #1 'on'
: values=1
How to enable Speaker using this Kcontrol:
amixer cget numid=63
numid=63,iface=MIXER,name='SPK Function'
; type=ENUMERATED,access=rw------,values=1,items=2
; Item #0 'off'
; Item #1 'on'
: values=0
amixer cset numid=63 1 //SPK enable
numid=63,iface=MIXER,name='SPK Function'
; type=ENUMERATED,access=rw------,values=1,items=2
; Item #0 'off'
; Item #1 'on'
: values=1