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