From: Zhang,Vivian Date: Thu, 29 Nov 2012 02:46:06 +0000 (+0800) Subject: Set profile of the audience chip and enable bluetooth port when voice call is routed... X-Git-Tag: accepted/tizen_2.0/20130305.215309~8 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=e8302415ffcb9eaeb360fdb5578e50f40c16ac94;p=adaptation%2Fintel_mfld%2Ftel-plugin-mfld-blackbay.git Set profile of the audience chip and enable bluetooth port when voice call is routed to bluetooth, fixed TZSP-4005: no voice via BT headset during calls Change-Id: I36e2458cf37db3d442c764ebe3fe2c6b910cbd5f --- diff --git a/CMakeLists.txt b/CMakeLists.txt index bf4e847..8cc39ea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,6 +33,7 @@ IF( ${KERNEL_MUX} EQUAL 1 ) src/desc_imc_kernel_mux_pr3.c src/util_imc.c src/pr3_audio.c + src/pr3_bt.c ) ELSE( ${KERNEL_MUX} EQUAL 1 ) MESSAGE( "KERNEL MUX is disabled" ) @@ -40,6 +41,7 @@ ELSE( ${KERNEL_MUX} EQUAL 1 ) src/desc_imc_pr3.c src/util_imc.c src/pr3_audio.c + src/pr3_bt.c ) ENDIF( ${KERNEL_MUX} EQUAL 1 ) diff --git a/include/pr3_bt.h b/include/pr3_bt.h new file mode 100644 index 0000000..ad7f1de --- /dev/null +++ b/include/pr3_bt.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2012 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + + +#include +#include + +gboolean enableBluetoothPort(gboolean bEnable); diff --git a/src/pr3_audio.c b/src/pr3_audio.c index 2a076db..ced5a5f 100644 --- a/src/pr3_audio.c +++ b/src/pr3_audio.c @@ -4,18 +4,37 @@ #include #include "pr3_audio.h" - - -#define MODE_NORMAL 0 -#define MODE_RINGTONE 1 -#define MODE_IN_CALL 2 -#define MODE_IN_COMMUNICATION 3 +#include "pr3_bt.h" + +typedef enum { + MODE_NORMAL = 0, + MODE_RINGTONE, + MODE_IN_CALL, + MODE_IN_COMMUNICATION +} mode_t; + +typedef enum { + PROFILE_EARPIECE = 0, + PROFILE_SPEAKER, + PROFILE_WIRED_HEADSET, + PROFILE_WIRED_HEADPHONE, + PROFILE_BLUETOOTH_HSP, + PROFILE_BLUETOOTH_CARKIT, + PROFILE_DEFAULT, + PROFILE_NUMBER +} profile_id_t; + +typedef enum { + PROFILE_MODE_OFFSET_IN_CALL = 0, + PROFILE_MODE_OFFSET_IN_COMMUNICATION = PROFILE_DEFAULT + 1 +} profile_mode_offset_t; #define DISCONNECT 0x00 #define DEVICE_OUT_EARPIECE 0x1 #define DEVICE_OUT_SPEAKER 0x2 #define DEVICE_OUT_WIRED_HEADSET 0x4 #define DEVICE_OUT_WIRED_HEADPHONE 0x8 +#define DEVICE_OUT_BLUETOOTH 0xa #define A1026_IOCTL_MAGIC 'u' @@ -26,13 +45,41 @@ #define ES305B_DEVICE "/dev/audience_es305" #define FIRMWARE_NAME_MAX_LENGTH 64 +/* The time is us before the ACK of es305b can be read: Audience + * specifies 20ms. + */ +#define ES305B_TIME_FOR_ACK_IN_US 20000 +#define PROFILE_MODE_NUMBER 2 +#define PROFILE_NUM PROFILE_NUMBER * PROFILE_MODE_NUMBER static const unsigned char es305b_no_acoustic[] = { 0x80, 0x31, 0x00, 0x06 }; static int a1026_enabled; +static int profile_cache_enabled; static snd_pcm_t *handle_playback = NULL; static snd_pcm_t *handle_capture = NULL; +static const uint32_t profile_path_len_max = 80; +static int profile_size[PROFILE_NUM]; +static unsigned char *i2c_cmd_profile[PROFILE_NUM] = { NULL, }; +static const char *vp_profile_prefix = "/lib/firmware/phone_call/phonecall_es305b_"; + +static const char *profile_name[PROFILE_NUM] = { + "close_talk.bin", // EP + "speaker_far_talk.bin", // IHF + "headset_close_talk.bin", // Headset + "headphone_close_talk.bin", // Headphone + "bt_hsp.bin", // BT HSP + "bt_carkit.bin", // BT Car Kit + "no_acoustic.bin", // All other devices + "close_talk_voip.bin", // EP VOIP + "speaker_far_talk_voip.bin", // IHF VOIP + "headset_close_talk_voip.bin", // Headset VOIP + "headphone_close_talk_voip.bin", // Headphone VOIP + "bt_hsp_voip.bin", // BT HSP VOIP + "bt_carkit_voip.bin", // BT Car Kit VOIP + "no_acoustic_voip.bin" // All other devices VOIP +}; static const char *playback_name(int mode, uint32_t device) { @@ -144,6 +191,172 @@ static void pcm_disable(void) } } +/*---------------------------------------------------------------------------*/ +/* Load profiles in cache */ +/*---------------------------------------------------------------------------*/ +static int private_cache_profiles() +{ + int i; + int rc; + char profile_path[profile_path_len_max]; + FILE *fd; + dbg("Initialize A1026 profiles cache\n"); + for (i = 0; i < PROFILE_NUM; i++) { + if (profile_name[i] == NULL) { + // Means that this profile is not supported + i2c_cmd_profile[i] = NULL; + continue; + } + snprintf(profile_path, sizeof(profile_path), "%s%s", vp_profile_prefix, profile_name[i]); + + fd = fopen(profile_path, "r"); + if (fd == NULL) { + dbg("A1026 profiles cache failed: Cannot open %s\n", profile_path); + return -1; + } + + fseek(fd, 0, SEEK_END); + profile_size[i] = ftell(fd); + fseek(fd, 0, SEEK_SET); + + dbg("Profile %d : size = %d, \t path = %s", i, profile_size[i], profile_path); + + if (i2c_cmd_profile[i] != NULL) + free(i2c_cmd_profile[i]); + + i2c_cmd_profile[i] = (unsigned char*)malloc(sizeof(unsigned char) * profile_size[i]); + if (i2c_cmd_profile[i] == NULL) { + dbg("A1026 profiles cache failed: Could not allocate memory\n"); + fclose(fd); + return -1; + } + else + memset(i2c_cmd_profile[i], '\0', profile_size[i]); + + rc = fread(&i2c_cmd_profile[i][0], 1, profile_size[i], fd); + if (rc < profile_size[i]) { + dbg("A1026 profiles cache failed: Error while reading config file\n"); + fclose(fd); + return -1; + } + fclose(fd); + } + + // Check that default profile has been loaded + if (i2c_cmd_profile[PROFILE_DEFAULT] == NULL) { + dbg("A1026 profiles cache failed: Audience default profile not found.\n"); + return -1; + } + + dbg("A1026 profiles cache OK\n"); + profile_cache_enabled = 1; + return 0; +} + +/*---------------------------------------------------------------------------*/ +/* Get profile ID to access cache */ +/*---------------------------------------------------------------------------*/ +static int private_get_profile_id(uint32_t device, uint32_t mode) +{ + int profile_id = PROFILE_DEFAULT; + + // Associate a profile to the detected device + switch(device) { + case DEVICE_OUT_EARPIECE: + dbg("Earpiece device detected, => force use of Earpiece device profile\n"); + profile_id = PROFILE_EARPIECE; + break; + case DEVICE_OUT_SPEAKER: + dbg("Speaker device detected, => force use of Speaker device profile\n"); + profile_id = PROFILE_SPEAKER; + break; + case DEVICE_OUT_WIRED_HEADSET: + dbg("Headset device detected, => force use of Headset device profile\n"); + profile_id = PROFILE_WIRED_HEADSET; + break; + case DEVICE_OUT_WIRED_HEADPHONE: + dbg("Headphone device detected, => force use of Headphone device profile\n"); + profile_id = PROFILE_WIRED_HEADPHONE; + break; + case DEVICE_OUT_BLUETOOTH: + dbg("BT SCO Headset device detected, => force use of BT HSP device profile\n"); + profile_id = PROFILE_BLUETOOTH_HSP; + break; + default: + dbg("No device detected, => force use of DEFAULT device profile\n"); + profile_id = PROFILE_DEFAULT; + break; + } + + if (mode == MODE_IN_CALL) + profile_id += PROFILE_MODE_OFFSET_IN_CALL; + else if (mode == MODE_IN_COMMUNICATION) + profile_id += PROFILE_MODE_OFFSET_IN_COMMUNICATION; + + dbg("Profile %d : size = %d, name = %s", profile_id, profile_size[profile_id], profile_name[profile_id]); + return profile_id; +} + +/*---------------------------------------------------------------------------*/ +/* Private ACK discard method */ +/*---------------------------------------------------------------------------*/ +static void private_discard_ack(int fd_a1026, int discard_size) +{ +#define TRASH_BUFFER_SIZE 32 +#define min(a,b) ((a) < (b) ? (a):(b)) + int rc; + int max_retry = 3; + char trash[TRASH_BUFFER_SIZE]; + + dbg("Discard %d byte(s) in es305b tx fifo\n", discard_size); + + while (discard_size && max_retry) { + rc = read(fd_a1026, trash, min(discard_size, TRASH_BUFFER_SIZE)); + if (rc < 0) { + dbg("Discard fails due to A1026_READ_DATA error, ret = %d\n", rc); + break; + } + if (rc == 0) { + max_retry--; + dbg("0 bytes discarded in es305b fifo, %d remaining. Retry.\n", discard_size); + usleep(ES305B_TIME_FOR_ACK_IN_US); + } else { + discard_size -= rc; + } + } + if (discard_size) + dbg("Discard aborted with %d bytes not discarded\n", discard_size); +} + +/*---------------------------------------------------------------------------*/ +/* Private suspend method */ +/*---------------------------------------------------------------------------*/ +static int private_suspend(int fd) +{ + int rc; + + if (profile_cache_enabled) { + rc = write(fd, &i2c_cmd_profile[PROFILE_DEFAULT][0], profile_size[PROFILE_DEFAULT]); + if (rc != profile_size[PROFILE_DEFAULT]) { + dbg("Audience A1026 write error, pass-through mode failed\n"); + } else { + /* All commands are acknowledged by es305b with responses which are the + * commands themselves: discard them. Audience recommends to wait 20ms + * per command before to read the ACK */ + usleep((profile_size[PROFILE_DEFAULT] / 4) * ES305B_TIME_FOR_ACK_IN_US); + private_discard_ack(fd, profile_size[PROFILE_DEFAULT]); + } + } + else + dbg("A1026 profile cache is not setup correctly, skip setting profile to default\n"); + + rc = ioctl(fd, A1026_SUSPEND); + if (rc < 0) + dbg("Audience A1026 suspend error\n"); + return rc; +} + + static void a1026_init(void) { const unsigned char cmd1[] = { 0x80, 0x20, 0x00, 0x00 }; @@ -158,6 +371,12 @@ static void a1026_init(void) dbg("A1026 init\n"); + profile_cache_enabled = 0; + err = private_cache_profiles(); + if (err < 0) { + dbg("Failed to cache profiles\n"); + } + fd = open(ES305B_DEVICE, O_RDWR, 0); if (fd < 0) { err("Failed to open audience device\n"); @@ -207,7 +426,7 @@ static void a1026_init(void) a1026_enabled = 0; suspend: - err = ioctl(fd, A1026_SUSPEND); + err = private_suspend(fd); if (err < 0) err("Failed to suspend audience\n"); @@ -261,8 +480,7 @@ static void a1026_suspend(void) err("Failed to open audience device\n"); return; } - - err = ioctl(fd, A1026_SUSPEND); + err = private_suspend(fd); if (err < 0) err("Failed to suspend audience\n"); @@ -271,6 +489,60 @@ static void a1026_suspend(void) close(fd); } +static int a1026_profile(uint32_t device, uint32_t mode) +{ + int fd_a1026 = -1; + int rc; + int profile_id; + + dbg("Set Audience A1026 profile\n"); + if (!a1026_enabled) { + dbg("A1026 set profile failed: a1026 is not enabled.") + return -1; + } + + if (!profile_cache_enabled) { + dbg("A1026 set profile failed: profile cache is not enabled correctly."); + return -1; + } + + fd_a1026 = open(ES305B_DEVICE, O_RDWR); + if (fd_a1026 < 0) { + dbg("Cannot open audience_a1026 device (%d)\n", fd_a1026); + goto return_error; + } + profile_id = private_get_profile_id(device, mode); + + // Check that the selected profile is valid + if (i2c_cmd_profile[profile_id] == NULL) { + dbg("Unsupported profile selected (%d)\n", profile_id); + goto return_error; + } + + rc = write(fd_a1026, &i2c_cmd_profile[profile_id][0], profile_size[profile_id]); + if (rc != profile_size[profile_id]) { + dbg("Audience write error \n"); + goto return_error; + } + /* All commands are acknowledged by es305b with responses which are the + * commands themselves: discard them. Audience recommends to wait 20ms + * per command before to read the ACK */ + usleep((profile_size[profile_id] / 4) * ES305B_TIME_FOR_ACK_IN_US); + private_discard_ack(fd_a1026, profile_size[profile_id]); + + dbg("Audience A1026 set profile OK\n"); + close(fd_a1026); + return 0; + +return_error: + + dbg("Audience A1026 set profile failed\n"); + if (fd_a1026 >= 0) + close(fd_a1026); + return -1; +} + + static void medfield_init(void) { a1026_init(); @@ -295,21 +567,26 @@ static void medfield_init(void) static void medfield_cleanup(void) { pcm_disable(); - + enableBluetoothPort(FALSE); a1026_suspend(); } static void medfield_enable(int mode, uint32_t device) { a1026_wakeup(); - - pcm_enable(mode, device); + a1026_profile(device, mode); + if (device == DEVICE_OUT_BLUETOOTH) { + pcm_disable(); + enableBluetoothPort(TRUE); + } + else + pcm_enable(mode, device); } static void medfield_disable(void) { pcm_disable(); - + enableBluetoothPort(FALSE); a1026_suspend(); } @@ -324,6 +601,8 @@ static inline int audio_device_converter(int device) return DEVICE_OUT_SPEAKER; case CALL_SOUND_PATH_HEADSET: return DEVICE_OUT_WIRED_HEADSET; + case CALL_SOUND_PATH_BLUETOOTH: + return DEVICE_OUT_BLUETOOTH; default: dbg("device Id not regcognize, use default device (earpiece)"); return DEVICE_OUT_EARPIECE; diff --git a/src/pr3_bt.c b/src/pr3_bt.c new file mode 100644 index 0000000..2dc7b3a --- /dev/null +++ b/src/pr3_bt.c @@ -0,0 +1,66 @@ +/* + ** + ** Copyright 2012 Intel Corporation + ** + ** Licensed under the Apache License, Version 2.0 (the "License"); + ** you may not use this file except in compliance with the License. + ** You may obtain a copy of the License at + ** + ** http://www.apache.org/licenses/LICENSE-2.0 + ** + ** Unless required by applicable law or agreed to in writing, software + ** distributed under the License is distributed on an "AS IS" BASIS, + ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ** See the License for the specific language governing permissions and + ** limitations under the License. + */ + +#include +#include +#include +#include +#include + +#include "pr3_bt.h" + + +gboolean enableBluetoothPort(gboolean bEnabled) +{ + int pid; + + const char hcitool_path[] = "/usr/bin/hcitool"; + + if ((pid = fork()) != 0) { + // Father + if (pid == -1) { + dbg("Cannot fork() to call hcitool: %s", strerror(errno)); + return FALSE; + } + + waitpid(pid, NULL, 0); + + } else { + // Child + if (bEnabled) { + const char * argv[] = {"hcitool", "cmd", "0x3F", "0X195", "FF", "FF", "FF", + "FF", "FF", "FF", "FF", "FF", "01", "FF", "FF", "00", + "00", "00", "00", NULL}; + dbg("enabling BT path"); + + execv(hcitool_path, (char **)argv); + + } else { + const char * argv[] = {"hcitool", "cmd", "0x3F", "0X195", "FF", "FF", "FF", + "FF", "FF", "FF", "FF", "FF", "00", "FF", "FF", "00", + "00", "00", "00", NULL}; + dbg("Disabling BT path"); + + execv(hcitool_path, (char **)argv); + + } + dbg("Failed to exec hcitool command for %s: %s", bEnabled ? "enabling" :"disabling", strerror(errno)); + } + + return TRUE; +} +