pcfclock.c revision 185003
1139749Simp/*- 256293Sjkh * Copyright (c) 2000 Sascha Schumann. All rights reserved. 356293Sjkh * 456293Sjkh * Redistribution and use in source and binary forms, with or without 556293Sjkh * modification, are permitted provided that the following conditions 656293Sjkh * are met: 756293Sjkh * 1. Redistributions of source code must retain the above copyright 856293Sjkh * notice, this list of conditions and the following disclaimer. 956293Sjkh * 2. Redistributions in binary form must reproduce the above copyright 1056293Sjkh * notice, this list of conditions and the following disclaimer in the 1156293Sjkh * documentation and/or other materials provided with the distribution. 12185003Sjhb * 1356293Sjkh * THIS SOFTWARE IS PROVIDED BY SASCHA SCHUMANN ``AS IS'' AND ANY EXPRESS OR 1456293Sjkh * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 1556293Sjkh * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 1677837Sphk * EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 1756293Sjkh * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 1856293Sjkh * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 1956293Sjkh * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 2056293Sjkh * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 2156293Sjkh * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 2256293Sjkh * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 2356293Sjkh * 2456293Sjkh * 2556293Sjkh */ 2656293Sjkh 27119418Sobrien#include <sys/cdefs.h> 28119418Sobrien__FBSDID("$FreeBSD: head/sys/dev/ppbus/pcfclock.c 185003 2008-11-16 17:42:02Z jhb $"); 29119418Sobrien 3056293Sjkh#include "opt_pcfclock.h" 3156293Sjkh 3256293Sjkh#include <sys/param.h> 3356293Sjkh#include <sys/systm.h> 3456293Sjkh#include <sys/bus.h> 3556293Sjkh#include <sys/sockio.h> 3656293Sjkh#include <sys/mbuf.h> 3756293Sjkh#include <sys/kernel.h> 38129879Sphk#include <sys/module.h> 3956293Sjkh#include <sys/conf.h> 4056293Sjkh#include <sys/fcntl.h> 4156293Sjkh#include <sys/uio.h> 4256293Sjkh 4356293Sjkh#include <machine/bus.h> 4456293Sjkh#include <machine/resource.h> 4556293Sjkh 4656293Sjkh#include <dev/ppbus/ppbconf.h> 4756293Sjkh#include <dev/ppbus/ppb_msq.h> 4856293Sjkh#include <dev/ppbus/ppbio.h> 4956293Sjkh 5056293Sjkh#include "ppbus_if.h" 5156293Sjkh 5256293Sjkh#define PCFCLOCK_NAME "pcfclock" 5356293Sjkh 5456293Sjkhstruct pcfclock_data { 55184130Sjhb device_t dev; 56184130Sjhb struct cdev *cdev; 5756293Sjkh int count; 5856293Sjkh}; 5956293Sjkh 6056293Sjkhstatic devclass_t pcfclock_devclass; 6156293Sjkh 6256293Sjkhstatic d_open_t pcfclock_open; 6356293Sjkhstatic d_close_t pcfclock_close; 6456293Sjkhstatic d_read_t pcfclock_read; 6556293Sjkh 6656293Sjkhstatic struct cdevsw pcfclock_cdevsw = { 67126080Sphk .d_version = D_VERSION, 68126080Sphk .d_flags = D_NEEDGIANT, 69111815Sphk .d_open = pcfclock_open, 70111815Sphk .d_close = pcfclock_close, 71111815Sphk .d_read = pcfclock_read, 72111815Sphk .d_name = PCFCLOCK_NAME, 7356293Sjkh}; 7456293Sjkh 7556293Sjkh#ifndef PCFCLOCK_MAX_RETRIES 7656293Sjkh#define PCFCLOCK_MAX_RETRIES 10 7756293Sjkh#endif 7856293Sjkh 7956293Sjkh#define AFC_HI 0 8056293Sjkh#define AFC_LO AUTOFEED 8156293Sjkh 8256293Sjkh/* AUTO FEED is used as clock */ 8356293Sjkh#define AUTOFEED_CLOCK(val) \ 8456293Sjkh ctr = (ctr & ~(AUTOFEED)) ^ (val); ppb_wctr(ppbus, ctr) 8556293Sjkh 8656293Sjkh/* SLCT is used as clock */ 8756293Sjkh#define CLOCK_OK \ 8856293Sjkh ((ppb_rstr(ppbus) & SELECT) == (i & 1 ? SELECT : 0)) 8956293Sjkh 9056293Sjkh/* PE is used as data */ 9156293Sjkh#define BIT_SET (ppb_rstr(ppbus)&PERROR) 9256293Sjkh 9356293Sjkh/* the first byte sent as reply must be 00001001b */ 9456293Sjkh#define PCFCLOCK_CORRECT_SYNC(buf) (buf[0] == 9) 9556293Sjkh 9656293Sjkh#define NR(buf, off) (buf[off+1]*10+buf[off]) 9756293Sjkh 9856293Sjkh/* check for correct input values */ 9956293Sjkh#define PCFCLOCK_CORRECT_FORMAT(buf) (\ 10056293Sjkh NR(buf, 14) <= 99 && \ 10156293Sjkh NR(buf, 12) <= 12 && \ 10256293Sjkh NR(buf, 10) <= 31 && \ 10356293Sjkh NR(buf, 6) <= 23 && \ 10456293Sjkh NR(buf, 4) <= 59 && \ 10556293Sjkh NR(buf, 2) <= 59) 10656293Sjkh 10756293Sjkh#define PCFCLOCK_BATTERY_STATUS_LOW(buf) (buf[8] & 4) 108185003Sjhb 10956293Sjkh#define PCFCLOCK_CMD_TIME 0 /* send current time */ 11056293Sjkh#define PCFCLOCK_CMD_COPY 7 /* copy received signal to PC */ 11156293Sjkh 11256455Speterstatic void 11356455Speterpcfclock_identify(driver_t *driver, device_t parent) 11456455Speter{ 11556455Speter 116127189Sguido device_t dev; 117127189Sguido 118184130Sjhb dev = device_find_child(parent, PCFCLOCK_NAME, -1); 119127189Sguido if (!dev) 120127189Sguido BUS_ADD_CHILD(parent, 0, PCFCLOCK_NAME, -1); 12156455Speter} 12256455Speter 12356293Sjkhstatic int 12456293Sjkhpcfclock_probe(device_t dev) 12556293Sjkh{ 12656293Sjkh 12756293Sjkh device_set_desc(dev, "PCF-1.0"); 12856293Sjkh return (0); 12956293Sjkh} 13056293Sjkh 13156293Sjkhstatic int 13256293Sjkhpcfclock_attach(device_t dev) 13356293Sjkh{ 134184130Sjhb struct pcfclock_data *sc = device_get_softc(dev); 13556293Sjkh int unit; 136185003Sjhb 13756293Sjkh unit = device_get_unit(dev); 13856293Sjkh 139184130Sjhb sc->dev = dev; 140184130Sjhb sc->cdev = make_dev(&pcfclock_cdevsw, unit, 141108321Srwatson UID_ROOT, GID_WHEEL, 0400, PCFCLOCK_NAME "%d", unit); 142184130Sjhb if (sc->cdev == NULL) { 143184130Sjhb device_printf(dev, "Failed to create character device\n"); 144184130Sjhb return (ENXIO); 145184130Sjhb } 146184130Sjhb sc->cdev->si_drv1 = sc; 14756293Sjkh 14856293Sjkh return (0); 14956293Sjkh} 15056293Sjkh 151185003Sjhbstatic int 152130585Sphkpcfclock_open(struct cdev *dev, int flag, int fms, struct thread *td) 15356293Sjkh{ 154184130Sjhb struct pcfclock_data *sc = dev->si_drv1; 155184130Sjhb device_t pcfclockdev = sc->dev; 15656293Sjkh device_t ppbus = device_get_parent(pcfclockdev); 15756293Sjkh int res; 158185003Sjhb 15956293Sjkh if (!sc) 16056293Sjkh return (ENXIO); 16156293Sjkh 16256293Sjkh if ((res = ppb_request_bus(ppbus, pcfclockdev, 16356293Sjkh (flag & O_NONBLOCK) ? PPB_DONTWAIT : PPB_WAIT))) 16456293Sjkh return (res); 16556293Sjkh 16656293Sjkh sc->count++; 167185003Sjhb 16856293Sjkh return (0); 16956293Sjkh} 17056293Sjkh 17156293Sjkhstatic int 172130585Sphkpcfclock_close(struct cdev *dev, int flags, int fmt, struct thread *td) 17356293Sjkh{ 174184130Sjhb struct pcfclock_data *sc = dev->si_drv1; 175184130Sjhb device_t pcfclockdev = sc->dev; 17656293Sjkh device_t ppbus = device_get_parent(pcfclockdev); 17756293Sjkh 17856293Sjkh sc->count--; 17977837Sphk if (sc->count == 0) 18056293Sjkh ppb_release_bus(ppbus, pcfclockdev); 18156293Sjkh 18256293Sjkh return (0); 18356293Sjkh} 18456293Sjkh 18556293Sjkhstatic void 186130585Sphkpcfclock_write_cmd(struct cdev *dev, unsigned char command) 18756293Sjkh{ 188184130Sjhb struct pcfclock_data *sc = dev->si_drv1; 189184130Sjhb device_t pcfclockdev = sc->dev; 190185003Sjhb device_t ppbus = device_get_parent(pcfclockdev); 19156293Sjkh unsigned char ctr = 14; 19256293Sjkh char i; 193185003Sjhb 19456293Sjkh for (i = 0; i <= 7; i++) { 19556293Sjkh ppb_wdtr(ppbus, i); 19656293Sjkh AUTOFEED_CLOCK(i & 1 ? AFC_HI : AFC_LO); 19756293Sjkh DELAY(3000); 19856293Sjkh } 19956293Sjkh ppb_wdtr(ppbus, command); 20056293Sjkh AUTOFEED_CLOCK(AFC_LO); 20156293Sjkh DELAY(3000); 20256293Sjkh AUTOFEED_CLOCK(AFC_HI); 20356293Sjkh} 20456293Sjkh 20556293Sjkhstatic void 206185003Sjhbpcfclock_display_data(struct cdev *dev, char buf[18]) 20756293Sjkh{ 208184130Sjhb struct pcfclock_data *sc = dev->si_drv1; 20956293Sjkh#ifdef PCFCLOCK_VERBOSE 21056293Sjkh int year; 21156293Sjkh 21256293Sjkh year = NR(buf, 14); 21356293Sjkh if (year < 70) 21456293Sjkh year += 100; 21577837Sphk 216184130Sjhb device_printf(sc->dev, "%02d.%02d.%4d %02d:%02d:%02d, " 21756293Sjkh "battery status: %s\n", 21856293Sjkh NR(buf, 10), NR(buf, 12), 1900 + year, 21956293Sjkh NR(buf, 6), NR(buf, 4), NR(buf, 2), 22056293Sjkh PCFCLOCK_BATTERY_STATUS_LOW(buf) ? "LOW" : "ok"); 22156293Sjkh#else 22256293Sjkh if (PCFCLOCK_BATTERY_STATUS_LOW(buf)) 223184130Sjhb device_printf(sc->dev, "BATTERY STATUS LOW ON\n"); 22456293Sjkh#endif 22556293Sjkh} 22656293Sjkh 227185003Sjhbstatic int 228130585Sphkpcfclock_read_data(struct cdev *dev, char *buf, ssize_t bits) 22956293Sjkh{ 230184130Sjhb struct pcfclock_data *sc = dev->si_drv1; 231184130Sjhb device_t pcfclockdev = sc->dev; 232185003Sjhb device_t ppbus = device_get_parent(pcfclockdev); 23356293Sjkh int i; 23456293Sjkh char waitfor; 23556293Sjkh int offset; 23656293Sjkh 23756293Sjkh /* one byte per four bits */ 23856293Sjkh bzero(buf, ((bits + 3) >> 2) + 1); 239185003Sjhb 24056293Sjkh waitfor = 100; 24156293Sjkh for (i = 0; i <= bits; i++) { 24256293Sjkh /* wait for clock, maximum (waitfor*100) usec */ 24356293Sjkh while(!CLOCK_OK && --waitfor > 0) 24456293Sjkh DELAY(100); 24556293Sjkh 24656293Sjkh /* timed out? */ 247185003Sjhb if (!waitfor) 24856293Sjkh return (EIO); 249185003Sjhb 25056293Sjkh waitfor = 100; /* reload */ 251185003Sjhb 25256293Sjkh /* give it some time */ 25356293Sjkh DELAY(500); 25456293Sjkh 25556293Sjkh /* calculate offset into buffer */ 25656293Sjkh offset = i >> 2; 25756293Sjkh buf[offset] <<= 1; 25856293Sjkh 25956293Sjkh if (BIT_SET) 26056293Sjkh buf[offset] |= 1; 26156293Sjkh } 26256293Sjkh 26356293Sjkh return (0); 26456293Sjkh} 26556293Sjkh 266185003Sjhbstatic int 267185003Sjhbpcfclock_read_dev(struct cdev *dev, char *buf, int maxretries) 26856293Sjkh{ 269184130Sjhb struct pcfclock_data *sc = dev->si_drv1; 270184130Sjhb device_t pcfclockdev = sc->dev; 271185003Sjhb device_t ppbus = device_get_parent(pcfclockdev); 27256293Sjkh int error = 0; 27356293Sjkh 27456293Sjkh ppb_set_mode(ppbus, PPB_COMPATIBLE); 27556293Sjkh 27656293Sjkh while (--maxretries > 0) { 27756293Sjkh pcfclock_write_cmd(dev, PCFCLOCK_CMD_TIME); 27856293Sjkh if (pcfclock_read_data(dev, buf, 68)) 27956293Sjkh continue; 280185003Sjhb 28156293Sjkh if (!PCFCLOCK_CORRECT_SYNC(buf)) 28256293Sjkh continue; 28356293Sjkh 28456293Sjkh if (!PCFCLOCK_CORRECT_FORMAT(buf)) 28556293Sjkh continue; 28656293Sjkh 28756293Sjkh break; 28856293Sjkh } 28956293Sjkh 29056293Sjkh if (!maxretries) 29156293Sjkh error = EIO; 292185003Sjhb 29356293Sjkh return (error); 29456293Sjkh} 29556293Sjkh 296106564Sjhbstatic int 297130585Sphkpcfclock_read(struct cdev *dev, struct uio *uio, int ioflag) 29856293Sjkh{ 299184130Sjhb struct pcfclock_data *sc = dev->si_drv1; 30056293Sjkh char buf[18]; 30156293Sjkh int error = 0; 30256293Sjkh 30357352Ssheldonh if (uio->uio_resid < 18) 30457352Ssheldonh return (ERANGE); 30557352Ssheldonh 30656293Sjkh error = pcfclock_read_dev(dev, buf, PCFCLOCK_MAX_RETRIES); 307185003Sjhb 30856293Sjkh if (error) { 309184130Sjhb device_printf(sc->dev, "no PCF found\n"); 31056293Sjkh } else { 31156293Sjkh pcfclock_display_data(dev, buf); 312185003Sjhb 31356293Sjkh uiomove(buf, 18, uio); 31456293Sjkh } 315185003Sjhb 31656293Sjkh return (error); 31756293Sjkh} 31856293Sjkh 31956455Speterstatic device_method_t pcfclock_methods[] = { 32056455Speter /* device interface */ 32156455Speter DEVMETHOD(device_identify, pcfclock_identify), 32256455Speter DEVMETHOD(device_probe, pcfclock_probe), 32356455Speter DEVMETHOD(device_attach, pcfclock_attach), 32456455Speter 32556455Speter { 0, 0 } 32656455Speter}; 32756455Speter 32856455Speterstatic driver_t pcfclock_driver = { 32956455Speter PCFCLOCK_NAME, 33056455Speter pcfclock_methods, 33156455Speter sizeof(struct pcfclock_data), 33256455Speter}; 33356455Speter 33456293SjkhDRIVER_MODULE(pcfclock, ppbus, pcfclock_driver, pcfclock_devclass, 0, 0); 335