summaryrefslogtreecommitdiff
path: root/samples/power/power_mgr/src/main.c
blob: a8c4331b3f471166ee7eec5d5dafbc1d9c9cc64f (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
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
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
/*
 * Copyright (c) 2016 Intel Corporation.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <zephyr.h>
#include <power.h>
#include <soc_power.h>
#include <misc/printk.h>
#include <string.h>
#include <rtc.h>

#define SECONDS_TO_SLEEP	5
#define ALARM		(RTC_ALARM_SECOND * (SECONDS_TO_SLEEP - 1))
#define GPIO_IN_PIN	16

/* In Tickless Kernel mode, time is passed in milliseconds instead of ticks */
#ifdef CONFIG_TICKLESS_KERNEL
#define TICKS_TO_SECONDS_MULTIPLIER 1000
#define TIME_UNIT_STRING "milliseconds"
#else
#define TICKS_TO_SECONDS_MULTIPLIER CONFIG_SYS_CLOCK_TICKS_PER_SEC
#define TIME_UNIT_STRING "ticks"
#endif

#define MIN_TIME_TO_SUSPEND	((SECONDS_TO_SLEEP * \
				  TICKS_TO_SECONDS_MULTIPLIER) - \
				  (TICKS_TO_SECONDS_MULTIPLIER / 2))

static void create_device_list(void);
static void suspend_devices(void);
static void resume_devices(void);

static struct device *device_list;
static int device_count;

static int post_ops_done = 1;

/*
 * Example ordered list to store devices on which
 * device power policies would be executed.
 */
#define DEVICE_POLICY_MAX 15
static char device_ordered_list[DEVICE_POLICY_MAX];
static char device_retval[DEVICE_POLICY_MAX];

static struct device *rtc_dev;
static u32_t start_time, end_time;
static void setup_rtc(void);
static void enable_wake_event(void);

int pm_state;

void main(void)
{
	printk("Power Management Demo on %s\n", CONFIG_ARCH);

	setup_rtc();

	create_device_list();

	while (1) {
		printk("\nApplication main thread\n");
		k_sleep(SECONDS_TO_SLEEP * 1000);
	}
}

static int check_pm_policy(s32_t ticks)
{
	static int policy;
	int power_states[] = {SYS_POWER_STATE_MAX, SYS_POWER_STATE_CPU_LPS,
		SYS_POWER_STATE_DEEP_SLEEP};

	/*
	 * Compare time available with wake latencies and select
	 * appropriate power saving policy
	 *
	 * For the demo we will alternate between following policies
	 *
	 * 0 = no power saving operation
	 * 1 = low power state
	 * 2 = deep sleep
	 *
	 */

#if (defined(CONFIG_SYS_POWER_DEEP_SLEEP))
	policy = (++policy > 2 ? 0 : policy);
	/*
	 * If deep sleep was selected, we need to check if any device is in
	 * the middle of a transaction
	 *
	 * Use device_busy_check() to check specific devices
	 */
	if ((policy == 2) && device_any_busy_check()) {
		/* Devices are busy - do CPU LPS instead */
		policy = 1;
	}
#else
	policy = (++policy > 1 ? 0 : policy);
#endif

	return power_states[policy];
}

static void low_power_state_exit(void)
{
	/* Turn on suspended peripherals/clocks as necessary */

	end_time = rtc_read(rtc_dev);
	printk("\nLow power state exit!\n");
	printk("Total Elapsed From Suspend To Resume = %d RTC Cycles\n",
			end_time - start_time);
}

static void deep_sleep_exit(void)
{
	/* Turn on peripherals and restore device states as necessary */
	resume_devices();

	printk("Wake from Deep Sleep!\n");

	end_time = rtc_read(rtc_dev);
	printk("\nDeep sleep exit!\n");
	printk("Total Elapsed From Suspend To Resume = %d RTC Cycles\n",
			end_time - start_time);
}

static int low_power_state_entry(s32_t ticks)
{
	printk("\nLow power state entry!\n");

	start_time = rtc_read(rtc_dev);

	/* Turn off peripherals/clocks as necessary */

	enable_wake_event();

	_sys_soc_set_power_state(SYS_POWER_STATE_CPU_LPS);

	return SYS_PM_LOW_POWER_STATE;
}

static int deep_sleep_entry(s32_t ticks)
{
	printk("\nDeep sleep entry!\n");

	start_time = rtc_read(rtc_dev);

	/* Don't need pm idle exit event notification */
	_sys_soc_pm_idle_exit_notification_disable();

	/* Save device states and turn off peripherals as necessary */
	suspend_devices();

	enable_wake_event();

	_sys_soc_set_power_state(SYS_POWER_STATE_DEEP_SLEEP);

	/*
	 * At this point system has woken up from
	 * deep sleep.
	 */

	deep_sleep_exit();

	return SYS_PM_DEEP_SLEEP;
}

