Linux preempt-rt

Check our new training course

Real-Time Linux with PREEMPT_RT

Check our new training course
with Creative Commons CC-BY-SA
lecture and lab materials

Bootlin logo

Elixir Cross Referencer

  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
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
/*
 * Copyright (c) 2016 Intel Corporation.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <zephyr.h>
#include <kernel.h>
#include <power.h>
#include <misc/printk.h>
#include <rtc.h>
#include <gpio.h>
#include <counter.h>
#include <aio_comparator.h>

#include <power.h>
#include <soc_power.h>
#include <string.h>
#include "soc_watch_logger.h"

static enum power_states states_list[] = {
	SYS_POWER_STATE_CPU_LPS,
	SYS_POWER_STATE_CPU_LPS_1,
	SYS_POWER_STATE_CPU_LPS_2,
#if (CONFIG_SYS_POWER_DEEP_SLEEP)
	SYS_POWER_STATE_DEEP_SLEEP,
	SYS_POWER_STATE_DEEP_SLEEP_1,
#endif
};

#define TIMEOUT 5 /* in seconds */
#define MAX_SUSPEND_DEVICE_COUNT 15
#define NB_STATES ARRAY_SIZE(states_list)

/* 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	((TIMEOUT * TICKS_TO_SECONDS_MULTIPLIER) - \
				  (TICKS_TO_SECONDS_MULTIPLIER / 2))

static struct device *suspend_devices[MAX_SUSPEND_DEVICE_COUNT];
static int suspend_device_count;
static unsigned int current_state = NB_STATES - 1;
static int post_ops_done = 1;

static enum power_states get_next_state(void)
{
	current_state = (current_state + 1) % NB_STATES;
	return states_list[current_state];
}

static const char *state_to_string(int state)
{
	switch (state) {
	case SYS_POWER_STATE_CPU_LPS:
		return "SYS_POWER_STATE_CPU_LPS";
	case SYS_POWER_STATE_CPU_LPS_1:
		return "SYS_POWER_STATE_CPU_LPS_1";
	case SYS_POWER_STATE_CPU_LPS_2:
		return "SYS_POWER_STATE_CPU_LPS_2";
	case SYS_POWER_STATE_DEEP_SLEEP:
		return "SYS_POWER_STATE_DEEP_SLEEP";
	case SYS_POWER_STATE_DEEP_SLEEP_1:
		return "SYS_POWER_STATE_DEEP_SLEEP_1";
	default:
		return "Unknown state";
	}
}

#if (CONFIG_RTC)
static struct device *rtc_dev;

static void setup_rtc(void)
{
	struct rtc_config cfg;

	/* Configure RTC device. RTC interrupt is used as 'wake event' when we
	 * are in C2LP state.
	 */
	cfg.init_val = 0;
	cfg.alarm_enable = 0;
	cfg.alarm_val = 0;
	cfg.cb_fn = NULL;
	rtc_dev = device_get_binding(CONFIG_RTC_0_NAME);
	rtc_enable(rtc_dev);
	rtc_set_config(rtc_dev, &cfg);
}

static void set_rtc_alarm(void)
{
	u32_t now = rtc_read(rtc_dev);
	u32_t alarm = now + (RTC_ALARM_SECOND * (TIMEOUT - 1));

	rtc_set_alarm(rtc_dev, alarm);

	/* Wait a few ticks to ensure the 'Counter Match Register' was loaded
	 * with the 'alarm' value.
	 * Refer to the documentation in qm_rtc.h for more details.
	 */
	while (rtc_read(rtc_dev) < now + 5)
		;
}
#elif (CONFIG_COUNTER)
static struct device *counter_dev;

static void setup_counter(void)
{
	volatile u32_t delay = 0;

	counter_dev = device_get_binding("AON_TIMER");

	if (!counter_dev) {
		printk("Timer device not found\n");
		return;
	}

	counter_start(counter_dev);

	/* The AON timer runs from the RTC clock at 32KHz (rather than
	 * the system clock which is 32MHz) so we need to spin for a few cycles
	 * to allow the register change to propagate.
	 */
	for (delay = 5000; delay--;) {
	}
}

