diff options
author | jkh <jkh@FreeBSD.org> | 2000-01-19 18:19:16 +0000 |
---|---|---|
committer | jkh <jkh@FreeBSD.org> | 2000-01-19 18:19:16 +0000 |
commit | 67e9a9c57f0f6b9501cedd6db2a59e815196d19a (patch) | |
tree | 0952aeb7d201429306a3c934fc31a1a580aa8074 /sys/dev/ppbus | |
parent | d9684b5bfdbe67c70705b78dc64f2f73c4955a8b (diff) | |
download | FreeBSD-src-67e9a9c57f0f6b9501cedd6db2a59e815196d19a.zip FreeBSD-src-67e9a9c57f0f6b9501cedd6db2a59e815196d19a.tar.gz |
Add parallel port clock driver.
Submitted by: Sascha Schumann <sascha@schumann.cx>
Diffstat (limited to 'sys/dev/ppbus')
-rw-r--r-- | sys/dev/ppbus/pcfclock.c | 348 |
1 files changed, 348 insertions, 0 deletions
diff --git a/sys/dev/ppbus/pcfclock.c b/sys/dev/ppbus/pcfclock.c new file mode 100644 index 0000000..926a723 --- /dev/null +++ b/sys/dev/ppbus/pcfclock.c @@ -0,0 +1,348 @@ +/* + * Copyright (c) 2000 Sascha Schumann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY SASCHA SCHUMANN ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + * + */ + +#include "pcfclock.h" + +#if NPCFCLOCK > 0 + +#include "opt_pcfclock.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/bus.h> +#include <sys/sockio.h> +#include <sys/mbuf.h> +#include <sys/malloc.h> +#include <sys/kernel.h> +#include <sys/conf.h> +#include <sys/fcntl.h> +#include <sys/uio.h> + +#include <machine/bus.h> +#include <machine/resource.h> +#include <machine/clock.h> /* for DELAY */ + +#include <dev/ppbus/ppbconf.h> +#include <dev/ppbus/ppb_msq.h> +#include <dev/ppbus/ppbio.h> + +#include "ppbus_if.h" + +#define PCFCLOCK_NAME "pcfclock" + +struct pcfclock_data { + int count; + struct ppb_device pcfclock_dev; +}; + +#define DEVTOSOFTC(dev) \ + ((struct pcfclock_data *)device_get_softc(dev)) +#define UNITOSOFTC(unit) \ + ((struct pcfclock_data *)devclass_get_softc(pcfclock_devclass, (unit))) +#define UNITODEVICE(unit) \ + (devclass_get_device(pcfclock_devclass, (unit))) + +static devclass_t pcfclock_devclass; + +static int pcfclock_probe(device_t); +static int pcfclock_attach(device_t); + +static device_method_t pcfclock_methods[] = { + /* device interface */ + DEVMETHOD(device_probe, pcfclock_probe), + DEVMETHOD(device_attach, pcfclock_attach), + + { 0, 0 } +}; + +static driver_t pcfclock_driver = { + PCFCLOCK_NAME, + pcfclock_methods, + sizeof(struct pcfclock_data), +}; + +static d_open_t pcfclock_open; +static d_close_t pcfclock_close; +static d_read_t pcfclock_read; + +#define CDEV_MAJOR 140 +static struct cdevsw pcfclock_cdevsw = { + /* open */ pcfclock_open, + /* close */ pcfclock_close, + /* read */ pcfclock_read, + /* write */ nowrite, + /* ioctl */ noioctl, + /* poll */ nopoll, + /* mmap */ nommap, + /* strategy */ nostrategy, + /* name */ PCFCLOCK_NAME, + /* maj */ CDEV_MAJOR, + /* dump */ nodump, + /* psize */ nopsize, + /* flags */ 0, + /* bmaj */ -1 +}; + +#ifndef PCFCLOCK_MAX_RETRIES +#define PCFCLOCK_MAX_RETRIES 10 +#endif + +#define AFC_HI 0 +#define AFC_LO AUTOFEED + +/* AUTO FEED is used as clock */ +#define AUTOFEED_CLOCK(val) \ + ctr = (ctr & ~(AUTOFEED)) ^ (val); ppb_wctr(ppbus, ctr) + +/* SLCT is used as clock */ +#define CLOCK_OK \ + ((ppb_rstr(ppbus) & SELECT) == (i & 1 ? SELECT : 0)) + +/* PE is used as data */ +#define BIT_SET (ppb_rstr(ppbus)&PERROR) + +/* the first byte sent as reply must be 00001001b */ +#define PCFCLOCK_CORRECT_SYNC(buf) (buf[0] == 9) + +#define NR(buf, off) (buf[off+1]*10+buf[off]) + +/* check for correct input values */ +#define PCFCLOCK_CORRECT_FORMAT(buf) (\ + NR(buf, 14) <= 99 && \ + NR(buf, 12) <= 12 && \ + NR(buf, 10) <= 31 && \ + NR(buf, 6) <= 23 && \ + NR(buf, 4) <= 59 && \ + NR(buf, 2) <= 59) + +#define PCFCLOCK_BATTERY_STATUS_LOW(buf) (buf[8] & 4) + +#define PCFCLOCK_CMD_TIME 0 /* send current time */ +#define PCFCLOCK_CMD_COPY 7 /* copy received signal to PC */ + +static int +pcfclock_probe(device_t dev) +{ + struct pcfclock_data *sc; + static int once; + + device_set_desc(dev, "PCF-1.0"); + + if (!once++) + cdevsw_add(&pcfclock_cdevsw); + + sc = DEVTOSOFTC(dev); + bzero(sc, sizeof(struct pcfclock_data)); + + return (0); +} + +static int +pcfclock_attach(device_t dev) +{ + int unit; + + unit = device_get_unit(dev); + + make_dev(&pcfclock_cdevsw, unit, + UID_ROOT, GID_WHEEL, 0444, PCFCLOCK_NAME "%d", unit); + + return (0); +} + +static int +pcfclock_open(dev_t dev, int flag, int fms, struct proc *p) +{ + u_int unit = minor(dev); + struct pcfclock_data *sc = UNITOSOFTC(unit); + device_t pcfclockdev = UNITODEVICE(unit); + device_t ppbus = device_get_parent(pcfclockdev); + int res; + + if (!sc) + return (ENXIO); + + if ((res = ppb_request_bus(ppbus, pcfclockdev, + (flag & O_NONBLOCK) ? PPB_DONTWAIT : PPB_WAIT))) + return (res); + + sc->count++; + + return (0); +} + +static int +pcfclock_close(dev_t dev, int flags, int fmt, struct proc *p) +{ + u_int unit = minor(dev); + struct pcfclock_data *sc = UNITOSOFTC(unit); + device_t pcfclockdev = UNITODEVICE(unit); + device_t ppbus = device_get_parent(pcfclockdev); + + sc->count--; + if (sc->count == 0) { + ppb_release_bus(ppbus, pcfclockdev); + } + + return (0); +} + +static void +pcfclock_write_cmd(dev_t dev, unsigned char command) +{ + u_int unit = minor(dev); + device_t ppidev = UNITODEVICE(unit); + device_t ppbus = device_get_parent(ppidev); + unsigned char ctr = 14; + char i; + + for (i = 0; i <= 7; i++) { + ppb_wdtr(ppbus, i); + AUTOFEED_CLOCK(i & 1 ? AFC_HI : AFC_LO); + DELAY(3000); + } + ppb_wdtr(ppbus, command); + AUTOFEED_CLOCK(AFC_LO); + DELAY(3000); + AUTOFEED_CLOCK(AFC_HI); +} + +static void +pcfclock_display_data(dev_t dev, char buf[18]) +{ + u_int unit = minor(dev); +#ifdef PCFCLOCK_VERBOSE + int year; + + year = NR(buf, 14); + if (year < 70) + year += 100; + printf(PCFCLOCK_NAME "%d: %02d.%02d.%4d %02d:%02d:%02d, " + "battery status: %s\n", + unit, + NR(buf, 10), NR(buf, 12), 1900 + year, + NR(buf, 6), NR(buf, 4), NR(buf, 2), + PCFCLOCK_BATTERY_STATUS_LOW(buf) ? "LOW" : "ok"); +#else + if (PCFCLOCK_BATTERY_STATUS_LOW(buf)) + printf(PCFCLOCK_NAME "%d: BATTERY STATUS LOW ON\n", + unit); +#endif +} + +static int +pcfclock_read_data(dev_t dev, char *buf, ssize_t bits) +{ + u_int unit = minor(dev); + device_t ppidev = UNITODEVICE(unit); + device_t ppbus = device_get_parent(ppidev); + int i; + char waitfor; + int offset; + + /* one byte per four bits */ + bzero(buf, ((bits + 3) >> 2) + 1); + + waitfor = 100; + for (i = 0; i <= bits; i++) { + /* wait for clock, maximum (waitfor*100) usec */ + while(!CLOCK_OK && --waitfor > 0) + DELAY(100); + + /* timed out? */ + if (!waitfor) + return (EIO); + + waitfor = 100; /* reload */ + + /* give it some time */ + DELAY(500); + + /* calculate offset into buffer */ + offset = i >> 2; + buf[offset] <<= 1; + + if (BIT_SET) + buf[offset] |= 1; + } + + return (0); +} + +static int +pcfclock_read_dev(dev_t dev, char *buf, int maxretries) +{ + u_int unit = minor(dev); + device_t ppidev = UNITODEVICE(unit); + device_t ppbus = device_get_parent(ppidev); + int error = 0; + + ppb_set_mode(ppbus, PPB_COMPATIBLE); + + while (--maxretries > 0) { + pcfclock_write_cmd(dev, PCFCLOCK_CMD_TIME); + if (pcfclock_read_data(dev, buf, 68)) + continue; + + if (!PCFCLOCK_CORRECT_SYNC(buf)) + continue; + + if (!PCFCLOCK_CORRECT_FORMAT(buf)) + continue; + + break; + } + + if (!maxretries) + error = EIO; + + return (error); +} + +static ssize_t +pcfclock_read(dev_t dev, struct uio *uio, int ioflag) +{ + u_int unit = minor(dev); + char buf[18]; + int error = 0; + + error = pcfclock_read_dev(dev, buf, PCFCLOCK_MAX_RETRIES); + + if (error) { + printf(PCFCLOCK_NAME "%d: no PCF found\n", unit); + } else { + pcfclock_display_data(dev, buf); + + uiomove(buf, 18, uio); + } + + return (error); +} + +DRIVER_MODULE(pcfclock, ppbus, pcfclock_driver, pcfclock_devclass, 0, 0); + +#endif /* NPCFCLOCK */ |