int _sys_soc_suspend(s32_t ticks)
{
	int ret = SYS_PM_NOT_HANDLED;

	post_ops_done = 0;

	if ((ticks != K_FOREVER) && (ticks < MIN_TIME_TO_SUSPEND)) {
		printk("Not enough time for PM operations (" TIME_UNIT_STRING
		       ": %d).\n", ticks);
		return SYS_PM_NOT_HANDLED;
	}

	pm_state = check_pm_policy(ticks);

	switch (pm_state) {
	case SYS_POWER_STATE_CPU_LPS:
		/* Do CPU LPS operations */
		ret = low_power_state_entry(ticks);
		break;
	case SYS_POWER_STATE_DEEP_SLEEP:
		/* Do deep sleep operations */
		ret = deep_sleep_entry(ticks);
		break;
	default:
		/* No PM operations */
		printk("\nNo PM operations done\n");
		ret = SYS_PM_NOT_HANDLED;
		break;
	}

	if (ret != SYS_PM_NOT_HANDLED) {
		/*
		 * Do any arch or soc specific post operations specific to the
		 * power state.
		 *
		 * If this enables interrupts, then it should be done
		 * right before function return.
		 *
		 * Some CPU power states would require interrupts to be
		 * enabled at the time of entering the low power state.
		 * For such states the post operations need to be done
		 * at _sys_soc_resume. To avoid doing it twice, check a
		 * flag.
		 */
		if (!post_ops_done) {
			if (pm_state == SYS_POWER_STATE_CPU_LPS) {
				low_power_state_exit();
			}
			post_ops_done = 1;
			_sys_soc_power_state_post_ops(pm_state);
		}
	}

	return ret;
}

void _sys_soc_resume(void)
{
	/*
	 * This notification is called from the ISR of the event
	 * that caused exit from kernel idling after PM operations.
	 *
	 * Some CPU low power states require enabling of interrupts
	 * atomically when entering those states. The wake up from
	 * such a state first executes code in the ISR of the interrupt
	 * that caused the wake. This hook will be called from the ISR.
	 * For such CPU LPS states, do post operations and restores here.
	 * The kernel scheduler will get control after the ISR finishes
	 * and it may schedule another thread.
	 *
	 * Call _sys_soc_pm_idle_exit_notification_disable() if this
	 * notification is not required.
	 */
	if (!post_ops_done) {
		if (pm_state == SYS_POWER_STATE_CPU_LPS) {
			low_power_state_exit();
		}
		post_ops_done = 1;
		_sys_soc_power_state_post_ops(pm_state);
	}
}

static void suspend_devices(void)
{
	int i;

	for (i = device_count - 1; i >= 0; i--) {
		int idx = device_ordered_list[i];

		/* If necessary  the policy manager can check if a specific
		 * device in the device list is busy as shown below :
		 * if(device_busy_check(&device_list[idx])) {do something}
		 */
		device_retval[i] = device_set_power_state(&device_list[idx],
						DEVICE_PM_SUSPEND_STATE);
	}
}

static void resume_devices(void)
{
	int i;

	for (i = 0; i < device_count; i++) {
		if (!device_retval[i]) {
			int idx = device_ordered_list[i];

			device_set_power_state(&device_list[idx],
						DEVICE_PM_ACTIVE_STATE);
		}
	}
}

static void create_device_list(void)
{
	int count;
	int i;

	/*
	 * Following is an example of how the device list can be used
	 * to suspend devices based on custom policies.
	 *
	 * Create an ordered list of devices that will be suspended.
	 * Ordering should be done based on dependencies. Devices
	 * in the beginning of the list will be resumed first.
	 *
	 * Other devices depend on APICs so ioapic and loapic devices
	 * will be placed first in the device ordered list. Move any
	 * other devices to the beginning as necessary. e.g. uart
	 * is useful to enable early prints.
	 */
	device_list_get(&device_list, &count);

#if (CONFIG_X86)
	device_count = 3; /* Reserve for ioapic, loapic and uart */

	for (i = 0; (i < count) && (device_count < DEVICE_POLICY_MAX); i++) {
		if (!strcmp(device_list[i].config->name, "loapic")) {
			device_ordered_list[0] = i;
		} else if (!strcmp(device_list[i].config->name, "ioapic")) {
			device_ordered_list[1] = i;
		} else if (!strcmp(device_list[i].config->name, "UART_0")) {
			device_ordered_list[2] = i;
		} else {
			device_ordered_list[device_count++] = i;
		}
	}
#elif (CONFIG_ARC)
	device_count = 1; /* Reserve for irq unit */

	for (i = 0; (i < count) && (device_count < DEVICE_POLICY_MAX); i++) {
		if (!strcmp(device_list[i].config->name, "arc_v2_irq_unit")) {
			device_ordered_list[0] = i;
		} else {
			device_ordered_list[device_count++] = i;
		}
	}
#endif
}

static void rtc_interrupt_fn(struct device *rtc_dev)
{
	printk("Wake up event handler\n");
}

static void setup_rtc(void)
{
	struct rtc_config config;

	rtc_dev = device_get_binding("RTC_0");

	config.init_val = 0;
	config.alarm_enable = 0;
	config.alarm_val = ALARM;
	config.cb_fn = rtc_interrupt_fn;

	rtc_enable(rtc_dev);

	rtc_set_config(rtc_dev, &config);

}

static void enable_wake_event(void)
{
	u32_t now = rtc_read(rtc_dev);
	u32_t alarm;

	alarm = (rtc_read(rtc_dev) + ALARM);
	rtc_set_alarm(rtc_dev, alarm);

	/* Wait a few ticks to ensure the alarm value gets loaded */
	while (rtc_read(rtc_dev) < now + 5)
		;
}