audio: Parse USB data service interval

Parse data service interval for all stream descriptors
and use minimum of them to notify ALSA drivers

Bug: 77867216
Test: Make.  manual audio playback capture with/without USB headset

Change-Id: I9bae50d4635b5126bc1dd5783b7a33d142c77342
diff --git a/hal/Android.mk b/hal/Android.mk
index ac6ea74..d249115 100644
--- a/hal/Android.mk
+++ b/hal/Android.mk
@@ -183,6 +183,10 @@
     LOCAL_SRC_FILES += audio_extn/sndmonitor.c
 endif
 
+ifeq ($(strip $(AUDIO_FEATURE_ENABLED_USB_SERVICE_INTERVAL)), true)
+    LOCAL_CFLAGS += -DUSB_SERVICE_INTERVAL_ENABLED
+endif
+
 LOCAL_SHARED_LIBRARIES += libbase libhidlbase libhwbinder libutils android.hardware.power@1.2 liblog
 
 LOCAL_SRC_FILES += audio_perf.cpp
diff --git a/hal/audio_extn/audio_extn.h b/hal/audio_extn/audio_extn.h
index eb7ddde..054beea 100644
--- a/hal/audio_extn/audio_extn.h
+++ b/hal/audio_extn/audio_extn.h
@@ -81,6 +81,8 @@
 #define audio_extn_usb_get_max_bit_width(dir)                          (0)
 #define audio_extn_usb_sup_sample_rates(t, s, l)        ((t), (s), (l), 0) /* fix unused warn */
 #define audio_extn_usb_alive(adev)                                     (false)
+#define audio_extn_usb_find_service_interval(m, p)      ((m), (p), 0) /* fix unused warn */
+#define audio_extn_usb_altset_for_service_interval(p, si, bw, sr, ch) (-1)
 #else
 void audio_extn_usb_init(void *adev);
 void audio_extn_usb_deinit();
@@ -98,6 +100,12 @@
 int audio_extn_usb_get_max_bit_width(bool is_playback);
 int audio_extn_usb_sup_sample_rates(bool is_playback, uint32_t *sr, uint32_t l);
 bool audio_extn_usb_alive(int card);
+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);
 #endif
 
 
diff --git a/hal/audio_extn/usb.c b/hal/audio_extn/usb.c
index aca4f17..71e1aed 100644
--- a/hal/audio_extn/usb.c
+++ b/hal/audio_extn/usb.c
@@ -37,10 +37,13 @@
 #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
 #define SAMPLE_RATE_11025         11025
+#define DEFAULT_SERVICE_INTERVAL_US    1000
+
 /* TODO: dynamically populate supported sample rates */
 static uint32_t supported_sample_rates[] =
     {192000, 176400, 96000, 88200, 64000, 48000, 44100};
@@ -50,10 +53,10 @@
 
 // assert on sizeof bm v/s size of rates if needed
 
-enum usb_usecase_type{
+typedef enum usb_usecase_type{
     USB_PLAYBACK = 0,
     USB_CAPTURE,
-};
+} usb_usecase_type_t;
 
 enum {
     USB_SIDETONE_ENABLE_INDEX = 0,
@@ -67,6 +70,8 @@
     unsigned int channel_count;
     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 {
@@ -271,6 +276,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;
+    ALOGV("%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)
@@ -286,6 +328,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;
@@ -366,6 +409,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) {
@@ -443,6 +487,18 @@
             free(usb_device_info);
             continue;
         }
+        // Data packet interval is an optional field.
+        // Assume 1ms interval if this cannot be read
+        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);
@@ -697,12 +753,14 @@
     return true;
 }
 
