-
Notifications
You must be signed in to change notification settings - Fork 6.8k
/
power.c
257 lines (228 loc) · 7.74 KB
/
power.c
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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
/*
* Copyright (c) 2021 Nuvoton Technology Corporation.
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief Nuvoton NPCX power management driver
*
* This file contains the drivers of NPCX Power Manager Modules that improves
* the efficiency of ec operation by adjusting the chip’s power consumption to
* the level of activity required by the application. The following table
* summarizes the main properties of the various power states and shows the
* activity levels of the various clocks while in these power states.
*
* +--------------------------------------------------------------------------+
* | Power State | LFCLK | HFCLK | APB/AHB | Core | RAM/Regs | VCC | VSBY |
* |--------------------------------------------------------------------------|
* | Active | On | On | On | Active | Active | On | On |
* | Idle (wfi) | On | On | On | Wait | Active | On | On |
* | Sleep | On | On | Stop | Stop | Preserved | On | On |
* | Deep Sleep | On | Stop | Stop | Stop | Power Down | On | On |
* | Stand-By | Off | Off | Off | Off | Off | Off | On |
* +--------------------------------------------------------------------------+
*
* LFCLK - Low-Frequency Clock. Its frequency is fixed to 32kHz.
* HFCLK - High-Frequency (PLL) Clock. Its frequency is configured to OFMCLK.
*
* Based on the following criteria:
*
* - A delay of 'Instant' wake-up from 'Deep Sleep' is 20 us.
* - A delay of 'Standard' wake-up from 'Deep Sleep' is 3.43 ms.
* - Max residency time in Deep Sleep for 'Instant' wake-up is 200 ms
* - Min Residency time in Deep Sleep for 'Instant' wake-up is 61 us
* - The unit to determine power state residency policy is tick.
*
* this driver implements one power state, PM_STATE_SUSPEND_TO_IDLE, with
* two sub-states for power management system.
* Sub-state 0 - "Deep Sleep" mode with “Instant” wake-up if residency time
* is greater or equal to 1 ms
* Sub-state 1 - "Deep Sleep" mode with "Standard" wake-up if residency time
* is greater or equal to 201 ms
*
* INCLUDE FILES: soc_clock.h
*/
#include <cmsis_core.h>
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/espi.h>
#include <zephyr/pm/pm.h>
#include <soc.h>
#include "soc_gpio.h"
#include "soc_host.h"
#include "soc_power.h"
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(soc, CONFIG_SOC_LOG_LEVEL);
/* The steps that npcx ec enters sleep/deep mode and leaves it. */
#define NPCX_ENTER_SYSTEM_SLEEP() ({ \
__asm__ volatile ( \
"push {r0-r5}\n" /* Save the registers used for delay */ \
"wfi\n" /* Enter sleep mode after receiving wfi */ \
"ldm %0, {r0-r5}\n" /* Add a delay before instructions fetching */ \
"pop {r0-r5}\n" /* Restore the registers used for delay */ \
"isb\n" /* Flush the cpu pipelines */ \
:: "r" (CONFIG_SRAM_BASE_ADDRESS)); /* A valid addr used for delay */ \
})
/* Variables for tracing */
static uint32_t cnt_sleep0;
static uint32_t cnt_sleep1;
/* Supported sleep mode in npcx series */
enum {
NPCX_SLEEP,
NPCX_DEEP_SLEEP,
};
/* Supported wake-up mode in npcx series */
enum {
NPCX_INSTANT_WAKE_UP,
NPCX_STANDARD_WAKE_UP,
};
#define NODE_LEAKAGE_IO DT_INST(0, nuvoton_npcx_leakage_io)
#if DT_NODE_HAS_PROP(NODE_LEAKAGE_IO, leak_gpios)
struct npcx_leak_gpio {
const struct device *gpio;
gpio_pin_t pin;
};
#define NPCX_POWER_LEAKAGE_IO_INIT(node_id, prop, idx) { \
.gpio = DEVICE_DT_GET(DT_GPIO_CTLR_BY_IDX(node_id, prop, idx)), \
.pin = DT_GPIO_PIN_BY_IDX(node_id, prop, idx), \
},
/*
* Get io array which have leakage current from 'leak-gpios' property of
* 'power_leakage_io' DT node. User can overwrite this prop. at board DT file to
* save power consumption when ec enter deep sleep.
*
* &power_leakage_io {
* leak-gpios = <&gpio0 0 0
* &gpiob 1 0>;
* };
*/
static struct npcx_leak_gpio leak_gpios[] = {
DT_FOREACH_PROP_ELEM(NODE_LEAKAGE_IO, leak_gpios, NPCX_POWER_LEAKAGE_IO_INIT)
};
static void npcx_power_suspend_leak_io_pads(void)
{
for (int i = 0; i < ARRAY_SIZE(leak_gpios); i++) {
npcx_gpio_disable_io_pads(leak_gpios[i].gpio, leak_gpios[i].pin);
}
}
static void npcx_power_restore_leak_io_pads(void)
{
for (int i = 0; i < ARRAY_SIZE(leak_gpios); i++) {
npcx_gpio_enable_io_pads(leak_gpios[i].gpio, leak_gpios[i].pin);
}
}
#else
void npcx_power_suspend_leak_io_pads(void)
{
/* do nothing */
}
void npcx_power_restore_leak_io_pads(void)
{
/* do nothing */
}
#endif /* DT_NODE_HAS_PROP(NODE_LEAKAGE_IO, leak_gpios) */
static void npcx_power_enter_system_sleep(int slp_mode, int wk_mode)
{
/* Disable interrupts */
__disable_irq();
/*
* Disable priority mask temporarily to make sure that wake-up events
* are visible to the WFI instruction.
*/
__set_BASEPRI(0);
/* Configure sleep/deep sleep settings in clock control module. */
npcx_clock_control_turn_on_system_sleep(slp_mode == NPCX_DEEP_SLEEP,
wk_mode == NPCX_INSTANT_WAKE_UP);
/*
* Disable the connection between io pads that have leakage current and
* input buffer to save power consumption.
*/
npcx_power_suspend_leak_io_pads();
/* Turn on eSPI/LPC host access wake-up interrupt. */
if (IS_ENABLED(CONFIG_ESPI_NPCX)) {
npcx_host_enable_access_interrupt();
}
/* Turn on UART RX wake-up interrupt. */
if (IS_ENABLED(CONFIG_UART_NPCX)) {
npcx_uart_enable_access_interrupt();
}
/*
* Capture the reading of low-freq timer for compensation before ec
* enters system sleep mode.
*/
npcx_clock_capture_low_freq_timer();
/* Enter system sleep mode */
NPCX_ENTER_SYSTEM_SLEEP();
/*
* Compensate system timer by the elapsed time of low-freq timer during
* system sleep mode.
*/
npcx_clock_compensate_system_timer();
/* Turn off eSPI/LPC host access wake-up interrupt. */
if (IS_ENABLED(CONFIG_ESPI_NPCX)) {
npcx_host_disable_access_interrupt();
}
/*
* Restore the connection between io pads that have leakage current and
* input buffer.
*/
npcx_power_restore_leak_io_pads();
/* Turn off system sleep mode. */
npcx_clock_control_turn_off_system_sleep();
}
/* Invoke when enter "Suspend/Low Power" mode. */
void pm_state_set(enum pm_state state, uint8_t substate_id)
{
if (state != PM_STATE_SUSPEND_TO_IDLE) {
LOG_DBG("Unsupported power state %u", state);
} else {
switch (substate_id) {
case 0: /* Sub-state 0: Deep sleep with instant wake-up */
npcx_power_enter_system_sleep(NPCX_DEEP_SLEEP,
NPCX_INSTANT_WAKE_UP);
if (IS_ENABLED(CONFIG_NPCX_PM_TRACE)) {
cnt_sleep0++;
}
break;
case 1: /* Sub-state 1: Deep sleep with standard wake-up */
npcx_power_enter_system_sleep(NPCX_DEEP_SLEEP,
NPCX_STANDARD_WAKE_UP);
if (IS_ENABLED(CONFIG_NPCX_PM_TRACE)) {
cnt_sleep1++;
}
break;
default:
LOG_DBG("Unsupported power substate-id %u",
substate_id);
break;
}
}
}
/* Handle soc specific activity after exiting "Suspend/Low Power" mode. */
void pm_state_exit_post_ops(enum pm_state state, uint8_t substate_id)
{
if (state != PM_STATE_SUSPEND_TO_IDLE) {
LOG_DBG("Unsupported power state %u", state);
} else {
switch (substate_id) {
case 0: /* Sub-state 0: Deep sleep with instant wake-up */
/* Restore interrupts */
__enable_irq();
break;
case 1: /* Sub-state 1: Deep sleep with standard wake-up */
/* Restore interrupts */
__enable_irq();
break;
default:
LOG_DBG("Unsupported power substate-id %u",
substate_id);
break;
}
}
if (IS_ENABLED(CONFIG_NPCX_PM_TRACE)) {
LOG_DBG("sleep: %d, deep sleep: %d", cnt_sleep0, cnt_sleep1);
LOG_INF("total ticks in sleep: %lld",
npcx_clock_get_sleep_ticks());
}
}