pcfclock.c revision 331722
11602Srgrimes/*-
21602Srgrimes * Copyright (c) 2000 Sascha Schumann. All rights reserved.
31602Srgrimes *
41602Srgrimes * Redistribution and use in source and binary forms, with or without
51602Srgrimes * modification, are permitted provided that the following conditions
61602Srgrimes * are met:
71602Srgrimes * 1. Redistributions of source code must retain the above copyright
81602Srgrimes *    notice, this list of conditions and the following disclaimer.
91602Srgrimes * 2. Redistributions in binary form must reproduce the above copyright
101602Srgrimes *    notice, this list of conditions and the following disclaimer in the
111602Srgrimes *    documentation and/or other materials provided with the distribution.
121602Srgrimes *
131602Srgrimes * THIS SOFTWARE IS PROVIDED BY SASCHA SCHUMANN ``AS IS'' AND ANY EXPRESS OR
141602Srgrimes * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
151602Srgrimes * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO
161602Srgrimes * EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
171602Srgrimes * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
181602Srgrimes * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
191602Srgrimes * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
201602Srgrimes * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
211602Srgrimes * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
221602Srgrimes * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
231602Srgrimes *
241602Srgrimes *
251602Srgrimes */
261602Srgrimes
271602Srgrimes#include <sys/cdefs.h>
281602Srgrimes__FBSDID("$FreeBSD: stable/11/sys/dev/ppbus/pcfclock.c 331722 2018-03-29 02:50:57Z eadler $");
291602Srgrimes
301602Srgrimes#include "opt_pcfclock.h"
311602Srgrimes
321602Srgrimes#include <sys/param.h>
331602Srgrimes#include <sys/systm.h>
3483551Sdillon#include <sys/bus.h>
3583551Sdillon#include <sys/sockio.h>
3683551Sdillon#include <sys/mbuf.h>
371602Srgrimes#include <sys/kernel.h>
3855127Speter#include <sys/module.h>
391602Srgrimes#include <sys/conf.h>
4055127Speter#include <sys/fcntl.h>
411602Srgrimes#include <sys/uio.h>
421602Srgrimes
431602Srgrimes#include <machine/bus.h>
44114330Speter#include <machine/resource.h>
451602Srgrimes
461602Srgrimes#include <dev/ppbus/ppbconf.h>
471602Srgrimes#include <dev/ppbus/ppb_msq.h>
481602Srgrimes#include <dev/ppbus/ppbio.h>
49291406Sjhb
50291406Sjhb#include "ppbus_if.h"
5117141Sjkh
52194186Sed#define PCFCLOCK_NAME "pcfclock"
531602Srgrimes
541602Srgrimesstruct pcfclock_data {
551602Srgrimes	device_t dev;
561602Srgrimes	struct cdev *cdev;
571602Srgrimes};
581602Srgrimes
59291406Sjhbstatic devclass_t pcfclock_devclass;
601602Srgrimes
611602Srgrimesstatic	d_open_t		pcfclock_open;
62291406Sjhbstatic	d_close_t		pcfclock_close;
63291406Sjhbstatic	d_read_t		pcfclock_read;
64291406Sjhb
651602Srgrimesstatic struct cdevsw pcfclock_cdevsw = {
661602Srgrimes	.d_version =	D_VERSION,
67147672Speter	.d_open =	pcfclock_open,
68147672Speter	.d_close =	pcfclock_close,
69147672Speter	.d_read =	pcfclock_read,
70147672Speter	.d_name =	PCFCLOCK_NAME,
71147672Speter};
72147672Speter
73291406Sjhb#ifndef PCFCLOCK_MAX_RETRIES
74291406Sjhb#define PCFCLOCK_MAX_RETRIES 10
75291406Sjhb#endif
76147672Speter
77170772Ssimokawa#define AFC_HI 0
78170772Ssimokawa#define AFC_LO AUTOFEED
79291406Sjhb
80170772Ssimokawa/* AUTO FEED is used as clock */
81170772Ssimokawa#define AUTOFEED_CLOCK(val) \
82291406Sjhb	ctr = (ctr & ~(AUTOFEED)) ^ (val); ppb_wctr(ppbus, ctr)
83291406Sjhb
84147672Speter/* SLCT is used as clock */
85147672Speter#define CLOCK_OK \
86147672Speter	((ppb_rstr(ppbus) & SELECT) == (i & 1 ? SELECT : 0))
87147672Speter
88147672Speter/* PE is used as data */
89291406Sjhb#define BIT_SET (ppb_rstr(ppbus)&PERROR)
90147672Speter
91147672Speter/* the first byte sent as reply must be 00001001b */
92291406Sjhb#define PCFCLOCK_CORRECT_SYNC(buf) (buf[0] == 9)
93291406Sjhb
9418798Speter#define NR(buf, off) (buf[off+1]*10+buf[off])
95147672Speter
96147672Speter/* check for correct input values */
97147672Speter#define PCFCLOCK_CORRECT_FORMAT(buf) (\
98147672Speter	NR(buf, 14) <= 99 && \
99291406Sjhb	NR(buf, 12) <= 12 && \
100147672Speter	NR(buf, 10) <= 31 && \
101147672Speter	NR(buf,  6) <= 23 && \
1021602Srgrimes	NR(buf,  4) <= 59 && \
1031602Srgrimes	NR(buf,  2) <= 59)
104291406Sjhb
105291406Sjhb#define PCFCLOCK_BATTERY_STATUS_LOW(buf) (buf[8] & 4)
10618798Speter
1071602Srgrimes#define PCFCLOCK_CMD_TIME 0		/* send current time */
108291406Sjhb#define PCFCLOCK_CMD_COPY 7 	/* copy received signal to PC */
109291406Sjhb
110291406Sjhbstatic void
111157911Speterpcfclock_identify(driver_t *driver, device_t parent)
112291406Sjhb{
113291406Sjhb
114291406Sjhb	device_t dev;
115291406Sjhb
116291406Sjhb	dev = device_find_child(parent, PCFCLOCK_NAME, -1);
117291406Sjhb	if (!dev)
118291406Sjhb		BUS_ADD_CHILD(parent, 0, PCFCLOCK_NAME, -1);
119291406Sjhb}
120147672Speter
121298485Sngiestatic int
1221603Srgrimespcfclock_probe(device_t dev)
1231602Srgrimes{
1241603Srgrimes
125147672Speter	device_set_desc(dev, "PCF-1.0");
1261602Srgrimes	return (0);
127170772Ssimokawa}
128291406Sjhb
129291406Sjhbstatic int
130170772Ssimokawapcfclock_attach(device_t dev)
131170772Ssimokawa{
132147672Speter	struct pcfclock_data *sc = device_get_softc(dev);
133217744Suqs	int unit;
134217744Suqs
1351602Srgrimes	unit = device_get_unit(dev);
136291406Sjhb
137129452Speter	sc->dev = dev;
138129452Speter	sc->cdev = make_dev(&pcfclock_cdevsw, unit,
139129452Speter			UID_ROOT, GID_WHEEL, 0400, PCFCLOCK_NAME "%d", unit);
140217744Suqs	if (sc->cdev == NULL) {
14182263Speter		device_printf(dev, "Failed to create character device\n");
142217744Suqs		return (ENXIO);
143217744Suqs	}
14482263Speter	sc->cdev->si_drv1 = sc;
145291406Sjhb
146129452Speter	return (0);
1471602Srgrimes}
1481602Srgrimes
149291406Sjhbstatic int
15082263Speterpcfclock_open(struct cdev *dev, int flag, int fms, struct thread *td)
151129452Speter{
1521602Srgrimes	struct pcfclock_data *sc = dev->si_drv1;
1531602Srgrimes	device_t pcfclockdev;
154291406Sjhb	device_t ppbus;
155291406Sjhb	int res;
156298485Sngie
157298485Sngie	if (!sc)
158298485Sngie		return (ENXIO);
159298485Sngie	pcfclockdev = sc->dev;
160291406Sjhb	ppbus = device_get_parent(pcfclockdev);
161129452Speter
162298842Sngie	ppb_lock(ppbus);
1631602Srgrimes	res = ppb_request_bus(ppbus, pcfclockdev,
1641602Srgrimes	    (flag & O_NONBLOCK) ? PPB_DONTWAIT : PPB_WAIT);
165147672Speter	ppb_unlock(ppbus);
1661602Srgrimes	return (res);
1671602Srgrimes}
1681602Srgrimes
1691602Srgrimesstatic int
170291406Sjhbpcfclock_close(struct cdev *dev, int flags, int fmt, struct thread *td)
17118798Speter{
17218798Speter	struct pcfclock_data *sc = dev->si_drv1;
173291406Sjhb	device_t pcfclockdev = sc->dev;
174291406Sjhb	device_t ppbus = device_get_parent(pcfclockdev);
175291406Sjhb
176291406Sjhb	ppb_lock(ppbus);
177291406Sjhb	ppb_release_bus(ppbus, pcfclockdev);
178291406Sjhb	ppb_unlock(ppbus);
179291406Sjhb
180291406Sjhb	return (0);
181291406Sjhb}
182291406Sjhb
183291406Sjhbstatic void
184291406Sjhbpcfclock_write_cmd(struct cdev *dev, unsigned char command)
185291406Sjhb{
186147672Speter	struct pcfclock_data *sc = dev->si_drv1;
187147672Speter	device_t pcfclockdev = sc->dev;
1881602Srgrimes	device_t ppbus = device_get_parent(pcfclockdev);
18918798Speter	unsigned char ctr = 14;
190291406Sjhb	char i;
19118798Speter
19218798Speter	for (i = 0; i <= 7; i++) {
19318798Speter		ppb_wdtr(ppbus, i);
19418798Speter		AUTOFEED_CLOCK(i & 1 ? AFC_HI : AFC_LO);
19518798Speter		DELAY(3000);
196298485Sngie	}
197147672Speter	ppb_wdtr(ppbus, command);
198147672Speter	AUTOFEED_CLOCK(AFC_LO);
199147672Speter	DELAY(3000);
200291406Sjhb	AUTOFEED_CLOCK(AFC_HI);
201147672Speter}
202147672Speter
203291406Sjhbstatic void
20418798Speterpcfclock_display_data(struct cdev *dev, char buf[18])
20518798Speter{
206291406Sjhb	struct pcfclock_data *sc = dev->si_drv1;
207291406Sjhb#ifdef PCFCLOCK_VERBOSE
208291406Sjhb	int year;
209291406Sjhb
210129452Speter	year = NR(buf, 14);
211147672Speter	if (year < 70)
212129452Speter		year += 100;
213291406Sjhb
214291406Sjhb	device_printf(sc->dev, "%02d.%02d.%4d %02d:%02d:%02d, "
215129452Speter			"battery status: %s\n",
216147672Speter			NR(buf, 10), NR(buf, 12), 1900 + year,
217291406Sjhb			NR(buf, 6), NR(buf, 4), NR(buf, 2),
218291406Sjhb			PCFCLOCK_BATTERY_STATUS_LOW(buf) ? "LOW" : "ok");
219147672Speter#else
220147672Speter	if (PCFCLOCK_BATTERY_STATUS_LOW(buf))
221291406Sjhb		device_printf(sc->dev, "BATTERY STATUS LOW ON\n");
222291406Sjhb#endif
223129452Speter}
224129452Speter
225291406Sjhbstatic int
226291406Sjhbpcfclock_read_data(struct cdev *dev, char *buf, ssize_t bits)
227291406Sjhb{
228129452Speter	struct pcfclock_data *sc = dev->si_drv1;
229129452Speter	device_t pcfclockdev = sc->dev;
230291406Sjhb	device_t ppbus = device_get_parent(pcfclockdev);
231291406Sjhb	int i;
232291406Sjhb	char waitfor;
233291406Sjhb	int offset;
234291406Sjhb
235297359Sjhb	/* one byte per four bits */
236291406Sjhb	bzero(buf, ((bits + 3) >> 2) + 1);
237291406Sjhb
238291406Sjhb	waitfor = 100;
239291406Sjhb	for (i = 0; i <= bits; i++) {
240291406Sjhb		/* wait for clock, maximum (waitfor*100) usec */
241291406Sjhb		while (!CLOCK_OK && --waitfor > 0)
242291406Sjhb			DELAY(100);
243147672Speter
244129452Speter		/* timed out? */
245291406Sjhb		if (!waitfor)
246291406Sjhb			return (EIO);
247129452Speter
248147672Speter		waitfor = 100; /* reload */
249291406Sjhb
250291406Sjhb		/* give it some time */
251129452Speter		DELAY(500);
252129452Speter
253291406Sjhb		/* calculate offset into buffer */
254291406Sjhb		offset = i >> 2;
255147672Speter		buf[offset] <<= 1;
256147672Speter
257291406Sjhb		if (BIT_SET)
258291406Sjhb			buf[offset] |= 1;
259291406Sjhb	}
260129452Speter
261129452Speter	return (0);
26218798Speter}
263291406Sjhb
264291406Sjhbstatic int
265291406Sjhbpcfclock_read_dev(struct cdev *dev, char *buf, int maxretries)
266291406Sjhb{
267291406Sjhb	struct pcfclock_data *sc = dev->si_drv1;
268147672Speter	device_t pcfclockdev = sc->dev;
269147672Speter	device_t ppbus = device_get_parent(pcfclockdev);
270147672Speter	int error = 0;
271291406Sjhb
272147672Speter	ppb_set_mode(ppbus, PPB_COMPATIBLE);
273147672Speter
274291406Sjhb	while (--maxretries > 0) {
27528318Stegge		pcfclock_write_cmd(dev, PCFCLOCK_CMD_TIME);
27628318Stegge		if (pcfclock_read_data(dev, buf, 68))
277291406Sjhb			continue;
278291406Sjhb
27918798Speter		if (!PCFCLOCK_CORRECT_SYNC(buf))
280147672Speter			continue;
281291406Sjhb
282291406Sjhb		if (!PCFCLOCK_CORRECT_FORMAT(buf))
283147672Speter			continue;
284147672Speter
285291406Sjhb		break;
286291406Sjhb	}
28718798Speter
28818798Speter	if (!maxretries)
289291406Sjhb		error = EIO;
290291406Sjhb
29118798Speter	return (error);
29218798Speter}
29318798Speter
294291406Sjhbstatic int
295147672Speterpcfclock_read(struct cdev *dev, struct uio *uio, int ioflag)
296147672Speter{
297291406Sjhb	struct pcfclock_data *sc = dev->si_drv1;
298147672Speter	device_t ppbus;
299147672Speter	char buf[18];
300291406Sjhb	int error = 0;
30118798Speter
30218798Speter	if (uio->uio_resid < 18)
303291406Sjhb		return (ERANGE);
30418798Speter
3051602Srgrimes	ppbus = device_get_parent(sc->dev);
3061602Srgrimes	ppb_lock(ppbus);
307291406Sjhb	error = pcfclock_read_dev(dev, buf, PCFCLOCK_MAX_RETRIES);
308291406Sjhb	ppb_unlock(ppbus);
30918798Speter
310147672Speter	if (error) {
311147672Speter		device_printf(sc->dev, "no PCF found\n");
312147672Speter	} else {
313147672Speter		pcfclock_display_data(dev, buf);
314147672Speter
315291406Sjhb		uiomove(buf, 18, uio);
3161602Srgrimes	}
317291406Sjhb
318291406Sjhb	return (error);
319316126Sngie}
320291406Sjhb
321291406Sjhbstatic device_method_t pcfclock_methods[] = {
322291406Sjhb	/* device interface */
323291406Sjhb	DEVMETHOD(device_identify,	pcfclock_identify),
324291406Sjhb	DEVMETHOD(device_probe,		pcfclock_probe),
325291406Sjhb	DEVMETHOD(device_attach,	pcfclock_attach),
326291406Sjhb
327291406Sjhb	{ 0, 0 }
328291406Sjhb};
329316126Sngie
330291406Sjhbstatic driver_t pcfclock_driver = {
331291406Sjhb	PCFCLOCK_NAME,
332291406Sjhb	pcfclock_methods,
333291406Sjhb	sizeof(struct pcfclock_data),
334291406Sjhb};
335291406Sjhb
336291406SjhbDRIVER_MODULE(pcfclock, ppbus, pcfclock_driver, pcfclock_devclass, 0, 0);
337291406Sjhb