-static bool usb_get_best_match_for_sample_rate(
+static bool usb_get_best_match_for_sample_rate (
                             struct listnode *dev_list,
                             unsigned int bit_width,
                             unsigned int channel_count,
                             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;
@@ -720,7 +778,10 @@
                  "%s: USB ch(%d)bw(%d), stm ch(%d)bw(%d)sr(%d), candidate(%d)",
                  __func__, dev_info->channel_count, dev_info->bit_width,
                  channel_count, bit_width, stream_sample_rate, candidate);
-        if ((dev_info->bit_width != bit_width) || dev_info->channel_count != channel_count)
+        if ((dev_info->bit_width != bit_width) ||
+            (dev_info->channel_count != channel_count) ||
+            (do_service_interval_check && (dev_info->service_interval_us !=
+                                           service_interval)))
             continue;
 
         candidate = 0;
@@ -787,7 +848,9 @@
                                        *bit_width,
                                        *channel_count,
                                        *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, *channel_count);
@@ -1110,6 +1173,84 @@
     return access(path, F_OK) == 0;
 }
 
+unsigned long audio_extn_usb_find_service_interval(bool min,
+                                                   bool playback) {
+    struct usb_card_config *card_info;
+    struct usb_device_config *dev_info;
+    struct listnode *node_i;
+    struct listnode *node_j;
+    unsigned long interval_us = min ? ULONG_MAX : 1; // 0 is invalid
+    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);
+            if ((playback && (dev_info->type == USB_PLAYBACK)) ||
+                (!playback && (dev_info->type == USB_CAPTURE))) {
+                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 *channel_count)
+{
+    struct usb_card_config *card_info;
+    struct usb_device_config *dev_info;
+    struct listnode *node_i;
+    struct listnode *node_j;
+    uint32_t bw = 0;
+    uint32_t ch = 0;
+    uint32_t sr = 0;
+    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);
+            if ((playback && dev_info->type == USB_PLAYBACK) ||
+                (!playback && dev_info->type == USB_CAPTURE)) {
+                if (dev_info->service_interval_us != service_interval)
+                    continue;
+                if (dev_info->bit_width > bw) {
+                    bw = dev_info->bit_width;
+                    ch = dev_info->channel_count;
+                } else if (dev_info->bit_width == bw &&
+                           dev_info->channel_count > ch) {
+                    ch = dev_info->channel_count;
+                }
+            }
+        }
+        break;
+    }
+    if (bw == 0 || ch == 0)
+        return -1;
+    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;
+    }
+    if (sr == 0)
+        return -1;
+    *bit_width = bw;
+    *sample_rate = sr;
+    *channel_count = ch;
+    return 0;
+}
+
 void audio_extn_usb_init(void *adev)
 {
     if (usbmod == NULL) {
diff --git a/hal/audio_hw.c b/hal/audio_hw.c
index 231b159..cdd10b3 100644
--- a/hal/audio_hw.c
+++ b/hal/audio_hw.c
@@ -1869,6 +1869,54 @@
     return 0;
 }
 
+static int check_and_set_usb_service_interval(struct audio_device *adev,
+                                              struct audio_usecase *uc_info,
+                                              bool min)
+{
+    struct listnode *node;
+    struct audio_usecase *usecase;
+    bool switch_usecases = false;
+    bool reconfig = false;
+
+    if ((uc_info->id != USECASE_AUDIO_PLAYBACK_MMAP) &&
+        (uc_info->id != USECASE_AUDIO_PLAYBACK_ULL))
+        return -1;
+
+    /* 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:
+                    // cannot reconfig while mmap/ull is present.
+                    return -1;
+                default:
+                    switch_usecases = true;
+                    break;
+            }
+        }
+        if (switch_usecases)
+            break;
+    }
+    /*
+     * client can try to set service interval in start_output_stream
+     * to min or to 0 (i.e reset) in stop_output_stream .
+     */
+    unsigned long service_interval =
+            audio_extn_usb_find_service_interval(min, true /*playback*/);
+    int ret = platform_set_usb_service_interval(adev->platform,
+                                                true /*playback*/,
+                                                service_interval,
+                                                &reconfig);
+    /* no change or not supported or no active usecases */
+    if (ret || !reconfig || !switch_usecases)
+        return -1;
+    return 0;
+#undef VALID_USECASE
+}
+
 static int stop_output_stream(struct stream_out *out)
 {
     int i, ret = 0;
@@ -1901,7 +1949,6 @@
     disable_snd_device(adev, uc_info->out_snd_device);
 
     list_remove(&uc_info->list);
-    free(uc_info);
 
     audio_extn_extspk_update(adev->extspk);
 
@@ -1916,8 +1963,17 @@
             if (usecase->devices & AUDIO_DEVICE_OUT_SPEAKER)
                 select_devices(adev, usecase->id);
         }
+    } else if (audio_is_usb_out_device(out->devices & AUDIO_DEVICE_OUT_ALL_USB)) {
+        ret = check_and_set_usb_service_interval(adev, uc_info, false /*min*/);
+        if (ret == 0) {
+            /* default service interval was successfully updated,
+               reopen USB backend with new service interval */
+            check_and_route_playback_usecases(adev, uc_info, uc_info->out_snd_device);
+        }
+        ret = 0;
     }
 
