#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)
{
}
}
+/*---------------------------------------------------------------------------*/
+/* 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 };
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");
a1026_enabled = 0;
suspend:
- err = ioctl(fd, A1026_SUSPEND);
+ err = private_suspend(fd);
if (err < 0)
err("Failed to suspend audience\n");
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");
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();
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();
}
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;
--- /dev/null
+/*
+ **
+ ** 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;
+}
+