blob: 508745199f055011eb572bdb04c7eb4c1aab155a [file] [log] [blame]
/* Copyright 2013 The Chromium OS Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
/*
* Keyboard power button LED state machine.
*
* This sets up TIM_POWER_LED to drive the power button LED so that the duty
* cycle can range from 0-100%. When the lid is closed or turned off, then the
* PWM is disabled and the GPIO is reconfigured to minimize leakage voltage.
*
* In suspend mode, duty cycle transitions progressively slower from 0%
* to 100%, and progressively faster from 100% back down to 0%. This
* results in a breathing effect. It takes about 2sec for a full cycle.
*/
#include "clock.h"
#include "console.h"
#include "gpio.h"
#include "hooks.h"
#include "hwtimer.h"
#include "power_led.h"
#include "pwm.h"
#include "pwm_chip.h"
#include "registers.h"
#include "task.h"
#include "timer.h"
#include "util.h"
#define LED_STATE_TIMEOUT_MIN (15 * MSEC) /* Minimum of 15ms per step */
#define LED_HOLD_TIME (330 * MSEC) /* Hold for 330ms at min/max */
#define LED_STEP_PERCENT 4 /* Incremental value of each step */
static enum powerled_state led_state = POWERLED_STATE_ON;
static int power_led_percent = 100;
void powerled_set_state(enum powerled_state new_state)
{
led_state = new_state;
/* Wake up the task */
task_wake(TASK_ID_POWERLED);
}
static void power_led_set_duty(int percent)
{
ASSERT((percent >= 0) && (percent <= 100));
power_led_percent = percent;
pwm_set_duty(PWM_CH_POWER_LED, percent);
}
static void power_led_use_pwm(void)
{
pwm_enable(PWM_CH_POWER_LED, 1);
power_led_set_duty(100);
}
static void power_led_manual_off(void)
{
pwm_enable(PWM_CH_POWER_LED, 0);
/*
* Reconfigure GPIO as a floating input. Alternatively we could
* configure it as an open-drain output and set it to high impedance,
* but reconfiguring as an input had better results in testing.
*/
gpio_config_module(MODULE_POWER_LED, 0);
}
/**
* Return the timeout period (in us) for the current step.
*/
static int power_led_step(void)
{
int state_timeout = 0;
static enum { DOWN = -1, UP = 1 } dir = UP;
if (0 == power_led_percent) {
dir = UP;
state_timeout = LED_HOLD_TIME;
} else if (100 == power_led_percent) {
dir = DOWN;
state_timeout = LED_HOLD_TIME;
} else {
/*
* Decreases timeout as duty cycle percentage approaches
* 0%, increase as it approaches 100%.
*/
state_timeout = LED_STATE_TIMEOUT_MIN +
LED_STATE_TIMEOUT_MIN * (power_led_percent / 33);
}
/*
* The next duty cycle will take effect after the timeout has
* elapsed for this duty cycle and the power LED task calls this
* function again.
*/
power_led_set_duty(power_led_percent);
power_led_percent += dir * LED_STEP_PERCENT;
return state_timeout;
}
void power_led_task(void)
{
while (1) {
int state_timeout = -1;
switch (led_state) {
case POWERLED_STATE_ON:
/*
* "ON" implies driving the LED using the PWM with a
* duty duty cycle of 100%. This produces a softer
* brightness than setting the GPIO to solid ON.
*/
power_led_use_pwm();
power_led_set_duty(100);
state_timeout = -1;
break;
case POWERLED_STATE_OFF:
/* Reconfigure GPIO to disable the LED */
power_led_manual_off();
state_timeout = -1;
break;
case POWERLED_STATE_SUSPEND:
/* Drive using PWM with variable duty cycle */
power_led_use_pwm();
state_timeout = power_led_step();
break;
default:
break;
}
task_wait_event(state_timeout);
}
}
#define CONFIG_CMD_POWERLED
#ifdef CONFIG_CMD_POWERLED
static int command_powerled(int argc, char **argv)
{
enum powerled_state state;
if (argc != 2)
return EC_ERROR_INVAL;
if (!strcasecmp(argv[1], "off"))
state = POWERLED_STATE_OFF;
else if (!strcasecmp(argv[1], "on"))
state = POWERLED_STATE_ON;
else if (!strcasecmp(argv[1], "suspend"))
state = POWERLED_STATE_SUSPEND;
else
return EC_ERROR_INVAL;
powerled_set_state(state);
return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(powerled, command_powerled,
"[off | on | suspend]",
"Change power LED state");
#endif