diff options
Diffstat (limited to 'chip/stm32/power_led.c')
-rw-r--r-- | chip/stm32/power_led.c | 161 |
1 files changed, 161 insertions, 0 deletions
diff --git a/chip/stm32/power_led.c b/chip/stm32/power_led.c new file mode 100644 index 0000000000..a080dbfa07 --- /dev/null +++ b/chip/stm32/power_led.c @@ -0,0 +1,161 @@ +/* Copyright (c) 2012 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 TIM2 to drive the power button LED so that the duty cycle + * can range from 0-100%. + * + * 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 "console.h" +#include "power_led.h" +#include "registers.h" +#include "task.h" +#include "timer.h" +#include "util.h" + +#define LED_STATE_TIMEOUT_MIN 15000 /* minimum of 15ms per step */ +#define LED_HOLD_TIME 330000 /* hold for 330ms at min/max */ +#define LED_STEP_PERCENT 4 /* incremental value of each step */ + +static enum powerled_state led_state; +static int power_led_percent; + +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_timer_init(void) +{ + /* enable TIM2 clock */ + STM32_RCC_APB1ENR |= 0x1; + + /* disable counter during setup */ + STM32_TIM_CR1(2) = 0x0000; + + /* + * CPU_CLOCK / PSC determines how fast the counter operates. + * ARR determines the wave period, CCRn determines duty cycle. + * Thus, frequency = CPU_CLOCK / PSC / ARR. + * + * Assuming 16MHz clock, the following yields: + * 16MHz / 1600 / 100 = 100Hz. + */ + STM32_TIM_PSC(2) = CPU_CLOCK / 10000; /* pre-scaler */ + STM32_TIM_ARR(2) = 100; /* auto-reload value */ + STM32_TIM_CCR2(2) = 100; /* duty cycle */ + + /* CC2 configured as output, PWM mode 1, preload enable */ + STM32_TIM_CCMR1(2) = (6 << 12) | (1 << 11); + + /* CC2 output enable, active low */ + STM32_TIM_CCER(2) = (1 << 4) | (1 << 5); + + /* generate update event to force loading of shadow registers */ + STM32_TIM_EGR(2) |= 1; + + /* enable auto-reload preload, start counting */ + STM32_TIM_CR1(2) |= (1 << 7) | (1 << 0); +} + +static void power_led_set_duty(int percent) +{ + ASSERT((percent >= 0) && (percent <= 100)); + power_led_percent = percent; + STM32_TIM_CCR2(2) = (STM32_TIM_ARR(2) / 100) * percent; +} + +/* returns the timeout period (in us) for 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 appraoches 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) +{ + power_led_timer_init(); + + while (1) { + int state_timeout = -1; + + switch (led_state) { + case POWERLED_STATE_ON: + power_led_set_duty(100); + state_timeout = -1; + break; + case POWERLED_STATE_OFF: + power_led_set_duty(0); + state_timeout = -1; + break; + case POWERLED_STATE_SUSPEND: + state_timeout = power_led_step(); + break; + default: + break; + } + + task_wait_event(state_timeout); + } +} + +#ifdef CONFIG_CMD_POWERLED +static int command_powerled(int argc, char **argv) +{ + enum powerled_state state = POWERLED_STATE_OFF; + + 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", + NULL); +#endif |