summaryrefslogtreecommitdiffstats
path: root/bsps/arm/atsam/start/power-rtc.c
blob: 5d964aeb451be627aeed852dfb6331dc9610d8ea (plain) (blame)
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
/*
 * Copyright (c) 2016 embedded brains GmbH.  All rights reserved.
 *
 * The license and distribution terms for this file may be
 * found in the file LICENSE in this distribution or at
 * http://www.rtems.org/license/LICENSE.
 */

#include <bsp.h>
#include <bsp/power.h>
#include <bsp/irq.h>

#include <libchip/chip.h>

#define ATSAM_ENABLE_ALARM_INTERRUPT RTC_IER_ALREN

static void set_rtc_alarm_interrupt(uint32_t interval)
{
	Rtc *rtc = RTC;
	uint8_t hour;
	uint8_t minute;
	uint8_t second;
	uint32_t time;

	/* Clear current status register */
	RTC_ClearSCCR(rtc, 0x3F);

	RTC_GetTime(rtc, &hour, &minute, &second);

	time = UINT32_C(3600) * hour + UINT32_C(60) * minute + second;
	time = (time + interval) % (UINT32_C(24) * 3600);

	second = (uint8_t) (time % 60);
	minute = (uint8_t) ((time / 60) % 60);
	hour = (uint8_t) (time / 3600);

	if (interval < 60) {
		RTC_SetTimeAlarm(rtc, NULL, NULL, &second);
	} else if (interval < 3600) {
		RTC_SetTimeAlarm(rtc, NULL, &minute, &second);
	} else {
		RTC_SetTimeAlarm(rtc, &hour, &minute, &second);
	}

	RTC_EnableIt(rtc, ATSAM_ENABLE_ALARM_INTERRUPT);
}

static void rtc_interrupt_handler(void *arg)
{
	atsam_power_data_rtc_driver *rtc_data;

	rtc_data = (atsam_power_data_rtc_driver *)arg;
	set_rtc_alarm_interrupt(rtc_data->interval);
}

static void rtc_alarm_handler(void *arg)
{
	Rtc *rtc = RTC;
	rtems_status_code sc;

	/* Clear current status register */
	RTC_ClearSCCR(rtc, 0x3F);

	/* Switch off all RTC interrupts */
	RTC_DisableIt(rtc, 0x1F);

	/* Install RTC interrupt handler */
	sc = rtems_interrupt_handler_install(RTC_IRQn,
	    "RTC",
	    RTEMS_INTERRUPT_UNIQUE,
	    rtc_interrupt_handler,
	    arg
	);
	assert(sc == RTEMS_SUCCESSFUL);
}

static void set_time(void)
{
	rtems_time_of_day tod;
	rtems_status_code sc;

	atsam_rtc_get_time(&tod);
	sc = rtems_clock_set(&tod);
	assert(sc == RTEMS_SUCCESSFUL);
}

void atsam_power_handler_rtc_driver(
    const atsam_power_control *control,
    atsam_power_state state
)
{
	atsam_power_data_rtc_driver *rtc_data;
	rtems_interrupt_level level;
	Rtc *rtc = RTC;

	rtc_data = (atsam_power_data_rtc_driver *)control->data.arg;

	switch (state) {
		case ATSAM_POWER_ON:
			RTC_DisableIt(rtc, ATSAM_ENABLE_ALARM_INTERRUPT);
			set_time();
			break;
		case ATSAM_POWER_OFF:
			set_rtc_alarm_interrupt(rtc_data->interval);
			break;
		case ATSAM_POWER_INIT:
			/* Enable fast startup via RTC alarm */
			rtems_interrupt_disable(level);
			PMC->PMC_FSMR |= PMC_FSMR_RTCAL;
			rtems_interrupt_enable(level);

			rtc_alarm_handler(rtc_data);
			break;
		default:
			break;
	}
}