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