summaryrefslogtreecommitdiff
path: root/chip/g/idle.c
blob: 7319c6edb898eb85c3095daf02ebae8a21320e41 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
/* Copyright (c) 2014 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 "common.h"
#include "console.h"
#include "pmu.h"
#include "system.h"
#include "task.h"
#include "util.h"

/* This function is assumed to exist, but we don't use it */
void clock_refresh_console_in_use(void)
{
}

/* What to do when we're just waiting */
static enum {
	IDLE_WFI,				/* default */
	IDLE_SLEEP,
	IDLE_DEEP_SLEEP,
	NUM_CHOICES
} idle_action;

static const char const *idle_name[] = {
	"wfi",
	"sleep",
	"deep sleep",
};
BUILD_ASSERT(ARRAY_SIZE(idle_name) == NUM_CHOICES);

static int command_idle(int argc, char **argv)
{
	int c, i;

	if (argc > 1) {
		c = tolower(argv[1][0]);
		for (i = 0; i < ARRAY_SIZE(idle_name); i++)
			if (idle_name[i][0] == c) {
				idle_action = i;
				break;
			}
	}

	ccprintf("idle action: %s\n", idle_name[idle_action]);

	return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(idle, command_idle,
			"[w|s|d]",
			"Set or show the idle action: wfi, sleep, deep sleep",
			NULL);

static void prepare_to_deep_sleep(void)
{
	/* No task switching! */
	interrupt_disable();

	/*
	 * Preserve some state prior to deep sleep. Pretty much all we need is
	 * the device address, since everything else can be reinitialized on
	 * resume.
	 */
	GREG32(PMU, PWRDN_SCRATCH18) = GR_USB_DCFG;

	/* Latch the pinmux values */
	GREG32(PINMUX, HOLD) = 1;

	/* Wake only from USB for now */
	GR_PMU_EXITPD_MASK =
		GC_PMU_EXITPD_MASK_UTMI_SUSPEND_N_MASK;

	/* Clamp the USB pins and shut the PHY down. We have to do this in
	 * three separate steps, or Bad Things happen. */
	GWRITE_FIELD(USB, PCGCCTL, PWRCLMP, 1);
	GWRITE_FIELD(USB, PCGCCTL, RSTPDWNMODULE, 1);
	GWRITE_FIELD(USB, PCGCCTL, STOPPCLK, 1);

	/* Get ready... */
	GR_PMU_LOW_POWER_DIS =
		/* The next "wfi" will trigger it */
		GC_PMU_LOW_POWER_DIS_START_MASK |
		/* ... with these rails off */
		GC_PMU_LOW_POWER_DIS_VDDL_MASK | /* <= this means deep sleep */
		GC_PMU_LOW_POWER_DIS_VDDIOF_MASK |
		GC_PMU_LOW_POWER_DIS_VDDXO_MASK |
		GC_PMU_LOW_POWER_DIS_JTR_RC_MASK;
}

/* Custom idle task, executed when no tasks are ready to be scheduled. */
void __idle(void)
{
	int sleep_ok;

	while (1) {

		/* Don't even bother unless we've enabled it */
		if (idle_action == IDLE_WFI)
			goto wfi;

		/* Anyone still busy? */
		sleep_ok = DEEP_SLEEP_ALLOWED;

		/* We're allowed to sleep now, so set it up. */
		if (sleep_ok)
			if (idle_action == IDLE_DEEP_SLEEP)
				prepare_to_deep_sleep();
		/* Normal sleep is not yet implemented */

wfi:
		/* Wait for the next irq event. This stops the CPU clock and
		 * may trigger sleep or deep sleep if enabled. */
		asm("wfi");

		/*
		 * TODO: Normal sleep resumes by handling the interrupt, but we
		 * need to clear PMU_LOW_POWER_DIS right away or we might sleep
		 * again by accident. We can't do that here because we don't
		 * get here until the next idle, so we'll have to do it in the
		 * interrupt handler or when task switching. Deep sleep resumes
		 * with a warm boot, which handles it differently.
		 */
	}
}