Home | History | Annotate | Download | only in power
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 #include <dirent.h>
     18 #include <errno.h>
     19 #include <string.h>
     20 #include <sys/types.h>
     21 #include <sys/stat.h>
     22 #include <fcntl.h>
     23 #include <unistd.h>
     24 #include <stdlib.h>
     25 #include <stdbool.h>
     26 #include <cutils/properties.h>
     27 //#define LOG_NDEBUG 0
     28 
     29 #define LOG_TAG "DragonPowerHAL"
     30 #include <utils/Log.h>
     31 
     32 #include <hardware/hardware.h>
     33 #include <hardware/power.h>
     34 
     35 #include "timed_qos_manager.h"
     36 
     37 #define BOOSTPULSE_PATH "/sys/devices/system/cpu/cpufreq/interactive/boostpulse"
     38 #define CPU_MAX_FREQ_PATH "/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq"
     39 #define IO_IS_BUSY_PATH "/sys/devices/system/cpu/cpufreq/interactive/io_is_busy"
     40 #define LIGHTBAR_SEQUENCE_PATH "/sys/class/chromeos/cros_ec/lightbar/sequence"
     41 #define IIO_ACTIVITY_DEVICE_PATH "/sys/class/chromeos/cros_ec/device/cros-ec-activity.0"
     42 #define IIO_DOUBLE_TAP_EVENT "events/in_activity_double_tap_change_falling_en"
     43 #define IIO_DEVICE_PREFIX "iio:device"
     44 #define EXT_VOLTAGE_LIM_PATH "/sys/class/chromeos/cros_ec/usb-pd-charger/ext_voltage_lim"
     45 #define EC_POWER_LIMIT_NONE "0xffff"
     46 #define LOW_POWER_MAX_FREQ "1020000"
     47 #define NORMAL_MAX_FREQ "1912500"
     48 #define GPU_CAP_PATH "/sys/kernel/debug/system_edp/capping/force_gpu"
     49 #define LOW_POWER_GPU_CAP "3000"
     50 #define NORMAL_GPU_CAP "0"
     51 #define GPU_BOOST_PATH "/sys/devices/57000000.gpu/pstate"
     52 #define GPU_BOOST_ENTER_CMD "06,0C"    // boost GPU to work at least on 06 - 460MHz
     53 #define GPU_BOOST_DURATION_MS 2000
     54 #define GPU_BOOST_EXIT_CMD "auto"
     55 #define GPU_FREQ_CONSTRAINT "852000 852000 -1 2000"
     56 
     57 struct dragon_power_module {
     58     struct power_module base;
     59     pthread_mutex_t boost_pulse_lock;
     60     pthread_mutex_t low_power_lock;
     61     int boostpulse_fd;
     62     int boostpulse_warned;
     63     TimedQosManager *gpu_qos_manager;
     64 };
     65 
     66 static bool low_power_mode = false;
     67 static char *iio_activity_device = NULL;
     68 
     69 static const char *max_cpu_freq = NORMAL_MAX_FREQ;
     70 static const char *low_power_max_cpu_freq = LOW_POWER_MAX_FREQ;
     71 
     72 static const char *normal_gpu_cap = NORMAL_GPU_CAP;
     73 static const char *low_power_gpu_cap = LOW_POWER_GPU_CAP;
     74 
     75 
     76 void sysfs_write(const char *path, const char *s)
     77 {
     78     char buf[80];
     79     int len;
     80     int fd = open(path, O_WRONLY);
     81 
     82     if (fd < 0) {
     83         strerror_r(errno, buf, sizeof(buf));
     84         ALOGE("Error opening %s: %s\n", path, buf);
     85         return;
     86     }
     87 
     88     len = write(fd, s, strlen(s));
     89     if (len < 0) {
     90         strerror_r(errno, buf, sizeof(buf));
     91         ALOGE("Error writing to %s: %s\n", path, buf);
     92     }
     93 
     94     close(fd);
     95 }
     96 
     97 static void power_init(struct power_module __unused *module)
     98 {
     99     struct dragon_power_module *dragon =
    100             (struct dragon_power_module *) module;
    101 
    102     dragon->gpu_qos_manager = new TimedQosManager("GPU",
    103         new SysfsQosObject(GPU_BOOST_PATH, GPU_BOOST_ENTER_CMD, GPU_BOOST_EXIT_CMD),
    104         false);
    105     dragon->gpu_qos_manager->run("GpuTimedQosManager", PRIORITY_FOREGROUND);
    106 
    107     sysfs_write("/sys/devices/system/cpu/cpufreq/interactive/timer_rate",
    108                 "20000");
    109     sysfs_write("/sys/devices/system/cpu/cpufreq/interactive/timer_slack",
    110                 "20000");
    111     sysfs_write("/sys/devices/system/cpu/cpufreq/interactive/min_sample_time",
    112                 "80000");
    113     sysfs_write("/sys/devices/system/cpu/cpufreq/interactive/hispeed_freq",
    114                 "1530000");
    115     sysfs_write("/sys/devices/system/cpu/cpufreq/interactive/go_hispeed_load",
    116                 "99");
    117     sysfs_write("/sys/devices/system/cpu/cpufreq/interactive/target_loads",
    118                 "65 228000:75 624000:85");
    119     sysfs_write("/sys/devices/system/cpu/cpufreq/interactive/above_hispeed_delay",
    120                 "20000");
    121     sysfs_write("/sys/devices/system/cpu/cpufreq/interactive/boostpulse_duration",
    122                 "1000000");
    123     sysfs_write("/sys/devices/system/cpu/cpufreq/interactive/io_is_busy", "0");
    124     sysfs_write("/sys/class/chromeos/cros_ec/lightbar/userspace_control", "1");
    125 
    126     /* Find the iio device used for activities/estures recognition */
    127     DIR *iio_activity_dir = opendir(IIO_ACTIVITY_DEVICE_PATH);
    128     if (iio_activity_dir == NULL) {
    129         ALOGE("%s is not available.\n", iio_activity_dir);
    130         return;
    131     }
    132     const struct dirent *ent_device;
    133     while (ent_device = readdir(iio_activity_dir), ent_device != NULL) {
    134         if (!strncmp(ent_device->d_name, IIO_DEVICE_PREFIX, strlen(IIO_DEVICE_PREFIX))) {
    135             iio_activity_device = strdup(ent_device->d_name);
    136             break;
    137         }
    138     }
    139     if (iio_activity_device == NULL)
    140         ALOGE("Activity device not found");
    141 }
    142 
    143 static void power_set_interactive(struct power_module __unused *module, int on)
    144 {
    145     ALOGV("power_set_interactive: %d\n", on);
    146 
    147     /*
    148      * Lower maximum frequency when screen is off.
    149      */
    150     sysfs_write(CPU_MAX_FREQ_PATH,
    151                 (!on || low_power_mode) ? low_power_max_cpu_freq : max_cpu_freq);
    152     sysfs_write(IO_IS_BUSY_PATH, on ? "1" : "0");
    153     sysfs_write(LIGHTBAR_SEQUENCE_PATH, on ? "s3s0" : "s0s3");
    154     /* limit charging voltage to 5V when interactive otherwise no limit */
    155     sysfs_write(EXT_VOLTAGE_LIM_PATH, on ? "5000" : EC_POWER_LIMIT_NONE);
    156     if (iio_activity_device != NULL) {
    157         char buf[128];
    158         snprintf(buf, sizeof(buf), "%s/%s/%s", IIO_ACTIVITY_DEVICE_PATH,
    159                  iio_activity_device, IIO_DOUBLE_TAP_EVENT);
    160         sysfs_write(buf, on ? "0" : "1");
    161     }
    162     ALOGV("power_set_interactive: %d done\n", on);
    163 }
    164 
    165 static int boostpulse_open(struct dragon_power_module *dragon)
    166 {
    167     char buf[80];
    168     int len;
    169 
    170     pthread_mutex_lock(&dragon->boost_pulse_lock);
    171 
    172     if (dragon->boostpulse_fd < 0) {
    173         dragon->boostpulse_fd = open(BOOSTPULSE_PATH, O_WRONLY);
    174 
    175         if (dragon->boostpulse_fd < 0) {
    176             if (!dragon->boostpulse_warned) {
    177                 strerror_r(errno, buf, sizeof(buf));
    178                 ALOGE("Error opening %s: %s\n", BOOSTPULSE_PATH, buf);
    179                 dragon->boostpulse_warned = 1;
    180             }
    181         }
    182     }
    183 
    184     pthread_mutex_unlock(&dragon->boost_pulse_lock);
    185     return dragon->boostpulse_fd;
    186 }
    187 
    188 static void dragon_power_hint(struct power_module *module, power_hint_t hint,
    189                                 void *data)
    190 {
    191     struct dragon_power_module *dragon =
    192             (struct dragon_power_module *) module;
    193     char buf[80];
    194     int len;
    195 
    196     switch (hint) {
    197     case POWER_HINT_INTERACTION:
    198         if (boostpulse_open(dragon) >= 0) {
    199             len = write(dragon->boostpulse_fd, "1", 1);
    200 
    201             if (len < 0) {
    202                 strerror_r(errno, buf, sizeof(buf));
    203                 ALOGE("Error writing to %s: %s\n", BOOSTPULSE_PATH, buf);
    204             }
    205         }
    206         if (dragon->gpu_qos_manager != NULL)
    207             dragon->gpu_qos_manager->requestTimedQos(ms2ns(GPU_BOOST_DURATION_MS));
    208 
    209         break;
    210 
    211     case POWER_HINT_VSYNC:
    212         break;
    213 
    214     case POWER_HINT_LOW_POWER:
    215         pthread_mutex_lock(&dragon->low_power_lock);
    216         if (data) {
    217             sysfs_write(CPU_MAX_FREQ_PATH, low_power_max_cpu_freq);
    218             sysfs_write(GPU_CAP_PATH, low_power_gpu_cap);
    219         } else {
    220             sysfs_write(CPU_MAX_FREQ_PATH, max_cpu_freq);
    221             sysfs_write(GPU_CAP_PATH, normal_gpu_cap);
    222         }
    223         low_power_mode = data;
    224         pthread_mutex_unlock(&dragon->low_power_lock);
    225         break;
    226 
    227     default:
    228             break;
    229     }
    230 }
    231 
    232 static int dragon_power_open(const hw_module_t *module, const char *name,
    233                             hw_device_t **device)
    234 {
    235     ALOGD("%s: enter; name=%s", __FUNCTION__, name);
    236     int retval = 0; /* 0 is ok; -1 is error */
    237 
    238     if (strcmp(name, POWER_HARDWARE_MODULE_ID) == 0) {
    239         dragon_power_module *dev = (dragon_power_module *)calloc(1,
    240                 sizeof(dragon_power_module));
    241 
    242         if (dev) {
    243             /* Common hw_device_t fields */
    244             dev->base.common.tag = HARDWARE_MODULE_TAG;
    245             dev->base.common.module_api_version = POWER_MODULE_API_VERSION_0_2;
    246             dev->base.common.hal_api_version = HARDWARE_HAL_API_VERSION;
    247 
    248             dev->base.init = power_init;
    249             dev->base.powerHint = dragon_power_hint;
    250             dev->base.setInteractive = power_set_interactive;
    251             dev->boost_pulse_lock = PTHREAD_MUTEX_INITIALIZER;
    252             dev->low_power_lock = PTHREAD_MUTEX_INITIALIZER;
    253             dev->boostpulse_fd = -1;
    254             dev->boostpulse_warned = 0;
    255             dev->gpu_qos_manager = NULL;
    256 
    257             *device = (hw_device_t*)dev;
    258         } else
    259             retval = -ENOMEM;
    260     } else {
    261         retval = -EINVAL;
    262     }
    263 
    264     ALOGD("%s: exit %d", __FUNCTION__, retval);
    265     return retval;
    266 }
    267 
    268 
    269 static struct hw_module_methods_t power_module_methods = {
    270     .open = dragon_power_open,
    271 };
    272 
    273 struct dragon_power_module HAL_MODULE_INFO_SYM = {
    274     base: {
    275         common: {
    276             tag: HARDWARE_MODULE_TAG,
    277             module_api_version: POWER_MODULE_API_VERSION_0_2,
    278             hal_api_version: HARDWARE_HAL_API_VERSION,
    279             id: POWER_HARDWARE_MODULE_ID,
    280             name: "Dragon Power HAL",
    281             author: "The Android Open Source Project",
    282             methods: &power_module_methods,
    283             dso: NULL,
    284             reserved: {0},
    285         },
    286 
    287         init: power_init,
    288         setInteractive: power_set_interactive,
    289         powerHint: dragon_power_hint,
    290     },
    291 
    292     boost_pulse_lock: PTHREAD_MUTEX_INITIALIZER,
    293     low_power_lock: PTHREAD_MUTEX_INITIALIZER,
    294     boostpulse_fd: -1,
    295     boostpulse_warned: 0,
    296     gpu_qos_manager: NULL,
    297 };
    298 
    299