pcfclock.c revision 119418
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 *
2556293Sjkh */
2656293Sjkh
27119418Sobrien#include <sys/cdefs.h>
28119418Sobrien__FBSDID("$FreeBSD: head/sys/dev/ppbus/pcfclock.c 119418 2003-08-24 17:55:58Z obrien $");
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>
3856293Sjkh#include <sys/conf.h>
3956293Sjkh#include <sys/fcntl.h>
4056293Sjkh#include <sys/uio.h>
4156293Sjkh
4256293Sjkh#include <machine/bus.h>
4356293Sjkh#include <machine/resource.h>
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};
5656293Sjkh
5756293Sjkh#define DEVTOSOFTC(dev) \
5856293Sjkh	((struct pcfclock_data *)device_get_softc(dev))
5956293Sjkh#define UNITOSOFTC(unit) \
6056293Sjkh	((struct pcfclock_data *)devclass_get_softc(pcfclock_devclass, (unit)))
6156293Sjkh#define UNITODEVICE(unit) \
6256293Sjkh	(devclass_get_device(pcfclock_devclass, (unit)))
6356293Sjkh
6456293Sjkhstatic devclass_t pcfclock_devclass;
6556293Sjkh
6656293Sjkhstatic	d_open_t		pcfclock_open;
6756293Sjkhstatic	d_close_t		pcfclock_close;
6856293Sjkhstatic	d_read_t		pcfclock_read;
6956293Sjkh
7056293Sjkh#define CDEV_MAJOR 140
7156293Sjkhstatic struct cdevsw pcfclock_cdevsw = {
72111815Sphk	.d_open =	pcfclock_open,
73111815Sphk	.d_close =	pcfclock_close,
74111815Sphk	.d_read =	pcfclock_read,
75111815Sphk	.d_name =	PCFCLOCK_NAME,
76111815Sphk	.d_maj =	CDEV_MAJOR,
7756293Sjkh};
7856293Sjkh
7956293Sjkh#ifndef PCFCLOCK_MAX_RETRIES
8056293Sjkh#define PCFCLOCK_MAX_RETRIES 10
8156293Sjkh#endif
8256293Sjkh
8356293Sjkh#define AFC_HI 0
8456293Sjkh#define AFC_LO AUTOFEED
8556293Sjkh
8656293Sjkh/* AUTO FEED is used as clock */
8756293Sjkh#define AUTOFEED_CLOCK(val) \
8856293Sjkh	ctr = (ctr & ~(AUTOFEED)) ^ (val); ppb_wctr(ppbus, ctr)
8956293Sjkh
9056293Sjkh/* SLCT is used as clock */
9156293Sjkh#define CLOCK_OK \
9256293Sjkh	((ppb_rstr(ppbus) & SELECT) == (i & 1 ? SELECT : 0))
9356293Sjkh
9456293Sjkh/* PE is used as data */
9556293Sjkh#define BIT_SET (ppb_rstr(ppbus)&PERROR)
9656293Sjkh
9756293Sjkh/* the first byte sent as reply must be 00001001b */
9856293Sjkh#define PCFCLOCK_CORRECT_SYNC(buf) (buf[0] == 9)
9956293Sjkh
10056293Sjkh#define NR(buf, off) (buf[off+1]*10+buf[off])
10156293Sjkh
10256293Sjkh/* check for correct input values */
10356293Sjkh#define PCFCLOCK_CORRECT_FORMAT(buf) (\
10456293Sjkh	NR(buf, 14) <= 99 && \
10556293Sjkh	NR(buf, 12) <= 12 && \
10656293Sjkh	NR(buf, 10) <= 31 && \
10756293Sjkh	NR(buf,  6) <= 23 && \
10856293Sjkh	NR(buf,  4) <= 59 && \
10956293Sjkh	NR(buf,  2) <= 59)
11056293Sjkh
11156293Sjkh#define PCFCLOCK_BATTERY_STATUS_LOW(buf) (buf[8] & 4)
11256293Sjkh
11356293Sjkh#define PCFCLOCK_CMD_TIME 0		/* send current time */
11456293Sjkh#define PCFCLOCK_CMD_COPY 7 	/* copy received signal to PC */
11556293Sjkh
11656455Speterstatic void
11756455Speterpcfclock_identify(driver_t *driver, device_t parent)
11856455Speter{
11956455Speter
12094154Sticso	BUS_ADD_CHILD(parent, 0, PCFCLOCK_NAME, -1);
12156455Speter}
12256455Speter
12356293Sjkhstatic int
12456293Sjkhpcfclock_probe(device_t dev)
12556293Sjkh{
12656293Sjkh	struct pcfclock_data *sc;
12756293Sjkh
12856293Sjkh	device_set_desc(dev, "PCF-1.0");
12956293Sjkh
13056293Sjkh	sc = DEVTOSOFTC(dev);
13156293Sjkh	bzero(sc, sizeof(struct pcfclock_data));
13256293Sjkh
13356293Sjkh	return (0);
13456293Sjkh}
13556293Sjkh
13656293Sjkhstatic int
13756293Sjkhpcfclock_attach(device_t dev)
13856293Sjkh{
13956293Sjkh	int unit;
14056293Sjkh
14156293Sjkh	unit = device_get_unit(dev);
14256293Sjkh
14356293Sjkh	make_dev(&pcfclock_cdevsw, unit,
144108321Srwatson			UID_ROOT, GID_WHEEL, 0400, PCFCLOCK_NAME "%d", unit);
14556293Sjkh
14656293Sjkh	return (0);
14756293Sjkh}
14856293Sjkh
14956293Sjkhstatic int
15083366Sjulianpcfclock_open(dev_t dev, int flag, int fms, struct thread *td)
15156293Sjkh{
15256293Sjkh	u_int unit = minor(dev);
15356293Sjkh	struct pcfclock_data *sc = UNITOSOFTC(unit);
15456293Sjkh	device_t pcfclockdev = UNITODEVICE(unit);
15556293Sjkh	device_t ppbus = device_get_parent(pcfclockdev);
15656293Sjkh	int res;
15756293Sjkh
15856293Sjkh	if (!sc)
15956293Sjkh		return (ENXIO);
16056293Sjkh
16156293Sjkh	if ((res = ppb_request_bus(ppbus, pcfclockdev,
16256293Sjkh		(flag & O_NONBLOCK) ? PPB_DONTWAIT : PPB_WAIT)))
16356293Sjkh		return (res);
16456293Sjkh
16556293Sjkh	sc->count++;
16656293Sjkh
16756293Sjkh	return (0);
16856293Sjkh}
16956293Sjkh
17056293Sjkhstatic int
17183366Sjulianpcfclock_close(dev_t dev, int flags, int fmt, struct thread *td)
17256293Sjkh{
17356293Sjkh	u_int unit = minor(dev);
17456293Sjkh	struct pcfclock_data *sc = UNITOSOFTC(unit);
17556293Sjkh	device_t pcfclockdev = UNITODEVICE(unit);
17656293Sjkh	device_t ppbus = device_get_parent(pcfclockdev);
17756293Sjkh
17856293Sjkh	sc->count--;
17977837Sphk	if (sc->count == 0)
18056293Sjkh		ppb_release_bus(ppbus, pcfclockdev);
18156293Sjkh
18256293Sjkh	return (0);
18356293Sjkh}
18456293Sjkh
18556293Sjkhstatic void
18656293Sjkhpcfclock_write_cmd(dev_t dev, unsigned char command)
18756293Sjkh{
18856293Sjkh	u_int unit = minor(dev);
18956293Sjkh	device_t ppidev = UNITODEVICE(unit);
19056293Sjkh        device_t ppbus = device_get_parent(ppidev);
19156293Sjkh	unsigned char ctr = 14;
19256293Sjkh	char i;
19356293Sjkh
19456293Sjkh	for (i = 0; i <= 7; i++) {
19556293Sjkh		ppb_wdtr(ppbus, i);
19656293Sjkh		AUTOFEED_CLOCK(i & 1 ? AFC_HI : AFC_LO);
19756293Sjkh		DELAY(3000);
19856293Sjkh	}
19956293Sjkh	ppb_wdtr(ppbus, command);
20056293Sjkh	AUTOFEED_CLOCK(AFC_LO);
20156293Sjkh	DELAY(3000);
20256293Sjkh	AUTOFEED_CLOCK(AFC_HI);
20356293Sjkh}
20456293Sjkh
20556293Sjkhstatic void
20656293Sjkhpcfclock_display_data(dev_t dev, char buf[18])
20756293Sjkh{
20856293Sjkh	u_int unit = minor(dev);
20956293Sjkh#ifdef PCFCLOCK_VERBOSE
21056293Sjkh	int year;
21156293Sjkh
21256293Sjkh	year = NR(buf, 14);
21356293Sjkh	if (year < 70)
21456293Sjkh		year += 100;
21577837Sphk
21656293Sjkh	printf(PCFCLOCK_NAME "%d: %02d.%02d.%4d %02d:%02d:%02d, "
21756293Sjkh			"battery status: %s\n",
21856293Sjkh			unit,
21956293Sjkh			NR(buf, 10), NR(buf, 12), 1900 + year,
22056293Sjkh			NR(buf, 6), NR(buf, 4), NR(buf, 2),
22156293Sjkh			PCFCLOCK_BATTERY_STATUS_LOW(buf) ? "LOW" : "ok");
22256293Sjkh#else
22356293Sjkh	if (PCFCLOCK_BATTERY_STATUS_LOW(buf))
22456293Sjkh		printf(PCFCLOCK_NAME "%d: BATTERY STATUS LOW ON\n",
22556293Sjkh				unit);
22656293Sjkh#endif
22756293Sjkh}
22856293Sjkh
22956293Sjkhstatic int
23056293Sjkhpcfclock_read_data(dev_t dev, char *buf, ssize_t bits)
23156293Sjkh{
23256293Sjkh	u_int unit = minor(dev);
23356293Sjkh	device_t ppidev = UNITODEVICE(unit);
23456293Sjkh        device_t ppbus = device_get_parent(ppidev);
23556293Sjkh	int i;
23656293Sjkh	char waitfor;
23756293Sjkh	int offset;
23856293Sjkh
23956293Sjkh	/* one byte per four bits */
24056293Sjkh	bzero(buf, ((bits + 3) >> 2) + 1);
24156293Sjkh
24256293Sjkh	waitfor = 100;
24356293Sjkh	for (i = 0; i <= bits; i++) {
24456293Sjkh		/* wait for clock, maximum (waitfor*100) usec */
24556293Sjkh		while(!CLOCK_OK && --waitfor > 0)
24656293Sjkh			DELAY(100);
24756293Sjkh
24856293Sjkh		/* timed out? */
24956293Sjkh		if (!waitfor)
25056293Sjkh			return (EIO);
25156293Sjkh
25256293Sjkh		waitfor = 100; /* reload */
25356293Sjkh
25456293Sjkh		/* give it some time */
25556293Sjkh		DELAY(500);
25656293Sjkh
25756293Sjkh		/* calculate offset into buffer */
25856293Sjkh		offset = i >> 2;
25956293Sjkh		buf[offset] <<= 1;
26056293Sjkh
26156293Sjkh		if (BIT_SET)
26256293Sjkh			buf[offset] |= 1;
26356293Sjkh	}
26456293Sjkh
26556293Sjkh	return (0);
26656293Sjkh}
26756293Sjkh
26856293Sjkhstatic int
26956293Sjkhpcfclock_read_dev(dev_t dev, char *buf, int maxretries)
27056293Sjkh{
27156293Sjkh	u_int unit = minor(dev);
27256293Sjkh	device_t ppidev = UNITODEVICE(unit);
27356293Sjkh        device_t ppbus = device_get_parent(ppidev);
27456293Sjkh	int error = 0;
27556293Sjkh
27656293Sjkh	ppb_set_mode(ppbus, PPB_COMPATIBLE);
27756293Sjkh
27856293Sjkh	while (--maxretries > 0) {
27956293Sjkh		pcfclock_write_cmd(dev, PCFCLOCK_CMD_TIME);
28056293Sjkh		if (pcfclock_read_data(dev, buf, 68))
28156293Sjkh			continue;
28256293Sjkh
28356293Sjkh		if (!PCFCLOCK_CORRECT_SYNC(buf))
28456293Sjkh			continue;
28556293Sjkh
28656293Sjkh		if (!PCFCLOCK_CORRECT_FORMAT(buf))
28756293Sjkh			continue;
28856293Sjkh
28956293Sjkh		break;
29056293Sjkh	}
29156293Sjkh
29256293Sjkh	if (!maxretries)
29356293Sjkh		error = EIO;
29456293Sjkh
29556293Sjkh	return (error);
29656293Sjkh}
29756293Sjkh
298106564Sjhbstatic int
29956293Sjkhpcfclock_read(dev_t dev, struct uio *uio, int ioflag)
30056293Sjkh{
30156293Sjkh	u_int unit = minor(dev);
30256293Sjkh	char buf[18];
30356293Sjkh	int error = 0;
30456293Sjkh
30557352Ssheldonh	if (uio->uio_resid < 18)
30657352Ssheldonh		return (ERANGE);
30757352Ssheldonh
30856293Sjkh	error = pcfclock_read_dev(dev, buf, PCFCLOCK_MAX_RETRIES);
30956293Sjkh
31056293Sjkh	if (error) {
31156293Sjkh		printf(PCFCLOCK_NAME "%d: no PCF found\n", unit);
31256293Sjkh	} else {
31356293Sjkh		pcfclock_display_data(dev, buf);
31456293Sjkh
31556293Sjkh		uiomove(buf, 18, uio);
31656293Sjkh	}
31756293Sjkh
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