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