pcfclock.c revision 56455
1251881Speter/*
2251881Speter * Copyright (c) 2000 Sascha Schumann. All rights reserved.
3251881Speter *
4251881Speter * Redistribution and use in source and binary forms, with or without
5251881Speter * modification, are permitted provided that the following conditions
6251881Speter * are met:
7251881Speter * 1. Redistributions of source code must retain the above copyright
8251881Speter *    notice, this list of conditions and the following disclaimer.
9251881Speter * 2. Redistributions in binary form must reproduce the above copyright
10251881Speter *    notice, this list of conditions and the following disclaimer in the
11251881Speter *    documentation and/or other materials provided with the distribution.
12251881Speter *
13251881Speter * THIS SOFTWARE IS PROVIDED BY SASCHA SCHUMANN ``AS IS'' AND ANY EXPRESS OR
14251881Speter * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
15251881Speter * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO
16251881Speter * EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
17251881Speter * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
18251881Speter * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
19251881Speter * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
20251881Speter * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
21251881Speter * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
22251881Speter * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23251881Speter *
24251881Speter * $FreeBSD: head/sys/dev/ppbus/pcfclock.c 56455 2000-01-23 14:41:04Z peter $
25251881Speter *
26251881Speter */
27251881Speter
28251881Speter#include "opt_pcfclock.h"
29251881Speter
30251881Speter#include <sys/param.h>
31251881Speter#include <sys/systm.h>
32251881Speter#include <sys/bus.h>
33251881Speter#include <sys/sockio.h>
34251881Speter#include <sys/mbuf.h>
35251881Speter#include <sys/malloc.h>
36251881Speter#include <sys/kernel.h>
37251881Speter#include <sys/conf.h>
38251881Speter#include <sys/fcntl.h>
39251881Speter#include <sys/uio.h>
40251881Speter
41251881Speter#include <machine/bus.h>
42251881Speter#include <machine/resource.h>
43251881Speter#include <machine/clock.h>      /* for DELAY */
44251881Speter
45251881Speter#include <dev/ppbus/ppbconf.h>
46251881Speter#include <dev/ppbus/ppb_msq.h>
47251881Speter#include <dev/ppbus/ppbio.h>
48251881Speter
49251881Speter#include "ppbus_if.h"
50251881Speter
51251881Speter#define PCFCLOCK_NAME "pcfclock"
52251881Speter
53251881Speterstruct pcfclock_data {
54251881Speter	int	count;
55251881Speter	struct	ppb_device pcfclock_dev;
56251881Speter};
57251881Speter
58251881Speter#define DEVTOSOFTC(dev) \
59251881Speter	((struct pcfclock_data *)device_get_softc(dev))
60251881Speter#define UNITOSOFTC(unit) \
61251881Speter	((struct pcfclock_data *)devclass_get_softc(pcfclock_devclass, (unit)))
62251881Speter#define UNITODEVICE(unit) \
63251881Speter	(devclass_get_device(pcfclock_devclass, (unit)))
64251881Speter
65251881Speterstatic devclass_t pcfclock_devclass;
66251881Speter
67251881Speterstatic	d_open_t		pcfclock_open;
68251881Speterstatic	d_close_t		pcfclock_close;
69251881Speterstatic	d_read_t		pcfclock_read;
70251881Speter
71251881Speter#define CDEV_MAJOR 140
72251881Speterstatic struct cdevsw pcfclock_cdevsw = {
73251881Speter	/* open */	pcfclock_open,
74251881Speter	/* close */	pcfclock_close,
75251881Speter	/* read */	pcfclock_read,
76251881Speter	/* write */	nowrite,
77251881Speter	/* ioctl */	noioctl,
78251881Speter	/* poll */	nopoll,
79251881Speter	/* mmap */	nommap,
80251881Speter	/* strategy */	nostrategy,
81	/* name */	PCFCLOCK_NAME,
82	/* maj */	CDEV_MAJOR,
83	/* dump */	nodump,
84	/* psize */	nopsize,
85	/* flags */	0,
86	/* bmaj */	-1
87};
88
89#ifndef PCFCLOCK_MAX_RETRIES
90#define PCFCLOCK_MAX_RETRIES 10
91#endif
92
93#define AFC_HI 0
94#define AFC_LO AUTOFEED
95
96/* AUTO FEED is used as clock */
97#define AUTOFEED_CLOCK(val) \
98	ctr = (ctr & ~(AUTOFEED)) ^ (val); ppb_wctr(ppbus, ctr)
99
100/* SLCT is used as clock */
101#define CLOCK_OK \
102	((ppb_rstr(ppbus) & SELECT) == (i & 1 ? SELECT : 0))
103
104/* PE is used as data */
105#define BIT_SET (ppb_rstr(ppbus)&PERROR)
106
107/* the first byte sent as reply must be 00001001b */
108#define PCFCLOCK_CORRECT_SYNC(buf) (buf[0] == 9)
109
110#define NR(buf, off) (buf[off+1]*10+buf[off])
111
112/* check for correct input values */
113#define PCFCLOCK_CORRECT_FORMAT(buf) (\
114	NR(buf, 14) <= 99 && \
115	NR(buf, 12) <= 12 && \
116	NR(buf, 10) <= 31 && \
117	NR(buf,  6) <= 23 && \
118	NR(buf,  4) <= 59 && \
119	NR(buf,  2) <= 59)
120
121#define PCFCLOCK_BATTERY_STATUS_LOW(buf) (buf[8] & 4)
122
123#define PCFCLOCK_CMD_TIME 0		/* send current time */
124#define PCFCLOCK_CMD_COPY 7 	/* copy received signal to PC */
125
126static void
127pcfclock_identify(driver_t *driver, device_t parent)
128{
129
130	BUS_ADD_CHILD(parent, 0, PCFCLOCK_NAME, 0);
131}
132
133static int
134pcfclock_probe(device_t dev)
135{
136	struct pcfclock_data *sc;
137
138	device_set_desc(dev, "PCF-1.0");
139
140	sc = DEVTOSOFTC(dev);
141	bzero(sc, sizeof(struct pcfclock_data));
142
143	return (0);
144}
145
146static int
147pcfclock_attach(device_t dev)
148{
149	int unit;
150
151	unit = device_get_unit(dev);
152
153	make_dev(&pcfclock_cdevsw, unit,
154			UID_ROOT, GID_WHEEL, 0444, PCFCLOCK_NAME "%d", unit);
155
156	return (0);
157}
158
159static int
160pcfclock_open(dev_t dev, int flag, int fms, struct proc *p)
161{
162	u_int unit = minor(dev);
163	struct pcfclock_data *sc = UNITOSOFTC(unit);
164	device_t pcfclockdev = UNITODEVICE(unit);
165	device_t ppbus = device_get_parent(pcfclockdev);
166	int res;
167
168	if (!sc)
169		return (ENXIO);
170
171	if ((res = ppb_request_bus(ppbus, pcfclockdev,
172		(flag & O_NONBLOCK) ? PPB_DONTWAIT : PPB_WAIT)))
173		return (res);
174
175	sc->count++;
176
177	return (0);
178}
179
180static int
181pcfclock_close(dev_t dev, int flags, int fmt, struct proc *p)
182{
183	u_int unit = minor(dev);
184	struct pcfclock_data *sc = UNITOSOFTC(unit);
185	device_t pcfclockdev = UNITODEVICE(unit);
186	device_t ppbus = device_get_parent(pcfclockdev);
187
188	sc->count--;
189	if (sc->count == 0) {
190		ppb_release_bus(ppbus, pcfclockdev);
191	}
192
193	return (0);
194}
195
196static void
197pcfclock_write_cmd(dev_t dev, unsigned char command)
198{
199	u_int unit = minor(dev);
200	device_t ppidev = UNITODEVICE(unit);
201        device_t ppbus = device_get_parent(ppidev);
202	unsigned char ctr = 14;
203	char i;
204
205	for (i = 0; i <= 7; i++) {
206		ppb_wdtr(ppbus, i);
207		AUTOFEED_CLOCK(i & 1 ? AFC_HI : AFC_LO);
208		DELAY(3000);
209	}
210	ppb_wdtr(ppbus, command);
211	AUTOFEED_CLOCK(AFC_LO);
212	DELAY(3000);
213	AUTOFEED_CLOCK(AFC_HI);
214}
215
216static void
217pcfclock_display_data(dev_t dev, char buf[18])
218{
219	u_int unit = minor(dev);
220#ifdef PCFCLOCK_VERBOSE
221	int year;
222
223	year = NR(buf, 14);
224	if (year < 70)
225		year += 100;
226	printf(PCFCLOCK_NAME "%d: %02d.%02d.%4d %02d:%02d:%02d, "
227			"battery status: %s\n",
228			unit,
229			NR(buf, 10), NR(buf, 12), 1900 + year,
230			NR(buf, 6), NR(buf, 4), NR(buf, 2),
231			PCFCLOCK_BATTERY_STATUS_LOW(buf) ? "LOW" : "ok");
232#else
233	if (PCFCLOCK_BATTERY_STATUS_LOW(buf))
234		printf(PCFCLOCK_NAME "%d: BATTERY STATUS LOW ON\n",
235				unit);
236#endif
237}
238
239static int
240pcfclock_read_data(dev_t dev, char *buf, ssize_t bits)
241{
242	u_int unit = minor(dev);
243	device_t ppidev = UNITODEVICE(unit);
244        device_t ppbus = device_get_parent(ppidev);
245	int i;
246	char waitfor;
247	int offset;
248
249	/* one byte per four bits */
250	bzero(buf, ((bits + 3) >> 2) + 1);
251
252	waitfor = 100;
253	for (i = 0; i <= bits; i++) {
254		/* wait for clock, maximum (waitfor*100) usec */
255		while(!CLOCK_OK && --waitfor > 0)
256			DELAY(100);
257
258		/* timed out? */
259		if (!waitfor)
260			return (EIO);
261
262		waitfor = 100; /* reload */
263
264		/* give it some time */
265		DELAY(500);
266
267		/* calculate offset into buffer */
268		offset = i >> 2;
269		buf[offset] <<= 1;
270
271		if (BIT_SET)
272			buf[offset] |= 1;
273	}
274
275	return (0);
276}
277
278static int
279pcfclock_read_dev(dev_t dev, char *buf, int maxretries)
280{
281	u_int unit = minor(dev);
282	device_t ppidev = UNITODEVICE(unit);
283        device_t ppbus = device_get_parent(ppidev);
284	int error = 0;
285
286	ppb_set_mode(ppbus, PPB_COMPATIBLE);
287
288	while (--maxretries > 0) {
289		pcfclock_write_cmd(dev, PCFCLOCK_CMD_TIME);
290		if (pcfclock_read_data(dev, buf, 68))
291			continue;
292
293		if (!PCFCLOCK_CORRECT_SYNC(buf))
294			continue;
295
296		if (!PCFCLOCK_CORRECT_FORMAT(buf))
297			continue;
298
299		break;
300	}
301
302	if (!maxretries)
303		error = EIO;
304
305	return (error);
306}
307
308static ssize_t
309pcfclock_read(dev_t dev, struct uio *uio, int ioflag)
310{
311	u_int unit = minor(dev);
312	char buf[18];
313	int error = 0;
314
315	error = pcfclock_read_dev(dev, buf, PCFCLOCK_MAX_RETRIES);
316
317	if (error) {
318		printf(PCFCLOCK_NAME "%d: no PCF found\n", unit);
319	} else {
320		pcfclock_display_data(dev, buf);
321
322		uiomove(buf, 18, uio);
323	}
324
325	return (error);
326}
327
328static device_method_t pcfclock_methods[] = {
329	/* device interface */
330	DEVMETHOD(device_identify,	pcfclock_identify),
331	DEVMETHOD(device_probe,		pcfclock_probe),
332	DEVMETHOD(device_attach,	pcfclock_attach),
333
334	{ 0, 0 }
335};
336
337static driver_t pcfclock_driver = {
338	PCFCLOCK_NAME,
339	pcfclock_methods,
340	sizeof(struct pcfclock_data),
341};
342
343DRIVER_MODULE(pcfclock, ppbus, pcfclock_driver, pcfclock_devclass, 0, 0);
344