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