diff --git a/device.mk b/device.mk index dbe41c6..51a810a 100644 --- a/device.mk +++ b/device.mk @@ -221,6 +221,9 @@ PRODUCT_PACKAGES += \ PRODUCT_PACKAGES += \ bdAddrLoader +PRODUCT_PACKAGES += \ + power.hammerhead + PRODUCT_PROPERTY_OVERRIDES += \ ro.opengles.version=196608 diff --git a/power/Android.mk b/power/Android.mk new file mode 100644 index 0000000..60ffb11 --- /dev/null +++ b/power/Android.mk @@ -0,0 +1,25 @@ +# +# Copyright 2014 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE_RELATIVE_PATH := hw +LOCAL_SRC_FILES := power_hammerhead.c +LOCAL_SHARED_LIBRARIES := liblog libcutils +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE := power.hammerhead +include $(BUILD_SHARED_LIBRARY) diff --git a/power/power_hammerhead.c b/power/power_hammerhead.c new file mode 100644 index 0000000..d025f3a --- /dev/null +++ b/power/power_hammerhead.c @@ -0,0 +1,409 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LOG_TAG "PowerHAL" +#include + +#include +#include + +#define STATE_ON "state=1" +#define STATE_OFF "state=0" +#define STATE_HDR_ON "state=2" +#define STATE_HDR_OFF "state=3" + +#define MAX_LENGTH 50 +#define BOOST_SOCKET "/dev/socket/pb" + +#define UEVENT_MSG_LEN 1024 +#define TOTAL_CPUS 4 +#define RETRY_TIME_CHANGING_FREQ 20 +#define SLEEP_USEC_BETWN_RETRY 200 +#define LOW_POWER_MAX_FREQ "729600" +#define LOW_POWER_MIN_FREQ "300000" +#define NORMAL_MAX_FREQ "2265600" +#define UEVENT_STRING "online@/devices/system/cpu/" + +static int client_sockfd; +static struct sockaddr_un client_addr; +static int last_state = -1; + +static struct pollfd pfd; +static char *cpu_path_min[] = { + "/sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq", + "/sys/devices/system/cpu/cpu1/cpufreq/scaling_min_freq", + "/sys/devices/system/cpu/cpu2/cpufreq/scaling_min_freq", + "/sys/devices/system/cpu/cpu3/cpufreq/scaling_min_freq", +}; +static char *cpu_path_max[] = { + "/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq", + "/sys/devices/system/cpu/cpu1/cpufreq/scaling_max_freq", + "/sys/devices/system/cpu/cpu2/cpufreq/scaling_max_freq", + "/sys/devices/system/cpu/cpu3/cpufreq/scaling_max_freq", +}; +static bool freq_set[TOTAL_CPUS]; +static bool low_power_mode = false; +static pthread_mutex_t low_power_mode_lock = PTHREAD_MUTEX_INITIALIZER; + +static void socket_init() +{ + if (!client_sockfd) { + client_sockfd = socket(PF_UNIX, SOCK_DGRAM, 0); + if (client_sockfd < 0) { + ALOGE("%s: failed to open: %s", __func__, strerror(errno)); + return; + } + memset(&client_addr, 0, sizeof(struct sockaddr_un)); + client_addr.sun_family = AF_UNIX; + snprintf(client_addr.sun_path, UNIX_PATH_MAX, BOOST_SOCKET); + } +} + +static int sysfs_write(const char *path, char *s) +{ + char buf[80]; + int len; + int fd = open(path, O_WRONLY); + + if (fd < 0) { + strerror_r(errno, buf, sizeof(buf)); + ALOGE("Error opening %s: %s\n", path, buf); + return -1; + } + + len = write(fd, s, strlen(s)); + if (len < 0) { + strerror_r(errno, buf, sizeof(buf)); + ALOGE("Error writing to %s: %s\n", path, buf); + return -1; + } + + close(fd); + return 0; +} + +static int uevent_event() +{ + char msg[UEVENT_MSG_LEN]; + char *cp; + int n, cpu, ret, retry = RETRY_TIME_CHANGING_FREQ; + + n = recv(pfd.fd, msg, UEVENT_MSG_LEN, MSG_DONTWAIT); + if (n <= 0) { + return -1; + } + if (n >= UEVENT_MSG_LEN) { /* overflow -- discard */ + return -1; + } + + cp = msg; + + if (strstr(cp, UEVENT_STRING)) { + n = strlen(cp); + errno = 0; + cpu = strtol(cp + n - 1, NULL, 10); + + if (errno == EINVAL || errno == ERANGE || cpu < 0 || cpu >= TOTAL_CPUS) { + return -1; + } + + pthread_mutex_lock(&low_power_mode_lock); + if (low_power_mode && !freq_set[cpu]) { + while (retry) { + sysfs_write(cpu_path_min[cpu], LOW_POWER_MIN_FREQ); + ret = sysfs_write(cpu_path_max[cpu], LOW_POWER_MAX_FREQ); + if (!ret) { + freq_set[cpu] = true; + break; + } + usleep(SLEEP_USEC_BETWN_RETRY); + retry--; + } + } else if (!low_power_mode && freq_set[cpu]) { + while (retry) { + ret = sysfs_write(cpu_path_max[cpu], NORMAL_MAX_FREQ); + if (!ret) { + freq_set[cpu] = false; + break; + } + usleep(SLEEP_USEC_BETWN_RETRY); + retry--; + } + } + pthread_mutex_unlock(&low_power_mode_lock); + } + return 0; +} + +void *thread_uevent(__attribute__((unused)) void *x) +{ + while (1) { + int nevents, ret; + + nevents = poll(&pfd, 1, -1); + + if (nevents == -1) { + if (errno == EINTR) + continue; + ALOGE("powerhal: thread_uevent: poll_wait failed\n"); + break; + } + ret = uevent_event(); + if (ret < 0) + ALOGE("Error processing the uevent event"); + } + return NULL; +} + +static void uevent_init() +{ + struct sockaddr_nl client; + pthread_t tid; + pfd.fd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT); + + if (pfd.fd < 0) { + ALOGE("%s: failed to open: %s", __func__, strerror(errno)); + return; + } + memset(&client, 0, sizeof(struct sockaddr_nl)); + pthread_create(&tid, NULL, thread_uevent, NULL); + client.nl_family = AF_NETLINK; + client.nl_pid = tid; + client.nl_groups = -1; + pfd.events = POLLIN; + bind(pfd.fd, (void *)&client, sizeof(struct sockaddr_nl)); + return; +} + +static void power_init(__attribute__((unused)) struct power_module *module) +{ + ALOGI("%s", __func__); + socket_init(); + uevent_init(); +} + +static void sync_thread(int off) +{ + int rc; + pid_t client; + char data[MAX_LENGTH]; + + if (client_sockfd < 0) { + ALOGE("%s: boost socket not created", __func__); + return; + } + + client = getpid(); + + if (!off) { + snprintf(data, MAX_LENGTH, "2:%d", client); + rc = sendto(client_sockfd, data, strlen(data), 0, + (const struct sockaddr *)&client_addr, sizeof(struct sockaddr_un)); + } else { + snprintf(data, MAX_LENGTH, "3:%d", client); + rc = sendto(client_sockfd, data, strlen(data), 0, + (const struct sockaddr *)&client_addr, sizeof(struct sockaddr_un)); + } + + if (rc < 0) { + ALOGE("%s: failed to send: %s", __func__, strerror(errno)); + } +} + +static void enc_boost(int off) +{ + int rc; + pid_t client; + char data[MAX_LENGTH]; + + if (client_sockfd < 0) { + ALOGE("%s: boost socket not created", __func__); + return; + } + + client = getpid(); + + if (!off) { + snprintf(data, MAX_LENGTH, "5:%d", client); + rc = sendto(client_sockfd, data, strlen(data), 0, + (const struct sockaddr *)&client_addr, sizeof(struct sockaddr_un)); + } else { + snprintf(data, MAX_LENGTH, "6:%d", client); + rc = sendto(client_sockfd, data, strlen(data), 0, + (const struct sockaddr *)&client_addr, sizeof(struct sockaddr_un)); + } + + if (rc < 0) { + ALOGE("%s: failed to send: %s", __func__, strerror(errno)); + } +} + +static void process_video_encode_hint(void *metadata) +{ + + socket_init(); + + if (client_sockfd < 0) { + ALOGE("%s: boost socket not created", __func__); + return; + } + + if (metadata) { + if (!strncmp(metadata, STATE_ON, sizeof(STATE_ON))) { + /* Video encode started */ + sync_thread(1); + enc_boost(1); + } else if (!strncmp(metadata, STATE_OFF, sizeof(STATE_OFF))) { + /* Video encode stopped */ + sync_thread(0); + enc_boost(0); + } else if (!strncmp(metadata, STATE_HDR_ON, sizeof(STATE_HDR_ON))) { + /* HDR usecase started */ + } else if (!strncmp(metadata, STATE_HDR_OFF, sizeof(STATE_HDR_OFF))) { + /* HDR usecase stopped */ + }else + return; + } else { + return; + } +} + + +static void touch_boost() +{ + int rc; + pid_t client; + char data[MAX_LENGTH]; + + if (client_sockfd < 0) { + ALOGE("%s: boost socket not created", __func__); + return; + } + + client = getpid(); + + snprintf(data, MAX_LENGTH, "1:%d", client); + rc = sendto(client_sockfd, data, strlen(data), 0, + (const struct sockaddr *)&client_addr, sizeof(struct sockaddr_un)); + if (rc < 0) { + ALOGE("%s: failed to send: %s", __func__, strerror(errno)); + } +} + +static void power_set_interactive(__attribute__((unused)) struct power_module *module, int on) +{ + if (last_state == -1) { + last_state = on; + } else { + if (last_state == on) + return; + else + last_state = on; + } + + ALOGV("%s %s", __func__, (on ? "ON" : "OFF")); + if (on) { + sync_thread(0); + touch_boost(); + } else { + sync_thread(1); + } +} + +static void power_hint( __attribute__((unused)) struct power_module *module, + power_hint_t hint, __attribute__((unused)) void *data) +{ + int cpu, ret; + + switch (hint) { + case POWER_HINT_INTERACTION: + ALOGV("POWER_HINT_INTERACTION"); + touch_boost(); + break; +#if 0 + case POWER_HINT_VSYNC: + ALOGV("POWER_HINT_VSYNC %s", (data ? "ON" : "OFF")); + break; +#endif + case POWER_HINT_VIDEO_ENCODE: + process_video_encode_hint(data); + break; + + case POWER_HINT_LOW_POWER: + pthread_mutex_lock(&low_power_mode_lock); + if (data) { + low_power_mode = true; + for (cpu = 0; cpu < TOTAL_CPUS; cpu++) { + sysfs_write(cpu_path_min[cpu], LOW_POWER_MIN_FREQ); + ret = sysfs_write(cpu_path_max[cpu], LOW_POWER_MAX_FREQ); + if (!ret) { + freq_set[cpu] = true; + } + } + // reduces the refresh rate + system("service call SurfaceFlinger 1016"); + } else { + low_power_mode = false; + for (cpu = 0; cpu < TOTAL_CPUS; cpu++) { + ret = sysfs_write(cpu_path_max[cpu], NORMAL_MAX_FREQ); + if (!ret) { + freq_set[cpu] = false; + } + } + // restores the refresh rate + system("service call SurfaceFlinger 1017"); + } + pthread_mutex_unlock(&low_power_mode_lock); + break; + default: + break; + } +} + +static struct hw_module_methods_t power_module_methods = { + .open = NULL, +}; + +struct power_module HAL_MODULE_INFO_SYM = { + .common = { + .tag = HARDWARE_MODULE_TAG, + .module_api_version = POWER_MODULE_API_VERSION_0_2, + .hal_api_version = HARDWARE_HAL_API_VERSION, + .id = POWER_HARDWARE_MODULE_ID, + .name = "Hammerhead Power HAL", + .author = "The Android Open Source Project", + .methods = &power_module_methods, + }, + + .init = power_init, + .setInteractive = power_set_interactive, + .powerHint = power_hint, +};