pcfclock.c revision 94154
156293Sjkh/*
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.
1256293Sjkh *
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 * $FreeBSD: head/sys/dev/ppbus/pcfclock.c 94154 2002-04-07 22:06:20Z ticso $
2556293Sjkh *
2656293Sjkh */
2756293Sjkh
2856293Sjkh#include "opt_pcfclock.h"
2956293Sjkh
3056293Sjkh#include <sys/param.h>
3156293Sjkh#include <sys/systm.h>
3256293Sjkh#include <sys/bus.h>
3356293Sjkh#include <sys/sockio.h>
3456293Sjkh#include <sys/mbuf.h>
3556293Sjkh#include <sys/kernel.h>
3656293Sjkh#include <sys/conf.h>
3756293Sjkh#include <sys/fcntl.h>
3856293Sjkh#include <sys/uio.h>
3956293Sjkh
4056293Sjkh#include <machine/bus.h>
4156293Sjkh#include <machine/resource.h>
4256293Sjkh
4356293Sjkh#include <dev/ppbus/ppbconf.h>
4456293Sjkh#include <dev/ppbus/ppb_msq.h>
4556293Sjkh#include <dev/ppbus/ppbio.h>
4656293Sjkh
4756293Sjkh#include "ppbus_if.h"
4856293Sjkh
4956293Sjkh#define PCFCLOCK_NAME "pcfclock"
5056293Sjkh
5156293Sjkhstruct pcfclock_data {
5256293Sjkh	int	count;
5356293Sjkh};
5456293Sjkh
5556293Sjkh#define DEVTOSOFTC(dev) \
5656293Sjkh	((struct pcfclock_data *)device_get_softc(dev))
5756293Sjkh#define UNITOSOFTC(unit) \
5856293Sjkh	((struct pcfclock_data *)devclass_get_softc(pcfclock_devclass, (unit)))
5956293Sjkh#define UNITODEVICE(unit) \
6056293Sjkh	(devclass_get_device(pcfclock_devclass, (unit)))
6156293Sjkh
6256293Sjkhstatic devclass_t pcfclock_devclass;
6356293Sjkh
6456293Sjkhstatic	d_open_t		pcfclock_open;
6556293Sjkhstatic	d_close_t		pcfclock_close;
6656293Sjkhstatic	d_read_t		pcfclock_read;
6756293Sjkh
6856293Sjkh#define CDEV_MAJOR 140
6956293Sjkhstatic struct cdevsw pcfclock_cdevsw = {
7056293Sjkh	/* open */	pcfclock_open,
7156293Sjkh	/* close */	pcfclock_close,
7256293Sjkh	/* read */	pcfclock_read,
7356293Sjkh	/* write */	nowrite,
7456293Sjkh	/* ioctl */	noioctl,
7556293Sjkh	/* poll */	nopoll,
7656293Sjkh	/* mmap */	nommap,
7756293Sjkh	/* strategy */	nostrategy,
7856293Sjkh	/* name */	PCFCLOCK_NAME,
7956293Sjkh	/* maj */	CDEV_MAJOR,
8056293Sjkh	/* dump */	nodump,
8156293Sjkh	/* psize */	nopsize,
8256293Sjkh	/* flags */	0,
8356293Sjkh};
8456293Sjkh
8556293Sjkh#ifndef PCFCLOCK_MAX_RETRIES
8656293Sjkh#define PCFCLOCK_MAX_RETRIES 10
8756293Sjkh#endif
8856293Sjkh
8956293Sjkh#define AFC_HI 0
9056293Sjkh#define AFC_LO AUTOFEED
9156293Sjkh
9256293Sjkh/* AUTO FEED is used as clock */
9356293Sjkh#define AUTOFEED_CLOCK(val) \
9456293Sjkh	ctr = (ctr & ~(AUTOFEED)) ^ (val); ppb_wctr(ppbus, ctr)
9556293Sjkh
9656293Sjkh/* SLCT is used as clock */
9756293Sjkh#define CLOCK_OK \
9856293Sjkh	((ppb_rstr(ppbus) & SELECT) == (i & 1 ? SELECT : 0))
9956293Sjkh
10056293Sjkh/* PE is used as data */
10156293Sjkh#define BIT_SET (ppb_rstr(ppbus)&PERROR)
10256293Sjkh
10356293Sjkh/* the first byte sent as reply must be 00001001b */
10456293Sjkh#define PCFCLOCK_CORRECT_SYNC(buf) (buf[0] == 9)
10556293Sjkh
10656293Sjkh#define NR(buf, off) (buf[off+1]*10+buf[off])
10756293Sjkh
10856293Sjkh/* check for correct input values */
10956293Sjkh#define PCFCLOCK_CORRECT_FORMAT(buf) (\
11056293Sjkh	NR(buf, 14) <= 99 && \
11156293Sjkh	NR(buf, 12) <= 12 && \
11256293Sjkh	NR(buf, 10) <= 31 && \
11356293Sjkh	NR(buf,  6) <= 23 && \
11456293Sjkh	NR(buf,  4) <= 59 && \
11556293Sjkh	NR(buf,  2) <= 59)
11656293Sjkh
11756293Sjkh#define PCFCLOCK_BATTERY_STATUS_LOW(buf) (buf[8] & 4)
11856293Sjkh
11956293Sjkh#define PCFCLOCK_CMD_TIME 0		/* send current time */
12056293Sjkh#define PCFCLOCK_CMD_COPY 7 	/* copy received signal to PC */
12156293Sjkh
12256455Speterstatic void
12356455Speterpcfclock_identify(driver_t *driver, device_t parent)
12456455Speter{
12556455Speter
12694154Sticso	BUS_ADD_CHILD(parent, 0, PCFCLOCK_NAME, -1);
12756455Speter}
12856455Speter
12956293Sjkhstatic int
13056293Sjkhpcfclock_probe(device_t dev)
13156293Sjkh{
13256293Sjkh	struct pcfclock_data *sc;
13356293Sjkh
13456293Sjkh	device_set_desc(dev, "PCF-1.0");
13556293Sjkh
13656293Sjkh	sc = DEVTOSOFTC(dev);
13756293Sjkh	bzero(sc, sizeof(struct pcfclock_data));
13856293Sjkh
13956293Sjkh	return (0);
14056293Sjkh}
14156293Sjkh
14256293Sjkhstatic int
14356293Sjkhpcfclock_attach(device_t dev)
14456293Sjkh{
14556293Sjkh	int unit;
14656293Sjkh
14756293Sjkh	unit = device_get_unit(dev);
14856293Sjkh
14956293Sjkh	make_dev(&pcfclock_cdevsw, unit,
15056293Sjkh			UID_ROOT, GID_WHEEL, 0444, PCFCLOCK_NAME "%d", unit);
15156293Sjkh
15256293Sjkh	return (0);
15356293Sjkh}
15456293Sjkh
15556293Sjkhstatic int
15683366Sjulianpcfclock_open(dev_t dev, int flag, int fms, struct thread *td)
15756293Sjkh{
15856293Sjkh	u_int unit = minor(dev);
15956293Sjkh	struct pcfclock_data *sc = UNITOSOFTC(unit);
16056293Sjkh	device_t pcfclockdev = UNITODEVICE(unit);
16156293Sjkh	device_t ppbus = device_get_parent(pcfclockdev);
16256293Sjkh	int res;
16356293Sjkh
16456293Sjkh	if (!sc)
16556293Sjkh		return (ENXIO);
16656293Sjkh
16756293Sjkh	if ((res = ppb_request_bus(ppbus, pcfclockdev,
16856293Sjkh		(flag & O_NONBLOCK) ? PPB_DONTWAIT : PPB_WAIT)))
16956293Sjkh		return (res);
17056293Sjkh
17156293Sjkh	sc->count++;
17256293Sjkh
17356293Sjkh	return (0);
17456293Sjkh}
17556293Sjkh
17656293Sjkhstatic int
17783366Sjulianpcfclock_close(dev_t dev, int flags, int fmt, struct thread *td)
17856293Sjkh{
17956293Sjkh	u_int unit = minor(dev);
18056293Sjkh	struct pcfclock_data *sc = UNITOSOFTC(unit);
18156293Sjkh	device_t pcfclockdev = UNITODEVICE(unit);
18256293Sjkh	device_t ppbus = device_get_parent(pcfclockdev);
18356293Sjkh
18456293Sjkh	sc->count--;
18577837Sphk	if (sc->count == 0)
18656293Sjkh		ppb_release_bus(ppbus, pcfclockdev);
18756293Sjkh
18856293Sjkh	return (0);
18956293Sjkh}
19056293Sjkh
19156293Sjkhstatic void
19256293Sjkhpcfclock_write_cmd(dev_t dev, unsigned char command)
19356293Sjkh{
19456293Sjkh	u_int unit = minor(dev);
19556293Sjkh	device_t ppidev = UNITODEVICE(unit);
19656293Sjkh        device_t ppbus = device_get_parent(ppidev);
19756293Sjkh	unsigned char ctr = 14;
19856293Sjkh	char i;
19956293Sjkh
20056293Sjkh	for (i = 0; i <= 7; i++) {
20156293Sjkh		ppb_wdtr(ppbus, i);
20256293Sjkh		AUTOFEED_CLOCK(i & 1 ? AFC_HI : AFC_LO);
20356293Sjkh		DELAY(3000);
20456293Sjkh	}
20556293Sjkh	ppb_wdtr(ppbus, command);
20656293Sjkh	AUTOFEED_CLOCK(AFC_LO);
20756293Sjkh	DELAY(3000);
20856293Sjkh	AUTOFEED_CLOCK(AFC_HI);
20956293Sjkh}
21056293Sjkh
21156293Sjkhstatic void
21256293Sjkhpcfclock_display_data(dev_t dev, char buf[18])
21356293Sjkh{
21456293Sjkh	u_int unit = minor(dev);
21556293Sjkh#ifdef PCFCLOCK_VERBOSE
21656293Sjkh	int year;
21756293Sjkh
21856293Sjkh	year = NR(buf, 14);
21956293Sjkh	if (year < 70)
22056293Sjkh		year += 100;
22177837Sphk
22256293Sjkh	printf(PCFCLOCK_NAME "%d: %02d.%02d.%4d %02d:%02d:%02d, "
22356293Sjkh			"battery status: %s\n",
22456293Sjkh			unit,
22556293Sjkh			NR(buf, 10), NR(buf, 12), 1900 + year,
22656293Sjkh			NR(buf, 6), NR(buf, 4), NR(buf, 2),
22756293Sjkh			PCFCLOCK_BATTERY_STATUS_LOW(buf) ? "LOW" : "ok");
22856293Sjkh#else
22956293Sjkh	if (PCFCLOCK_BATTERY_STATUS_LOW(buf))
23056293Sjkh		printf(PCFCLOCK_NAME "%d: BATTERY STATUS LOW ON\n",
23156293Sjkh				unit);
23256293Sjkh#endif
23356293Sjkh}
23456293Sjkh
23556293Sjkhstatic int
23656293Sjkhpcfclock_read_data(dev_t dev, char *buf, ssize_t bits)
23756293Sjkh{
23856293Sjkh	u_int unit = minor(dev);
23956293Sjkh	device_t ppidev = UNITODEVICE(unit);
24056293Sjkh        device_t ppbus = device_get_parent(ppidev);
24156293Sjkh	int i;
24256293Sjkh	char waitfor;
24356293Sjkh	int offset;
24456293Sjkh
24556293Sjkh	/* one byte per four bits */
24656293Sjkh	bzero(buf, ((bits + 3) >> 2) + 1);
24756293Sjkh
24856293Sjkh	waitfor = 100;
24956293Sjkh	for (i = 0; i <= bits; i++) {
25056293Sjkh		/* wait for clock, maximum (waitfor*100) usec */
25156293Sjkh		while(!CLOCK_OK && --waitfor > 0)
25256293Sjkh			DELAY(100);
25356293Sjkh
25456293Sjkh		/* timed out? */
25556293Sjkh		if (!waitfor)
25656293Sjkh			return (EIO);
25756293Sjkh
25856293Sjkh		waitfor = 100; /* reload */
25956293Sjkh
26056293Sjkh		/* give it some time */
26156293Sjkh		DELAY(500);
26256293Sjkh
26356293Sjkh		/* calculate offset into buffer */
26456293Sjkh		offset = i >> 2;
26556293Sjkh		buf[offset] <<= 1;
26656293Sjkh
26756293Sjkh		if (BIT_SET)
26856293Sjkh			buf[offset] |= 1;
26956293Sjkh	}
27056293Sjkh
27156293Sjkh	return (0);
27256293Sjkh}
27356293Sjkh
27456293Sjkhstatic int
27556293Sjkhpcfclock_read_dev(dev_t dev, char *buf, int maxretries)
27656293Sjkh{
27756293Sjkh	u_int unit = minor(dev);
27856293Sjkh	device_t ppidev = UNITODEVICE(unit);
27956293Sjkh        device_t ppbus = device_get_parent(ppidev);
28056293Sjkh	int error = 0;
28156293Sjkh
28256293Sjkh	ppb_set_mode(ppbus, PPB_COMPATIBLE);
28356293Sjkh
28456293Sjkh	while (--maxretries > 0) {
28556293Sjkh		pcfclock_write_cmd(dev, PCFCLOCK_CMD_TIME);
28656293Sjkh		if (pcfclock_read_data(dev, buf, 68))
28756293Sjkh			continue;
28856293Sjkh
28956293Sjkh		if (!PCFCLOCK_CORRECT_SYNC(buf))
29056293Sjkh			continue;
29156293Sjkh
29256293Sjkh		if (!PCFCLOCK_CORRECT_FORMAT(buf))
29356293Sjkh			continue;
29456293Sjkh
29556293Sjkh		break;
29656293Sjkh	}
29756293Sjkh
29856293Sjkh	if (!maxretries)
29956293Sjkh		error = EIO;
30056293Sjkh
30156293Sjkh	return (error);
30256293Sjkh}
30356293Sjkh
30456293Sjkhstatic ssize_t
30556293Sjkhpcfclock_read(dev_t dev, struct uio *uio, int ioflag)
30656293Sjkh{
30756293Sjkh	u_int unit = minor(dev);
30856293Sjkh	char buf[18];
30956293Sjkh	int error = 0;
31056293Sjkh
31157352Ssheldonh	if (uio->uio_resid < 18)
31257352Ssheldonh		return (ERANGE);
31357352Ssheldonh
31456293Sjkh	error = pcfclock_read_dev(dev, buf, PCFCLOCK_MAX_RETRIES);
31556293Sjkh
31656293Sjkh	if (error) {
31756293Sjkh		printf(PCFCLOCK_NAME "%d: no PCF found\n", unit);
31856293Sjkh	} else {
31956293Sjkh		pcfclock_display_data(dev, buf);
32056293Sjkh
32156293Sjkh		uiomove(buf, 18, uio);
32256293Sjkh	}
32356293Sjkh
32456293Sjkh	return (error);
32556293Sjkh}
32656293Sjkh
32756455Speterstatic device_method_t pcfclock_methods[] = {
32856455Speter	/* device interface */
32956455Speter	DEVMETHOD(device_identify,	pcfclock_identify),
33056455Speter	DEVMETHOD(device_probe,		pcfclock_probe),
33156455Speter	DEVMETHOD(device_attach,	pcfclock_attach),
33256455Speter
33356455Speter	{ 0, 0 }
33456455Speter};
33556455Speter
33656455Speterstatic driver_t pcfclock_driver = {
33756455Speter	PCFCLOCK_NAME,
33856455Speter	pcfclock_methods,
33956455Speter	sizeof(struct pcfclock_data),
34056455Speter};
34156455Speter
34256293SjkhDRIVER_MODULE(pcfclock, ppbus, pcfclock_driver, pcfclock_devclass, 0, 0);
343