blob: 68899abd886be3b8cd7455bc9029190aa83f868c [file] [log] [blame]
/*
* Copyright (C) 2012 The Android Open Source Project
* Copyright (c) 2012-2015, NVIDIA CORPORATION. All rights reserved.
* Copyright (C) 2015 The CyanogenMod 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.
*/
#define LOG_TAG "powerHAL::common"
#include <hardware/hardware.h>
#include <hardware/power.h>
#include "powerhal_utils.h"
#include "powerhal.h"
#ifdef POWER_MODE_SET_INTERACTIVE
static int get_system_power_mode(void);
static void set_interactive_governor(int mode);
static const interactive_data_t interactive_data_array[] =
{
{ "1122000", "65 304000:75 1122000:80", "19000", "20000", "0", "41000", "90" },
{ "1020000", "65 256000:75 1020000:80", "19000", "20000", "0", "30000", "99" },
{ "640000", "65 256000:75 640000:80", "80000", "20000", "2", "30000", "99" },
{ "1020002", "65 256000:75 1020000:80", "19000", "20000", "0", "30000", "99" },
{ "420000", "80", "80000", "300000","2", "30000", "99" }
};
#endif
static const int VSyncActiveBoostFreq = 300000;
static int uC_input_number = 1;
/*static void set_led_state(int on)
{
struct input_event ev_out;
int len;
char buf[80];
int size;
int fd;
bool breathe = get_property_bool(LED_BREATHE_PROP, true);
char path[20];
size = sizeof(struct input_event);
on = (on == 0)?0:1;
snprintf(path, sizeof(path), "/dev/input/event%d", uC_input_number);
fd = open(path, O_WRONLY);
if (fd < 0) {
strerror_r(errno, buf, sizeof(buf));
ALOGE("Error opening %s: %s\n", path, buf);
return;
}
ev_out.type = EV_LED;
if (on == 1) {
ev_out.code = LED_CAPSL;
} else {
if (breathe == true)
ev_out.code = LED_SCROLLL;
else
ev_out.code = LED_COMPOSE;
}
ev_out.value = 1;
len = write(fd, &ev_out, size);
usleep(10000);
if (len < 0) {
strerror_r(errno, buf, sizeof(buf));
ALOGE("Error writing to %s: %s\n", path, buf);
}
ev_out.value = 0;
len = write(fd, &ev_out, size);
usleep(10000);
if (len < 0) {
strerror_r(errno, buf, sizeof(buf));
ALOGE("Error writing to %s: %s\n", path, buf);
}
close(fd);
}*/
static int get_input_count(void)
{
int i = 0;
int ret;
char path[80];
char name[50];
while(1)
{
snprintf(path, sizeof(path), "/sys/class/input/input%d/name", i);
ret = access(path, F_OK);
if (ret < 0)
break;
memset(name, 0, 50);
sysfs_read(path, name, 50);
if (0 == strcmp(name, "NVIDIA Corporation NVIDIA Controller v01.01\n"))
uC_input_number = i;
ALOGI("input device id:%d present with name:%s", i++, name);
}
return i;
}
static void find_input_device_ids(struct powerhal_info *pInfo)
{
int i = 0;
int status;
int count = 0;
char path[80];
char name[MAX_CHARS];
while (1)
{
snprintf(path, sizeof(path), "/sys/class/input/input%d/name", i);
if (access(path, F_OK) < 0)
break;
else {
memset(name, 0, MAX_CHARS);
sysfs_read(path, name, MAX_CHARS);
for (int j = 0; j < pInfo->input_cnt; j++) {
status = (-1 == pInfo->input_devs[j].dev_id)
&& (0 == strncmp(name,
pInfo->input_devs[j].dev_name, MAX_CHARS));
if (status) {
++count;
pInfo->input_devs[j].dev_id = i;
ALOGI("find_input_device_ids: %d %s",
pInfo->input_devs[j].dev_id,
pInfo->input_devs[j].dev_name);
}
}
++i;
}
if (count == pInfo->input_cnt)
break;
}
}
static int check_hint(struct powerhal_info *pInfo, power_hint_t hint, uint64_t *t)
{
struct timespec ts;
uint64_t time;
if (hint >= MAX_POWER_HINT_COUNT) {
ALOGE("Invalid power hint: 0x%x", hint);
return -1;
}
clock_gettime(CLOCK_MONOTONIC, &ts);
time = ts.tv_sec * 1000000 + ts.tv_nsec / 1000;
if (pInfo->hint_time[hint] && pInfo->hint_interval[hint] &&
(time - pInfo->hint_time[hint] < pInfo->hint_interval[hint]))
return -1;
*t = time;
return 0;
}
static bool is_available_frequency(struct powerhal_info *pInfo, int freq)
{
int i;
for(i = 0; i < pInfo->num_available_frequencies; i++) {
if(pInfo->available_frequencies[i] == freq)
return true;
}
return false;
}
void common_power_open(struct powerhal_info *pInfo)
{
int i;
int size = 256;
char *pch;
if (0 == pInfo->input_devs || 0 == pInfo->input_cnt)
pInfo->input_cnt = get_input_count();
else
find_input_device_ids(pInfo);
// Initialize timeout poker
Barrier readyToRun;
pInfo->mTimeoutPoker = new TimeoutPoker(&readyToRun);
readyToRun.wait();
// Read available frequencies
char *buf = (char*)malloc(sizeof(char) * size);
memset(buf, 0, size);
sysfs_read("/sys/devices/system/cpu/cpu0/cpufreq/scaling_available_frequencies",
buf, size);
// Determine number of available frequencies
pch = strtok(buf, " ");
pInfo->num_available_frequencies = -1;
while(pch != NULL)
{
pch = strtok(NULL, " ");
pInfo->num_available_frequencies++;
}
// Store available frequencies in a lookup array
pInfo->available_frequencies = (int*)malloc(sizeof(int) * pInfo->num_available_frequencies);
sysfs_read("/sys/devices/system/cpu/cpu0/cpufreq/scaling_available_frequencies",
buf, size);
pch = strtok(buf, " ");
for(i = 0; i < pInfo->num_available_frequencies; i++)
{
pInfo->available_frequencies[i] = atoi(pch);
pch = strtok(NULL, " ");
}
// Store LP cluster max frequency
sysfs_read("/sys/devices/system/cpu/cpuquiet/tegra_cpuquiet/idle_top_freq",
buf, size);
pInfo->lp_max_frequency = atoi(buf);
pInfo->interaction_boost_frequency = pInfo->lp_max_frequency;
pInfo->animation_boost_frequency = pInfo->lp_max_frequency;
for (i = 0; i < pInfo->num_available_frequencies; i++)
{
if (pInfo->available_frequencies[i] >= 1326000) {
pInfo->interaction_boost_frequency = pInfo->available_frequencies[i];
break;
}
}
for (i = 0; i < pInfo->num_available_frequencies; i++)
{
if (pInfo->available_frequencies[i] >= 1024000) {
pInfo->animation_boost_frequency = pInfo->available_frequencies[i];
break;
}
}
// Store CPU0 max frequency
sysfs_read(SYS_NODE_CPU0_MAX_FREQ, buf, size);
pInfo->cpu0_max_frequency = atoi(buf);
// Initialize hint intervals in usec
//
// Set the interaction timeout to be slightly shorter than the duration of
// the interaction boost so that we can maintain is constantly during
// interaction.
pInfo->hint_interval[POWER_HINT_INTERACTION] = 90000;
//pInfo->hint_interval[POWER_HINT_CPU_BOOST] = 500000;
//pInfo->hint_interval[POWER_HINT_AUDIO] = 500000;
pInfo->hint_interval[POWER_HINT_LOW_POWER] = 0;
// Initialize features
pInfo->features.fan = sysfs_exists("/sys/devices/platform/pwm-fan/pwm_cap");
free(buf);
}
static void set_vsync_min_cpu_freq(struct powerhal_info *pInfo, int enabled)
{
static int vsync_min_cpu = -1;
if (enabled && vsync_min_cpu == -1) {
vsync_min_cpu =
pInfo->mTimeoutPoker->requestPmQos(PMQOS_CONSTRAINT_CPU_FREQ, PM_QOS_BOOST_PRIORITY, PM_QOS_DEFAULT_VALUE, VSyncActiveBoostFreq);
} else if (!enabled && vsync_min_cpu >= 0) {
close(vsync_min_cpu);
vsync_min_cpu = -1;
}
ALOGV("%s: set min CPU floor =%i", __func__, VSyncActiveBoostFreq);
}
static void set_fan_cap(struct powerhal_info *pInfo, int value)
{
if (!pInfo->features.fan)
return;
if (value < 0)
value = 70;
sysfs_write_int("/sys/devices/platform/pwm-fan/pwm_cap", value);
}
static void set_affinity_daemon_enable(__attribute__ ((unused)) struct powerhal_info *pInfo, int value)
{
if (value == 1)
set_property_int(AFFINITY_DAEMON_CONTROL_PROP, 1);
else
set_property_int(AFFINITY_DAEMON_CONTROL_PROP, 0);
ALOGV("%s: set affinity daemon enable =%d", __func__, value);
}
void common_power_init(__attribute__ ((unused)) struct power_module *module, struct powerhal_info *pInfo)
{
common_power_open(pInfo);
pInfo->ftrace_enable = get_property_bool("nvidia.hwc.ftrace_enable", false);
// Boost to max frequency on initialization to decrease boot time
pInfo->mTimeoutPoker->requestPmQosTimed(PMQOS_CONSTRAINT_CPU_FREQ,
PM_QOS_BOOST_PRIORITY,
PM_QOS_DEFAULT_VALUE,
pInfo->available_frequencies[pInfo->num_available_frequencies - 1],
s2ns(15));
}
void common_power_set_interactive(__attribute__ ((unused)) struct power_module *module, struct powerhal_info *pInfo, int on)
{
int i;
int dev_id;
char path[80];
const char* state = (0 == on)?"0":"1";
int power_mode = -1;
sysfs_write("/sys/devices/platform/host1x/nvavp/boost_sclk", state);
if (0 != pInfo) {
for (i = 0; i < pInfo->input_cnt; i++) {
if (0 == pInfo->input_devs)
dev_id = i;
else if (-1 == pInfo->input_devs[i].dev_id)
continue;
else
dev_id = pInfo->input_devs[i].dev_id;
snprintf(path, sizeof(path), "/sys/class/input/input%d/enabled", dev_id);
if (!access(path, W_OK)) {
if (0 == on)
ALOGI("Disabling input device:%d", dev_id);
else
ALOGI("Enabling input device:%d", dev_id);
sysfs_write(path, state);
}
}
}
#ifdef POWER_MODE_SET_INTERACTIVE // Tegra X1
if (on) {
power_mode = get_system_power_mode();
if (power_mode < 0 || power_mode >= sizeof(interactive_data_array)/sizeof(interactive_data_array[0])) {
ALOGV("%s: no system power mode info, take optimized settings", __func__);
power_mode = 0;
}
} else {
power_mode = sizeof(interactive_data_array)/sizeof(interactive_data_array[0]);
}
set_interactive_governor(power_mode);
#elif defined(POWER_MODE_LEGACY) // Tegra 4
sysfs_write("/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor",
(on == 0)?"conservative":"interactive");
#else // Tegra K1
sysfs_write("/sys/devices/system/cpu/cpufreq/interactive/hispeed_freq", (on == 0)?"420000":"624000");
sysfs_write("/sys/devices/system/cpu/cpufreq/interactive/target_loads", (on == 0)?"45 312000:75 564000:85":"65 228000:75 624000:85");
sysfs_write("/sys/devices/system/cpu/cpufreq/interactive/above_hispeed_delay", (on == 0)?"80000":"19000");
sysfs_write("/sys/devices/system/cpu/cpufreq/interactive/timer_rate", (on == 0)?"300000":"20000");
sysfs_write("/sys/devices/system/cpu/cpufreq/interactive/boost_factor", (on == 0)?"2":"0");
#endif
}
#ifdef POWER_MODE_SET_INTERACTIVE
static int get_system_power_mode(void)
{
char value[PROPERTY_VALUE_MAX] = { 0 };
int power_mode = -1;
property_get("persist.sys.NV_POWER_MODE", value, "");
if (value[0] != '\0')
{
power_mode = atoi(value);
}
if (get_property_bool("persist.sys.NV_ECO.STATE.ISECO", false))
{
power_mode = 2;
}
return power_mode;
}
static void __sysfs_write(const char *file, const char *data)
{
if (data != NULL)
{
sysfs_write(file, data);
}
}
static void set_interactive_governor(int mode)
{
__sysfs_write("/sys/devices/system/cpu/cpufreq/interactive/hispeed_freq",
interactive_data_array[mode].hispeed_freq);
__sysfs_write("/sys/devices/system/cpu/cpufreq/interactive/target_loads",
interactive_data_array[mode].target_loads);
__sysfs_write("/sys/devices/system/cpu/cpufreq/interactive/above_hispeed_delay",
interactive_data_array[mode].above_hispeed_delay);
__sysfs_write("/sys/devices/system/cpu/cpufreq/interactive/timer_rate",
interactive_data_array[mode].timer_rate);
__sysfs_write("/sys/devices/system/cpu/cpufreq/interactive/boost_factor",
interactive_data_array[mode].boost_factor);
__sysfs_write("/sys/devices/system/cpu/cpufreq/interactive/min_sample_time",
interactive_data_array[mode].min_sample_time);
__sysfs_write("/sys/devices/system/cpu/cpufreq/interactive/go_hispeed_load",
interactive_data_array[mode].go_hispeed_load);
}
static void set_power_mode_hint(struct powerhal_info *pInfo, int mode)
{
int status;
char value[4] = { 0 };
if (mode < 0 || mode > sizeof(interactive_data_array)/sizeof(interactive_data_array[0]))
{
ALOGE("%s: invalid hint mode = %d", __func__, mode);
return;
}
// only set interactive governor parameters when display on
sysfs_read("/sys/class/backlight/pwm-backlight/brightness", value, sizeof(value));
status = atoi(value);
if (status)
{
set_interactive_governor(mode);
}
}
#endif
void common_power_hint(__attribute__ ((unused)) struct power_module *module, struct powerhal_info *pInfo,
power_hint_t hint, void *data)
{
uint64_t t;
if (!pInfo)
return;
if (check_hint(pInfo, hint, &t) < 0)
return;
switch (hint) {
case POWER_HINT_VSYNC:
if (data)
set_vsync_min_cpu_freq(pInfo, *(int *)data);
break;
case POWER_HINT_INTERACTION:
if (pInfo->ftrace_enable) {
sysfs_write("/sys/kernel/debug/tracing/trace_marker", "Start POWER_HINT_INTERACTION\n");
}
// Boost to interaction_boost_frequency
pInfo->mTimeoutPoker->requestPmQosTimed(PMQOS_CONSTRAINT_CPU_FREQ,
PM_QOS_BOOST_PRIORITY,
PM_QOS_DEFAULT_VALUE,
pInfo->interaction_boost_frequency,
ms2ns(100));
// During the animation, we need some level of CPU/GPU/EMC frequency floor
// to get perfect animation. Forcing CPU frequency through PMQoS does not
// scale EMC fast enough so EMC frequency boosting should be placed first.
pInfo->mTimeoutPoker->requestPmQosTimed(PMQOS_CONSTRAINT_ONLINE_CPUS,
PM_QOS_BOOST_PRIORITY,
PM_QOS_DEFAULT_VALUE,
2,
s2ns(2));
pInfo->mTimeoutPoker->requestPmQosTimed(PMQOS_CONSTRAINT_CPU_FREQ,
PM_QOS_BOOST_PRIORITY,
PM_QOS_DEFAULT_VALUE,
pInfo->animation_boost_frequency,
s2ns(2));
pInfo->mTimeoutPoker->requestPmQosTimed(PMQOS_CONSTRAINT_GPU_FREQ,
PM_QOS_BOOST_PRIORITY,
PM_QOS_DEFAULT_VALUE,
540000,
s2ns(2));
pInfo->mTimeoutPoker->requestPmQosTimed("/dev/emc_freq_min",
396000,
s2ns(2));
break;
//case POWER_HINT_CPU_BOOST:
// // Boost to 1.2Ghz dual core
// pInfo->mTimeoutPoker->requestPmQosTimed(PMQOS_CONSTRAINT_CPU_FREQ,
// PM_QOS_BOOST_PRIORITY,
// PM_QOS_DEFAULT_VALUE,
// INT_MAX,
// ms2ns(1500));
// pInfo->mTimeoutPoker->requestPmQosTimed(PMQOS_CONSTRAINT_ONLINE_CPUS,
// PM_QOS_BOOST_PRIORITY,
// PM_QOS_DEFAULT_VALUE,
// 2,
// ms2ns(1500));
// pInfo->mTimeoutPoker->requestPmQosTimed(PMQOS_CONSTRAINT_GPU_FREQ,
// PM_QOS_BOOST_PRIORITY,
// PM_QOS_DEFAULT_VALUE,
// 180000,
// ms2ns(1500));
// pInfo->mTimeoutPoker->requestPmQosTimed("/dev/emc_freq_min",
// 792000,
// ms2ns(1500));
// break;
//case POWER_HINT_AUDIO:
// // Boost to 512 Mhz frequency for one second
// pInfo->mTimeoutPoker->requestPmQosTimed(PMQOS_CONSTRAINT_CPU_FREQ,
// PM_QOS_BOOST_PRIORITY,
// PM_QOS_DEFAULT_VALUE,
// 512000,
// s2ns(1));
// break;
case POWER_HINT_LOW_POWER:
#ifdef POWER_MODE_SET_INTERACTIVE
// Set interactive governor parameters according to power mode
// This is only partially using the functionality since aosp only has
// two power modes: normal and low power.
set_power_mode_hint(pInfo, (data ? 2 : 0));
#else
// Drop max frequencies and limit to one core for low power mode
pInfo->mTimeoutPoker->requestPmQosTimed(PMQOS_MAX_CPU_FREQ,
PM_QOS_BOOST_PRIORITY,
PM_QOS_DEFAULT_VALUE,
(data ? 1020000 : INT_MAX),
s2ns(1));
pInfo->mTimeoutPoker->requestPmQosTimed(PMQOS_MAX_ONLINE_CPUS,
PM_QOS_BOOST_PRIORITY,
PM_QOS_DEFAULT_VALUE,
(data ? 1 : INT_MAX),
s2ns(1));
#endif
break;
default:
ALOGE("Unknown power hint: 0x%x", hint);
break;
}
pInfo->hint_time[hint] = t;
}