blob: beb27dac2dd57c9933e4ccfc7dd366f0f02535b0 [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>
Christopher N. Hessee1434192016-11-18 18:56:17 +010026#include <stdlib.h>
Christopher N. Hessede5e3c62015-12-21 21:28:23 +010027#include <string.h>
28#include <unistd.h>
29
30#include <sys/types.h>
31#include <sys/stat.h>
32
33#define LOG_TAG "SamsungPowerHAL"
34/* #define LOG_NDEBUG 0 */
35#include <utils/Log.h>
36
37#include <hardware/hardware.h>
38#include <hardware/power.h>
39
Christopher N. Hesse4139d852016-12-07 12:21:44 +010040#include "samsung_power.h"
Christopher N. Hessede5e3c62015-12-21 21:28:23 +010041
Christopher N. Hesse3360b092016-07-11 15:48:35 +020042#define PANEL_BRIGHTNESS "/sys/class/backlight/panel/brightness"
43
Christopher N. Hessede5e3c62015-12-21 21:28:23 +010044struct samsung_power_module {
45 struct power_module base;
46 pthread_mutex_t lock;
47 int boostpulse_fd;
48 int boostpulse_warned;
49 char cpu0_hispeed_freq[10];
50 char cpu0_max_freq[10];
51 char cpu4_hispeed_freq[10];
52 char cpu4_max_freq[10];
53 char* touchscreen_power_path;
54 char* touchkey_power_path;
55 bool touchkey_blocked;
56};
57
Andreas Schneiderf15d7f42016-02-03 10:43:47 +010058enum power_profile_e {
59 PROFILE_POWER_SAVE = 0,
60 PROFILE_BALANCED,
61 PROFILE_HIGH_PERFORMANCE
62};
63static enum power_profile_e current_power_profile = PROFILE_BALANCED;
Christopher N. Hessede5e3c62015-12-21 21:28:23 +010064
65/**********************************************************
66 *** HELPER FUNCTIONS
67 **********************************************************/
68
69static int sysfs_read(char *path, char *s, int num_bytes)
70{
71 char errno_str[64];
72 int len;
73 int ret = 0;
74 int fd;
75
76 fd = open(path, O_RDONLY);
77 if (fd < 0) {
78 strerror_r(errno, errno_str, sizeof(errno_str));
79 ALOGE("Error opening %s: %s\n", path, errno_str);
80
81 return -1;
82 }
83
84 len = read(fd, s, num_bytes - 1);
85 if (len < 0) {
86 strerror_r(errno, errno_str, sizeof(errno_str));
87 ALOGE("Error reading from %s: %s\n", path, errno_str);
88
89 ret = -1;
90 } else {
91 s[len] = '\0';
92 }
93
94 close(fd);
95
96 return ret;
97}
98
99static void sysfs_write(const char *path, char *s)
100{
101 char errno_str[64];
102 int len;
103 int fd;
104
105 fd = open(path, O_WRONLY);
106 if (fd < 0) {
107 strerror_r(errno, errno_str, sizeof(errno_str));
108 ALOGE("Error opening %s: %s\n", path, errno_str);
109 return;
110 }
111
112 len = write(fd, s, strlen(s));
113 if (len < 0) {
114 strerror_r(errno, errno_str, sizeof(errno_str));
115 ALOGE("Error writing to %s: %s\n", path, errno_str);
116 }
117
118 close(fd);
119}
120
Christopher N. Hessee1434192016-11-18 18:56:17 +0100121static int read_panel_brightness() {
122 int ret = 0;
Christopher N. Hesse3360b092016-07-11 15:48:35 +0200123 int read_status;
124 // brightness can range from 0 to 255, so max. 3 chars + '\0'
125 char panel_brightness[4];
Christopher N. Hessee1434192016-11-18 18:56:17 +0100126 // for strtol
127 char *dummy;
128 const int base = 10;
Christopher N. Hesse3360b092016-07-11 15:48:35 +0200129
130 read_status = sysfs_read(PANEL_BRIGHTNESS, panel_brightness, sizeof(PANEL_BRIGHTNESS));
131 if (read_status < 0) {
132 ALOGE("%s: Failed to read panel brightness from %s!\n", __func__, PANEL_BRIGHTNESS);
133 return -1;
134 }
135
Christopher N. Hessee1434192016-11-18 18:56:17 +0100136 ret = strtol(panel_brightness, &dummy, base);
Christopher N. Hesse3360b092016-07-11 15:48:35 +0200137 ALOGV("%s: Panel brightness is: %d", __func__, ret);
138
139 return ret;
140}
141
Christopher N. Hessede5e3c62015-12-21 21:28:23 +0100142/**********************************************************
143 *** POWER FUNCTIONS
144 **********************************************************/
145
146/* You need to request the powerhal lock before calling this function */
147static int boostpulse_open(struct samsung_power_module *samsung_pwr)
148{
149 char errno_str[64];
150
151 if (samsung_pwr->boostpulse_fd < 0) {
152 samsung_pwr->boostpulse_fd = open(BOOSTPULSE_PATH, O_WRONLY);
153 if (samsung_pwr->boostpulse_fd < 0) {
154 if (!samsung_pwr->boostpulse_warned) {
155 strerror_r(errno, errno_str, sizeof(errno_str));
156 ALOGE("Error opening %s: %s\n", BOOSTPULSE_PATH, errno_str);
157 samsung_pwr->boostpulse_warned = 1;
158 }
159 }
160 }
161
162 return samsung_pwr->boostpulse_fd;
163}
164
Andreas Schneiderf15d7f42016-02-03 10:43:47 +0100165static void set_power_profile(struct samsung_power_module *samsung_pwr,
166 enum power_profile_e profile)
167{
168 int rc;
169 struct stat sb;
170
171 if (current_power_profile == profile) {
172 return;
173 }
174
175 ALOGV("%s: profile=%d", __func__, profile);
176
177 switch (profile) {
178 case PROFILE_POWER_SAVE:
179 // Limit to hispeed freq
180 sysfs_write(CPU0_MAX_FREQ_PATH, samsung_pwr->cpu0_hispeed_freq);
181 rc = stat(CPU4_MAX_FREQ_PATH, &sb);
182 if (rc == 0) {
183 sysfs_write(CPU4_MAX_FREQ_PATH, samsung_pwr->cpu4_hispeed_freq);
184 }
185 ALOGD("%s: set powersave mode", __func__);
186 break;
187 case PROFILE_BALANCED:
188 // Restore normal max freq
189 sysfs_write(CPU0_MAX_FREQ_PATH, samsung_pwr->cpu0_max_freq);
190 rc = stat(CPU4_MAX_FREQ_PATH, &sb);
191 if (rc == 0) {
192 sysfs_write(CPU4_MAX_FREQ_PATH, samsung_pwr->cpu4_max_freq);
193 }
194 ALOGD("%s: set balanced mode", __func__);
195 break;
196 case PROFILE_HIGH_PERFORMANCE:
197 // Restore normal max freq
198 sysfs_write(CPU0_MAX_FREQ_PATH, samsung_pwr->cpu0_max_freq);
199 rc = stat(CPU4_MAX_FREQ_PATH, &sb);
200 if (rc == 0) {
201 sysfs_write(CPU4_MAX_FREQ_PATH, samsung_pwr->cpu4_max_freq);
202 }
203 ALOGD("%s: set performance mode", __func__);
204 break;
205 }
206
207 current_power_profile = profile;
208}
209
Christopher N. Hessede5e3c62015-12-21 21:28:23 +0100210static void find_input_nodes(struct samsung_power_module *samsung_pwr, char *dir)
211{
212 const char filename[] = "name";
213 char errno_str[64];
214 struct dirent *de;
215 char file_content[20];
216 char *path = NULL;
217 char *node_path = NULL;
218 size_t pathsize;
219 size_t node_pathsize;
220 DIR *d;
221
222 d = opendir(dir);
223 if (d == NULL) {
224 return;
225 }
226
227 while ((de = readdir(d)) != NULL) {
228 if (strncmp(filename, de->d_name, sizeof(filename)) == 0) {
229 pathsize = strlen(dir) + strlen(de->d_name) + 2;
230 node_pathsize = strlen(dir) + strlen("enabled") + 2;
231
232 path = malloc(pathsize);
233 node_path = malloc(node_pathsize);
234 if (path == NULL || node_path == NULL) {
235 strerror_r(errno, errno_str, sizeof(errno_str));
236 ALOGE("Out of memory: %s\n", errno_str);
237 return;
238 }
239
240 snprintf(path, pathsize, "%s/%s", dir, filename);
241 sysfs_read(path, file_content, sizeof(file_content));
242
243 snprintf(node_path, node_pathsize, "%s/%s", dir, "enabled");
244
245 if (strncmp(file_content, "sec_touchkey", 12) == 0) {
246 ALOGV("%s: found touchkey path: %s\n", __func__, node_path);
247 samsung_pwr->touchkey_power_path = malloc(node_pathsize);
248 if (samsung_pwr->touchkey_power_path == NULL) {
249 strerror_r(errno, errno_str, sizeof(errno_str));
250 ALOGE("Out of memory: %s\n", errno_str);
251 return;
252 }
Christopher N. Hesse22da3132016-02-01 12:36:54 +0100253 snprintf(samsung_pwr->touchkey_power_path, node_pathsize,
254 "%s", node_path);
Christopher N. Hessede5e3c62015-12-21 21:28:23 +0100255 }
256
257 if (strncmp(file_content, "sec_touchscreen", 15) == 0) {
258 ALOGV("%s: found touchscreen path: %s\n", __func__, node_path);
259 samsung_pwr->touchscreen_power_path = malloc(node_pathsize);
260 if (samsung_pwr->touchscreen_power_path == NULL) {
261 strerror_r(errno, errno_str, sizeof(errno_str));
262 ALOGE("Out of memory: %s\n", errno_str);
263 return;
264 }
Christopher N. Hesse22da3132016-02-01 12:36:54 +0100265 snprintf(samsung_pwr->touchscreen_power_path, node_pathsize,
266 "%s", node_path);
Christopher N. Hessede5e3c62015-12-21 21:28:23 +0100267 }
268 }
269 }
270
271 if (path)
272 free(path);
273 if (node_path)
274 free(node_path);
275 closedir(d);
276}
277
278/**********************************************************
279 *** INIT FUNCTIONS
280 **********************************************************/
281
282static void init_cpufreqs(struct samsung_power_module *samsung_pwr)
283{
284 int rc;
285 struct stat sb;
286
287 sysfs_read(CPU0_HISPEED_FREQ_PATH, samsung_pwr->cpu0_hispeed_freq,
288 sizeof(samsung_pwr->cpu0_hispeed_freq));
289 sysfs_read(CPU0_MAX_FREQ_PATH, samsung_pwr->cpu0_max_freq,
290 sizeof(samsung_pwr->cpu0_max_freq));
291 ALOGV("%s: CPU 0 hispeed freq: %s\n", __func__, samsung_pwr->cpu0_hispeed_freq);
292 ALOGV("%s: CPU 0 max freq: %s\n", __func__, samsung_pwr->cpu0_max_freq);
293
294 rc = stat(CPU4_HISPEED_FREQ_PATH, &sb);
295 if (rc == 0) {
296 sysfs_read(CPU4_HISPEED_FREQ_PATH, samsung_pwr->cpu4_hispeed_freq,
297 sizeof(samsung_pwr->cpu4_hispeed_freq));
298 sysfs_read(CPU4_MAX_FREQ_PATH, samsung_pwr->cpu4_max_freq,
299 sizeof(samsung_pwr->cpu4_max_freq));
300 ALOGV("%s: CPU 4 hispeed freq: %s\n", __func__, samsung_pwr->cpu4_hispeed_freq);
301 ALOGV("%s: CPU 4 max freq: %s\n", __func__, samsung_pwr->cpu4_max_freq);
302 }
303}
304
305static void init_touch_input_power_path(struct samsung_power_module *samsung_pwr)
306{
307 char dir[1024];
308 char errno_str[64];
309 uint32_t i;
310
311 for (i = 0; i < 20; i++) {
312 snprintf(dir, sizeof(dir), "/sys/class/input/input%d", i);
313 find_input_nodes(samsung_pwr, dir);
314 }
315}
316
317/*
318 * The init function performs power management setup actions at runtime
319 * startup, such as to set default cpufreq parameters. This is called only by
320 * the Power HAL instance loaded by PowerManagerService.
321 */
322static void samsung_power_init(struct power_module *module)
323{
324 struct samsung_power_module *samsung_pwr = (struct samsung_power_module *) module;
325
326 init_cpufreqs(samsung_pwr);
327 init_touch_input_power_path(samsung_pwr);
328}
329
330/*
331 * The setInteractive function performs power management actions upon the
332 * system entering interactive state (that is, the system is awake and ready
333 * for interaction, often with UI devices such as display and touchscreen
334 * enabled) or non-interactive state (the system appears asleep, display
335 * usually turned off). The non-interactive state is usually entered after a
336 * period of inactivity, in order to conserve battery power during such
337 * inactive periods.
338 *
339 * Typical actions are to turn on or off devices and adjust cpufreq parameters.
340 * This function may also call the appropriate interfaces to allow the kernel
341 * to suspend the system to low-power sleep state when entering non-interactive
342 * state, and to disallow low-power suspend when the system is in interactive
343 * state. When low-power suspend state is allowed, the kernel may suspend the
344 * system whenever no wakelocks are held.
345 *
346 * on is non-zero when the system is transitioning to an interactive / awake
347 * state, and zero when transitioning to a non-interactive / asleep state.
348 *
349 * This function is called to enter non-interactive state after turning off the
350 * screen (if present), and called to enter interactive state prior to turning
351 * on the screen.
352 */
353static void samsung_power_set_interactive(struct power_module *module, int on)
354{
355 struct samsung_power_module *samsung_pwr = (struct samsung_power_module *) module;
356 struct stat sb;
357 char buf[80];
358 char touchkey_node[2];
Christopher N. Hessede5e3c62015-12-21 21:28:23 +0100359 int rc;
360
361 ALOGV("power_set_interactive: %d\n", on);
362
Christopher N. Hesse3360b092016-07-11 15:48:35 +0200363 // Do not disable any input devices if the screen is on but we are in a non-interactive state
364 if (!on) {
365 if (read_panel_brightness() > 0) {
366 ALOGV("%s: Moving to non-interactive state, but screen is still on,"
367 " not disabling input devices\n", __func__);
368 goto out;
369 }
370 }
371
Christopher N. Hessede5e3c62015-12-21 21:28:23 +0100372 sysfs_write(samsung_pwr->touchscreen_power_path, on ? "1" : "0");
373
374 rc = stat(samsung_pwr->touchkey_power_path, &sb);
375 if (rc < 0) {
376 goto out;
377 }
378
379 if (!on) {
380 if (sysfs_read(samsung_pwr->touchkey_power_path, touchkey_node,
381 sizeof(touchkey_node)) == 0) {
Christopher N. Hessede5e3c62015-12-21 21:28:23 +0100382 /*
Christopher N. Hesse3360b092016-07-11 15:48:35 +0200383 * If touchkey_node is 0, the keys have been disabled by another component
Christopher N. Hessede5e3c62015-12-21 21:28:23 +0100384 * (for example cmhw), which means we don't want them to be enabled when resuming
385 * from suspend.
386 */
Christopher N. Hesse3360b092016-07-11 15:48:35 +0200387 if ((touchkey_node[0] - '0') == 0) {
Christopher N. Hessede5e3c62015-12-21 21:28:23 +0100388 samsung_pwr->touchkey_blocked = true;
389 } else {
390 samsung_pwr->touchkey_blocked = false;
391 sysfs_write(samsung_pwr->touchkey_power_path, "0");
392 }
393 }
394 } else if (!samsung_pwr->touchkey_blocked) {
395 sysfs_write(samsung_pwr->touchkey_power_path, "1");
396 }
397
Christopher N. Hessede5e3c62015-12-21 21:28:23 +0100398out:
Christopher N. Hesse2879c692016-07-11 15:49:49 +0200399 sysfs_write(IO_IS_BUSY_PATH, on ? "1" : "0");
Christopher N. Hessede5e3c62015-12-21 21:28:23 +0100400 ALOGV("power_set_interactive: %d done\n", on);
401}
402
403/*
404 * The powerHint function is called to pass hints on power requirements, which
405 * may result in adjustment of power/performance parameters of the cpufreq
406 * governor and other controls.
407 *
408 * The possible hints are:
409 *
410 * POWER_HINT_VSYNC
411 *
412 * Foreground app has started or stopped requesting a VSYNC pulse
413 * from SurfaceFlinger. If the app has started requesting VSYNC
414 * then CPU and GPU load is expected soon, and it may be appropriate
415 * to raise speeds of CPU, memory bus, etc. The data parameter is
416 * non-zero to indicate VSYNC pulse is now requested, or zero for
417 * VSYNC pulse no longer requested.
418 *
419 * POWER_HINT_INTERACTION
420 *
421 * User is interacting with the device, for example, touchscreen
422 * events are incoming. CPU and GPU load may be expected soon,
423 * and it may be appropriate to raise speeds of CPU, memory bus,
424 * etc. The data parameter is unused.
425 *
426 * POWER_HINT_LOW_POWER
427 *
428 * Low power mode is activated or deactivated. Low power mode
429 * is intended to save battery at the cost of performance. The data
430 * parameter is non-zero when low power mode is activated, and zero
431 * when deactivated.
432 *
433 * POWER_HINT_CPU_BOOST
434 *
435 * An operation is happening where it would be ideal for the CPU to
436 * be boosted for a specific duration. The data parameter is an
437 * integer value of the boost duration in microseconds.
438 */
439static void samsung_power_hint(struct power_module *module,
440 power_hint_t hint,
441 void *data)
442{
443 struct samsung_power_module *samsung_pwr = (struct samsung_power_module *) module;
444 char errno_str[64];
445 int len;
446
447 switch (hint) {
448 case POWER_HINT_INTERACTION: {
Andreas Schneiderf15d7f42016-02-03 10:43:47 +0100449 char errno_str[64];
450 ssize_t len;
451 int fd;
452
453 if (current_power_profile == PROFILE_POWER_SAVE) {
454 return;
455 }
Christopher N. Hessede5e3c62015-12-21 21:28:23 +0100456
457 ALOGV("%s: POWER_HINT_INTERACTION", __func__);
458
459 if (boostpulse_open(samsung_pwr) >= 0) {
460 len = write(samsung_pwr->boostpulse_fd, "1", 1);
461
462 if (len < 0) {
463 strerror_r(errno, errno_str, sizeof(errno_str));
464 ALOGE("Error writing to %s: %s\n", BOOSTPULSE_PATH, errno_str);
465 }
466 }
467
468 break;
469 }
470 case POWER_HINT_VSYNC: {
471
472 ALOGV("%s: POWER_HINT_VSYNC", __func__);
Christopher N. Hessede5e3c62015-12-21 21:28:23 +0100473 break;
474 }
Andreas Schneiderf15d7f42016-02-03 10:43:47 +0100475 case POWER_HINT_SET_PROFILE: {
476 int profile = *((intptr_t *)data);
Christopher N. Hessede5e3c62015-12-21 21:28:23 +0100477
Andreas Schneiderf15d7f42016-02-03 10:43:47 +0100478 ALOGV("%s: POWER_HINT_SET_PROFILE", __func__);
Christopher N. Hessede5e3c62015-12-21 21:28:23 +0100479
Andreas Schneiderf15d7f42016-02-03 10:43:47 +0100480 set_power_profile(samsung_pwr, profile);
Christopher N. Hessede5e3c62015-12-21 21:28:23 +0100481 break;
482 }
483 default:
484 break;
485 }
486}
487
Andreas Schneiderf15d7f42016-02-03 10:43:47 +0100488static int samsung_get_feature(struct power_module *module __unused,
489 feature_t feature)
490{
491 if (feature == POWER_FEATURE_SUPPORTED_PROFILES) {
492 return 3;
493 }
494
495 return -1;
496}
497
Christopher N. Hesse1c474662016-11-18 18:59:06 +0100498static void samsung_set_feature(struct power_module *module, feature_t feature, int state __unused)
Christopher N. Hessee480d892016-06-22 23:04:39 +0200499{
500 struct samsung_power_module *samsung_pwr = (struct samsung_power_module *) module;
501
502 switch (feature) {
ishantvivek987dcca2016-11-21 06:05:47 +0000503#ifdef TARGET_TAP_TO_WAKE_NODE
Christopher N. Hessee480d892016-06-22 23:04:39 +0200504 case POWER_FEATURE_DOUBLE_TAP_TO_WAKE:
505 ALOGV("%s: %s double tap to wake", __func__, state ? "enabling" : "disabling");
ishantvivek987dcca2016-11-21 06:05:47 +0000506 sysfs_write(TARGET_TAP_TO_WAKE_NODE, state > 0 ? "1" : "0");
Christopher N. Hessee480d892016-06-22 23:04:39 +0200507 break;
508#endif
509 default:
510 break;
511 }
512}
513
Christopher N. Hessede5e3c62015-12-21 21:28:23 +0100514static struct hw_module_methods_t power_module_methods = {
515 .open = NULL,
516};
517
518struct samsung_power_module HAL_MODULE_INFO_SYM = {
519 .base = {
520 .common = {
521 .tag = HARDWARE_MODULE_TAG,
522 .module_api_version = POWER_MODULE_API_VERSION_0_2,
523 .hal_api_version = HARDWARE_HAL_API_VERSION,
524 .id = POWER_HARDWARE_MODULE_ID,
525 .name = "Samsung Power HAL",
526 .author = "The CyanogenMod Project",
527 .methods = &power_module_methods,
528 },
529
530 .init = samsung_power_init,
531 .setInteractive = samsung_power_set_interactive,
532 .powerHint = samsung_power_hint,
Christopher N. Hessee480d892016-06-22 23:04:39 +0200533 .getFeature = samsung_get_feature,
534 .setFeature = samsung_set_feature
Christopher N. Hessede5e3c62015-12-21 21:28:23 +0100535 },
536
537 .lock = PTHREAD_MUTEX_INITIALIZER,
538 .boostpulse_fd = -1,
539 .boostpulse_warned = 0,
540};