Set profile of the audience chip and enable bluetooth port when voice call is routed...
authorZhang,Vivian <vivian.zhang@intel.com>
Thu, 29 Nov 2012 02:46:06 +0000 (10:46 +0800)
committerAuke Kok <auke-jan.h.kok@intel.com>
Fri, 8 Feb 2013 20:25:56 +0000 (12:25 -0800)
Change-Id: I36e2458cf37db3d442c764ebe3fe2c6b910cbd5f

CMakeLists.txt
include/pr3_bt.h [new file with mode: 0644]
src/pr3_audio.c
src/pr3_bt.c [new file with mode: 0644]

index bf4e847..8cc39ea 100644 (file)
@@ -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 (file)
index 0000000..ad7f1de
--- /dev/null
@@ -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 <glib.h>
+#include <tcore.h>
+
+gboolean enableBluetoothPort(gboolean bEnable);
index 2a076db..ced5a5f 100644 (file)
@@ -4,18 +4,37 @@
 #include <alsa/asoundlib.h>
 
 #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'
 
 #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 (file)
index 0000000..2dc7b3a
--- /dev/null
@@ -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 <sys/types.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+
+#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;
+}
+