blob: 579925fff9a9fdaa91117ce9fd8ca3b05dede234 [file] [log] [blame]
Mike Frysinger71b2ef72022-09-12 18:54:361/* Copyright 2013 The ChromiumOS Authors
David Hendricks1bedd552012-06-25 20:51:462 * Use of this source code is governed by a BSD-style license that can be
3 * found in the LICENSE file.
4 */
5
6/*
7 * Keyboard power button LED state machine.
8 *
Randall Spangler8e72f582013-06-24 21:22:039 * This sets up TIM_POWER_LED to drive the power button LED so that the duty
10 * cycle can range from 0-100%. When the lid is closed or turned off, then the
David Hendricks1f091482012-08-09 00:28:1311 * PWM is disabled and the GPIO is reconfigured to minimize leakage voltage.
David Hendricks1bedd552012-06-25 20:51:4612 *
13 * In suspend mode, duty cycle transitions progressively slower from 0%
14 * to 100%, and progressively faster from 100% back down to 0%. This
15 * results in a breathing effect. It takes about 2sec for a full cycle.
16 */
17
Yuval Peressa639c132022-07-28 17:23:2218#include "builtin/assert.h"
Randall Spanglerb0c8ce62013-06-21 17:16:2819#include "clock.h"
David Hendricks1bedd552012-06-25 20:51:4620#include "console.h"
Randall Spanglerfcb1e1c2013-04-05 17:28:4121#include "gpio.h"
Randall Spanglerb0c8ce62013-06-21 17:16:2822#include "hooks.h"
Randall Spangler8e72f582013-06-24 21:22:0323#include "hwtimer.h"
David Hendricks1bedd552012-06-25 20:51:4624#include "power_led.h"
Vic Yang5d014fd2013-08-05 03:17:3525#include "pwm.h"
Bill Richardsonf20ed632013-10-28 19:48:2626#include "pwm_chip.h"
David Hendricks1bedd552012-06-25 20:51:4627#include "registers.h"
28#include "task.h"
29#include "timer.h"
30#include "util.h"
31
Jack Rosenthald2fa0572022-06-27 20:30:0432#define LED_STATE_TIMEOUT_MIN (15 * MSEC) /* Minimum of 15ms per step */
33#define LED_HOLD_TIME (330 * MSEC) /* Hold for 330ms at min/max */
34#define LED_STEP_PERCENT 4 /* Incremental value of each step */
David Hendricks1bedd552012-06-25 20:51:4635
David Hendricks1f091482012-08-09 00:28:1336static enum powerled_state led_state = POWERLED_STATE_ON;
David Hendricks1f091482012-08-09 00:28:1337static int power_led_percent = 100;
David Hendricks1bedd552012-06-25 20:51:4638
39void powerled_set_state(enum powerled_state new_state)
40{
41 led_state = new_state;
42 /* Wake up the task */
43 task_wake(TASK_ID_POWERLED);
44}
45
Randall Spangleraa180852013-04-17 21:08:0046static void power_led_set_duty(int percent)
47{
48 ASSERT((percent >= 0) && (percent <= 100));
49 power_led_percent = percent;
Vic Yang5d014fd2013-08-05 03:17:3550 pwm_set_duty(PWM_CH_POWER_LED, percent);
Randall Spangleraa180852013-04-17 21:08:0051}
52
David Hendricks1f091482012-08-09 00:28:1353static void power_led_use_pwm(void)
54{
Vic Yang5d014fd2013-08-05 03:17:3555 pwm_enable(PWM_CH_POWER_LED, 1);
Randall Spangleraa180852013-04-17 21:08:0056 power_led_set_duty(100);
David Hendricks1f091482012-08-09 00:28:1357}
58
59static void power_led_manual_off(void)
60{
Vic Yang5d014fd2013-08-05 03:17:3561 pwm_enable(PWM_CH_POWER_LED, 0);
David Hendricks1f091482012-08-09 00:28:1362
Randall Spanglerfcb1e1c2013-04-05 17:28:4163 /*
64 * Reconfigure GPIO as a floating input. Alternatively we could
Tom Hughes4309ccb2019-05-31 15:42:2865 * configure it as an open-drain output and set it to high impedance,
Randall Spanglerfcb1e1c2013-04-05 17:28:4166 * but reconfiguring as an input had better results in testing.
67 */
Randall Spanglerf2b56fc2013-08-02 21:28:4368 gpio_config_module(MODULE_POWER_LED, 0);
David Hendricks1bedd552012-06-25 20:51:4669}
70
Randall Spanglerd4bd1672012-10-25 19:35:4971/**
72 * Return the timeout period (in us) for the current step.
73 */
David Hendricks1bedd552012-06-25 20:51:4674static int power_led_step(void)
75{
76 int state_timeout = 0;
77 static enum { DOWN = -1, UP = 1 } dir = UP;
78
79 if (0 == power_led_percent) {
80 dir = UP;
81 state_timeout = LED_HOLD_TIME;
82 } else if (100 == power_led_percent) {
83 dir = DOWN;
84 state_timeout = LED_HOLD_TIME;
85 } else {
86 /*
87 * Decreases timeout as duty cycle percentage approaches
Randall Spanglerfcb1e1c2013-04-05 17:28:4188 * 0%, increase as it approaches 100%.
David Hendricks1bedd552012-06-25 20:51:4689 */
Jack Rosenthald2fa0572022-06-27 20:30:0490 state_timeout =
91 LED_STATE_TIMEOUT_MIN +
David Hendricks1bedd552012-06-25 20:51:4692 LED_STATE_TIMEOUT_MIN * (power_led_percent / 33);
93 }
94
95 /*
96 * The next duty cycle will take effect after the timeout has
97 * elapsed for this duty cycle and the power LED task calls this
98 * function again.
99 */
100 power_led_set_duty(power_led_percent);
101 power_led_percent += dir * LED_STEP_PERCENT;
102
103 return state_timeout;
104}
105
106void power_led_task(void)
107{
David Hendricks1bedd552012-06-25 20:51:46108 while (1) {
109 int state_timeout = -1;
110
111 switch (led_state) {
112 case POWERLED_STATE_ON:
David Hendricks1f091482012-08-09 00:28:13113 /*
114 * "ON" implies driving the LED using the PWM with a
115 * duty duty cycle of 100%. This produces a softer
116 * brightness than setting the GPIO to solid ON.
117 */
Vic Yang5d014fd2013-08-05 03:17:35118 power_led_use_pwm();
David Hendricks1bedd552012-06-25 20:51:46119 power_led_set_duty(100);
120 state_timeout = -1;
121 break;
122 case POWERLED_STATE_OFF:
Randall Spanglerd4bd1672012-10-25 19:35:49123 /* Reconfigure GPIO to disable the LED */
Vic Yang5d014fd2013-08-05 03:17:35124 power_led_manual_off();
David Hendricks1bedd552012-06-25 20:51:46125 state_timeout = -1;
126 break;
127 case POWERLED_STATE_SUSPEND:
Randall Spanglerd4bd1672012-10-25 19:35:49128 /* Drive using PWM with variable duty cycle */
Vic Yang5d014fd2013-08-05 03:17:35129 power_led_use_pwm();
David Hendricks1bedd552012-06-25 20:51:46130 state_timeout = power_led_step();
131 break;
132 default:
133 break;
134 }
135
136 task_wait_event(state_timeout);
137 }
138}
139
Vic Yang5d014fd2013-08-05 03:17:35140#define CONFIG_CMD_POWERLED
David Hendricks1bedd552012-06-25 20:51:46141#ifdef CONFIG_CMD_POWERLED
Caveh Jalali2e864b22022-08-30 07:03:12142static int command_powerled(int argc, const char **argv)
David Hendricks1bedd552012-06-25 20:51:46143{
Randall Spanglerfcb1e1c2013-04-05 17:28:41144 enum powerled_state state;
David Hendricks1bedd552012-06-25 20:51:46145
146 if (argc != 2)
147 return EC_ERROR_INVAL;
148
149 if (!strcasecmp(argv[1], "off"))
150 state = POWERLED_STATE_OFF;
151 else if (!strcasecmp(argv[1], "on"))
152 state = POWERLED_STATE_ON;
153 else if (!strcasecmp(argv[1], "suspend"))
154 state = POWERLED_STATE_SUSPEND;
155 else
156 return EC_ERROR_INVAL;
157
158 powerled_set_state(state);
159 return EC_SUCCESS;
160}
Jack Rosenthald2fa0572022-06-27 20:30:04161DECLARE_CONSOLE_COMMAND(powerled, command_powerled, "[off | on | suspend]",
162 "Change power LED state");
David Hendricks1bedd552012-06-25 20:51:46163#endif