hal: Enable USB Service Interval Config
Enabling service interval configuration allows toggling of
burst mode for USB RX path for all use cases other than
ULL and voice call (assuming compatible USB audio hardware).
Backend reconfiguration will occur when burst mode feature
is enabled while entering/exiting voice call/ULL.
Change-Id: Ide1f6e9945ae4ba9be2d5f16fac321c661e1d90b
diff --git a/hal/Android.mk b/hal/Android.mk
index 4d107e5..fec5236 100644
--- a/hal/Android.mk
+++ b/hal/Android.mk
@@ -401,6 +401,10 @@
LOCAL_CFLAGS += -DINSTANCE_ID_ENABLED
endif
+ifeq ($(strip $(AUDIO_FEATURE_ENABLED_USB_BURST_MODE)), true)
+ LOCAL_CFLAGS += -DUSB_BURST_MODE_ENABLED
+endif
+
LOCAL_CFLAGS += -Wall -Werror
LOCAL_COPY_HEADERS_TO := mm-audio
diff --git a/hal/audio_extn/audio_extn.h b/hal/audio_extn/audio_extn.h
old mode 100755
new mode 100644
index 66e2166..090cd15
--- a/hal/audio_extn/audio_extn.h
+++ b/hal/audio_extn/audio_extn.h
@@ -222,6 +222,7 @@
#define audio_extn_usb_get_sup_sample_rates(t, s, l) (0)
#define audio_extn_usb_is_tunnel_supported() (0)
#define audio_extn_usb_alive(adev) (false)
+#undef USB_BURST_MODE_ENABLED
#else
void audio_extn_usb_init(void *adev);
void audio_extn_usb_deinit();
@@ -242,6 +243,32 @@
bool audio_extn_usb_alive(int card);
#endif
+#ifndef USB_BURST_MODE_ENABLED
+#define audio_extn_usb_find_service_interval(m, p) (0)
+#define audio_extn_usb_altset_for_service_interval(p, si, bw, sr, ch) (-1)
+#define audio_extn_usb_set_service_interval(p, si, recfg) (-1)
+#define audio_extn_usb_get_service_interval(p, si) (-1)
+#define audio_extn_usb_check_and_set_svc_int(uc,ss) (0)
+#define audio_extn_usb_is_reconfig_req() (0)
+#define audio_extn_usb_set_reconfig(isreq) (0)
+#else
+unsigned long audio_extn_usb_find_service_interval(bool min, bool playback);
+int audio_extn_usb_altset_for_service_interval(bool is_playback,
+ unsigned long service_interval,
+ uint32_t *bit_width,
+ uint32_t *sample_rate,
+ uint32_t *channel_count);
+int audio_extn_usb_set_service_interval(bool playback,
+ unsigned long service_interval,
+ bool *reconfig);
+int audio_extn_usb_get_service_interval(bool playback,
+ unsigned long *service_interval);
+int audio_extn_usb_check_and_set_svc_int(struct audio_usecase *uc_info,
+ bool starting_output_stream);
+bool audio_extn_usb_is_reconfig_req();
+void audio_extn_usb_set_reconfig(bool is_required);
+#endif
+
#ifndef SPLIT_A2DP_ENABLED
#define audio_extn_a2dp_init(adev) (0)
#define audio_extn_a2dp_start_playback() (0)
diff --git a/hal/audio_extn/usb.c b/hal/audio_extn/usb.c
index e8b72b0..d96da2b 100644
--- a/hal/audio_extn/usb.c
+++ b/hal/audio_extn/usb.c
@@ -48,6 +48,7 @@
#define CHANNEL_NUMBER_STR "Channels: "
#define PLAYBACK_PROFILE_STR "Playback:"
#define CAPTURE_PROFILE_STR "Capture:"
+#define DATA_PACKET_INTERVAL_STR "Data packet interval:"
#define USB_SIDETONE_GAIN_STR "usb_sidetone_gain"
#define ABS_SUB(A, B) (((A) > (B)) ? ((A) - (B)):((B) - (A)))
#define SAMPLE_RATE_8000 8000
@@ -60,13 +61,15 @@
#define MAX_SAMPLE_RATE_SIZE sizeof(supported_sample_rates)/sizeof(supported_sample_rates[0])
+#define DEFAULT_SERVICE_INTERVAL_US 0
+
#define _MAX(x, y) (((x) >= (y)) ? (x) : (y))
#define _MIN(x, y) (((x) <= (y)) ? (x) : (y))
-enum usb_usecase_type{
+typedef enum usb_usecase_type{
USB_PLAYBACK = 0,
USB_CAPTURE,
-};
+} usb_usecase_type_t;
enum {
USB_SIDETONE_ENABLE_INDEX = 0,
@@ -80,6 +83,8 @@
unsigned int channels;
unsigned int rate_size;
unsigned int rates[MAX_SAMPLE_RATE_SIZE];
+ unsigned long service_interval_us;
+ usb_usecase_type_t type;
};
struct usb_card_config {
@@ -99,6 +104,7 @@
struct audio_device *adev;
int sidetone_gain;
bool is_capture_supported;
+ bool usb_reconfig;
};
static struct usb_module *usbmod = NULL;
@@ -318,6 +324,43 @@
return 0;
}
+static int usb_get_service_interval(const char *interval_str_start,
+ struct usb_device_config *usb_device_info)
+{
+ unsigned long interval = 0;
+ char time_unit[8] = {0};
+ int multiplier = 0;
+
+ char *eol = strchr(interval_str_start, '\n');
+ if (!eol) {
+ ALOGE("%s: No EOL found", __func__);
+ return -1;
+ }
+ char *tmp = (char *)calloc(1, eol-interval_str_start+1);
+ if (!tmp) {
+ ALOGE("%s: failed to allocate tmp", __func__);
+ return -1;
+ }
+ memcpy(tmp, interval_str_start, eol-interval_str_start);
+ sscanf(tmp, "%lu %2s", &interval, &time_unit[0]);
+ if (!strcmp(time_unit, "us")) {
+ multiplier = 1;
+ } else if (!strcmp(time_unit, "ms")) {
+ multiplier = 1000;
+ } else if (!strcmp(time_unit, "s")) {
+ multiplier = 1000000;
+ } else {
+ ALOGE("%s: unknown time_unit %s, assume default", __func__, time_unit);
+ interval = DEFAULT_SERVICE_INTERVAL_US;
+ multiplier = 1;
+ }
+ interval *= multiplier;
+ ALOGD("%s: set service_interval_us %lu", __func__, interval);
+ usb_device_info->service_interval_us = interval;
+ free(tmp);
+ return 0;
+}
+
static int usb_get_capability(int type,
struct usb_card_config *usb_card_info,
int card)
@@ -333,6 +376,7 @@
char *target = NULL;
char *read_buf = NULL;
char *rates_str = NULL;
+ char *interval_str_start = NULL;
char path[128];
int ret = 0;
char *bit_width_str = NULL;
@@ -402,6 +446,7 @@
ret = -ENOMEM;
break;
}
+ usb_device_info->type = type;
/* Bit bit_width parsing */
bit_width_start = strstr(str_start, "Format: ");
if (bit_width_start == NULL) {
@@ -482,6 +527,19 @@
free(usb_device_info);
continue;
}
+ // Data packet interval is an optional field.
+ // Assume 0ms interval if this cannot be read
+ // LPASS USB and HLOS USB will figure out the default to use
+ usb_device_info->service_interval_us = DEFAULT_SERVICE_INTERVAL_US;
+ interval_str_start = strstr(str_start, DATA_PACKET_INTERVAL_STR);
+ if (interval_str_start != NULL) {
+ interval_str_start += strlen(DATA_PACKET_INTERVAL_STR);
+ ret = usb_get_service_interval(interval_str_start, usb_device_info);
+ if (ret < 0) {
+ ALOGE("%s: error unable to get service interval, assume default",
+ __func__);
+ }
+ }
/* Add to list if every field is valid */
list_add_tail(&usb_card_info->usb_device_conf_list,
&usb_device_info->list);
@@ -750,7 +808,9 @@
unsigned int bit_width,
unsigned int ch,
unsigned int stream_sample_rate,
- unsigned int *sr)
+ unsigned int *sr,
+ unsigned int service_interval,
+ bool do_service_interval_check)
{
struct listnode *node_i;
struct usb_device_config *dev_info;
@@ -768,7 +828,11 @@
"%s: USB ch(%d)bw(%d), stm ch(%d)bw(%d)sr(%d), candidate(%d)",
__func__, dev_info->channels, dev_info->bit_width,
ch, bit_width, stream_sample_rate, candidate);
- if ((dev_info->bit_width != bit_width) || dev_info->channels != ch)
+
+ if ((dev_info->bit_width != bit_width) ||
+ (dev_info->channels != ch) ||
+ (do_service_interval_check && (dev_info->service_interval_us !=
+ service_interval)))
continue;
candidate = 0;
@@ -837,7 +901,9 @@
*bit_width,
*ch,
*sample_rate,
- sample_rate);
+ sample_rate,
+ 0 /*service int*/,
+ false /*do service int check*/);
exit:
ALOGV("%s: Updated sample rate per profile: bit-width(%d) rate(%d) chs(%d)",
__func__, *bit_width, *sample_rate, *ch);
@@ -1152,6 +1218,218 @@
return access(path, F_OK) == 0;
}
+unsigned long audio_extn_usb_find_service_interval(bool min,
+ bool playback) {
+ struct usb_card_config *card_info = NULL;
+ struct usb_device_config *dev_info = NULL;
+ struct listnode *node_i = NULL;
+ struct listnode *node_j = NULL;
+ unsigned long interval_us = min ? UINT_MAX : 0;
+ list_for_each(node_i, &usbmod->usb_card_conf_list) {
+ card_info = node_to_item(node_i, struct usb_card_config, list);
+ list_for_each(node_j, &card_info->usb_device_conf_list) {
+ dev_info = node_to_item(node_j, struct usb_device_config, list);
+ bool match = (playback && (dev_info->type == USB_PLAYBACK)) ||
+ (!playback && (dev_info->type == USB_CAPTURE));
+ if (match) {
+ interval_us = min ?
+ _MIN(interval_us, dev_info->service_interval_us) :
+ _MAX(interval_us, dev_info->service_interval_us);
+ }
+ }
+ break;
+ }
+ return interval_us;
+}
+
+int audio_extn_usb_altset_for_service_interval(bool playback,
+ unsigned long service_interval,
+ uint32_t *bit_width,
+ uint32_t *sample_rate,
+ uint32_t *channels)
+{
+ struct usb_card_config *card_info = NULL;
+ struct usb_device_config *dev_info = NULL;;
+ struct listnode *node_i = NULL;;
+ struct listnode *node_j = NULL;;
+ uint32_t bw = 0;
+ uint32_t ch = 0;
+ uint32_t sr = 0;
+
+ if (service_interval == 0)
+ return 0;
+ /* not a valid service interval to search for */
+
+#define FIND_BEST_MATCH(local_var, field, cond) \
+ list_for_each(node_i, &usbmod->usb_card_conf_list) { \
+ /* Currently only apply the first playback sound card configuration */ \
+ card_info = node_to_item(node_i, struct usb_card_config, list); \
+ list_for_each(node_j, &card_info->usb_device_conf_list) { \
+ dev_info = node_to_item(node_j, struct usb_device_config, list); \
+ bool match = (playback && (dev_info->type == USB_PLAYBACK)) || \
+ (!playback && (dev_info->type == USB_CAPTURE)); \
+ if (match && (cond)) { \
+ local_var = _MAX(local_var, dev_info->field); \
+ } \
+ } \
+ break; \
+ }
+
+ FIND_BEST_MATCH(bw, bit_width, dev_info->service_interval_us == service_interval);
+ FIND_BEST_MATCH(ch, channels, \
+ dev_info->service_interval_us == service_interval && \
+ dev_info->bit_width == bw);
+ list_for_each(node_i, &usbmod->usb_card_conf_list) {
+ /* Currently only apply the first playback sound card configuration */
+ card_info = node_to_item(node_i, struct usb_card_config, list);
+ if ((playback && usb_output_device(card_info->usb_device_type)) ||
+ (!playback && usb_input_device(card_info->usb_device_type))) {
+ usb_get_best_match_for_sample_rate(&card_info->usb_device_conf_list,
+ bw, ch, sr, &sr,
+ service_interval,
+ true);
+ }
+ break;
+ }
+
+#define SET_OR_RETURN_ON_ERROR(arg, local_var, cond) \
+ if (local_var != (cond)) arg = local_var; else return -1;
+
+ SET_OR_RETURN_ON_ERROR(*bit_width, bw, 0);
+ SET_OR_RETURN_ON_ERROR(*sample_rate, sr, 0);
+ SET_OR_RETURN_ON_ERROR(*channels, ch, 0);
+ return 0;
+#undef FIND_BEST_MATCH
+#undef SET_OR_RETURN_ON_ERROR
+}
+
+int audio_extn_usb_get_service_interval(bool playback,
+ unsigned long *service_interval)
+{
+ const char *ctl_name = "USB_AUDIO_RX service_interval";
+ struct mixer_ctl *ctl = mixer_get_ctl_by_name(usbmod->adev->mixer,
+ ctl_name);
+
+ if (!playback) {
+ ALOGE("%s not valid for capture", __func__);
+ return -1;
+ }
+
+ if (!ctl) {
+ ALOGV("%s: could not get mixer %s", __func__, ctl_name);
+ return -1;
+ }
+
+ *service_interval = mixer_ctl_get_value(ctl, 0);
+ return 0;
+}
+
+int audio_extn_usb_set_service_interval(bool playback,
+ unsigned long service_interval,
+ bool *reconfig)
+{
+ *reconfig = false;
+ unsigned long current_service_interval = 0;
+ const char *ctl_name = "USB_AUDIO_RX service_interval";
+ struct mixer_ctl *ctl = mixer_get_ctl_by_name(usbmod->adev->mixer,
+ ctl_name);
+
+ if (!playback) {
+ ALOGE("%s not valid for capture", __func__);
+ return -1;
+ }
+
+ if (!ctl) {
+ ALOGV("%s: could not get mixer %s", __func__, ctl_name);
+ return -1;
+ }
+
+ if (audio_extn_usb_get_service_interval(playback,
+ ¤t_service_interval) != 0) {
+ ALOGE("%s Unable to get current service interval", __func__);
+ return -1;
+ }
+
+ if (current_service_interval != service_interval) {
+ mixer_ctl_set_value(ctl, 0, service_interval);
+ *reconfig = usbmod->usb_reconfig = true;
+ }
+ else
+ *reconfig = usbmod->usb_reconfig = false;
+ return 0;
+}
+
+int audio_extn_usb_check_and_set_svc_int(struct audio_usecase *uc_info,
+ bool starting_output_stream)
+{
+ struct listnode *node = NULL;
+ struct audio_usecase *usecase = uc_info;
+ bool reconfig = false;
+ bool burst_mode = true;
+ unsigned long service_interval = 0;
+ struct audio_device *adev = usbmod->adev;
+
+ ALOGV("%s: enter:", __func__);
+
+ if ((starting_output_stream == true &&
+ ((uc_info->id == USECASE_AUDIO_PLAYBACK_MMAP) ||
+ (uc_info->id == USECASE_AUDIO_PLAYBACK_ULL))) ||
+ (voice_is_call_state_active(usbmod->adev))) {
+ burst_mode = false;
+ } else {
+ /* set if the valid usecase do not already exist */
+ list_for_each(node, &adev->usecase_list) {
+ usecase = node_to_item(node, struct audio_usecase, list);
+ if (usecase->type == PCM_PLAYBACK &&
+ audio_is_usb_out_device(usecase->devices & AUDIO_DEVICE_OUT_ALL_USB )) {
+ switch (usecase->id) {
+ case USECASE_AUDIO_PLAYBACK_MMAP:
+ case USECASE_AUDIO_PLAYBACK_ULL:
+ {
+ if (uc_info != usecase) {
+ //another ULL stream exists
+ ALOGV("%s: another ULL Stream in active use-case list burst mode = false.", __func__);
+ burst_mode = false;
+ } else {
+ ALOGV("%s:current ULL uc is the same as incoming uc_info \
+ which means we are stopping the output stream, \
+ we don't want to set burst mode to false", __func__);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ }
+ }
+
+ ALOGV("%s: burst mode(%d).", __func__,burst_mode);
+
+ service_interval =
+ audio_extn_usb_find_service_interval(!burst_mode, true /*playback*/);
+
+ if (service_interval != 0)
+ audio_extn_usb_set_service_interval(true /*playback*/,
+ service_interval,
+ &reconfig);
+
+ /* no change or not supported or no active usecases */
+ if (reconfig)
+ return -1;
+ return 0;
+}
+
+bool audio_extn_usb_is_reconfig_req()
+{
+ return usbmod->usb_reconfig;
+}
+
+void audio_extn_usb_set_reconfig(bool is_required)
+{
+ usbmod->usb_reconfig = is_required;
+}
+
void audio_extn_usb_init(void *adev)
{
if (usbmod == NULL) {
@@ -1165,6 +1443,7 @@
usbmod->adev = (struct audio_device*)adev;
usbmod->sidetone_gain = usb_sidetone_gain;
usbmod->is_capture_supported = false;
+ usbmod->usb_reconfig = false;
exit:
return;
}
diff --git a/hal/audio_hw.c b/hal/audio_hw.c
index e414c86..d726a9f 100644
--- a/hal/audio_hw.c
+++ b/hal/audio_hw.c
@@ -2899,9 +2899,18 @@
adev->dsp_bit_width_enforce_mode,
false);
}
+ if (audio_is_usb_out_device(out->devices & AUDIO_DEVICE_OUT_ALL_USB)) {
+ ret = audio_extn_usb_check_and_set_svc_int(uc_info,
+ false);
+
+ if (ret != 0)
+ check_usecases_codec_backend(adev, uc_info, uc_info->out_snd_device);
+ /* default service interval was successfully updated,
+ reopen USB backend with new service interval */
+ ret = 0;
+ }
list_remove(&uc_info->list);
- free(uc_info);
out->started = 0;
if (is_offload_usecase(out->usecase) &&
(audio_extn_passthru_is_passthrough_stream(out))) {
@@ -2922,6 +2931,7 @@
ALOGE("%s: audio_extn_ip_hdlr_intf_close failed %d",__func__, ret);
}
+ free(uc_info);
ALOGV("%s: exit: status(%d)", __func__, ret);
return ret;
}
@@ -3001,6 +3011,14 @@
uc_info->devices = out->devices;
uc_info->in_snd_device = SND_DEVICE_NONE;
uc_info->out_snd_device = SND_DEVICE_NONE;
+
+ /* This must be called before adding this usecase to the list */
+ if (audio_is_usb_out_device(out->devices & AUDIO_DEVICE_OUT_ALL_USB)) {
+ audio_extn_usb_check_and_set_svc_int(uc_info, true);
+ /* USB backend is not reopened immediately.
+ This is eventually done as part of select_devices */
+ }
+
list_add_tail(&adev->usecase_list, &uc_info->list);
audio_extn_perf_lock_acquire(&adev->perf_lock_handle, 0,
@@ -3625,6 +3643,8 @@
char value[32];
int ret = 0, val = 0, err;
bool bypass_a2dp = false;
+ bool reconfig = false;
+ unsigned long service_interval = 0;
ALOGD("%s: enter: usecase(%d: %s) kvpairs: %s",
__func__, out->usecase, use_case_table[out->usecase], kvpairs);
@@ -3727,7 +3747,14 @@
if (!voice_is_call_state_active(adev)) {
if (adev->mode == AUDIO_MODE_IN_CALL) {
adev->current_call_output = out;
- ret = voice_start_call(adev);
+ if (audio_is_usb_out_device(out->devices & AUDIO_DEVICE_OUT_ALL_USB)) {
+ service_interval = audio_extn_usb_find_service_interval(true, true /*playback*/);
+ audio_extn_usb_set_service_interval(true /*playback*/,
+ service_interval,
+ &reconfig);
+ ALOGD("%s, svc_int(%ld),reconfig(%d)",__func__,service_interval, reconfig);
+ }
+ ret = voice_start_call(adev);
}
} else {
adev->current_call_output = out;
@@ -6560,12 +6587,32 @@
static int adev_set_mode(struct audio_hw_device *dev, audio_mode_t mode)
{
struct audio_device *adev = (struct audio_device *)dev;
-
+ struct listnode *node;
+ struct audio_usecase *usecase = NULL;
+ int ret = 0;
pthread_mutex_lock(&adev->lock);
if (adev->mode != mode) {
ALOGD("%s: mode %d\n", __func__, mode);
adev->mode = mode;
if ((mode == AUDIO_MODE_NORMAL) && voice_is_in_call(adev)) {
+ list_for_each(node, &adev->usecase_list) {
+ usecase = node_to_item(node, struct audio_usecase, list);
+ if (usecase->type == VOICE_CALL)
+ break;
+ }
+ if (usecase &&
+ audio_is_usb_out_device(usecase->out_snd_device & AUDIO_DEVICE_OUT_ALL_USB)) {
+ ret = audio_extn_usb_check_and_set_svc_int(usecase,
+ true);
+ if (ret != 0) {
+ /* default service interval was successfully updated,
+ reopen USB backend with new service interval */
+ check_usecases_codec_backend(adev,
+ usecase,
+ usecase->out_snd_device);
+ }
+ }
+
voice_stop_call(adev);
platform_set_gsm_mode(adev->platform, false);
adev->current_call_output = NULL;
diff --git a/hal/msm8974/platform.c b/hal/msm8974/platform.c
index ca36adf..8b4ba5e 100644
--- a/hal/msm8974/platform.c
+++ b/hal/msm8974/platform.c
@@ -2266,7 +2266,6 @@
ALOGD("ACDB initialization failed");
}
}
-
/* init keep-alive for compress passthru */
audio_extn_keep_alive_init(adev);
#ifdef DYNAMIC_LOG_ENABLED
@@ -5952,6 +5951,8 @@
unsigned int bit_width;
unsigned int sample_rate;
unsigned int channels;
+ unsigned long service_interval = 0;
+ bool service_interval_update = false;
bool passthrough_enabled = false;
bool voice_call_active = false;
int backend_idx = DEFAULT_CODEC_BACKEND;
@@ -6100,6 +6101,27 @@
audio_extn_usb_is_config_supported(&bit_width, &sample_rate, &channels, true);
ALOGV("%s: USB BE configured as bit_width(%d)sample_rate(%d)channels(%d)",
__func__, bit_width, sample_rate, channels);
+
+ if (audio_extn_usb_get_service_interval(true,
+ &service_interval) == 0) {
+ /* overwrite with best altset for this service interval */
+ int ret =
+ audio_extn_usb_altset_for_service_interval(true /*playback*/,
+ service_interval,
+ &bit_width,
+ &sample_rate,
+ &channels);
+ ALOGD("%s: Override USB BE configured as bit_width(%d)sample_rate(%d)channels(%d)SI(%lu)",
+ __func__, bit_width, sample_rate, channels, service_interval);
+ if (ret < 0) {
+ ALOGW("Failed to find altset for service interval %lu, skip reconfig",
+ service_interval);
+ return false;
+ }
+ service_interval_update = audio_extn_usb_is_reconfig_req();
+ audio_extn_usb_set_reconfig(false);
+ }
+
if (channels != my_data->current_backend_cfg[backend_idx].channels)
channels_updated = true;
}
@@ -6138,7 +6160,7 @@
// is not same as current backend comfiguration
if ((bit_width != my_data->current_backend_cfg[backend_idx].bit_width) ||
(sample_rate != my_data->current_backend_cfg[backend_idx].sample_rate) ||
- passthrough_enabled || channels_updated) {
+ passthrough_enabled || channels_updated || service_interval_update ) {
backend_cfg->bit_width = bit_width;
backend_cfg->sample_rate = sample_rate;
backend_cfg->channels = channels;