163 lines
4.0 KiB
C
163 lines
4.0 KiB
C
|
/* 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
|