hal: add audio notification acknowledge feature

- Modify sysfs node to allow audio hal to acknowledge
  every HDMI connect/disconnect event.
- This makes audio hal and HDMI driver to be in sync
  with respect to notifications and avoid any fast
  plug-in/out notification issues.

Change-Id: I6aead358967c3e978f4f599de66c9884b28bf7e9
diff --git a/hal/audio_extn/audio_extn.c b/hal/audio_extn/audio_extn.c
index e7822b3..265f2c0 100644
--- a/hal/audio_extn/audio_extn.c
+++ b/hal/audio_extn/audio_extn.c
@@ -42,6 +42,7 @@
 #include <stdlib.h>
 #include <errno.h>
 #include <dlfcn.h>
+#include <fcntl.h>
 #include <cutils/properties.h>
 #include <cutils/log.h>
 
@@ -82,6 +83,124 @@
 #define AUDIO_PARAMETER_OFFLOAD_NUM_ACTIVE "offload_num_active"
 #define AUDIO_PARAMETER_HPX            "HPX"
 
+/*
+* update sysfs node hdmi_audio_cb to enable notification acknowledge feature
+* bit(5) set to 1 to enable this feature
+* bit(4) set to 1 to enable acknowledgement
+* this is done only once at the first connect event
+*
+* bit(0) set to 1 when HDMI cable is connected
+* bit(0) set to 0 when HDMI cable is disconnected
+* this is done when device switch happens by setting audioparamter
+*/
+
+#define HDMI_PLUG_STATUS_NOTIFY_ENABLE 0x30
+
+static ssize_t update_sysfs_node(const char *path, const char *data, size_t len)
+{
+    ssize_t err = 0;
+    int fd = -1;
+
+    err = access(path, W_OK);
+    if (!err) {
+        fd = open(path, O_WRONLY);
+        errno = 0;
+        err = write(fd, data, len);
+        if (err < 0) {
+            err = -errno;
+        }
+        close(fd);
+    } else {
+        ALOGE("%s: Failed to access path: %s error: %s",
+                __FUNCTION__, path, strerror(errno));
+        err = -errno;
+    }
+
+    return err;
+}
+
+static int get_hdmi_sysfs_node_index()
+{
+    static int node_index = -1;
+    char fbvalue[80] = {0};
+    char fbpath[80] = {0};
+    int i = 0;
+    FILE *hdmi_fp = NULL;
+
+    if(node_index >= 0) {
+        //hdmi sysfs node will not change so we just need to get the index once.
+        ALOGV("HDMI sysfs node is at fb%d", node_index);
+        return node_index;
+    }
+
+    for(i = 0; i < 3; i++) {
+        snprintf(fbpath, sizeof(fbpath),
+                  "/sys/class/graphics/fb%d/msm_fb_type", i);
+        hdmi_fp = fopen(fbpath, "r");
+        if(hdmi_fp) {
+            fread(fbvalue, sizeof(char), 80, hdmi_fp);
+            if(strncmp(fbvalue, "dtv panel", strlen("dtv panel")) == 0) {
+                node_index = i;
+                ALOGV("HDMI is at fb%d",i);
+                fclose(hdmi_fp);
+                return node_index;
+            }
+            fclose(hdmi_fp);
+        } else {
+            ALOGE("Failed to open fb node %d",i);
+        }
+    }
+
+    return -1;
+}
+
+static int update_hdmi_sysfs_node(int node_value)
+{
+    char hdmi_ack_path[80] = {0};
+    char hdmi_ack_value[3] = {0};
+    int index, ret = -1;
+
+    index = get_hdmi_sysfs_node_index();
+
+    if (index >= 0) {
+        snprintf(hdmi_ack_value, sizeof(hdmi_ack_value), "%d", node_value);
+        snprintf(hdmi_ack_path, sizeof(hdmi_ack_path),
+                  "/sys/class/graphics/fb%d/hdmi_audio_cb", index);
+
+        ret = update_sysfs_node(hdmi_ack_path, hdmi_ack_value,
+                sizeof(hdmi_ack_value));
+
+        ALOGI("update hdmi_audio_cb at fb[%d] to:[%d] %s",
+            index, node_value, (ret >= 0) ? "success":"fail");
+    }
+
+    return ret;
+}
+
+static void check_and_set_hdmi_connection_status(struct str_parms *parms)
+{
+    char value[32] = {0};
+    static bool is_hdmi_sysfs_node_init = false;
+
+    if (str_parms_get_str(parms, "connect", value, sizeof(value)) >= 0
+            && (atoi(value) & AUDIO_DEVICE_OUT_HDMI)) {
+        //params = "connect=1024" for HDMI connection.
+        if (is_hdmi_sysfs_node_init == false) {
+            is_hdmi_sysfs_node_init = true;
+            update_hdmi_sysfs_node(HDMI_PLUG_STATUS_NOTIFY_ENABLE);
+        }
+        update_hdmi_sysfs_node(1);
+    } else if(str_parms_get_str(parms, "disconnect", value, sizeof(value)) >= 0
+            && (atoi(value) & AUDIO_DEVICE_OUT_HDMI)){
+        //params = "disconnect=1024" for HDMI disconnection.
+        update_hdmi_sysfs_node(0);
+    } else {
+        // handle hdmi devices only
+        return;
+    }
+}
+
+
 #ifndef FM_POWER_OPT
 #define audio_extn_fm_set_parameters(adev, parms) (0)
 #else
@@ -574,6 +693,7 @@
    audio_extn_hpx_set_parameters(adev, parms);
    audio_extn_pm_set_parameters(parms);
    audio_extn_source_track_set_parameters(adev, parms);
+   check_and_set_hdmi_connection_status(parms);
    if (adev->offload_effects_set_parameters != NULL)
        adev->offload_effects_set_parameters(parms);
 }