1/*
2 * Copyright (C) 2008 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
18// #define LOG_NDEBUG 0
19#define LOG_TAG "lights"
20
21#include <cutils/log.h>
22
23#include <stdint.h>
24#include <string.h>
25#include <unistd.h>
26#include <errno.h>
27#include <fcntl.h>
28#include <pthread.h>
29
30#include <sys/ioctl.h>
31#include <sys/types.h>
32
33#include <hardware/lights.h>
34
35/******************************************************************************/
36
37static pthread_once_t g_init = PTHREAD_ONCE_INIT;
38static pthread_mutex_t g_lock = PTHREAD_MUTEX_INITIALIZER;
39static int g_haveTrackballLight = 0;
40static struct light_state_t g_notification;
41static struct light_state_t g_battery;
42static int g_backlight = 255;
43static int g_trackball = -1;
44static int g_buttons = 0;
45static int g_attention = 0;
46static int g_haveAmberLed = 0;
47
48char const*const TRACKBALL_FILE
49        = "/sys/class/leds/jogball-backlight/brightness";
50
51char const*const RED_LED_FILE
52        = "/sys/class/leds/red/brightness";
53
54char const*const GREEN_LED_FILE
55        = "/sys/class/leds/green/brightness";
56
57char const*const BLUE_LED_FILE
58        = "/sys/class/leds/blue/brightness";
59
60char const*const AMBER_LED_FILE
61        = "/sys/class/leds/amber/brightness";
62
63char const*const LCD_FILE
64        = "/sys/class/leds/lcd-backlight/brightness";
65
66char const*const RED_FREQ_FILE
67        = "/sys/class/leds/red/device/grpfreq";
68
69char const*const RED_PWM_FILE
70        = "/sys/class/leds/red/device/grppwm";
71
72char const*const RED_BLINK_FILE
73        = "/sys/class/leds/red/device/blink";
74
75char const*const AMBER_BLINK_FILE
76        = "/sys/class/leds/amber/blink";
77
78char const*const KEYBOARD_FILE
79        = "/sys/class/leds/keyboard-backlight/brightness";
80
81char const*const BUTTON_FILE
82        = "/sys/class/leds/button-backlight/brightness";
83
84/**
85 * device methods
86 */
87
88void init_globals(void)
89{
90    // init the mutex
91    pthread_mutex_init(&g_lock, NULL);
92
93    // figure out if we have the trackball LED or not
94    g_haveTrackballLight = (access(TRACKBALL_FILE, W_OK) == 0) ? 1 : 0;
95
96    /* figure out if we have the amber LED or not.
97       If yes, just support green and amber.         */
98    g_haveAmberLed = (access(AMBER_LED_FILE, W_OK) == 0) ? 1 : 0;
99}
100
101static int
102write_int(char const* path, int value)
103{
104    int fd;
105    static int already_warned = 0;
106
107    fd = open(path, O_RDWR);
108    if (fd >= 0) {
109        char buffer[20];
110        int bytes = sprintf(buffer, "%d\n", value);
111        int amt = write(fd, buffer, bytes);
112        close(fd);
113        return amt == -1 ? -errno : 0;
114    } else {
115        if (already_warned == 0) {
116            ALOGE("write_int failed to open %s\n", path);
117            already_warned = 1;
118        }
119        return -errno;
120    }
121}
122
123static int
124is_lit(struct light_state_t const* state)
125{
126    return state->color & 0x00ffffff;
127}
128
129static int
130handle_trackball_light_locked(struct light_device_t* dev)
131{
132    int mode = g_attention;
133
134    if (mode == 7 && g_backlight) {
135        mode = 0;
136    }
137    ALOGV("%s g_backlight = %d, mode = %d, g_attention = %d\n",
138        __func__, g_backlight, mode, g_attention);
139
140    // If the value isn't changing, don't set it, because this
141    // can reset the timer on the breathing mode, which looks bad.
142    if (g_trackball == mode) {
143        return 0;
144    }
145
146    return write_int(TRACKBALL_FILE, mode);
147}
148
149static int
150rgb_to_brightness(struct light_state_t const* state)
151{
152    int color = state->color & 0x00ffffff;
153    return ((77*((color>>16)&0x00ff))
154            + (150*((color>>8)&0x00ff)) + (29*(color&0x00ff))) >> 8;
155}
156
157static int
158set_light_backlight(struct light_device_t* dev,
159        struct light_state_t const* state)
160{
161    int err = 0;
162    int brightness = rgb_to_brightness(state);
163    pthread_mutex_lock(&g_lock);
164    g_backlight = brightness;
165    err = write_int(LCD_FILE, brightness);
166    if (g_haveTrackballLight) {
167        handle_trackball_light_locked(dev);
168    }
169    pthread_mutex_unlock(&g_lock);
170    return err;
171}
172
173static int
174set_light_keyboard(struct light_device_t* dev,
175        struct light_state_t const* state)
176{
177    int err = 0;
178    int on = is_lit(state);
179    pthread_mutex_lock(&g_lock);
180    err = write_int(KEYBOARD_FILE, on?255:0);
181    pthread_mutex_unlock(&g_lock);
182    return err;
183}
184
185static int
186set_light_buttons(struct light_device_t* dev,
187        struct light_state_t const* state)
188{
189    int err = 0;
190    int on = is_lit(state);
191    pthread_mutex_lock(&g_lock);
192    g_buttons = on;
193    err = write_int(BUTTON_FILE, on?255:0);
194    pthread_mutex_unlock(&g_lock);
195    return err;
196}
197
198static int
199set_speaker_light_locked(struct light_device_t* dev,
200        struct light_state_t const* state)
201{
202    int len;
203    int alpha, red, green, blue;
204    int blink, freq, pwm;
205    int onMS, offMS;
206    unsigned int colorRGB;
207
208    switch (state->flashMode) {
209        case LIGHT_FLASH_TIMED:
210            onMS = state->flashOnMS;
211            offMS = state->flashOffMS;
212            break;
213        case LIGHT_FLASH_NONE:
214        default:
215            onMS = 0;
216            offMS = 0;
217            break;
218    }
219
220    colorRGB = state->color;
221
222#if 0
223    ALOGD("set_speaker_light_locked colorRGB=%08X, onMS=%d, offMS=%d\n",
224            colorRGB, onMS, offMS);
225#endif
226
227    red = (colorRGB >> 16) & 0xFF;
228    green = (colorRGB >> 8) & 0xFF;
229    blue = colorRGB & 0xFF;
230
231    if (!g_haveAmberLed) {
232        write_int(RED_LED_FILE, red);
233        write_int(GREEN_LED_FILE, green);
234        write_int(BLUE_LED_FILE, blue);
235    } else {
236        /* all of related red led is replaced by amber */
237        if (red) {
238            write_int(AMBER_LED_FILE, 1);
239            write_int(GREEN_LED_FILE, 0);
240        } else if (green) {
241            write_int(AMBER_LED_FILE, 0);
242            write_int(GREEN_LED_FILE, 1);
243        } else {
244            write_int(GREEN_LED_FILE, 0);
245            write_int(AMBER_LED_FILE, 0);
246        }
247    }
248
249    if (onMS > 0 && offMS > 0) {
250        int totalMS = onMS + offMS;
251
252        // the LED appears to blink about once per second if freq is 20
253        // 1000ms / 20 = 50
254        freq = totalMS / 50;
255        // pwm specifies the ratio of ON versus OFF
256        // pwm = 0 -> always off
257        // pwm = 255 => always on
258        pwm = (onMS * 255) / totalMS;
259
260        // the low 4 bits are ignored, so round up if necessary
261        if (pwm > 0 && pwm < 16)
262            pwm = 16;
263
264        blink = 1;
265    } else {
266        blink = 0;
267        freq = 0;
268        pwm = 0;
269    }
270
271    if (!g_haveAmberLed) {
272        if (blink) {
273            write_int(RED_FREQ_FILE, freq);
274            write_int(RED_PWM_FILE, pwm);
275        }
276        write_int(RED_BLINK_FILE, blink);
277    } else {
278        write_int(AMBER_BLINK_FILE, blink);
279    }
280
281    return 0;
282}
283
284static void
285handle_speaker_battery_locked(struct light_device_t* dev)
286{
287    if (is_lit(&g_battery)) {
288        set_speaker_light_locked(dev, &g_battery);
289    } else {
290        set_speaker_light_locked(dev, &g_notification);
291    }
292}
293
294static int
295set_light_battery(struct light_device_t* dev,
296        struct light_state_t const* state)
297{
298    pthread_mutex_lock(&g_lock);
299    g_battery = *state;
300    if (g_haveTrackballLight) {
301        set_speaker_light_locked(dev, state);
302    }
303    handle_speaker_battery_locked(dev);
304    pthread_mutex_unlock(&g_lock);
305    return 0;
306}
307
308static int
309set_light_notifications(struct light_device_t* dev,
310        struct light_state_t const* state)
311{
312    pthread_mutex_lock(&g_lock);
313    g_notification = *state;
314    ALOGV("set_light_notifications g_trackball=%d color=0x%08x",
315            g_trackball, state->color);
316    if (g_haveTrackballLight) {
317        handle_trackball_light_locked(dev);
318    }
319    handle_speaker_battery_locked(dev);
320    pthread_mutex_unlock(&g_lock);
321    return 0;
322}
323
324static int
325set_light_attention(struct light_device_t* dev,
326        struct light_state_t const* state)
327{
328    pthread_mutex_lock(&g_lock);
329    ALOGV("set_light_attention g_trackball=%d color=0x%08x",
330            g_trackball, state->color);
331    if (state->flashMode == LIGHT_FLASH_HARDWARE) {
332        g_attention = state->flashOnMS;
333    } else if (state->flashMode == LIGHT_FLASH_NONE) {
334        g_attention = 0;
335    }
336    if (g_haveTrackballLight) {
337        handle_trackball_light_locked(dev);
338    }
339    pthread_mutex_unlock(&g_lock);
340    return 0;
341}
342
343
344/** Close the lights device */
345static int
346close_lights(struct light_device_t *dev)
347{
348    if (dev) {
349        free(dev);
350    }
351    return 0;
352}
353
354
355/******************************************************************************/
356
357/**
358 * module methods
359 */
360
361/** Open a new instance of a lights device using name */
362static int open_lights(const struct hw_module_t* module, char const* name,
363        struct hw_device_t** device)
364{
365    int (*set_light)(struct light_device_t* dev,
366            struct light_state_t const* state);
367
368    if (0 == strcmp(LIGHT_ID_BACKLIGHT, name)) {
369        set_light = set_light_backlight;
370    }
371    else if (0 == strcmp(LIGHT_ID_KEYBOARD, name)) {
372        set_light = set_light_keyboard;
373    }
374    else if (0 == strcmp(LIGHT_ID_BUTTONS, name)) {
375        set_light = set_light_buttons;
376    }
377    else if (0 == strcmp(LIGHT_ID_BATTERY, name)) {
378        set_light = set_light_battery;
379    }
380    else if (0 == strcmp(LIGHT_ID_NOTIFICATIONS, name)) {
381        set_light = set_light_notifications;
382    }
383    else if (0 == strcmp(LIGHT_ID_ATTENTION, name)) {
384        set_light = set_light_attention;
385    }
386    else {
387        return -EINVAL;
388    }
389
390    pthread_once(&g_init, init_globals);
391
392    struct light_device_t *dev = malloc(sizeof(struct light_device_t));
393    memset(dev, 0, sizeof(*dev));
394
395    dev->common.tag = HARDWARE_DEVICE_TAG;
396    dev->common.version = 0;
397    dev->common.module = (struct hw_module_t*)module;
398    dev->common.close = (int (*)(struct hw_device_t*))close_lights;
399    dev->set_light = set_light;
400
401    *device = (struct hw_device_t*)dev;
402    return 0;
403}
404
405
406static struct hw_module_methods_t lights_module_methods = {
407    .open =  open_lights,
408};
409
410/*
411 * The lights Module
412 */
413struct hw_module_t HAL_MODULE_INFO_SYM = {
414    .tag = HARDWARE_MODULE_TAG,
415    .version_major = 1,
416    .version_minor = 0,
417    .id = LIGHTS_HARDWARE_MODULE_ID,
418    .name = "QCT MSM7K lights Module",
419    .author = "Google, Inc.",
420    .methods = &lights_module_methods,
421};
422