| /* |
| * Copyright (C) 2009 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #define LOG_TAG "lights" |
| |
| #include <cutils/log.h> |
| |
| #include <stdint.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <pthread.h> |
| |
| #include <sys/ioctl.h> |
| #include <sys/types.h> |
| |
| #include <hardware/lights.h> |
| |
| #define LIGHT_ATTENTION 1 |
| #define LIGHT_NOTIFY 2 |
| |
| /******************************************************************************/ |
| static struct light_state_t *g_notify; |
| static struct light_state_t *g_attention; |
| static pthread_once_t g_init = PTHREAD_ONCE_INIT; |
| static pthread_mutex_t g_lock = PTHREAD_MUTEX_INITIALIZER; |
| static int g_backlight = 255; |
| static int g_buttons = 0; |
| struct led_prop { |
| const char *filename; |
| int fd; |
| }; |
| |
| struct led { |
| struct led_prop mode; |
| struct led_prop brightness; |
| struct led_prop blink; |
| struct led_prop color; |
| struct led_prop period; |
| }; |
| |
| enum { |
| JOGBALL_LED, |
| BUTTONS_LED, |
| AMBER_LED, |
| GREEN_LED, |
| BLUE_LED, |
| RED_LED, |
| LCD_BACKLIGHT, |
| NUM_LEDS, |
| }; |
| |
| struct led leds[NUM_LEDS] = { |
| [JOGBALL_LED] = { |
| .brightness = { "/sys/class/leds/jogball-backlight/brightness", 0}, |
| .color = { "/sys/class/leds/jogball-backlight/color", 0}, |
| .period = { "/sys/class/leds/jogball-backlight/period", 0}, |
| }, |
| [BUTTONS_LED] = { |
| .brightness = { "/sys/class/leds/button-backlight/brightness", 0}, |
| }, |
| [RED_LED] = { |
| .brightness = { "/sys/class/leds/red/brightness", 0}, |
| .blink = { "/sys/class/leds/red/blink", 0}, |
| }, |
| [GREEN_LED] = { |
| .brightness = { "/sys/class/leds/green/brightness", 0}, |
| .blink = { "/sys/class/leds/green/blink", 0}, |
| }, |
| [BLUE_LED] = { |
| .brightness = { "/sys/class/leds/blue/brightness", 0}, |
| .blink = { "/sys/class/leds/blue/blink", 0}, |
| }, |
| [AMBER_LED] = { |
| .brightness = { "/sys/class/leds/amber/brightness", 0}, |
| .blink = { "/sys/class/leds/amber/blink", 0}, |
| }, |
| [LCD_BACKLIGHT] = { |
| .brightness = { "/sys/class/leds/lcd-backlight/brightness", 0}, |
| }, |
| }; |
| |
| enum { |
| RGB_BLACK = 0x000000, |
| RGB_RED = 0xFF0000, |
| RGB_AMBER = 0xFFFF00, /* note this is actually RGB yellow */ |
| RGB_GREEN = 0x00FF00, |
| RGB_BLUE = 0x0000FF, |
| RGB_WHITE = 0xFFFFFF, |
| RGB_PINK = 0xFFC0CB, |
| RGB_ORANGE = 0xFFA500, |
| RGB_YELLOW = 0xFFFF00, |
| RGB_PURPLE = 0x800080, |
| RGB_LT_BLUE = 0xADD8E6, |
| }; |
| |
| /** |
| * device methods |
| */ |
| |
| static int init_prop(struct led_prop *prop) |
| { |
| int fd; |
| |
| prop->fd = -1; |
| if (!prop->filename) |
| return 0; |
| fd = open(prop->filename, O_RDWR); |
| if (fd < 0) { |
| LOGE("init_prop: %s cannot be opened (%s)\n", prop->filename, |
| strerror(errno)); |
| return -errno; |
| } |
| |
| prop->fd = fd; |
| return 0; |
| } |
| |
| static void close_prop(struct led_prop *prop) |
| { |
| int fd; |
| |
| if (prop->fd > 0) |
| close(prop->fd); |
| return; |
| } |
| |
| void init_globals(void) |
| { |
| int i; |
| pthread_mutex_init(&g_lock, NULL); |
| |
| for (i = 0; i < NUM_LEDS; ++i) { |
| init_prop(&leds[i].brightness); |
| init_prop(&leds[i].blink); |
| init_prop(&leds[i].mode); |
| init_prop(&leds[i].color); |
| init_prop(&leds[i].period); |
| } |
| g_attention = malloc(sizeof(struct light_state_t)); |
| memset(g_attention, 0, sizeof(*g_attention)); |
| g_notify = malloc(sizeof(struct light_state_t)); |
| memset(g_notify, 0, sizeof(*g_notify)); |
| } |
| |
| static int |
| write_int(struct led_prop *prop, int value) |
| { |
| char buffer[20]; |
| int bytes; |
| int amt; |
| |
| if (prop->fd < 0) |
| return 0; |
| |
| LOGV("%s %s: 0x%x\n", __func__, prop->filename, value); |
| |
| bytes = snprintf(buffer, sizeof(buffer), "%d\n", value); |
| while (bytes > 0) { |
| amt = write(prop->fd, buffer, bytes); |
| if (amt < 0) { |
| if (errno == EINTR) |
| continue; |
| return -errno; |
| } |
| bytes -= amt; |
| } |
| |
| return 0; |
| } |
| |
| static int |
| write_rgb(struct led_prop *prop, int red, int green, int blue) |
| { |
| char buffer[20]; |
| int bytes; |
| int amt; |
| |
| if (prop->fd < 0) |
| return 0; |
| |
| LOGV("%s %s: red:%d green:%d blue:%d\n", |
| __func__, prop->filename, red, green, blue); |
| |
| bytes = snprintf(buffer, sizeof(buffer), "%d %d %d\n", red, green, blue); |
| while (bytes > 0) { |
| amt = write(prop->fd, buffer, bytes); |
| if (amt < 0) { |
| if (errno == EINTR) |
| continue; |
| return -errno; |
| } |
| bytes -= amt; |
| } |
| |
| return 0; |
| } |
| |
| static unsigned int |
| set_rgb(int red, int green, int blue) |
| { |
| return(((red << 16) & 0x00ff0000) | |
| ((green << 8) & 0x0000ff00) | |
| (blue & 0x000000ff)); |
| } |
| |
| static int |
| is_lit(struct light_state_t const* state) |
| { |
| return state->color & 0x00ffffff; |
| } |
| |
| static int |
| set_trackball_light(struct light_state_t const* state) |
| { |
| static int trackball_mode = 0; |
| int rc = 0; |
| int mode = state->flashMode; |
| int red, blue, green; |
| int period = 0; |
| |
| if (state->flashMode == LIGHT_FLASH_HARDWARE) { |
| mode = state->flashOnMS; |
| period = state->flashOffMS; |
| } |
| LOGV("%s color=%08x mode=%d period %d\n", __func__, |
| state->color, mode, period); |
| |
| |
| if (mode != 0) { |
| red = (state->color >> 16) & 0xff; |
| green = (state->color >> 8) & 0xff; |
| blue = state->color & 0xff; |
| |
| rc = write_rgb(&leds[JOGBALL_LED].color, red, green, blue); |
| if (rc != 0) |
| LOGE("set color failed rc = %d\n", rc); |
| if (period) { |
| rc = write_int(&leds[JOGBALL_LED].period, period); |
| if (rc != 0) |
| LOGE("set period failed rc = %d\n", rc); |
| } |
| } |
| // If the value isn't changing, don't set it, because this |
| // can reset the timer on the breathing mode, which looks bad. |
| if (trackball_mode == mode) { |
| return 0; |
| } |
| trackball_mode = mode; |
| |
| return write_int(&leds[JOGBALL_LED].brightness, mode); |
| } |
| |
| static void |
| handle_trackball_light_locked(int type) |
| { |
| struct light_state_t *new_state = 0; |
| int attn_mode = 0; |
| |
| if (g_attention->flashMode == LIGHT_FLASH_HARDWARE) |
| attn_mode = g_attention->flashOnMS; |
| |
| LOGV("%s type %d attention %p notify %p\n", |
| __func__, type, g_attention, g_notify); |
| |
| switch (type) { |
| case LIGHT_ATTENTION: { |
| if (attn_mode == 0) { |
| /* go back to notify state */ |
| new_state = g_notify; |
| } else { |
| new_state = g_attention; |
| } |
| break; |
| } |
| case LIGHT_NOTIFY: { |
| if (attn_mode != 0) { |
| /* attention takes priority over notify state */ |
| new_state = g_attention; |
| } else { |
| new_state = g_notify; |
| } |
| break; |
| } |
| } |
| if (new_state == 0) { |
| LOGE("%s: unknown type (%d)\n", __func__, type); |
| return; |
| } |
| LOGV("%s new state %p\n", __func__, new_state); |
| set_trackball_light(new_state); |
| return; |
| } |
| |
| static int |
| rgb_to_brightness(struct light_state_t const* state) |
| { |
| int color = state->color & 0x00ffffff; |
| return ((77*((color>>16)&0x00ff)) |
| + (150*((color>>8)&0x00ff)) + (29*(color&0x00ff))) >> 8; |
| } |
| |
| static int |
| set_light_backlight(struct light_device_t* dev, |
| struct light_state_t const* state) |
| { |
| int err = 0; |
| int brightness = rgb_to_brightness(state); |
| LOGV("%s brightness=%d color=0x%08x", |
| __func__,brightness, state->color); |
| pthread_mutex_lock(&g_lock); |
| g_backlight = brightness; |
| err = write_int(&leds[LCD_BACKLIGHT].brightness, brightness); |
| pthread_mutex_unlock(&g_lock); |
| return err; |
| } |
| |
| static int |
| set_light_keyboard(struct light_device_t* dev, |
| struct light_state_t const* state) |
| { |
| /* nothing to do on mahimahi*/ |
| return 0; |
| } |
| |
| static int |
| set_light_buttons(struct light_device_t* dev, |
| struct light_state_t const* state) |
| { |
| int err = 0; |
| int on = is_lit(state); |
| pthread_mutex_lock(&g_lock); |
| g_buttons = on; |
| err = write_int(&leds[BUTTONS_LED].brightness, on?255:0); |
| pthread_mutex_unlock(&g_lock); |
| return err; |
| } |
| |
| static int |
| set_speaker_light_locked(struct light_device_t* dev, |
| struct light_state_t const* state) |
| { |
| int len; |
| unsigned int colorRGB; |
| |
| /* Red = amber_led, blue or green = green_led */ |
| colorRGB = state->color & 0xFFFFFF; |
| |
| switch (state->flashMode) { |
| case LIGHT_FLASH_TIMED: |
| LOGV("set_led_state colorRGB=%08X, flashing\n", colorRGB); |
| switch (colorRGB) { |
| case RGB_RED: |
| write_int(&leds[RED_LED].blink, 1); |
| break; |
| case RGB_AMBER: |
| write_int(&leds[AMBER_LED].blink, 2); |
| break; |
| case RGB_GREEN: |
| write_int(&leds[GREEN_LED].blink, 1); |
| break; |
| case RGB_BLUE: |
| write_int(&leds[BLUE_LED].blink, 1); |
| break; |
| case RGB_BLACK: /*off*/ |
| write_int(&leds[GREEN_LED].blink, 0); |
| write_int(&leds[RED_LED].blink, 0); |
| write_int(&leds[BLUE_LED].blink, 0); |
| write_int(&leds[AMBER_LED].blink, 0); |
| break; |
| break; |
| default: |
| LOGE("set_led_state colorRGB=%08X, unknown color\n", |
| colorRGB); |
| break; |
| } |
| break; |
| case LIGHT_FLASH_NONE: |
| LOGV("set_led_state colorRGB=%08X, on\n", colorRGB); |
| switch (colorRGB) { |
| case RGB_RED: |
| /*no support for red solid */ |
| case RGB_AMBER: |
| write_int(&leds[AMBER_LED].brightness, 1); |
| write_int(&leds[GREEN_LED].brightness, 0); |
| write_int(&leds[BLUE_LED].brightness, 0); |
| break; |
| case RGB_GREEN: |
| write_int(&leds[GREEN_LED].brightness, 1); |
| write_int(&leds[AMBER_LED].brightness, 0); |
| write_int(&leds[BLUE_LED].brightness, 0); |
| break; |
| case RGB_BLUE: |
| write_int(&leds[BLUE_LED].brightness, 1); |
| write_int(&leds[GREEN_LED].brightness, 0); |
| write_int(&leds[AMBER_LED].brightness, 0); |
| break; |
| case RGB_BLACK: /*off*/ |
| write_int(&leds[GREEN_LED].brightness, 0); |
| write_int(&leds[AMBER_LED].brightness, 0); |
| write_int(&leds[BLUE_LED].brightness, 0); |
| write_int(&leds[RED_LED].brightness, 0); |
| break; |
| default: |
| LOGE("set_led_state colorRGB=%08X, unknown color\n", |
| colorRGB); |
| break; |
| } |
| break; |
| default: |
| LOGE("set_led_state colorRGB=%08X, unknown mode %d\n", |
| colorRGB, state->flashMode); |
| } |
| return 0; |
| } |
| |
| static int |
| set_light_battery(struct light_device_t* dev, |
| struct light_state_t const* state) |
| { |
| pthread_mutex_lock(&g_lock); |
| LOGV("%s mode=%d color=0x%08x", |
| __func__,state->flashMode, state->color); |
| set_speaker_light_locked(dev, state); |
| pthread_mutex_unlock(&g_lock); |
| return 0; |
| } |
| |
| static int |
| set_light_notifications(struct light_device_t* dev, |
| struct light_state_t const* state) |
| { |
| pthread_mutex_lock(&g_lock); |
| |
| LOGV("%s mode=%d color=0x%08x On=%d Off=%d\n", |
| __func__,state->flashMode, state->color, |
| state->flashOnMS, state->flashOffMS); |
| /* |
| ** TODO Allow for user settings of color and interval |
| ** Setting 60% brightness |
| */ |
| switch (state->color & 0x00FFFFFF) { |
| case RGB_BLACK: |
| g_notify->color = set_rgb(0, 0, 0); |
| break; |
| case RGB_WHITE: |
| g_notify->color = set_rgb(50, 127, 48); |
| break; |
| case RGB_RED: |
| g_notify->color = set_rgb(141, 0, 0); |
| break; |
| case RGB_GREEN: |
| g_notify->color = set_rgb(0, 141, 0); |
| break; |
| case RGB_BLUE: |
| g_notify->color = set_rgb(0, 0, 141); |
| break; |
| case RGB_PINK: |
| g_notify->color = set_rgb(141, 52, 58); |
| break; |
| case RGB_PURPLE: |
| g_notify->color = set_rgb(70, 0, 70); |
| break; |
| case RGB_ORANGE: |
| g_notify->color = set_rgb(141, 99, 0); |
| break; |
| case RGB_YELLOW: |
| g_notify->color = set_rgb(100, 141, 0); |
| break; |
| case RGB_LT_BLUE: |
| g_notify->color = set_rgb(35, 55, 98); |
| break; |
| default: |
| g_notify->color = state->color; |
| break; |
| } |
| |
| if (state->flashMode != LIGHT_FLASH_NONE) { |
| g_notify->flashMode = LIGHT_FLASH_HARDWARE; |
| g_notify->flashOnMS = 7; |
| g_notify->flashOffMS = (state->flashOnMS + state->flashOffMS)/1000; |
| } else { |
| g_notify->flashOnMS = 0; |
| g_notify->flashOffMS = 0; |
| } |
| handle_trackball_light_locked(LIGHT_NOTIFY); |
| |
| pthread_mutex_unlock(&g_lock); |
| return 0; |
| } |
| |
| static int |
| set_light_attention(struct light_device_t* dev, |
| struct light_state_t const* state) |
| { |
| unsigned int colorRGB; |
| |
| LOGV("%s color=0x%08x mode=0x%08x submode=0x%08x", |
| __func__, state->color, state->flashMode, state->flashOnMS); |
| |
| pthread_mutex_lock(&g_lock); |
| /* tune color for hardware*/ |
| switch (state->color & 0x00FFFFFF) { |
| case RGB_WHITE: |
| colorRGB = set_rgb(101, 255, 96); |
| break; |
| case RGB_BLUE: |
| colorRGB = set_rgb(0, 0, 235); |
| break; |
| case RGB_BLACK: |
| colorRGB = set_rgb(0, 0, 0); |
| break; |
| default: |
| LOGE("%s colorRGB=%08X, unknown color\n", |
| __func__, state->color); |
| colorRGB = set_rgb(101, 255, 96); |
| break; |
| } |
| g_attention->flashMode = state->flashMode; |
| g_attention->flashOnMS = state->flashOnMS; |
| g_attention->color = colorRGB; |
| g_attention->flashOffMS = 0; |
| handle_trackball_light_locked(LIGHT_ATTENTION); |
| |
| pthread_mutex_unlock(&g_lock); |
| return 0; |
| } |
| |
| |
| /** Close the lights device */ |
| static int |
| close_lights(struct light_device_t *dev) |
| { |
| int i; |
| |
| for (i = 0; i < NUM_LEDS; ++i) { |
| close_prop(&leds[i].brightness); |
| close_prop(&leds[i].blink); |
| close_prop(&leds[i].mode); |
| } |
| |
| if (dev) { |
| free(dev); |
| } |
| return 0; |
| } |
| |
| |
| /******************************************************************************/ |
| |
| /** |
| * module methods |
| */ |
| |
| /** Open a new instance of a lights device using name */ |
| static int open_lights(const struct hw_module_t* module, char const* name, |
| struct hw_device_t** device) |
| { |
| int (*set_light)(struct light_device_t* dev, |
| struct light_state_t const* state); |
| |
| if (0 == strcmp(LIGHT_ID_BACKLIGHT, name)) { |
| set_light = set_light_backlight; |
| } |
| else if (0 == strcmp(LIGHT_ID_KEYBOARD, name)) { |
| set_light = set_light_keyboard; |
| } |
| else if (0 == strcmp(LIGHT_ID_BUTTONS, name)) { |
| set_light = set_light_buttons; |
| } |
| else if (0 == strcmp(LIGHT_ID_BATTERY, name)) { |
| set_light = set_light_battery; |
| } |
| else if (0 == strcmp(LIGHT_ID_NOTIFICATIONS, name)) { |
| set_light = set_light_notifications; |
| } |
| else if (0 == strcmp(LIGHT_ID_ATTENTION, name)) { |
| set_light = set_light_attention; |
| } |
| else { |
| return -EINVAL; |
| } |
| |
| pthread_once(&g_init, init_globals); |
| |
| struct light_device_t *dev = malloc(sizeof(struct light_device_t)); |
| memset(dev, 0, sizeof(*dev)); |
| |
| dev->common.tag = HARDWARE_DEVICE_TAG; |
| dev->common.version = 0; |
| dev->common.module = (struct hw_module_t*)module; |
| dev->common.close = (int (*)(struct hw_device_t*))close_lights; |
| dev->set_light = set_light; |
| |
| *device = (struct hw_device_t*)dev; |
| return 0; |
| } |
| |
| |
| static struct hw_module_methods_t lights_module_methods = { |
| .open = open_lights, |
| }; |
| |
| /* |
| * The lights Module |
| */ |
| const struct hw_module_t HAL_MODULE_INFO_SYM = { |
| .tag = HARDWARE_MODULE_TAG, |
| .version_major = 1, |
| .version_minor = 0, |
| .id = LIGHTS_HARDWARE_MODULE_ID, |
| .name = "mahimahi lights Module", |
| .author = "Google, Inc.", |
| .methods = &lights_module_methods, |
| }; |