pcfclock.c revision 331722
11602Srgrimes/*- 21602Srgrimes * Copyright (c) 2000 Sascha Schumann. All rights reserved. 31602Srgrimes * 41602Srgrimes * Redistribution and use in source and binary forms, with or without 51602Srgrimes * modification, are permitted provided that the following conditions 61602Srgrimes * are met: 71602Srgrimes * 1. Redistributions of source code must retain the above copyright 81602Srgrimes * notice, this list of conditions and the following disclaimer. 91602Srgrimes * 2. Redistributions in binary form must reproduce the above copyright 101602Srgrimes * notice, this list of conditions and the following disclaimer in the 111602Srgrimes * documentation and/or other materials provided with the distribution. 121602Srgrimes * 131602Srgrimes * THIS SOFTWARE IS PROVIDED BY SASCHA SCHUMANN ``AS IS'' AND ANY EXPRESS OR 141602Srgrimes * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 151602Srgrimes * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 161602Srgrimes * EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 171602Srgrimes * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 181602Srgrimes * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 191602Srgrimes * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 201602Srgrimes * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 211602Srgrimes * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 221602Srgrimes * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 231602Srgrimes * 241602Srgrimes * 251602Srgrimes */ 261602Srgrimes 271602Srgrimes#include <sys/cdefs.h> 281602Srgrimes__FBSDID("$FreeBSD: stable/11/sys/dev/ppbus/pcfclock.c 331722 2018-03-29 02:50:57Z eadler $"); 291602Srgrimes 301602Srgrimes#include "opt_pcfclock.h" 311602Srgrimes 321602Srgrimes#include <sys/param.h> 331602Srgrimes#include <sys/systm.h> 3483551Sdillon#include <sys/bus.h> 3583551Sdillon#include <sys/sockio.h> 3683551Sdillon#include <sys/mbuf.h> 371602Srgrimes#include <sys/kernel.h> 3855127Speter#include <sys/module.h> 391602Srgrimes#include <sys/conf.h> 4055127Speter#include <sys/fcntl.h> 411602Srgrimes#include <sys/uio.h> 421602Srgrimes 431602Srgrimes#include <machine/bus.h> 44114330Speter#include <machine/resource.h> 451602Srgrimes 461602Srgrimes#include <dev/ppbus/ppbconf.h> 471602Srgrimes#include <dev/ppbus/ppb_msq.h> 481602Srgrimes#include <dev/ppbus/ppbio.h> 49291406Sjhb 50291406Sjhb#include "ppbus_if.h" 5117141Sjkh 52194186Sed#define PCFCLOCK_NAME "pcfclock" 531602Srgrimes 541602Srgrimesstruct pcfclock_data { 551602Srgrimes device_t dev; 561602Srgrimes struct cdev *cdev; 571602Srgrimes}; 581602Srgrimes 59291406Sjhbstatic devclass_t pcfclock_devclass; 601602Srgrimes 611602Srgrimesstatic d_open_t pcfclock_open; 62291406Sjhbstatic d_close_t pcfclock_close; 63291406Sjhbstatic d_read_t pcfclock_read; 64291406Sjhb 651602Srgrimesstatic struct cdevsw pcfclock_cdevsw = { 661602Srgrimes .d_version = D_VERSION, 67147672Speter .d_open = pcfclock_open, 68147672Speter .d_close = pcfclock_close, 69147672Speter .d_read = pcfclock_read, 70147672Speter .d_name = PCFCLOCK_NAME, 71147672Speter}; 72147672Speter 73291406Sjhb#ifndef PCFCLOCK_MAX_RETRIES 74291406Sjhb#define PCFCLOCK_MAX_RETRIES 10 75291406Sjhb#endif 76147672Speter 77170772Ssimokawa#define AFC_HI 0 78170772Ssimokawa#define AFC_LO AUTOFEED 79291406Sjhb 80170772Ssimokawa/* AUTO FEED is used as clock */ 81170772Ssimokawa#define AUTOFEED_CLOCK(val) \ 82291406Sjhb ctr = (ctr & ~(AUTOFEED)) ^ (val); ppb_wctr(ppbus, ctr) 83291406Sjhb 84147672Speter/* SLCT is used as clock */ 85147672Speter#define CLOCK_OK \ 86147672Speter ((ppb_rstr(ppbus) & SELECT) == (i & 1 ? SELECT : 0)) 87147672Speter 88147672Speter/* PE is used as data */ 89291406Sjhb#define BIT_SET (ppb_rstr(ppbus)&PERROR) 90147672Speter 91147672Speter/* the first byte sent as reply must be 00001001b */ 92291406Sjhb#define PCFCLOCK_CORRECT_SYNC(buf) (buf[0] == 9) 93291406Sjhb 9418798Speter#define NR(buf, off) (buf[off+1]*10+buf[off]) 95147672Speter 96147672Speter/* check for correct input values */ 97147672Speter#define PCFCLOCK_CORRECT_FORMAT(buf) (\ 98147672Speter NR(buf, 14) <= 99 && \ 99291406Sjhb NR(buf, 12) <= 12 && \ 100147672Speter NR(buf, 10) <= 31 && \ 101147672Speter NR(buf, 6) <= 23 && \ 1021602Srgrimes NR(buf, 4) <= 59 && \ 1031602Srgrimes NR(buf, 2) <= 59) 104291406Sjhb 105291406Sjhb#define PCFCLOCK_BATTERY_STATUS_LOW(buf) (buf[8] & 4) 10618798Speter 1071602Srgrimes#define PCFCLOCK_CMD_TIME 0 /* send current time */ 108291406Sjhb#define PCFCLOCK_CMD_COPY 7 /* copy received signal to PC */ 109291406Sjhb 110291406Sjhbstatic void 111157911Speterpcfclock_identify(driver_t *driver, device_t parent) 112291406Sjhb{ 113291406Sjhb 114291406Sjhb device_t dev; 115291406Sjhb 116291406Sjhb dev = device_find_child(parent, PCFCLOCK_NAME, -1); 117291406Sjhb if (!dev) 118291406Sjhb BUS_ADD_CHILD(parent, 0, PCFCLOCK_NAME, -1); 119291406Sjhb} 120147672Speter 121298485Sngiestatic int 1221603Srgrimespcfclock_probe(device_t dev) 1231602Srgrimes{ 1241603Srgrimes 125147672Speter device_set_desc(dev, "PCF-1.0"); 1261602Srgrimes return (0); 127170772Ssimokawa} 128291406Sjhb 129291406Sjhbstatic int 130170772Ssimokawapcfclock_attach(device_t dev) 131170772Ssimokawa{ 132147672Speter struct pcfclock_data *sc = device_get_softc(dev); 133217744Suqs int unit; 134217744Suqs 1351602Srgrimes unit = device_get_unit(dev); 136291406Sjhb 137129452Speter sc->dev = dev; 138129452Speter sc->cdev = make_dev(&pcfclock_cdevsw, unit, 139129452Speter UID_ROOT, GID_WHEEL, 0400, PCFCLOCK_NAME "%d", unit); 140217744Suqs if (sc->cdev == NULL) { 14182263Speter device_printf(dev, "Failed to create character device\n"); 142217744Suqs return (ENXIO); 143217744Suqs } 14482263Speter sc->cdev->si_drv1 = sc; 145291406Sjhb 146129452Speter return (0); 1471602Srgrimes} 1481602Srgrimes 149291406Sjhbstatic int 15082263Speterpcfclock_open(struct cdev *dev, int flag, int fms, struct thread *td) 151129452Speter{ 1521602Srgrimes struct pcfclock_data *sc = dev->si_drv1; 1531602Srgrimes device_t pcfclockdev; 154291406Sjhb device_t ppbus; 155291406Sjhb int res; 156298485Sngie 157298485Sngie if (!sc) 158298485Sngie return (ENXIO); 159298485Sngie pcfclockdev = sc->dev; 160291406Sjhb ppbus = device_get_parent(pcfclockdev); 161129452Speter 162298842Sngie ppb_lock(ppbus); 1631602Srgrimes res = ppb_request_bus(ppbus, pcfclockdev, 1641602Srgrimes (flag & O_NONBLOCK) ? PPB_DONTWAIT : PPB_WAIT); 165147672Speter ppb_unlock(ppbus); 1661602Srgrimes return (res); 1671602Srgrimes} 1681602Srgrimes 1691602Srgrimesstatic int 170291406Sjhbpcfclock_close(struct cdev *dev, int flags, int fmt, struct thread *td) 17118798Speter{ 17218798Speter struct pcfclock_data *sc = dev->si_drv1; 173291406Sjhb device_t pcfclockdev = sc->dev; 174291406Sjhb device_t ppbus = device_get_parent(pcfclockdev); 175291406Sjhb 176291406Sjhb ppb_lock(ppbus); 177291406Sjhb ppb_release_bus(ppbus, pcfclockdev); 178291406Sjhb ppb_unlock(ppbus); 179291406Sjhb 180291406Sjhb return (0); 181291406Sjhb} 182291406Sjhb 183291406Sjhbstatic void 184291406Sjhbpcfclock_write_cmd(struct cdev *dev, unsigned char command) 185291406Sjhb{ 186147672Speter struct pcfclock_data *sc = dev->si_drv1; 187147672Speter device_t pcfclockdev = sc->dev; 1881602Srgrimes device_t ppbus = device_get_parent(pcfclockdev); 18918798Speter unsigned char ctr = 14; 190291406Sjhb char i; 19118798Speter 19218798Speter for (i = 0; i <= 7; i++) { 19318798Speter ppb_wdtr(ppbus, i); 19418798Speter AUTOFEED_CLOCK(i & 1 ? AFC_HI : AFC_LO); 19518798Speter DELAY(3000); 196298485Sngie } 197147672Speter ppb_wdtr(ppbus, command); 198147672Speter AUTOFEED_CLOCK(AFC_LO); 199147672Speter DELAY(3000); 200291406Sjhb AUTOFEED_CLOCK(AFC_HI); 201147672Speter} 202147672Speter 203291406Sjhbstatic void 20418798Speterpcfclock_display_data(struct cdev *dev, char buf[18]) 20518798Speter{ 206291406Sjhb struct pcfclock_data *sc = dev->si_drv1; 207291406Sjhb#ifdef PCFCLOCK_VERBOSE 208291406Sjhb int year; 209291406Sjhb 210129452Speter year = NR(buf, 14); 211147672Speter if (year < 70) 212129452Speter year += 100; 213291406Sjhb 214291406Sjhb device_printf(sc->dev, "%02d.%02d.%4d %02d:%02d:%02d, " 215129452Speter "battery status: %s\n", 216147672Speter NR(buf, 10), NR(buf, 12), 1900 + year, 217291406Sjhb NR(buf, 6), NR(buf, 4), NR(buf, 2), 218291406Sjhb PCFCLOCK_BATTERY_STATUS_LOW(buf) ? "LOW" : "ok"); 219147672Speter#else 220147672Speter if (PCFCLOCK_BATTERY_STATUS_LOW(buf)) 221291406Sjhb device_printf(sc->dev, "BATTERY STATUS LOW ON\n"); 222291406Sjhb#endif 223129452Speter} 224129452Speter 225291406Sjhbstatic int 226291406Sjhbpcfclock_read_data(struct cdev *dev, char *buf, ssize_t bits) 227291406Sjhb{ 228129452Speter struct pcfclock_data *sc = dev->si_drv1; 229129452Speter device_t pcfclockdev = sc->dev; 230291406Sjhb device_t ppbus = device_get_parent(pcfclockdev); 231291406Sjhb int i; 232291406Sjhb char waitfor; 233291406Sjhb int offset; 234291406Sjhb 235297359Sjhb /* one byte per four bits */ 236291406Sjhb bzero(buf, ((bits + 3) >> 2) + 1); 237291406Sjhb 238291406Sjhb waitfor = 100; 239291406Sjhb for (i = 0; i <= bits; i++) { 240291406Sjhb /* wait for clock, maximum (waitfor*100) usec */ 241291406Sjhb while (!CLOCK_OK && --waitfor > 0) 242291406Sjhb DELAY(100); 243147672Speter 244129452Speter /* timed out? */ 245291406Sjhb if (!waitfor) 246291406Sjhb return (EIO); 247129452Speter 248147672Speter waitfor = 100; /* reload */ 249291406Sjhb 250291406Sjhb /* give it some time */ 251129452Speter DELAY(500); 252129452Speter 253291406Sjhb /* calculate offset into buffer */ 254291406Sjhb offset = i >> 2; 255147672Speter buf[offset] <<= 1; 256147672Speter 257291406Sjhb if (BIT_SET) 258291406Sjhb buf[offset] |= 1; 259291406Sjhb } 260129452Speter 261129452Speter return (0); 26218798Speter} 263291406Sjhb 264291406Sjhbstatic int 265291406Sjhbpcfclock_read_dev(struct cdev *dev, char *buf, int maxretries) 266291406Sjhb{ 267291406Sjhb struct pcfclock_data *sc = dev->si_drv1; 268147672Speter device_t pcfclockdev = sc->dev; 269147672Speter device_t ppbus = device_get_parent(pcfclockdev); 270147672Speter int error = 0; 271291406Sjhb 272147672Speter ppb_set_mode(ppbus, PPB_COMPATIBLE); 273147672Speter 274291406Sjhb while (--maxretries > 0) { 27528318Stegge pcfclock_write_cmd(dev, PCFCLOCK_CMD_TIME); 27628318Stegge if (pcfclock_read_data(dev, buf, 68)) 277291406Sjhb continue; 278291406Sjhb 27918798Speter if (!PCFCLOCK_CORRECT_SYNC(buf)) 280147672Speter continue; 281291406Sjhb 282291406Sjhb if (!PCFCLOCK_CORRECT_FORMAT(buf)) 283147672Speter continue; 284147672Speter 285291406Sjhb break; 286291406Sjhb } 28718798Speter 28818798Speter if (!maxretries) 289291406Sjhb error = EIO; 290291406Sjhb 29118798Speter return (error); 29218798Speter} 29318798Speter 294291406Sjhbstatic int 295147672Speterpcfclock_read(struct cdev *dev, struct uio *uio, int ioflag) 296147672Speter{ 297291406Sjhb struct pcfclock_data *sc = dev->si_drv1; 298147672Speter device_t ppbus; 299147672Speter char buf[18]; 300291406Sjhb int error = 0; 30118798Speter 30218798Speter if (uio->uio_resid < 18) 303291406Sjhb return (ERANGE); 30418798Speter 3051602Srgrimes ppbus = device_get_parent(sc->dev); 3061602Srgrimes ppb_lock(ppbus); 307291406Sjhb error = pcfclock_read_dev(dev, buf, PCFCLOCK_MAX_RETRIES); 308291406Sjhb ppb_unlock(ppbus); 30918798Speter 310147672Speter if (error) { 311147672Speter device_printf(sc->dev, "no PCF found\n"); 312147672Speter } else { 313147672Speter pcfclock_display_data(dev, buf); 314147672Speter 315291406Sjhb uiomove(buf, 18, uio); 3161602Srgrimes } 317291406Sjhb 318291406Sjhb return (error); 319316126Sngie} 320291406Sjhb 321291406Sjhbstatic device_method_t pcfclock_methods[] = { 322291406Sjhb /* device interface */ 323291406Sjhb DEVMETHOD(device_identify, pcfclock_identify), 324291406Sjhb DEVMETHOD(device_probe, pcfclock_probe), 325291406Sjhb DEVMETHOD(device_attach, pcfclock_attach), 326291406Sjhb 327291406Sjhb { 0, 0 } 328291406Sjhb}; 329316126Sngie 330291406Sjhbstatic driver_t pcfclock_driver = { 331291406Sjhb PCFCLOCK_NAME, 332291406Sjhb pcfclock_methods, 333291406Sjhb sizeof(struct pcfclock_data), 334291406Sjhb}; 335291406Sjhb 336291406SjhbDRIVER_MODULE(pcfclock, ppbus, pcfclock_driver, pcfclock_devclass, 0, 0); 337291406Sjhb