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 
sysfs_write(const char * path,const char * s)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 
power_init(struct power_module __unused * module)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 
power_set_interactive(struct power_module __unused * module,int on)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 
boostpulse_open(struct dragon_power_module * dragon)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 
dragon_power_hint(struct power_module * module,power_hint_t hint,void * data)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 
dragon_power_open(const hw_module_t * module,const char * name,hw_device_t ** device)232 static int dragon_power_open(const hw_module_t *module, const char *name,
233                             hw_device_t **device)
234 {
235     return 0;
236 }
237 
238 
239 static struct hw_module_methods_t power_module_methods = {
240     .open = dragon_power_open,
241 };
242 
243 struct dragon_power_module HAL_MODULE_INFO_SYM = {
244     base: {
245         common: {
246             tag: HARDWARE_MODULE_TAG,
247             module_api_version: POWER_MODULE_API_VERSION_0_2,
248             hal_api_version: HARDWARE_HAL_API_VERSION,
249             id: POWER_HARDWARE_MODULE_ID,
250             name: "Dragon Power HAL",
251             author: "The Android Open Source Project",
252             methods: &power_module_methods,
253             dso: NULL,
254             reserved: {0},
255         },
256 
257         init: power_init,
258         setInteractive: power_set_interactive,
259         powerHint: dragon_power_hint,
260     },
261 
262     boost_pulse_lock: PTHREAD_MUTEX_INITIALIZER,
263     low_power_lock: PTHREAD_MUTEX_INITIALIZER,
264     boostpulse_fd: -1,
265     boostpulse_warned: 0,
266     gpu_qos_manager: NULL,
267 };
268 
269