/* * Copyright (C) 2012 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 <errno.h> #include <fcntl.h> #include <limits.h> #include <pthread.h> #include <sched.h> #include <stdbool.h> #include <stdint.h> #include <string.h> #include <sys/types.h> #include <time.h> #include <cutils/log.h> #include <hardware/lights.h> #define LED_SLOPE_UP_DEFAULT 450 #define LED_SLOPE_DOWN_DEFAULT 450 #define LED_BRIGHTNESS_OFF 0 #define LED_BRIGHTNESS_MAX 255 #define ALPHA_MASK 0xff000000 #define COLOR_MASK 0x00ffffff #define NSEC_PER_MSEC 1000000ULL #define NSEC_PER_SEC 1000000000ULL static pthread_mutex_t g_lock = PTHREAD_MUTEX_INITIALIZER; char const *const LCD_FILE = "/sys/class/backlight/pwm-backlight.0/brightness"; const char *const LED_DIR = "/sys/class/leds/as3668"; const char *const LED_COLOR_FILE = "color"; const char *const LED_BRIGHTNESS_FILE = "brightness"; const char *const LED_DELAY_ON_FILE = "delay_on"; const char *const LED_DELAY_OFF_FILE = "delay_off"; const char *const LED_TRIGGER_FILE = "trigger"; const char *const LED_SLOPE_UP_FILE = "slope_up"; const char *const LED_SLOPE_DOWN_FILE = "slope_down"; enum LED_STATE { OFF, ON, BLINK, }; struct as3668_led_info { unsigned int color; unsigned int delay_on; unsigned int delay_off; unsigned int slope_up; unsigned int slope_down; enum LED_STATE state; }; static int write_int(char const *path, int value) { int fd; static int already_warned; ALOGV("write_int: path %s, value %d", path, value); fd = open(path, O_RDWR); if (fd >= 0) { char buffer[20]; int bytes = sprintf(buffer, "%d\n", value); int amt = write(fd, buffer, bytes); close(fd); return amt == -1 ? -errno : 0; } else { if (already_warned == 0) { ALOGE("write_int failed to open %s\n", path); already_warned = 1; } return -errno; } } static int rgb_to_brightness(struct light_state_t const *state) { /* use max of the RGB components for brightness */ int color = state->color & 0x00ffffff; int red = (color >> 16) & 0x000000ff; int green = (color >> 8) & 0x000000ff; int blue = color & 0x000000ff; int brightness = red; if (green > brightness) brightness = green; if (blue > brightness) brightness = blue; return brightness; } 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); pthread_mutex_lock(&g_lock); err = write_int(LCD_FILE, brightness); pthread_mutex_unlock(&g_lock); return err; } static int close_lights(struct hw_device_t *dev) { ALOGV("close_light is called"); free(dev); return 0; } /* For LEDs */ static void set_led_colors(unsigned int color, struct as3668_led_info *leds) { unsigned int red; unsigned int green; unsigned int blue; unsigned int white; red = (color >> 16) & 0x000000ff; green = (color >> 8) & 0x000000ff; blue = color & 0x000000ff; white = red; if (green < white) white = green; if (blue < white) white = blue; color -= (white << 16) | (white << 8) | white; leds->color = (color << 8) | white; } static void time_add(struct timespec *time, int sec, int nsec) { time->tv_nsec += nsec; time->tv_sec += time->tv_nsec / NSEC_PER_SEC; time->tv_nsec %= NSEC_PER_SEC; time->tv_sec += sec; } static bool time_after(struct timespec *t) { struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); return now.tv_sec > t->tv_sec || (now.tv_sec == t->tv_sec && now.tv_nsec > t->tv_nsec); } static int led_sysfs_write(char *buf, const char *command, char *format, ...) { int fd; char path_name[PATH_MAX]; int err; int len; va_list args; struct timespec timeout; int ret; err = sprintf(path_name, "%s/%s", LED_DIR, command); if (err < 0) return err; clock_gettime(CLOCK_MONOTONIC, &timeout); time_add(&timeout, 0, 100 * NSEC_PER_MSEC); do { fd = open(path_name, O_WRONLY); err = -errno; if (fd < 0) { if (errno != EINTR && errno != EACCES && time_after(&timeout)) { ALOGE("failed to open %s!", path_name); return err; } sched_yield(); } } while (fd < 0); va_start(args, format); len = vsprintf(buf, format, args); va_end(args); if (len < 0) return len; err = write(fd, buf, len); if (err == -1) return -errno; err = close(fd); if (err == -1) return -errno; return 0; } static int write_leds(struct as3668_led_info *leds) { char buf[20]; int err; pthread_mutex_lock(&g_lock); err = led_sysfs_write(buf, LED_SLOPE_UP_FILE, "%u", leds->slope_up); if (err) goto err_write_fail; err = led_sysfs_write(buf, LED_SLOPE_DOWN_FILE, "%u", leds->slope_down); if (err) goto err_write_fail; switch(leds->state) { case OFF: err = led_sysfs_write(buf, LED_BRIGHTNESS_FILE, "%d", LED_BRIGHTNESS_OFF); break; case BLINK: err = led_sysfs_write(buf, LED_TRIGGER_FILE, "%s", "timer"); if (err) goto err_write_fail; err = led_sysfs_write(buf, LED_DELAY_ON_FILE, "%u", leds->delay_on); if (err) goto err_write_fail; err = led_sysfs_write(buf, LED_DELAY_OFF_FILE, "%u", leds->delay_off); if (err) goto err_write_fail; case ON: err = led_sysfs_write(buf, LED_COLOR_FILE, "%x", leds->color); if (err) goto err_write_fail; err = led_sysfs_write(buf, LED_BRIGHTNESS_FILE, "%d", LED_BRIGHTNESS_MAX); if (err) goto err_write_fail; default: break; } err_write_fail: pthread_mutex_unlock(&g_lock); return err; } static int set_light_leds(struct light_state_t const *state, int type) { struct as3668_led_info leds; unsigned int color; memset(&leds, 0, sizeof(leds)); leds.slope_up = LED_SLOPE_UP_DEFAULT; leds.slope_down = LED_SLOPE_DOWN_DEFAULT; switch (state->flashMode) { case LIGHT_FLASH_NONE: leds.state = OFF; break; case LIGHT_FLASH_TIMED: case LIGHT_FLASH_HARDWARE: if (state->flashOnMS < 0 || state->flashOffMS < 0) return -EINVAL; leds.delay_off = state->flashOffMS; leds.delay_on = state->flashOnMS; if (leds.delay_on <= leds.slope_up + leds.slope_down) leds.delay_on = 1; else leds.delay_on -= leds.slope_up + leds.slope_down; if (!(state->color & ALPHA_MASK)) { leds.state = OFF; break; } color = state->color & COLOR_MASK; if (color == 0) { leds.state = OFF; break; } set_led_colors(color, &leds); if (leds.delay_on == 0) leds.state = OFF; else if (leds.delay_off) leds.state = BLINK; else leds.state = ON; break; default: return -EINVAL; } return write_leds(&leds); } static int set_light_leds_notifications(struct light_device_t *dev, struct light_state_t const *state) { return set_light_leds(state, 0); } static int set_light_leds_attention(struct light_device_t *dev, struct light_state_t const *state) { return set_light_leds(state, 1); } 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 (strcmp(LIGHT_ID_BACKLIGHT, name) == 0) set_light = set_light_backlight; else if (strcmp(LIGHT_ID_NOTIFICATIONS, name) == 0) set_light = set_light_leds_notifications; else if (strcmp(LIGHT_ID_ATTENTION, name) == 0) set_light = set_light_leds_attention; else return -EINVAL; struct light_device_t *dev = malloc(sizeof(struct light_device_t)); if (!dev) return -ENOMEM; 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 = 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, }; struct hw_module_t HAL_MODULE_INFO_SYM = { .tag = HARDWARE_MODULE_TAG, .version_major = 1, .version_minor = 0, .id = LIGHTS_HARDWARE_MODULE_ID, .name = "lights Module", .author = "Google, Inc.", .methods = &lights_module_methods, };