hal: enable haptic audio synchronization

Add support to get qtimer value.
Add support to get path latency for a given pcm stream
from ADSP.
Add mechanism to synchronize audio and haptic pcm streams
if enabled via system property.

CRs-Fixed: 2410990
Change-Id: I59952ad2e4e453c327ac8682c8f1991440b755eb
diff --git a/hal/audio_hw.c b/hal/audio_hw.c
index 41a3d5d..c2195fc 100644
--- a/hal/audio_hw.c
+++ b/hal/audio_hw.c
@@ -3471,6 +3471,11 @@
                                    &(adev->haptics_config));
             // failure to open haptics pcm shouldnt stop audio,
             // so do not close audio pcm in case of error
+
+            if (property_get_bool("vendor.audio.enable_haptic_audio_sync", false)) {
+                ALOGD("%s: enable haptic audio synchronization", __func__);
+                platform_set_qtime(adev->platform, out->pcm_device_id, adev->haptic_pcm_device_id);
+            }
         }
 
         if (!out->realtime)
diff --git a/hal/msm8974/platform.c b/hal/msm8974/platform.c
index f5aa3c4..b2d8b02 100644
--- a/hal/msm8974/platform.c
+++ b/hal/msm8974/platform.c
@@ -95,6 +95,10 @@
 
 #include <resolv.h>
 
+#define QTIME_FREQ_KHZ  19200
+#define IPC_ERROR_DELAY 10000
+
+#define max(a, b) ((a) > (b) ? (a) : (b))
 #define min(a, b) ((a) < (b) ? (a) : (b))
 
 #define TOSTRING_(x) #x
@@ -3975,6 +3979,186 @@
     return HAPTICS_PCM_DEVICE;
 }
 
