xref: /freebsd/sys/dev/ppbus/pcfclock.c (revision a05a680469a7ac77b195021fed74e3aa58152dd7)
1098ca2bdSWarner Losh /*-
24d846d26SWarner Losh  * SPDX-License-Identifier: BSD-2-Clause
3718cf2ccSPedro F. Giffuni  *
46417807eSJordan K. Hubbard  * Copyright (c) 2000 Sascha Schumann. All rights reserved.
56417807eSJordan K. Hubbard  *
66417807eSJordan K. Hubbard  * Redistribution and use in source and binary forms, with or without
76417807eSJordan K. Hubbard  * modification, are permitted provided that the following conditions
86417807eSJordan K. Hubbard  * are met:
96417807eSJordan K. Hubbard  * 1. Redistributions of source code must retain the above copyright
106417807eSJordan K. Hubbard  *    notice, this list of conditions and the following disclaimer.
116417807eSJordan K. Hubbard  * 2. Redistributions in binary form must reproduce the above copyright
126417807eSJordan K. Hubbard  *    notice, this list of conditions and the following disclaimer in the
136417807eSJordan K. Hubbard  *    documentation and/or other materials provided with the distribution.
146417807eSJordan K. Hubbard  *
156417807eSJordan K. Hubbard  * THIS SOFTWARE IS PROVIDED BY SASCHA SCHUMANN ``AS IS'' AND ANY EXPRESS OR
166417807eSJordan K. Hubbard  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
176417807eSJordan K. Hubbard  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO
1877a7881dSPoul-Henning Kamp  * EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
196417807eSJordan K. Hubbard  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
206417807eSJordan K. Hubbard  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
216417807eSJordan K. Hubbard  * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
226417807eSJordan K. Hubbard  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
236417807eSJordan K. Hubbard  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
246417807eSJordan K. Hubbard  * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
256417807eSJordan K. Hubbard  *
266417807eSJordan K. Hubbard  *
276417807eSJordan K. Hubbard  */
286417807eSJordan K. Hubbard 
29aad970f1SDavid E. O'Brien #include <sys/cdefs.h>
306417807eSJordan K. Hubbard #include "opt_pcfclock.h"
316417807eSJordan K. Hubbard 
326417807eSJordan K. Hubbard #include <sys/param.h>
336417807eSJordan K. Hubbard #include <sys/systm.h>
346417807eSJordan K. Hubbard #include <sys/bus.h>
356417807eSJordan K. Hubbard #include <sys/sockio.h>
366417807eSJordan K. Hubbard #include <sys/mbuf.h>
376417807eSJordan K. Hubbard #include <sys/kernel.h>
38fe12f24bSPoul-Henning Kamp #include <sys/module.h>
396417807eSJordan K. Hubbard #include <sys/conf.h>
406417807eSJordan K. Hubbard #include <sys/fcntl.h>
416417807eSJordan K. Hubbard #include <sys/uio.h>
426417807eSJordan K. Hubbard 
436417807eSJordan K. Hubbard #include <machine/bus.h>
446417807eSJordan K. Hubbard #include <machine/resource.h>
456417807eSJordan K. Hubbard 
466417807eSJordan K. Hubbard #include <dev/ppbus/ppbconf.h>
476417807eSJordan K. Hubbard #include <dev/ppbus/ppb_msq.h>
486417807eSJordan K. Hubbard #include <dev/ppbus/ppbio.h>
496417807eSJordan K. Hubbard 
506417807eSJordan K. Hubbard #include "ppbus_if.h"
516417807eSJordan K. Hubbard 
526417807eSJordan K. Hubbard #define PCFCLOCK_NAME "pcfclock"
536417807eSJordan K. Hubbard 
546417807eSJordan K. Hubbard struct pcfclock_data {
55ae6b868aSJohn Baldwin 	device_t dev;
56ae6b868aSJohn Baldwin 	struct cdev *cdev;
576417807eSJordan K. Hubbard };
586417807eSJordan K. Hubbard 
596417807eSJordan K. Hubbard static	d_open_t		pcfclock_open;
606417807eSJordan K. Hubbard static	d_close_t		pcfclock_close;
616417807eSJordan K. Hubbard static	d_read_t		pcfclock_read;
626417807eSJordan K. Hubbard 
636417807eSJordan K. Hubbard static struct cdevsw pcfclock_cdevsw = {
64dc08ffecSPoul-Henning Kamp 	.d_version =	D_VERSION,
657ac40f5fSPoul-Henning Kamp 	.d_open =	pcfclock_open,
667ac40f5fSPoul-Henning Kamp 	.d_close =	pcfclock_close,
677ac40f5fSPoul-Henning Kamp 	.d_read =	pcfclock_read,
687ac40f5fSPoul-Henning Kamp 	.d_name =	PCFCLOCK_NAME,
696417807eSJordan K. Hubbard };
706417807eSJordan K. Hubbard 
716417807eSJordan K. Hubbard #ifndef PCFCLOCK_MAX_RETRIES
726417807eSJordan K. Hubbard #define PCFCLOCK_MAX_RETRIES 10
736417807eSJordan K. Hubbard #endif
746417807eSJordan K. Hubbard 
756417807eSJordan K. Hubbard #define AFC_HI 0
766417807eSJordan K. Hubbard #define AFC_LO AUTOFEED
776417807eSJordan K. Hubbard 
786417807eSJordan K. Hubbard /* AUTO FEED is used as clock */
796417807eSJordan K. Hubbard #define AUTOFEED_CLOCK(val) \
806417807eSJordan K. Hubbard 	ctr = (ctr & ~(AUTOFEED)) ^ (val); ppb_wctr(ppbus, ctr)
816417807eSJordan K. Hubbard 
826417807eSJordan K. Hubbard /* SLCT is used as clock */
836417807eSJordan K. Hubbard #define CLOCK_OK \
846417807eSJordan K. Hubbard 	((ppb_rstr(ppbus) & SELECT) == (i & 1 ? SELECT : 0))
856417807eSJordan K. Hubbard 
866417807eSJordan K. Hubbard /* PE is used as data */
876417807eSJordan K. Hubbard #define BIT_SET (ppb_rstr(ppbus)&PERROR)
886417807eSJordan K. Hubbard 
896417807eSJordan K. Hubbard /* the first byte sent as reply must be 00001001b */
906417807eSJordan K. Hubbard #define PCFCLOCK_CORRECT_SYNC(buf) (buf[0] == 9)
916417807eSJordan K. Hubbard 
926417807eSJordan K. Hubbard #define NR(buf, off) (buf[off+1]*10+buf[off])
936417807eSJordan K. Hubbard 
946417807eSJordan K. Hubbard /* check for correct input values */
956417807eSJordan K. Hubbard #define PCFCLOCK_CORRECT_FORMAT(buf) (\
966417807eSJordan K. Hubbard 	NR(buf, 14) <= 99 && \
976417807eSJordan K. Hubbard 	NR(buf, 12) <= 12 && \
986417807eSJordan K. Hubbard 	NR(buf, 10) <= 31 && \
996417807eSJordan K. Hubbard 	NR(buf,  6) <= 23 && \
1006417807eSJordan K. Hubbard 	NR(buf,  4) <= 59 && \
1016417807eSJordan K. Hubbard 	NR(buf,  2) <= 59)
1026417807eSJordan K. Hubbard 
1036417807eSJordan K. Hubbard #define PCFCLOCK_BATTERY_STATUS_LOW(buf) (buf[8] & 4)
1046417807eSJordan K. Hubbard 
1056417807eSJordan K. Hubbard #define PCFCLOCK_CMD_TIME 0		/* send current time */
1066417807eSJordan K. Hubbard #define PCFCLOCK_CMD_COPY 7 	/* copy received signal to PC */
1076417807eSJordan K. Hubbard 
1080f063508SPeter Wemm static void
1090f063508SPeter Wemm pcfclock_identify(driver_t *driver, device_t parent)
1100f063508SPeter Wemm {
1110f063508SPeter Wemm 
112a5c7e3bbSGuido van Rooij 	device_t dev;
113a5c7e3bbSGuido van Rooij 
114ae6b868aSJohn Baldwin 	dev = device_find_child(parent, PCFCLOCK_NAME, -1);
115a5c7e3bbSGuido van Rooij 	if (!dev)
116*a05a6804SWarner Losh 		BUS_ADD_CHILD(parent, 0, PCFCLOCK_NAME, DEVICE_UNIT_ANY);
1170f063508SPeter Wemm }
1180f063508SPeter Wemm 
1196417807eSJordan K. Hubbard static int
1206417807eSJordan K. Hubbard pcfclock_probe(device_t dev)
1216417807eSJordan K. Hubbard {
1226417807eSJordan K. Hubbard 
1236417807eSJordan K. Hubbard 	device_set_desc(dev, "PCF-1.0");
1246417807eSJordan K. Hubbard 	return (0);
1256417807eSJordan K. Hubbard }
1266417807eSJordan K. Hubbard 
1276417807eSJordan K. Hubbard static int
1286417807eSJordan K. Hubbard pcfclock_attach(device_t dev)
1296417807eSJordan K. Hubbard {
130ae6b868aSJohn Baldwin 	struct pcfclock_data *sc = device_get_softc(dev);
1316417807eSJordan K. Hubbard 	int unit;
1326417807eSJordan K. Hubbard 
1336417807eSJordan K. Hubbard 	unit = device_get_unit(dev);
1346417807eSJordan K. Hubbard 
135ae6b868aSJohn Baldwin 	sc->dev = dev;
136ae6b868aSJohn Baldwin 	sc->cdev = make_dev(&pcfclock_cdevsw, unit,
137805229ebSRobert Watson 			UID_ROOT, GID_WHEEL, 0400, PCFCLOCK_NAME "%d", unit);
138ae6b868aSJohn Baldwin 	if (sc->cdev == NULL) {
139ae6b868aSJohn Baldwin 		device_printf(dev, "Failed to create character device\n");
140ae6b868aSJohn Baldwin 		return (ENXIO);
141ae6b868aSJohn Baldwin 	}
142ae6b868aSJohn Baldwin 	sc->cdev->si_drv1 = sc;
1436417807eSJordan K. Hubbard 
1446417807eSJordan K. Hubbard 	return (0);
1456417807eSJordan K. Hubbard }
1466417807eSJordan K. Hubbard 
1476417807eSJordan K. Hubbard static int
14889c9c53dSPoul-Henning Kamp pcfclock_open(struct cdev *dev, int flag, int fms, struct thread *td)
1496417807eSJordan K. Hubbard {
150ae6b868aSJohn Baldwin 	struct pcfclock_data *sc = dev->si_drv1;
151ef1be3b0SChristian Brueffer 	device_t pcfclockdev;
152ef1be3b0SChristian Brueffer 	device_t ppbus;
1536417807eSJordan K. Hubbard 	int res;
1546417807eSJordan K. Hubbard 
1556417807eSJordan K. Hubbard 	if (!sc)
1566417807eSJordan K. Hubbard 		return (ENXIO);
157ef1be3b0SChristian Brueffer 	pcfclockdev = sc->dev;
158ef1be3b0SChristian Brueffer 	ppbus = device_get_parent(pcfclockdev);
1596417807eSJordan K. Hubbard 
1602067d312SJohn Baldwin 	ppb_lock(ppbus);
1612067d312SJohn Baldwin 	res = ppb_request_bus(ppbus, pcfclockdev,
1622067d312SJohn Baldwin 	    (flag & O_NONBLOCK) ? PPB_DONTWAIT : PPB_WAIT);
1632067d312SJohn Baldwin 	ppb_unlock(ppbus);
1646417807eSJordan K. Hubbard 	return (res);
1656417807eSJordan K. Hubbard }
1666417807eSJordan K. Hubbard 
1676417807eSJordan K. Hubbard static int
16889c9c53dSPoul-Henning Kamp pcfclock_close(struct cdev *dev, int flags, int fmt, struct thread *td)
1696417807eSJordan K. Hubbard {
170ae6b868aSJohn Baldwin 	struct pcfclock_data *sc = dev->si_drv1;
171ae6b868aSJohn Baldwin 	device_t pcfclockdev = sc->dev;
1726417807eSJordan K. Hubbard 	device_t ppbus = device_get_parent(pcfclockdev);
1736417807eSJordan K. Hubbard 
1742067d312SJohn Baldwin 	ppb_lock(ppbus);
1756417807eSJordan K. Hubbard 	ppb_release_bus(ppbus, pcfclockdev);
1762067d312SJohn Baldwin 	ppb_unlock(ppbus);
1776417807eSJordan K. Hubbard 
1786417807eSJordan K. Hubbard 	return (0);
1796417807eSJordan K. Hubbard }
1806417807eSJordan K. Hubbard 
1816417807eSJordan K. Hubbard static void
18289c9c53dSPoul-Henning Kamp pcfclock_write_cmd(struct cdev *dev, unsigned char command)
1836417807eSJordan K. Hubbard {
184ae6b868aSJohn Baldwin 	struct pcfclock_data *sc = dev->si_drv1;
185ae6b868aSJohn Baldwin 	device_t pcfclockdev = sc->dev;
186ae6b868aSJohn Baldwin 	device_t ppbus = device_get_parent(pcfclockdev);
1876417807eSJordan K. Hubbard 	unsigned char ctr = 14;
1886417807eSJordan K. Hubbard 	char i;
1896417807eSJordan K. Hubbard 
1906417807eSJordan K. Hubbard 	for (i = 0; i <= 7; i++) {
1916417807eSJordan K. Hubbard 		ppb_wdtr(ppbus, i);
1926417807eSJordan K. Hubbard 		AUTOFEED_CLOCK(i & 1 ? AFC_HI : AFC_LO);
1936417807eSJordan K. Hubbard 		DELAY(3000);
1946417807eSJordan K. Hubbard 	}
1956417807eSJordan K. Hubbard 	ppb_wdtr(ppbus, command);
1966417807eSJordan K. Hubbard 	AUTOFEED_CLOCK(AFC_LO);
1976417807eSJordan K. Hubbard 	DELAY(3000);
1986417807eSJordan K. Hubbard 	AUTOFEED_CLOCK(AFC_HI);
1996417807eSJordan K. Hubbard }
2006417807eSJordan K. Hubbard 
2016417807eSJordan K. Hubbard static void
20289c9c53dSPoul-Henning Kamp pcfclock_display_data(struct cdev *dev, char buf[18])
2036417807eSJordan K. Hubbard {
204ae6b868aSJohn Baldwin 	struct pcfclock_data *sc = dev->si_drv1;
2056417807eSJordan K. Hubbard #ifdef PCFCLOCK_VERBOSE
2066417807eSJordan K. Hubbard 	int year;
2076417807eSJordan K. Hubbard 
2086417807eSJordan K. Hubbard 	year = NR(buf, 14);
2096417807eSJordan K. Hubbard 	if (year < 70)
2106417807eSJordan K. Hubbard 		year += 100;
21177a7881dSPoul-Henning Kamp 
212ae6b868aSJohn Baldwin 	device_printf(sc->dev, "%02d.%02d.%4d %02d:%02d:%02d, "
2136417807eSJordan K. Hubbard 			"battery status: %s\n",
2146417807eSJordan K. Hubbard 			NR(buf, 10), NR(buf, 12), 1900 + year,
2156417807eSJordan K. Hubbard 			NR(buf, 6), NR(buf, 4), NR(buf, 2),
2166417807eSJordan K. Hubbard 			PCFCLOCK_BATTERY_STATUS_LOW(buf) ? "LOW" : "ok");
2176417807eSJordan K. Hubbard #else
2186417807eSJordan K. Hubbard 	if (PCFCLOCK_BATTERY_STATUS_LOW(buf))
219ae6b868aSJohn Baldwin 		device_printf(sc->dev, "BATTERY STATUS LOW ON\n");
2206417807eSJordan K. Hubbard #endif
2216417807eSJordan K. Hubbard }
2226417807eSJordan K. Hubbard 
2236417807eSJordan K. Hubbard static int
22489c9c53dSPoul-Henning Kamp pcfclock_read_data(struct cdev *dev, char *buf, ssize_t bits)
2256417807eSJordan K. Hubbard {
226ae6b868aSJohn Baldwin 	struct pcfclock_data *sc = dev->si_drv1;
227ae6b868aSJohn Baldwin 	device_t pcfclockdev = sc->dev;
228ae6b868aSJohn Baldwin 	device_t ppbus = device_get_parent(pcfclockdev);
2296417807eSJordan K. Hubbard 	int i;
2306417807eSJordan K. Hubbard 	char waitfor;
2316417807eSJordan K. Hubbard 	int offset;
2326417807eSJordan K. Hubbard 
2336417807eSJordan K. Hubbard 	/* one byte per four bits */
2346417807eSJordan K. Hubbard 	bzero(buf, ((bits + 3) >> 2) + 1);
2356417807eSJordan K. Hubbard 
2366417807eSJordan K. Hubbard 	waitfor = 100;
2376417807eSJordan K. Hubbard 	for (i = 0; i <= bits; i++) {
2386417807eSJordan K. Hubbard 		/* wait for clock, maximum (waitfor*100) usec */
2396417807eSJordan K. Hubbard 		while (!CLOCK_OK && --waitfor > 0)
2406417807eSJordan K. Hubbard 			DELAY(100);
2416417807eSJordan K. Hubbard 
2426417807eSJordan K. Hubbard 		/* timed out? */
2436417807eSJordan K. Hubbard 		if (!waitfor)
2446417807eSJordan K. Hubbard 			return (EIO);
2456417807eSJordan K. Hubbard 
2466417807eSJordan K. Hubbard 		waitfor = 100; /* reload */
2476417807eSJordan K. Hubbard 
2486417807eSJordan K. Hubbard 		/* give it some time */
2496417807eSJordan K. Hubbard 		DELAY(500);
2506417807eSJordan K. Hubbard 
2516417807eSJordan K. Hubbard 		/* calculate offset into buffer */
2526417807eSJordan K. Hubbard 		offset = i >> 2;
2536417807eSJordan K. Hubbard 		buf[offset] <<= 1;
2546417807eSJordan K. Hubbard 
2556417807eSJordan K. Hubbard 		if (BIT_SET)
2566417807eSJordan K. Hubbard 			buf[offset] |= 1;
2576417807eSJordan K. Hubbard 	}
2586417807eSJordan K. Hubbard 
2596417807eSJordan K. Hubbard 	return (0);
2606417807eSJordan K. Hubbard }
2616417807eSJordan K. Hubbard 
2626417807eSJordan K. Hubbard static int
26389c9c53dSPoul-Henning Kamp pcfclock_read_dev(struct cdev *dev, char *buf, int maxretries)
2646417807eSJordan K. Hubbard {
265ae6b868aSJohn Baldwin 	struct pcfclock_data *sc = dev->si_drv1;
266ae6b868aSJohn Baldwin 	device_t pcfclockdev = sc->dev;
267ae6b868aSJohn Baldwin 	device_t ppbus = device_get_parent(pcfclockdev);
2686417807eSJordan K. Hubbard 	int error = 0;
2696417807eSJordan K. Hubbard 
2706417807eSJordan K. Hubbard 	ppb_set_mode(ppbus, PPB_COMPATIBLE);
2716417807eSJordan K. Hubbard 
2726417807eSJordan K. Hubbard 	while (--maxretries > 0) {
2736417807eSJordan K. Hubbard 		pcfclock_write_cmd(dev, PCFCLOCK_CMD_TIME);
2746417807eSJordan K. Hubbard 		if (pcfclock_read_data(dev, buf, 68))
2756417807eSJordan K. Hubbard 			continue;
2766417807eSJordan K. Hubbard 
2776417807eSJordan K. Hubbard 		if (!PCFCLOCK_CORRECT_SYNC(buf))
2786417807eSJordan K. Hubbard 			continue;
2796417807eSJordan K. Hubbard 
2806417807eSJordan K. Hubbard 		if (!PCFCLOCK_CORRECT_FORMAT(buf))
2816417807eSJordan K. Hubbard 			continue;
2826417807eSJordan K. Hubbard 
2836417807eSJordan K. Hubbard 		break;
2846417807eSJordan K. Hubbard 	}
2856417807eSJordan K. Hubbard 
2866417807eSJordan K. Hubbard 	if (!maxretries)
2876417807eSJordan K. Hubbard 		error = EIO;
2886417807eSJordan K. Hubbard 
2896417807eSJordan K. Hubbard 	return (error);
2906417807eSJordan K. Hubbard }
2916417807eSJordan K. Hubbard 
2928d667a42SJohn Baldwin static int
29389c9c53dSPoul-Henning Kamp pcfclock_read(struct cdev *dev, struct uio *uio, int ioflag)
2946417807eSJordan K. Hubbard {
295ae6b868aSJohn Baldwin 	struct pcfclock_data *sc = dev->si_drv1;
2962067d312SJohn Baldwin 	device_t ppbus;
2976417807eSJordan K. Hubbard 	char buf[18];
2986417807eSJordan K. Hubbard 	int error = 0;
2996417807eSJordan K. Hubbard 
3004ca7c740SSheldon Hearn 	if (uio->uio_resid < 18)
3014ca7c740SSheldon Hearn 		return (ERANGE);
3024ca7c740SSheldon Hearn 
3032067d312SJohn Baldwin 	ppbus = device_get_parent(sc->dev);
3042067d312SJohn Baldwin 	ppb_lock(ppbus);
3056417807eSJordan K. Hubbard 	error = pcfclock_read_dev(dev, buf, PCFCLOCK_MAX_RETRIES);
3062067d312SJohn Baldwin 	ppb_unlock(ppbus);
3076417807eSJordan K. Hubbard 
3086417807eSJordan K. Hubbard 	if (error) {
309ae6b868aSJohn Baldwin 		device_printf(sc->dev, "no PCF found\n");
3106417807eSJordan K. Hubbard 	} else {
3116417807eSJordan K. Hubbard 		pcfclock_display_data(dev, buf);
3126417807eSJordan K. Hubbard 
3136417807eSJordan K. Hubbard 		uiomove(buf, 18, uio);
3146417807eSJordan K. Hubbard 	}
3156417807eSJordan K. Hubbard 
3166417807eSJordan K. Hubbard 	return (error);
3176417807eSJordan K. Hubbard }
3186417807eSJordan K. Hubbard 
3190f063508SPeter Wemm static device_method_t pcfclock_methods[] = {
3200f063508SPeter Wemm 	/* device interface */
3210f063508SPeter Wemm 	DEVMETHOD(device_identify,	pcfclock_identify),
3220f063508SPeter Wemm 	DEVMETHOD(device_probe,		pcfclock_probe),
3230f063508SPeter Wemm 	DEVMETHOD(device_attach,	pcfclock_attach),
3240f063508SPeter Wemm 	{ 0, 0 }
3250f063508SPeter Wemm };
3260f063508SPeter Wemm 
3270f063508SPeter Wemm static driver_t pcfclock_driver = {
3280f063508SPeter Wemm 	PCFCLOCK_NAME,
3290f063508SPeter Wemm 	pcfclock_methods,
3300f063508SPeter Wemm 	sizeof(struct pcfclock_data),
3310f063508SPeter Wemm };
3320f063508SPeter Wemm 
3336d588090SJohn Baldwin DRIVER_MODULE(pcfclock, ppbus, pcfclock_driver, 0, 0);
334