chrome-ec/power/host_sleep.c

216 lines
5.1 KiB
C

/* Copyright 2019 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.
*/
#include "ec_commands.h"
#include "hooks.h"
#include "host_command.h"
#include "power.h"
#include "util.h"
/* Console output macros */
#define CPRINTS(format, args...) cprints(CC_CHIPSET, format, ## args)
#define CPRINTF(format, args...) cprintf(CC_CHIPSET, format, ## args)
/* Track last reported sleep event */
static enum host_sleep_event host_sleep_state;
__overridable void power_chipset_handle_host_sleep_event(
enum host_sleep_event state,
struct host_sleep_event_context *ctx)
{
/* Default weak implementation -- no action required. */
}
static enum ec_status
host_command_host_sleep_event(struct host_cmd_handler_args *args)
{
const struct ec_params_host_sleep_event_v1 *p = args->params;
struct ec_response_host_sleep_event_v1 *r = args->response;
struct host_sleep_event_context ctx;
enum host_sleep_event state = p->sleep_event;
host_sleep_state = state;
ctx.sleep_transitions = 0;
switch (state) {
case HOST_SLEEP_EVENT_S0IX_SUSPEND:
case HOST_SLEEP_EVENT_S3_SUSPEND:
case HOST_SLEEP_EVENT_S3_WAKEABLE_SUSPEND:
ctx.sleep_timeout_ms = EC_HOST_SLEEP_TIMEOUT_DEFAULT;
/* The original version contained only state. */
if (args->version >= 1)
ctx.sleep_timeout_ms =
p->suspend_params.sleep_timeout_ms;
break;
default:
break;
}
power_chipset_handle_host_sleep_event(host_sleep_state, &ctx);
switch (state) {
case HOST_SLEEP_EVENT_S0IX_RESUME:
case HOST_SLEEP_EVENT_S3_RESUME:
if (args->version >= 1) {
r->resume_response.sleep_transitions =
ctx.sleep_transitions;
args->response_size = sizeof(*r);
}
break;
default:
break;
}
return EC_RES_SUCCESS;
}
DECLARE_HOST_COMMAND(EC_CMD_HOST_SLEEP_EVENT,
host_command_host_sleep_event,
EC_VER_MASK(0) | EC_VER_MASK(1));
enum host_sleep_event power_get_host_sleep_state(void)
{
return host_sleep_state;
}
void power_set_host_sleep_state(enum host_sleep_event state)
{
host_sleep_state = state;
}
/* Flag to notify listeners about suspend/resume events. */
enum sleep_notify_type sleep_notify = SLEEP_NOTIFY_NONE;
/*
* Note: the following sleep_ functions do not get called in the S3 path on
* Intel devices. On Intel devices, they are called in the S0ix path.
*/
void sleep_set_notify(enum sleep_notify_type notify)
{
sleep_notify = notify;
}
void sleep_notify_transition(int check_state, int hook_id)
{
if (sleep_notify != check_state)
return;
hook_notify(hook_id);
sleep_set_notify(SLEEP_NOTIFY_NONE);
}
#ifdef CONFIG_POWER_SLEEP_FAILURE_DETECTION
static uint16_t sleep_signal_timeout;
static uint32_t sleep_signal_transitions;
static void (*sleep_timeout_callback)(void);
static void sleep_transition_timeout(void);
DECLARE_DEFERRED(sleep_transition_timeout);
static void sleep_increment_transition(void)
{
if ((sleep_signal_transitions & EC_HOST_RESUME_SLEEP_TRANSITIONS_MASK) <
EC_HOST_RESUME_SLEEP_TRANSITIONS_MASK)
sleep_signal_transitions += 1;
}
void sleep_suspend_transition(void)
{
sleep_increment_transition();
hook_call_deferred(&sleep_transition_timeout_data, -1);
}
void sleep_resume_transition(void)
{
sleep_increment_transition();
/*
* Start the timer again to ensure the AP doesn't get itself stuck in
* a state where it's no longer in a sleep state (S0ix/S3), but from
* the Linux perspective is still suspended. Perhaps a bug in the SoC-
* internal periodic housekeeping code might result in a situation
* like this.
*/
if (sleep_signal_timeout)
hook_call_deferred(&sleep_transition_timeout_data,
(uint32_t)sleep_signal_timeout * 1000);
}
static void sleep_transition_timeout(void)
{
/* Mark the timeout. */
sleep_signal_transitions |= EC_HOST_RESUME_SLEEP_TIMEOUT;
hook_call_deferred(&sleep_transition_timeout_data, -1);
/* Call the custom callback */
if (sleep_timeout_callback)
sleep_timeout_callback();
}
void sleep_start_suspend(struct host_sleep_event_context *ctx,
void (*callback)(void))
{
uint16_t timeout = ctx->sleep_timeout_ms;
sleep_timeout_callback = callback;
sleep_signal_transitions = 0;
/* Use zero internally to indicate no timeout. */
if (timeout == EC_HOST_SLEEP_TIMEOUT_DEFAULT) {
timeout = CONFIG_SLEEP_TIMEOUT_MS;
} else if (timeout == EC_HOST_SLEEP_TIMEOUT_INFINITE) {
sleep_signal_timeout = 0;
return;
}
sleep_signal_timeout = timeout;
hook_call_deferred(&sleep_transition_timeout_data,
(uint32_t)timeout * 1000);
}
void sleep_complete_resume(struct host_sleep_event_context *ctx)
{
hook_call_deferred(&sleep_transition_timeout_data, -1);
ctx->sleep_transitions = sleep_signal_transitions;
}
void sleep_reset_tracking(void)
{
sleep_signal_transitions = 0;
sleep_signal_timeout = 0;
sleep_timeout_callback = NULL;
}
#else /* !CONFIG_POWER_SLEEP_FAILURE_DETECTION */
/* No action */
void sleep_suspend_transition(void)
{
}
void sleep_resume_transition(void)
{
}
void sleep_start_suspend(struct host_sleep_event_context *ctx,
void (*callback)(void))
{
}
void sleep_complete_resume(struct host_sleep_event_context *ctx)
{
}
void sleep_reset_tracking(void)
{
}
#endif /* CONFIG_POWER_SLEEP_FAILURE_DETECTION */