Sabresd板子上WM8962的耳机和功放的关系

本文档主要讨论i.mx6_sabresd板子WM8962 machine driver中耳机(HP)和喇叭(Ext Spk)之间的关系。
主要包括三个部分:
1
Linux 3.10.17版本内核中HPExt Spk的关系。
2
Linux 3.0.35版本内核中HPExt Spk的关系,以及改进。
3
:如何增加kcontrol接口,通过amixer来控制HPExt 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 SpkRouter,耳机拔出时使能Ext SpkRouter
这样做导致:
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,以及我们sgtl5000audio machine driver中,加入了通过amixer来控制HPExt 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 cgetamixer 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
:获取刚刚加入的两个kcontrolnumid
    amixer controls:
    numid=62,iface=MIXER,name='HP Function'
    numid=63,iface=MIXER,name='SPK Function'
2
:根据刚刚获得的numid来操作HPSPK
    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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值