Daisy/Snow: Drive power LED with PWM

This drives the power LED for Snow (PB3) using TIM2 in PWM mode.

Since timer setup and manipulation is STM32-specific, the power LED
logic moved to to chip/stm32/power_led.c.

This also adds a "powerled" console command for testing.

Signed-off-by: David Hendricks <dhendrix@chromium.org>

BUG=chrome-os-partner:10647
TEST=Tested on Snow with powerled command, compiled for Daisy

Change-Id: I5a7dc20d201ea058767e3e76d54e7c8567a3b83c
Reviewed-on: https://gerrit.chromium.org/gerrit/26267
Commit-Ready: David Hendricks <dhendrix@chromium.org>
Reviewed-by: David Hendricks <dhendrix@chromium.org>
Tested-by: David Hendricks <dhendrix@chromium.org>
This commit is contained in:
David Hendricks
2012-06-25 13:51:46 -07:00
committed by Gerrit
parent bb27b96f13
commit 1bedd55970
6 changed files with 186 additions and 60 deletions

View File

@@ -56,7 +56,6 @@ const struct gpio_info gpio_list[GPIO_COUNT] = {
{"PMIC_PWRON_L",GPIO_A, (1<<12), GPIO_OUT_HIGH, NULL},
{"ENTERING_RW", GPIO_D, (1<<0), GPIO_OUT_LOW, NULL},
{"CHARGER_EN", GPIO_B, (1<<2), GPIO_OUT_LOW, NULL},
{"POWER_LED_L", GPIO_B, (1<<3), GPIO_OUT_HIGH, NULL},
{"EC_INT", GPIO_B, (1<<9), GPIO_HI_Z, NULL},
{"CODEC_INT", GPIO_D, (1<<1), GPIO_HI_Z, NULL},
{"KB_OUT00", GPIO_B, (1<<0), GPIO_KB_OUTPUT, NULL},
@@ -130,6 +129,15 @@ void configure_board(void)
STM32_GPIO_AFIO_MAPR = (STM32_GPIO_AFIO_MAPR & ~(0x7 << 24))
| (2 << 24);
/* remap TIM2_CH2 to PB3 */
STM32_GPIO_AFIO_MAPR = (STM32_GPIO_AFIO_MAPR & ~(0x3 << 8))
| (1 << 8);
/* set power LED to alternate function to be driven by TIM2/PWM */
val = STM32_GPIO_CRL_OFF(GPIO_B) & ~0x0000f000;
val |= 0x00009000;
STM32_GPIO_CRL_OFF(GPIO_B) = val;
/*
* I2C SCL/SDA on PB10-11 and PB6-7, bi-directional, no pull-up/down,
* initialized as hi-Z until alt. function is set

View File

@@ -79,7 +79,6 @@ enum gpio_signal {
GPIO_PMIC_PWRON_L, /* 5v rail ready */
GPIO_EC_ENTERING_RW, /* EC is R/W mode for the kbc mux */
GPIO_CHARGER_EN,
GPIO_POWER_LED_L, /* Power state keyboard LED */
GPIO_EC_INT,
GPIO_CODEC_INT, /* To audio codec (KB noise cancellation) */
GPIO_KB_OUT00,

View File

@@ -9,7 +9,7 @@
# STM32 SoC family has a Cortex-M3 ARM core
CORE:=cortex-m
chip-y=dma.o gpio.o hwtimer.o system.o uart.o
chip-y=dma.o gpio.o hwtimer.o power_led.o system.o uart.o
chip-y+=jtag-$(CHIP_VARIANT).o clock-$(CHIP_VARIANT).o gpio-$(CHIP_VARIANT).o
chip-$(CONFIG_SPI)+=spi.o
chip-$(CONFIG_I2C)+=i2c.o

161
chip/stm32/power_led.c Normal file
View File

@@ -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

View File

@@ -28,6 +28,7 @@
#include "console.h"
#include "gpio.h"
#include "keyboard_scan.h"
#include "power_led.h"
#include "task.h"
#include "timer.h"
#include "util.h"
@@ -101,60 +102,6 @@ static timestamp_t pwron_deadline;
/* force AP power on (used for recovery keypress) */
static int auto_power_on;
/*****************************************************************************/
/* Keyboard power button LED state machine */
/* Suspend mode blinking duty cycle */
#define LED_OFF_PERIOD 1500000
#define LED_ON_PERIOD 50000
/* GPIO level for each LED state : it's an active low output */
#define LED_GPIO_ON 0
#define LED_GPIO_OFF 1
/* power LED FSM states */
static enum power_led_state {
POWER_LED_OFF,
POWER_LED_ON,
POWER_LED_SUSP0,
POWER_LED_SUSP1,
POWER_LED_STATE_COUNT
} led_state;
/* power LED FSM parameters for each state */
static const struct {
int gpio;
int duration;
enum power_led_state next;
} led_config[POWER_LED_STATE_COUNT] = {
[POWER_LED_OFF] = {LED_GPIO_OFF, -1, POWER_LED_OFF},
[POWER_LED_ON] = {LED_GPIO_ON, -1, POWER_LED_ON},
[POWER_LED_SUSP0] = {LED_GPIO_OFF, LED_OFF_PERIOD, POWER_LED_SUSP1},
[POWER_LED_SUSP1] = {LED_GPIO_ON, LED_ON_PERIOD, POWER_LED_SUSP0},
};
static void set_power_led(enum power_led_state new_state)
{
led_state = new_state;
/* Wake up the task */
task_wake(TASK_ID_POWERLED);
}
void power_led_task(void)
{
while (1) {
int state_timeout;
gpio_set_level(GPIO_POWER_LED_L, led_config[led_state].gpio);
state_timeout = led_config[led_state].duration;
led_state = led_config[led_state].next;
task_wait_event(state_timeout);
}
}
/*****************************************************************************/
/*
* Wait for GPIO "signal" to reach level "value".
* Returns EC_ERROR_TIMEOUT if timeout before reaching the desired state.
@@ -242,7 +189,8 @@ void gaia_suspend_event(enum gpio_signal signal)
ap_suspended = !gpio_get_level(GPIO_SUSPEND_L);
set_power_led(ap_suspended ? POWER_LED_SUSP0 : POWER_LED_ON);
powerled_set_state(ap_suspended ?
POWERLED_STATE_SUSPEND : POWERLED_STATE_ON);
}
void gaia_power_event(enum gpio_signal signal)
@@ -356,7 +304,7 @@ static int power_on(void)
/* Enable 3.3v power rail */
gpio_set_level(GPIO_EN_PP3300, 1);
ap_on = 1;
set_power_led(POWER_LED_ON);
powerled_set_state(POWERLED_STATE_ON);
CPUTS("AP running ...\n");
return 0;
}
@@ -391,7 +339,7 @@ static void power_off(void)
gpio_set_level(GPIO_PMIC_PWRON_L, 1);
gpio_set_level(GPIO_EN_PP5000, 0);
ap_on = 0;
set_power_led(POWER_LED_OFF);
powerled_set_state(POWERLED_STATE_OFF);
CPUTS("Shutdown complete.\n");
}

View File

@@ -18,7 +18,17 @@ enum powerled_color {
POWERLED_COLOR_COUNT /* Number of colors, not a color itself */
};
enum powerled_state {
POWERLED_STATE_OFF,
POWERLED_STATE_ON,
POWERLED_STATE_SUSPEND,
POWERLED_STATE_COUNT
};
/* Set the power adapter LED to the specified color. */
int powerled_set(enum powerled_color color);
/* Set the power LED according to the specified state. */
void powerled_set_state(enum powerled_state state);
#endif /* __CROS_EC_POWER_LED_H */