static void set_counter_alarm(void)
{
	u32_t timer_initial_value = (RTC_ALARM_SECOND * (TIMEOUT - 1));

	if (counter_set_alarm(counter_dev, NULL,
			      timer_initial_value, NULL)
			      != 0) {
		printk("Periodic Timer was not started yet\n");
	}
}
#elif (CONFIG_GPIO_QMSI_1)
static struct device *gpio_dev;
#define GPIO_INTERRUPT_PIN 4

static void setup_aon_gpio(void)
{
	gpio_dev = device_get_binding("GPIO_1");
	if (!gpio_dev) {
		printk("gpio device not found.\n");
		return;
	}

	gpio_pin_configure(gpio_dev, GPIO_INTERRUPT_PIN,
			   GPIO_DIR_IN | GPIO_INT | GPIO_INT_EDGE |
			   GPIO_INT_ACTIVE_LOW | GPIO_INT_DEBOUNCE);
}
#elif (CONFIG_AIO_COMPARATOR)
static struct device *cmp_dev;
#define CMP_INTERRUPT_PIN 13

static void setup_aon_comparator(void)
{
	volatile u32_t delay = 0;

	cmp_dev = device_get_binding("AIO_CMP_0");
	if (!cmp_dev) {
		printk("comparator device not found.\n");
		return;
	}

	/* Wait for the comparator to be grounded. */
	printk("USER_ACTION: Ground the comparator pin.\n");
	for (delay = 0; delay < 5000000; delay++) {
	}

	aio_cmp_configure(cmp_dev, CMP_INTERRUPT_PIN,
			  AIO_CMP_POL_RISE, 0,
			  NULL, NULL);

	printk("USER_ACTION: Set the comparator pin to 3.3V/1.8V.\n");
}
#endif

static void setup_wake_event(void)
{
#if (CONFIG_RTC)
	set_rtc_alarm();
#elif (CONFIG_COUNTER)
	set_counter_alarm();
#elif (CONFIG_GPIO_QMSI_1)
	printk("USER_ACTION: Press AON_GPIO 4.\n");
#elif (CONFIG_AIO_COMPARATOR)
	setup_aon_comparator();
#endif
}

static void do_soc_sleep(enum power_states state)
{
	int woken_up = 0;
	int i, devices_retval[suspend_device_count];

	setup_wake_event();

	for (i = suspend_device_count - 1; i >= 0; i--) {
		devices_retval[i] = device_set_power_state(suspend_devices[i],
						DEVICE_PM_SUSPEND_STATE);
	}

	_sys_soc_set_power_state(state);

	/*
	 * Before enabling the interrupts, check the wake source
	 * as it will get cleared after.
	 */
#if (CONFIG_RTC)
	woken_up = rtc_get_pending_int(rtc_dev);
	const char source[] = "RTC";
#elif (CONFIG_COUNTER)
	woken_up = counter_get_pending_int(counter_dev);
	const char source[] = "counter";
#elif (CONFIG_GPIO_QMSI_1)
	woken_up = gpio_get_pending_int(gpio_dev);
	const char source[] = "GPIO";
#elif (CONFIG_AIO_COMPARATOR)
	woken_up = aio_cmp_get_pending_int(cmp_dev);
	const char source[] = "AON compare";
#endif

	for (i = 0; i < suspend_device_count; i++) {
		if (!devices_retval[i]) {
			device_set_power_state(suspend_devices[i],
						DEVICE_PM_ACTIVE_STATE);
		}
	}

	if (woken_up) {
		printk("Woke up with %s (pin:%x)\n", source, woken_up);
	}
}