+    free(uc_info);
     ALOGV("%s: exit: status(%d)", __func__, ret);
     return ret;
 }
@@ -1971,6 +2027,11 @@
     /* This must be called before adding this usecase to the list */
     if (out->devices & AUDIO_DEVICE_OUT_AUX_DIGITAL)
         check_and_set_hdmi_channels(adev, out->config.channels);
+    else if (audio_is_usb_out_device(out->devices & AUDIO_DEVICE_OUT_ALL_USB)) {
+        check_and_set_usb_service_interval(adev, uc_info, true /*min*/);
+        /* USB backend is not reopened immediately.
+           This is eventually done as part of select_devices */
+    }
 
     list_add_tail(&adev->usecase_list, &uc_info->list);
 
diff --git a/hal/msm8916/platform.c b/hal/msm8916/platform.c
index 170dbd8..1e6e95f 100644
--- a/hal/msm8916/platform.c
+++ b/hal/msm8916/platform.c
@@ -2512,3 +2512,19 @@
                                     size_t *mic_count __unused) {
     return -ENOSYS;
 }
+
+int platform_set_usb_service_interval(void *platform __unused,
+                                      bool playback __unused,
+                                      unsigned long service_interval __unused,
+                                      bool *reconfig)
+{
+    *reconfig = false;
+    return 0;
+}
+
+int platform_set_backend_cfg(const struct audio_device* adev,
+                             snd_device_t snd_device,
+                             const struct audio_backend_cfg *backend_cfg)
+{
+    return -1;
+}
diff --git a/hal/msm8960/platform.c b/hal/msm8960/platform.c
index 9f10aeb..5f8bd32 100644
--- a/hal/msm8960/platform.c
+++ b/hal/msm8960/platform.c
@@ -1361,3 +1361,19 @@
                                     size_t *mic_count __unused) {
     return -ENOSYS;
 }
+
+int platform_set_usb_service_interval(void *platform __unused,
+                                      bool playback __unused,
+                                      unsigned long service_interval __unused,
+                                      bool *reconfig)
+{
+    *reconfig = false;
+    return 0;
+}
+
+int platform_set_backend_cfg(const struct audio_device* adev,
+                             snd_device_t snd_device,
+                             const struct audio_backend_cfg *backend_cfg)
+{
+    return -1;
+}
diff --git a/hal/msm8974/platform.c b/hal/msm8974/platform.c
index d39e043..c80f994 100644
--- a/hal/msm8974/platform.c
+++ b/hal/msm8974/platform.c
@@ -3479,9 +3479,9 @@
 /*
  * configures afe with bit width and Sample Rate
  */
