pcfclock.c revision 185003
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: head/sys/dev/ppbus/pcfclock.c 185003 2008-11-16 17:42:02Z jhb $");
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	int	count;
5856293Sjkh};
5956293Sjkh
6056293Sjkhstatic devclass_t pcfclock_devclass;
6156293Sjkh
6256293Sjkhstatic	d_open_t		pcfclock_open;
6356293Sjkhstatic	d_close_t		pcfclock_close;
6456293Sjkhstatic	d_read_t		pcfclock_read;
6556293Sjkh
6656293Sjkhstatic struct cdevsw pcfclock_cdevsw = {
67126080Sphk	.d_version =	D_VERSION,
68126080Sphk	.d_flags =	D_NEEDGIANT,
69111815Sphk	.d_open =	pcfclock_open,
70111815Sphk	.d_close =	pcfclock_close,
71111815Sphk	.d_read =	pcfclock_read,
72111815Sphk	.d_name =	PCFCLOCK_NAME,
7356293Sjkh};
7456293Sjkh
7556293Sjkh#ifndef PCFCLOCK_MAX_RETRIES
7656293Sjkh#define PCFCLOCK_MAX_RETRIES 10
7756293Sjkh#endif
7856293Sjkh
7956293Sjkh#define AFC_HI 0
8056293Sjkh#define AFC_LO AUTOFEED
8156293Sjkh
8256293Sjkh/* AUTO FEED is used as clock */
8356293Sjkh#define AUTOFEED_CLOCK(val) \
8456293Sjkh	ctr = (ctr & ~(AUTOFEED)) ^ (val); ppb_wctr(ppbus, ctr)
8556293Sjkh
8656293Sjkh/* SLCT is used as clock */
8756293Sjkh#define CLOCK_OK \
8856293Sjkh	((ppb_rstr(ppbus) & SELECT) == (i & 1 ? SELECT : 0))
8956293Sjkh
9056293Sjkh/* PE is used as data */
9156293Sjkh#define BIT_SET (ppb_rstr(ppbus)&PERROR)
9256293Sjkh
9356293Sjkh/* the first byte sent as reply must be 00001001b */
9456293Sjkh#define PCFCLOCK_CORRECT_SYNC(buf) (buf[0] == 9)
9556293Sjkh
9656293Sjkh#define NR(buf, off) (buf[off+1]*10+buf[off])
9756293Sjkh
9856293Sjkh/* check for correct input values */
9956293Sjkh#define PCFCLOCK_CORRECT_FORMAT(buf) (\
10056293Sjkh	NR(buf, 14) <= 99 && \
10156293Sjkh	NR(buf, 12) <= 12 && \
10256293Sjkh	NR(buf, 10) <= 31 && \
10356293Sjkh	NR(buf,  6) <= 23 && \
10456293Sjkh	NR(buf,  4) <= 59 && \
10556293Sjkh	NR(buf,  2) <= 59)
10656293Sjkh
10756293Sjkh#define PCFCLOCK_BATTERY_STATUS_LOW(buf) (buf[8] & 4)
108185003Sjhb
10956293Sjkh#define PCFCLOCK_CMD_TIME 0		/* send current time */
11056293Sjkh#define PCFCLOCK_CMD_COPY 7 	/* copy received signal to PC */
11156293Sjkh
11256455Speterstatic void
11356455Speterpcfclock_identify(driver_t *driver, device_t parent)
11456455Speter{
11556455Speter
116127189Sguido	device_t dev;
117127189Sguido
118184130Sjhb	dev = device_find_child(parent, PCFCLOCK_NAME, -1);
119127189Sguido	if (!dev)
120127189Sguido		BUS_ADD_CHILD(parent, 0, PCFCLOCK_NAME, -1);
12156455Speter}
12256455Speter
12356293Sjkhstatic int
12456293Sjkhpcfclock_probe(device_t dev)
12556293Sjkh{
12656293Sjkh
12756293Sjkh	device_set_desc(dev, "PCF-1.0");
12856293Sjkh	return (0);
12956293Sjkh}
13056293Sjkh
13156293Sjkhstatic int
13256293Sjkhpcfclock_attach(device_t dev)
13356293Sjkh{
134184130Sjhb	struct pcfclock_data *sc = device_get_softc(dev);
13556293Sjkh	int unit;
136185003Sjhb
13756293Sjkh	unit = device_get_unit(dev);
13856293Sjkh
139184130Sjhb	sc->dev = dev;
140184130Sjhb	sc->cdev = make_dev(&pcfclock_cdevsw, unit,
141108321Srwatson			UID_ROOT, GID_WHEEL, 0400, PCFCLOCK_NAME "%d", unit);
142184130Sjhb	if (sc->cdev == NULL) {
143184130Sjhb		device_printf(dev, "Failed to create character device\n");
144184130Sjhb		return (ENXIO);
145184130Sjhb	}
146184130Sjhb	sc->cdev->si_drv1 = sc;
14756293Sjkh
14856293Sjkh	return (0);
14956293Sjkh}
15056293Sjkh
151185003Sjhbstatic int
152130585Sphkpcfclock_open(struct cdev *dev, int flag, int fms, struct thread *td)
15356293Sjkh{
154184130Sjhb	struct pcfclock_data *sc = dev->si_drv1;
155184130Sjhb	device_t pcfclockdev = sc->dev;
15656293Sjkh	device_t ppbus = device_get_parent(pcfclockdev);
15756293Sjkh	int res;
158185003Sjhb
15956293Sjkh	if (!sc)
16056293Sjkh		return (ENXIO);
16156293Sjkh
16256293Sjkh	if ((res = ppb_request_bus(ppbus, pcfclockdev,
16356293Sjkh		(flag & O_NONBLOCK) ? PPB_DONTWAIT : PPB_WAIT)))
16456293Sjkh		return (res);
16556293Sjkh
16656293Sjkh	sc->count++;
167185003Sjhb
16856293Sjkh	return (0);
16956293Sjkh}
17056293Sjkh
17156293Sjkhstatic int
172130585Sphkpcfclock_close(struct cdev *dev, int flags, int fmt, struct thread *td)
17356293Sjkh{
174184130Sjhb	struct pcfclock_data *sc = dev->si_drv1;
175184130Sjhb	device_t pcfclockdev = sc->dev;
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
186130585Sphkpcfclock_write_cmd(struct cdev *dev, unsigned char command)
18756293Sjkh{
188184130Sjhb	struct pcfclock_data *sc = dev->si_drv1;
189184130Sjhb	device_t pcfclockdev = sc->dev;
190185003Sjhb	device_t ppbus = device_get_parent(pcfclockdev);
19156293Sjkh	unsigned char ctr = 14;
19256293Sjkh	char i;
193185003Sjhb
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
206185003Sjhbpcfclock_display_data(struct cdev *dev, char buf[18])
20756293Sjkh{
208184130Sjhb	struct pcfclock_data *sc = dev->si_drv1;
20956293Sjkh#ifdef PCFCLOCK_VERBOSE
21056293Sjkh	int year;
21156293Sjkh
21256293Sjkh	year = NR(buf, 14);
21356293Sjkh	if (year < 70)
21456293Sjkh		year += 100;
21577837Sphk
216184130Sjhb	device_printf(sc->dev, "%02d.%02d.%4d %02d:%02d:%02d, "
21756293Sjkh			"battery status: %s\n",
21856293Sjkh			NR(buf, 10), NR(buf, 12), 1900 + year,
21956293Sjkh			NR(buf, 6), NR(buf, 4), NR(buf, 2),
22056293Sjkh			PCFCLOCK_BATTERY_STATUS_LOW(buf) ? "LOW" : "ok");
22156293Sjkh#else
22256293Sjkh	if (PCFCLOCK_BATTERY_STATUS_LOW(buf))
223184130Sjhb		device_printf(sc->dev, "BATTERY STATUS LOW ON\n");
22456293Sjkh#endif
22556293Sjkh}
22656293Sjkh
227185003Sjhbstatic int
228130585Sphkpcfclock_read_data(struct cdev *dev, char *buf, ssize_t bits)
22956293Sjkh{
230184130Sjhb	struct pcfclock_data *sc = dev->si_drv1;
231184130Sjhb	device_t pcfclockdev = sc->dev;
232185003Sjhb	device_t ppbus = device_get_parent(pcfclockdev);
23356293Sjkh	int i;
23456293Sjkh	char waitfor;
23556293Sjkh	int offset;
23656293Sjkh
23756293Sjkh	/* one byte per four bits */
23856293Sjkh	bzero(buf, ((bits + 3) >> 2) + 1);
239185003Sjhb
24056293Sjkh	waitfor = 100;
24156293Sjkh	for (i = 0; i <= bits; i++) {
24256293Sjkh		/* wait for clock, maximum (waitfor*100) usec */
24356293Sjkh		while(!CLOCK_OK && --waitfor > 0)
24456293Sjkh			DELAY(100);
24556293Sjkh
24656293Sjkh		/* timed out? */
247185003Sjhb		if (!waitfor)
24856293Sjkh			return (EIO);
249185003Sjhb
25056293Sjkh		waitfor = 100; /* reload */
251185003Sjhb
25256293Sjkh		/* give it some time */
25356293Sjkh		DELAY(500);
25456293Sjkh
25556293Sjkh		/* calculate offset into buffer */
25656293Sjkh		offset = i >> 2;
25756293Sjkh		buf[offset] <<= 1;
25856293Sjkh
25956293Sjkh		if (BIT_SET)
26056293Sjkh			buf[offset] |= 1;
26156293Sjkh	}
26256293Sjkh
26356293Sjkh	return (0);
26456293Sjkh}
26556293Sjkh
266185003Sjhbstatic int
267185003Sjhbpcfclock_read_dev(struct cdev *dev, char *buf, int maxretries)
26856293Sjkh{
269184130Sjhb	struct pcfclock_data *sc = dev->si_drv1;
270184130Sjhb	device_t pcfclockdev = sc->dev;
271185003Sjhb	device_t ppbus = device_get_parent(pcfclockdev);
27256293Sjkh	int error = 0;
27356293Sjkh
27456293Sjkh	ppb_set_mode(ppbus, PPB_COMPATIBLE);
27556293Sjkh
27656293Sjkh	while (--maxretries > 0) {
27756293Sjkh		pcfclock_write_cmd(dev, PCFCLOCK_CMD_TIME);
27856293Sjkh		if (pcfclock_read_data(dev, buf, 68))
27956293Sjkh			continue;
280185003Sjhb
28156293Sjkh		if (!PCFCLOCK_CORRECT_SYNC(buf))
28256293Sjkh			continue;
28356293Sjkh
28456293Sjkh		if (!PCFCLOCK_CORRECT_FORMAT(buf))
28556293Sjkh			continue;
28656293Sjkh
28756293Sjkh		break;
28856293Sjkh	}
28956293Sjkh
29056293Sjkh	if (!maxretries)
29156293Sjkh		error = EIO;
292185003Sjhb
29356293Sjkh	return (error);
29456293Sjkh}
29556293Sjkh
296106564Sjhbstatic int
297130585Sphkpcfclock_read(struct cdev *dev, struct uio *uio, int ioflag)
29856293Sjkh{
299184130Sjhb	struct pcfclock_data *sc = dev->si_drv1;
30056293Sjkh	char buf[18];
30156293Sjkh	int error = 0;
30256293Sjkh
30357352Ssheldonh	if (uio->uio_resid < 18)
30457352Ssheldonh		return (ERANGE);
30557352Ssheldonh
30656293Sjkh	error = pcfclock_read_dev(dev, buf, PCFCLOCK_MAX_RETRIES);
307185003Sjhb
30856293Sjkh	if (error) {
309184130Sjhb		device_printf(sc->dev, "no PCF found\n");
31056293Sjkh	} else {
31156293Sjkh		pcfclock_display_data(dev, buf);
312185003Sjhb
31356293Sjkh		uiomove(buf, 18, uio);
31456293Sjkh	}
315185003Sjhb
31656293Sjkh	return (error);
31756293Sjkh}
31856293Sjkh
31956455Speterstatic device_method_t pcfclock_methods[] = {
32056455Speter	/* device interface */
32156455Speter	DEVMETHOD(device_identify,	pcfclock_identify),
32256455Speter	DEVMETHOD(device_probe,		pcfclock_probe),
32356455Speter	DEVMETHOD(device_attach,	pcfclock_attach),
32456455Speter
32556455Speter	{ 0, 0 }
32656455Speter};
32756455Speter
32856455Speterstatic driver_t pcfclock_driver = {
32956455Speter	PCFCLOCK_NAME,
33056455Speter	pcfclock_methods,
33156455Speter	sizeof(struct pcfclock_data),
33256455Speter};
33356455Speter
33456293SjkhDRIVER_MODULE(pcfclock, ppbus, pcfclock_driver, pcfclock_devclass, 0, 0);
335