int _sys_soc_suspend(s32_t ticks)
{
	enum power_states state;
	int pm_operation = 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;
	}

	state = get_next_state();

	printk("Entering %s state\n", state_to_string(state));

	switch (state) {
	case SYS_POWER_STATE_CPU_LPS:
	case SYS_POWER_STATE_CPU_LPS_1:
	case SYS_POWER_STATE_CPU_LPS_2:
		/*
		 * A wake event is needed in the following cases:
		 *
		 * On Quark SE C1000 x86:
		 * - SYS_POWER_STATE_CPU_LPS:
		 *   The PIC timer is gated and cannot wake the core from
		 *   that state.
		 *
		 * - SYS_POWER_STATE_CPU_LPS_1:
		 *   If the ARC enables LPSS, the PIC timer will
		 *   not wake us up from SYS_POWER_STATE_CPU_LPS_1
		 *   which is mapped to C2.
		 *
		 *   As the ARC enables LPSS, it should as well take care of
		 *   setting up the relevant wake event or communicate
		 *   to the x86 that information.
		 *
		 * On Quark SE C1000 ARC:
		 * - SYS_POWER_STATE_CPU_LPS:
		 *   The ARC timer is gated and cannot wake the core from
		 *   that state.
		 *
		 * - SYS_POWER_STATE_CPU_LPS_1:
		 *   The ARC timer is gated and cannot wake the core from
		 *   that state.
		 */
		setup_wake_event();
		pm_operation = SYS_PM_LOW_POWER_STATE;
		_sys_soc_set_power_state(state);
		break;
	case SYS_POWER_STATE_DEEP_SLEEP:
	case SYS_POWER_STATE_DEEP_SLEEP_1:
		/* Don't need pm idle exit notification */
		_sys_soc_pm_idle_exit_notification_disable();

		pm_operation = SYS_PM_DEEP_SLEEP;
		do_soc_sleep(state);
		break;
	default:
		printk("State not supported\n");
		break;
	}

	if (pm_operation != SYS_PM_NOT_HANDLED) {
		if (!post_ops_done) {
			post_ops_done = 1;
			printk("Exiting %s state\n", state_to_string(state));
			_sys_soc_power_state_post_ops(state);
		}
	}

	return pm_operation;
}

void _sys_soc_resume(void)
{
	enum power_states state = states_list[current_state];

	switch (state) {
	case SYS_POWER_STATE_CPU_LPS:
	case SYS_POWER_STATE_CPU_LPS_1:
	case SYS_POWER_STATE_CPU_LPS_2:
		if (!post_ops_done) {
			post_ops_done = 1;
			printk("Exiting %s state\n", state_to_string(state));
			_sys_soc_power_state_post_ops(state);
		}
		break;
	case SYS_POWER_STATE_DEEP_SLEEP:
	case SYS_POWER_STATE_DEEP_SLEEP_1:
		/* Do not perform post_ops in _sys_soc_resume for deep sleep.
		 * This would make the application task run without the full
		 * context restored.
		 */
		break;
	default:
		break;
	}
}

static void build_suspend_device_list(void)
{
	int i, devcount;
	struct device *devices;

	device_list_get(&devices, &devcount);
	if (devcount > MAX_SUSPEND_DEVICE_COUNT) {
		printk("Error: List of devices exceeds what we can track "
		       "for suspend. Built: %d, Max: %d\n",
		       devcount, MAX_SUSPEND_DEVICE_COUNT);
		return;
	}

#if (CONFIG_X86)
	suspend_device_count = 3;
	for (i = 0; i < devcount; i++) {
		if (!strcmp(devices[i].config->name, "loapic")) {
			suspend_devices[0] = &devices[i];
		} else if (!strcmp(devices[i].config->name, "ioapic")) {
			suspend_devices[1] = &devices[i];
		} else if (!strcmp(devices[i].config->name,
				 CONFIG_UART_CONSOLE_ON_DEV_NAME)) {
			suspend_devices[2] = &devices[i];
		} else {
			suspend_devices[suspend_device_count++] = &devices[i];
		}
	}
#elif (CONFIG_ARC)
	suspend_device_count = 2;
	for (i = 0; i < devcount; i++) {
		if (!strcmp(devices[i].config->name, "arc_v2_irq_unit")) {
			suspend_devices[0] = &devices[i];
		} else if (!strcmp(devices[i].config->name, "sys_clock")) {
			suspend_devices[1] = &devices[i];
		} else {
			suspend_devices[suspend_device_count++] = &devices[i];
		}
	}
#endif
}

void main(void)
{
	printk("Quark SE: Power Management sample application\n");

#if (CONFIG_RTC)
	setup_rtc();
#elif (CONFIG_COUNTER)
	setup_counter();
#elif (CONFIG_GPIO_QMSI_1)
	setup_aon_gpio();
#endif

	build_suspend_device_list();

#ifdef CONFIG_SOC_WATCH
	/* Start the event monitoring thread */
	soc_watch_logger_thread_start();
#endif

	/* All our application does is putting the task to sleep so the kernel
	 * triggers the suspend operation.
	 */
	while (1) {
		k_sleep(TIMEOUT * 1000);
		printk("Back to the application\n");
	}
}