1/*	$OpenBSD: acrtc.c,v 1.6 2022/10/17 19:09:46 kettenis Exp $	*/
2/*
3 * Copyright (c) 2017 Mark Kettenis <kettenis@openbsd.org>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17
18#include <sys/param.h>
19#include <sys/systm.h>
20#include <sys/device.h>
21#include <sys/malloc.h>
22
23#include <dev/fdt/rsbvar.h>
24
25#include <dev/ofw/openfirm.h>
26#include <dev/ofw/ofw_clock.h>
27#include <dev/ofw/fdt.h>
28
29#include <dev/clock_subr.h>
30
31#define isleap(y) ((((y) % 4) == 0 && ((y) % 100) != 0) || ((y) % 400) == 0)
32
33#define CK32K_OUT_CTRL1			0xc1
34#define  CK32K_OUT_CTRL_PRE_DIV_MASK	(0x7 << 5)
35#define  CK32K_OUT_CTRL_PRE_DIV_32K	(0x7 << 5)
36#define  CK32K_OUT_CTRL_MUX_SEL_MASK	(1 << 4)
37#define  CK32K_OUT_CTRL_MUX_SEL_32K	(0 << 4)
38#define  CK32K_OUT_CTRL_POST_DIV_MASK	(0x7 << 1)
39#define  CK32K_OUT_CTRL_POST_DIV_32K	(0x0 << 1)
40#define  CK32K_OUT_CTRL_ENA		(1 << 0)
41#define RTC_CTRL			0xc7
42#define  RTC_CTRL_12H_24H_MODE		(1 << 0)
43#define RTC_SEC				0xc8
44#define  RTC_SEC_MASK			(0x7f << 0)
45#define RTC_MIN				0xc9
46#define  RTC_MIN_MASK			(0x7f << 0)
47#define RTC_HOU				0xca
48#define  RTC_HOU_MASK			(0x3f << 0)
49#define RTC_WEE				0xcb
50#define  RTC_WEE_MASK			(0x07 << 0)
51#define RTC_DAY				0xcc
52#define  RTC_DAY_MASK			(0x3f << 0)
53#define RTC_MON				0xcd
54#define  RTC_MON_MASK			(0x1f << 0)
55#define RTC_YEA				0xce
56#define  RTC_YEA_LEAP_YEAR		(1 << 15)
57#define  RTC_YEA_MASK			(0xff << 0)
58#define RTC_UPD_TRIG			0xcf
59#define  RTC_UPD_TRIG_UPDATE		(1 << 15)
60
61struct acrtc_softc {
62	struct device		sc_dev;
63	void			*sc_cookie;
64	uint16_t 		sc_rta;
65
66	struct todr_chip_handle	sc_todr;
67	struct clock_device	sc_cd;
68};
69
70int	acrtc_match(struct device *, void *, void *);
71void	acrtc_attach(struct device *, struct device *, void *);
72
73const struct cfattach acrtc_ca = {
74	sizeof(struct acrtc_softc), acrtc_match, acrtc_attach
75};
76
77struct cfdriver acrtc_cd = {
78	NULL, "acrtc", DV_DULL
79};
80
81int	acrtc_clock_read(struct acrtc_softc *, struct clock_ymdhms *);
82int	acrtc_clock_write(struct acrtc_softc *, struct clock_ymdhms *);
83int	acrtc_gettime(struct todr_chip_handle *, struct timeval *);
84int	acrtc_settime(struct todr_chip_handle *, struct timeval *);
85
86void	acrtc_ck32k_enable(void *, uint32_t *, int);
87
88int
89acrtc_match(struct device *parent, void *match, void *aux)
90{
91	struct rsb_attach_args *ra = aux;
92
93	if (strcmp(ra->ra_name, "x-powers,ac100") == 0)
94		return 1;
95	return 0;
96}
97
98void
99acrtc_attach(struct device *parent, struct device *self, void *aux)
100{
101	struct acrtc_softc *sc = (struct acrtc_softc *)self;
102	struct rsb_attach_args *ra = aux;
103	int node;
104
105	sc->sc_cookie = ra->ra_cookie;
106	sc->sc_rta = ra->ra_rta;
107
108	printf("\n");
109
110	sc->sc_todr.cookie = sc;
111	sc->sc_todr.todr_gettime = acrtc_gettime;
112	sc->sc_todr.todr_settime = acrtc_settime;
113	sc->sc_todr.todr_quality = 1000;
114	todr_attach(&sc->sc_todr);
115
116	node = OF_getnodebyname(ra->ra_node, "rtc");
117	if (node == 0)
118		return;
119
120	sc->sc_cd.cd_node = node;
121	sc->sc_cd.cd_cookie = sc;
122	sc->sc_cd.cd_enable = acrtc_ck32k_enable;
123	clock_register(&sc->sc_cd);
124}
125
126static inline uint16_t
127acrtc_read_reg(struct acrtc_softc *sc, uint8_t reg)
128{
129	return rsb_read_2(sc->sc_cookie, sc->sc_rta, reg);
130}
131
132static inline void
133acrtc_write_reg(struct acrtc_softc *sc, uint8_t reg, uint16_t value)
134{
135	rsb_write_2(sc->sc_cookie, sc->sc_rta, reg, value);
136}
137
138int
139acrtc_gettime(struct todr_chip_handle *handle, struct timeval *tv)
140{
141	struct acrtc_softc *sc = handle->cookie;
142	struct clock_ymdhms dt;
143	int error;
144
145	error = acrtc_clock_read(sc, &dt);
146	if (error)
147		return error;
148
149	if (dt.dt_sec > 59 || dt.dt_min > 59 || dt.dt_hour > 23 ||
150	    dt.dt_day > 31 || dt.dt_day == 0 ||
151	    dt.dt_mon > 12 || dt.dt_mon == 0 ||
152	    dt.dt_year < POSIX_BASE_YEAR)
153		return EINVAL;
154
155	tv->tv_sec = clock_ymdhms_to_secs(&dt);
156	tv->tv_usec = 0;
157	return 0;
158}
159
160int
161acrtc_settime(struct todr_chip_handle *handle, struct timeval *tv)
162{
163	struct acrtc_softc *sc = handle->cookie;
164	struct clock_ymdhms dt;
165
166	clock_secs_to_ymdhms(tv->tv_sec, &dt);
167
168	return acrtc_clock_write(sc, &dt);
169}
170
171int
172acrtc_clock_read(struct acrtc_softc *sc, struct clock_ymdhms *dt)
173{
174	uint16_t ctrl;
175
176	dt->dt_sec = FROMBCD(acrtc_read_reg(sc, RTC_SEC) & RTC_SEC_MASK);
177	dt->dt_min = FROMBCD(acrtc_read_reg(sc, RTC_MIN) & RTC_MIN_MASK);
178	dt->dt_hour = FROMBCD(acrtc_read_reg(sc, RTC_HOU) & RTC_HOU_MASK);
179	dt->dt_day = FROMBCD(acrtc_read_reg(sc, RTC_DAY) & RTC_DAY_MASK);
180	dt->dt_mon = FROMBCD(acrtc_read_reg(sc, RTC_MON) & RTC_MON_MASK);
181	dt->dt_year = FROMBCD(acrtc_read_reg(sc, RTC_YEA) & RTC_YEA_MASK);
182	dt->dt_year += 2000;
183
184#ifdef DEBUG
185	printf("%02d/%02d/%04d %02d:%02d:%0d\n", dt->dt_day, dt->dt_mon,
186	    dt->dt_year, dt->dt_hour, dt->dt_min, dt->dt_sec);
187#endif
188
189	/* Consider the time to be invalid if the clock is in 12H mode. */
190	ctrl = acrtc_read_reg(sc, RTC_CTRL);
191	if ((ctrl & RTC_CTRL_12H_24H_MODE) == 0)
192		return EINVAL;
193
194	return 0;
195}
196
197int
198acrtc_clock_write(struct acrtc_softc *sc, struct clock_ymdhms *dt)
199{
200	uint16_t leap = isleap(dt->dt_year) ? RTC_YEA_LEAP_YEAR : 0;
201
202	acrtc_write_reg(sc, RTC_SEC, TOBCD(dt->dt_sec));
203	acrtc_write_reg(sc, RTC_MIN, TOBCD(dt->dt_min));
204	acrtc_write_reg(sc, RTC_HOU, TOBCD(dt->dt_hour));
205	acrtc_write_reg(sc, RTC_WEE, TOBCD(dt->dt_wday));
206	acrtc_write_reg(sc, RTC_DAY, TOBCD(dt->dt_day));
207	acrtc_write_reg(sc, RTC_MON, TOBCD(dt->dt_mon));
208	acrtc_write_reg(sc, RTC_YEA, TOBCD(dt->dt_year - 2000) | leap);
209	acrtc_write_reg(sc, RTC_UPD_TRIG, RTC_UPD_TRIG_UPDATE);
210
211	/* Switch to 24H mode to indicate the time is now valid. */
212	acrtc_write_reg(sc, RTC_CTRL, RTC_CTRL_12H_24H_MODE);
213
214	return 0;
215}
216
217void
218acrtc_ck32k_enable(void *cookie, uint32_t *cells, int on)
219{
220	struct acrtc_softc *sc = cookie;
221	uint32_t idx = cells[0];
222	uint16_t reg;
223
224	reg = acrtc_read_reg(sc, CK32K_OUT_CTRL1 + idx);
225	reg &= ~CK32K_OUT_CTRL_PRE_DIV_MASK;
226	reg &= ~CK32K_OUT_CTRL_MUX_SEL_MASK;
227	reg &= ~CK32K_OUT_CTRL_POST_DIV_MASK;
228	reg |= CK32K_OUT_CTRL_PRE_DIV_32K;
229	reg |= CK32K_OUT_CTRL_MUX_SEL_32K;
230	reg |= CK32K_OUT_CTRL_POST_DIV_32K;
231	if (on)
232		reg |= CK32K_OUT_CTRL_ENA;
233	else
234		reg &= ~CK32K_OUT_CTRL_ENA;
235	acrtc_write_reg(sc, CK32K_OUT_CTRL1 + idx, reg);
236}
237