-static int platform_set_backend_cfg(const struct audio_device* adev,
-                                          snd_device_t snd_device,
-                                          const struct audio_backend_cfg *backend_cfg)
+int platform_set_backend_cfg(const struct audio_device* adev,
+                             snd_device_t snd_device,
+                             const struct audio_backend_cfg *backend_cfg)
 {
 
     int ret = 0;
@@ -3806,6 +3806,7 @@
     unsigned int sample_rate;
     unsigned int channels;
     int backend_idx = DEFAULT_CODEC_BACKEND;
+    unsigned long service_interval = 0; // 0 is invalid
     struct platform_data *my_data = (struct platform_data *)adev->platform;
 
     if (snd_device == SND_DEVICE_OUT_BT_SCO ||
@@ -3850,6 +3851,21 @@
         case USB_AUDIO_RX_BACKEND:
             audio_extn_usb_is_config_supported(&bit_width,
                                                &sample_rate, &channels, true);
+            if (platform_get_usb_service_interval(adev->platform, 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);
+                if (ret < 0) {
+                    ALOGE("Failed to find altset for service interval %lu, skip reconfig",
+                          service_interval);
+                    return false;
+                }
+            }
             ALOGV("%s: USB BE configured as bit_width(%d)sample_rate(%d)channels(%d)",
                   __func__, bit_width, sample_rate, channels);
             break;
@@ -4387,3 +4403,63 @@
     *mic_count = actual_mic_count;
     return 0;
 }
+
+int platform_set_usb_service_interval(void *platform,
+                                      bool playback,
+                                      unsigned long service_interval,
+                                      bool *reconfig)
+{
+#if defined (USB_SERVICE_INTERVAL_ENABLED)
+    struct platform_data *_platform = (struct platform_data *)platform;
+    *reconfig = false;
+    if (!playback) {
+        ALOGE("%s not valid for capture", __func__);
+        return -1;
+    }
+    const char *ctl_name = "USB_AUDIO_RX service_interval";
+    struct mixer_ctl *ctl = mixer_get_ctl_by_name(_platform->adev->mixer,
+                                                  ctl_name);
+    if (!ctl) {
+        ALOGV("%s: could not get mixer %s", __func__, ctl_name);
+        return -1;
+    }
+    if (mixer_ctl_get_value(ctl, 0) != (int)service_interval) {
+        mixer_ctl_set_value(ctl, 0, service_interval);
+        *reconfig = true;
+    }
+    return 0;
+#else
+    *reconfig = false;
+    (void)platform;
+    (void)playback;
+    (void)service_interval;
+    return -1;
+#endif
+}
+
+int platform_get_usb_service_interval(void *platform,
+                                      bool playback,
+                                      unsigned long *service_interval)
+{
+#if defined (USB_SERVICE_INTERVAL_ENABLED)
+    struct platform_data *_platform = (struct platform_data *)platform;
+    if (!playback) {
+        ALOGE("%s not valid for capture", __func__);
+        return -1;
+    }
+    const char *ctl_name = "USB_AUDIO_RX service_interval";
+    struct mixer_ctl *ctl = mixer_get_ctl_by_name(_platform->adev->mixer,
+                                                  ctl_name);
+    if (!ctl) {
+        ALOGV("%s: could not get mixer %s", __func__, ctl_name);
+        return -1;
+    }
+    *service_interval = mixer_ctl_get_value(ctl, 0);
+    return 0;
+#else
+    (void)platform;
+    (void)playback;
+    (void)service_interval;
+    return -1;
+#endif
+}
diff --git a/hal/platform_api.h b/hal/platform_api.h
index e2120b7..e3dc5d1 100644
--- a/hal/platform_api.h
+++ b/hal/platform_api.h
@@ -36,6 +36,7 @@
 enum card_status_t;
 struct audio_usecase;
 enum usecase_type_t;
+struct audio_backend_cfg;
 
 void *platform_init(struct audio_device *adev);
 void platform_deinit(void *platform);
@@ -162,4 +163,11 @@
                                     unsigned int channels, int source, audio_usecase_t usecase,
                                     struct audio_microphone_characteristic_t *mic_array,
                                     size_t *mic_count);
+int platform_set_usb_service_interval(void *platform,
+                                      bool playback,
+                                      unsigned long service_interval,
+                                      bool *reconfig);
+int platform_get_usb_service_interval(void *platform,
+                                      bool playback,
+                                      unsigned long *service_interval);
 #endif // AUDIO_PLATFORM_API_H