1507e2e44SPoul-Henning Kamp /* 2507e2e44SPoul-Henning Kamp * ---------------------------------------------------------------------------- 3507e2e44SPoul-Henning Kamp * "THE BEER-WARE LICENSE" (Revision 42): 4507e2e44SPoul-Henning Kamp * <phk@FreeBSD.org> wrote this file. As long as you retain this notice you 5507e2e44SPoul-Henning Kamp * can do whatever you want with this stuff. If we meet some day, and you think 6507e2e44SPoul-Henning Kamp * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp 7507e2e44SPoul-Henning Kamp * ---------------------------------------------------------------------------- 8507e2e44SPoul-Henning Kamp * 9c3aac50fSPeter Wemm * $FreeBSD$ 10389825d5SPoul-Henning Kamp * 11389825d5SPoul-Henning Kamp * This driver implements a draft-mogul-pps-api-02.txt PPS source. 12389825d5SPoul-Henning Kamp * 13389825d5SPoul-Henning Kamp * The input pin is pin#10 14389825d5SPoul-Henning Kamp * The echo output pin is pin#14 15507e2e44SPoul-Henning Kamp * 16507e2e44SPoul-Henning Kamp */ 17507e2e44SPoul-Henning Kamp 18507e2e44SPoul-Henning Kamp #include <sys/param.h> 19507e2e44SPoul-Henning Kamp #include <sys/kernel.h> 20507e2e44SPoul-Henning Kamp #include <sys/systm.h> 210f210c92SNicolas Souchu #include <sys/module.h> 220f210c92SNicolas Souchu #include <sys/bus.h> 23507e2e44SPoul-Henning Kamp #include <sys/conf.h> 248afeddf0SPoul-Henning Kamp #include <sys/timepps.h> 250f210c92SNicolas Souchu #include <machine/bus.h> 260f210c92SNicolas Souchu #include <machine/resource.h> 270f210c92SNicolas Souchu #include <sys/rman.h> 28507e2e44SPoul-Henning Kamp 29507e2e44SPoul-Henning Kamp #include <dev/ppbus/ppbconf.h> 300f210c92SNicolas Souchu #include "ppbus_if.h" 310f210c92SNicolas Souchu #include <dev/ppbus/ppbio.h> 32507e2e44SPoul-Henning Kamp 331a03ce6cSPoul-Henning Kamp #define PPS_NAME "pps" /* our official name */ 34507e2e44SPoul-Henning Kamp 35daefef7cSPeter Wemm #define PRVERBOSE(fmt, arg...) if (bootverbose) printf(fmt, ##arg); 36d7e53105SWarner Losh 371a03ce6cSPoul-Henning Kamp struct pps_data { 3895f0f58cSPoul-Henning Kamp struct ppb_device pps_dev; 39bd61b8e8SPoul-Henning Kamp struct pps_state pps[9]; 40bd61b8e8SPoul-Henning Kamp dev_t devs[9]; 41bd61b8e8SPoul-Henning Kamp device_t ppsdev; 42bd61b8e8SPoul-Henning Kamp device_t ppbus; 43bd61b8e8SPoul-Henning Kamp int busy; 44bd61b8e8SPoul-Henning Kamp struct callout_handle timeout; 45bd61b8e8SPoul-Henning Kamp int lastdata; 460f210c92SNicolas Souchu 470f210c92SNicolas Souchu struct resource *intr_resource; /* interrupt resource */ 480f210c92SNicolas Souchu void *intr_cookie; /* interrupt registration cookie */ 491a03ce6cSPoul-Henning Kamp }; 50507e2e44SPoul-Henning Kamp 510f210c92SNicolas Souchu static void ppsintr(void *arg); 52bd61b8e8SPoul-Henning Kamp static void ppshcpoll(void *arg); 53507e2e44SPoul-Henning Kamp 540f210c92SNicolas Souchu #define DEVTOSOFTC(dev) \ 550f210c92SNicolas Souchu ((struct pps_data *)device_get_softc(dev)) 56507e2e44SPoul-Henning Kamp 570f210c92SNicolas Souchu static devclass_t pps_devclass; 58507e2e44SPoul-Henning Kamp 5995f0f58cSPoul-Henning Kamp static d_open_t ppsopen; 6095f0f58cSPoul-Henning Kamp static d_close_t ppsclose; 618afeddf0SPoul-Henning Kamp static d_ioctl_t ppsioctl; 62507e2e44SPoul-Henning Kamp 63507e2e44SPoul-Henning Kamp #define CDEV_MAJOR 89 644e2f199eSPoul-Henning Kamp static struct cdevsw pps_cdevsw = { 654e2f199eSPoul-Henning Kamp /* open */ ppsopen, 664e2f199eSPoul-Henning Kamp /* close */ ppsclose, 674e2f199eSPoul-Henning Kamp /* read */ noread, 684e2f199eSPoul-Henning Kamp /* write */ nowrite, 694e2f199eSPoul-Henning Kamp /* ioctl */ ppsioctl, 704e2f199eSPoul-Henning Kamp /* poll */ nopoll, 714e2f199eSPoul-Henning Kamp /* mmap */ nommap, 724e2f199eSPoul-Henning Kamp /* strategy */ nostrategy, 734e2f199eSPoul-Henning Kamp /* name */ PPS_NAME, 744e2f199eSPoul-Henning Kamp /* maj */ CDEV_MAJOR, 754e2f199eSPoul-Henning Kamp /* dump */ nodump, 764e2f199eSPoul-Henning Kamp /* psize */ nopsize, 774e2f199eSPoul-Henning Kamp /* flags */ 0, 784e2f199eSPoul-Henning Kamp }; 79507e2e44SPoul-Henning Kamp 800f063508SPeter Wemm static void 810f063508SPeter Wemm ppsidentify(driver_t *driver, device_t parent) 820f063508SPeter Wemm { 830f063508SPeter Wemm 84338cad62SBernd Walter BUS_ADD_CHILD(parent, 0, PPS_NAME, -1); 850f063508SPeter Wemm } 860f063508SPeter Wemm 870f210c92SNicolas Souchu static int 88bd61b8e8SPoul-Henning Kamp ppstry(device_t ppbus, int send, int expect) 89bd61b8e8SPoul-Henning Kamp { 90bd61b8e8SPoul-Henning Kamp int i; 91bd61b8e8SPoul-Henning Kamp 92bd61b8e8SPoul-Henning Kamp ppb_wdtr(ppbus, send); 93bd61b8e8SPoul-Henning Kamp i = ppb_rdtr(ppbus); 94d7e53105SWarner Losh PRVERBOSE("S: %02x E: %02x G: %02x\n", send, expect, i); 95bd61b8e8SPoul-Henning Kamp return (i != expect); 96bd61b8e8SPoul-Henning Kamp } 97bd61b8e8SPoul-Henning Kamp 98bd61b8e8SPoul-Henning Kamp static int 990f210c92SNicolas Souchu ppsprobe(device_t ppsdev) 100507e2e44SPoul-Henning Kamp { 1010f210c92SNicolas Souchu device_set_desc(ppsdev, "Pulse per second Timing Interface"); 102507e2e44SPoul-Henning Kamp 103d7e53105SWarner Losh return (0); 104d7e53105SWarner Losh } 105d7e53105SWarner Losh 106d7e53105SWarner Losh static int 107d7e53105SWarner Losh ppsattach(device_t dev) 108d7e53105SWarner Losh { 109d7e53105SWarner Losh struct pps_data *sc = DEVTOSOFTC(dev); 110d7e53105SWarner Losh device_t ppbus = device_get_parent(dev); 111d7e53105SWarner Losh int irq, zero = 0; 112d7e53105SWarner Losh dev_t d; 113d7e53105SWarner Losh int unit, i; 114d7e53105SWarner Losh 115d7e53105SWarner Losh bzero(sc, sizeof(struct pps_data)); /* XXX doesn't newbus do this? */ 116d7e53105SWarner Losh 117d7e53105SWarner Losh /* retrieve the ppbus irq */ 118d7e53105SWarner Losh BUS_READ_IVAR(ppbus, dev, PPBUS_IVAR_IRQ, &irq); 119d7e53105SWarner Losh 120d7e53105SWarner Losh if (irq > 0) { 121d7e53105SWarner Losh /* declare our interrupt handler */ 122d7e53105SWarner Losh sc->intr_resource = bus_alloc_resource(dev, SYS_RES_IRQ, 123d7e53105SWarner Losh &zero, irq, irq, 1, RF_SHAREABLE); 124d7e53105SWarner Losh } 125d7e53105SWarner Losh /* interrupts seem mandatory */ 126d7e53105SWarner Losh if (sc->intr_resource == NULL) 127d7e53105SWarner Losh return (ENXIO); 128d7e53105SWarner Losh 129d7e53105SWarner Losh sc->ppsdev = dev; 130d7e53105SWarner Losh sc->ppbus = ppbus; 131bd61b8e8SPoul-Henning Kamp unit = device_get_unit(ppbus); 132d7e53105SWarner Losh d = make_dev(&pps_cdevsw, unit, 133bd61b8e8SPoul-Henning Kamp UID_ROOT, GID_WHEEL, 0644, PPS_NAME "%d", unit); 134d7e53105SWarner Losh sc->devs[0] = d; 135bd61b8e8SPoul-Henning Kamp sc->pps[0].ppscap = PPS_CAPTUREASSERT | PPS_ECHOASSERT; 136d7e53105SWarner Losh d->si_drv1 = sc; 137d7e53105SWarner Losh d->si_drv2 = (void*)0; 138bd61b8e8SPoul-Henning Kamp pps_init(&sc->pps[0]); 139bd61b8e8SPoul-Henning Kamp 140d7e53105SWarner Losh if (ppb_request_bus(ppbus, dev, PPB_DONTWAIT)) 141bd61b8e8SPoul-Henning Kamp return (0); 142bd61b8e8SPoul-Henning Kamp 143bd61b8e8SPoul-Henning Kamp do { 144bd61b8e8SPoul-Henning Kamp i = ppb_set_mode(sc->ppbus, PPB_EPP); 145d7e53105SWarner Losh PRVERBOSE("EPP: %d %d\n", i, PPB_IN_EPP_MODE(sc->ppbus)); 146bd61b8e8SPoul-Henning Kamp if (i == -1) 147bd61b8e8SPoul-Henning Kamp break; 148bd61b8e8SPoul-Henning Kamp i = 0; 149bd61b8e8SPoul-Henning Kamp ppb_wctr(ppbus, i); 150bd61b8e8SPoul-Henning Kamp if (ppstry(ppbus, 0x00, 0x00)) 151bd61b8e8SPoul-Henning Kamp break; 152bd61b8e8SPoul-Henning Kamp if (ppstry(ppbus, 0x55, 0x55)) 153bd61b8e8SPoul-Henning Kamp break; 154bd61b8e8SPoul-Henning Kamp if (ppstry(ppbus, 0xaa, 0xaa)) 155bd61b8e8SPoul-Henning Kamp break; 156bd61b8e8SPoul-Henning Kamp if (ppstry(ppbus, 0xff, 0xff)) 157bd61b8e8SPoul-Henning Kamp break; 158bd61b8e8SPoul-Henning Kamp 159bd61b8e8SPoul-Henning Kamp i = IRQENABLE | PCD | STROBE | nINIT | SELECTIN; 160bd61b8e8SPoul-Henning Kamp ppb_wctr(ppbus, i); 161d7e53105SWarner Losh PRVERBOSE("CTR = %02x (%02x)\n", ppb_rctr(ppbus), i); 162bd61b8e8SPoul-Henning Kamp if (ppstry(ppbus, 0x00, 0x00)) 163bd61b8e8SPoul-Henning Kamp break; 164bd61b8e8SPoul-Henning Kamp if (ppstry(ppbus, 0x55, 0x00)) 165bd61b8e8SPoul-Henning Kamp break; 166bd61b8e8SPoul-Henning Kamp if (ppstry(ppbus, 0xaa, 0x00)) 167bd61b8e8SPoul-Henning Kamp break; 168bd61b8e8SPoul-Henning Kamp if (ppstry(ppbus, 0xff, 0x00)) 169bd61b8e8SPoul-Henning Kamp break; 170bd61b8e8SPoul-Henning Kamp 171bd61b8e8SPoul-Henning Kamp i = IRQENABLE | PCD | nINIT | SELECTIN; 172bd61b8e8SPoul-Henning Kamp ppb_wctr(ppbus, i); 173d7e53105SWarner Losh PRVERBOSE("CTR = %02x (%02x)\n", ppb_rctr(ppbus), i); 174bd61b8e8SPoul-Henning Kamp ppstry(ppbus, 0x00, 0xff); 175bd61b8e8SPoul-Henning Kamp ppstry(ppbus, 0x55, 0xff); 176bd61b8e8SPoul-Henning Kamp ppstry(ppbus, 0xaa, 0xff); 177bd61b8e8SPoul-Henning Kamp ppstry(ppbus, 0xff, 0xff); 178bd61b8e8SPoul-Henning Kamp 179bd61b8e8SPoul-Henning Kamp for (i = 1; i < 9; i++) { 180d7e53105SWarner Losh d = make_dev(&pps_cdevsw, unit + 0x10000 * i, 181bd61b8e8SPoul-Henning Kamp UID_ROOT, GID_WHEEL, 0644, PPS_NAME "%db%d", unit, i - 1); 182d7e53105SWarner Losh sc->devs[i] = d; 183bd61b8e8SPoul-Henning Kamp sc->pps[i].ppscap = PPS_CAPTUREASSERT | PPS_CAPTURECLEAR; 184d7e53105SWarner Losh d->si_drv1 = sc; 185d7e53105SWarner Losh d->si_drv2 = (void*)i; 186bd61b8e8SPoul-Henning Kamp pps_init(&sc->pps[i]); 187bd61b8e8SPoul-Henning Kamp } 188bd61b8e8SPoul-Henning Kamp } while (0); 189bd61b8e8SPoul-Henning Kamp i = ppb_set_mode(sc->ppbus, PPB_COMPATIBLE); 190d7e53105SWarner Losh ppb_release_bus(ppbus, dev); 1910f210c92SNicolas Souchu 1920f210c92SNicolas Souchu return (0); 193507e2e44SPoul-Henning Kamp } 194507e2e44SPoul-Henning Kamp 195507e2e44SPoul-Henning Kamp static int 196b40ce416SJulian Elischer ppsopen(dev_t dev, int flags, int fmt, struct thread *td) 197507e2e44SPoul-Henning Kamp { 198bd61b8e8SPoul-Henning Kamp struct pps_data *sc = dev->si_drv1; 199bd61b8e8SPoul-Henning Kamp int subdev = (int)dev->si_drv2; 200bd61b8e8SPoul-Henning Kamp int error, i; 201507e2e44SPoul-Henning Kamp 202bd61b8e8SPoul-Henning Kamp if (!sc->busy) { 203bd61b8e8SPoul-Henning Kamp device_t ppsdev = sc->ppsdev; 204bd61b8e8SPoul-Henning Kamp device_t ppbus = sc->ppbus; 205bd61b8e8SPoul-Henning Kamp 2060f210c92SNicolas Souchu if (ppb_request_bus(ppbus, ppsdev, PPB_WAIT|PPB_INTR)) 207507e2e44SPoul-Henning Kamp return (EINTR); 208507e2e44SPoul-Henning Kamp 2090f210c92SNicolas Souchu /* attach the interrupt handler */ 2100f210c92SNicolas Souchu if ((error = BUS_SETUP_INTR(ppbus, ppsdev, sc->intr_resource, 2110f210c92SNicolas Souchu INTR_TYPE_TTY, ppsintr, ppsdev, 2120f210c92SNicolas Souchu &sc->intr_cookie))) { 2130f210c92SNicolas Souchu ppb_release_bus(ppbus, ppsdev); 2140f210c92SNicolas Souchu return (error); 2150f210c92SNicolas Souchu } 2160f210c92SNicolas Souchu 217bd61b8e8SPoul-Henning Kamp i = ppb_set_mode(sc->ppbus, PPB_PS2); 218d7e53105SWarner Losh PRVERBOSE("EPP: %d %d\n", i, PPB_IN_EPP_MODE(sc->ppbus)); 219507e2e44SPoul-Henning Kamp 220bd61b8e8SPoul-Henning Kamp i = IRQENABLE | PCD | nINIT | SELECTIN; 221bd61b8e8SPoul-Henning Kamp ppb_wctr(ppbus, i); 222bd61b8e8SPoul-Henning Kamp } 223bd61b8e8SPoul-Henning Kamp if (subdev > 0 && !(sc->busy & ~1)) { 224bd61b8e8SPoul-Henning Kamp sc->timeout = timeout(ppshcpoll, sc, 1); 225bd61b8e8SPoul-Henning Kamp sc->lastdata = ppb_rdtr(sc->ppbus); 226bd61b8e8SPoul-Henning Kamp } 227bd61b8e8SPoul-Henning Kamp sc->busy |= (1 << subdev); 228507e2e44SPoul-Henning Kamp return(0); 229507e2e44SPoul-Henning Kamp } 230507e2e44SPoul-Henning Kamp 231507e2e44SPoul-Henning Kamp static int 232b40ce416SJulian Elischer ppsclose(dev_t dev, int flags, int fmt, struct thread *td) 233507e2e44SPoul-Henning Kamp { 234bd61b8e8SPoul-Henning Kamp struct pps_data *sc = dev->si_drv1; 235bd61b8e8SPoul-Henning Kamp int subdev = (int)dev->si_drv2; 236507e2e44SPoul-Henning Kamp 237bd61b8e8SPoul-Henning Kamp sc->pps[subdev].ppsparam.mode = 0; /* PHK ??? */ 238bd61b8e8SPoul-Henning Kamp sc->busy &= ~(1 << subdev); 239bd61b8e8SPoul-Henning Kamp if (subdev > 0 && !(sc->busy & ~1)) 240bd61b8e8SPoul-Henning Kamp untimeout(ppshcpoll, sc, sc->timeout); 241bd61b8e8SPoul-Henning Kamp if (!sc->busy) { 242bd61b8e8SPoul-Henning Kamp device_t ppsdev = sc->ppsdev; 243bd61b8e8SPoul-Henning Kamp device_t ppbus = sc->ppbus; 24420240fa3SNicolas Souchu 2450f210c92SNicolas Souchu ppb_wdtr(ppbus, 0); 2460f210c92SNicolas Souchu ppb_wctr(ppbus, 0); 24720240fa3SNicolas Souchu 2480f210c92SNicolas Souchu /* Note: the interrupt handler is automatically detached */ 249bd61b8e8SPoul-Henning Kamp ppb_set_mode(ppbus, PPB_COMPATIBLE); 2500f210c92SNicolas Souchu ppb_release_bus(ppbus, ppsdev); 251bd61b8e8SPoul-Henning Kamp } 252507e2e44SPoul-Henning Kamp return(0); 253507e2e44SPoul-Henning Kamp } 254507e2e44SPoul-Henning Kamp 255507e2e44SPoul-Henning Kamp static void 256bd61b8e8SPoul-Henning Kamp ppshcpoll(void *arg) 257bd61b8e8SPoul-Henning Kamp { 258bd61b8e8SPoul-Henning Kamp struct pps_data *sc = arg; 259bd61b8e8SPoul-Henning Kamp int i, j, k, l; 260bd61b8e8SPoul-Henning Kamp 261bd61b8e8SPoul-Henning Kamp if (!(sc->busy & ~1)) 262bd61b8e8SPoul-Henning Kamp return; 263bd61b8e8SPoul-Henning Kamp sc->timeout = timeout(ppshcpoll, sc, 1); 264bd61b8e8SPoul-Henning Kamp i = ppb_rdtr(sc->ppbus); 265bd61b8e8SPoul-Henning Kamp if (i == sc->lastdata) 266bd61b8e8SPoul-Henning Kamp return; 267bd61b8e8SPoul-Henning Kamp l = sc->lastdata ^ i; 268bd61b8e8SPoul-Henning Kamp k = 1; 269bd61b8e8SPoul-Henning Kamp for (j = 1; j < 9; j ++) { 2707bf758bfSPoul-Henning Kamp if (l & k) { 2717bf758bfSPoul-Henning Kamp pps_capture(&sc->pps[j]); 2727bf758bfSPoul-Henning Kamp pps_event(&sc->pps[j], 2737bf758bfSPoul-Henning Kamp i & k ? PPS_CAPTUREASSERT : PPS_CAPTURECLEAR); 2747bf758bfSPoul-Henning Kamp } 275bd61b8e8SPoul-Henning Kamp k += k; 276bd61b8e8SPoul-Henning Kamp } 277bd61b8e8SPoul-Henning Kamp sc->lastdata = i; 278bd61b8e8SPoul-Henning Kamp } 279bd61b8e8SPoul-Henning Kamp 280bd61b8e8SPoul-Henning Kamp static void 2810f210c92SNicolas Souchu ppsintr(void *arg) 282507e2e44SPoul-Henning Kamp { 2830f210c92SNicolas Souchu device_t ppsdev = (device_t)arg; 2840f210c92SNicolas Souchu struct pps_data *sc = DEVTOSOFTC(ppsdev); 285bd61b8e8SPoul-Henning Kamp device_t ppbus = sc->ppbus; 286507e2e44SPoul-Henning Kamp 2877bf758bfSPoul-Henning Kamp pps_capture(&sc->pps[0]); 2880f210c92SNicolas Souchu if (!(ppb_rstr(ppbus) & nACK)) 289507e2e44SPoul-Henning Kamp return; 290bd61b8e8SPoul-Henning Kamp if (sc->pps[0].ppsparam.mode & PPS_ECHOASSERT) 2910f210c92SNicolas Souchu ppb_wctr(ppbus, IRQENABLE | AUTOFEED); 2927bf758bfSPoul-Henning Kamp pps_event(&sc->pps[0], PPS_CAPTUREASSERT); 293bd61b8e8SPoul-Henning Kamp if (sc->pps[0].ppsparam.mode & PPS_ECHOASSERT) 2940f210c92SNicolas Souchu ppb_wctr(ppbus, IRQENABLE); 295507e2e44SPoul-Henning Kamp } 296507e2e44SPoul-Henning Kamp 297507e2e44SPoul-Henning Kamp static int 298b40ce416SJulian Elischer ppsioctl(dev_t dev, u_long cmd, caddr_t data, int flags, struct thread *td) 299507e2e44SPoul-Henning Kamp { 300bd61b8e8SPoul-Henning Kamp struct pps_data *sc = dev->si_drv1; 301bd61b8e8SPoul-Henning Kamp int subdev = (int)dev->si_drv2; 302507e2e44SPoul-Henning Kamp 303bd61b8e8SPoul-Henning Kamp return (pps_ioctl(cmd, data, &sc->pps[subdev])); 3048afeddf0SPoul-Henning Kamp } 305507e2e44SPoul-Henning Kamp 3060f063508SPeter Wemm static device_method_t pps_methods[] = { 3070f063508SPeter Wemm /* device interface */ 3080f063508SPeter Wemm DEVMETHOD(device_identify, ppsidentify), 3090f063508SPeter Wemm DEVMETHOD(device_probe, ppsprobe), 3100f063508SPeter Wemm DEVMETHOD(device_attach, ppsattach), 3110f063508SPeter Wemm 3120f063508SPeter Wemm { 0, 0 } 3130f063508SPeter Wemm }; 3140f063508SPeter Wemm 3150f063508SPeter Wemm static driver_t pps_driver = { 3160f063508SPeter Wemm PPS_NAME, 3170f063508SPeter Wemm pps_methods, 3180f063508SPeter Wemm sizeof(struct pps_data), 3190f063508SPeter Wemm }; 3200f210c92SNicolas Souchu DRIVER_MODULE(pps, ppbus, pps_driver, pps_devclass, 0, 0); 321