summaryrefslogtreecommitdiff
path: root/chip/stm32/clock-f.c
blob: 1a77d8ad6040c77c5ea81a8645fe979abc6f1670 (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
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
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
/* Copyright 2016 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.
 */

/* Clocks and power management settings */

#include "chipset.h"
#include "clock.h"
#include "clock-f.h"
#include "common.h"
#include "console.h"
#include "cpu.h"
#include "hooks.h"
#include "host_command.h"
#include "hwtimer.h"
#include "registers.h"
#include "rtc.h"
#include "system.h"
#include "task.h"
#include "timer.h"
#include "util.h"

/* Console output macros */
#define CPUTS(outstr) cputs(CC_CLOCK, outstr)
#define CPRINTS(format, args...) cprints(CC_CLOCK, format, ## args)

/* Convert decimal to BCD */
static uint8_t u8_to_bcd(uint8_t val)
{
	/* Fast division by 10 (when lacking HW div) */
	uint32_t quot = ((uint32_t)val * 0xCCCD) >> 19;
	uint32_t rem = val - quot * 10;

	return rem | (quot << 4);
}

/* Convert between RTC regs in BCD and seconds */
static uint32_t rtc_tr_to_sec(uint32_t rtc_tr)
{
	uint32_t sec;

	/* convert the hours field */
	sec = (((rtc_tr & 0x300000) >> 20) * 10 +
	       ((rtc_tr & 0xf0000) >> 16)) * 3600;
	/* convert the minutes field */
	sec += (((rtc_tr & 0x7000) >> 12) * 10 + ((rtc_tr & 0xf00) >> 8)) * 60;
	/* convert the seconds field */
	sec += ((rtc_tr & 0x70) >> 4) * 10 + (rtc_tr & 0xf);
	return sec;
}

static uint32_t sec_to_rtc_tr(uint32_t sec)
{
	uint32_t rtc_tr;
	uint8_t hour;
	uint8_t min;

	sec %= SECS_PER_DAY;
	/* convert the hours field */
	hour = sec / 3600;
	rtc_tr = u8_to_bcd(hour) << 16;
	/* convert the minutes field */
	sec -= hour * 3600;
	min = sec / 60;
	rtc_tr |= u8_to_bcd(min) << 8;
	/* convert the seconds field */
	sec -= min * 60;
	rtc_tr |= u8_to_bcd(sec);

	return rtc_tr;
}

/* Register setup before RTC alarm is allowed for update */
static void pre_work_set_rtc_alarm(void)
{
	rtc_unlock_regs();

	/* Make sure alarm is disabled */
	STM32_RTC_CR &= ~STM32_RTC_CR_ALRAE;
	while (!(STM32_RTC_ISR & STM32_RTC_ISR_ALRAWF))
		;
	STM32_RTC_ISR &= ~STM32_RTC_ISR_ALRAF;
}

/* Register setup after RTC alarm is updated */
static void post_work_set_rtc_alarm(void)
{
	STM32_EXTI_PR = EXTI_RTC_ALR_EVENT;

	/* Enable alarm and alarm interrupt */
	STM32_EXTI_IMR |= EXTI_RTC_ALR_EVENT;
	STM32_RTC_CR |= STM32_RTC_CR_ALRAE;

	rtc_lock_regs();
}

#ifdef CONFIG_HOSTCMD_RTC
static struct wake_time host_wake_time;

int is_host_wake_alarm_expired(timestamp_t ts)
{
	return host_wake_time.ts.val &&
	       timestamp_expired(host_wake_time.ts, &ts);
}

void restore_host_wake_alarm(void)
{
	if (!host_wake_time.ts.val)
		return;

	pre_work_set_rtc_alarm();

	/* Set alarm time */
	STM32_RTC_ALRMAR = host_wake_time.rtc_alrmar;

	post_work_set_rtc_alarm();
}

static uint32_t rtc_dr_to_sec(uint32_t rtc_dr)
{
	struct calendar_date time;
	uint32_t sec;

	time.year = (((rtc_dr & 0xf00000) >> 20) * 10 +
		    ((rtc_dr & 0xf0000) >> 16));
	time.month = (((rtc_dr & 0x1000) >> 12) * 10 +
		     ((rtc_dr & 0xf00) >> 8));
	time.day = ((rtc_dr & 0x30) >> 4) * 10 + (rtc_dr & 0xf);

	sec = date_to_sec(time);

	return sec;
}

static uint32_t sec_to_rtc_dr(uint32_t sec)
{
	struct calendar_date time;
	uint32_t rtc_dr;

	time = sec_to_date(sec);

	rtc_dr = u8_to_bcd(time.year) << 16;
	rtc_dr |= u8_to_bcd(time.month) << 8;
	rtc_dr |= u8_to_bcd(time.day);

	return rtc_dr;
}
#endif

uint32_t rtc_to_sec(const struct rtc_time_reg *rtc)
{
	uint32_t sec = 0;
#ifdef CONFIG_HOSTCMD_RTC
	sec = rtc_dr_to_sec(rtc->rtc_dr);
#endif
	return sec + (rtcss_to_us(rtc->rtc_ssr) / SECOND) +
	       rtc_tr_to_sec(rtc->rtc_tr);
}

void sec_to_rtc(uint32_t sec, struct rtc_time_reg *rtc)
{
	rtc->rtc_dr = 0;
#ifdef CONFIG_HOSTCMD_RTC
	rtc->rtc_dr = sec_to_rtc_dr(sec);
#endif
	rtc->rtc_tr = sec_to_rtc_tr(sec);
	rtc->rtc_ssr = 0;
}

/* Return sub-10-sec time diff between two rtc readings
 *
 * Note: this function assumes rtc0 was sampled before rtc1.
 * Additionally, this function only looks at the difference mod 10
 * seconds.
 */
uint32_t get_rtc_diff(const struct rtc_time_reg *rtc0,
		      const struct rtc_time_reg *rtc1)
{
	uint32_t rtc0_val, rtc1_val, diff;

	rtc0_val = (rtc0->rtc_tr & 0xF) * SECOND + rtcss_to_us(rtc0->rtc_ssr);
	rtc1_val = (rtc1->rtc_tr & 0xF) * SECOND + rtcss_to_us(rtc1->rtc_ssr);
	diff = rtc1_val;
	if (rtc1_val < rtc0_val) {
		/* rtc_ssr has wrapped, since we assume rtc0 < rtc1, add
		 * 10 seconds to get the correct value
		 */
		diff += 10 * SECOND;
	}
	diff -= rtc0_val;
	return diff;
}

void rtc_read(struct rtc_time_reg *rtc)
{
	/*
	 * Read current time synchronously. Each register must be read
	 * twice with identical values because glitches may occur for reads
	 * close to the RTCCLK edge.
	 */
	do {
		rtc->rtc_dr = STM32_RTC_DR;

		do {
			rtc->rtc_tr = STM32_RTC_TR;

			do {
				rtc->rtc_ssr = STM32_RTC_SSR;
			} while (rtc->rtc_ssr != STM32_RTC_SSR);

		} while (rtc->rtc_tr != STM32_RTC_TR);

	} while (rtc->rtc_dr != STM32_RTC_DR);
}

void set_rtc_alarm(uint32_t delay_s, uint32_t delay_us,
		   struct rtc_time_reg *rtc, uint8_t save_alarm)
{
	uint32_t alarm_sec = 0;
	uint32_t alarm_us = 0;

	if (delay_s == EC_RTC_ALARM_CLEAR && !delay_us) {
		reset_rtc_alarm(rtc);
		return;
	}

	/* Alarm timeout must be within 1 day (86400 seconds) */
	ASSERT((delay_s + delay_us / SECOND) < SECS_PER_DAY);

	pre_work_set_rtc_alarm();
	rtc_read(rtc);

	/* Calculate alarm time */
	alarm_sec = rtc_tr_to_sec(rtc->rtc_tr) + delay_s;

	if (delay_us) {
		alarm_us = rtcss_to_us(rtc->rtc_ssr) + delay_us;
		alarm_sec = alarm_sec + alarm_us / SECOND;
		alarm_us = alarm_us % SECOND;
	}

	/*
	 * If seconds is greater than 1 day, subtract by 1 day to deal with
	 * 24-hour rollover.
	 */
	if (alarm_sec >= SECS_PER_DAY)
		alarm_sec -= SECS_PER_DAY;

	/*
	 * Set alarm time in seconds and check for match on
	 * hours, minutes, and seconds.
	 */
	STM32_RTC_ALRMAR = sec_to_rtc_tr(alarm_sec) | 0xc0000000;

	/*
	 * Set alarm time in subseconds and check for match on subseconds.
	 * If the caller doesn't specify subsecond delay (e.g. host command),
	 * just align the alarm time to second.
	 */
	STM32_RTC_ALRMASSR = delay_us ?
			     (us_to_rtcss(alarm_us) | 0x0f000000) : 0;

#ifdef CONFIG_HOSTCMD_RTC
	/*
	 * If alarm is set by the host, preserve the wake time timestamp
	 * and alarm registers.
	 */
	if (save_alarm) {
		host_wake_time.ts.val = delay_s * SECOND + get_time().val;
		host_wake_time.rtc_alrmar = STM32_RTC_ALRMAR;
	}
#endif
	post_work_set_rtc_alarm();
}

uint32_t get_rtc_alarm(void)
{
	struct rtc_time_reg now;
	uint32_t now_sec;
	uint32_t alarm_sec;

	if (!(STM32_RTC_CR & STM32_RTC_CR_ALRAE))
		return 0;

	rtc_read(&now);

	now_sec = rtc_tr_to_sec(now.rtc_tr);
	alarm_sec = rtc_tr_to_sec(STM32_RTC_ALRMAR & 0x3fffff);

	return ((alarm_sec < now_sec) ? SECS_PER_DAY : 0) +
	       (alarm_sec - now_sec);
}

void reset_rtc_alarm(struct rtc_time_reg *rtc)
{
	rtc_unlock_regs();

	/* Disable alarm */
	STM32_RTC_CR &= ~STM32_RTC_CR_ALRAE;
	STM32_RTC_ISR &= ~STM32_RTC_ISR_ALRAF;

	/* Disable RTC alarm interrupt */
	STM32_EXTI_IMR &= ~EXTI_RTC_ALR_EVENT;
	STM32_EXTI_PR = EXTI_RTC_ALR_EVENT;

	/* Clear the pending RTC alarm IRQ in NVIC */
	task_clear_pending_irq(STM32_IRQ_RTC_ALARM);

	/* Read current time */
	rtc_read(rtc);

	rtc_lock_regs();
}

#ifdef CONFIG_HOSTCMD_RTC
static void set_rtc_host_event(void)
{
	host_set_single_event(EC_HOST_EVENT_RTC);
}
DECLARE_DEFERRED(set_rtc_host_event);
#endif

test_mockable
void __rtc_alarm_irq(void)
{
	struct rtc_time_reg rtc;
	reset_rtc_alarm(&rtc);

#ifdef CONFIG_HOSTCMD_RTC
	/* Wake up the host if there is a saved rtc wake alarm. */
	if (host_wake_time.ts.val) {
		host_wake_time.ts.val = 0;
		hook_call_deferred(&set_rtc_host_event_data, 0);
	}
#endif
}
DECLARE_IRQ(STM32_IRQ_RTC_ALARM, __rtc_alarm_irq, 1);

__attribute__((weak))
int clock_get_timer_freq(void)
{
	return clock_get_freq();
}

void clock_init(void)
{
	/*
	 * The initial state :
	 *  SYSCLK from HSI (=8MHz), no divider on AHB, APB1, APB2
	 *  PLL unlocked, RTC enabled on LSE
	 */

	/*
	 * put 1 Wait-State for flash access to ensure proper reads at 48Mhz
	 * and enable prefetch buffer.
	 */
	STM32_FLASH_ACR = STM32_FLASH_ACR_LATENCY | STM32_FLASH_ACR_PRFTEN;

#ifdef CHIP_FAMILY_STM32F4
	/* Enable data and instruction cache. */
	STM32_FLASH_ACR |= STM32_FLASH_ACR_DCEN | STM32_FLASH_ACR_ICEN;
#endif

	config_hispeed_clock();

	rtc_init();
}

#ifdef CHIP_FAMILY_STM32F4
void reset_flash_cache(void)
{
	/* Disable data and instruction cache. */
	STM32_FLASH_ACR &= ~(STM32_FLASH_ACR_DCEN | STM32_FLASH_ACR_ICEN);

	/* Reset data and instruction cache */
	STM32_FLASH_ACR |= STM32_FLASH_ACR_DCRST | STM32_FLASH_ACR_ICRST;
}
DECLARE_HOOK(HOOK_SYSJUMP, reset_flash_cache, HOOK_PRIO_DEFAULT);
#endif

/*****************************************************************************/
/* Console commands */

void print_system_rtc(enum console_channel ch)
{
	uint32_t sec;
	struct rtc_time_reg rtc;

	rtc_read(&rtc);
	sec = rtc_to_sec(&rtc);

	cprintf(ch, "RTC: 0x%08x (%d.00 s)\n", sec, sec);
}

#ifdef CONFIG_CMD_RTC
static int command_system_rtc(int argc, char **argv)
{
	char *e;
	uint32_t t;

	if (argc == 3 && !strcasecmp(argv[1], "set")) {
		t = strtoi(argv[2], &e, 0);
		if (*e)
			return EC_ERROR_PARAM2;
		rtc_set(t);
	} else if (argc > 1)
		return EC_ERROR_INVAL;

	print_system_rtc(CC_COMMAND);

	return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(rtc, command_system_rtc,
		"[set <seconds>]",
		"Get/set real-time clock");

#ifdef CONFIG_CMD_RTC_ALARM
static int command_rtc_alarm_test(int argc, char **argv)
{
	int s = 1, us = 0;
	struct rtc_time_reg rtc;
	char *e;

	ccprintf("Setting RTC alarm\n");

	if (argc > 1) {
		s = strtoi(argv[1], &e, 10);
		if (*e)
			return EC_ERROR_PARAM1;

	}
	if (argc > 2) {
		us = strtoi(argv[2], &e, 10);
		if (*e)
			return EC_ERROR_PARAM2;
	}

	set_rtc_alarm(s, us, &rtc, 0);
	return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(rtc_alarm, command_rtc_alarm_test,
			"[seconds [microseconds]]",
			"Test alarm");
#endif /* CONFIG_CMD_RTC_ALARM */
#endif /* CONFIG_CMD_RTC */

/*****************************************************************************/
/* Host commands */

#ifdef CONFIG_HOSTCMD_RTC
static enum ec_status system_rtc_get_value(struct host_cmd_handler_args *args)
{
	struct ec_response_rtc *r = args->response;
	struct rtc_time_reg rtc;

	rtc_read(&rtc);
	r->time = rtc_to_sec(&rtc);
	args->response_size = sizeof(*r);

	return EC_RES_SUCCESS;
}
DECLARE_HOST_COMMAND(EC_CMD_RTC_GET_VALUE,
		system_rtc_get_value,
		EC_VER_MASK(0));

static enum ec_status system_rtc_set_value(struct host_cmd_handler_args *args)
{
	const struct ec_params_rtc *p = args->params;

	rtc_set(p->time);
	return EC_RES_SUCCESS;
}
DECLARE_HOST_COMMAND(EC_CMD_RTC_SET_VALUE,
		system_rtc_set_value,
		EC_VER_MASK(0));

static enum ec_status system_rtc_set_alarm(struct host_cmd_handler_args *args)
{
	struct rtc_time_reg rtc;
	const struct ec_params_rtc *p = args->params;

	/* Alarm timeout must be within 1 day (86400 seconds) */
	if (p->time >= SECS_PER_DAY)
		return EC_RES_INVALID_PARAM;

	set_rtc_alarm(p->time, 0, &rtc, 1);
	return EC_RES_SUCCESS;
}
DECLARE_HOST_COMMAND(EC_CMD_RTC_SET_ALARM,
		system_rtc_set_alarm,
		EC_VER_MASK(0));

static enum ec_status system_rtc_get_alarm(struct host_cmd_handler_args *args)
{
	struct ec_response_rtc *r = args->response;

	r->time = get_rtc_alarm();
	args->response_size = sizeof(*r);

	return EC_RES_SUCCESS;
}
DECLARE_HOST_COMMAND(EC_CMD_RTC_GET_ALARM,
		system_rtc_get_alarm,
		EC_VER_MASK(0));

#endif /* CONFIG_HOSTCMD_RTC */