1/*
2 * Copyright (C) 2013 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 * Based on the FlounderPowerHAL
17 */
18
19#include <dirent.h>
20#include <errno.h>
21#include <string.h>
22#include <sys/types.h>
23#include <sys/stat.h>
24#include <fcntl.h>
25#include <unistd.h>
26#include <stdlib.h>
27#include <stdbool.h>
28#include <pthread.h>
29#include <semaphore.h>
30#include <cutils/properties.h>
31//#define LOG_NDEBUG 0
32
33#define LOG_TAG "HiKeyPowerHAL"
34#include <utils/Log.h>
35
36#include <hardware/hardware.h>
37#include <hardware/power.h>
38
39#define SCHEDTUNE_BOOST_PATH "/dev/stune/top-app/schedtune.boost"
40#define SCHEDTUNE_BOOST_NORM "10"
41#define SCHEDTUNE_BOOST_INTERACTIVE "40"
42#define SCHEDTUNE_BOOST_TIME_NS 1000000000LL
43#define INTERACTIVE_BOOSTPULSE_PATH "/sys/devices/system/cpu/cpufreq/interactive/boostpulse"
44#define INTERACTIVE_IO_IS_BUSY_PATH "/sys/devices/system/cpu/cpufreq/interactive/io_is_busy"
45#define CPU_MAX_FREQ_PATH "/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq"
46#define LOW_POWER_MAX_FREQ "729000"
47#define NORMAL_MAX_FREQ "1200000"
48#define SVELTE_PROP "ro.boot.svelte"
49#define SVELTE_MAX_FREQ_PROP "ro.config.svelte.max_cpu_freq"
50#define SVELTE_LOW_POWER_MAX_FREQ_PROP "ro.config.svelte.low_power_max_cpu_freq"
51
52struct hikey_power_module {
53    struct power_module base;
54    pthread_mutex_t lock;
55    /* interactive gov boost values */
56    int boostpulse_fd;
57    int boostpulse_warned;
58    /* EAS schedtune values */
59    int schedtune_boost_fd;
60    long long deboost_time;
61    sem_t signal_lock;
62};
63
64static bool low_power_mode = false;
65
66static char *max_cpu_freq = NORMAL_MAX_FREQ;
67static char *low_power_max_cpu_freq = LOW_POWER_MAX_FREQ;
68
69
70#define container_of(addr, struct_name, field_name) \
71    ((struct_name *)((char *)(addr) - offsetof(struct_name, field_name)))
72
73
74static int sysfs_write(const char *path, char *s)
75{
76    char buf[80];
77    int len;
78    int fd = open(path, O_WRONLY);
79
80    if (fd < 0) {
81        strerror_r(errno, buf, sizeof(buf));
82        ALOGE("Error opening %s: %s\n", path, buf);
83        return fd;
84    }
85
86    len = write(fd, s, strlen(s));
87    if (len < 0) {
88        strerror_r(errno, buf, sizeof(buf));
89        ALOGE("Error writing to %s: %s\n", path, buf);
90    }
91
92    close(fd);
93    return len;
94}
95
96#define NSEC_PER_SEC 1000000000LL
97static long long gettime_ns(void)
98{
99    struct timespec ts;
100
101    clock_gettime(CLOCK_MONOTONIC, &ts);
102    return ts.tv_sec * NSEC_PER_SEC + ts.tv_nsec;
103}
104
105static void nanosleep_ns(long long ns)
106{
107    struct timespec ts;
108    ts.tv_sec = ns/NSEC_PER_SEC;
109    ts.tv_nsec = ns%NSEC_PER_SEC;
110    nanosleep(&ts, NULL);
111}
112
113/*[interactive cpufreq gov funcs]*********************************************/
114static void interactive_power_init(struct hikey_power_module __unused *hikey)
115{
116    int32_t is_svelte = property_get_int32(SVELTE_PROP, 0);
117
118    if (sysfs_write("/sys/devices/system/cpu/cpufreq/interactive/timer_rate",
119                "20000") < 0)
120        return;
121    sysfs_write("/sys/devices/system/cpu/cpufreq/interactive/timer_slack",
122                "20000");
123    sysfs_write("/sys/devices/system/cpu/cpufreq/interactive/min_sample_time",
124                "80000");
125    sysfs_write("/sys/devices/system/cpu/cpufreq/interactive/hispeed_freq",
126                "1200000");
127    sysfs_write("/sys/devices/system/cpu/cpufreq/interactive/go_hispeed_load",
128                "99");
129    sysfs_write("/sys/devices/system/cpu/cpufreq/interactive/target_loads",
130                "65 729000:75 960000:85");
131    sysfs_write("/sys/devices/system/cpu/cpufreq/interactive/above_hispeed_delay",
132                "20000");
133    sysfs_write("/sys/devices/system/cpu/cpufreq/interactive/boostpulse_duration",
134                "1000000");
135    sysfs_write("/sys/devices/system/cpu/cpufreq/interactive/io_is_busy", "0");
136
137    if (is_svelte) {
138        char prop_buffer[PROPERTY_VALUE_MAX];
139        int len = property_get(SVELTE_MAX_FREQ_PROP, prop_buffer,
140                               LOW_POWER_MAX_FREQ);
141
142        max_cpu_freq = strndup(prop_buffer, len);
143        len = property_get(SVELTE_LOW_POWER_MAX_FREQ_PROP, prop_buffer,
144                           LOW_POWER_MAX_FREQ);
145        low_power_max_cpu_freq = strndup(prop_buffer, len);
146    }
147}
148
149static void power_set_interactive(struct power_module __unused *module, int on)
150{
151    ALOGV("power_set_interactive: %d\n", on);
152
153    /*
154     * Lower maximum frequency when screen is off.
155     */
156    sysfs_write(CPU_MAX_FREQ_PATH,
157                (!on || low_power_mode) ? low_power_max_cpu_freq : max_cpu_freq);
158    sysfs_write(INTERACTIVE_IO_IS_BUSY_PATH, on ? "1" : "0");
159    ALOGV("power_set_interactive: %d done\n", on);
160}
161
162static int interactive_boostpulse(struct hikey_power_module *hikey)
163{
164    char buf[80];
165    int len;
166
167   if (hikey->boostpulse_fd < 0)
168        hikey->boostpulse_fd = open(INTERACTIVE_BOOSTPULSE_PATH, O_WRONLY);
169
170    if (hikey->boostpulse_fd < 0) {
171        if (!hikey->boostpulse_warned) {
172            strerror_r(errno, buf, sizeof(buf));
173            ALOGE("Error opening %s: %s\n", INTERACTIVE_BOOSTPULSE_PATH,
174                      buf);
175            hikey->boostpulse_warned = 1;
176        }
177        return hikey->boostpulse_fd;
178    }
179
180    len = write(hikey->boostpulse_fd, "1", 1);
181    if (len < 0) {
182        strerror_r(errno, buf, sizeof(buf));
183        ALOGE("Error writing to %s: %s\n",
184                                 INTERACTIVE_BOOSTPULSE_PATH, buf);
185        return -1;
186    }
187    return 0;
188}
189
190/*[schedtune functions]*******************************************************/
191
192int schedtune_sysfs_boost(struct hikey_power_module *hikey, char* booststr)
193{
194    char buf[80];
195    int len;
196
197    if (hikey->schedtune_boost_fd < 0)
198        return hikey->schedtune_boost_fd;
199
200    len = write(hikey->schedtune_boost_fd, booststr, 2);
201    if (len < 0) {
202        strerror_r(errno, buf, sizeof(buf));
203        ALOGE("Error writing to %s: %s\n", SCHEDTUNE_BOOST_PATH, buf);
204    }
205    return len;
206}
207
208static void* schedtune_deboost_thread(void* arg)
209{
210    struct hikey_power_module *hikey = (struct hikey_power_module *)arg;
211
212    while(1) {
213        sem_wait(&hikey->signal_lock);
214        while(1) {
215            long long now, sleeptime = 0;
216
217            pthread_mutex_lock(&hikey->lock);
218            now = gettime_ns();
219            if (hikey->deboost_time > now) {
220                sleeptime = hikey->deboost_time - now;
221                pthread_mutex_unlock(&hikey->lock);
222                nanosleep_ns(sleeptime);
223                continue;
224            }
225
226            schedtune_sysfs_boost(hikey, SCHEDTUNE_BOOST_NORM);
227            hikey->deboost_time = 0;
228            pthread_mutex_unlock(&hikey->lock);
229            break;
230        }
231    }
232    return NULL;
233}
234
235static int schedtune_boost(struct hikey_power_module *hikey)
236{
237    long long now;
238
239    if (hikey->schedtune_boost_fd < 0)
240        return hikey->schedtune_boost_fd;
241
242    now = gettime_ns();
243    if (!hikey->deboost_time) {
244        schedtune_sysfs_boost(hikey, SCHEDTUNE_BOOST_INTERACTIVE);
245        sem_post(&hikey->signal_lock);
246    }
247    hikey->deboost_time = now + SCHEDTUNE_BOOST_TIME_NS;
248
249    return 0;
250}
251
252static void schedtune_power_init(struct hikey_power_module *hikey)
253{
254    char buf[50];
255    pthread_t tid;
256
257
258    hikey->deboost_time = 0;
259    sem_init(&hikey->signal_lock, 0, 1);
260
261    hikey->schedtune_boost_fd = open(SCHEDTUNE_BOOST_PATH, O_WRONLY);
262    if (hikey->schedtune_boost_fd < 0) {
263        strerror_r(errno, buf, sizeof(buf));
264        ALOGE("Error opening %s: %s\n", SCHEDTUNE_BOOST_PATH, buf);
265    }
266
267    pthread_create(&tid, NULL, schedtune_deboost_thread, hikey);
268}
269
270/*[generic functions]*********************************************************/
271static void hikey_power_init(struct power_module __unused *module)
272{
273    struct hikey_power_module *hikey = container_of(module,
274                                              struct hikey_power_module, base);
275    interactive_power_init(hikey);
276    schedtune_power_init(hikey);
277}
278
279static void hikey_hint_interaction(struct hikey_power_module *mod)
280{
281    /* Try interactive cpufreq boosting first */
282    if(!interactive_boostpulse(mod))
283        return;
284    /* Then try EAS schedtune boosting */
285    if(!schedtune_boost(mod))
286        return;
287}
288
289static void hikey_power_hint(struct power_module *module, power_hint_t hint,
290                                void *data)
291{
292    struct hikey_power_module *hikey = container_of(module,
293                                              struct hikey_power_module, base);
294
295    pthread_mutex_lock(&hikey->lock);
296    switch (hint) {
297     case POWER_HINT_INTERACTION:
298        hikey_hint_interaction(hikey);
299        break;
300
301   case POWER_HINT_VSYNC:
302        break;
303
304    case POWER_HINT_LOW_POWER:
305        if (data) {
306            sysfs_write(CPU_MAX_FREQ_PATH, low_power_max_cpu_freq);
307        } else {
308            sysfs_write(CPU_MAX_FREQ_PATH, max_cpu_freq);
309        }
310        low_power_mode = data;
311        break;
312
313    default:
314            break;
315    }
316    pthread_mutex_unlock(&hikey->lock);
317}
318
319static void set_feature(struct power_module *module, feature_t feature, int state)
320{
321    struct hikey_power_module *hikey = container_of(module,
322                                              struct hikey_power_module, base);
323    switch (feature) {
324    default:
325        ALOGW("Error setting the feature %d and state %d, it doesn't exist\n",
326              feature, state);
327        break;
328    }
329}
330
331static int power_open(const hw_module_t* __unused module, const char* name,
332                    hw_device_t** device)
333{
334    int retval = 0; /* 0 is ok; -1 is error */
335    ALOGD("%s: enter; name=%s", __FUNCTION__, name);
336
337    if (strcmp(name, POWER_HARDWARE_MODULE_ID) == 0) {
338        power_module_t *dev = (power_module_t *)calloc(1,
339                sizeof(power_module_t));
340
341        if (dev) {
342            /* Common hw_device_t fields */
343            dev->common.tag = HARDWARE_DEVICE_TAG;
344            dev->common.module_api_version = POWER_MODULE_API_VERSION_0_5;
345            dev->common.hal_api_version = HARDWARE_HAL_API_VERSION;
346
347            dev->init = hikey_power_init;
348            dev->powerHint = hikey_power_hint;
349            dev->setInteractive = power_set_interactive;
350            dev->setFeature = set_feature;
351
352            *device = (hw_device_t*)dev;
353        } else
354            retval = -ENOMEM;
355    } else {
356        retval = -EINVAL;
357    }
358
359    ALOGD("%s: exit %d", __FUNCTION__, retval);
360    return retval;
361}
362
363static struct hw_module_methods_t power_module_methods = {
364    .open = power_open,
365};
366
367struct hikey_power_module HAL_MODULE_INFO_SYM = {
368    .base = {
369        .common = {
370            .tag = HARDWARE_MODULE_TAG,
371            .module_api_version = POWER_MODULE_API_VERSION_0_2,
372            .hal_api_version = HARDWARE_HAL_API_VERSION,
373            .id = POWER_HARDWARE_MODULE_ID,
374            .name = "HiKey Power HAL",
375            .author = "The Android Open Source Project",
376            .methods = &power_module_methods,
377        },
378
379        .init = hikey_power_init,
380        .setInteractive = power_set_interactive,
381        .powerHint = hikey_power_hint,
382        .setFeature = set_feature,
383    },
384
385    .lock = PTHREAD_MUTEX_INITIALIZER,
386    .boostpulse_fd = -1,
387    .boostpulse_warned = 0,
388};
389