summaryrefslogtreecommitdiffstats
path: root/sys/dev/ppbus/pcfclock.c
diff options
context:
space:
mode:
authorjkh <jkh@FreeBSD.org>2000-01-19 18:19:16 +0000
committerjkh <jkh@FreeBSD.org>2000-01-19 18:19:16 +0000
commit67e9a9c57f0f6b9501cedd6db2a59e815196d19a (patch)
tree0952aeb7d201429306a3c934fc31a1a580aa8074 /sys/dev/ppbus/pcfclock.c
parentd9684b5bfdbe67c70705b78dc64f2f73c4955a8b (diff)
downloadFreeBSD-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/pcfclock.c')
-rw-r--r--sys/dev/ppbus/pcfclock.c348
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 */
OpenPOWER on IntegriCloud