+uint64_t getQtime()
+{
+    uint64_t qTimerCount = 0;
+
+#if __aarch64__
+    asm volatile("mrs %0, cntvct_el0" : "=r" (qTimerCount));
+#else
+    asm volatile("mrrc p15, 1, %Q0, %R0, c14" : "=r" (qTimerCount));
+#endif
+
+    return qTimerCount;
+}
+
+int platform_get_delay(void *platform, int pcm_device_id)
+{
+    int ctl_len = 0;
+    struct audio_device *adev = ((struct platform_data *)platform)->adev;
+    struct mixer_ctl *ctl = NULL;
+    const char *mixer_ctl_name = "ADSP Path Latency";
+    const char *deviceNo = "NN";
+    char *mixer_str = NULL;
+    int path_delay = 0;
+
+    if (NULL == platform) {
+        ALOGE("%s: platform is NULL", __func__);
+        return -EINVAL;
+    }
+    if (pcm_device_id <= 0) {
+        ALOGE("%s: invalid pcm device id: %d", __func__, pcm_device_id);
+        return -EINVAL;
+    }
+
+    // Mixer control format: "ADSP Path Latency NN"
+    ctl_len = strlen(mixer_ctl_name) + 1 + strlen(deviceNo) + 1;
+
+    mixer_str = (char*) calloc(ctl_len, sizeof(char));
+    if (!mixer_str) {
+        ALOGE("%s: Could not allocate memory", __func__);
+        return -ENOMEM;
+    }
+
+    snprintf(mixer_str, ctl_len, "%s %d", mixer_ctl_name, pcm_device_id);
+
+    ctl = mixer_get_ctl_by_name(adev->mixer, mixer_str);
+    if (!ctl) {
+        ALOGE("%s: Could not get ctl for mixer cmd - %s", __func__, mixer_str);
+        free(mixer_str);
+        return -EINVAL;
+    }
+
+    path_delay = mixer_ctl_get_value(ctl, 0);
+    if (path_delay < 0) {
+        ALOGE("%s: Could not get val for mixer cmd - %s", __func__, mixer_str);
+    }
+    ALOGD("%s: Path Delay: %d", __func__, path_delay);
+
+    free(mixer_str);
+    return path_delay;
+}
+
+int send_qtime(void *platform, uint64_t qtime_value, int pcm_device_id)
+{
+    int ret = 0;
+    int ctl_len = 0;
+    struct audio_device *adev = ((struct platform_data *)platform)->adev;
+    struct mixer_ctl *ctl = NULL;
+    const char *mixer_ctl_name = "QTimer";
+    const char *deviceNo = "NN";
+    char *mixer_str = NULL;
+    uint32_t set_values[2];
+
+    set_values[0] = (uint32_t)qtime_value;
+    set_values[1] = (uint32_t)((qtime_value >> 16) >> 16);
+    ALOGD("%s: Send qtime msw: %u, lsw: %u", __func__, set_values[1],
+          set_values[0]);
+
+    // Mixer control format: "Qtimer NN"
+    ctl_len = strlen(mixer_ctl_name) + 1 + strlen(deviceNo) + 1;
+
+    mixer_str = (char*) calloc(ctl_len, sizeof(char));
+    if (!mixer_str) {
+        ALOGE("%s: Could not allocate memory", __func__);
+        return -ENOMEM;
+    }
+
+    snprintf(mixer_str, ctl_len, "%s %d", mixer_ctl_name, pcm_device_id);
+
+    ctl = mixer_get_ctl_by_name(adev->mixer, mixer_str);
+    if (!ctl) {
+        ALOGE("%s: Could not get ctl for mixer cmd - %s",
+              __func__, mixer_str);
+        free(mixer_str);
+        return -EINVAL;
+    }
+
+    ret = mixer_ctl_set_array(ctl, set_values, ARRAY_SIZE(set_values));
+    if (ret < 0) {
+        ALOGE("%s: Could not set array for mixer cmd - %s",
+              __func__, mixer_str);
+    }
+    free(mixer_str);
+
+    return ret;
+}
+
+int platform_set_qtime(void *platform, int audio_pcm_device_id,
+                        int haptic_pcm_device_id)
+{
+    int ret = 0;
+    uint64_t qtime_count = 0;
+    uint64_t qtime_value = 0;
+    uint32_t qtime_remainder = 0;
+    int32_t audio_path_latency = 0;
+    int32_t haptic_path_latency = 0;
+
+    if (NULL == platform) {
+        ALOGE("%s: platform is NULL", __func__);
+        return -EINVAL;
+    }
+    if (audio_pcm_device_id <= 0 || haptic_pcm_device_id <= 0) {
+        ALOGE("%s: Invalid pcm device id - %d", __func__,
+              audio_pcm_device_id <= 0 ? audio_pcm_device_id
+              : haptic_pcm_device_id);
+        return -EINVAL;
+    }
+
+    audio_path_latency = platform_get_delay(platform, audio_pcm_device_id);
+    if (audio_path_latency <= 0) {
+        ALOGE("%s: error getting audio path latency: %d", __func__,
+              audio_path_latency);
+        return -EINVAL;
+    }
+    ALOGD("%s: Audio Path Latency: %d", __func__, audio_path_latency);
+
+    haptic_path_latency = platform_get_delay(platform, haptic_pcm_device_id);
+    if (haptic_path_latency <= 0) {
+        ALOGE("%s: error getting haptic path latency: %d", __func__,
+              haptic_path_latency);
+        return -EINVAL;
+    }
+    ALOGD("%s: Haptic Path Latency: %d", __func__, haptic_path_latency);
+
+    qtime_count = getQtime();
+
+    // Qtime count / Qtime freq (KHZ) = Qtime in milliseconds
+    qtime_value = (uint64_t) (qtime_count / QTIME_FREQ_KHZ);
+
+    // Convert Qtime to microseconds
+    qtime_value *= 1000;
+
+    // Adding max(path_latency)
+    qtime_value += (uint32_t) max(audio_path_latency, haptic_path_latency);
+
+    // Adding IPC delay + error correction ~10ms
+    qtime_value += IPC_ERROR_DELAY;
+
+    // Calculate remainder in microseconds
+    qtime_remainder = ((qtime_count % QTIME_FREQ_KHZ) * 1000) / QTIME_FREQ_KHZ;
+
+    // Add the remainder to qtime
+    qtime_value += qtime_remainder;
+    ALOGD("%s: Set qtime: %llu microsecs\n", __func__,
+          (unsigned long long int)qtime_value);
+
+    ret = send_qtime(platform, qtime_value, haptic_pcm_device_id);
+    if (ret < 0) {
+        ALOGE("%s: Could not send qtime for haptic session - %d",
+              __func__, ret);
+        return ret;
+    }
+
+    ret = send_qtime(platform, qtime_value, audio_pcm_device_id);
+    if (ret < 0) {
+        ALOGE("%s: Could not send qtime for audio session - %d",
+              __func__, ret);
+    }
+
+    return ret;
+}
+
 static int find_index(struct name_to_index * table, int32_t len, const char * name)
 {
     int ret = 0;
diff --git a/hal/platform_api.h b/hal/platform_api.h
index f9e5f2a..f43aa88 100644
--- a/hal/platform_api.h
+++ b/hal/platform_api.h
@@ -347,6 +347,9 @@
 
 int platform_get_license_by_product(void *platform, const char* product_name, int *product_id, char* product_license);
 int platform_get_haptics_pcm_device_id();
+int platform_set_qtime(void *platform, int audio_pcm_device_id,
+                       int haptic_pcm_device_id);
+int platform_get_delay(void *platform, int pcm_device_id);
 struct audio_custom_mtmx_params *
     platform_get_custom_mtmx_params(void *platform,
                                     struct audio_custom_mtmx_params_info *info);