1139749Simp/*-
256293Sjkh * Copyright (c) 2000 Sascha Schumann. All rights reserved.
356293Sjkh *
456293Sjkh * Redistribution and use in source and binary forms, with or without
556293Sjkh * modification, are permitted provided that the following conditions
656293Sjkh * are met:
756293Sjkh * 1. Redistributions of source code must retain the above copyright
856293Sjkh *    notice, this list of conditions and the following disclaimer.
956293Sjkh * 2. Redistributions in binary form must reproduce the above copyright
1056293Sjkh *    notice, this list of conditions and the following disclaimer in the
1156293Sjkh *    documentation and/or other materials provided with the distribution.
12185003Sjhb *
1356293Sjkh * THIS SOFTWARE IS PROVIDED BY SASCHA SCHUMANN ``AS IS'' AND ANY EXPRESS OR
1456293Sjkh * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
1556293Sjkh * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO
1677837Sphk * EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
1756293Sjkh * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
1856293Sjkh * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
1956293Sjkh * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
2056293Sjkh * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
2156293Sjkh * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
2256293Sjkh * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2356293Sjkh *
2456293Sjkh *
2556293Sjkh */
2656293Sjkh
27119418Sobrien#include <sys/cdefs.h>
28119418Sobrien__FBSDID("$FreeBSD$");
29119418Sobrien
3056293Sjkh#include "opt_pcfclock.h"
3156293Sjkh
3256293Sjkh#include <sys/param.h>
3356293Sjkh#include <sys/systm.h>
3456293Sjkh#include <sys/bus.h>
3556293Sjkh#include <sys/sockio.h>
3656293Sjkh#include <sys/mbuf.h>
3756293Sjkh#include <sys/kernel.h>
38129879Sphk#include <sys/module.h>
3956293Sjkh#include <sys/conf.h>
4056293Sjkh#include <sys/fcntl.h>
4156293Sjkh#include <sys/uio.h>
4256293Sjkh
4356293Sjkh#include <machine/bus.h>
4456293Sjkh#include <machine/resource.h>
4556293Sjkh
4656293Sjkh#include <dev/ppbus/ppbconf.h>
4756293Sjkh#include <dev/ppbus/ppb_msq.h>
4856293Sjkh#include <dev/ppbus/ppbio.h>
4956293Sjkh
5056293Sjkh#include "ppbus_if.h"
5156293Sjkh
5256293Sjkh#define PCFCLOCK_NAME "pcfclock"
5356293Sjkh
5456293Sjkhstruct pcfclock_data {
55184130Sjhb	device_t dev;
56184130Sjhb	struct cdev *cdev;
5756293Sjkh};
5856293Sjkh
5956293Sjkhstatic devclass_t pcfclock_devclass;
6056293Sjkh
6156293Sjkhstatic	d_open_t		pcfclock_open;
6256293Sjkhstatic	d_close_t		pcfclock_close;
6356293Sjkhstatic	d_read_t		pcfclock_read;
6456293Sjkh
6556293Sjkhstatic struct cdevsw pcfclock_cdevsw = {
66126080Sphk	.d_version =	D_VERSION,
67111815Sphk	.d_open =	pcfclock_open,
68111815Sphk	.d_close =	pcfclock_close,
69111815Sphk	.d_read =	pcfclock_read,
70111815Sphk	.d_name =	PCFCLOCK_NAME,
7156293Sjkh};
7256293Sjkh
7356293Sjkh#ifndef PCFCLOCK_MAX_RETRIES
7456293Sjkh#define PCFCLOCK_MAX_RETRIES 10
7556293Sjkh#endif
7656293Sjkh
7756293Sjkh#define AFC_HI 0
7856293Sjkh#define AFC_LO AUTOFEED
7956293Sjkh
8056293Sjkh/* AUTO FEED is used as clock */
8156293Sjkh#define AUTOFEED_CLOCK(val) \
8256293Sjkh	ctr = (ctr & ~(AUTOFEED)) ^ (val); ppb_wctr(ppbus, ctr)
8356293Sjkh
8456293Sjkh/* SLCT is used as clock */
8556293Sjkh#define CLOCK_OK \
8656293Sjkh	((ppb_rstr(ppbus) & SELECT) == (i & 1 ? SELECT : 0))
8756293Sjkh
8856293Sjkh/* PE is used as data */
8956293Sjkh#define BIT_SET (ppb_rstr(ppbus)&PERROR)
9056293Sjkh
9156293Sjkh/* the first byte sent as reply must be 00001001b */
9256293Sjkh#define PCFCLOCK_CORRECT_SYNC(buf) (buf[0] == 9)
9356293Sjkh
9456293Sjkh#define NR(buf, off) (buf[off+1]*10+buf[off])
9556293Sjkh
9656293Sjkh/* check for correct input values */
9756293Sjkh#define PCFCLOCK_CORRECT_FORMAT(buf) (\
9856293Sjkh	NR(buf, 14) <= 99 && \
9956293Sjkh	NR(buf, 12) <= 12 && \
10056293Sjkh	NR(buf, 10) <= 31 && \
10156293Sjkh	NR(buf,  6) <= 23 && \
10256293Sjkh	NR(buf,  4) <= 59 && \
10356293Sjkh	NR(buf,  2) <= 59)
10456293Sjkh
10556293Sjkh#define PCFCLOCK_BATTERY_STATUS_LOW(buf) (buf[8] & 4)
106185003Sjhb
10756293Sjkh#define PCFCLOCK_CMD_TIME 0		/* send current time */
10856293Sjkh#define PCFCLOCK_CMD_COPY 7 	/* copy received signal to PC */
10956293Sjkh
11056455Speterstatic void
11156455Speterpcfclock_identify(driver_t *driver, device_t parent)
11256455Speter{
11356455Speter
114127189Sguido	device_t dev;
115127189Sguido
116184130Sjhb	dev = device_find_child(parent, PCFCLOCK_NAME, -1);
117127189Sguido	if (!dev)
118127189Sguido		BUS_ADD_CHILD(parent, 0, PCFCLOCK_NAME, -1);
11956455Speter}
12056455Speter
12156293Sjkhstatic int
12256293Sjkhpcfclock_probe(device_t dev)
12356293Sjkh{
12456293Sjkh
12556293Sjkh	device_set_desc(dev, "PCF-1.0");
12656293Sjkh	return (0);
12756293Sjkh}
12856293Sjkh
12956293Sjkhstatic int
13056293Sjkhpcfclock_attach(device_t dev)
13156293Sjkh{
132184130Sjhb	struct pcfclock_data *sc = device_get_softc(dev);
13356293Sjkh	int unit;
134185003Sjhb
13556293Sjkh	unit = device_get_unit(dev);
13656293Sjkh
137184130Sjhb	sc->dev = dev;
138184130Sjhb	sc->cdev = make_dev(&pcfclock_cdevsw, unit,
139108321Srwatson			UID_ROOT, GID_WHEEL, 0400, PCFCLOCK_NAME "%d", unit);
140184130Sjhb	if (sc->cdev == NULL) {
141184130Sjhb		device_printf(dev, "Failed to create character device\n");
142184130Sjhb		return (ENXIO);
143184130Sjhb	}
144184130Sjhb	sc->cdev->si_drv1 = sc;
14556293Sjkh
14656293Sjkh	return (0);
14756293Sjkh}
14856293Sjkh
149185003Sjhbstatic int
150130585Sphkpcfclock_open(struct cdev *dev, int flag, int fms, struct thread *td)
15156293Sjkh{
152184130Sjhb	struct pcfclock_data *sc = dev->si_drv1;
153198358Sbrueffer	device_t pcfclockdev;
154198358Sbrueffer	device_t ppbus;
15556293Sjkh	int res;
156185003Sjhb
15756293Sjkh	if (!sc)
15856293Sjkh		return (ENXIO);
159198358Sbrueffer	pcfclockdev = sc->dev;
160198358Sbrueffer	ppbus = device_get_parent(pcfclockdev);
16156293Sjkh
162187576Sjhb	ppb_lock(ppbus);
163187576Sjhb	res = ppb_request_bus(ppbus, pcfclockdev,
164187576Sjhb	    (flag & O_NONBLOCK) ? PPB_DONTWAIT : PPB_WAIT);
165187576Sjhb	ppb_unlock(ppbus);
166187576Sjhb	return (res);
16756293Sjkh}
16856293Sjkh
16956293Sjkhstatic int
170130585Sphkpcfclock_close(struct cdev *dev, int flags, int fmt, struct thread *td)
17156293Sjkh{
172184130Sjhb	struct pcfclock_data *sc = dev->si_drv1;
173184130Sjhb	device_t pcfclockdev = sc->dev;
17456293Sjkh	device_t ppbus = device_get_parent(pcfclockdev);
17556293Sjkh
176187576Sjhb	ppb_lock(ppbus);
177187576Sjhb	ppb_release_bus(ppbus, pcfclockdev);
178187576Sjhb	ppb_unlock(ppbus);
17956293Sjkh
18056293Sjkh	return (0);
18156293Sjkh}
18256293Sjkh
18356293Sjkhstatic void
184130585Sphkpcfclock_write_cmd(struct cdev *dev, unsigned char command)
18556293Sjkh{
186184130Sjhb	struct pcfclock_data *sc = dev->si_drv1;
187184130Sjhb	device_t pcfclockdev = sc->dev;
188185003Sjhb	device_t ppbus = device_get_parent(pcfclockdev);
18956293Sjkh	unsigned char ctr = 14;
19056293Sjkh	char i;
191185003Sjhb
19256293Sjkh	for (i = 0; i <= 7; i++) {
19356293Sjkh		ppb_wdtr(ppbus, i);
19456293Sjkh		AUTOFEED_CLOCK(i & 1 ? AFC_HI : AFC_LO);
19556293Sjkh		DELAY(3000);
19656293Sjkh	}
19756293Sjkh	ppb_wdtr(ppbus, command);
19856293Sjkh	AUTOFEED_CLOCK(AFC_LO);
19956293Sjkh	DELAY(3000);
20056293Sjkh	AUTOFEED_CLOCK(AFC_HI);
20156293Sjkh}
20256293Sjkh
20356293Sjkhstatic void
204185003Sjhbpcfclock_display_data(struct cdev *dev, char buf[18])
20556293Sjkh{
206184130Sjhb	struct pcfclock_data *sc = dev->si_drv1;
20756293Sjkh#ifdef PCFCLOCK_VERBOSE
20856293Sjkh	int year;
20956293Sjkh
21056293Sjkh	year = NR(buf, 14);
21156293Sjkh	if (year < 70)
21256293Sjkh		year += 100;
21377837Sphk
214184130Sjhb	device_printf(sc->dev, "%02d.%02d.%4d %02d:%02d:%02d, "
21556293Sjkh			"battery status: %s\n",
21656293Sjkh			NR(buf, 10), NR(buf, 12), 1900 + year,
21756293Sjkh			NR(buf, 6), NR(buf, 4), NR(buf, 2),
21856293Sjkh			PCFCLOCK_BATTERY_STATUS_LOW(buf) ? "LOW" : "ok");
21956293Sjkh#else
22056293Sjkh	if (PCFCLOCK_BATTERY_STATUS_LOW(buf))
221184130Sjhb		device_printf(sc->dev, "BATTERY STATUS LOW ON\n");
22256293Sjkh#endif
22356293Sjkh}
22456293Sjkh
225185003Sjhbstatic int
226130585Sphkpcfclock_read_data(struct cdev *dev, char *buf, ssize_t bits)
22756293Sjkh{
228184130Sjhb	struct pcfclock_data *sc = dev->si_drv1;
229184130Sjhb	device_t pcfclockdev = sc->dev;
230185003Sjhb	device_t ppbus = device_get_parent(pcfclockdev);
23156293Sjkh	int i;
23256293Sjkh	char waitfor;
23356293Sjkh	int offset;
23456293Sjkh
23556293Sjkh	/* one byte per four bits */
23656293Sjkh	bzero(buf, ((bits + 3) >> 2) + 1);
237185003Sjhb
23856293Sjkh	waitfor = 100;
23956293Sjkh	for (i = 0; i <= bits; i++) {
24056293Sjkh		/* wait for clock, maximum (waitfor*100) usec */
241187576Sjhb		while (!CLOCK_OK && --waitfor > 0)
24256293Sjkh			DELAY(100);
24356293Sjkh
24456293Sjkh		/* timed out? */
245185003Sjhb		if (!waitfor)
24656293Sjkh			return (EIO);
247185003Sjhb
24856293Sjkh		waitfor = 100; /* reload */
249185003Sjhb
25056293Sjkh		/* give it some time */
25156293Sjkh		DELAY(500);
25256293Sjkh
25356293Sjkh		/* calculate offset into buffer */
25456293Sjkh		offset = i >> 2;
25556293Sjkh		buf[offset] <<= 1;
25656293Sjkh
25756293Sjkh		if (BIT_SET)
25856293Sjkh			buf[offset] |= 1;
25956293Sjkh	}
26056293Sjkh
26156293Sjkh	return (0);
26256293Sjkh}
26356293Sjkh
264185003Sjhbstatic int
265185003Sjhbpcfclock_read_dev(struct cdev *dev, char *buf, int maxretries)
26656293Sjkh{
267184130Sjhb	struct pcfclock_data *sc = dev->si_drv1;
268184130Sjhb	device_t pcfclockdev = sc->dev;
269185003Sjhb	device_t ppbus = device_get_parent(pcfclockdev);
27056293Sjkh	int error = 0;
27156293Sjkh
27256293Sjkh	ppb_set_mode(ppbus, PPB_COMPATIBLE);
27356293Sjkh
27456293Sjkh	while (--maxretries > 0) {
27556293Sjkh		pcfclock_write_cmd(dev, PCFCLOCK_CMD_TIME);
27656293Sjkh		if (pcfclock_read_data(dev, buf, 68))
27756293Sjkh			continue;
278185003Sjhb
27956293Sjkh		if (!PCFCLOCK_CORRECT_SYNC(buf))
28056293Sjkh			continue;
28156293Sjkh
28256293Sjkh		if (!PCFCLOCK_CORRECT_FORMAT(buf))
28356293Sjkh			continue;
28456293Sjkh
28556293Sjkh		break;
28656293Sjkh	}
28756293Sjkh
28856293Sjkh	if (!maxretries)
28956293Sjkh		error = EIO;
290185003Sjhb
29156293Sjkh	return (error);
29256293Sjkh}
29356293Sjkh
294106564Sjhbstatic int
295130585Sphkpcfclock_read(struct cdev *dev, struct uio *uio, int ioflag)
29656293Sjkh{
297184130Sjhb	struct pcfclock_data *sc = dev->si_drv1;
298187576Sjhb	device_t ppbus;
29956293Sjkh	char buf[18];
30056293Sjkh	int error = 0;
30156293Sjkh
30257352Ssheldonh	if (uio->uio_resid < 18)
30357352Ssheldonh		return (ERANGE);
30457352Ssheldonh
305187576Sjhb	ppbus = device_get_parent(sc->dev);
306187576Sjhb	ppb_lock(ppbus);
30756293Sjkh	error = pcfclock_read_dev(dev, buf, PCFCLOCK_MAX_RETRIES);
308187576Sjhb	ppb_unlock(ppbus);
309185003Sjhb
31056293Sjkh	if (error) {
311184130Sjhb		device_printf(sc->dev, "no PCF found\n");
31256293Sjkh	} else {
31356293Sjkh		pcfclock_display_data(dev, buf);
314185003Sjhb
31556293Sjkh		uiomove(buf, 18, uio);
31656293Sjkh	}
317185003Sjhb
31856293Sjkh	return (error);
31956293Sjkh}
32056293Sjkh
32156455Speterstatic device_method_t pcfclock_methods[] = {
32256455Speter	/* device interface */
32356455Speter	DEVMETHOD(device_identify,	pcfclock_identify),
32456455Speter	DEVMETHOD(device_probe,		pcfclock_probe),
32556455Speter	DEVMETHOD(device_attach,	pcfclock_attach),
32656455Speter
32756455Speter	{ 0, 0 }
32856455Speter};
32956455Speter
33056455Speterstatic driver_t pcfclock_driver = {
33156455Speter	PCFCLOCK_NAME,
33256455Speter	pcfclock_methods,
33356455Speter	sizeof(struct pcfclock_data),
33456455Speter};
33556455Speter
33656293SjkhDRIVER_MODULE(pcfclock, ppbus, pcfclock_driver, pcfclock_devclass, 0, 0);
337