pcfclock.c revision 111815
1189251Ssam/*
2189251Ssam * Copyright (c) 2000 Sascha Schumann. All rights reserved.
3189251Ssam *
4189251Ssam * Redistribution and use in source and binary forms, with or without
5252726Srpaulo * modification, are permitted provided that the following conditions
6252726Srpaulo * are met:
7189251Ssam * 1. Redistributions of source code must retain the above copyright
8189251Ssam *    notice, this list of conditions and the following disclaimer.
9189251Ssam * 2. Redistributions in binary form must reproduce the above copyright
10189251Ssam *    notice, this list of conditions and the following disclaimer in the
11189251Ssam *    documentation and/or other materials provided with the distribution.
12189251Ssam *
13189251Ssam * THIS SOFTWARE IS PROVIDED BY SASCHA SCHUMANN ``AS IS'' AND ANY EXPRESS OR
14189251Ssam * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
15189251Ssam * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO
16189251Ssam * EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
17189251Ssam * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
18189251Ssam * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
19189251Ssam * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
20189251Ssam * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
21189251Ssam * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
22189251Ssam * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23189251Ssam *
24189251Ssam * $FreeBSD: head/sys/dev/ppbus/pcfclock.c 111815 2003-03-03 12:15:54Z phk $
25189251Ssam *
26189251Ssam */
27189251Ssam
28189251Ssam#include "opt_pcfclock.h"
29189251Ssam
30189251Ssam#include <sys/param.h>
31189251Ssam#include <sys/systm.h>
32189251Ssam#include <sys/bus.h>
33189251Ssam#include <sys/sockio.h>
34189251Ssam#include <sys/mbuf.h>
35189251Ssam#include <sys/kernel.h>
36189251Ssam#include <sys/conf.h>
37189251Ssam#include <sys/fcntl.h>
38189251Ssam#include <sys/uio.h>
39189251Ssam
40189251Ssam#include <machine/bus.h>
41189251Ssam#include <machine/resource.h>
42189251Ssam
43189251Ssam#include <dev/ppbus/ppbconf.h>
44189251Ssam#include <dev/ppbus/ppb_msq.h>
45189251Ssam#include <dev/ppbus/ppbio.h>
46189251Ssam
47189251Ssam#include "ppbus_if.h"
48189251Ssam
49189251Ssam#define PCFCLOCK_NAME "pcfclock"
50189251Ssam
51189251Ssamstruct pcfclock_data {
52189251Ssam	int	count;
53189251Ssam};
54189251Ssam
55189251Ssam#define DEVTOSOFTC(dev) \
56189251Ssam	((struct pcfclock_data *)device_get_softc(dev))
57189251Ssam#define UNITOSOFTC(unit) \
58189251Ssam	((struct pcfclock_data *)devclass_get_softc(pcfclock_devclass, (unit)))
59189251Ssam#define UNITODEVICE(unit) \
60189251Ssam	(devclass_get_device(pcfclock_devclass, (unit)))
61189251Ssam
62189251Ssamstatic devclass_t pcfclock_devclass;
63189251Ssam
64189251Ssamstatic	d_open_t		pcfclock_open;
65189251Ssamstatic	d_close_t		pcfclock_close;
66189251Ssamstatic	d_read_t		pcfclock_read;
67189251Ssam
68189251Ssam#define CDEV_MAJOR 140
69189251Ssamstatic struct cdevsw pcfclock_cdevsw = {
70	.d_open =	pcfclock_open,
71	.d_close =	pcfclock_close,
72	.d_read =	pcfclock_read,
73	.d_name =	PCFCLOCK_NAME,
74	.d_maj =	CDEV_MAJOR,
75};
76
77#ifndef PCFCLOCK_MAX_RETRIES
78#define PCFCLOCK_MAX_RETRIES 10
79#endif
80
81#define AFC_HI 0
82#define AFC_LO AUTOFEED
83
84/* AUTO FEED is used as clock */
85#define AUTOFEED_CLOCK(val) \
86	ctr = (ctr & ~(AUTOFEED)) ^ (val); ppb_wctr(ppbus, ctr)
87
88/* SLCT is used as clock */
89#define CLOCK_OK \
90	((ppb_rstr(ppbus) & SELECT) == (i & 1 ? SELECT : 0))
91
92/* PE is used as data */
93#define BIT_SET (ppb_rstr(ppbus)&PERROR)
94
95/* the first byte sent as reply must be 00001001b */
96#define PCFCLOCK_CORRECT_SYNC(buf) (buf[0] == 9)
97
98#define NR(buf, off) (buf[off+1]*10+buf[off])
99
100/* check for correct input values */
101#define PCFCLOCK_CORRECT_FORMAT(buf) (\
102	NR(buf, 14) <= 99 && \
103	NR(buf, 12) <= 12 && \
104	NR(buf, 10) <= 31 && \
105	NR(buf,  6) <= 23 && \
106	NR(buf,  4) <= 59 && \
107	NR(buf,  2) <= 59)
108
109#define PCFCLOCK_BATTERY_STATUS_LOW(buf) (buf[8] & 4)
110
111#define PCFCLOCK_CMD_TIME 0		/* send current time */
112#define PCFCLOCK_CMD_COPY 7 	/* copy received signal to PC */
113
114static void
115pcfclock_identify(driver_t *driver, device_t parent)
116{
117
118	BUS_ADD_CHILD(parent, 0, PCFCLOCK_NAME, -1);
119}
120
121static int
122pcfclock_probe(device_t dev)
123{
124	struct pcfclock_data *sc;
125
126	device_set_desc(dev, "PCF-1.0");
127
128	sc = DEVTOSOFTC(dev);
129	bzero(sc, sizeof(struct pcfclock_data));
130
131	return (0);
132}
133
134static int
135pcfclock_attach(device_t dev)
136{
137	int unit;
138
139	unit = device_get_unit(dev);
140
141	make_dev(&pcfclock_cdevsw, unit,
142			UID_ROOT, GID_WHEEL, 0400, PCFCLOCK_NAME "%d", unit);
143
144	return (0);
145}
146
147static int
148pcfclock_open(dev_t dev, int flag, int fms, struct thread *td)
149{
150	u_int unit = minor(dev);
151	struct pcfclock_data *sc = UNITOSOFTC(unit);
152	device_t pcfclockdev = UNITODEVICE(unit);
153	device_t ppbus = device_get_parent(pcfclockdev);
154	int res;
155
156	if (!sc)
157		return (ENXIO);
158
159	if ((res = ppb_request_bus(ppbus, pcfclockdev,
160		(flag & O_NONBLOCK) ? PPB_DONTWAIT : PPB_WAIT)))
161		return (res);
162
163	sc->count++;
164
165	return (0);
166}
167
168static int
169pcfclock_close(dev_t dev, int flags, int fmt, struct thread *td)
170{
171	u_int unit = minor(dev);
172	struct pcfclock_data *sc = UNITOSOFTC(unit);
173	device_t pcfclockdev = UNITODEVICE(unit);
174	device_t ppbus = device_get_parent(pcfclockdev);
175
176	sc->count--;
177	if (sc->count == 0)
178		ppb_release_bus(ppbus, pcfclockdev);
179
180	return (0);
181}
182
183static void
184pcfclock_write_cmd(dev_t dev, unsigned char command)
185{
186	u_int unit = minor(dev);
187	device_t ppidev = UNITODEVICE(unit);
188        device_t ppbus = device_get_parent(ppidev);
189	unsigned char ctr = 14;
190	char i;
191
192	for (i = 0; i <= 7; i++) {
193		ppb_wdtr(ppbus, i);
194		AUTOFEED_CLOCK(i & 1 ? AFC_HI : AFC_LO);
195		DELAY(3000);
196	}
197	ppb_wdtr(ppbus, command);
198	AUTOFEED_CLOCK(AFC_LO);
199	DELAY(3000);
200	AUTOFEED_CLOCK(AFC_HI);
201}
202
203static void
204pcfclock_display_data(dev_t dev, char buf[18])
205{
206	u_int unit = minor(dev);
207#ifdef PCFCLOCK_VERBOSE
208	int year;
209
210	year = NR(buf, 14);
211	if (year < 70)
212		year += 100;
213
214	printf(PCFCLOCK_NAME "%d: %02d.%02d.%4d %02d:%02d:%02d, "
215			"battery status: %s\n",
216			unit,
217			NR(buf, 10), NR(buf, 12), 1900 + year,
218			NR(buf, 6), NR(buf, 4), NR(buf, 2),
219			PCFCLOCK_BATTERY_STATUS_LOW(buf) ? "LOW" : "ok");
220#else
221	if (PCFCLOCK_BATTERY_STATUS_LOW(buf))
222		printf(PCFCLOCK_NAME "%d: BATTERY STATUS LOW ON\n",
223				unit);
224#endif
225}
226
227static int
228pcfclock_read_data(dev_t dev, char *buf, ssize_t bits)
229{
230	u_int unit = minor(dev);
231	device_t ppidev = UNITODEVICE(unit);
232        device_t ppbus = device_get_parent(ppidev);
233	int i;
234	char waitfor;
235	int offset;
236
237	/* one byte per four bits */
238	bzero(buf, ((bits + 3) >> 2) + 1);
239
240	waitfor = 100;
241	for (i = 0; i <= bits; i++) {
242		/* wait for clock, maximum (waitfor*100) usec */
243		while(!CLOCK_OK && --waitfor > 0)
244			DELAY(100);
245
246		/* timed out? */
247		if (!waitfor)
248			return (EIO);
249
250		waitfor = 100; /* reload */
251
252		/* give it some time */
253		DELAY(500);
254
255		/* calculate offset into buffer */
256		offset = i >> 2;
257		buf[offset] <<= 1;
258
259		if (BIT_SET)
260			buf[offset] |= 1;
261	}
262
263	return (0);
264}
265
266static int
267pcfclock_read_dev(dev_t dev, char *buf, int maxretries)
268{
269	u_int unit = minor(dev);
270	device_t ppidev = UNITODEVICE(unit);
271        device_t ppbus = device_get_parent(ppidev);
272	int error = 0;
273
274	ppb_set_mode(ppbus, PPB_COMPATIBLE);
275
276	while (--maxretries > 0) {
277		pcfclock_write_cmd(dev, PCFCLOCK_CMD_TIME);
278		if (pcfclock_read_data(dev, buf, 68))
279			continue;
280
281		if (!PCFCLOCK_CORRECT_SYNC(buf))
282			continue;
283
284		if (!PCFCLOCK_CORRECT_FORMAT(buf))
285			continue;
286
287		break;
288	}
289
290	if (!maxretries)
291		error = EIO;
292
293	return (error);
294}
295
296static int
297pcfclock_read(dev_t dev, struct uio *uio, int ioflag)
298{
299	u_int unit = minor(dev);
300	char buf[18];
301	int error = 0;
302
303	if (uio->uio_resid < 18)
304		return (ERANGE);
305
306	error = pcfclock_read_dev(dev, buf, PCFCLOCK_MAX_RETRIES);
307
308	if (error) {
309		printf(PCFCLOCK_NAME "%d: no PCF found\n", unit);
310	} else {
311		pcfclock_display_data(dev, buf);
312
313		uiomove(buf, 18, uio);
314	}
315
316	return (error);
317}
318
319static device_method_t pcfclock_methods[] = {
320	/* device interface */
321	DEVMETHOD(device_identify,	pcfclock_identify),
322	DEVMETHOD(device_probe,		pcfclock_probe),
323	DEVMETHOD(device_attach,	pcfclock_attach),
324
325	{ 0, 0 }
326};
327
328static driver_t pcfclock_driver = {
329	PCFCLOCK_NAME,
330	pcfclock_methods,
331	sizeof(struct pcfclock_data),
332};
333
334DRIVER_MODULE(pcfclock, ppbus, pcfclock_driver, pcfclock_devclass, 0, 0);
335