1/*	$OpenBSD: sxirtc.c,v 1.9 2024/01/27 11:22:16 kettenis Exp $	*/
2/*
3 * Copyright (c) 2008 Mark Kettenis
4 * Copyright (c) 2013 Artturi Alm
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19#include <sys/param.h>
20#include <sys/device.h>
21#include <sys/malloc.h>
22#include <sys/systm.h>
23
24#include <dev/clock_subr.h>
25
26#include <machine/bus.h>
27#include <machine/fdt.h>
28
29#include <dev/fdt/sunxireg.h>
30
31#include <dev/ofw/openfirm.h>
32#include <dev/ofw/fdt.h>
33#include <dev/ofw/ofw_clock.h>
34
35#define SXIRTC_LOSC_CTRL		0x00
36#define  SXIRTC_LOSC_CTRL_KEY_FIELD	0x16aa0000
37#define  SXIRTC_LOSC_CTRL_SEL_EXT32K	0x00000001
38#define SXIRTC_YYMMDD_A10		0x04
39#define SXIRTC_HHMMSS_A10		0x08
40#define SXIRTC_YYMMDD_A31		0x10
41#define SXIRTC_HHMMSS_A31		0x14
42#define SXIRTC_LOSC_OUT_GATING		0x60
43
44#define LEAPYEAR(y)        \
45    (((y) % 4 == 0 &&    \
46    (y) % 100 != 0) ||    \
47    (y) % 400 == 0)
48
49struct sxirtc_softc {
50	struct device		sc_dev;
51	bus_space_tag_t		sc_iot;
52	bus_space_handle_t	sc_ioh;
53
54	struct clock_device	sc_cd;
55
56	bus_size_t		sc_yymmdd;
57	bus_size_t		sc_hhmmss;
58	uint32_t		base_year;
59	uint32_t		year_mask;
60	uint32_t		leap_shift;
61	int			linear_day;
62};
63
64int	sxirtc_match(struct device *, void *, void *);
65void	sxirtc_attach(struct device *, struct device *, void *);
66
67const struct cfattach sxirtc_ca = {
68	sizeof(struct sxirtc_softc), sxirtc_match, sxirtc_attach
69};
70
71struct cfdriver sxirtc_cd = {
72	NULL, "sxirtc", DV_DULL
73};
74
75uint32_t sxirtc_get_frequency(void *, uint32_t *);
76void	sxirtc_enable(void *, uint32_t *, int);
77int	sxirtc_gettime(todr_chip_handle_t, struct timeval *);
78int	sxirtc_settime(todr_chip_handle_t, struct timeval *);
79
80int
81sxirtc_match(struct device *parent, void *match, void *aux)
82{
83	struct fdt_attach_args *faa = aux;
84
85	return (OF_is_compatible(faa->fa_node, "allwinner,sun4i-a10-rtc") ||
86	    OF_is_compatible(faa->fa_node, "allwinner,sun7i-a20-rtc") ||
87	    OF_is_compatible(faa->fa_node, "allwinner,sun6i-a31-rtc") ||
88	    OF_is_compatible(faa->fa_node, "allwinner,sun8i-h3-rtc") ||
89	    OF_is_compatible(faa->fa_node, "allwinner,sun50i-h5-rtc") ||
90	    OF_is_compatible(faa->fa_node, "allwinner,sun50i-h616-rtc") ||
91	    OF_is_compatible(faa->fa_node, "allwinner,sun50i-r329-rtc"));
92}
93
94void
95sxirtc_attach(struct device *parent, struct device *self, void *aux)
96{
97	struct sxirtc_softc *sc = (struct sxirtc_softc *)self;
98	struct fdt_attach_args *faa = aux;
99	todr_chip_handle_t handle;
100
101	if (faa->fa_nreg < 1)
102		return;
103
104	handle = malloc(sizeof(struct todr_chip_handle), M_DEVBUF, M_NOWAIT);
105	if (handle == NULL)
106		panic("sxirtc_attach: couldn't allocate todr_handle");
107
108	sc->sc_iot = faa->fa_iot;
109	if (bus_space_map(sc->sc_iot, faa->fa_reg[0].addr,
110	    faa->fa_reg[0].size, 0, &sc->sc_ioh))
111		panic("sxirtc_attach: bus_space_map failed!");
112
113	if (OF_is_compatible(faa->fa_node, "allwinner,sun6i-a31-rtc") ||
114	    OF_is_compatible(faa->fa_node, "allwinner,sun8i-h3-rtc") ||
115	    OF_is_compatible(faa->fa_node, "allwinner,sun50i-h5-rtc") ||
116	    OF_is_compatible(faa->fa_node, "allwinner,sun50i-h616-rtc") ||
117	    OF_is_compatible(faa->fa_node, "allwinner,sun50i-r329-rtc")) {
118		sc->sc_yymmdd = SXIRTC_YYMMDD_A31;
119		sc->sc_hhmmss = SXIRTC_HHMMSS_A31;
120	} else {
121		sc->sc_yymmdd = SXIRTC_YYMMDD_A10;
122		sc->sc_hhmmss = SXIRTC_HHMMSS_A10;
123	}
124
125	if (OF_is_compatible(faa->fa_node, "allwinner,sun7i-a20-rtc")) {
126		sc->base_year = 1970;
127		sc->year_mask = 0xff;
128		sc->leap_shift = 24;
129	} else {
130		sc->base_year = 2010;
131		sc->year_mask = 0x3f;
132		sc->leap_shift = 22;
133	}
134
135	/*
136	 * Newer SoCs store the number of days since a fixed epoch
137	 * instead of YYMMDD.  Take this to be the number of days
138	 * since the Unix epoch since that is what Linux does.
139	 */
140	if (OF_is_compatible(faa->fa_node, "allwinner,sun50i-h616-rtc") ||
141	    OF_is_compatible(faa->fa_node, "allwinner,sun50i-r329-rtc")) {
142		sc->base_year = 1970;
143		sc->linear_day = 1;
144	}
145
146	if (OF_is_compatible(faa->fa_node, "allwinner,sun8i-h3-rtc") ||
147	    OF_is_compatible(faa->fa_node, "allwinner,sun50i-h5-rtc")) {
148		/* Switch to external oscillator. */
149		SXIWRITE4(sc, SXIRTC_LOSC_CTRL,
150		    SXIRTC_LOSC_CTRL_KEY_FIELD | SXIRTC_LOSC_CTRL_SEL_EXT32K);
151
152		sc->sc_cd.cd_node = faa->fa_node;
153		sc->sc_cd.cd_cookie = sc;
154		sc->sc_cd.cd_get_frequency = sxirtc_get_frequency;
155		sc->sc_cd.cd_enable = sxirtc_enable;
156		clock_register(&sc->sc_cd);
157	}
158
159	handle->cookie = self;
160	handle->todr_gettime = sxirtc_gettime;
161	handle->todr_settime = sxirtc_settime;
162	handle->bus_cookie = NULL;
163	handle->todr_setwen = NULL;
164	handle->todr_quality = 0;
165	todr_attach(handle);
166
167	printf("\n");
168}
169
170uint32_t
171sxirtc_get_frequency(void *cookie, uint32_t *cells)
172{
173	struct sxirtc_softc *sc = cookie;
174	uint32_t idx = cells[0];
175
176	switch (idx) {
177	case 0:			/* osc32k */
178	case 1:			/* osc32k-out */
179		return clock_get_frequency_idx(sc->sc_cd.cd_node, 0);
180	case 2:			/* iosc */
181		return 16000000;
182	}
183
184	printf("%s: 0x%08x\n", __func__, idx);
185	return 0;
186}
187
188void
189sxirtc_enable(void *cookie, uint32_t *cells, int on)
190{
191	struct sxirtc_softc *sc = cookie;
192	uint32_t idx = cells[0];
193
194	switch (idx) {
195	case 0:			/* osc32k */
196		break;
197	case 1:			/* osc32k-out */
198		if (on)
199			SXISET4(sc, SXIRTC_LOSC_OUT_GATING, 1);
200		else
201			SXICLR4(sc, SXIRTC_LOSC_OUT_GATING, 1);
202		break;
203	case 2:			/* iosc */
204		break;
205	default:
206		printf("%s: 0x%08x\n", __func__, idx);
207		break;
208	}
209}
210
211int
212sxirtc_gettime(todr_chip_handle_t handle, struct timeval *tv)
213{
214	struct sxirtc_softc *sc = (struct sxirtc_softc *)handle->cookie;
215	struct clock_ymdhms dt;
216	uint32_t reg;
217
218	reg = SXIREAD4(sc, sc->sc_yymmdd);
219	if (sc->linear_day) {
220		clock_secs_to_ymdhms(reg * SECDAY, &dt);
221	} else {
222		dt.dt_day = reg & 0x1f;
223		dt.dt_mon = reg >> 8 & 0x0f;
224		dt.dt_year = (reg >> 16 & sc->year_mask) + sc->base_year;
225	}
226
227	reg = SXIREAD4(sc, sc->sc_hhmmss);
228	dt.dt_sec = reg & 0x3f;
229	dt.dt_min = reg >> 8 & 0x3f;
230	dt.dt_hour = reg >> 16 & 0x1f;
231	dt.dt_wday = reg >> 29 & 0x07;
232
233	if (dt.dt_sec > 59 || dt.dt_min > 59 ||
234	    dt.dt_hour > 23 || dt.dt_wday > 6 ||
235	    dt.dt_day > 31 || dt.dt_day == 0 ||
236	    dt.dt_mon > 12 || dt.dt_mon == 0)
237		return 1;
238
239	/*
240	 * Reject the first year that can be represented by the clock.
241	 * This avoids reporting a bogus time if the RTC isn't battery
242	 * powered.
243	 */
244	if (dt.dt_year == sc->base_year)
245		return 1;
246
247	tv->tv_sec = clock_ymdhms_to_secs(&dt);
248	tv->tv_usec = 0;
249	return 0;
250}
251
252int
253sxirtc_settime(todr_chip_handle_t handle, struct timeval *tv)
254{
255	struct sxirtc_softc *sc = (struct sxirtc_softc *)handle->cookie;
256	struct clock_ymdhms dt;
257
258	clock_secs_to_ymdhms(tv->tv_sec, &dt);
259
260	if (dt.dt_sec > 59 || dt.dt_min > 59 ||
261	    dt.dt_hour > 23 || dt.dt_wday > 6 ||
262	    dt.dt_day > 31 || dt.dt_day == 0 ||
263	    dt.dt_mon > 12 || dt.dt_mon == 0)
264		return 1;
265
266	SXICMS4(sc, sc->sc_hhmmss, 0xe0000000 | 0x1f0000 | 0x3f00 | 0x3f,
267	    dt.dt_sec | (dt.dt_min << 8) | (dt.dt_hour << 16) |
268	    (dt.dt_wday << 29));
269
270	if (sc->linear_day) {
271		SXICMS4(sc, sc->sc_yymmdd, 0xffff, tv->tv_sec / SECDAY);
272	} else {
273		SXICMS4(sc, sc->sc_yymmdd, 0x00400000 | (sc->year_mask << 16) |
274		    0x0f00 | 0x1f, dt.dt_day | (dt.dt_mon << 8) |
275		    ((dt.dt_year - sc->base_year) << 16) |
276		    (LEAPYEAR(dt.dt_year) << sc->leap_shift));
277	}
278
279	return 0;
280}
281