blob: f54c72ffc5ade38e627845adae164d297af7a5a4 [file] [log] [blame]
Christopher N. Hessede5e3c62015-12-21 21:28:23 +01001/*
2 * Copyright (C) 2012 The Android Open Source Project
3 * Copyright (C) 2014 The CyanogenMod Project
4 * Copyright (C) 2014-2015 Andreas Schneider <asn@cryptomilk.org>
5 * Copyright (C) 2014-2015 Christopher N. Hesse <raymanfx@gmail.com>
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 */
19
Christopher N. Hesse3360b092016-07-11 15:48:35 +020020#include <ctype.h>
Christopher N. Hessede5e3c62015-12-21 21:28:23 +010021#include <dirent.h>
22#include <errno.h>
23#include <fcntl.h>
24#include <malloc.h>
25#include <stdbool.h>
26#include <string.h>
27#include <unistd.h>
28
29#include <sys/types.h>
30#include <sys/stat.h>
31
32#define LOG_TAG "SamsungPowerHAL"
33/* #define LOG_NDEBUG 0 */
34#include <utils/Log.h>
35
36#include <hardware/hardware.h>
37#include <hardware/power.h>
38
39#define BOOSTPULSE_PATH "/sys/devices/system/cpu/cpu0/cpufreq/interactive/boostpulse"
40
41#define IO_IS_BUSY_PATH "/sys/devices/system/cpu/cpu0/cpufreq/interactive/io_is_busy"
42
43#define CPU0_HISPEED_FREQ_PATH "/sys/devices/system/cpu/cpu0/cpufreq/interactive/hispeed_freq"
44#define CPU0_MAX_FREQ_PATH "/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq"
45#define CPU4_HISPEED_FREQ_PATH "/sys/devices/system/cpu/cpu4/cpufreq/interactive/hispeed_freq"
46#define CPU4_MAX_FREQ_PATH "/sys/devices/system/cpu/cpu4/cpufreq/scaling_max_freq"
47
Christopher N. Hesse3360b092016-07-11 15:48:35 +020048#define PANEL_BRIGHTNESS "/sys/class/backlight/panel/brightness"
49
Christopher N. Hessede5e3c62015-12-21 21:28:23 +010050struct samsung_power_module {
51 struct power_module base;
52 pthread_mutex_t lock;
53 int boostpulse_fd;
54 int boostpulse_warned;
55 char cpu0_hispeed_freq[10];
56 char cpu0_max_freq[10];
57 char cpu4_hispeed_freq[10];
58 char cpu4_max_freq[10];
59 char* touchscreen_power_path;
60 char* touchkey_power_path;
61 bool touchkey_blocked;
62};
63
Andreas Schneiderf15d7f42016-02-03 10:43:47 +010064enum power_profile_e {
65 PROFILE_POWER_SAVE = 0,
66 PROFILE_BALANCED,
67 PROFILE_HIGH_PERFORMANCE
68};
69static enum power_profile_e current_power_profile = PROFILE_BALANCED;
Christopher N. Hessede5e3c62015-12-21 21:28:23 +010070
71/**********************************************************
72 *** HELPER FUNCTIONS
73 **********************************************************/
74
75static int sysfs_read(char *path, char *s, int num_bytes)
76{
77 char errno_str[64];
78 int len;
79 int ret = 0;
80 int fd;
81
82 fd = open(path, O_RDONLY);
83 if (fd < 0) {
84 strerror_r(errno, errno_str, sizeof(errno_str));
85 ALOGE("Error opening %s: %s\n", path, errno_str);
86
87 return -1;
88 }
89
90 len = read(fd, s, num_bytes - 1);
91 if (len < 0) {
92 strerror_r(errno, errno_str, sizeof(errno_str));
93 ALOGE("Error reading from %s: %s\n", path, errno_str);
94
95 ret = -1;
96 } else {
97 s[len] = '\0';
98 }
99
100 close(fd);
101
102 return ret;
103}
104
105static void sysfs_write(const char *path, char *s)
106{
107 char errno_str[64];
108 int len;
109 int fd;
110
111 fd = open(path, O_WRONLY);
112 if (fd < 0) {
113 strerror_r(errno, errno_str, sizeof(errno_str));
114 ALOGE("Error opening %s: %s\n", path, errno_str);
115 return;
116 }
117
118 len = write(fd, s, strlen(s));
119 if (len < 0) {
120 strerror_r(errno, errno_str, sizeof(errno_str));
121 ALOGE("Error writing to %s: %s\n", path, errno_str);
122 }
123
124 close(fd);
125}
126
Christopher N. Hesse3360b092016-07-11 15:48:35 +0200127static unsigned int read_panel_brightness() {
128 unsigned int ret = 0;
129 int read_status;
130 // brightness can range from 0 to 255, so max. 3 chars + '\0'
131 char panel_brightness[4];
132
133 read_status = sysfs_read(PANEL_BRIGHTNESS, panel_brightness, sizeof(PANEL_BRIGHTNESS));
134 if (read_status < 0) {
135 ALOGE("%s: Failed to read panel brightness from %s!\n", __func__, PANEL_BRIGHTNESS);
136 return -1;
137 }
138
139 for (unsigned int i = 0; i < (sizeof(panel_brightness) / sizeof(panel_brightness[0])); i++) {
140 if (isdigit(panel_brightness[i])) {
141 ret += (panel_brightness[i] - '0');
142 }
143 }
144
145 ALOGV("%s: Panel brightness is: %d", __func__, ret);
146
147 return ret;
148}
149
Christopher N. Hessede5e3c62015-12-21 21:28:23 +0100150/**********************************************************
151 *** POWER FUNCTIONS
152 **********************************************************/
153
154/* You need to request the powerhal lock before calling this function */
155static int boostpulse_open(struct samsung_power_module *samsung_pwr)
156{
157 char errno_str[64];
158
159 if (samsung_pwr->boostpulse_fd < 0) {
160 samsung_pwr->boostpulse_fd = open(BOOSTPULSE_PATH, O_WRONLY);
161 if (samsung_pwr->boostpulse_fd < 0) {
162 if (!samsung_pwr->boostpulse_warned) {
163 strerror_r(errno, errno_str, sizeof(errno_str));
164 ALOGE("Error opening %s: %s\n", BOOSTPULSE_PATH, errno_str);
165 samsung_pwr->boostpulse_warned = 1;
166 }
167 }
168 }
169
170 return samsung_pwr->boostpulse_fd;
171}
172
Andreas Schneiderf15d7f42016-02-03 10:43:47 +0100173static void set_power_profile(struct samsung_power_module *samsung_pwr,
174 enum power_profile_e profile)
175{
176 int rc;
177 struct stat sb;
178
179 if (current_power_profile == profile) {
180 return;
181 }
182
183 ALOGV("%s: profile=%d", __func__, profile);
184
185 switch (profile) {
186 case PROFILE_POWER_SAVE:
187 // Limit to hispeed freq
188 sysfs_write(CPU0_MAX_FREQ_PATH, samsung_pwr->cpu0_hispeed_freq);
189 rc = stat(CPU4_MAX_FREQ_PATH, &sb);
190 if (rc == 0) {
191 sysfs_write(CPU4_MAX_FREQ_PATH, samsung_pwr->cpu4_hispeed_freq);
192 }
193 ALOGD("%s: set powersave mode", __func__);
194 break;
195 case PROFILE_BALANCED:
196 // Restore normal max freq
197 sysfs_write(CPU0_MAX_FREQ_PATH, samsung_pwr->cpu0_max_freq);
198 rc = stat(CPU4_MAX_FREQ_PATH, &sb);
199 if (rc == 0) {
200 sysfs_write(CPU4_MAX_FREQ_PATH, samsung_pwr->cpu4_max_freq);
201 }
202 ALOGD("%s: set balanced mode", __func__);
203 break;
204 case PROFILE_HIGH_PERFORMANCE:
205 // Restore normal max freq
206 sysfs_write(CPU0_MAX_FREQ_PATH, samsung_pwr->cpu0_max_freq);
207 rc = stat(CPU4_MAX_FREQ_PATH, &sb);
208 if (rc == 0) {
209 sysfs_write(CPU4_MAX_FREQ_PATH, samsung_pwr->cpu4_max_freq);
210 }
211 ALOGD("%s: set performance mode", __func__);
212 break;
213 }
214
215 current_power_profile = profile;
216}
217
Christopher N. Hessede5e3c62015-12-21 21:28:23 +0100218static void find_input_nodes(struct samsung_power_module *samsung_pwr, char *dir)
219{
220 const char filename[] = "name";
221 char errno_str[64];
222 struct dirent *de;
223 char file_content[20];
224 char *path = NULL;
225 char *node_path = NULL;
226 size_t pathsize;
227 size_t node_pathsize;
228 DIR *d;
229
230 d = opendir(dir);
231 if (d == NULL) {
232 return;
233 }
234
235 while ((de = readdir(d)) != NULL) {
236 if (strncmp(filename, de->d_name, sizeof(filename)) == 0) {
237 pathsize = strlen(dir) + strlen(de->d_name) + 2;
238 node_pathsize = strlen(dir) + strlen("enabled") + 2;
239
240 path = malloc(pathsize);
241 node_path = malloc(node_pathsize);
242 if (path == NULL || node_path == NULL) {
243 strerror_r(errno, errno_str, sizeof(errno_str));
244 ALOGE("Out of memory: %s\n", errno_str);
245 return;
246 }
247
248 snprintf(path, pathsize, "%s/%s", dir, filename);
249 sysfs_read(path, file_content, sizeof(file_content));
250
251 snprintf(node_path, node_pathsize, "%s/%s", dir, "enabled");
252
253 if (strncmp(file_content, "sec_touchkey", 12) == 0) {
254 ALOGV("%s: found touchkey path: %s\n", __func__, node_path);
255 samsung_pwr->touchkey_power_path = malloc(node_pathsize);
256 if (samsung_pwr->touchkey_power_path == NULL) {
257 strerror_r(errno, errno_str, sizeof(errno_str));
258 ALOGE("Out of memory: %s\n", errno_str);
259 return;
260 }
Christopher N. Hesse22da3132016-02-01 12:36:54 +0100261 snprintf(samsung_pwr->touchkey_power_path, node_pathsize,
262 "%s", node_path);
Christopher N. Hessede5e3c62015-12-21 21:28:23 +0100263 }
264
265 if (strncmp(file_content, "sec_touchscreen", 15) == 0) {
266 ALOGV("%s: found touchscreen path: %s\n", __func__, node_path);
267 samsung_pwr->touchscreen_power_path = malloc(node_pathsize);
268 if (samsung_pwr->touchscreen_power_path == NULL) {
269 strerror_r(errno, errno_str, sizeof(errno_str));
270 ALOGE("Out of memory: %s\n", errno_str);
271 return;
272 }
Christopher N. Hesse22da3132016-02-01 12:36:54 +0100273 snprintf(samsung_pwr->touchscreen_power_path, node_pathsize,
274 "%s", node_path);
Christopher N. Hessede5e3c62015-12-21 21:28:23 +0100275 }
276 }
277 }
278
279 if (path)
280 free(path);
281 if (node_path)
282 free(node_path);
283 closedir(d);
284}
285
286/**********************************************************
287 *** INIT FUNCTIONS
288 **********************************************************/
289
290static void init_cpufreqs(struct samsung_power_module *samsung_pwr)
291{
292 int rc;
293 struct stat sb;
294
295 sysfs_read(CPU0_HISPEED_FREQ_PATH, samsung_pwr->cpu0_hispeed_freq,
296 sizeof(samsung_pwr->cpu0_hispeed_freq));
297 sysfs_read(CPU0_MAX_FREQ_PATH, samsung_pwr->cpu0_max_freq,
298 sizeof(samsung_pwr->cpu0_max_freq));
299 ALOGV("%s: CPU 0 hispeed freq: %s\n", __func__, samsung_pwr->cpu0_hispeed_freq);
300 ALOGV("%s: CPU 0 max freq: %s\n", __func__, samsung_pwr->cpu0_max_freq);
301
302 rc = stat(CPU4_HISPEED_FREQ_PATH, &sb);
303 if (rc == 0) {
304 sysfs_read(CPU4_HISPEED_FREQ_PATH, samsung_pwr->cpu4_hispeed_freq,
305 sizeof(samsung_pwr->cpu4_hispeed_freq));
306 sysfs_read(CPU4_MAX_FREQ_PATH, samsung_pwr->cpu4_max_freq,
307 sizeof(samsung_pwr->cpu4_max_freq));
308 ALOGV("%s: CPU 4 hispeed freq: %s\n", __func__, samsung_pwr->cpu4_hispeed_freq);
309 ALOGV("%s: CPU 4 max freq: %s\n", __func__, samsung_pwr->cpu4_max_freq);
310 }
311}
312
313static void init_touch_input_power_path(struct samsung_power_module *samsung_pwr)
314{
315 char dir[1024];
316 char errno_str[64];
317 uint32_t i;
318
319 for (i = 0; i < 20; i++) {
320 snprintf(dir, sizeof(dir), "/sys/class/input/input%d", i);
321 find_input_nodes(samsung_pwr, dir);
322 }
323}
324
325/*
326 * The init function performs power management setup actions at runtime
327 * startup, such as to set default cpufreq parameters. This is called only by
328 * the Power HAL instance loaded by PowerManagerService.
329 */
330static void samsung_power_init(struct power_module *module)
331{
332 struct samsung_power_module *samsung_pwr = (struct samsung_power_module *) module;
333
334 init_cpufreqs(samsung_pwr);
335 init_touch_input_power_path(samsung_pwr);
336}
337
338/*
339 * The setInteractive function performs power management actions upon the
340 * system entering interactive state (that is, the system is awake and ready
341 * for interaction, often with UI devices such as display and touchscreen
342 * enabled) or non-interactive state (the system appears asleep, display
343 * usually turned off). The non-interactive state is usually entered after a
344 * period of inactivity, in order to conserve battery power during such
345 * inactive periods.
346 *
347 * Typical actions are to turn on or off devices and adjust cpufreq parameters.
348 * This function may also call the appropriate interfaces to allow the kernel
349 * to suspend the system to low-power sleep state when entering non-interactive
350 * state, and to disallow low-power suspend when the system is in interactive
351 * state. When low-power suspend state is allowed, the kernel may suspend the
352 * system whenever no wakelocks are held.
353 *
354 * on is non-zero when the system is transitioning to an interactive / awake
355 * state, and zero when transitioning to a non-interactive / asleep state.
356 *
357 * This function is called to enter non-interactive state after turning off the
358 * screen (if present), and called to enter interactive state prior to turning
359 * on the screen.
360 */
361static void samsung_power_set_interactive(struct power_module *module, int on)
362{
363 struct samsung_power_module *samsung_pwr = (struct samsung_power_module *) module;
364 struct stat sb;
365 char buf[80];
366 char touchkey_node[2];
Christopher N. Hessede5e3c62015-12-21 21:28:23 +0100367 int rc;
368
369 ALOGV("power_set_interactive: %d\n", on);
370
Christopher N. Hesse3360b092016-07-11 15:48:35 +0200371 // Do not disable any input devices if the screen is on but we are in a non-interactive state
372 if (!on) {
373 if (read_panel_brightness() > 0) {
374 ALOGV("%s: Moving to non-interactive state, but screen is still on,"
375 " not disabling input devices\n", __func__);
376 goto out;
377 }
378 }
379
Christopher N. Hessede5e3c62015-12-21 21:28:23 +0100380 sysfs_write(samsung_pwr->touchscreen_power_path, on ? "1" : "0");
381
382 rc = stat(samsung_pwr->touchkey_power_path, &sb);
383 if (rc < 0) {
384 goto out;
385 }
386
387 if (!on) {
388 if (sysfs_read(samsung_pwr->touchkey_power_path, touchkey_node,
389 sizeof(touchkey_node)) == 0) {
Christopher N. Hessede5e3c62015-12-21 21:28:23 +0100390 /*
Christopher N. Hesse3360b092016-07-11 15:48:35 +0200391 * If touchkey_node is 0, the keys have been disabled by another component
Christopher N. Hessede5e3c62015-12-21 21:28:23 +0100392 * (for example cmhw), which means we don't want them to be enabled when resuming
393 * from suspend.
394 */
Christopher N. Hesse3360b092016-07-11 15:48:35 +0200395 if ((touchkey_node[0] - '0') == 0) {
Christopher N. Hessede5e3c62015-12-21 21:28:23 +0100396 samsung_pwr->touchkey_blocked = true;
397 } else {
398 samsung_pwr->touchkey_blocked = false;
399 sysfs_write(samsung_pwr->touchkey_power_path, "0");
400 }
401 }
402 } else if (!samsung_pwr->touchkey_blocked) {
403 sysfs_write(samsung_pwr->touchkey_power_path, "1");
404 }
405
Christopher N. Hessede5e3c62015-12-21 21:28:23 +0100406out:
Christopher N. Hesse2879c692016-07-11 15:49:49 +0200407 sysfs_write(IO_IS_BUSY_PATH, on ? "1" : "0");
Christopher N. Hessede5e3c62015-12-21 21:28:23 +0100408 ALOGV("power_set_interactive: %d done\n", on);
409}
410
411/*
412 * The powerHint function is called to pass hints on power requirements, which
413 * may result in adjustment of power/performance parameters of the cpufreq
414 * governor and other controls.
415 *
416 * The possible hints are:
417 *
418 * POWER_HINT_VSYNC
419 *
420 * Foreground app has started or stopped requesting a VSYNC pulse
421 * from SurfaceFlinger. If the app has started requesting VSYNC
422 * then CPU and GPU load is expected soon, and it may be appropriate
423 * to raise speeds of CPU, memory bus, etc. The data parameter is
424 * non-zero to indicate VSYNC pulse is now requested, or zero for
425 * VSYNC pulse no longer requested.
426 *
427 * POWER_HINT_INTERACTION
428 *
429 * User is interacting with the device, for example, touchscreen
430 * events are incoming. CPU and GPU load may be expected soon,
431 * and it may be appropriate to raise speeds of CPU, memory bus,
432 * etc. The data parameter is unused.
433 *
434 * POWER_HINT_LOW_POWER
435 *
436 * Low power mode is activated or deactivated. Low power mode
437 * is intended to save battery at the cost of performance. The data
438 * parameter is non-zero when low power mode is activated, and zero
439 * when deactivated.
440 *
441 * POWER_HINT_CPU_BOOST
442 *
443 * An operation is happening where it would be ideal for the CPU to
444 * be boosted for a specific duration. The data parameter is an
445 * integer value of the boost duration in microseconds.
446 */
447static void samsung_power_hint(struct power_module *module,
448 power_hint_t hint,
449 void *data)
450{
451 struct samsung_power_module *samsung_pwr = (struct samsung_power_module *) module;
452 char errno_str[64];
453 int len;
454
455 switch (hint) {
456 case POWER_HINT_INTERACTION: {
Andreas Schneiderf15d7f42016-02-03 10:43:47 +0100457 char errno_str[64];
458 ssize_t len;
459 int fd;
460
461 if (current_power_profile == PROFILE_POWER_SAVE) {
462 return;
463 }
Christopher N. Hessede5e3c62015-12-21 21:28:23 +0100464
465 ALOGV("%s: POWER_HINT_INTERACTION", __func__);
466
467 if (boostpulse_open(samsung_pwr) >= 0) {
468 len = write(samsung_pwr->boostpulse_fd, "1", 1);
469
470 if (len < 0) {
471 strerror_r(errno, errno_str, sizeof(errno_str));
472 ALOGE("Error writing to %s: %s\n", BOOSTPULSE_PATH, errno_str);
473 }
474 }
475
476 break;
477 }
478 case POWER_HINT_VSYNC: {
479
480 ALOGV("%s: POWER_HINT_VSYNC", __func__);
Christopher N. Hessede5e3c62015-12-21 21:28:23 +0100481 break;
482 }
Andreas Schneiderf15d7f42016-02-03 10:43:47 +0100483 case POWER_HINT_SET_PROFILE: {
484 int profile = *((intptr_t *)data);
Christopher N. Hessede5e3c62015-12-21 21:28:23 +0100485
Andreas Schneiderf15d7f42016-02-03 10:43:47 +0100486 ALOGV("%s: POWER_HINT_SET_PROFILE", __func__);
Christopher N. Hessede5e3c62015-12-21 21:28:23 +0100487
Andreas Schneiderf15d7f42016-02-03 10:43:47 +0100488 set_power_profile(samsung_pwr, profile);
Christopher N. Hessede5e3c62015-12-21 21:28:23 +0100489 break;
490 }
491 default:
492 break;
493 }
494}
495
Andreas Schneiderf15d7f42016-02-03 10:43:47 +0100496static int samsung_get_feature(struct power_module *module __unused,
497 feature_t feature)
498{
499 if (feature == POWER_FEATURE_SUPPORTED_PROFILES) {
500 return 3;
501 }
502
503 return -1;
504}
505
Christopher N. Hessede5e3c62015-12-21 21:28:23 +0100506static struct hw_module_methods_t power_module_methods = {
507 .open = NULL,
508};
509
510struct samsung_power_module HAL_MODULE_INFO_SYM = {
511 .base = {
512 .common = {
513 .tag = HARDWARE_MODULE_TAG,
514 .module_api_version = POWER_MODULE_API_VERSION_0_2,
515 .hal_api_version = HARDWARE_HAL_API_VERSION,
516 .id = POWER_HARDWARE_MODULE_ID,
517 .name = "Samsung Power HAL",
518 .author = "The CyanogenMod Project",
519 .methods = &power_module_methods,
520 },
521
522 .init = samsung_power_init,
523 .setInteractive = samsung_power_set_interactive,
524 .powerHint = samsung_power_hint,
Andreas Schneiderf15d7f42016-02-03 10:43:47 +0100525 .getFeature = samsung_get_feature
Christopher N. Hessede5e3c62015-12-21 21:28:23 +0100526 },
527
528 .lock = PTHREAD_MUTEX_INITIALIZER,
529 .boostpulse_fd = -1,
530 .boostpulse_warned = 0,
531};