1098ca2bdSWarner Losh /*- 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 * 9389825d5SPoul-Henning Kamp * 10389825d5SPoul-Henning Kamp * This driver implements a draft-mogul-pps-api-02.txt PPS source. 11389825d5SPoul-Henning Kamp * 12389825d5SPoul-Henning Kamp * The input pin is pin#10 13389825d5SPoul-Henning Kamp * The echo output pin is pin#14 14507e2e44SPoul-Henning Kamp * 15507e2e44SPoul-Henning Kamp */ 16507e2e44SPoul-Henning Kamp 17aad970f1SDavid E. O'Brien #include <sys/cdefs.h> 18aad970f1SDavid E. O'Brien __FBSDID("$FreeBSD$"); 19aad970f1SDavid E. O'Brien 20507e2e44SPoul-Henning Kamp #include <sys/param.h> 21507e2e44SPoul-Henning Kamp #include <sys/kernel.h> 22507e2e44SPoul-Henning Kamp #include <sys/systm.h> 230f210c92SNicolas Souchu #include <sys/module.h> 240f210c92SNicolas Souchu #include <sys/bus.h> 25507e2e44SPoul-Henning Kamp #include <sys/conf.h> 268afeddf0SPoul-Henning Kamp #include <sys/timepps.h> 270f210c92SNicolas Souchu #include <machine/bus.h> 280f210c92SNicolas Souchu #include <machine/resource.h> 290f210c92SNicolas Souchu #include <sys/rman.h> 30507e2e44SPoul-Henning Kamp 31507e2e44SPoul-Henning Kamp #include <dev/ppbus/ppbconf.h> 320f210c92SNicolas Souchu #include "ppbus_if.h" 330f210c92SNicolas Souchu #include <dev/ppbus/ppbio.h> 34507e2e44SPoul-Henning Kamp 351a03ce6cSPoul-Henning Kamp #define PPS_NAME "pps" /* our official name */ 36507e2e44SPoul-Henning Kamp 37daefef7cSPeter Wemm #define PRVERBOSE(fmt, arg...) if (bootverbose) printf(fmt, ##arg); 38d7e53105SWarner Losh 391a03ce6cSPoul-Henning Kamp struct pps_data { 4095f0f58cSPoul-Henning Kamp struct ppb_device pps_dev; 41bd61b8e8SPoul-Henning Kamp struct pps_state pps[9]; 4289c9c53dSPoul-Henning Kamp struct cdev *devs[9]; 43bd61b8e8SPoul-Henning Kamp device_t ppsdev; 44bd61b8e8SPoul-Henning Kamp device_t ppbus; 45bd61b8e8SPoul-Henning Kamp int busy; 46bd61b8e8SPoul-Henning Kamp struct callout_handle timeout; 47bd61b8e8SPoul-Henning Kamp int lastdata; 480f210c92SNicolas Souchu 4976061701SWarner Losh struct mtx mtx; 500f210c92SNicolas Souchu struct resource *intr_resource; /* interrupt resource */ 510f210c92SNicolas Souchu void *intr_cookie; /* interrupt registration cookie */ 521a03ce6cSPoul-Henning Kamp }; 53507e2e44SPoul-Henning Kamp 540f210c92SNicolas Souchu static void ppsintr(void *arg); 55bd61b8e8SPoul-Henning Kamp static void ppshcpoll(void *arg); 56507e2e44SPoul-Henning Kamp 570f210c92SNicolas Souchu #define DEVTOSOFTC(dev) \ 580f210c92SNicolas Souchu ((struct pps_data *)device_get_softc(dev)) 59507e2e44SPoul-Henning Kamp 600f210c92SNicolas Souchu static devclass_t pps_devclass; 61507e2e44SPoul-Henning Kamp 6295f0f58cSPoul-Henning Kamp static d_open_t ppsopen; 6395f0f58cSPoul-Henning Kamp static d_close_t ppsclose; 648afeddf0SPoul-Henning Kamp static d_ioctl_t ppsioctl; 65507e2e44SPoul-Henning Kamp 664e2f199eSPoul-Henning Kamp static struct cdevsw pps_cdevsw = { 67dc08ffecSPoul-Henning Kamp .d_version = D_VERSION, 687ac40f5fSPoul-Henning Kamp .d_open = ppsopen, 697ac40f5fSPoul-Henning Kamp .d_close = ppsclose, 707ac40f5fSPoul-Henning Kamp .d_ioctl = ppsioctl, 717ac40f5fSPoul-Henning Kamp .d_name = PPS_NAME, 724e2f199eSPoul-Henning Kamp }; 73507e2e44SPoul-Henning Kamp 740f063508SPeter Wemm static void 750f063508SPeter Wemm ppsidentify(driver_t *driver, device_t parent) 760f063508SPeter Wemm { 770f063508SPeter Wemm 78a5c7e3bbSGuido van Rooij device_t dev; 79a5c7e3bbSGuido van Rooij 80a5c7e3bbSGuido van Rooij dev = device_find_child(parent, PPS_NAME, 0); 81a5c7e3bbSGuido van Rooij if (!dev) 82338cad62SBernd Walter BUS_ADD_CHILD(parent, 0, PPS_NAME, -1); 830f063508SPeter Wemm } 840f063508SPeter Wemm 850f210c92SNicolas Souchu static int 86bd61b8e8SPoul-Henning Kamp ppstry(device_t ppbus, int send, int expect) 87bd61b8e8SPoul-Henning Kamp { 88bd61b8e8SPoul-Henning Kamp int i; 89bd61b8e8SPoul-Henning Kamp 90bd61b8e8SPoul-Henning Kamp ppb_wdtr(ppbus, send); 91bd61b8e8SPoul-Henning Kamp i = ppb_rdtr(ppbus); 92d7e53105SWarner Losh PRVERBOSE("S: %02x E: %02x G: %02x\n", send, expect, i); 93bd61b8e8SPoul-Henning Kamp return (i != expect); 94bd61b8e8SPoul-Henning Kamp } 95bd61b8e8SPoul-Henning Kamp 96bd61b8e8SPoul-Henning Kamp static int 970f210c92SNicolas Souchu ppsprobe(device_t ppsdev) 98507e2e44SPoul-Henning Kamp { 990f210c92SNicolas Souchu device_set_desc(ppsdev, "Pulse per second Timing Interface"); 100507e2e44SPoul-Henning Kamp 101d7e53105SWarner Losh return (0); 102d7e53105SWarner Losh } 103d7e53105SWarner Losh 104d7e53105SWarner Losh static int 105d7e53105SWarner Losh ppsattach(device_t dev) 106d7e53105SWarner Losh { 107d7e53105SWarner Losh struct pps_data *sc = DEVTOSOFTC(dev); 108d7e53105SWarner Losh device_t ppbus = device_get_parent(dev); 10989c9c53dSPoul-Henning Kamp struct cdev *d; 110a885bb60SJohn Baldwin intptr_t irq; 111a885bb60SJohn Baldwin int i, unit, zero = 0; 112d7e53105SWarner Losh 11376061701SWarner Losh mtx_init(&sc->mtx, device_get_nameunit(dev), "pps", MTX_SPIN); 114d7e53105SWarner Losh /* retrieve the ppbus irq */ 115d7e53105SWarner Losh BUS_READ_IVAR(ppbus, dev, PPBUS_IVAR_IRQ, &irq); 116d7e53105SWarner Losh 117d7e53105SWarner Losh if (irq > 0) { 118d7e53105SWarner Losh /* declare our interrupt handler */ 119d7e53105SWarner Losh sc->intr_resource = bus_alloc_resource(dev, SYS_RES_IRQ, 120d7e53105SWarner Losh &zero, irq, irq, 1, RF_SHAREABLE); 121d7e53105SWarner Losh } 122d7e53105SWarner Losh /* interrupts seem mandatory */ 123d7e53105SWarner Losh if (sc->intr_resource == NULL) 124d7e53105SWarner Losh return (ENXIO); 125d7e53105SWarner Losh 126d7e53105SWarner Losh sc->ppsdev = dev; 127d7e53105SWarner Losh sc->ppbus = ppbus; 128bd61b8e8SPoul-Henning Kamp unit = device_get_unit(ppbus); 129d7e53105SWarner Losh d = make_dev(&pps_cdevsw, unit, 1302066d8e7SRobert Watson UID_ROOT, GID_WHEEL, 0600, PPS_NAME "%d", unit); 131d7e53105SWarner Losh sc->devs[0] = d; 132bd61b8e8SPoul-Henning Kamp sc->pps[0].ppscap = PPS_CAPTUREASSERT | PPS_ECHOASSERT; 133d7e53105SWarner Losh d->si_drv1 = sc; 134d7e53105SWarner Losh d->si_drv2 = (void*)0; 135bd61b8e8SPoul-Henning Kamp pps_init(&sc->pps[0]); 136bd61b8e8SPoul-Henning Kamp 137d7e53105SWarner Losh if (ppb_request_bus(ppbus, dev, PPB_DONTWAIT)) 138bd61b8e8SPoul-Henning Kamp return (0); 139bd61b8e8SPoul-Henning Kamp 140bd61b8e8SPoul-Henning Kamp do { 141bd61b8e8SPoul-Henning Kamp i = ppb_set_mode(sc->ppbus, PPB_EPP); 142d7e53105SWarner Losh PRVERBOSE("EPP: %d %d\n", i, PPB_IN_EPP_MODE(sc->ppbus)); 143bd61b8e8SPoul-Henning Kamp if (i == -1) 144bd61b8e8SPoul-Henning Kamp break; 145bd61b8e8SPoul-Henning Kamp i = 0; 146bd61b8e8SPoul-Henning Kamp ppb_wctr(ppbus, i); 147bd61b8e8SPoul-Henning Kamp if (ppstry(ppbus, 0x00, 0x00)) 148bd61b8e8SPoul-Henning Kamp break; 149bd61b8e8SPoul-Henning Kamp if (ppstry(ppbus, 0x55, 0x55)) 150bd61b8e8SPoul-Henning Kamp break; 151bd61b8e8SPoul-Henning Kamp if (ppstry(ppbus, 0xaa, 0xaa)) 152bd61b8e8SPoul-Henning Kamp break; 153bd61b8e8SPoul-Henning Kamp if (ppstry(ppbus, 0xff, 0xff)) 154bd61b8e8SPoul-Henning Kamp break; 155bd61b8e8SPoul-Henning Kamp 156bd61b8e8SPoul-Henning Kamp i = IRQENABLE | PCD | STROBE | nINIT | SELECTIN; 157bd61b8e8SPoul-Henning Kamp ppb_wctr(ppbus, i); 158d7e53105SWarner Losh PRVERBOSE("CTR = %02x (%02x)\n", ppb_rctr(ppbus), i); 159bd61b8e8SPoul-Henning Kamp if (ppstry(ppbus, 0x00, 0x00)) 160bd61b8e8SPoul-Henning Kamp break; 161bd61b8e8SPoul-Henning Kamp if (ppstry(ppbus, 0x55, 0x00)) 162bd61b8e8SPoul-Henning Kamp break; 163bd61b8e8SPoul-Henning Kamp if (ppstry(ppbus, 0xaa, 0x00)) 164bd61b8e8SPoul-Henning Kamp break; 165bd61b8e8SPoul-Henning Kamp if (ppstry(ppbus, 0xff, 0x00)) 166bd61b8e8SPoul-Henning Kamp break; 167bd61b8e8SPoul-Henning Kamp 168bd61b8e8SPoul-Henning Kamp i = IRQENABLE | PCD | nINIT | SELECTIN; 169bd61b8e8SPoul-Henning Kamp ppb_wctr(ppbus, i); 170d7e53105SWarner Losh PRVERBOSE("CTR = %02x (%02x)\n", ppb_rctr(ppbus), i); 171bd61b8e8SPoul-Henning Kamp ppstry(ppbus, 0x00, 0xff); 172bd61b8e8SPoul-Henning Kamp ppstry(ppbus, 0x55, 0xff); 173bd61b8e8SPoul-Henning Kamp ppstry(ppbus, 0xaa, 0xff); 174bd61b8e8SPoul-Henning Kamp ppstry(ppbus, 0xff, 0xff); 175bd61b8e8SPoul-Henning Kamp 176bd61b8e8SPoul-Henning Kamp for (i = 1; i < 9; i++) { 177d7e53105SWarner Losh d = make_dev(&pps_cdevsw, unit + 0x10000 * i, 1782066d8e7SRobert Watson UID_ROOT, GID_WHEEL, 0600, PPS_NAME "%db%d", unit, i - 1); 179d7e53105SWarner Losh sc->devs[i] = d; 180bd61b8e8SPoul-Henning Kamp sc->pps[i].ppscap = PPS_CAPTUREASSERT | PPS_CAPTURECLEAR; 181d7e53105SWarner Losh d->si_drv1 = sc; 182a885bb60SJohn Baldwin d->si_drv2 = (void *)(intptr_t)i; 183bd61b8e8SPoul-Henning Kamp pps_init(&sc->pps[i]); 184bd61b8e8SPoul-Henning Kamp } 185bd61b8e8SPoul-Henning Kamp } while (0); 186bd61b8e8SPoul-Henning Kamp i = ppb_set_mode(sc->ppbus, PPB_COMPATIBLE); 187d7e53105SWarner Losh ppb_release_bus(ppbus, dev); 1880f210c92SNicolas Souchu 1890f210c92SNicolas Souchu return (0); 190507e2e44SPoul-Henning Kamp } 191507e2e44SPoul-Henning Kamp 192507e2e44SPoul-Henning Kamp static int 19389c9c53dSPoul-Henning Kamp ppsopen(struct cdev *dev, int flags, int fmt, struct thread *td) 194507e2e44SPoul-Henning Kamp { 195bd61b8e8SPoul-Henning Kamp struct pps_data *sc = dev->si_drv1; 196a885bb60SJohn Baldwin int subdev = (intptr_t)dev->si_drv2; 197bd61b8e8SPoul-Henning Kamp int error, i; 198507e2e44SPoul-Henning Kamp 199bd61b8e8SPoul-Henning Kamp if (!sc->busy) { 200bd61b8e8SPoul-Henning Kamp device_t ppsdev = sc->ppsdev; 201bd61b8e8SPoul-Henning Kamp device_t ppbus = sc->ppbus; 202bd61b8e8SPoul-Henning Kamp 2030f210c92SNicolas Souchu if (ppb_request_bus(ppbus, ppsdev, PPB_WAIT|PPB_INTR)) 204507e2e44SPoul-Henning Kamp return (EINTR); 205507e2e44SPoul-Henning Kamp 2060f210c92SNicolas Souchu /* attach the interrupt handler */ 20776061701SWarner Losh if ((error = bus_setup_intr(ppsdev, sc->intr_resource, 20876061701SWarner Losh (INTR_TYPE_TTY | INTR_MPSAFE | INTR_FAST), ppsintr, 20976061701SWarner Losh ppsdev, &sc->intr_cookie))) { 2100f210c92SNicolas Souchu ppb_release_bus(ppbus, ppsdev); 2110f210c92SNicolas Souchu return (error); 2120f210c92SNicolas Souchu } 2130f210c92SNicolas Souchu 214bd61b8e8SPoul-Henning Kamp i = ppb_set_mode(sc->ppbus, PPB_PS2); 215d7e53105SWarner Losh PRVERBOSE("EPP: %d %d\n", i, PPB_IN_EPP_MODE(sc->ppbus)); 216507e2e44SPoul-Henning Kamp 217bd61b8e8SPoul-Henning Kamp i = IRQENABLE | PCD | nINIT | SELECTIN; 218bd61b8e8SPoul-Henning Kamp ppb_wctr(ppbus, i); 219bd61b8e8SPoul-Henning Kamp } 220bd61b8e8SPoul-Henning Kamp if (subdev > 0 && !(sc->busy & ~1)) { 221bd61b8e8SPoul-Henning Kamp sc->timeout = timeout(ppshcpoll, sc, 1); 222bd61b8e8SPoul-Henning Kamp sc->lastdata = ppb_rdtr(sc->ppbus); 223bd61b8e8SPoul-Henning Kamp } 224bd61b8e8SPoul-Henning Kamp sc->busy |= (1 << subdev); 225507e2e44SPoul-Henning Kamp return(0); 226507e2e44SPoul-Henning Kamp } 227507e2e44SPoul-Henning Kamp 228507e2e44SPoul-Henning Kamp static int 22989c9c53dSPoul-Henning Kamp ppsclose(struct cdev *dev, int flags, int fmt, struct thread *td) 230507e2e44SPoul-Henning Kamp { 231bd61b8e8SPoul-Henning Kamp struct pps_data *sc = dev->si_drv1; 232a885bb60SJohn Baldwin int subdev = (intptr_t)dev->si_drv2; 233507e2e44SPoul-Henning Kamp 234bd61b8e8SPoul-Henning Kamp sc->pps[subdev].ppsparam.mode = 0; /* PHK ??? */ 235bd61b8e8SPoul-Henning Kamp sc->busy &= ~(1 << subdev); 236bd61b8e8SPoul-Henning Kamp if (subdev > 0 && !(sc->busy & ~1)) 237bd61b8e8SPoul-Henning Kamp untimeout(ppshcpoll, sc, sc->timeout); 238bd61b8e8SPoul-Henning Kamp if (!sc->busy) { 239bd61b8e8SPoul-Henning Kamp device_t ppsdev = sc->ppsdev; 240bd61b8e8SPoul-Henning Kamp device_t ppbus = sc->ppbus; 24120240fa3SNicolas Souchu 2420f210c92SNicolas Souchu ppb_wdtr(ppbus, 0); 2430f210c92SNicolas Souchu ppb_wctr(ppbus, 0); 24420240fa3SNicolas Souchu 2450f210c92SNicolas Souchu /* Note: the interrupt handler is automatically detached */ 246bd61b8e8SPoul-Henning Kamp ppb_set_mode(ppbus, PPB_COMPATIBLE); 2470f210c92SNicolas Souchu ppb_release_bus(ppbus, ppsdev); 248bd61b8e8SPoul-Henning Kamp } 249507e2e44SPoul-Henning Kamp return(0); 250507e2e44SPoul-Henning Kamp } 251507e2e44SPoul-Henning Kamp 252507e2e44SPoul-Henning Kamp static void 253bd61b8e8SPoul-Henning Kamp ppshcpoll(void *arg) 254bd61b8e8SPoul-Henning Kamp { 255bd61b8e8SPoul-Henning Kamp struct pps_data *sc = arg; 256bd61b8e8SPoul-Henning Kamp int i, j, k, l; 257bd61b8e8SPoul-Henning Kamp 258bd61b8e8SPoul-Henning Kamp if (!(sc->busy & ~1)) 259bd61b8e8SPoul-Henning Kamp return; 260bd61b8e8SPoul-Henning Kamp sc->timeout = timeout(ppshcpoll, sc, 1); 261bd61b8e8SPoul-Henning Kamp i = ppb_rdtr(sc->ppbus); 262bd61b8e8SPoul-Henning Kamp if (i == sc->lastdata) 263bd61b8e8SPoul-Henning Kamp return; 264bd61b8e8SPoul-Henning Kamp l = sc->lastdata ^ i; 265bd61b8e8SPoul-Henning Kamp k = 1; 266bd61b8e8SPoul-Henning Kamp for (j = 1; j < 9; j ++) { 2677bf758bfSPoul-Henning Kamp if (l & k) { 2687bf758bfSPoul-Henning Kamp pps_capture(&sc->pps[j]); 2697bf758bfSPoul-Henning Kamp pps_event(&sc->pps[j], 2707bf758bfSPoul-Henning Kamp i & k ? PPS_CAPTUREASSERT : PPS_CAPTURECLEAR); 2717bf758bfSPoul-Henning Kamp } 272bd61b8e8SPoul-Henning Kamp k += k; 273bd61b8e8SPoul-Henning Kamp } 274bd61b8e8SPoul-Henning Kamp sc->lastdata = i; 275bd61b8e8SPoul-Henning Kamp } 276bd61b8e8SPoul-Henning Kamp 277bd61b8e8SPoul-Henning Kamp static void 2780f210c92SNicolas Souchu ppsintr(void *arg) 279507e2e44SPoul-Henning Kamp { 2800f210c92SNicolas Souchu device_t ppsdev = (device_t)arg; 2810f210c92SNicolas Souchu struct pps_data *sc = DEVTOSOFTC(ppsdev); 282bd61b8e8SPoul-Henning Kamp device_t ppbus = sc->ppbus; 283507e2e44SPoul-Henning Kamp 28476061701SWarner Losh mtx_lock(&sc->mtx); 2857bf758bfSPoul-Henning Kamp pps_capture(&sc->pps[0]); 28676061701SWarner Losh if (!(ppb_rstr(ppbus) & nACK)) { 28776061701SWarner Losh mtx_unlock(&sc->mtx); 288507e2e44SPoul-Henning Kamp return; 28976061701SWarner Losh } 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); 29576061701SWarner Losh mtx_unlock(&sc->mtx); 296507e2e44SPoul-Henning Kamp } 297507e2e44SPoul-Henning Kamp 298507e2e44SPoul-Henning Kamp static int 29989c9c53dSPoul-Henning Kamp ppsioctl(struct cdev *dev, u_long cmd, caddr_t data, int flags, struct thread *td) 300507e2e44SPoul-Henning Kamp { 301bd61b8e8SPoul-Henning Kamp struct pps_data *sc = dev->si_drv1; 302a885bb60SJohn Baldwin int subdev = (intptr_t)dev->si_drv2; 30376061701SWarner Losh int err; 304507e2e44SPoul-Henning Kamp 30576061701SWarner Losh mtx_lock(&sc->mtx); 30676061701SWarner Losh err = pps_ioctl(cmd, data, &sc->pps[subdev]); 30776061701SWarner Losh mtx_unlock(&sc->mtx); 30876061701SWarner Losh return (err); 3098afeddf0SPoul-Henning Kamp } 310507e2e44SPoul-Henning Kamp 3110f063508SPeter Wemm static device_method_t pps_methods[] = { 3120f063508SPeter Wemm /* device interface */ 3130f063508SPeter Wemm DEVMETHOD(device_identify, ppsidentify), 3140f063508SPeter Wemm DEVMETHOD(device_probe, ppsprobe), 3150f063508SPeter Wemm DEVMETHOD(device_attach, ppsattach), 3160f063508SPeter Wemm 3170f063508SPeter Wemm { 0, 0 } 3180f063508SPeter Wemm }; 3190f063508SPeter Wemm 3200f063508SPeter Wemm static driver_t pps_driver = { 3210f063508SPeter Wemm PPS_NAME, 3220f063508SPeter Wemm pps_methods, 3230f063508SPeter Wemm sizeof(struct pps_data), 3240f063508SPeter Wemm }; 3250f210c92SNicolas Souchu DRIVER_MODULE(pps, ppbus, pps_driver, pps_devclass, 0, 0); 326