| 1 | /* |
| 2 | * ---------------------------------------------------------------------------- |
| 3 | * "THE BEER-WARE LICENSE" (Revision 42): |
| 4 | * <phk@FreeBSD.org> wrote this file. As long as you retain this notice you |
| 5 | * can do whatever you want with this stuff. If we meet some day, and you think |
| 6 | * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp |
| 7 | * ---------------------------------------------------------------------------- |
| 8 | * |
| 9 | * $FreeBSD: src/sys/dev/ppbus/pps.c,v 1.24.2.1 2000/05/24 00:20:57 n_hibma Exp $ |
| 10 | * $DragonFly: src/sys/dev/misc/pps/pps.c,v 1.4 2003/07/21 07:57:41 dillon Exp $ |
| 11 | * |
| 12 | * This driver implements a draft-mogul-pps-api-02.txt PPS source. |
| 13 | * |
| 14 | * The input pin is pin#10 |
| 15 | * The echo output pin is pin#14 |
| 16 | * |
| 17 | */ |
| 18 | |
| 19 | #include <sys/param.h> |
| 20 | #include <sys/kernel.h> |
| 21 | #include <sys/systm.h> |
| 22 | #include <sys/module.h> |
| 23 | #include <sys/bus.h> |
| 24 | #include <sys/conf.h> |
| 25 | #include <sys/timepps.h> |
| 26 | #include <machine/bus.h> |
| 27 | #include <machine/resource.h> |
| 28 | #include <sys/rman.h> |
| 29 | |
| 30 | #include <dev/ppbus/ppbconf.h> |
| 31 | #include "ppbus_if.h" |
| 32 | #include <dev/ppbus/ppbio.h> |
| 33 | #include "pps.h" |
| 34 | |
| 35 | #define PPS_NAME "pps" /* our official name */ |
| 36 | |
| 37 | struct pps_data { |
| 38 | int pps_open; |
| 39 | struct ppb_device pps_dev; |
| 40 | struct pps_state pps; |
| 41 | |
| 42 | struct resource *intr_resource; /* interrupt resource */ |
| 43 | void *intr_cookie; /* interrupt registration cookie */ |
| 44 | }; |
| 45 | |
| 46 | static void ppsintr(void *arg); |
| 47 | |
| 48 | #define DEVTOSOFTC(dev) \ |
| 49 | ((struct pps_data *)device_get_softc(dev)) |
| 50 | #define UNITOSOFTC(unit) \ |
| 51 | ((struct pps_data *)devclass_get_softc(pps_devclass, (unit))) |
| 52 | #define UNITODEVICE(unit) \ |
| 53 | (devclass_get_device(pps_devclass, (unit))) |
| 54 | |
| 55 | static devclass_t pps_devclass; |
| 56 | |
| 57 | static d_open_t ppsopen; |
| 58 | static d_close_t ppsclose; |
| 59 | static d_ioctl_t ppsioctl; |
| 60 | |
| 61 | #define CDEV_MAJOR 89 |
| 62 | static struct cdevsw pps_cdevsw = { |
| 63 | /* name */ PPS_NAME, |
| 64 | /* maj */ CDEV_MAJOR, |
| 65 | /* flags */ 0, |
| 66 | /* port */ NULL, |
| 67 | /* autoq */ 0, |
| 68 | |
| 69 | /* open */ ppsopen, |
| 70 | /* close */ ppsclose, |
| 71 | /* read */ noread, |
| 72 | /* write */ nowrite, |
| 73 | /* ioctl */ ppsioctl, |
| 74 | /* poll */ nopoll, |
| 75 | /* mmap */ nommap, |
| 76 | /* strategy */ nostrategy, |
| 77 | /* dump */ nodump, |
| 78 | /* psize */ nopsize |
| 79 | }; |
| 80 | |
| 81 | static void |
| 82 | ppsidentify(driver_t *driver, device_t parent) |
| 83 | { |
| 84 | |
| 85 | BUS_ADD_CHILD(parent, 0, PPS_NAME, 0); |
| 86 | } |
| 87 | |
| 88 | static int |
| 89 | ppsprobe(device_t ppsdev) |
| 90 | { |
| 91 | struct pps_data *sc; |
| 92 | dev_t dev; |
| 93 | int unit; |
| 94 | |
| 95 | sc = DEVTOSOFTC(ppsdev); |
| 96 | bzero(sc, sizeof(struct pps_data)); |
| 97 | |
| 98 | unit = device_get_unit(ppsdev); |
| 99 | dev = make_dev(&pps_cdevsw, unit, |
| 100 | UID_ROOT, GID_WHEEL, 0644, PPS_NAME "%d", unit); |
| 101 | |
| 102 | device_set_desc(ppsdev, "Pulse per second Timing Interface"); |
| 103 | |
| 104 | sc->pps.ppscap = PPS_CAPTUREASSERT | PPS_ECHOASSERT; |
| 105 | pps_init(&sc->pps); |
| 106 | return (0); |
| 107 | } |
| 108 | |
| 109 | static int |
| 110 | ppsattach(device_t dev) |
| 111 | { |
| 112 | struct pps_data *sc = DEVTOSOFTC(dev); |
| 113 | device_t ppbus = device_get_parent(dev); |
| 114 | int irq, zero = 0; |
| 115 | |
| 116 | /* retrieve the ppbus irq */ |
| 117 | BUS_READ_IVAR(ppbus, dev, PPBUS_IVAR_IRQ, &irq); |
| 118 | |
| 119 | if (irq > 0) { |
| 120 | /* declare our interrupt handler */ |
| 121 | sc->intr_resource = bus_alloc_resource(dev, SYS_RES_IRQ, |
| 122 | &zero, irq, irq, 1, RF_SHAREABLE); |
| 123 | } |
| 124 | /* interrupts seem mandatory */ |
| 125 | if (sc->intr_resource == 0) |
| 126 | return (ENXIO); |
| 127 | |
| 128 | return (0); |
| 129 | } |
| 130 | |
| 131 | static int |
| 132 | ppsopen(dev_t dev, int flags, int fmt, struct thread *td) |
| 133 | { |
| 134 | u_int unit = minor(dev); |
| 135 | struct pps_data *sc = UNITOSOFTC(unit); |
| 136 | device_t ppsdev = UNITODEVICE(unit); |
| 137 | device_t ppbus = device_get_parent(ppsdev); |
| 138 | int error; |
| 139 | |
| 140 | if (!sc->pps_open) { |
| 141 | if (ppb_request_bus(ppbus, ppsdev, PPB_WAIT|PPB_INTR)) |
| 142 | return (EINTR); |
| 143 | |
| 144 | /* attach the interrupt handler */ |
| 145 | if ((error = BUS_SETUP_INTR(ppbus, ppsdev, sc->intr_resource, |
| 146 | INTR_TYPE_TTY, ppsintr, ppsdev, |
| 147 | &sc->intr_cookie))) { |
| 148 | ppb_release_bus(ppbus, ppsdev); |
| 149 | return (error); |
| 150 | } |
| 151 | |
| 152 | ppb_wctr(ppbus, 0); |
| 153 | ppb_wctr(ppbus, IRQENABLE); |
| 154 | sc->pps_open = 1; |
| 155 | } |
| 156 | |
| 157 | return(0); |
| 158 | } |
| 159 | |
| 160 | static int |
| 161 | ppsclose(dev_t dev, int flags, int fmt, struct thread *td) |
| 162 | { |
| 163 | u_int unit = minor(dev); |
| 164 | struct pps_data *sc = UNITOSOFTC(unit); |
| 165 | device_t ppsdev = UNITODEVICE(unit); |
| 166 | device_t ppbus = device_get_parent(ppsdev); |
| 167 | |
| 168 | sc->pps.ppsparam.mode = 0; /* PHK ??? */ |
| 169 | |
| 170 | ppb_wdtr(ppbus, 0); |
| 171 | ppb_wctr(ppbus, 0); |
| 172 | |
| 173 | /* Note: the interrupt handler is automatically detached */ |
| 174 | ppb_release_bus(ppbus, ppsdev); |
| 175 | sc->pps_open = 0; |
| 176 | return(0); |
| 177 | } |
| 178 | |
| 179 | static void |
| 180 | ppsintr(void *arg) |
| 181 | { |
| 182 | device_t ppsdev = (device_t)arg; |
| 183 | device_t ppbus = device_get_parent(ppsdev); |
| 184 | struct pps_data *sc = DEVTOSOFTC(ppsdev); |
| 185 | struct timecounter *tc; |
| 186 | unsigned count; |
| 187 | |
| 188 | tc = timecounter; |
| 189 | count = timecounter->tc_get_timecount(tc); |
| 190 | if (!(ppb_rstr(ppbus) & nACK)) |
| 191 | return; |
| 192 | if (sc->pps.ppsparam.mode & PPS_ECHOASSERT) |
| 193 | ppb_wctr(ppbus, IRQENABLE | AUTOFEED); |
| 194 | pps_event(&sc->pps, tc, count, PPS_CAPTUREASSERT); |
| 195 | if (sc->pps.ppsparam.mode & PPS_ECHOASSERT) |
| 196 | ppb_wctr(ppbus, IRQENABLE); |
| 197 | } |
| 198 | |
| 199 | static int |
| 200 | ppsioctl(dev_t dev, u_long cmd, caddr_t data, int flags, struct thread *td) |
| 201 | { |
| 202 | u_int unit = minor(dev); |
| 203 | struct pps_data *sc = UNITOSOFTC(unit); |
| 204 | |
| 205 | return (pps_ioctl(cmd, data, &sc->pps)); |
| 206 | } |
| 207 | |
| 208 | static device_method_t pps_methods[] = { |
| 209 | /* device interface */ |
| 210 | DEVMETHOD(device_identify, ppsidentify), |
| 211 | DEVMETHOD(device_probe, ppsprobe), |
| 212 | DEVMETHOD(device_attach, ppsattach), |
| 213 | |
| 214 | { 0, 0 } |
| 215 | }; |
| 216 | |
| 217 | static driver_t pps_driver = { |
| 218 | PPS_NAME, |
| 219 | pps_methods, |
| 220 | sizeof(struct pps_data), |
| 221 | }; |
| 222 | DRIVER_MODULE(pps, ppbus, pps_driver, pps_devclass, 0, 0); |