1/*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2024 Jessica Clarke <jrtc27@FreeBSD.org>
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 * SUCH DAMAGE.
26 */
27
28#include <sys/param.h>
29
30#include <assert.h>
31#include <limits.h>
32#include <pthread.h>
33#include <stdlib.h>
34#include <time.h>
35
36#include "config.h"
37#include "mevent.h"
38#include "rtc_pl031.h"
39
40#define	RTCDR		0x000
41#define	RTCMR		0x004
42#define	RTCLR		0x008
43#define	RTCCR		0x00C
44#define	RTCIMSC		0x010
45#define	RTCRIS		0x014
46#define	RTCMIS		0x018
47#define	RTCICR		0x01C
48
49#define	RTCPeriphID0	0xFE0
50#define	RTCPeriphID1	0xFE4
51#define	RTCPeriphID2	0xFE8
52#define	RTCPeriphID3	0xFEC
53#define	 _RTCPeriphID_VAL	0x00141031
54#define	 RTCPeriphID_VAL(_n)	((_RTCPeriphID_VAL >> (8 * (_n))) & 0xff)
55
56#define	RTCCellID0	0xFF0
57#define	RTCCellID1	0xFF4
58#define	RTCCellID2	0xFF8
59#define	RTCCellID3	0xFFC
60#define	 _RTCCellID_VAL		0xb105f00d
61#define	 RTCCellID_VAL(_n)	((_RTCCellID_VAL >> (8 * (_n))) & 0xff)
62
63struct rtc_pl031_softc {
64	pthread_mutex_t		mtx;
65
66	time_t			last_tick;
67	uint32_t		dr;
68	uint32_t		mr;
69	uint32_t		lr;
70	uint8_t			imsc;
71	uint8_t			ris;
72	uint8_t			prev_mis;
73
74	struct mevent		*mevp;
75
76	void			*arg;
77	rtc_pl031_intr_func_t	intr_assert;
78	rtc_pl031_intr_func_t	intr_deassert;
79};
80
81static void	rtc_pl031_callback(int fd, enum ev_type type, void *param);
82
83/*
84 * Returns the current RTC time as number of seconds since 00:00:00 Jan 1, 1970
85 */
86static time_t
87rtc_pl031_time(void)
88{
89	struct tm tm;
90	time_t t;
91
92	time(&t);
93	if (get_config_bool_default("rtc.use_localtime", false)) {
94		localtime_r(&t, &tm);
95		t = timegm(&tm);
96	}
97	return (t);
98}
99
100static void
101rtc_pl031_update_mis(struct rtc_pl031_softc *sc)
102{
103	uint8_t mis;
104
105	mis = sc->ris & sc->imsc;
106	if (mis == sc->prev_mis)
107		return;
108
109	sc->prev_mis = mis;
110	if (mis)
111		(*sc->intr_assert)(sc->arg);
112	else
113		(*sc->intr_deassert)(sc->arg);
114}
115
116static uint64_t
117rtc_pl031_next_match_ticks(struct rtc_pl031_softc *sc)
118{
119	uint32_t ticks;
120
121	ticks = sc->mr - sc->dr;
122	if (ticks == 0)
123		return ((uint64_t)1 << 32);
124
125	return (ticks);
126}
127
128static int
129rtc_pl031_next_timer_msecs(struct rtc_pl031_softc *sc)
130{
131	uint64_t ticks;
132
133	ticks = rtc_pl031_next_match_ticks(sc);
134	return (MIN(ticks * 1000, INT_MAX));
135}
136
137static void
138rtc_pl031_update_timer(struct rtc_pl031_softc *sc)
139{
140	mevent_timer_update(sc->mevp, rtc_pl031_next_timer_msecs(sc));
141}
142
143static void
144rtc_pl031_tick(struct rtc_pl031_softc *sc, bool from_timer)
145{
146	bool match;
147	time_t now, ticks;
148
149	now = rtc_pl031_time();
150	ticks = now - sc->last_tick;
151	match = ticks >= 0 &&
152	    (uint64_t)ticks >= rtc_pl031_next_match_ticks(sc);
153	sc->dr += ticks;
154	sc->last_tick = now;
155
156	if (match) {
157		sc->ris = 1;
158		rtc_pl031_update_mis(sc);
159	}
160
161	if (match || from_timer || ticks < 0)
162		rtc_pl031_update_timer(sc);
163}
164
165static void
166rtc_pl031_callback(int fd __unused, enum ev_type type __unused, void *param)
167{
168	struct rtc_pl031_softc *sc = param;
169
170	pthread_mutex_lock(&sc->mtx);
171	rtc_pl031_tick(sc, true);
172	pthread_mutex_unlock(&sc->mtx);
173}
174
175void
176rtc_pl031_write(struct rtc_pl031_softc *sc, int offset, uint32_t value)
177{
178	pthread_mutex_lock(&sc->mtx);
179	rtc_pl031_tick(sc, false);
180	switch (offset) {
181	case RTCMR:
182		sc->mr = value;
183		rtc_pl031_update_timer(sc);
184		break;
185	case RTCLR:
186		sc->lr = value;
187		sc->dr = sc->lr;
188		rtc_pl031_update_timer(sc);
189		break;
190	case RTCIMSC:
191		sc->imsc = value & 1;
192		rtc_pl031_update_mis(sc);
193		break;
194	case RTCICR:
195		sc->ris &= ~value;
196		rtc_pl031_update_mis(sc);
197		break;
198	default:
199		/* Ignore writes to read-only/unassigned/ID registers */
200		break;
201	}
202	pthread_mutex_unlock(&sc->mtx);
203}
204
205uint32_t
206rtc_pl031_read(struct rtc_pl031_softc *sc, int offset)
207{
208	uint32_t reg;
209
210	pthread_mutex_lock(&sc->mtx);
211	rtc_pl031_tick(sc, false);
212	switch (offset) {
213	case RTCDR:
214		reg = sc->dr;
215		break;
216	case RTCMR:
217		reg = sc->mr;
218		break;
219	case RTCLR:
220		reg = sc->lr;
221		break;
222	case RTCCR:
223		/* RTC enabled from reset */
224		reg = 1;
225		break;
226	case RTCIMSC:
227		reg = sc->imsc;
228		break;
229	case RTCRIS:
230		reg = sc->ris;
231		break;
232	case RTCMIS:
233		reg = sc->ris & sc->imsc;
234		break;
235	case RTCPeriphID0:
236	case RTCPeriphID1:
237	case RTCPeriphID2:
238	case RTCPeriphID3:
239		reg = RTCPeriphID_VAL(offset - RTCPeriphID0);
240		break;
241	case RTCCellID0:
242	case RTCCellID1:
243	case RTCCellID2:
244	case RTCCellID3:
245		reg = RTCCellID_VAL(offset - RTCCellID0);
246		break;
247	default:
248		/* Return 0 in reads from unasigned registers */
249		reg = 0;
250		break;
251	}
252	pthread_mutex_unlock(&sc->mtx);
253
254	return (reg);
255}
256
257struct rtc_pl031_softc *
258rtc_pl031_init(rtc_pl031_intr_func_t intr_assert,
259    rtc_pl031_intr_func_t intr_deassert, void *arg)
260{
261	struct rtc_pl031_softc *sc;
262	time_t now;
263
264	sc = calloc(1, sizeof(struct rtc_pl031_softc));
265
266	pthread_mutex_init(&sc->mtx, NULL);
267
268	now = rtc_pl031_time();
269	sc->dr = now;
270	sc->last_tick = now;
271	sc->arg = arg;
272	sc->intr_assert = intr_assert;
273	sc->intr_deassert = intr_deassert;
274
275	sc->mevp = mevent_add(rtc_pl031_next_timer_msecs(sc), EVF_TIMER,
276	    rtc_pl031_callback, sc);
277
278	return (sc);
279}
280