146283Sdfr/*-
298944Sobrien * SPDX-License-Identifier: BSD-2-Clause
398944Sobrien *
446283Sdfr * Copyright (c) 2019 Axiado Corporation
546283Sdfr * All rights reserved.
698944Sobrien *
746283Sdfr * This software was developed in part by Nick O'Brien and Rishul Naik
898944Sobrien * for Axiado Corporation.
998944Sobrien *
1098944Sobrien * Redistribution and use in source and binary forms, with or without
1198944Sobrien * modification, are permitted provided that the following conditions
1246283Sdfr * are met:
1398944Sobrien * 1. Redistributions of source code must retain the above copyright
1498944Sobrien *    notice, this list of conditions and the following disclaimer.
1598944Sobrien * 2. Redistributions in binary form must reproduce the above copyright
1698944Sobrien *    notice, this list of conditions and the following disclaimer in the
1746283Sdfr *    documentation and/or other materials provided with the distribution.
1898944Sobrien *
1998944Sobrien * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
2098944Sobrien * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2198944Sobrien * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2246283Sdfr * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
23130803Smarcel * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24130803Smarcel * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25130803Smarcel * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26130803Smarcel * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27130803Smarcel * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28130803Smarcel * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29130803Smarcel * SUCH DAMAGE.
30130803Smarcel */
31130803Smarcel
32130803Smarcel#include <sys/param.h>
33130803Smarcel#include <sys/systm.h>
34130803Smarcel#include <sys/bus.h>
35130803Smarcel#include <sys/clock.h>
36130803Smarcel#include <sys/eventhandler.h>
37130803Smarcel#include <sys/kernel.h>
38130803Smarcel#include <sys/lock.h>
39130803Smarcel#include <sys/module.h>
40130803Smarcel#include <sys/mutex.h>
41130803Smarcel#include <sys/rman.h>
42130803Smarcel#include <sys/sdt.h>
43130803Smarcel#include <sys/time.h>
44130803Smarcel#include <sys/timespec.h>
45130803Smarcel#include <sys/timex.h>
46130803Smarcel#include <sys/watchdog.h>
47130803Smarcel
48130803Smarcel#include <dev/ofw/ofw_bus.h>
49130803Smarcel#include <dev/ofw/ofw_bus_subr.h>
50130803Smarcel
51130803Smarcel#include <machine/bus.h>
52130803Smarcel#include <machine/clock.h>
53130803Smarcel#include <machine/intr.h>
54130803Smarcel#include <machine/resource.h>
55130803Smarcel
56130803Smarcel#include "clock_if.h"
57130803Smarcel
58130803Smarcel#define FEAON_AON_WDT_BASE		0x0
59130803Smarcel#define FEAON_AON_RTC_BASE		0x40
60130803Smarcel#define FEAON_AON_CLKCFG_BASE		0x70
61130803Smarcel#define FEAON_AON_BACKUP_BASE		0x80
62130803Smarcel#define FEAON_AON_PMU_BASE		0x100
63130803Smarcel
64130803Smarcel/* Watchdog specific */
65130803Smarcel#define FEAON_WDT_CFG			0x0
66130803Smarcel#define FEAON_WDT_COUNT			0x8
67130803Smarcel#define FEAON_WDT_DOGS			0x10
68130803Smarcel#define FEAON_WDT_FEED			0x18
69130803Smarcel#define FEAON_WDT_KEY			0x1C
70130803Smarcel#define FEAON_WDT_CMP			0x20
71130803Smarcel
72130803Smarcel#define FEAON_WDT_CFG_SCALE_MASK	0xF
73130803Smarcel#define FEAON_WDT_CFG_RST_EN		(1 << 8)
74130803Smarcel#define FEAON_WDT_CFG_ZERO_CMP		(1 << 9)
75130803Smarcel#define FEAON_WDT_CFG_EN_ALWAYS		(1 << 12)
76130803Smarcel#define FEAON_WDT_CFG_EN_CORE_AWAKE	(1 << 13)
77130803Smarcel#define FEAON_WDT_CFG_IP		(1 << 28)
78130803Smarcel
79130803Smarcel#define FEAON_WDT_CMP_MASK		0xFFFF
80130803Smarcel
81130803Smarcel#define FEAON_WDT_FEED_FOOD		0xD09F00D
82130803Smarcel
83130803Smarcel#define FEAON_WDT_KEY_UNLOCK		0x51F15E
84130803Smarcel
85130803Smarcel#define FEAON_WDT_TIMEBASE_FREQ		31250
86130803Smarcel#define FEAON_WDT_TIMEBASE_RATIO	(NANOSECOND / FEAON_WDT_TIMEBASE_FREQ)
87130803Smarcel
88130803Smarcel/* Real-time clock specific */
89130803Smarcel#define FEAON_RTC_CFG			0x40
90130803Smarcel#define FEAON_RTC_LO			0x48
91130803Smarcel#define FEAON_RTC_HI			0x4C
92130803Smarcel#define FEAON_RTC_CMP			0x60
93
94#define FEAON_RTC_CFG_SCALE_MASK	0xF
95#define FEAON_RTC_CFG_EN		(1 << 12)
96#define FEAON_RTC_CFG_IP		(1 << 28)
97
98#define FEAON_RTC_HI_MASK		0xFFFF
99
100#define FEAON_RTC_TIMEBASE_FREQ		31250LL
101
102#define FEAON_LOCK(sc)			mtx_lock(&(sc)->mtx)
103#define FEAON_UNLOCK(sc)		mtx_unlock(&(sc)->mtx)
104#define FEAON_ASSERT_LOCKED(sc)		mtx_assert(&(sc)->mtx, MA_OWNED)
105#define FEAON_ASSERT_UNLOCKED(sc)	mtx_assert(&(sc)->mtx, MA_NOTOWNED)
106
107#define FEAON_READ_4(sc, reg)		bus_read_4(sc->reg_res, reg)
108#define FEAON_WRITE_4(sc, reg, val)	bus_write_4(sc->reg_res, reg, val)
109
110#define FEAON_WDT_WRITE_4(sc, reg, val) do {					\
111		FEAON_WRITE_4(sc, (FEAON_WDT_KEY), (FEAON_WDT_KEY_UNLOCK));	\
112		FEAON_WRITE_4(sc, reg, val);					\
113	} while (0)
114
115struct feaon_softc {
116	device_t		dev;
117	struct mtx		mtx;
118
119	/* Resources */
120	int			reg_rid;
121	struct resource		*reg_res;
122
123	/* WDT */
124	eventhandler_tag	ev_tag;
125};
126
127static void
128feaon_wdt_event(void *arg, unsigned int cmd, int *err)
129{
130	struct feaon_softc *sc;
131	uint32_t scale, val;
132	uint64_t time;
133
134	sc = (struct feaon_softc *)arg;
135	FEAON_LOCK(sc);
136
137	/* First feed WDT */
138	FEAON_WDT_WRITE_4(sc, FEAON_WDT_FEED, FEAON_WDT_FEED_FOOD);
139
140	if ((cmd & WD_INTERVAL) == WD_TO_NEVER) {
141		/* Disable WDT */
142		val = FEAON_READ_4(sc, FEAON_WDT_CFG);
143		val &= ~(FEAON_WDT_CFG_EN_ALWAYS | FEAON_WDT_CFG_EN_CORE_AWAKE);
144		FEAON_WDT_WRITE_4(sc, FEAON_WDT_CFG, val);
145		goto exit;
146	}
147
148	/* Calculate time in WDT frequency */
149	time = 1LL << (cmd & WD_INTERVAL);
150	time /= FEAON_WDT_TIMEBASE_RATIO;
151
152	/* Fit time in CMP register with scale */
153	scale = 0;
154	while (time > FEAON_WDT_CMP_MASK) {
155		time >>= 1;
156		scale++;
157	}
158
159	if (time > FEAON_WDT_CMP_MASK || scale > FEAON_WDT_CFG_SCALE_MASK) {
160		device_printf(sc->dev, "Time interval too large for WDT\n");
161		*err = EINVAL;
162		goto exit;
163	}
164
165	/* Program WDT */
166	val = FEAON_READ_4(sc, FEAON_WDT_CFG);
167	val &= ~FEAON_WDT_CFG_SCALE_MASK;
168	val |= scale | FEAON_WDT_CFG_RST_EN | FEAON_WDT_CFG_EN_ALWAYS |
169	    FEAON_WDT_CFG_ZERO_CMP;
170
171	FEAON_WDT_WRITE_4(sc, FEAON_WDT_CMP, (uint32_t)time);
172	FEAON_WDT_WRITE_4(sc, FEAON_WDT_CFG, val);
173
174exit:
175	FEAON_UNLOCK(sc);
176}
177
178static int
179feaon_rtc_settime(device_t dev, struct timespec *ts)
180{
181	struct feaon_softc *sc;
182	uint64_t time;
183	uint32_t cfg;
184	uint8_t scale;
185
186	scale = 0;
187	sc = device_get_softc(dev);
188
189	FEAON_LOCK(sc);
190
191	clock_dbgprint_ts(dev, CLOCK_DBG_WRITE, ts);
192
193	time = ts->tv_sec * FEAON_RTC_TIMEBASE_FREQ;
194
195	/* Find an appropriate scale */
196	while (time >= 0xFFFFFFFFFFFFLL) {
197		scale++;
198		time >>= 1;
199	}
200	if (scale > FEAON_RTC_CFG_SCALE_MASK) {
201		device_printf(sc->dev, "Time value too large for RTC\n");
202		FEAON_UNLOCK(sc);
203		return (1);
204	}
205	cfg = FEAON_READ_4(sc, FEAON_RTC_CFG) & ~FEAON_RTC_CFG_SCALE_MASK;
206	cfg |= scale;
207
208	FEAON_WRITE_4(sc, FEAON_RTC_CFG, cfg);
209	FEAON_WRITE_4(sc, FEAON_RTC_LO, (uint32_t)time);
210	FEAON_WRITE_4(sc, FEAON_RTC_HI, (time >> 32) & FEAON_RTC_HI_MASK);
211
212	FEAON_UNLOCK(sc);
213
214	return (0);
215}
216
217static int
218feaon_rtc_gettime(device_t dev, struct timespec *ts)
219{
220	struct feaon_softc *sc;
221	uint64_t time;
222	uint8_t scale;
223
224	sc = device_get_softc(dev);
225	FEAON_LOCK(sc);
226
227	time = FEAON_READ_4(sc, FEAON_RTC_LO);
228	time |= ((uint64_t)FEAON_READ_4(sc, FEAON_RTC_HI)) << 32;
229
230	scale = FEAON_READ_4(sc, FEAON_RTC_CFG) & FEAON_RTC_CFG_SCALE_MASK;
231	time <<= scale;
232
233	ts->tv_sec = time / FEAON_RTC_TIMEBASE_FREQ;
234	ts->tv_nsec = (time % FEAON_RTC_TIMEBASE_FREQ) *
235	    (NANOSECOND / FEAON_RTC_TIMEBASE_FREQ);
236
237	clock_dbgprint_ts(dev, CLOCK_DBG_READ, ts);
238
239	FEAON_UNLOCK(sc);
240
241	return (0);
242}
243
244static int
245feaon_attach(device_t dev)
246{
247	struct feaon_softc *sc;
248	int err;
249
250	sc = device_get_softc(dev);
251	sc->dev = dev;
252
253	/* Mutex setup */
254	mtx_init(&sc->mtx, device_get_nameunit(sc->dev), NULL, MTX_DEF);
255
256	/* Resource setup */
257	sc->reg_rid = 0;
258	if ((sc->reg_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY,
259	    &sc->reg_rid, RF_ACTIVE)) == NULL) {
260		device_printf(dev, "Error allocating memory resource.\n");
261		err = ENXIO;
262		goto error;
263	}
264
265	/* Enable RTC */
266	clock_register(dev, 1000000); /* 1 sec resolution */
267	FEAON_LOCK(sc);
268	FEAON_WRITE_4(sc, FEAON_RTC_CFG, FEAON_RTC_CFG_EN);
269	FEAON_UNLOCK(sc);
270
271	/* Register WDT */
272	sc->ev_tag = EVENTHANDLER_REGISTER(watchdog_list, feaon_wdt_event, sc, 0);
273
274	return (0);
275
276error:
277	bus_release_resource(dev, SYS_RES_MEMORY, sc->reg_rid, sc->reg_res);
278	mtx_destroy(&sc->mtx);
279	return (err);
280}
281
282static int
283feaon_probe(device_t dev)
284{
285
286	if (!ofw_bus_status_okay(dev))
287		return (ENXIO);
288
289	if (!ofw_bus_is_compatible(dev, "sifive,aon0"))
290		return (ENXIO);
291
292	device_set_desc(dev, "SiFive FE310 Always-On Controller");
293	return (BUS_PROBE_DEFAULT);
294}
295
296static device_method_t feaon_methods[] = {
297	DEVMETHOD(device_probe, feaon_probe),
298	DEVMETHOD(device_attach, feaon_attach),
299
300	/* RTC */
301	DEVMETHOD(clock_gettime, feaon_rtc_gettime),
302	DEVMETHOD(clock_settime, feaon_rtc_settime),
303
304	DEVMETHOD_END
305};
306
307static driver_t feaon_driver = {
308	"fe310aon",
309	feaon_methods,
310	sizeof(struct feaon_softc)
311};
312
313DRIVER_MODULE(fe310aon, simplebus, feaon_driver, 0, 0);
314