dt282x.c revision f11c4b6caef19f331a9ce4821602eed6b12f131f
1/* 2 comedi/drivers/dt282x.c 3 Hardware driver for Data Translation DT2821 series 4 5 COMEDI - Linux Control and Measurement Device Interface 6 Copyright (C) 1997-8 David A. Schleef <ds@schleef.org> 7 8 This program is free software; you can redistribute it and/or modify 9 it under the terms of the GNU General Public License as published by 10 the Free Software Foundation; either version 2 of the License, or 11 (at your option) any later version. 12 13 This program is distributed in the hope that it will be useful, 14 but WITHOUT ANY WARRANTY; without even the implied warranty of 15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 GNU General Public License for more details. 17 */ 18/* 19Driver: dt282x 20Description: Data Translation DT2821 series (including DT-EZ) 21Author: ds 22Devices: [Data Translation] DT2821 (dt2821), 23 DT2821-F-16SE (dt2821-f), DT2821-F-8DI (dt2821-f), 24 DT2821-G-16SE (dt2821-f), DT2821-G-8DI (dt2821-g), 25 DT2823 (dt2823), 26 DT2824-PGH (dt2824-pgh), DT2824-PGL (dt2824-pgl), DT2825 (dt2825), 27 DT2827 (dt2827), DT2828 (dt2828), DT21-EZ (dt21-ez), DT23-EZ (dt23-ez), 28 DT24-EZ (dt24-ez), DT24-EZ-PGL (dt24-ez-pgl) 29Status: complete 30Updated: Wed, 22 Aug 2001 17:11:34 -0700 31 32Configuration options: 33 [0] - I/O port base address 34 [1] - IRQ 35 [2] - DMA 1 36 [3] - DMA 2 37 [4] - AI jumpered for 0=single ended, 1=differential 38 [5] - AI jumpered for 0=straight binary, 1=2's complement 39 [6] - AO 0 jumpered for 0=straight binary, 1=2's complement 40 [7] - AO 1 jumpered for 0=straight binary, 1=2's complement 41 [8] - AI jumpered for 0=[-10,10]V, 1=[0,10], 2=[-5,5], 3=[0,5] 42 [9] - AO 0 jumpered for 0=[-10,10]V, 1=[0,10], 2=[-5,5], 3=[0,5], 43 4=[-2.5,2.5] 44 [10]- A0 1 jumpered for 0=[-10,10]V, 1=[0,10], 2=[-5,5], 3=[0,5], 45 4=[-2.5,2.5] 46 47Notes: 48 - AO commands might be broken. 49 - If you try to run a command on both the AI and AO subdevices 50 simultaneously, bad things will happen. The driver needs to 51 be fixed to check for this situation and return an error. 52*/ 53 54#include <linux/module.h> 55#include "../comedidev.h" 56 57#include <linux/delay.h> 58#include <linux/gfp.h> 59#include <linux/interrupt.h> 60#include <linux/io.h> 61 62#include <asm/dma.h> 63 64#include "comedi_fc.h" 65 66#define DT2821_SIZE 0x10 67 68/* 69 * Registers in the DT282x 70 */ 71 72#define DT2821_ADCSR 0x00 /* A/D Control/Status */ 73#define DT2821_CHANCSR 0x02 /* Channel Control/Status */ 74#define DT2821_ADDAT 0x04 /* A/D data */ 75#define DT2821_DACSR 0x06 /* D/A Control/Status */ 76#define DT2821_DADAT 0x08 /* D/A data */ 77#define DT2821_DIODAT 0x0a /* digital data */ 78#define DT2821_SUPCSR 0x0c /* Supervisor Control/Status */ 79#define DT2821_TMRCTR 0x0e /* Timer/Counter */ 80 81/* 82 * At power up, some registers are in a well-known state. The 83 * masks and values are as follows: 84 */ 85 86#define DT2821_ADCSR_MASK 0xfff0 87#define DT2821_ADCSR_VAL 0x7c00 88 89#define DT2821_CHANCSR_MASK 0xf0f0 90#define DT2821_CHANCSR_VAL 0x70f0 91 92#define DT2821_DACSR_MASK 0x7c93 93#define DT2821_DACSR_VAL 0x7c90 94 95#define DT2821_SUPCSR_MASK 0xf8ff 96#define DT2821_SUPCSR_VAL 0x0000 97 98#define DT2821_TMRCTR_MASK 0xff00 99#define DT2821_TMRCTR_VAL 0xf000 100 101/* 102 * Bit fields of each register 103 */ 104 105/* ADCSR */ 106 107#define DT2821_ADERR 0x8000 /* (R) 1 for A/D error */ 108#define DT2821_ADCLK 0x0200 /* (R/W) A/D clock enable */ 109 /* 0x7c00 read as 1's */ 110#define DT2821_MUXBUSY 0x0100 /* (R) multiplexer busy */ 111#define DT2821_ADDONE 0x0080 /* (R) A/D done */ 112#define DT2821_IADDONE 0x0040 /* (R/W) interrupt on A/D done */ 113 /* 0x0030 gain select */ 114 /* 0x000f channel select */ 115 116/* CHANCSR */ 117 118#define DT2821_LLE 0x8000 /* (R/W) Load List Enable */ 119 /* 0x7000 read as 1's */ 120 /* 0x0f00 (R) present address */ 121 /* 0x00f0 read as 1's */ 122 /* 0x000f (R) number of entries - 1 */ 123 124/* DACSR */ 125 126#define DT2821_DAERR 0x8000 /* (R) D/A error */ 127#define DT2821_YSEL 0x0200 /* (R/W) DAC 1 select */ 128#define DT2821_SSEL 0x0100 /* (R/W) single channel select */ 129#define DT2821_DACRDY 0x0080 /* (R) DAC ready */ 130#define DT2821_IDARDY 0x0040 /* (R/W) interrupt on DAC ready */ 131#define DT2821_DACLK 0x0020 /* (R/W) D/A clock enable */ 132#define DT2821_HBOE 0x0002 /* (R/W) DIO high byte output enable */ 133#define DT2821_LBOE 0x0001 /* (R/W) DIO low byte output enable */ 134 135/* SUPCSR */ 136 137#define DT2821_DMAD 0x8000 /* (R) DMA done */ 138#define DT2821_ERRINTEN 0x4000 /* (R/W) interrupt on error */ 139#define DT2821_CLRDMADNE 0x2000 /* (W) clear DMA done */ 140#define DT2821_DDMA 0x1000 /* (R/W) dual DMA */ 141#define DT2821_DS1 0x0800 /* (R/W) DMA select 1 */ 142#define DT2821_DS0 0x0400 /* (R/W) DMA select 0 */ 143#define DT2821_BUFFB 0x0200 /* (R/W) buffer B selected */ 144#define DT2821_SCDN 0x0100 /* (R) scan done */ 145#define DT2821_DACON 0x0080 /* (W) DAC single conversion */ 146#define DT2821_ADCINIT 0x0040 /* (W) A/D initialize */ 147#define DT2821_DACINIT 0x0020 /* (W) D/A initialize */ 148#define DT2821_PRLD 0x0010 /* (W) preload multiplexer */ 149#define DT2821_STRIG 0x0008 /* (W) software trigger */ 150#define DT2821_XTRIG 0x0004 /* (R/W) external trigger enable */ 151#define DT2821_XCLK 0x0002 /* (R/W) external clock enable */ 152#define DT2821_BDINIT 0x0001 /* (W) initialize board */ 153 154static const struct comedi_lrange range_dt282x_ai_lo_bipolar = { 155 4, { 156 BIP_RANGE(10), 157 BIP_RANGE(5), 158 BIP_RANGE(2.5), 159 BIP_RANGE(1.25) 160 } 161}; 162 163static const struct comedi_lrange range_dt282x_ai_lo_unipolar = { 164 4, { 165 UNI_RANGE(10), 166 UNI_RANGE(5), 167 UNI_RANGE(2.5), 168 UNI_RANGE(1.25) 169 } 170}; 171 172static const struct comedi_lrange range_dt282x_ai_5_bipolar = { 173 4, { 174 BIP_RANGE(5), 175 BIP_RANGE(2.5), 176 BIP_RANGE(1.25), 177 BIP_RANGE(0.625) 178 } 179}; 180 181static const struct comedi_lrange range_dt282x_ai_5_unipolar = { 182 4, { 183 UNI_RANGE(5), 184 UNI_RANGE(2.5), 185 UNI_RANGE(1.25), 186 UNI_RANGE(0.625) 187 } 188}; 189 190static const struct comedi_lrange range_dt282x_ai_hi_bipolar = { 191 4, { 192 BIP_RANGE(10), 193 BIP_RANGE(1), 194 BIP_RANGE(0.1), 195 BIP_RANGE(0.02) 196 } 197}; 198 199static const struct comedi_lrange range_dt282x_ai_hi_unipolar = { 200 4, { 201 UNI_RANGE(10), 202 UNI_RANGE(1), 203 UNI_RANGE(0.1), 204 UNI_RANGE(0.02) 205 } 206}; 207 208struct dt282x_board { 209 const char *name; 210 int adbits; 211 int adchan_se; 212 int adchan_di; 213 int ai_speed; 214 int ispgl; 215 int dachan; 216 int dabits; 217}; 218 219static const struct dt282x_board boardtypes[] = { 220 { 221 .name = "dt2821", 222 .adbits = 12, 223 .adchan_se = 16, 224 .adchan_di = 8, 225 .ai_speed = 20000, 226 .dachan = 2, 227 .dabits = 12, 228 }, { 229 .name = "dt2821-f", 230 .adbits = 12, 231 .adchan_se = 16, 232 .adchan_di = 8, 233 .ai_speed = 6500, 234 .dachan = 2, 235 .dabits = 12, 236 }, { 237 .name = "dt2821-g", 238 .adbits = 12, 239 .adchan_se = 16, 240 .adchan_di = 8, 241 .ai_speed = 4000, 242 .dachan = 2, 243 .dabits = 12, 244 }, { 245 .name = "dt2823", 246 .adbits = 16, 247 .adchan_di = 4, 248 .ai_speed = 10000, 249 .dachan = 2, 250 .dabits = 16, 251 }, { 252 .name = "dt2824-pgh", 253 .adbits = 12, 254 .adchan_se = 16, 255 .adchan_di = 8, 256 .ai_speed = 20000, 257 }, { 258 .name = "dt2824-pgl", 259 .adbits = 12, 260 .adchan_se = 16, 261 .adchan_di = 8, 262 .ai_speed = 20000, 263 .ispgl = 1, 264 }, { 265 .name = "dt2825", 266 .adbits = 12, 267 .adchan_se = 16, 268 .adchan_di = 8, 269 .ai_speed = 20000, 270 .ispgl = 1, 271 .dachan = 2, 272 .dabits = 12, 273 }, { 274 .name = "dt2827", 275 .adbits = 16, 276 .adchan_di = 4, 277 .ai_speed = 10000, 278 .dachan = 2, 279 .dabits = 12, 280 }, { 281 .name = "dt2828", 282 .adbits = 12, 283 .adchan_se = 4, 284 .ai_speed = 10000, 285 .dachan = 2, 286 .dabits = 12, 287 }, { 288 .name = "dt2829", 289 .adbits = 16, 290 .adchan_se = 8, 291 .ai_speed = 33250, 292 .dachan = 2, 293 .dabits = 16, 294 }, { 295 .name = "dt21-ez", 296 .adbits = 12, 297 .adchan_se = 16, 298 .adchan_di = 8, 299 .ai_speed = 10000, 300 .dachan = 2, 301 .dabits = 12, 302 }, { 303 .name = "dt23-ez", 304 .adbits = 16, 305 .adchan_se = 16, 306 .adchan_di = 8, 307 .ai_speed = 10000, 308 }, { 309 .name = "dt24-ez", 310 .adbits = 12, 311 .adchan_se = 16, 312 .adchan_di = 8, 313 .ai_speed = 10000, 314 }, { 315 .name = "dt24-ez-pgl", 316 .adbits = 12, 317 .adchan_se = 16, 318 .adchan_di = 8, 319 .ai_speed = 10000, 320 .ispgl = 1, 321 }, 322}; 323 324struct dt282x_private { 325 int ad_2scomp; /* we have 2's comp jumper set */ 326 int da0_2scomp; /* same, for DAC0 */ 327 int da1_2scomp; /* same, for DAC1 */ 328 329 const struct comedi_lrange *darangelist[2]; 330 331 unsigned short ao[2]; 332 333 volatile int dacsr; /* software copies of registers */ 334 volatile int adcsr; 335 volatile int supcsr; 336 337 volatile int ntrig; 338 volatile int nread; 339 340 struct { 341 int chan; 342 unsigned short *buf; /* DMA buffer */ 343 volatile int size; /* size of current transfer */ 344 } dma[2]; 345 int dma_maxsize; /* max size of DMA transfer (in bytes) */ 346 int usedma; /* driver uses DMA */ 347 volatile int current_dma_index; 348 int dma_dir; 349}; 350 351/* 352 * Some useless abstractions 353 */ 354#define chan_to_DAC(a) ((a)&1) 355 356static int prep_ai_dma(struct comedi_device *dev, int dma_index, int n) 357{ 358 struct dt282x_private *devpriv = dev->private; 359 int dma_chan; 360 unsigned long dma_ptr; 361 unsigned long flags; 362 363 if (!devpriv->ntrig) 364 return 0; 365 366 if (n == 0) 367 n = devpriv->dma_maxsize; 368 if (n > devpriv->ntrig * 2) 369 n = devpriv->ntrig * 2; 370 devpriv->ntrig -= n / 2; 371 372 devpriv->dma[dma_index].size = n; 373 dma_chan = devpriv->dma[dma_index].chan; 374 dma_ptr = virt_to_bus(devpriv->dma[dma_index].buf); 375 376 set_dma_mode(dma_chan, DMA_MODE_READ); 377 flags = claim_dma_lock(); 378 clear_dma_ff(dma_chan); 379 set_dma_addr(dma_chan, dma_ptr); 380 set_dma_count(dma_chan, n); 381 release_dma_lock(flags); 382 383 enable_dma(dma_chan); 384 385 return n; 386} 387 388static int prep_ao_dma(struct comedi_device *dev, int dma_index, int n) 389{ 390 struct dt282x_private *devpriv = dev->private; 391 int dma_chan; 392 unsigned long dma_ptr; 393 unsigned long flags; 394 395 devpriv->dma[dma_index].size = n; 396 dma_chan = devpriv->dma[dma_index].chan; 397 dma_ptr = virt_to_bus(devpriv->dma[dma_index].buf); 398 399 set_dma_mode(dma_chan, DMA_MODE_WRITE); 400 flags = claim_dma_lock(); 401 clear_dma_ff(dma_chan); 402 set_dma_addr(dma_chan, dma_ptr); 403 set_dma_count(dma_chan, n); 404 release_dma_lock(flags); 405 406 enable_dma(dma_chan); 407 408 return n; 409} 410 411static void dt282x_disable_dma(struct comedi_device *dev) 412{ 413 struct dt282x_private *devpriv = dev->private; 414 415 if (devpriv->usedma) { 416 disable_dma(devpriv->dma[0].chan); 417 disable_dma(devpriv->dma[1].chan); 418 } 419} 420 421static int dt282x_ns_to_timer(int *nanosec, int round_mode) 422{ 423 int prescale, base, divider; 424 425 for (prescale = 0; prescale < 16; prescale++) { 426 if (prescale == 1) 427 continue; 428 base = 250 * (1 << prescale); 429 switch (round_mode) { 430 case TRIG_ROUND_NEAREST: 431 default: 432 divider = (*nanosec + base / 2) / base; 433 break; 434 case TRIG_ROUND_DOWN: 435 divider = (*nanosec) / base; 436 break; 437 case TRIG_ROUND_UP: 438 divider = (*nanosec + base - 1) / base; 439 break; 440 } 441 if (divider < 256) { 442 *nanosec = divider * base; 443 return (prescale << 8) | (255 - divider); 444 } 445 } 446 base = 250 * (1 << 15); 447 divider = 255; 448 *nanosec = divider * base; 449 return (15 << 8) | (255 - divider); 450} 451 452static void dt282x_munge(struct comedi_device *dev, unsigned short *buf, 453 unsigned int nbytes) 454{ 455 const struct dt282x_board *board = comedi_board(dev); 456 struct dt282x_private *devpriv = dev->private; 457 unsigned int i; 458 unsigned short mask = (1 << board->adbits) - 1; 459 unsigned short sign = 1 << (board->adbits - 1); 460 int n; 461 462 if (devpriv->ad_2scomp) 463 sign = 1 << (board->adbits - 1); 464 else 465 sign = 0; 466 467 if (nbytes % 2) 468 comedi_error(dev, "bug! odd number of bytes from dma xfer"); 469 n = nbytes / 2; 470 for (i = 0; i < n; i++) 471 buf[i] = (buf[i] & mask) ^ sign; 472} 473 474static void dt282x_ao_dma_interrupt(struct comedi_device *dev) 475{ 476 struct dt282x_private *devpriv = dev->private; 477 struct comedi_subdevice *s = dev->write_subdev; 478 void *ptr; 479 int size; 480 int i; 481 482 outw(devpriv->supcsr | DT2821_CLRDMADNE, dev->iobase + DT2821_SUPCSR); 483 484 if (!s->async->prealloc_buf) { 485 dev_err(dev->class_dev, "no buffer in %s\n", __func__); 486 return; 487 } 488 489 i = devpriv->current_dma_index; 490 ptr = devpriv->dma[i].buf; 491 492 disable_dma(devpriv->dma[i].chan); 493 494 devpriv->current_dma_index = 1 - i; 495 496 size = cfc_read_array_from_buffer(s, ptr, devpriv->dma_maxsize); 497 if (size == 0) { 498 dev_err(dev->class_dev, "AO underrun\n"); 499 s->async->events |= COMEDI_CB_OVERFLOW; 500 return; 501 } 502 prep_ao_dma(dev, i, size); 503 return; 504} 505 506static void dt282x_ai_dma_interrupt(struct comedi_device *dev) 507{ 508 struct dt282x_private *devpriv = dev->private; 509 struct comedi_subdevice *s = dev->read_subdev; 510 void *ptr; 511 int size; 512 int i; 513 int ret; 514 515 outw(devpriv->supcsr | DT2821_CLRDMADNE, dev->iobase + DT2821_SUPCSR); 516 517 if (!s->async->prealloc_buf) { 518 dev_err(dev->class_dev, "no buffer in %s\n", __func__); 519 return; 520 } 521 522 i = devpriv->current_dma_index; 523 ptr = devpriv->dma[i].buf; 524 size = devpriv->dma[i].size; 525 526 disable_dma(devpriv->dma[i].chan); 527 528 devpriv->current_dma_index = 1 - i; 529 530 dt282x_munge(dev, ptr, size); 531 ret = cfc_write_array_to_buffer(s, ptr, size); 532 if (ret != size) { 533 s->async->events |= COMEDI_CB_OVERFLOW; 534 return; 535 } 536 devpriv->nread -= size / 2; 537 538 if (devpriv->nread < 0) { 539 dev_info(dev->class_dev, "nread off by one\n"); 540 devpriv->nread = 0; 541 } 542 if (!devpriv->nread) { 543 s->async->events |= COMEDI_CB_EOA; 544 return; 545 } 546#if 0 547 /* clear the dual dma flag, making this the last dma segment */ 548 /* XXX probably wrong */ 549 if (!devpriv->ntrig) { 550 devpriv->supcsr &= ~(DT2821_DDMA); 551 outw(devpriv->supcsr, dev->iobase + DT2821_SUPCSR); 552 } 553#endif 554 /* restart the channel */ 555 prep_ai_dma(dev, i, 0); 556} 557 558static irqreturn_t dt282x_interrupt(int irq, void *d) 559{ 560 struct comedi_device *dev = d; 561 struct dt282x_private *devpriv = dev->private; 562 struct comedi_subdevice *s = dev->read_subdev; 563 struct comedi_subdevice *s_ao = dev->write_subdev; 564 unsigned int supcsr, adcsr, dacsr; 565 int handled = 0; 566 567 if (!dev->attached) { 568 comedi_error(dev, "spurious interrupt"); 569 return IRQ_HANDLED; 570 } 571 572 adcsr = inw(dev->iobase + DT2821_ADCSR); 573 dacsr = inw(dev->iobase + DT2821_DACSR); 574 supcsr = inw(dev->iobase + DT2821_SUPCSR); 575 if (supcsr & DT2821_DMAD) { 576 if (devpriv->dma_dir == DMA_MODE_READ) 577 dt282x_ai_dma_interrupt(dev); 578 else 579 dt282x_ao_dma_interrupt(dev); 580 handled = 1; 581 } 582 if (adcsr & DT2821_ADERR) { 583 if (devpriv->nread != 0) { 584 comedi_error(dev, "A/D error"); 585 s->async->events |= COMEDI_CB_ERROR; 586 } 587 handled = 1; 588 } 589 if (dacsr & DT2821_DAERR) { 590 comedi_error(dev, "D/A error"); 591 s_ao->async->events |= COMEDI_CB_ERROR; 592 handled = 1; 593 } 594#if 0 595 if (adcsr & DT2821_ADDONE) { 596 int ret; 597 unsigned short data; 598 599 data = inw(dev->iobase + DT2821_ADDAT); 600 data &= (1 << board->adbits) - 1; 601 602 if (devpriv->ad_2scomp) 603 data ^= 1 << (board->adbits - 1); 604 ret = comedi_buf_put(s, data); 605 606 if (ret == 0) 607 s->async->events |= COMEDI_CB_OVERFLOW; 608 609 devpriv->nread--; 610 if (!devpriv->nread) { 611 s->async->events |= COMEDI_CB_EOA; 612 } else { 613 if (supcsr & DT2821_SCDN) 614 outw(devpriv->supcsr | DT2821_STRIG, 615 dev->iobase + DT2821_SUPCSR); 616 } 617 handled = 1; 618 } 619#endif 620 cfc_handle_events(dev, s); 621 cfc_handle_events(dev, s_ao); 622 623 return IRQ_RETVAL(handled); 624} 625 626static void dt282x_load_changain(struct comedi_device *dev, int n, 627 unsigned int *chanlist) 628{ 629 struct dt282x_private *devpriv = dev->private; 630 unsigned int i; 631 unsigned int chan, range; 632 633 outw(DT2821_LLE | (n - 1), dev->iobase + DT2821_CHANCSR); 634 for (i = 0; i < n; i++) { 635 chan = CR_CHAN(chanlist[i]); 636 range = CR_RANGE(chanlist[i]); 637 outw(devpriv->adcsr | (range << 4) | chan, 638 dev->iobase + DT2821_ADCSR); 639 } 640 outw(n - 1, dev->iobase + DT2821_CHANCSR); 641} 642 643static int dt282x_ai_timeout(struct comedi_device *dev, 644 struct comedi_subdevice *s, 645 struct comedi_insn *insn, 646 unsigned long context) 647{ 648 unsigned int status; 649 650 status = inw(dev->iobase + DT2821_ADCSR); 651 switch (context) { 652 case DT2821_MUXBUSY: 653 if ((status & DT2821_MUXBUSY) == 0) 654 return 0; 655 break; 656 case DT2821_ADDONE: 657 if (status & DT2821_ADDONE) 658 return 0; 659 break; 660 default: 661 return -EINVAL; 662 } 663 return -EBUSY; 664} 665 666/* 667 * Performs a single A/D conversion. 668 * - Put channel/gain into channel-gain list 669 * - preload multiplexer 670 * - trigger conversion and wait for it to finish 671 */ 672static int dt282x_ai_insn_read(struct comedi_device *dev, 673 struct comedi_subdevice *s, 674 struct comedi_insn *insn, unsigned int *data) 675{ 676 const struct dt282x_board *board = comedi_board(dev); 677 struct dt282x_private *devpriv = dev->private; 678 int ret; 679 int i; 680 681 /* XXX should we really be enabling the ad clock here? */ 682 devpriv->adcsr = DT2821_ADCLK; 683 outw(devpriv->adcsr, dev->iobase + DT2821_ADCSR); 684 685 dt282x_load_changain(dev, 1, &insn->chanspec); 686 687 outw(devpriv->supcsr | DT2821_PRLD, dev->iobase + DT2821_SUPCSR); 688 ret = comedi_timeout(dev, s, insn, dt282x_ai_timeout, DT2821_MUXBUSY); 689 if (ret) 690 return ret; 691 692 for (i = 0; i < insn->n; i++) { 693 outw(devpriv->supcsr | DT2821_STRIG, 694 dev->iobase + DT2821_SUPCSR); 695 696 ret = comedi_timeout(dev, s, insn, dt282x_ai_timeout, 697 DT2821_ADDONE); 698 if (ret) 699 return ret; 700 701 data[i] = 702 inw(dev->iobase + 703 DT2821_ADDAT) & ((1 << board->adbits) - 1); 704 if (devpriv->ad_2scomp) 705 data[i] ^= (1 << (board->adbits - 1)); 706 } 707 708 return i; 709} 710 711static int dt282x_ai_cmdtest(struct comedi_device *dev, 712 struct comedi_subdevice *s, struct comedi_cmd *cmd) 713{ 714 const struct dt282x_board *board = comedi_board(dev); 715 int err = 0; 716 unsigned int arg; 717 718 /* Step 1 : check if triggers are trivially valid */ 719 720 err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW); 721 err |= cfc_check_trigger_src(&cmd->scan_begin_src, 722 TRIG_FOLLOW | TRIG_EXT); 723 err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_TIMER); 724 err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); 725 err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); 726 727 if (err) 728 return 1; 729 730 /* Step 2a : make sure trigger sources are unique */ 731 732 err |= cfc_check_trigger_is_unique(cmd->scan_begin_src); 733 err |= cfc_check_trigger_is_unique(cmd->stop_src); 734 735 /* Step 2b : and mutually compatible */ 736 737 if (err) 738 return 2; 739 740 /* Step 3: check if arguments are trivially valid */ 741 742 err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); 743 744 if (cmd->scan_begin_src == TRIG_FOLLOW) { 745 /* internal trigger */ 746 err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0); 747 } else { 748 /* external trigger */ 749 /* should be level/edge, hi/lo specification here */ 750 err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0); 751 } 752 753 err |= cfc_check_trigger_arg_min(&cmd->convert_arg, 4000); 754 755#define SLOWEST_TIMER (250*(1<<15)*255) 756 err |= cfc_check_trigger_arg_max(&cmd->convert_arg, SLOWEST_TIMER); 757 err |= cfc_check_trigger_arg_min(&cmd->convert_arg, board->ai_speed); 758 err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); 759 760 if (cmd->stop_src == TRIG_COUNT) { 761 /* any count is allowed */ 762 } else { /* TRIG_NONE */ 763 err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0); 764 } 765 766 if (err) 767 return 3; 768 769 /* step 4: fix up any arguments */ 770 771 arg = cmd->convert_arg; 772 dt282x_ns_to_timer(&arg, cmd->flags & TRIG_ROUND_MASK); 773 err |= cfc_check_trigger_arg_is(&cmd->convert_arg, arg); 774 775 if (err) 776 return 4; 777 778 return 0; 779} 780 781static int dt282x_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) 782{ 783 const struct dt282x_board *board = comedi_board(dev); 784 struct dt282x_private *devpriv = dev->private; 785 struct comedi_cmd *cmd = &s->async->cmd; 786 int timer; 787 int ret; 788 789 if (devpriv->usedma == 0) { 790 comedi_error(dev, 791 "driver requires 2 dma channels" 792 " to execute command"); 793 return -EIO; 794 } 795 796 dt282x_disable_dma(dev); 797 798 if (cmd->convert_arg < board->ai_speed) 799 cmd->convert_arg = board->ai_speed; 800 timer = dt282x_ns_to_timer(&cmd->convert_arg, TRIG_ROUND_NEAREST); 801 outw(timer, dev->iobase + DT2821_TMRCTR); 802 803 if (cmd->scan_begin_src == TRIG_FOLLOW) { 804 /* internal trigger */ 805 devpriv->supcsr = DT2821_ERRINTEN | DT2821_DS0; 806 } else { 807 /* external trigger */ 808 devpriv->supcsr = DT2821_ERRINTEN | DT2821_DS0 | DT2821_DS1; 809 } 810 outw(devpriv->supcsr | DT2821_CLRDMADNE | DT2821_BUFFB | DT2821_ADCINIT, 811 dev->iobase + DT2821_SUPCSR); 812 813 devpriv->ntrig = cmd->stop_arg * cmd->scan_end_arg; 814 devpriv->nread = devpriv->ntrig; 815 816 devpriv->dma_dir = DMA_MODE_READ; 817 devpriv->current_dma_index = 0; 818 prep_ai_dma(dev, 0, 0); 819 if (devpriv->ntrig) { 820 prep_ai_dma(dev, 1, 0); 821 devpriv->supcsr |= DT2821_DDMA; 822 outw(devpriv->supcsr, dev->iobase + DT2821_SUPCSR); 823 } 824 825 devpriv->adcsr = 0; 826 827 dt282x_load_changain(dev, cmd->chanlist_len, cmd->chanlist); 828 829 devpriv->adcsr = DT2821_ADCLK | DT2821_IADDONE; 830 outw(devpriv->adcsr, dev->iobase + DT2821_ADCSR); 831 832 outw(devpriv->supcsr | DT2821_PRLD, dev->iobase + DT2821_SUPCSR); 833 ret = comedi_timeout(dev, s, NULL, dt282x_ai_timeout, DT2821_MUXBUSY); 834 if (ret) 835 return ret; 836 837 if (cmd->scan_begin_src == TRIG_FOLLOW) { 838 outw(devpriv->supcsr | DT2821_STRIG, 839 dev->iobase + DT2821_SUPCSR); 840 } else { 841 devpriv->supcsr |= DT2821_XTRIG; 842 outw(devpriv->supcsr, dev->iobase + DT2821_SUPCSR); 843 } 844 845 return 0; 846} 847 848static int dt282x_ai_cancel(struct comedi_device *dev, 849 struct comedi_subdevice *s) 850{ 851 struct dt282x_private *devpriv = dev->private; 852 853 dt282x_disable_dma(dev); 854 855 devpriv->adcsr = 0; 856 outw(devpriv->adcsr, dev->iobase + DT2821_ADCSR); 857 858 devpriv->supcsr = 0; 859 outw(devpriv->supcsr | DT2821_ADCINIT, dev->iobase + DT2821_SUPCSR); 860 861 return 0; 862} 863 864/* 865 * Analog output routine. Selects single channel conversion, 866 * selects correct channel, converts from 2's compliment to 867 * offset binary if necessary, loads the data into the DAC 868 * data register, and performs the conversion. 869 */ 870static int dt282x_ao_insn_read(struct comedi_device *dev, 871 struct comedi_subdevice *s, 872 struct comedi_insn *insn, unsigned int *data) 873{ 874 struct dt282x_private *devpriv = dev->private; 875 876 data[0] = devpriv->ao[CR_CHAN(insn->chanspec)]; 877 878 return 1; 879} 880 881static int dt282x_ao_insn_write(struct comedi_device *dev, 882 struct comedi_subdevice *s, 883 struct comedi_insn *insn, unsigned int *data) 884{ 885 const struct dt282x_board *board = comedi_board(dev); 886 struct dt282x_private *devpriv = dev->private; 887 unsigned short d; 888 unsigned int chan; 889 890 chan = CR_CHAN(insn->chanspec); 891 d = data[0]; 892 d &= (1 << board->dabits) - 1; 893 devpriv->ao[chan] = d; 894 895 devpriv->dacsr |= DT2821_SSEL; 896 897 if (chan) { 898 /* select channel */ 899 devpriv->dacsr |= DT2821_YSEL; 900 if (devpriv->da0_2scomp) 901 d ^= (1 << (board->dabits - 1)); 902 } else { 903 devpriv->dacsr &= ~DT2821_YSEL; 904 if (devpriv->da1_2scomp) 905 d ^= (1 << (board->dabits - 1)); 906 } 907 908 outw(devpriv->dacsr, dev->iobase + DT2821_DACSR); 909 910 outw(d, dev->iobase + DT2821_DADAT); 911 912 outw(devpriv->supcsr | DT2821_DACON, dev->iobase + DT2821_SUPCSR); 913 914 return 1; 915} 916 917static int dt282x_ao_cmdtest(struct comedi_device *dev, 918 struct comedi_subdevice *s, struct comedi_cmd *cmd) 919{ 920 int err = 0; 921 unsigned int arg; 922 923 /* Step 1 : check if triggers are trivially valid */ 924 925 err |= cfc_check_trigger_src(&cmd->start_src, TRIG_INT); 926 err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_TIMER); 927 err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW); 928 err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); 929 err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); 930 931 if (err) 932 return 1; 933 934 /* Step 2a : make sure trigger sources are unique */ 935 936 err |= cfc_check_trigger_is_unique(cmd->stop_src); 937 938 /* Step 2b : and mutually compatible */ 939 940 if (err) 941 return 2; 942 943 /* Step 3: check if arguments are trivially valid */ 944 945 err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); 946 err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg, 5000); 947 err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0); 948 err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); 949 950 if (cmd->stop_src == TRIG_COUNT) { 951 /* any count is allowed */ 952 } else { /* TRIG_NONE */ 953 err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0); 954 } 955 956 if (err) 957 return 3; 958 959 /* step 4: fix up any arguments */ 960 961 arg = cmd->scan_begin_arg; 962 dt282x_ns_to_timer(&arg, cmd->flags & TRIG_ROUND_MASK); 963 err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, arg); 964 965 if (err) 966 return 4; 967 968 return 0; 969 970} 971 972static int dt282x_ao_inttrig(struct comedi_device *dev, 973 struct comedi_subdevice *s, 974 unsigned int trig_num) 975{ 976 struct dt282x_private *devpriv = dev->private; 977 struct comedi_cmd *cmd = &s->async->cmd; 978 int size; 979 980 if (trig_num != cmd->start_src) 981 return -EINVAL; 982 983 size = cfc_read_array_from_buffer(s, devpriv->dma[0].buf, 984 devpriv->dma_maxsize); 985 if (size == 0) { 986 dev_err(dev->class_dev, "AO underrun\n"); 987 return -EPIPE; 988 } 989 prep_ao_dma(dev, 0, size); 990 991 size = cfc_read_array_from_buffer(s, devpriv->dma[1].buf, 992 devpriv->dma_maxsize); 993 if (size == 0) { 994 dev_err(dev->class_dev, "AO underrun\n"); 995 return -EPIPE; 996 } 997 prep_ao_dma(dev, 1, size); 998 999 outw(devpriv->supcsr | DT2821_STRIG, dev->iobase + DT2821_SUPCSR); 1000 s->async->inttrig = NULL; 1001 1002 return 1; 1003} 1004 1005static int dt282x_ao_cmd(struct comedi_device *dev, struct comedi_subdevice *s) 1006{ 1007 struct dt282x_private *devpriv = dev->private; 1008 int timer; 1009 struct comedi_cmd *cmd = &s->async->cmd; 1010 1011 if (devpriv->usedma == 0) { 1012 comedi_error(dev, 1013 "driver requires 2 dma channels" 1014 " to execute command"); 1015 return -EIO; 1016 } 1017 1018 dt282x_disable_dma(dev); 1019 1020 devpriv->supcsr = DT2821_ERRINTEN | DT2821_DS1 | DT2821_DDMA; 1021 outw(devpriv->supcsr | DT2821_CLRDMADNE | DT2821_BUFFB | DT2821_DACINIT, 1022 dev->iobase + DT2821_SUPCSR); 1023 1024 devpriv->ntrig = cmd->stop_arg * cmd->chanlist_len; 1025 devpriv->nread = devpriv->ntrig; 1026 1027 devpriv->dma_dir = DMA_MODE_WRITE; 1028 devpriv->current_dma_index = 0; 1029 1030 timer = dt282x_ns_to_timer(&cmd->scan_begin_arg, TRIG_ROUND_NEAREST); 1031 outw(timer, dev->iobase + DT2821_TMRCTR); 1032 1033 devpriv->dacsr = DT2821_SSEL | DT2821_DACLK | DT2821_IDARDY; 1034 outw(devpriv->dacsr, dev->iobase + DT2821_DACSR); 1035 1036 s->async->inttrig = dt282x_ao_inttrig; 1037 1038 return 0; 1039} 1040 1041static int dt282x_ao_cancel(struct comedi_device *dev, 1042 struct comedi_subdevice *s) 1043{ 1044 struct dt282x_private *devpriv = dev->private; 1045 1046 dt282x_disable_dma(dev); 1047 1048 devpriv->dacsr = 0; 1049 outw(devpriv->dacsr, dev->iobase + DT2821_DACSR); 1050 1051 devpriv->supcsr = 0; 1052 outw(devpriv->supcsr | DT2821_DACINIT, dev->iobase + DT2821_SUPCSR); 1053 1054 return 0; 1055} 1056 1057static int dt282x_dio_insn_bits(struct comedi_device *dev, 1058 struct comedi_subdevice *s, 1059 struct comedi_insn *insn, 1060 unsigned int *data) 1061{ 1062 if (comedi_dio_update_state(s, data)) 1063 outw(s->state, dev->iobase + DT2821_DIODAT); 1064 1065 data[1] = inw(dev->iobase + DT2821_DIODAT); 1066 1067 return insn->n; 1068} 1069 1070static int dt282x_dio_insn_config(struct comedi_device *dev, 1071 struct comedi_subdevice *s, 1072 struct comedi_insn *insn, 1073 unsigned int *data) 1074{ 1075 struct dt282x_private *devpriv = dev->private; 1076 unsigned int chan = CR_CHAN(insn->chanspec); 1077 unsigned int mask; 1078 int ret; 1079 1080 if (chan < 8) 1081 mask = 0x00ff; 1082 else 1083 mask = 0xff00; 1084 1085 ret = comedi_dio_insn_config(dev, s, insn, data, mask); 1086 if (ret) 1087 return ret; 1088 1089 devpriv->dacsr &= ~(DT2821_LBOE | DT2821_HBOE); 1090 if (s->io_bits & 0x00ff) 1091 devpriv->dacsr |= DT2821_LBOE; 1092 if (s->io_bits & 0xff00) 1093 devpriv->dacsr |= DT2821_HBOE; 1094 1095 outw(devpriv->dacsr, dev->iobase + DT2821_DACSR); 1096 1097 return insn->n; 1098} 1099 1100static const struct comedi_lrange *const ai_range_table[] = { 1101 &range_dt282x_ai_lo_bipolar, 1102 &range_dt282x_ai_lo_unipolar, 1103 &range_dt282x_ai_5_bipolar, 1104 &range_dt282x_ai_5_unipolar 1105}; 1106 1107static const struct comedi_lrange *const ai_range_pgl_table[] = { 1108 &range_dt282x_ai_hi_bipolar, 1109 &range_dt282x_ai_hi_unipolar 1110}; 1111 1112static const struct comedi_lrange *opt_ai_range_lkup(int ispgl, int x) 1113{ 1114 if (ispgl) { 1115 if (x < 0 || x >= 2) 1116 x = 0; 1117 return ai_range_pgl_table[x]; 1118 } else { 1119 if (x < 0 || x >= 4) 1120 x = 0; 1121 return ai_range_table[x]; 1122 } 1123} 1124 1125static const struct comedi_lrange *const ao_range_table[] = { 1126 &range_bipolar10, 1127 &range_unipolar10, 1128 &range_bipolar5, 1129 &range_unipolar5, 1130 &range_bipolar2_5 1131}; 1132 1133static const struct comedi_lrange *opt_ao_range_lkup(int x) 1134{ 1135 if (x < 0 || x >= 5) 1136 x = 0; 1137 return ao_range_table[x]; 1138} 1139 1140enum { /* i/o base, irq, dma channels */ 1141 opt_iobase = 0, opt_irq, opt_dma1, opt_dma2, 1142 opt_diff, /* differential */ 1143 opt_ai_twos, opt_ao0_twos, opt_ao1_twos, /* twos comp */ 1144 opt_ai_range, opt_ao0_range, opt_ao1_range, /* range */ 1145}; 1146 1147static int dt282x_grab_dma(struct comedi_device *dev, int dma1, int dma2) 1148{ 1149 struct dt282x_private *devpriv = dev->private; 1150 int ret; 1151 1152 devpriv->usedma = 0; 1153 1154 if (!dma1 && !dma2) 1155 return 0; 1156 1157 if (dma1 == dma2 || dma1 < 5 || dma2 < 5 || dma1 > 7 || dma2 > 7) 1158 return -EINVAL; 1159 1160 if (dma2 < dma1) { 1161 int i; 1162 i = dma1; 1163 dma1 = dma2; 1164 dma2 = i; 1165 } 1166 1167 ret = request_dma(dma1, "dt282x A"); 1168 if (ret) 1169 return -EBUSY; 1170 devpriv->dma[0].chan = dma1; 1171 1172 ret = request_dma(dma2, "dt282x B"); 1173 if (ret) 1174 return -EBUSY; 1175 devpriv->dma[1].chan = dma2; 1176 1177 devpriv->dma_maxsize = PAGE_SIZE; 1178 devpriv->dma[0].buf = (void *)__get_free_page(GFP_KERNEL | GFP_DMA); 1179 devpriv->dma[1].buf = (void *)__get_free_page(GFP_KERNEL | GFP_DMA); 1180 if (!devpriv->dma[0].buf || !devpriv->dma[1].buf) 1181 return -ENOMEM; 1182 1183 devpriv->usedma = 1; 1184 1185 return 0; 1186} 1187 1188/* 1189 options: 1190 0 i/o base 1191 1 irq 1192 2 dma1 1193 3 dma2 1194 4 0=single ended, 1=differential 1195 5 ai 0=straight binary, 1=2's comp 1196 6 ao0 0=straight binary, 1=2's comp 1197 7 ao1 0=straight binary, 1=2's comp 1198 8 ai 0=±10 V, 1=0-10 V, 2=±5 V, 3=0-5 V 1199 9 ao0 0=±10 V, 1=0-10 V, 2=±5 V, 3=0-5 V, 4=±2.5 V 1200 10 ao1 0=±10 V, 1=0-10 V, 2=±5 V, 3=0-5 V, 4=±2.5 V 1201 */ 1202static int dt282x_attach(struct comedi_device *dev, struct comedi_devconfig *it) 1203{ 1204 const struct dt282x_board *board = comedi_board(dev); 1205 struct dt282x_private *devpriv; 1206 struct comedi_subdevice *s; 1207 int ret; 1208 int i; 1209 1210 ret = comedi_request_region(dev, it->options[0], DT2821_SIZE); 1211 if (ret) 1212 return ret; 1213 1214 outw(DT2821_BDINIT, dev->iobase + DT2821_SUPCSR); 1215 i = inw(dev->iobase + DT2821_ADCSR); 1216 1217 if (((inw(dev->iobase + DT2821_ADCSR) & DT2821_ADCSR_MASK) 1218 != DT2821_ADCSR_VAL) || 1219 ((inw(dev->iobase + DT2821_CHANCSR) & DT2821_CHANCSR_MASK) 1220 != DT2821_CHANCSR_VAL) || 1221 ((inw(dev->iobase + DT2821_DACSR) & DT2821_DACSR_MASK) 1222 != DT2821_DACSR_VAL) || 1223 ((inw(dev->iobase + DT2821_SUPCSR) & DT2821_SUPCSR_MASK) 1224 != DT2821_SUPCSR_VAL) || 1225 ((inw(dev->iobase + DT2821_TMRCTR) & DT2821_TMRCTR_MASK) 1226 != DT2821_TMRCTR_VAL)) { 1227 dev_err(dev->class_dev, "board not found\n"); 1228 return -EIO; 1229 } 1230 /* should do board test */ 1231 1232 if (it->options[opt_irq] > 0) { 1233 ret = request_irq(it->options[opt_irq], dt282x_interrupt, 0, 1234 dev->board_name, dev); 1235 if (ret == 0) 1236 dev->irq = it->options[opt_irq]; 1237 } 1238 1239 devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); 1240 if (!devpriv) 1241 return -ENOMEM; 1242 1243 if (dev->irq) { 1244 ret = dt282x_grab_dma(dev, it->options[opt_dma1], 1245 it->options[opt_dma2]); 1246 if (ret < 0) 1247 return ret; 1248 } 1249 1250 ret = comedi_alloc_subdevices(dev, 3); 1251 if (ret) 1252 return ret; 1253 1254 s = &dev->subdevices[0]; 1255 1256 /* ai subdevice */ 1257 s->type = COMEDI_SUBD_AI; 1258 s->subdev_flags = SDF_READABLE | 1259 ((it->options[opt_diff]) ? SDF_DIFF : SDF_COMMON); 1260 s->n_chan = 1261 (it->options[opt_diff]) ? board->adchan_di : board->adchan_se; 1262 s->insn_read = dt282x_ai_insn_read; 1263 s->maxdata = (1 << board->adbits) - 1; 1264 s->range_table = 1265 opt_ai_range_lkup(board->ispgl, it->options[opt_ai_range]); 1266 devpriv->ad_2scomp = it->options[opt_ai_twos]; 1267 if (dev->irq) { 1268 dev->read_subdev = s; 1269 s->subdev_flags |= SDF_CMD_READ; 1270 s->len_chanlist = 16; 1271 s->do_cmdtest = dt282x_ai_cmdtest; 1272 s->do_cmd = dt282x_ai_cmd; 1273 s->cancel = dt282x_ai_cancel; 1274 } 1275 1276 s = &dev->subdevices[1]; 1277 1278 s->n_chan = board->dachan; 1279 if (s->n_chan) { 1280 /* ao subsystem */ 1281 s->type = COMEDI_SUBD_AO; 1282 s->subdev_flags = SDF_WRITABLE; 1283 s->insn_read = dt282x_ao_insn_read; 1284 s->insn_write = dt282x_ao_insn_write; 1285 s->maxdata = (1 << board->dabits) - 1; 1286 s->range_table_list = devpriv->darangelist; 1287 devpriv->darangelist[0] = 1288 opt_ao_range_lkup(it->options[opt_ao0_range]); 1289 devpriv->darangelist[1] = 1290 opt_ao_range_lkup(it->options[opt_ao1_range]); 1291 devpriv->da0_2scomp = it->options[opt_ao0_twos]; 1292 devpriv->da1_2scomp = it->options[opt_ao1_twos]; 1293 if (dev->irq) { 1294 dev->write_subdev = s; 1295 s->subdev_flags |= SDF_CMD_WRITE; 1296 s->len_chanlist = 2; 1297 s->do_cmdtest = dt282x_ao_cmdtest; 1298 s->do_cmd = dt282x_ao_cmd; 1299 s->cancel = dt282x_ao_cancel; 1300 } 1301 } else { 1302 s->type = COMEDI_SUBD_UNUSED; 1303 } 1304 1305 s = &dev->subdevices[2]; 1306 /* dio subsystem */ 1307 s->type = COMEDI_SUBD_DIO; 1308 s->subdev_flags = SDF_READABLE | SDF_WRITABLE; 1309 s->n_chan = 16; 1310 s->insn_bits = dt282x_dio_insn_bits; 1311 s->insn_config = dt282x_dio_insn_config; 1312 s->maxdata = 1; 1313 s->range_table = &range_digital; 1314 1315 return 0; 1316} 1317 1318static void dt282x_detach(struct comedi_device *dev) 1319{ 1320 struct dt282x_private *devpriv = dev->private; 1321 1322 if (dev->private) { 1323 if (devpriv->dma[0].chan) 1324 free_dma(devpriv->dma[0].chan); 1325 if (devpriv->dma[1].chan) 1326 free_dma(devpriv->dma[1].chan); 1327 if (devpriv->dma[0].buf) 1328 free_page((unsigned long)devpriv->dma[0].buf); 1329 if (devpriv->dma[1].buf) 1330 free_page((unsigned long)devpriv->dma[1].buf); 1331 } 1332 comedi_legacy_detach(dev); 1333} 1334 1335static struct comedi_driver dt282x_driver = { 1336 .driver_name = "dt282x", 1337 .module = THIS_MODULE, 1338 .attach = dt282x_attach, 1339 .detach = dt282x_detach, 1340 .board_name = &boardtypes[0].name, 1341 .num_names = ARRAY_SIZE(boardtypes), 1342 .offset = sizeof(struct dt282x_board), 1343}; 1344module_comedi_driver(dt282x_driver); 1345 1346MODULE_AUTHOR("Comedi http://www.comedi.org"); 1347MODULE_DESCRIPTION("Comedi low-level driver"); 1348MODULE_LICENSE("GPL"); 1349