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