1/*-
2 * Copyright (c) 2003 Izumi Tsutsui.  All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
14 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
15 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
16 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
17 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
18 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
19 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
20 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
22 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 *
24 *	$NetBSD: mc146818.c,v 1.16 2008/05/14 13:29:28 tsutsui Exp $
25 */
26
27#include <sys/cdefs.h>
28__FBSDID("$FreeBSD$");
29
30/*
31 * mc146818 and compatible time of day chip subroutines
32 */
33
34#include <sys/param.h>
35#include <sys/systm.h>
36#include <sys/bus.h>
37#include <sys/clock.h>
38#include <sys/lock.h>
39#include <sys/mutex.h>
40
41#include <machine/bus.h>
42
43#include <dev/mc146818/mc146818reg.h>
44#include <dev/mc146818/mc146818var.h>
45
46#include "clock_if.h"
47
48static u_int	mc146818_def_getcent(device_t);
49static void	mc146818_def_setcent(device_t, u_int);
50
51int
52mc146818_attach(device_t dev)
53{
54	struct mc146818_softc *sc;
55
56	sc = device_get_softc(dev);
57
58	if (mtx_initialized(&sc->sc_mtx) == 0) {
59		device_printf(dev, "%s: mutex not initialized\n", __func__);
60		return (ENXIO);
61	}
62
63	if (sc->sc_mcread == NULL)
64		sc->sc_mcread = mc146818_def_read;
65	if (sc->sc_mcwrite == NULL)
66		sc->sc_mcwrite = mc146818_def_write;
67
68	if (sc->sc_flag & MC146818_NO_CENT_ADJUST) {
69		/*
70		 * Note that setting MC146818_NO_CENT_ADJUST means that
71		 * the century has to be stored in NVRAM somewhere.
72		 */
73		if (sc->sc_getcent == NULL)
74			sc->sc_getcent = mc146818_def_getcent;
75		if (sc->sc_setcent == NULL)
76			sc->sc_setcent = mc146818_def_setcent;
77	}
78
79	mtx_lock_spin(&sc->sc_mtx);
80	if (!(*sc->sc_mcread)(dev, MC_REGD) & MC_REGD_VRT) {
81		mtx_unlock_spin(&sc->sc_mtx);
82		device_printf(dev, "%s: battery low\n", __func__);
83		return (ENXIO);
84	}
85
86	sc->sc_rega = MC_BASE_32_KHz;
87	(*sc->sc_mcwrite)(dev, MC_REGA, sc->sc_rega);
88
89	sc->sc_regb = 0;
90	sc->sc_regb |= (sc->sc_flag & MC146818_BCD) ? 0 : MC_REGB_BINARY;
91	sc->sc_regb |= (sc->sc_flag & MC146818_12HR) ? 0 : MC_REGB_24HR;
92	(*sc->sc_mcwrite)(dev, MC_REGB, sc->sc_regb);
93	mtx_unlock_spin(&sc->sc_mtx);
94
95	clock_register(dev, 1000000);	/* 1 second resolution */
96
97	return (0);
98}
99
100/*
101 * Get time of day and convert it to a struct timespec.
102 * Return 0 on success, an error number otherwise.
103 */
104int
105mc146818_gettime(device_t dev, struct timespec *ts)
106{
107	struct mc146818_softc *sc;
108	struct clocktime ct;
109	int timeout, cent, year;
110
111	sc = device_get_softc(dev);
112
113	timeout = 1000000;	/* XXX how long should we wait? */
114
115	/*
116	 * If MC_REGA_UIP is 0 we have at least 244us before the next
117	 * update.  If it's 1 an update is imminent.
118	 */
119	for (;;) {
120		mtx_lock_spin(&sc->sc_mtx);
121		if (!((*sc->sc_mcread)(dev, MC_REGA) & MC_REGA_UIP))
122			break;
123		mtx_unlock_spin(&sc->sc_mtx);
124		if (--timeout < 0) {
125			device_printf(dev, "%s: timeout\n", __func__);
126			return (EBUSY);
127		}
128	}
129
130#define	FROMREG(x)	((sc->sc_flag & MC146818_BCD) ? FROMBCD(x) : (x))
131
132	ct.nsec = 0;
133	ct.sec = FROMREG((*sc->sc_mcread)(dev, MC_SEC));
134	ct.min = FROMREG((*sc->sc_mcread)(dev, MC_MIN));
135	ct.hour = FROMREG((*sc->sc_mcread)(dev, MC_HOUR));
136	/* Map dow from 1 - 7 to 0 - 6. */
137	ct.dow = FROMREG((*sc->sc_mcread)(dev, MC_DOW)) - 1;
138	ct.day = FROMREG((*sc->sc_mcread)(dev, MC_DOM));
139	ct.mon = FROMREG((*sc->sc_mcread)(dev, MC_MONTH));
140	year = FROMREG((*sc->sc_mcread)(dev, MC_YEAR));
141	year += sc->sc_year0;
142	if (sc->sc_flag & MC146818_NO_CENT_ADJUST) {
143		cent = (*sc->sc_getcent)(dev);
144		year += cent * 100;
145	} else if (year < POSIX_BASE_YEAR)
146		year += 100;
147	mtx_unlock_spin(&sc->sc_mtx);
148
149	ct.year = year;
150
151	return (clock_ct_to_ts(&ct, ts));
152}
153
154#ifdef notyet
155int
156mc146818_getsecs(device_t dev, int *secp)
157{
158	struct mc146818_softc *sc;
159	int sec, timeout;
160
161	sc = device_get_softc(dev);
162
163	timeout = 1000000;	/* XXX how long should we wait? */
164
165	for (;;) {
166		mtx_lock_spin(&sc->sc_mtx);
167		if (!((*sc->sc_mcread)(dev, MC_REGA) & MC_REGA_UIP)) {
168			sec = FROMREG((*sc->sc_mcread)(dev, MC_SEC));
169			mtx_unlock_spin(&sc->sc_mtx);
170			break;
171		}
172		mtx_unlock_spin(&sc->sc_mtx);
173		if (--timeout == 0) {
174			device_printf(dev, "%s: timeout\n", __func__);
175			return (EBUSY);
176		}
177	}
178
179#undef FROMREG
180
181	*secp = sec;
182	return (0);
183}
184#endif
185
186/*
187 * Set the time of day clock based on the value of the struct timespec arg.
188 * Return 0 on success, an error number otherwise.
189 */
190int
191mc146818_settime(device_t dev, struct timespec *ts)
192{
193	struct mc146818_softc *sc;
194	struct clocktime ct;
195	int cent, year;
196
197	sc = device_get_softc(dev);
198
199	/* Accuracy is only one second. */
200	if (ts->tv_nsec >= 500000000)
201		ts->tv_sec++;
202	ts->tv_nsec = 0;
203	clock_ts_to_ct(ts, &ct);
204
205	mtx_lock_spin(&sc->sc_mtx);
206	/* Disable RTC updates and interrupts (if enabled). */
207	(*sc->sc_mcwrite)(dev, MC_REGB,
208	    ((sc->sc_regb & (MC_REGB_BINARY | MC_REGB_24HR)) | MC_REGB_SET));
209
210#define	TOREG(x)	((sc->sc_flag & MC146818_BCD) ? TOBCD(x) : (x))
211
212	(*sc->sc_mcwrite)(dev, MC_SEC, TOREG(ct.sec));
213	(*sc->sc_mcwrite)(dev, MC_MIN, TOREG(ct.min));
214	(*sc->sc_mcwrite)(dev, MC_HOUR, TOREG(ct.hour));
215	/* Map dow from 0 - 6 to 1 - 7. */
216	(*sc->sc_mcwrite)(dev, MC_DOW, TOREG(ct.dow + 1));
217	(*sc->sc_mcwrite)(dev, MC_DOM, TOREG(ct.day));
218	(*sc->sc_mcwrite)(dev, MC_MONTH, TOREG(ct.mon));
219
220	year = ct.year - sc->sc_year0;
221	if (sc->sc_flag & MC146818_NO_CENT_ADJUST) {
222		cent = year / 100;
223		(*sc->sc_setcent)(dev, cent);
224		year -= cent * 100;
225	} else if (year > 99)
226		year -= 100;
227	(*sc->sc_mcwrite)(dev, MC_YEAR, TOREG(year));
228
229	/* Reenable RTC updates and interrupts. */
230	(*sc->sc_mcwrite)(dev, MC_REGB, sc->sc_regb);
231	mtx_unlock_spin(&sc->sc_mtx);
232
233#undef TOREG
234
235	return (0);
236}
237
238#define	MC_ADDR	0
239#define	MC_DATA	1
240
241u_int
242mc146818_def_read(device_t dev, u_int reg)
243{
244	struct mc146818_softc *sc;
245
246	sc = device_get_softc(dev);
247	bus_space_write_1(sc->sc_bst, sc->sc_bsh, MC_ADDR, reg);
248	return (bus_space_read_1(sc->sc_bst, sc->sc_bsh, MC_DATA));
249}
250
251void
252mc146818_def_write(device_t dev, u_int reg, u_int val)
253{
254	struct mc146818_softc *sc;
255
256	sc = device_get_softc(dev);
257	bus_space_write_1(sc->sc_bst, sc->sc_bsh, MC_ADDR, reg);
258	bus_space_write_1(sc->sc_bst, sc->sc_bsh, MC_DATA, val);
259}
260
261#undef MC_ADDR
262#undef MC_DATA
263
264/*
265 * Looks like it's common even across platforms to store the century at
266 * 0x32 in the NVRAM of the mc146818.
267 */
268#define	MC_CENT	0x32
269
270static u_int
271mc146818_def_getcent(device_t dev)
272{
273	struct mc146818_softc *sc;
274
275	sc = device_get_softc(dev);
276	return ((*sc->sc_mcread)(dev, MC_CENT));
277}
278
279static void
280mc146818_def_setcent(device_t dev, u_int cent)
281{
282	struct mc146818_softc *sc;
283
284	sc = device_get_softc(dev);
285	(*sc->sc_mcwrite)(dev, MC_CENT, cent);
286}
287
288#undef MC_CENT
289