1/*
2 * Copyright (C) 2012 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 <linux/time.h>
26#include <stdbool.h>
27//#define LOG_NDEBUG 0
28
29#define LOG_TAG "MantaPowerHAL"
30#include <utils/Log.h>
31
32#include <hardware/hardware.h>
33#include <hardware/power.h>
34
35#define BOOSTPULSE_PATH "/sys/devices/system/cpu/cpufreq/interactive/boostpulse"
36#define BOOST_PATH "/sys/devices/system/cpu/cpufreq/interactive/boost"
37#define CPU_MAX_FREQ_PATH "/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq"
38//BOOST_PULSE_DURATION and BOOT_PULSE_DURATION_STR should always be in sync
39#define BOOST_PULSE_DURATION 1000000
40#define BOOST_PULSE_DURATION_STR "1000000"
41#define NSEC_PER_SEC 1000000000
42#define USEC_PER_SEC 1000000
43#define NSEC_PER_USEC 100
44#define LOW_POWER_MAX_FREQ "800000"
45#define NORMAL_MAX_FREQ "1700000"
46
47struct manta_power_module {
48    struct power_module base;
49    pthread_mutex_t lock;
50    int boostpulse_fd;
51    int boostpulse_warned;
52    const char *touchscreen_power_path;
53};
54
55static unsigned int vsync_count;
56static struct timespec last_touch_boost;
57static bool touch_boost;
58static bool low_power_mode = false;
59
60static void sysfs_write(const char *path, char *s)
61{
62    char buf[80];
63    int len;
64    int fd = open(path, O_WRONLY);
65
66    if (fd < 0) {
67        strerror_r(errno, buf, sizeof(buf));
68        ALOGE("Error opening %s: %s\n", path, buf);
69        return;
70    }
71
72    len = write(fd, s, strlen(s));
73    if (len < 0) {
74        strerror_r(errno, buf, sizeof(buf));
75        ALOGE("Error writing to %s: %s\n", path, buf);
76    }
77
78    close(fd);
79}
80
81static void init_touchscreen_power_path(struct manta_power_module *manta)
82{
83    char buf[80];
84    const char dir[] = "/sys/devices/platform/s3c2440-i2c.3/i2c-3/3-004a/input";
85    const char filename[] = "enabled";
86    DIR *d;
87    struct dirent *de;
88    char *path;
89    int pathsize;
90
91    d = opendir(dir);
92    if (d == NULL) {
93        strerror_r(errno, buf, sizeof(buf));
94        ALOGE("Error opening directory %s: %s\n", dir, buf);
95        return;
96    }
97    while ((de = readdir(d)) != NULL) {
98        if (strncmp("input", de->d_name, 5) == 0) {
99            pathsize = strlen(dir) + strlen(de->d_name) + sizeof(filename) + 2;
100            path = malloc(pathsize);
101            if (path == NULL) {
102                strerror_r(errno, buf, sizeof(buf));
103                ALOGE("Out of memory: %s\n", buf);
104                return;
105            }
106            snprintf(path, pathsize, "%s/%s/%s", dir, de->d_name, filename);
107            manta->touchscreen_power_path = path;
108            goto done;
109        }
110    }
111    ALOGE("Error failed to find input dir in %s\n", dir);
112done:
113    closedir(d);
114}
115
116static void power_init(struct power_module *module)
117{
118    struct manta_power_module *manta = (struct manta_power_module *) module;
119    struct dirent **namelist;
120    int n;
121
122    sysfs_write("/sys/devices/system/cpu/cpufreq/interactive/timer_rate",
123                "20000");
124    sysfs_write("/sys/devices/system/cpu/cpufreq/interactive/timer_slack",
125                "20000");
126    sysfs_write("/sys/devices/system/cpu/cpufreq/interactive/min_sample_time",
127                "40000");
128    sysfs_write("/sys/devices/system/cpu/cpufreq/interactive/hispeed_freq",
129                "1000000");
130    sysfs_write("/sys/devices/system/cpu/cpufreq/interactive/go_hispeed_load",
131                "99");
132    sysfs_write("/sys/devices/system/cpu/cpufreq/interactive/target_loads", "70 1200000:70 1300000:75 1400000:80 1500000:99");
133    sysfs_write("/sys/devices/system/cpu/cpufreq/interactive/above_hispeed_delay",
134                "80000");
135    sysfs_write("/sys/devices/system/cpu/cpufreq/interactive/boostpulse_duration",
136                BOOST_PULSE_DURATION_STR);
137    sysfs_write("/sys/devices/system/cpu/cpufreq/interactive/io_is_busy", "1");
138
139    init_touchscreen_power_path(manta);
140}
141
142static void power_set_interactive(struct power_module *module, int on)
143{
144    struct manta_power_module *manta = (struct manta_power_module *) module;
145    char buf[80];
146    int ret;
147
148    ALOGV("power_set_interactive: %d\n", on);
149
150    /*
151     * Lower maximum frequency when screen is off.  CPU 0 and 1 share a
152     * cpufreq policy.
153     */
154    sysfs_write(CPU_MAX_FREQ_PATH,
155                (!on || low_power_mode) ? LOW_POWER_MAX_FREQ : NORMAL_MAX_FREQ);
156
157    sysfs_write(manta->touchscreen_power_path, on ? "Y" : "N");
158
159    ALOGV("power_set_interactive: %d done\n", on);
160}
161
162static int boostpulse_open(struct manta_power_module *manta)
163{
164    char buf[80];
165
166    pthread_mutex_lock(&manta->lock);
167
168    if (manta->boostpulse_fd < 0) {
169        manta->boostpulse_fd = open(BOOSTPULSE_PATH, O_WRONLY);
170
171        if (manta->boostpulse_fd < 0) {
172            if (!manta->boostpulse_warned) {
173                strerror_r(errno, buf, sizeof(buf));
174                ALOGE("Error opening %s: %s\n", BOOSTPULSE_PATH, buf);
175                manta->boostpulse_warned = 1;
176            }
177        }
178    }
179
180    pthread_mutex_unlock(&manta->lock);
181    return manta->boostpulse_fd;
182}
183
184static struct timespec timespec_diff(struct timespec lhs, struct timespec rhs)
185{
186    struct timespec result;
187    if (rhs.tv_nsec > lhs.tv_nsec) {
188        result.tv_sec = lhs.tv_sec - rhs.tv_sec - 1;
189        result.tv_nsec = NSEC_PER_SEC + lhs.tv_nsec - rhs.tv_nsec;
190    } else {
191        result.tv_sec = lhs.tv_sec - rhs.tv_sec;
192        result.tv_nsec = lhs.tv_nsec - rhs.tv_nsec;
193    }
194    return result;
195}
196
197static int check_boostpulse_on(struct timespec diff)
198{
199    long boost_ns = (BOOST_PULSE_DURATION * NSEC_PER_USEC) % NSEC_PER_SEC;
200    long boost_s = BOOST_PULSE_DURATION / USEC_PER_SEC;
201
202    if (diff.tv_sec == boost_s)
203        return (diff.tv_nsec < boost_ns);
204    return (diff.tv_sec < boost_s);
205}
206
207static void manta_power_hint(struct power_module *module, power_hint_t hint,
208                             void *data)
209{
210    struct manta_power_module *manta = (struct manta_power_module *) module;
211    struct timespec now, diff;
212    char buf[80];
213    int len;
214
215    switch (hint) {
216     case POWER_HINT_INTERACTION:
217        if (boostpulse_open(manta) >= 0) {
218            pthread_mutex_lock(&manta->lock);
219            len = write(manta->boostpulse_fd, "1", 1);
220
221            if (len < 0) {
222                strerror_r(errno, buf, sizeof(buf));
223                ALOGE("Error writing to %s: %s\n", BOOSTPULSE_PATH, buf);
224            } else {
225                clock_gettime(CLOCK_MONOTONIC, &last_touch_boost);
226                touch_boost = true;
227            }
228            pthread_mutex_unlock(&manta->lock);
229        }
230
231        break;
232
233     case POWER_HINT_VSYNC:
234        pthread_mutex_lock(&manta->lock);
235        if (data) {
236            if (vsync_count < UINT_MAX)
237                vsync_count++;
238        } else {
239            if (vsync_count)
240                vsync_count--;
241            if (vsync_count == 0 && touch_boost) {
242                touch_boost = false;
243                clock_gettime(CLOCK_MONOTONIC, &now);
244                diff = timespec_diff(now, last_touch_boost);
245                if (check_boostpulse_on(diff)) {
246                    sysfs_write(BOOST_PATH, "0");
247                }
248            }
249        }
250        pthread_mutex_unlock(&manta->lock);
251        break;
252
253    case POWER_HINT_LOW_POWER:
254        pthread_mutex_lock(&manta->lock);
255        if (data)
256            sysfs_write(CPU_MAX_FREQ_PATH, LOW_POWER_MAX_FREQ);
257        else
258            sysfs_write(CPU_MAX_FREQ_PATH, NORMAL_MAX_FREQ);
259        low_power_mode = data;
260        pthread_mutex_unlock(&manta->lock);
261        break;
262    default:
263            break;
264    }
265}
266
267static struct hw_module_methods_t power_module_methods = {
268    .open = NULL,
269};
270
271struct manta_power_module HAL_MODULE_INFO_SYM = {
272    .base = {
273        .common = {
274            .tag = HARDWARE_MODULE_TAG,
275            .module_api_version = POWER_MODULE_API_VERSION_0_2,
276            .hal_api_version = HARDWARE_HAL_API_VERSION,
277            .id = POWER_HARDWARE_MODULE_ID,
278            .name = "Manta Power HAL",
279            .author = "The Android Open Source Project",
280            .methods = &power_module_methods,
281        },
282
283        .init = power_init,
284        .setInteractive = power_set_interactive,
285        .powerHint = manta_power_hint,
286    },
287
288    .lock = PTHREAD_MUTEX_INITIALIZER,
289    .boostpulse_fd = -1,
290    .boostpulse_warned = 0,
291};
292
293