| /* |
| * 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, |
| }; |