Android/Linux/音频/驱动
全部博文(41)
分类: Android平台
2017-05-27 17:00:58
【问题现象】
根据测试组同事反馈:在我们的设备上使用 JBL 品牌某款带有 3 个按键的有线耳机时,按下“音量+”键时设备会减小音量而不是增加音量,按下“音量-”键时设备无响应;在设备上使用 Samsung 品牌某款带有 3 个按键的有线耳机时,按下“音量-”键时设备会启动语音助手而不是减小音量。但按下 2 款耳机的中间键(播放/暂停键)均可以得到正确的响应。
【分析问题】
首先查看这 2 款耳机的音频接口,均为 3.5mm 耳机插头,其中三星耳机是白色环的 CTIA 标准插头,JBL耳机是黑色环的 OMTP 标准插头。这 2 种标准插头的区别在于第 3、4个金属环的接法是相反的。CTIA标准的第 3 个金属环为 GND,第 4 个金属环为 MIC;OMTP标准的第 3 个金属环则为 MIC,而第 4 个金属环为 GND。更具体的信息可以参考我之前写的《3.5mm 音频接口类型说明》这篇文章。
然后 adb 登录到设备上,使用 getevent 命令查看耳机按键按下时,设备的实时上报键值和响应。类似下图中这样,参数 /dev/input/event9 :
经过实时查看发现:JBL耳机的“音量+”键按下时,实际上报的键值为 KEY_VOLUMEDOWN,所以设备上相应地做出了减小音量的响应,而“音量-”键按下时,没有键值被上报。这很异常。三星耳机的“音量-”键按下时,实际上报的键值为 KEY_VOICECOMMAND,所以设备上相应地启动了语音助手程序。至此可以看到,不是系统上层做出了错误的响应,而是系统底层上报按键事件时就发生了错误,所以应该在底层驱动来解决这个问题。
可是为什么按下 JBL 耳机的“音量-”时没有键值被上报呢?让我们从按键事件的本质来分析:按键本身是个硬件,当它被按下时实际上是接通了耳机检测芯片到地线(GND)的一条通路。这条通路可以是直接连接到 GND,也可以是通过电阻连接到 GND。建立这样的连接通路会在耳机检测芯片上造成电平变化(上升沿或下降沿),而这实际上就是一个中断信号。耳机检测芯片接收到这个中断后,会对连接到按键的芯片引脚上的电平进行 ADC 采样,再将得到的数值与芯片内存储的各按键值(KEY_VOLUMEUP、KEY_VOLUMEDOWN、KEY_MEDIA、KEY_VOICECOMMAND)进行对比,如果有符合的键值,则向 SoC 芯片发送一个中断,同时发送该键值,如果没有找到匹配的键值,自然也就不会上报了。
我们可以通过 /proc/interrupts 节点来查看 SoC 芯片是否收到了中断。比如可以像下方这样查看 ts3a227e 发送的中断数,如果按下“音量-”按键后中断计数没有增加,那么就说明耳机检测芯片确实没有向 SoC 芯片发出中断:
耳机按键分压电路模型类似于下图:
之所以 2 款耳机的中间键被按下时都可以得到正确的响应,是因为大部分耳机的中间键都是直接接地的,按键被按下时对应的电平都是 0V。具体电路类似下图:
综上所述,各键值与各电压值是对应的,这个问题的本质实际上是底层驱动检测到了错误的电压继而上报了错误的键值或没有上报键值。我们无法改变耳机本身各按键所对应的电阻,但可以通过修改芯片的寄存器值来改变按键分压电路上总的偏置电压(也就是上图的MICBIAS),从而改变每个按键上的电压值。
【解决问题】
在我参与开发的设备上,耳机按键检测使用的是 ts3a227e 这款芯片,偏置电压则由 Codec 芯片 RT5677 提供。所以要修改耳机按键分压电路的偏置电压需要修改 RT5677 芯片的寄存器,要修改电平与键值的对应关系需要修改 ts3a227e 芯片的寄存器。查阅 2 款芯片的 datasheet,对电压配置作出如下修改:
diff --git a/sound/soc/codecs/rt5677.c b/sound/soc/codecs/rt5677.c index 5668ac0..206b356 100644 --- a/sound/soc/codecs/rt5677.c +++ b/sound/soc/codecs/rt5677.c @@ -99,7 +99,7 @@ static struct rt5677_init_reg init_list[] = { {RT5677_PRIV_INDEX , 0x0014}, {RT5677_PRIV_DATA , 0x018a}, {RT5677_IN1 , 0x00c0}, - {RT5677_MICBIAS , 0x4000}, + {RT5677_MICBIAS , 0x0000}, //0x4000 {RT5677_STO1_ADC_MIXER , 0x5480}, //{RT5677_STO2_ADC_MIXER , 0x9440}, {RT5677_DMIC_CTRL1 , 0x2505}, diff --git a/sound/soc/codecs/ts3a227e.c b/sound/soc/codecs/ts3a227e.c index e4f30f9..796b686 100644 --- a/sound/soc/codecs/ts3a227e.c +++ b/sound/soc/codecs/ts3a227e.c @@ -169,7 +169,7 @@ static const struct reg_default ts3a227e_reg_defaults[] = { { TS3A227E_REG_INTERRUPT_DISABLE, 0x08 }, { TS3A227E_REG_SETTING_1, 0x27 }, { TS3A227E_REG_SETTING_2, 0x00 }, - { TS3A227E_REG_SETTING_3, 0x3f }, + { TS3A227E_REG_SETTING_3, 0x27 }, // 0x3f { TS3A227E_REG_SWITCH_CONTROL_1, 0x00 }, { TS3A227E_REG_SWITCH_CONTROL_2, 0x00 }, { TS3A227E_REG_SWITCH_STATUS_1, 0x0c },按照上方所示修改好电压后,三星耳机的按键就可以正常使用了,但 JBL 耳机的“音量-”按键依然会被识别为语音助手,在尝试了其它可配置的电压组合后依然如此。既然无法通过修改电压来完全解决问题,那么我们可以从软件上修改键值-上报事件映射的逻辑,使系统底层上报 KEY_VOICECOMMAND 键值的时候,系统上层依然可以作为 KEY_VOLUMEDOWN 来处理。为了看懂我接下来要对驱动代码进行的改动,这里先看一下驱动代码里对耳机按键状态数组的定义:
struct ts3a227e_keystatus { int key_num; volatile int key_value; }; struct ts3a227e_keystatus key_status_map[] = { {KEY_MEDIA, KEY_IS_UP}, {KEY_VOLUMEUP, KEY_IS_UP}, {KEY_VOICECOMMAND, KEY_IS_UP}, {KEY_VOLUMEDOWN, KEY_IS_UP}, };
为了重新映射键值消息与上报事件的关系,我对耳机检测芯片 ts3a227e 的驱动代码作如下具体改动:
diff --git a/sound/soc/codecs/ts3a227e.c b/sound/soc/codecs/ts3a227e.c index e4f30f9..796b686 100644 --- a/sound/soc/codecs/ts3a227e.c +++ b/sound/soc/codecs/ts3a227e.c @@ -286,6 +286,25 @@ static void long_press_func(struct work_struct *work) /*volume up&down long press */ // 添加 KEY_MEDIA 的长按功能代码,仅在这种情况下上报 KEY_VOICECOMMAND 事件以启动语音助手 for (i = 0; i < TS3A227E_NUM_KEYS; i++) { + /* add key_media long press - 20170523 */ + if (key_status_map[i].key_num == KEY_MEDIA && + key_status_map[i].key_value == KEY_IS_DOWN) { + key_status_map[i].key_value = KEY_IS_WAIT_UP; + pr_err("%s, i:%x , key_voicecommand down %d ,value:%d\n", __func__, i, + KEY_VOICECOMMAND, key_status_map[i].key_value); + input_report_key(ts3a227e->button_dev, KEY_VOICECOMMAND, 1); + input_sync(ts3a227e->button_dev); + } + /* add key_voicecommand long press - 20170523 */ // 添加 KEY_VOICECOMMAND 的长按功能代码,上报 KEY_VOLUMEDOWN 长按时的 KEY_NEXTSONG 事件 + if (key_status_map[i].key_num == KEY_VOICECOMMAND&& + key_status_map[i + 1].key_value == KEY_IS_DOWN) { + key_status_map[i + 1].key_value = KEY_IS_WAIT_UP; + pr_err("%s, i:%x , key_voicecommand down %d ,value:%d\n", __func__, i, + KEY_NEXTSONG, key_status_map[i + 1].key_value); + input_report_key(ts3a227e->button_dev, KEY_NEXTSONG, 1); + input_sync(ts3a227e->button_dev); + } + if (key_status_map[i].key_num == KEY_VOLUMEUP && key_status_map[i].key_value == KEY_IS_DOWN) { key_status_map[i].key_value = KEY_IS_WAIT_UP; @@ -324,6 +343,7 @@ static void check_jack_status(struct ts3a227e *ts3a227e) for (i = 0; i < TS3A227E_NUM_KEYS; i++) { if (ts3a227e->kp_int_reg & PRESS_MASK(i)) { pr_err("%s, kp %d down \n", __func__, i); + /* if (key_status_map[i].key_num != KEY_VOLUMEUP && key_status_map[i].key_num != KEY_VOLUMEDOWN) input_report_key(ts3a227e->button_dev, ts3a227e_keycodes[i], 1); @@ -331,29 +351,60 @@ static void check_jack_status(struct ts3a227e *ts3a227e) key_status_map[i].key_value = KEY_IS_DOWN; long_press_det = true; } + */ + + if (key_status_map[i].key_num != KEY_VOICECOMMAND) { + key_status_map[i].key_value = KEY_IS_DOWN; + } else { + pr_err("%s, key_voicecommand %d is detected, remap to key %d.\n", __func__, key_status_map[i].key_num, key_status_map[i+1].key_num); + key_status_map[i+1].key_value = KEY_IS_DOWN; // 在检测到 KEY_VOICECOMMAND 消息时,将其视为 KEY_VOLUMEDOWN 进行处理 + } + long_press_det = true; + pr_err("Qidi - keypress - key_status_map[%d].value = %d\n", i, key_status_map[i].key_value); } if (ts3a227e->kp_int_reg & RELEASE_MASK(i)) { pr_err("%s, kp %d up\n", __func__, i); - /* just for volume up & down long press */ - if (key_status_map[i].key_value == KEY_IS_DOWN) { - pr_err("%s, kp %d short up \n", __func__, i); - cancel_delayed_work_sync(&ts3a227e->long_press_work); - input_report_key(ts3a227e->button_dev, - ts3a227e_keycodes[i], 1); + if (i != 2) { // 检测到非 KEY_VOICECOMMAND 消息时,按照正常流程进行处理 + /* just for volume up & down long press */ + if (key_status_map[i].key_value == KEY_IS_DOWN) { + pr_err("%s, kp %d short up \n", __func__, i); + cancel_delayed_work_sync(&ts3a227e->long_press_work); + input_report_key(ts3a227e->button_dev, + ts3a227e_keycodes[i], 1); + key_status_map[i].key_value = KEY_IS_UP; + } else if (key_status_map[i].key_value == KEY_IS_WAIT_UP) { + pr_err("%s, kp %d long up code:%d\n", __func__, i, key_status_map[i].key_num); + if (key_status_map[i].key_num == KEY_VOLUMEUP) + input_report_key(ts3a227e->button_dev, KEY_PREVIOUSSONG, 0); + else if (key_status_map[i].key_num == KEY_VOLUMEDOWN) + input_report_key(ts3a227e->button_dev, KEY_NEXTSONG, 0); + else if (key_status_map[i].key_num == KEY_MEDIA) + input_report_key(ts3a227e->button_dev, KEY_VOICECOMMAND, 0); + key_status_map[i].key_value = KEY_IS_UP; - } else if (key_status_map[i].key_value == KEY_IS_WAIT_UP) { - pr_err("%s, kp %d long up code:%d\n", __func__, i, key_status_map[i].key_num); - if (key_status_map[i].key_num == KEY_VOLUMEUP) - input_report_key(ts3a227e->button_dev, KEY_PREVIOUSSONG, 0); - else if (key_status_map[i].key_num == KEY_VOLUMEDOWN) - input_report_key(ts3a227e->button_dev, KEY_NEXTSONG, 0); - - key_status_map[i].key_value = KEY_IS_UP; + } + input_report_key(ts3a227e->button_dev, + ts3a227e_keycodes[i], 0); + }else { // 在检测到 KEY_VOICECOMMAND 消息时,将其视为 KEY_VOLUMEDOWN 进行处理 + /* just for key_voicecommand long press */ + if (key_status_map[i + 1].key_value == KEY_IS_DOWN) { + pr_err("%s, kp %d short up \n", __func__, i + 1); + cancel_delayed_work_sync(&ts3a227e->long_press_work); + input_report_key(ts3a227e->button_dev, + ts3a227e_keycodes[i + 1], 1); + key_status_map[i + 1].key_value = KEY_IS_UP; + } else if (key_status_map[i + 1].key_value == KEY_IS_WAIT_UP) { + pr_err("%s, kp %d long up code:%d\n", __func__, i + 1, key_status_map[i + 1].key_num); + if (key_status_map[i + 1].key_num == KEY_VOLUMEDOWN) + input_report_key(ts3a227e->button_dev, KEY_NEXTSONG, 0); + + key_status_map[i + 1].key_value = KEY_IS_UP; + } + input_report_key(ts3a227e->button_dev, + ts3a227e_keycodes[i + 1], 0); } - input_report_key(ts3a227e->button_dev, - ts3a227e_keycodes[i], 0); } }按照上文所述进行修改后,重新编译代码并烧写设备进行测试,发现耳机按键功能已经可以正常工作了。
其实这就是纯底层驱动的修改,不仅可以解决 Android 系统上这样的问题,纯 Linux 系统下出现了这样的问题也可以如此类比修改。