1113094Sphk/*- 2135482Sphk * Copyright (c) 2003-2004 Poul-Henning Kamp 3113094Sphk * All rights reserved. 4113094Sphk * 5113094Sphk * Redistribution and use in source and binary forms, with or without 6113094Sphk * modification, are permitted provided that the following conditions 7113094Sphk * are met: 8113094Sphk * 1. Redistributions of source code must retain the above copyright 9113094Sphk * notice, this list of conditions and the following disclaimer. 10113094Sphk * 2. Redistributions in binary form must reproduce the above copyright 11113094Sphk * notice, this list of conditions and the following disclaimer in the 12113094Sphk * documentation and/or other materials provided with the distribution. 13113094Sphk * 3. The names of the authors may not be used to endorse or promote 14113094Sphk * products derived from this software without specific prior written 15113094Sphk * permission. 16113094Sphk * 17113094Sphk * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18113094Sphk * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19113094Sphk * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20113094Sphk * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 21113094Sphk * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22113094Sphk * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23113094Sphk * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24113094Sphk * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25113094Sphk * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26113094Sphk * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27113094Sphk * SUCH DAMAGE. 28135482Sphk * 29135482Sphk * This is a device driver or the Adlink 9812 and 9810 ADC cards, mainly 30135482Sphk * intended to support Software Defined Radio reception of timesignals 31135482Sphk * in the VLF band. See http://phk.freebsd.dk/loran-c 32135482Sphk * 33135482Sphk * The driver is configured with ioctls which define a ringbuffer with 34135482Sphk * a given number of chunks in it. The a control structure and the 35135482Sphk * ringbuffer can then be mmap(2)'ed into userland and the application 36135482Sphk * can operate on the data directly. 37135482Sphk * 38135482Sphk * Tested with 10MHz external clock, divisor of 2 (ie: 5MHz sampling), 39135482Sphk * One channel active (ie: 2 bytes per sample = 10MB/sec) on a 660MHz 40135482Sphk * Celeron PC. 41135482Sphk * 42113094Sphk */ 43113094Sphk 44135482Sphk#ifdef _KERNEL 45119418Sobrien#include <sys/cdefs.h> 46119418Sobrien__FBSDID("$FreeBSD$"); 47119418Sobrien 48113094Sphk#include <sys/param.h> 49113094Sphk#include <sys/systm.h> 50113094Sphk#include <sys/malloc.h> 51113094Sphk#include <sys/kernel.h> 52130026Sphk#include <sys/module.h> 53113270Sphk#include <sys/kthread.h> 54113094Sphk#include <sys/conf.h> 55113094Sphk#include <sys/bus.h> 56113094Sphk#include <machine/bus.h> 57113094Sphk#include <machine/resource.h> 58113094Sphk#include <sys/rman.h> 59119274Simp#include <dev/pci/pcireg.h> 60119274Simp#include <dev/pci/pcivar.h> 61113094Sphk#include <pci_if.h> 62113094Sphk#include <vm/vm.h> 63113094Sphk#include <vm/pmap.h> 64113094Sphk 65113270Sphk#endif /* _KERNEL */ 66113270Sphk 67113270Sphk#include <sys/ioccom.h> 68113270Sphk 69135482Sphk#define ADLINK_SETDIVISOR _IOWR('A', 255, u_int) /* divisor */ 70135482Sphk#define ADLINK_SETCHUNKSIZE _IOWR('A', 254, u_int) /* bytes */ 71135482Sphk#define ADLINK_SETRINGSIZE _IOWR('A', 253, u_int) /* bytes */ 72135482Sphk#define ADLINK_START _IOWR('A', 252, u_int) /* dummy */ 73135482Sphk#define ADLINK_STOP _IOWR('A', 251, u_int) /* dummy */ 74135482Sphk#define ADLINK_RESET _IOWR('A', 250, u_int) /* dummy */ 75113270Sphk 76135482Sphkstruct page0 { 77135482Sphk u_int version; 78135482Sphk int state; 79135482Sphk# define STATE_RESET -1 80135482Sphk# define STATE_RUN 0 81135482Sphk u_int divisor; /* int */ 82135482Sphk u_int chunksize; /* bytes */ 83135482Sphk u_int ringsize; /* chunks */ 84143844Sphk u_int o_sample; /* 85135482Sphk * offset of ring generation 86135482Sphk * array 87135482Sphk */ 88135482Sphk u_int o_ring; /* offset of ring */ 89113270Sphk}; 90113270Sphk 91143844Sphk#define PAGE0VERSION 20050219 92113270Sphk 93113270Sphk#ifdef _KERNEL 94113270Sphk 95135482Sphkstruct pgstat { 96143844Sphk uint64_t *sample; 97135482Sphk vm_paddr_t phys; 98135482Sphk void *virt; 99135482Sphk struct pgstat *next; 100113094Sphk}; 101113094Sphk 102113094Sphkstruct softc { 103113094Sphk device_t device; 104113094Sphk void *intrhand; 105150526Sphk struct resource *res[3]; 106135482Sphk struct cdev *dev; 107113270Sphk off_t mapvir; 108135482Sphk int error; 109135482Sphk struct page0 *p0; 110135482Sphk u_int nchunks; 111135482Sphk struct pgstat *chunks; 112135482Sphk struct pgstat *next; 113143844Sphk uint64_t sample; 114135482Sphk}; 115113094Sphk 116135482Sphkstatic d_ioctl_t adlink_ioctl; 117135482Sphkstatic d_mmap_t adlink_mmap; 118166901Spisostatic int adlink_intr(void *arg); 119113270Sphk 120135482Sphkstatic struct cdevsw adlink_cdevsw = { 121135482Sphk .d_version = D_VERSION, 122171717Skib .d_flags = D_NEEDGIANT, 123135482Sphk .d_ioctl = adlink_ioctl, 124135482Sphk .d_mmap = adlink_mmap, 125135482Sphk .d_name = "adlink", 126135482Sphk}; 127113094Sphk 128166901Spisostatic int 129135482Sphkadlink_intr(void *arg) 130135482Sphk{ 131135482Sphk struct softc *sc; 132135482Sphk struct pgstat *pg; 133135482Sphk uint32_t u; 134113270Sphk 135135482Sphk sc = arg; 136150526Sphk u = bus_read_4(sc->res[0], 0x38); 137135482Sphk if (!(u & 0x00800000)) 138166901Spiso return; // XXX - FILTER_STRAY? 139150526Sphk bus_write_4(sc->res[0], 0x38, u | 0x003f4000); 140113270Sphk 141143844Sphk sc->sample += sc->p0->chunksize / 2; 142135482Sphk pg = sc->next; 143143844Sphk *(pg->sample) = sc->sample; 144113270Sphk 145150526Sphk u = bus_read_4(sc->res[1], 0x18); 146135482Sphk if (u & 1) 147135482Sphk sc->p0->state = EIO; 148113094Sphk 149135482Sphk if (sc->p0->state != STATE_RUN) { 150135482Sphk printf("adlink: stopping %d\n", sc->p0->state); 151166901Spiso return; // XXX - FILTER_STRAY? 152113270Sphk } 153113270Sphk 154135482Sphk pg = pg->next; 155135482Sphk sc->next = pg; 156143844Sphk *(pg->sample) = 0; 157150526Sphk bus_write_4(sc->res[0], 0x24, pg->phys); 158150526Sphk bus_write_4(sc->res[0], 0x28, sc->p0->chunksize); 159113270Sphk wakeup(sc); 160166901Spiso return (FILTER_HANDLED); 161113270Sphk} 162113270Sphk 163113094Sphkstatic int 164201223Srnolandadlink_mmap(struct cdev *dev, vm_ooffset_t offset, vm_paddr_t *paddr, 165201223Srnoland int nprot, vm_memattr_t *memattr) 166113270Sphk{ 167135482Sphk struct softc *sc; 168135482Sphk vm_offset_t o; 169135482Sphk int i; 170135482Sphk struct pgstat *pg; 171113270Sphk 172135482Sphk sc = dev->si_drv1; 173135482Sphk if (nprot != VM_PROT_READ) 174135482Sphk return (-1); 175135482Sphk if (offset == 0) { 176135482Sphk *paddr = vtophys(sc->p0); 177135482Sphk return (0); 178113270Sphk } 179135482Sphk o = PAGE_SIZE; 180135482Sphk pg = sc->chunks; 181135482Sphk for (i = 0; i < sc->nchunks; i++, pg++) { 182135482Sphk if (offset - o >= sc->p0->chunksize) { 183135482Sphk o += sc->p0->chunksize; 184135482Sphk continue; 185113270Sphk } 186135482Sphk *paddr = pg->phys + (offset - o); 187135482Sphk return (0); 188113270Sphk } 189135482Sphk return (-1); 190113270Sphk} 191113270Sphk 192113270Sphkstatic int 193135482Sphkadlink_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag, struct thread *td) 194113094Sphk{ 195113094Sphk struct softc *sc; 196113270Sphk int i, error; 197135482Sphk u_int u; 198135482Sphk struct pgstat *pg; 199143844Sphk uint64_t *sample; 200135482Sphk 201113094Sphk sc = dev->si_drv1; 202135482Sphk u = *(u_int*)data; 203135482Sphk error = 0; 204135482Sphk switch (cmd) { 205135482Sphk case ADLINK_SETDIVISOR: 206135482Sphk if (sc->p0->state == STATE_RUN) 207135482Sphk return (EBUSY); 208135482Sphk if (u & 1) 209135482Sphk return (EINVAL); 210135482Sphk sc->p0->divisor = u; 211135482Sphk break; 212135482Sphk case ADLINK_SETCHUNKSIZE: 213135482Sphk if (sc->p0->state != STATE_RESET) 214135482Sphk return (EBUSY); 215135482Sphk if (u % PAGE_SIZE) 216135482Sphk return (EINVAL); 217135482Sphk if (sc->p0->ringsize != 0 && sc->p0->ringsize % u) 218135482Sphk return (EINVAL); 219135482Sphk sc->p0->chunksize = u; 220135482Sphk break; 221135482Sphk case ADLINK_SETRINGSIZE: 222135482Sphk if (sc->p0->state != STATE_RESET) 223135482Sphk return (EBUSY); 224135482Sphk if (u % PAGE_SIZE) 225135482Sphk return (EINVAL); 226135482Sphk if (sc->p0->chunksize != 0 && u % sc->p0->chunksize) 227135482Sphk return (EINVAL); 228135482Sphk sc->p0->ringsize = u; 229135482Sphk break; 230135482Sphk case ADLINK_START: 231135482Sphk if (sc->p0->state == STATE_RUN) 232135482Sphk return (EBUSY); 233135482Sphk if (sc->p0->state == STATE_RESET) { 234135482Sphk 235135482Sphk if (sc->p0->chunksize == 0) 236135482Sphk sc->p0->chunksize = 4 * PAGE_SIZE; 237135482Sphk if (sc->p0->ringsize == 0) 238135482Sphk sc->p0->ringsize = 16 * sc->p0->chunksize; 239135482Sphk if (sc->p0->divisor == 0) 240135482Sphk sc->p0->divisor = 4; 241113270Sphk 242135482Sphk sc->nchunks = sc->p0->ringsize / sc->p0->chunksize; 243143844Sphk if (sc->nchunks * sizeof (*pg->sample) + 244135482Sphk sizeof *sc->p0 > PAGE_SIZE) 245135482Sphk return (EINVAL); 246135482Sphk sc->p0->o_ring = PAGE_SIZE; 247143844Sphk sample = (uint64_t *)(sc->p0 + 1); 248143844Sphk sc->p0->o_sample = 249143844Sphk (uintptr_t)sample - (uintptr_t)(sc->p0); 250135482Sphk pg = malloc(sizeof *pg * sc->nchunks, 251135482Sphk M_DEVBUF, M_WAITOK | M_ZERO); 252135482Sphk sc->chunks = pg; 253135482Sphk for (i = 0; i < sc->nchunks; i++) { 254143844Sphk pg->sample = sample; 255143844Sphk *pg->sample = 0; 256143844Sphk sample++; 257135482Sphk pg->virt = contigmalloc(sc->p0->chunksize, 258135482Sphk M_DEVBUF, M_WAITOK, 259135482Sphk 0ul, 0xfffffffful, 260135482Sphk PAGE_SIZE, 0); 261135482Sphk pg->phys = vtophys(pg->virt); 262135482Sphk if (i == sc->nchunks - 1) 263135482Sphk pg->next = sc->chunks; 264135482Sphk else 265135482Sphk pg->next = pg + 1; 266135482Sphk pg++; 267135482Sphk } 268135482Sphk sc->next = sc->chunks; 269135482Sphk } 270113094Sphk 271135482Sphk /* Reset generation numbers */ 272135482Sphk pg = sc->chunks; 273135482Sphk for (i = 0; i < sc->nchunks; i++) { 274143844Sphk *pg->sample = 0; 275135482Sphk pg++; 276135482Sphk } 277113270Sphk 278135482Sphk /* Enable interrupts on write complete */ 279150526Sphk bus_write_4(sc->res[0], 0x38, 0x00004000); 280113270Sphk 281135482Sphk /* Sample CH0 only */ 282150526Sphk bus_write_4(sc->res[1], 0x00, 1); 283113270Sphk 284135482Sphk /* Divide clock by four */ 285150526Sphk bus_write_4(sc->res[1], 0x04, sc->p0->divisor); 286113270Sphk 287135482Sphk /* Software trigger mode: software */ 288150526Sphk bus_write_4(sc->res[1], 0x08, 0); 289113270Sphk 290135482Sphk /* Trigger level zero */ 291150526Sphk bus_write_4(sc->res[1], 0x0c, 0); 292113270Sphk 293135482Sphk /* Trigger source CH0 (not used) */ 294150526Sphk bus_write_4(sc->res[1], 0x10, 0); 295113270Sphk 296135482Sphk /* Fifo control/status: flush */ 297150526Sphk bus_write_4(sc->res[1], 0x18, 3); 298113270Sphk 299135482Sphk /* Clock source: external sine */ 300150526Sphk bus_write_4(sc->res[1], 0x20, 2); 301113270Sphk 302135482Sphk /* Chipmunks are go! */ 303135482Sphk sc->p0->state = STATE_RUN; 304113094Sphk 305135482Sphk /* Set up Write DMA */ 306135482Sphk pg = sc->next = sc->chunks; 307143844Sphk *(pg->sample) = 0; 308150526Sphk bus_write_4(sc->res[0], 0x24, pg->phys); 309150526Sphk bus_write_4(sc->res[0], 0x28, sc->p0->chunksize); 310150526Sphk u = bus_read_4(sc->res[0], 0x3c); 311150526Sphk bus_write_4(sc->res[0], 0x3c, u | 0x00000600); 312113094Sphk 313135482Sphk /* Acquisition Enable Register: go! */ 314150526Sphk bus_write_4(sc->res[1], 0x1c, 1); 315113094Sphk 316135482Sphk break; 317135482Sphk case ADLINK_STOP: 318135482Sphk if (sc->p0->state == STATE_RESET) 319135482Sphk break; 320135482Sphk sc->p0->state = EINTR; 321143844Sphk while (*(sc->next->sample) == 0) 322135482Sphk tsleep(sc, PUSER | PCATCH, "adstop", 1); 323135482Sphk break; 324135482Sphk#ifdef notyet 325135482Sphk /* 326135482Sphk * I'm not sure we can actually do this. How do we revoke 327135482Sphk * the mmap'ed pages from any process having them mmapped ? 328135482Sphk */ 329135482Sphk case ADLINK_RESET: 330135482Sphk if (sc->p0->state == STATE_RESET) 331135482Sphk break; 332135482Sphk sc->p0->state = EINTR; 333143844Sphk while (*(sc->next->samp) == 0) 334135482Sphk tsleep(sc, PUSER | PCATCH, "adreset", 1); 335135482Sphk /* deallocate ring buffer */ 336135482Sphk break; 337135482Sphk#endif 338135482Sphk default: 339135482Sphk error = ENOIOCTL; 340135482Sphk break; 341113270Sphk } 342135482Sphk return (error); 343113270Sphk} 344113270Sphk 345113094Sphkstatic devclass_t adlink_devclass; 346113094Sphk 347113094Sphkstatic int 348113094Sphkadlink_probe(device_t self) 349113094Sphk{ 350113094Sphk 351113094Sphk if (pci_get_devid(self) != 0x80da10e8) 352113094Sphk return (ENXIO); 353113094Sphk device_set_desc(self, "Adlink PCI-9812 4 ch 12 bit 20 msps"); 354143168Simp return (BUS_PROBE_DEFAULT); 355113094Sphk} 356113094Sphk 357150526Sphkstatic struct resource_spec adlink_res_spec[] = { 358150526Sphk { SYS_RES_IOPORT, PCIR_BAR(0), RF_ACTIVE}, 359150526Sphk { SYS_RES_IOPORT, PCIR_BAR(1), RF_ACTIVE}, 360150526Sphk { SYS_RES_IRQ, 0, RF_ACTIVE | RF_SHAREABLE}, 361150526Sphk { -1, 0, 0 } 362150526Sphk}; 363150526Sphk 364113094Sphkstatic int 365113094Sphkadlink_attach(device_t self) 366113094Sphk{ 367113094Sphk struct softc *sc; 368150526Sphk int i, error; 369113094Sphk 370113094Sphk sc = device_get_softc(self); 371113094Sphk bzero(sc, sizeof *sc); 372113094Sphk sc->device = self; 373113094Sphk 374150526Sphk error = bus_alloc_resources(self, adlink_res_spec, sc->res); 375150526Sphk if (error) 376150526Sphk return (error); 377113094Sphk 378217068Sjhb i = bus_setup_intr(self, sc->res[2], INTR_TYPE_MISC, 379166901Spiso adlink_intr, NULL, sc, &sc->intrhand); 380113270Sphk if (i) { 381113270Sphk printf("adlink: Couldn't get FAST intr\n"); 382150526Sphk i = bus_setup_intr(self, sc->res[2], 383135482Sphk INTR_MPSAFE | INTR_TYPE_MISC, 384166901Spiso NULL, (driver_intr_t *)adlink_intr, sc, &sc->intrhand); 385113270Sphk } 386113094Sphk 387150526Sphk if (i) { 388150526Sphk bus_release_resources(self, adlink_res_spec, sc->res); 389113094Sphk return (ENODEV); 390150526Sphk } 391113094Sphk 392135482Sphk sc->p0 = malloc(PAGE_SIZE, M_DEVBUF, M_WAITOK | M_ZERO); 393135482Sphk sc->p0->version = PAGE0VERSION; 394135482Sphk sc->p0->state = STATE_RESET; 395135482Sphk 396113094Sphk sc->dev = make_dev(&adlink_cdevsw, device_get_unit(self), 397113094Sphk UID_ROOT, GID_WHEEL, 0444, "adlink%d", device_get_unit(self)); 398113094Sphk sc->dev->si_drv1 = sc; 399113094Sphk 400113094Sphk return (0); 401113094Sphk} 402113094Sphk 403113094Sphkstatic device_method_t adlink_methods[] = { 404113094Sphk /* Device interface */ 405113094Sphk DEVMETHOD(device_probe, adlink_probe), 406113094Sphk DEVMETHOD(device_attach, adlink_attach), 407113094Sphk DEVMETHOD(device_suspend, bus_generic_suspend), 408113094Sphk DEVMETHOD(device_resume, bus_generic_resume), 409113094Sphk DEVMETHOD(device_shutdown, bus_generic_shutdown), 410246128Ssbz 411246128Ssbz DEVMETHOD_END 412113094Sphk}; 413113094Sphk 414113094Sphkstatic driver_t adlink_driver = { 415113094Sphk "adlink", 416113094Sphk adlink_methods, 417113094Sphk sizeof(struct softc) 418113094Sphk}; 419113094Sphk 420113094SphkDRIVER_MODULE(adlink, pci, adlink_driver, adlink_devclass, 0, 0); 421135482Sphk 422113270Sphk#endif /* _KERNEL */ 423