power: Add Platform-level sleep state stats for bullhead

Bug: 26564574
Change-Id: I12ff2260a28584d2db6289f062ce44d7d486b18c
diff --git a/power/power.c b/power/power.c
index 308ea0c..76a5aa2 100644
--- a/power/power.c
+++ b/power/power.c
@@ -30,6 +30,7 @@
 #define LOG_NIDEBUG 0
 
 #include <errno.h>
+#include <inttypes.h>
 #include <string.h>
 #include <sys/types.h>
 #include <sys/stat.h>
@@ -48,6 +49,36 @@
 #include "performance.h"
 #include "power-common.h"
 
+#define PLATFORM_SLEEP_MODES 2
+#define XO_VOTERS 3
+#define VMIN_VOTERS 0
+
+#define RPM_PARAMETERS 4
+#define NUM_PARAMETERS 10
+
+#ifndef RPM_STAT
+#define RPM_STAT "/d/rpm_stats"
+#endif
+
+#ifndef RPM_MASTER_STAT
+#define RPM_MASTER_STAT "/d/rpm_master_stats"
+#endif
+
+/* RPM runs at 19.2Mhz. Divide by 19200 for msec */
+#define RPM_CLK 19200
+
+const char *parameter_names[] = {
+    "vlow_count",
+    "accumulated_vlow_time",
+    "vmin_count",
+    "accumulated_vmin_time",
+    "xo_accumulated_duration",
+    "xo_count",
+    "xo_accumulated_duration",
+    "xo_count",
+    "xo_accumulated_duration",
+    "xo_count"};
+
 static int saved_dcvs_cpu0_slack_max = -1;
 static int saved_dcvs_cpu0_slack_min = -1;
 static int saved_mpdecision_slack_max = -1;
@@ -505,10 +536,125 @@
     saved_interactive_mode = !!on;
 }
 
+static ssize_t get_number_of_platform_modes(struct power_module *module) {
+   return PLATFORM_SLEEP_MODES;
+}
+
+static int get_voter_list(struct power_module *module, size_t *voter) {
+   voter[0] = XO_VOTERS;
+   voter[1] = VMIN_VOTERS;
+
+   return 0;
+}
+
+static int extract_stats(uint64_t *list, char *file,
+    unsigned int num_parameters, unsigned int index) {
+    FILE *fp;
+    ssize_t read;
+    size_t len;
+    char *line;
+    int ret;
+
+    fp = fopen(file, "r");
+    if (fp == NULL) {
+        ret = -errno;
+        ALOGE("%s: failed to open: %s", __func__, strerror(errno));
+        return ret;
+    }
+
+    for (line = NULL, len = 0;
+         ((read = getline(&line, &len, fp) != -1) && (index < num_parameters));
+         free(line), line = NULL, len = 0) {
+        uint64_t value;
+        char* offset;
+
+        size_t begin = strspn(line, " \t");
+        if (strncmp(line + begin, parameter_names[index], strlen(parameter_names[index]))) {
+            continue;
+        }
+
+        offset = memchr(line, ':', len);
+        if (!offset) {
+            continue;
+        }
+
+        if (!strcmp(file, RPM_MASTER_STAT)) {
+            /* RPM_MASTER_STAT is reported in hex */
+            sscanf(offset, ":%" SCNx64, &value);
+            /* Duration is reported in rpm SLEEP TICKS */
+            if (!strcmp(parameter_names[index], "xo_accumulated_duration")) {
+                value /= RPM_CLK;
+            }
+        } else {
+            /* RPM_STAT is reported in decimal */
+            sscanf(offset, ":%" SCNu64, &value);
+        }
+        list[index] = value;
+        index++;
+    }
+    free(line);
+
+    fclose(fp);
+    return 0;
+}
+
+static int get_platform_low_power_stats(struct power_module *module,
+    power_state_platform_sleep_state_t *list) {
+    uint64_t stats[sizeof(parameter_names)] = {0};
+    int ret;
+
+    if (!list) {
+        return -EINVAL;
+    }
+
+    ret = extract_stats(stats, RPM_STAT, RPM_PARAMETERS, 0);
+
+    if (ret) {
+        return ret;
+    }
+
+    ret = extract_stats(stats, RPM_MASTER_STAT, NUM_PARAMETERS, 4);
+
+    if (ret) {
+        return ret;
+    }
+
+    /* Update statistics for XO_shutdown */
+    strcpy(list[0].name, "XO_shutdown");
+    list[0].total_transitions = stats[0];
+    list[0].residency_in_msec_since_boot = stats[1];
+    list[0].supported_only_in_suspend = false;
+    list[0].number_of_voters = XO_VOTERS;
+
+    /* Update statistics for APSS voter */
+    strcpy(list[0].voters[0].name, "APSS");
+    list[0].voters[0].total_time_in_msec_voted_for_since_boot = stats[4];
+    list[0].voters[0].total_number_of_times_voted_since_boot = stats[5];
+
+    /* Update statistics for MPSS voter */
+    strcpy(list[0].voters[1].name, "MPSS");
+    list[0].voters[1].total_time_in_msec_voted_for_since_boot = stats[6];
+    list[0].voters[1].total_number_of_times_voted_since_boot = stats[7];
+
+    /* Update statistics for LPASS voter */
+    strcpy(list[0].voters[2].name, "LPASS");
+    list[0].voters[2].total_time_in_msec_voted_for_since_boot = stats[8];
+    list[0].voters[2].total_number_of_times_voted_since_boot = stats[9];
+
+    /* Update statistics for VMIN state */
+    strcpy(list[1].name, "VMIN");
+    list[1].total_transitions = stats[2];
+    list[1].residency_in_msec_since_boot = stats[3];
+    list[1].supported_only_in_suspend = false;
+    list[1].number_of_voters = VMIN_VOTERS;
+
+    return 0;
+}
+
 struct power_module HAL_MODULE_INFO_SYM = {
     .common = {
         .tag = HARDWARE_MODULE_TAG,
-        .module_api_version = POWER_MODULE_API_VERSION_0_2,
+        .module_api_version = POWER_MODULE_API_VERSION_0_5,
         .hal_api_version = HARDWARE_HAL_API_VERSION,
         .id = POWER_HARDWARE_MODULE_ID,
         .name = "QCOM Power HAL",
@@ -519,4 +665,7 @@
     .init = power_init,
     .powerHint = power_hint,
     .setInteractive = set_interactive,
+    .get_number_of_platform_modes = get_number_of_platform_modes,
+    .get_platform_low_power_stats = get_platform_low_power_stats,
+    .get_voter_list = get_voter_list
 };